1. 引言
Linux系統開放源代碼、系統漏洞少,在面對病毒和黑客入侵時能提供更好的安全性和穩定性,基于以上這些優點,近年來對Linux操作系統及其相關技術的應用和研究越來越多。對Linux操作系統擴充或裁剪功能需要在重新編譯內核上花費大量的時間。LKM機制由于大大縮短了開發和測試的時間,在 Linux開發、研究的過程中起到了舉足輕重的作用。
LKM主要包括內核模塊在操作系統中的加載和卸載兩部分功能,內核模塊是一些在啟動的操作系統內核需要時可以載入內核執行的代碼塊,不需要時由操作系統卸載。它們擴展了操作系統內核功能卻不需要重新編譯內核、啟動系統。如果沒有內核模塊,就不得不反復編譯生成操作系統的內核鏡像來加入新功能,當附加的功能很多時,還會使內核變得臃腫。
2. LKM的編寫和編譯
2.1 內核模塊的基本結構
一個內核模塊至少包含兩個函數,模塊被加載時執行的初始化函數init_module()和模塊
被卸載時執行的結束函數cleanup_module()。在最新內核穩定版本2.6中,兩個函數可以起
任意的名字,通過宏module_init()和module_exit()實現。唯一需要注意的地方是函數必須在宏的使用前定義。例如:
static int __init hello_init(void){}
static void __exit hello_exit(void ){}
module_init(hello_init);
module_exit(hello_exit);
這里聲明函數為static的目的是使函數在文件以外不可見,__init的作用是在完成初始化后收回該函數占用的內存,宏__exit用于模塊被編譯進內核時忽略結束函數。這兩個宏只針對模塊被編譯進內核的情況,而對動態加載模塊是無效的。這是因為編譯進內核的模塊是沒有清理收尾工作的,而動態加載模塊卻需要自己完成這些工作。
2.2 內核模塊的編譯
編譯時需要提供一個makefile來隱藏底層大量的復雜操作,使用戶通過make命令就可以完成編譯的任務。下面就是一個簡單的編譯hello.c的makefile文件:
obj-m += hello.ko
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
編譯后獲得可加載的模塊文件hello.ko。
內核版本2.6中使用.ko文件后綴代替了.o,這是為了與普通可執行文件相區別。
3. LKM的主要功能
3.1 模塊的加載
模塊的加載有兩種方法,第一種是使用insmod命令加載,另一種是當內核發現需要加載某個模塊時,請求內核后臺進程kmod加載適當的模塊。當內核需要加載模塊時,kmod被喚醒并執行modprobe,同時傳遞需加載模塊的名字作為參數。modprobe像insmod一樣將模塊加載進內核,不同的是在模塊被加載時查看它是否涉及到當前沒有定義在內核中的任何符號。如果有,在當前模塊路徑的其他模塊中查找。如果找到,它們也會被加載到內核中。但在這種情況下使用insmod,會以“未解析符號”信息結束。
關于模塊加載,可以用圖3.1來簡要描述:
insmod程序必須找到要求加載的內核模塊,這些內核模塊是已鏈接的目標文件,與其他文件不同的是,它們被鏈接成可重定位映象即映象沒有被鏈接到特定地址上。insmod將執行一個特權級系統調用來查找內核的輸出符號,這些符號都以符號名和數值形式如地址值成對保存。內核輸出符號表被保存在內核維護的模塊鏈表的第一個module結構中。只有特殊符號才被添加,它們在內核編譯與鏈接時確定。insmod將模塊讀入虛擬內存并通過使用內核輸出符號來修改其未解析的內核函數和資源的引用地址。這些工作采取由insmod程序直接將符號的地址寫入模塊中相應地址來進行。
當insmod修改完模塊對內核輸出符號的引用后,它將再次使用特權級系統調用申請足夠的空間容納新模塊。內核將為其分配一個新的module結構以及足夠的內核內存來保存新模塊,并將其插入到內核模塊鏈表的尾部,最后將新模塊標志為UNINITIALIZED。insmod將模塊拷貝到已分配空間中,如果為它分配的內核內存已用完,將再次申請,但模塊被多次加載必然處于不同的地址。另外此重定位工作包括使用適當地址來修改模塊映象。如果新模塊也希望將其符號輸出到系統中,insmod將為其構造輸出符號映象表。每個內核模塊必須包含模塊初始化和結束函數,所以為了避免沖突它們的符號被設計成不輸出,但是insmod必須知道這些地址,這樣可以將它們傳遞給內核。在所有這些工作完成以后,insmod將調用初始化代碼并執行一個特權級系統調用將模塊的初始化和結束函數地址傳遞給內核。當將一個新模塊加載到內核中時,內核必須更新其符號表并修改那些被新模塊使用的老模塊。那些依賴于其他模塊的模塊必須在其符號表尾部維護一個引用鏈表并在其module數據結構中指向它。內核調用模塊的初始化函數,如果成功將安裝此模塊。模塊的結束函數地址被存儲在其module結構中,將在模塊卸載時由內核調用,模塊的狀態最后被設置成RUNNING。
3.2 模塊的卸載
模塊可以使用rmmod命令刪除,但是請求加載模塊在其使用計數為0時,自動被系統刪除。kmod在其每次idle定時器到期時都執行一個系統調用,將系統中所有不再使用的請求加載模塊刪除。
關于模塊卸載,可以用圖3.2來描述:
內核中其他部分還在使用的模塊不能被卸載。例如系統中安裝了多個VFAT文件系統則不能卸載VFAT模塊。執行lsmod將看到每個模塊的引用計數。模塊的引用計數被保存在其映象的第一個常字中,這個字還包含autoclean和visited標志。如果模塊被標記成autoclean,則內核知道此模塊可以自動卸載。visited標志表示此模塊正被一個或多個文件系統部分使用,只要有其他部分使用此模塊則這個標志被置位。每次系統要將沒有被使用的請求加載模塊刪除時,內核將在所有模塊中掃描,但是一般只查看那些被標志為autoclean并處于running狀態的模塊。如果某模塊的 visited標記被清除則它將被刪除。其他依賴于它的模塊將修改各自的引用域,表示它們間的依賴關系不復存在。此模塊占有的內核內存將被回收。
4. LKM的應用
零拷貝基本思想是:數據分組從網絡設備到用戶程序空間傳遞的過程中,減少數據拷貝次數,減少系統調用,實現CPU的零參與,徹底消除CPU在這方面的負載。零拷貝的實現分為實現DMA數據傳輸和地址映射兩個部分。其中DMA數據傳輸與本文關系不大,就不詳細敘述了,這里主要介紹應用LKM機制實現的地址映射。
地址映射的基本原理是在內核空間申請內存,通過proc文件系統和mmap函數將其映射到用戶空間來允許應用程序訪問,這樣就消除了內核空間到應用程序空間的數據拷貝。地址映射部分的實現主要分為以下三步:
第一,建立LKM的基本結構,包括編寫初始化和結束函數等。
第二,聲明完成映射功能所需要的函數,主要有分配和初始化內核內存函數init_mem(),釋放內核內存函數del_mem(),向內核內存輸入內容的函數put_mem()等。
第三,在初始化函數中應用第二步建立的函數分配一塊內存空間、輸入內容、建立proc文件系統入口。在結束函數中釋放已分配的內核內存,刪除proc文件系統入口。
編寫應用程序測試該LKM,發現已經達到了映射內核內存到應用程序空間的目的。在實現零拷貝的過程中采用LKM機制不但便于調試而且大大減少了開發時間。
5. LKM與普通應用程序的比較
LKM與普通應用程序之間的區別主要體現在四個方面。
第一,也是最重要的區別,普通應用程序運行在用戶空間,而LKM運行在內核空間。通過區分不同的運行空間,操作系統能夠安全地保護操作系統中一些重要數據結構的內容不被普通應用程序所修改,達到保證操作系統正常運轉的目的。
第二,普通應用程序的目標很明確,它們從頭至尾都是為了完成某一項特定任務。而LKM是在內核中注冊并為后續應用程序的請求提供服務的。
第三,普通應用程序可以調用并沒有在其中定義的函數,但一個LKM是鏈接到內核上的,它所能調用的函數只有內核導出來的那些函數。
第四,普通應用程序和LKM處理錯誤的方式不同。當應用程序中出現錯誤時并不會給系統造成很大的傷害。LKM則不然,在其中出現的錯誤對子系統來說通常是致命的,至少對于當前正在運行的進程而言。LKM中的一個錯誤常常會導致整個系統崩潰。
6. 編寫LKM需要注意的問題
LKM運行在內核空間,它們擁有對整個系統所有資源的訪問權限,因此,編寫LKM首先要注意就是安全問題,而且還應該避免將可能導致出現安全問題的代碼帶到LKM中。
LKM加載后是作為操作系統內核的一部分運行的,因此,在設計、編寫操作系統內核過程中應該注意的問題在LKM中也應該引起足夠的重視。在這里,主要指的是并發問題和指針引用問題。并發是指在同一時間有多個進程在操作系統內核中同時運行。并發結合共享資源最終會導致競態條件,在這種情況下應該對各個并發進程訪問共享資源進行嚴格的控制。如果在LKM中出現指針引用錯誤,內核將沒有辦法將內存的虛擬地址映射到物理地址,從而導致出現內核中的意外,如內存訪問沖突、除0以及非法操作等。
7. LKM的不足之處
LKM雖然在設備驅動程序的編寫和擴充內核功能中扮演著非常重要的角色,但它仍有許多不足的地方。
第一,LKM對于內核版本的依賴性過強,每一個LKM都是靠內核提供的函數和數據結構組織起來的。當這些內核函數和數據結構因為內核版本變化而發生變動時,原先的LKM不經過修改就可能不能正常運行。
第二,雖然現在有針對內核編程調試的工具kgdb,但是在LKM編寫過程中調試仍非常麻煩,而且在調試過程中,系統所能提供的出錯信息極為晦澀。
創新點:針對Linux內核,利用LKM,在實現了數據的零拷貝(Zero-copy)的過程中,將LKM與普通應用程序進行比較,提出了LKM的優勢和不足。
評論