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