歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux管理 >> Linux維護

詳解Linux 2.6內核新變化(2)

  虛擬內存的變化

  從虛擬內存的角度來看,新內核融合了 Rik van Riel 的 r-map (反向映射,reverse mapping)技術,將顯著改善虛擬內存 在一定程度負載下的性能。

  為了理解反向映射技術,讓我們來首先簡單了解 Linux 虛擬內存系統的一些基本原理。

  Linux 內核工作於虛擬內存模式:每一個虛擬頁對應一個相應的系統內存的物理頁。虛擬頁和物理頁之間的地址轉換由硬件的頁表來完成。對於一個特定的虛擬頁,根據一條頁表記錄可以找到對應的物理頁,或者是頁無法找到的提示(說明存在一個頁錯誤)。但是這種"虛擬到物理"的頁映射不是總是一一對應的:多個虛擬頁(被不同的進程共享的頁)有可能指向同一個物理頁。在這種情況下,每個共享進程的頁記錄將有指向對應物理頁的映射。如果有類似這樣的情況,當內核想要釋放特定的物理頁時,事情會變得復雜,因為它必須遍歷所有的進程頁表記錄來查找指向這個物理頁的引用;它只能在引用數達到0時才能釋放這個物理頁,因為它沒有別的辦法可以知道是不是還存在實際指向這個頁的引用。這樣當負載較高時會讓虛擬內存變得非常慢。

  反向地址映射補丁通過在結構頁引入一個叫做 pte_chain 的數據結構(物理頁結構)來解決這一問題。pte_chain 是一個指向頁的 PTE 的簡單鏈接列表,可以返回特定的被引用頁的 PTE 列表。頁釋放一下子變得非常簡單了。 不過,在這種模式中存在一個指針開銷。系統中的每一個結構頁都必須有一個額外的用於 pte_chain 的結構。在一個256M內存的系統中,有64K個物理頁,這樣就需要有 64KB * (sizeof(struct pte_chain)) 的內存被分配用於 pte_chain 的結構――一個很可觀的數字。

  有一些可以解決這個問題的技術,包括從結構頁中刪掉 wait_queue_head_t 域(用於對頁的獨占訪問)。因為這個等待隊列極少用到,所以在 rmap 補丁中實現了一個更小的隊列,通過哈希隊列來找到正確的等待隊列。

  盡管如此,rmap 的性能――尤其是處於高負載的高端系統――相對於2.4內核的虛擬內存系統還是有了顯著的提高。

  Linux 2.6的驅動程序移植

  2.6內核給驅動程序開發人員帶來了一系列非常有意義的變化。本節重點介紹將驅動程序從2.4內核移植到2.6內核的一些重要方面。

  首先,相對於2.4來說,改進了內核編譯系統,從而獲得更快的編譯速度。加入了改進的圖形化工具:make xconfig(需要Qt庫)和make gconfig(需要GTK庫)。

  以下是2.6編譯系統的一些亮點:

  當使用make時自動創建 arch-zImage 和模塊

  使用 make -jN 可以進行並行的 make

  make 默認的不是冗余方式(可以通過設置 KBUILD_VERBOSE=1 或者使用 make V=1來設置為冗余方式)

  make subdir/ 將編譯 subdir/ 及其子目錄下的所有文件

  make help 將提供 make 目標支持

  在任何一個階段都不需要再運行 make dep

  內核模塊加載器也在2.5中完全被重新實現,這意味著模塊編譯機制相對於2.4有了很大不同。需要一組新的模塊工具來完成模塊的加載和缷載 (他們的下載鏈接可以在參考資料中找到),原來的2.4所用的 makefile 在2.6下不能再用。

  新的內核模塊加載器是由 Rusty Russel 開發的。它使用內核編譯機制,產生一個 .ko(內核目標文件,kernel object)模塊目標文件而不是一個 .o 模塊目標文件。內核編譯系統首先編譯這些模塊,並將其連接成為 vermagic.o。這一過程在目標模塊創建了一個特定部分,以記錄使用的編譯器版本號,內核版本號,是否使用內核搶占等信息。

  現在讓我們來看一個例子,分析一下新的內核編譯系統如何來編譯並加載一個簡單的模塊。這個模塊是一個“hello world”模塊,代碼和2.4模塊代碼基本類似,只是 module_init 和 module_exit 要換成 init_module 和 cleanup_module (內核2.4.10模塊已經使用這種機制)。這個模塊命名為 hello.c,Makefile 文件如下:

  清單 3. 驅動程序 makefile 文件示例

  

KERNEL_SRC = /usr/src/linux
    SUBDIR = $(KERNEL_SRC)/drivers/char/hello/
    all: modules
obj-m := module.o
     hello-objs := hello.o
EXTRA_FLAGS += -DDEBUG=1
modules:
      $(MAKE) -C $(KERNEL_SRC) SUBDIR=$(SUBDIR) modules

  makefile 文件使用內核編譯機制來編譯模塊。編譯好的模塊將被命名為 module.ko,並通過編譯 hello.c 和連接 vermagic 而獲得。KERNEL_SRC 指定內核源文件所在的目錄,SUBDIR 指定放置模塊的目錄。EXTRA_FLAGS 指定了需要給出的編譯期標記。

  一旦新模塊(module.ko)被創建,它可以被新的模塊工具加載或缷載。2.4中的原有模塊工具不能用來加載或缷載2.6的內核模塊。這個新的模塊加載工具會盡量減少在一個設備仍在使用的情況下相應的模塊卻被缷載的沖突發生,而是在確認這些模塊已經沒有任何設備在使用後再缷載它。產生這種沖突的原因之一是模塊使用計數是由模塊代碼自己來控制的(通過MOD_DEC/INC_USE_COUNT)。

  在2.6中,模塊不再需要對引用計數進行加或減,這些工作將在模塊代碼外部進行。任何要引用模塊的代碼都必須調用 try_module_get(&module),只有在調用成功以後才能訪問那個模塊;如果被調用的模塊已經被缷載,那麼這次調用會失敗。相應的,可以通過使用 module_put() 來釋放對模塊的引用。

  內存管理的變化

  在2.5的開發過程中,加入了內存池,以滿足無間斷地進行內存分配。其思想是預分配一個內存池,並保留到真正需要的時候。一個內存池由 mempool_create() 調用來創建(應該包含頭文件 linux/mempool.h)。

  mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn,

  mempool_free_t *free_fn, void *pool_data);

  在這裡 min_nr 是需要預分配對象的數目,alloc_fn 和 free_fn 是指向內存池機制提供的標准對象分配和回收例程的指針。他們的類型是:

  typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data);

  typedef void (mempool_free_t)(void *element, void *pool_data);

  pool_data 是分配和回收函數用到的指針,gfp_mask 是分配標記。只有當 __GFP_WAIT 標記被指定時,分配函數才會休眠。

  在池中分配和回收對象是由以下程序完成的:

  void *mempool_alloc(mempool_t *pool, int gfp_mask);

  void mempool_free(void *element, mempool_t *pool);

  mempool_alloc() 用來分配對象;如果內存池分配器無法提供內存,那麼就可以用預分配的池。

  系統使用 mempool_destroy() 來回收內存池。

  除了為內存分配引入了內存池之外,2.5內核還引入了三個用於常規內存分配的新的GFP標記,它們是:

  __GFP_REPEAT -- 告訴頁分配器盡力去分配內存。如果內存分配失敗過多,應該減少這個標記的使用。

  __GFP_NOFAIL -- 不能出現內存分配失敗。這樣,由於調用者被轉入休眠狀態,可能需要一段比較長的時間才能完成分配,調用者的需求才能得到滿足。

  __GFP_NORETRY -- 保證分配失敗後不再重試,而向調用者報告失敗狀態。

  除了內存分配的變化以外,remap_page_range()調用——用來映射頁到用戶空間——也經過了少量修改。相對於2.4來說,現在它多了一個參數。虛擬內存區域(VMA)指針要作為第一個參數,然後是四個常用的參數(start,end,size 和 protection 標記)。

  工作隊列接口

  工作隊列接口是在2.5的開發過程中引入的,用於取代任務隊列接口(用於調度內核任務)。每個工作隊列有一個專門的線程,所有來自運行隊列的任務在進程的上下文中運行(這樣它們可以休眠)。驅動程序可以創建並使用它們自己的工作隊列,或者使用內核的一個工作隊列。工作隊列用以下方式創建:

  struct workqueue_struct *create_workqueue(const char *name);

  在這裡 name 是工作隊列的名字。

  工作隊列任務可以在編譯時或者運行時創建。任務需要封裝為一個叫做 work_struct 的結構體。在編譯期初始化一個工作隊列任務時要用到:

  DECLARE_WORK(name, void (*function)(void *), void *data);

  在這裡 name 是 work_struct 的名字,function 是當任務被調度時調用的函數,data 是指向那個函數的指針。

  在運行期初始化一個工作隊列時要用到:

  INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);

  用下面的函數調用來把一個作業(一個類型為work_struct 結構的工作隊列作業/任務)加入到工作隊列中:

  int queue_work(struct workqueue_struct *queue, struct work_struct *work);

Copyright © Linux教程網 All Rights Reserved