內核雜七雜八的基礎知識

內核通知鏈

  • 內核子系統之間事件的通知,一般用內核通知鏈來實現。通知鏈只能在內核子系統之間使用,不能在內核與用戶控件之間進行時間的通知。
  • 通知鏈是一個函數鏈表,鏈表上的每一個節點都註冊了一個函數,當某個事件發生時,鏈表上所有節點對應的函數都會被執行,屬於訂閱者-發佈者模型。
  • 通知鏈有四種類型:
    //1. 原始通知鏈(Atomic notifier chains):回調函數只能在中斷上下文中運行,不允許阻塞(爲什麼還不清楚)? 對應鏈表結構體: 
       struct atomic_notifier_head
       {
            spinlock_tlock;
            structnotifier_block *head;
       };
    //2. 可阻塞通知鏈(Blocking notifier chains):回調函數在進程上下文中運行,允許阻塞。對應鏈表結構體: 
       struct blocking_notifier_head
       {
            structrw_semaphore rwsem;
            structnotifier_block *head;
       };
    //3. 原始通知鏈(Raw notifier chains): 回調函數沒有限制,所有所和保護機制都由調用者維護。對應鏈表結構體: 
       struct raw_notifier_head
       {
            structnotifier_block *head;
       };
    //4. SRCU通知鏈(SRCU notifier chains):可阻塞通知鏈的一種變體,對應鏈表結構體:
       struct srcu_notifier_head
       {
            struct mutexmutex;
            struct srcu_struct srcu;
            struct notifier_block *head;
       };
    //這四者的核心結構都是notifier_block,其定義如下:
    struct notifier_block
    {
        //要執行的函數指針
        int(*notifier_call)(struct notifier_block *, unsigned long, void*);
        //下一個函數
        structnotifier_block *next;
        //優先級,同一條鏈上,notifier_block是按照優先級排列的。
        intpriority;
    };
通知鏈的一般使用流程:

1. 通知者定義通知鏈。
2. 被通知者在某個通知鏈上通過notifier_chain_register註冊回調函數。
3. 通知者檢測到/自身產生某事件後,調用notifier_call_chain, 這個函數內部調用對應通知鏈中的所有函數。


每cpu變量

多處理器

多處理器的分類

  • 非對稱多處理器(Asymmetric multiprocessing, AMP)
    每個cpu內核運行一個獨立的操作系統,或同一操作系統的獨立實例。在AMP模式下只有一個主處理器控制系統,其他處理器要麼從主處理器出獲取要執行的代碼,要麼就是有預定義好的代碼,處理器之間是主從關係。
  • 對稱多處理器(Symmetric multiprocessing, SMP)
    每一個處理器可以在執行某個系統中的所有任務,所有處理器是對等的。也就是說,一個操作系統的實例可以同時管理所有cpu內核,且應用並不綁定某一個內核。
  • 混合多處理器(Bound multiprocessing, BMP)
    一個操作系統實例可以同時管理所有cpu內核,但每個應用被鎖定在某個指定的核心上。

多核技術

  • 多核技術實際上就是SMP技術,只不過SMP沒說多個處理器如何封裝,多核技術是把多個處理器封裝到同一個芯片中(Chip MultiProcessor, CMP)。
  • 處理器主頻的提高會引起更大的能耗和更多的散熱,而多核來說總體性能要不提高主頻來的好。
  • CMP給操作系統帶來一些問題,如多線程如何並行修改共享數據(單線程在同一時刻始終是在某個cpu上執行的,所以單線程在任何時候東不存在同步問題),還有多處理器的進程調度(每個進程應該在哪個cpu上調度,進程在cpu之間何時應該遷移,遷移導致的高速緩存命中下降問題等。2.4內核中所有進程都是在一個共享隊列上的,導致競爭激烈,後來每個cpu都有一個進程隊列了)等。

SMP的分類

  • SMP系統根據系統內部件連接方式不同,分爲:基於總線的SMP,基於交叉開關的SMP和基於多級交換網絡的SMP,這裏主要說一下基於總線的SMP。
  • 基於總線的SMP,是系統的所有部件都掛在同一組系統總線上,如果需要內存訪問的時候,處理器先要檢查系統總線是否空閒,如果總線繁忙就要一直等待到總線空閒,會浪費不少cpu時間,隨着處理器個數的增加,系統整機性能必定會急劇下降,總線SMP系統最大的瓶頸在於其總線帶寬。
  • 總線SMP的改進方法,就是在每個處理器上(一般是內部)都加上高速緩存,這樣處理器的很多讀寫請求在cache中就可以滿足,以減少總線的請求次數。
  • 每當處理器訪問一個字長的數據時,與該數據相鄰的整個數據塊都會被讀取到高速緩存中。高速緩存每一個數據塊都帶有一個讀寫標誌位,當該標誌位爲可讀寫時,該數據塊的副本可以同時存在於幾個處理器的高速緩存中,當該標誌位可讀寫時,則該數據塊可能不允許同時存在於多個高速緩存中。當一個處理器修改一個存在於多個處理器的多個高速緩存的數據時,系統總線監視器會檢測到這個寫操作,然後向總線上其他處理器高速緩存發出通知,如果此時其他高速緩存中的數據是乾淨的(與當前內存一致),則高速緩存直接丟棄當前cache副本,下次讀取的時候,從內存重新獲取數據;如果高速緩存中數據是髒的,則必須在當前寫操作執行前,將數據會寫會內存。高速緩存是通過硬件實現的,有具體的高速緩存傳輸協議,我們只需要知道,系統訪問內存時,會先去緩存找,內存一次讀取32/64字節到緩存。
  • 給處理器加緩存會很大程度上減緩內存訪問瓶頸,但內存訪問始終還是瓶頸,有的系統,爲每個處理器設置了私有內存來減少內存訪問瓶頸,但這一版需要系統/程序的適配,使用並不廣泛。

商用服務器主要三大體系:SMP、NUMA、MPP

  • 目前的商用服務器從系統結構來看可大體分爲三類:對稱多處理器結構(SMP, Symmeric Multi-Processor),非一致存儲訪問結構(NUMA, Non-Uniform Memory Access),海量並行處理結構(MMP, Massive Parallel Processing)。
  • SMP服務器:服務器中多個cpu對稱工作,無從屬關係,各cpu共享相同的物理內存,每個cpu的訪問權限和代價相同,因此SMP也被成爲一直存儲器訪問呢結構(UMA, Uniform Memory Access)。SMP服務器的主要特徵是共享,這種特徵導致其擴展能力是否有限,每一個共享緩解都可能造成SMP服務器擴展時的瓶頸,而最受限制的的則是內存,由於每個cpu必須通過相同的內存總線訪問資源,因此隨着cpu數量的增加,內存訪問衝突將迅速增加,cpu數量是有上限的。實驗證明,SMP服務器cpu利用率最好的情況是2-4個cpu。
  • NUMA服務器:具有多個cpu模塊,每個cpu模塊由多個cpu組成,並有獨立的本地內存,IO槽等。其節點之間可以通過互聯模塊進行連接和信息交互,因此每個cpu也是可以訪問整個系統的內存的(MPP不能,這是二者區別)。但由於本地內存的訪問速度遠遠高於遠端內存的訪問速度(非一致存儲訪問的名字就是這麼由來的),開發應用時要儘量少的在cpu之間交互信息。利用NUMA技術可以在一個物理服務器內擴展上百個CPU。其缺陷在於,由於訪問速度的不一致,導致系統性能無法線性增加。
  • MMP服務器:MMP服務器是由多個SMP服務器通過一定的結點互聯網絡進行連接並協同工作的,從用戶角度看是一個服務器系統,實際上是多個SMP服務器通過結點互聯網絡工程的,每個節點只能訪問自己的本地資源,是一種完全無共享結構,因此擴展能力最好,理論上無限制,這種服務器類似於集羣。在NUMA上,訪問遠程內存的時候cpu必須忙等,而MPP上是通過遠程通信是通過IO實現的,所以cpu不需要忙等,可以執行其他任。

存儲體系

現在計算機系統的存儲系統是分層的,主要有六個層次:
1. CPU + 寄存器
2. 1級高速緩存(On-chip L1 Cache,cpu的片上緩存,一般由static RAM組成,size較小,如16KB)
3. 二級高速緩存(Off-chip L2 Cache,一般由static RAM組成,size較大,如2MB)
4. 主存(一般由Dynamic RAM組成,幾百MB到幾GB不等)
5. 本地磁盤(目前已有TB級)
6. Remote disk(網絡存儲,分佈式文件系統)
決定分層的因素主要是:容量,價格和訪問速度,越往上層,訪問速度越快,容量越小,單位價格越貴。

SRAM/DRAM

SRAM(static RAM, 靜態隨機存儲器),特點是工作速度快,只要電源不撤除,寫入SRAM的信息就不會消失,不需要刷新電路,同時在讀出時不破壞原來存放的信息,已經寫入可多次讀出,但集成度較低,功耗較大,一般用作計算機中的高速緩存。
DRAM(dynamic RAM,動態隨機存儲器),它是利用場效應管的柵極對其襯底間的分佈電榮充電來保存信息,以電容兩端電壓來表示1/0,DRAM的每個存儲單元所需的場效應管較少,故集成度較高,功耗也較低,缺點是會漏電,一般1~2ms左右就要刷新一次來防止信息丟失,故一般用作計算機中的主存(集成度高,功耗低)。

存儲體系中的cache

  • 緩存可以引用在存儲體系的任意兩層之間,只要覺得兩層速度差異較大,就可以考慮用緩存來解決。
  • 一般而言,cache是按照cache line來組織的,當訪問主存儲器的時候,如果數據/指令位於cache line中,稱之爲cache hit;如果數據不在cache中,稱爲cache miss,這種情況下,需要從外部的主存中加載數據/指令到cache中來。如果cache miss的時候cache已滿,還要考慮替換算法。
  • cache miss的三種情況:
    1. 在系統初始化時,cache中沒有數據,此時稱這個cache是cold cache,這時的miss稱爲cold miss。
    2. 加載cache line的時候有兩種策略,一是主存中的數據可以放在任何一個cache line中,這種方法的問題是判斷cache hit的開銷太大,需要掃描整個cache。因此實際中,制定的主存數據只能加載到cache中的一個subset中。因此產生了另外一種cache miss叫conflict miss,即當前cache中雖然有空閒的cache line,但由於主存單元對應的哪個subset已經滿了,此時叫conflict miss。
    3. 程序有時會在若干個指令中循環,這個循環中有可能不斷地訪問一個/多個數據塊,這些數據塊就叫做這個循環過程的working set(工作集),當working set的大小大於cache的大小,則就會產生capacity miss。加大cache或減小working set的的大小是解決capacity miss的唯一方法。
  • 對於L1/L2緩存來說,cache的管理策略是由硬件邏輯來管控的。
  • TLB和cpu緩存用的都是cache,只是一般subset不同。

寫操作帶來的問題

對於寫操作,也存在cache hit 和cache miss的問題:
在cache hit時的三種策略:
1. write through(通寫):CPU向cache寫入數據時,同時也想主存寫入一份,等到主存寫完畢纔可進行下一步(通常這種用法和不用寫緩存差不多了)
2. 帶write buffer的write through:cpu向cache寫入的時候,也是需要同時寫入主存的,只不過寫入主存這個任務並不由cpu直接實現,cpu把數據直接寫入write buffer,然後就可以執行下一步了,後續會有其他硬件處理write buffer->主存的步驟。這樣的目的是提速很多。
3. write back(回寫):cpu只是將數據寫入cache,並不同步到主存,優點是cpu執行效率提高,缺點是實現技術比較複雜。

在cache miss時的兩種策略:
1. no-write-allocate cache:在write cache miss時,直接將數據寫入主存而沒有cache操作,一般而言,write through(通寫)會採用no-write-allocate cache策略。
2. write allocate cache:當write cache miss時,分配cache line,並將數據從主存讀入,之後再進行數據更新的動作,一般而言,write back的cache會採用write allocate的策略。

cache的命中

  • 假定一個cpu中年的數據cache爲16K, cache line = 32字節。那麼16K的cache是由512條cache line組成的。由於每條cache line = 32 = 2^5,所以cache塊內偏移用5個字節表示即可。
  • 這樣每一條cache line 可表示爲 |Tag|VA|Offset|Data|,其中Tag + VA + Offset = 4字節,Data = 32字節,其中Data是這個cache line中存放的數據,Offset就是我們前面說的cache塊內偏移,大小爲=5bit,用這個Offset可以定位一個cache line中的任何一個字節。Tag + VA = 27字節,根據不同的緩存算法,二者長度的分配不同。
  • 在所有的cache算法中,主存的分塊大小和cache的分塊大小都是相同的,故這裏將主存也是分爲32字節一塊的,故主存中共有2^27個塊。前面說到的Tag是用來區分當前主存中的地址塊應該映射到緩存的哪個組中的,VA用來區分是組中的哪個塊的。

全相連算法

  • 在這種情況下cache被分爲1組,每組512個塊。此時Tag = 27, VA = 0, Offset = 5。
  • 主存的任意地址塊可以映射到cache的任意地址塊,此時主存中的2^27個塊可以映射到cache中的512個組中的任意一組。所以整個Tag就用27bit來表示,當前這個cache中存的是主存中的哪一個塊。
  • 此種映射的優點在於,cache miss最小,但每次cache查找都要遍歷512個塊,效率低。

直接映射算法

  • 在這種情況下cache被分爲512組,每組1個塊。此時Tag = 18,VA = 9, Offset =5。
  • 主存的地址x所在的地址塊Y(=x/32),只能被映射到Y%512這個組中,故每組中有2^18個可能的地址塊,所以Tag用18位即可區分某個cache爲哪個主存地址塊的緩存。VA用9位,表示當前主存的緩存在哪個組中,正好代表512個組之一。Offset同樣是每個cache塊中的偏移。
  • 此種映射的優點在於,速度快,因爲根據VA計算出當前主存地址塊存放的唯一組,而此組中只有唯一cache塊,故可以直接定位到具體cache塊,缺點就在於cache miss最高。

組相連算法

  • 這裏以cache 被分爲8組,每組64個塊爲例。此時Tag = 24, VA = 3, Offset = 5。
  • 組相連算法是前二者的這種,512條cache line分成8組,每組64條。主存地址x所在的地址塊Y(=x/32),只能被映射到Y%8這個組中,故每組中有2^24個可能的地址塊,所以Tag用24位來區分某個cache爲哪個主存地址塊的緩存。VA用3位,表示當前主存的緩存在哪個組中,正好代表8個組之一。Offset同樣是每個cache塊中的偏移。
  • 此種映射中和了前兩種,對每次cache的查找,需要在64個塊中匹配,但同時也提高了cache的命中率。

物理地址/虛擬地址

當cpu發出地址訪問的時候,從cpu出去的地址是虛擬地址,經過MMU的映射,最終變成物理地址,此時的問題是:
1. 我們用虛擬地址還是物理地址來尋找cache set?
2. 找到cache set後,用虛擬地址還是物理地址來匹配cache line?
因此引出三種解決方案:
1. VIVT(Virtual index Virtual tag),尋找cache set的index(index就是前面的VA字段,是用來找到當前塊對應的cache組的,cache set相當於cache組的意思)和匹配cache line的tag都是使用物理地址。其好處是,匹配具有唯一性,但由於開啓MMU後,cpu輸出的已經是虛擬地址了,此時MMU譯碼需要消耗一定的時間。
2. PIPT(Physical index Phsical tag),尋找cache set的index和匹配cache line的tag都是使用虛擬地址。其好處是,cpu直接輸出虛擬地址,省略了譯碼的時間,但匹配可能不具有唯一性,可能存在cache ambiguity和cache alias的問題。
3. VIPT(Virtaul index Physical tag),尋找cacheset的index使用虛擬地址,而匹配cache line的tag使用的是物理地址,這種方法是二者的這種,不會存在cache ambiguity問題,可能也會存在cache alise的問題,但是可以避過的。

每cpu變量

  • 多核的情況下,cpu是同時併發運行的,但多個cpu共同使用其他的硬件資源,因此需要解決多個cpu之間的同步問題。每cpu變量(per-cpu-variable)是內核中一種重要的同步機制,爲每個cpu構造一個變量的副本,這樣多個cpu互相操作各自的副本,互不干涉。current_task(當前進程)就是一個每cpu變量。
  • 每cpu變量的特點:
    1. 用於多個cpu之間的同步,如果是單核結構,沒有必要使用每cpu變量。
    2. 每cpu變量不能解決由中斷或延遲函數導致的同步問題。
    3. 訪問每cpu變量的時候,一定要確保關閉進程搶佔,否則一個進程被強佔後可能會更換cpu運行,會導致每cpu變量的引用錯誤。
  • per cpu變量不是通過普通的數組實現的,因爲有cpu cache,如果用數組來實現,很可能每個cpu都將這個數組裝入了cache,每個cpu修改了其內容,都會導致cache失效,頻繁的失效會導致性能急劇下降(如果以cache line大小分開,應該就ok了)。

參考資料

[1]. http://server.51cto.com/sCollege-198840.htm
[2]. http://publish.it168.com/2007/0124/20070124003601.shtml
[3]. http://blog.163.com/huang_bp/blog/static/123119837200911305045437/
[4]. http://www.wowotech.net/basic_subject/memory-hierarchy.html
[5]. http://blog.sina.com.cn/s/blog_62e4d8800100mosm.html
[6]. http://www.wowotech.net/linux_kenrel/per-cpu.html
[7]. http://blog.chinaunix.net/uid-26817832-id-3244916.html
[8]. http://blog.chinaunix.net/uid-10701701-id-91727.html
[9]. http://blog.csdn.net/hsly_support/article/details/7664689
[10]. 多處理器系統中的線程調度研究,範光雄,電子科技大學

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章