Linux內(nèi)核模塊編程
簡(jiǎn)介
模塊(module)是在內(nèi)核空間運(yùn)行的程序,實(shí)際上是一種目標(biāo)對(duì)象文件,沒有鏈接,不能獨(dú)立運(yùn)行,但是可以裝載到系統(tǒng)中作為內(nèi)核的一部分運(yùn)行,從而可以動(dòng)態(tài)擴(kuò)充內(nèi)核的功能。模塊最主要的用處就是用來實(shí)現(xiàn)設(shè)備驅(qū)動(dòng)程序。
使用模塊的優(yōu)點(diǎn):
1,將來修改內(nèi)核時(shí),不必全部重新編譯整個(gè)內(nèi)核,可節(jié)省不少時(shí)間
2,系統(tǒng)中如果需要使用新模塊,不必重新編譯內(nèi)核,只要插入相應(yīng)的模塊即可
模塊的使用方式:(modules-1.3.57.tar.gz)
命令 功能 備注
modprobe symbolic link to modprobe
depmod make module dependency file,以告訴將來的 insmod 要去哪抓 modules 來插。 這個(gè) dependency file 就在/lib/modules/[您的kernel版本]/modules.dep
insmod 把某個(gè) module 插入 kernel 中
rmmod 把某個(gè)沒在用的 module 拔出 kernel symbolic link to insmod
lsmod 把現(xiàn)在 kernel 中插入的 modules 列出來a shell script in 1.3.69f
ksyms symbolic link to insmod
kerneld 一個(gè) daemon,可負(fù)責(zé)自動(dòng)呼叫 insmod 插入 module,是一個(gè)很方便的 daemon。它也同時(shí)查看,若某 module 插入後但很久沒人在用了,就會(huì)把它拔出來,以省記憶體。
相關(guān)文件:
/etc/rc.d/ rc.modules
/etc/rc.d/rc.S or /etc/rc.d/rc.syinit
/etc/conf.modules or /etc/modules.conf
模塊的裝載/卸載:
靜態(tài):在系統(tǒng)啟動(dòng)時(shí)就裝載
動(dòng)態(tài):使用insmod等命令在系統(tǒng)運(yùn)行過程中裝載
注:
1.現(xiàn)在kerneld已經(jīng)被kmod所取代,原因主要是kerneld是使用System V IPC,經(jīng)過了中間層,比較復(fù)雜。
2.需要超級(jí)用戶權(quán)限。
3.形式:
Module: #pages: Used by:
msdos 5 1
vfat 4 1 (autoclean)
fat 6 [vfat msdos] 2 (autoclean)
工作原理
摘要:
*內(nèi)核符號(hào)表
*模塊裝載
*模塊卸載
*多個(gè)模塊間的依賴關(guān)系。
Kernel 里有一個(gè)變量叫 module_list,每當(dāng) user 將一個(gè) module 載到 kernel 里的時(shí)候,這個(gè) module 就會(huì)被記錄在 module_list 里面。當(dāng) kernel 要使用到這個(gè) module 提供的 function 時(shí),它就會(huì)去 search 這個(gè) list,找到 module,然后再使用其提供的 function 或 variable。每一個(gè) module 都可以 export 一些 function 或變量來讓別人使用。除此之外,module 也可以使用已經(jīng)載到 kernel 里的 module 提供的 function。這種情形叫做 module stack。比方說,module A 用到 module B 的東西,那在加載 module A 之前必須要先加載 module B。否則 module A 會(huì)無法加載。除了 module 會(huì) export 東西之外,kernel 本身也會(huì) export 一些 function 或 variable。同樣的,module 也可以使用 kernel 所 export 出來的東西。由于大家平時(shí)都是撰寫 user space 的程序,所以,當(dāng)突然去寫 module 的時(shí)候,會(huì)把平時(shí)寫程序用的 function 拿到 module 里使用。像是 printf 之類的東西。我要告訴各位的是,module 所使用的 function 或 variable,要嘛就是自己寫在 module 里,要嘛就是別的 module 提供的,再不就是 kernel 所提供的。你不能使用一般 libc 或 glibc所提供的 function。像 printf 之類的東西。這一點(diǎn)可能是各位要多小心的地方。(也許你可以先 link 好,再載到 kernel,我好象試過,但是忘了)。
Linux核心是一種monolithic類型的內(nèi)核,即單一的大程序,核心中所有的功能部件都可以對(duì)其全部?jī)?nèi)部數(shù)據(jù)結(jié)構(gòu)和例程進(jìn)行訪問。核心的另外一種形式是微內(nèi)核結(jié)構(gòu),此時(shí)核心的所有功能部件都被拆成獨(dú)立部分, 這些部分之間通過嚴(yán)格的通訊機(jī)制進(jìn)行聯(lián)系。這樣通過配置進(jìn)程將新部件加入核心的方式非常耗時(shí)。比如說我們想為一個(gè)NCR 810 SCSI卡配置SCSI驅(qū)動(dòng),但是核心中沒有這個(gè)部分。那么我們必須重新配置并重構(gòu)核心。 Linux可以讓我們可以隨意動(dòng)態(tài)的加載與卸載操作系統(tǒng)部件。Linux模塊就是這樣一種可在系統(tǒng)啟動(dòng)后的任何時(shí)候動(dòng)態(tài)連入核心的代碼塊。當(dāng)我們不再需要它時(shí)又可以將它從核心中卸載并刪除。Linux模塊多指設(shè)備驅(qū)動(dòng)、偽設(shè)備驅(qū)動(dòng), 如網(wǎng)絡(luò)設(shè)備和文件系統(tǒng)。
Linux為我們提供了兩個(gè)命令:使用insmod來顯式加載核心模塊,使用rmmod來卸載模塊。同時(shí)核心自身也可以請(qǐng)求核心后臺(tái)進(jìn)程kerneld來加載與卸載模塊。
動(dòng)態(tài)可加載代碼的好處在于可以讓核心保持很小的尺寸同時(shí)非常靈活。在我的Intel系統(tǒng)中由于使用了模塊,整個(gè)核心僅為406K字節(jié)長(zhǎng)。由于我只是偶爾使用VFAT文件系統(tǒng), 所以我將Linux核心構(gòu)造成當(dāng)mount VFAT分區(qū)時(shí)自動(dòng)加載VFAT文件系統(tǒng)模塊。當(dāng)我卸載VFAT分區(qū)時(shí)系統(tǒng)將檢測(cè)到我不再需要VFAT文件系統(tǒng)模塊,將把它從系統(tǒng)中卸載。模塊同時(shí)還可以讓我們無需重構(gòu)核心并頻繁重新啟動(dòng)來嘗試運(yùn)行新核心代碼。盡管使用模塊很自由,但是也有可能同時(shí)帶來與核心模塊相關(guān)的性能與內(nèi)存損失。可加載模塊的代碼一般有些長(zhǎng)并且額外的數(shù)據(jù)結(jié)構(gòu)可能會(huì)占據(jù)一些內(nèi)存。同時(shí)對(duì)核心資源的間接使用可能帶來一些效率問題。
一旦Linux模塊被加載則它和普通核心代碼一樣都是核心的一部分。它們具有與其他核心代碼相同的權(quán)限與職 責(zé);換句話說Linux核心模塊可以象所有核心代碼和設(shè)備驅(qū)動(dòng)一樣使核心崩潰。
模塊為了使用所需核心資源所以必須能夠找到它們。例如模塊需要調(diào)用核心內(nèi)存分配例程kmalloc()來分配 內(nèi)存。模塊在構(gòu)造時(shí)并不知道kmalloc()在內(nèi)存中何處,這樣核心必須在使用這些模塊前修改模塊中對(duì) kmalloc()的引用地址。核心在其核心符號(hào)表中維護(hù)著一個(gè)核心資源鏈表這樣當(dāng)加載模塊時(shí)它能夠解析出模塊 中對(duì)核心資源的引用。Linux還允許存在模塊堆棧,它在模塊之間相互調(diào)用時(shí)使用。例如VFAT文件系統(tǒng)模塊 可能需要FAT文件系統(tǒng)模塊的服務(wù),因?yàn)閂FAT文件系統(tǒng)多少是從FAT文件系統(tǒng)中擴(kuò)展而來。某個(gè)模塊對(duì)其他模 塊的服務(wù)或資源的需求類似于模塊對(duì)核心本身資源或服務(wù)的請(qǐng)求。不過此時(shí)所請(qǐng)求的服務(wù)是來自另外一個(gè)事先 已加載的模塊。每當(dāng)加載模塊時(shí),核心將把新近加載模塊輸出的所有資源和符號(hào)添加到核心符號(hào)表中。
當(dāng)試圖卸載某個(gè)模塊時(shí),核心需要知道此模塊是否已經(jīng)沒有被使用,同時(shí)它需要有種方法來通知此將卸載模塊。 模塊必須能夠在從核心種刪除之前釋放其分配的所有系統(tǒng)資源,如核心內(nèi)存或中斷。當(dāng)模塊被卸載時(shí),核心將從核心符號(hào)表中刪除所有與之對(duì)應(yīng)的符號(hào)。
可加載模塊具有使操作系統(tǒng)崩潰的能力,而編寫較差的模塊會(huì)帶來另外一種問題。當(dāng)你在一個(gè)或早或遲構(gòu)造的核心而不是當(dāng)前你運(yùn)行的核心上加載模塊時(shí)將會(huì)出現(xiàn)什么結(jié)果?一種可能的情況是模塊將調(diào)用具有錯(cuò)誤參數(shù)的核心例程。核心應(yīng)該使用嚴(yán)格的版本控制來對(duì)加載模塊進(jìn)行檢查以防止這種這些情況的發(fā)生。
1 模塊的加載
圖1 核心模塊鏈表
核心模塊的加載方式有兩種。首先一種是使用insmod命令手工加載模塊。另外一種則是在需要時(shí)加載模塊;我們稱它為請(qǐng)求加載。當(dāng)核心發(fā)現(xiàn)有必要加載某個(gè)模塊時(shí),如用戶安裝了核心中不存在的文件系統(tǒng)時(shí),核心將請(qǐng)求核心后臺(tái)進(jìn)程(kerneld)準(zhǔn)備加載適當(dāng)?shù)哪K。這個(gè)核心后臺(tái)進(jìn)程僅僅是一個(gè)帶有超級(jí)用戶權(quán)限的普通用戶進(jìn)程。當(dāng)系統(tǒng)啟動(dòng)時(shí)它也被啟動(dòng)并為核心打開了一個(gè)進(jìn)程間通訊(IPC)通道。核心需要執(zhí)行各種任務(wù)時(shí)用它來向kerneld發(fā)送消息。
kerneld的主要功能是加載和卸載核心模塊, 但是它還可以執(zhí)行其他任務(wù), 如通過串行線路建立PPP連接并在適當(dāng)時(shí)候關(guān)閉它。kerneld自身并不執(zhí)行這些任務(wù),它通過某些程序如insmod來做此工作。它只是核心的代理,為核心進(jìn)行調(diào)度。
insmod程序必須找到要求加載的核心模塊。請(qǐng)求加載核心模塊一般被保存在/lib/modules/kernel-version 中。這些核心模塊和系統(tǒng)中其他程序一樣是已連接的目標(biāo)文件,但是它們被連接成可重定位映象。即映象沒有被連接到在特定地址上運(yùn)行。這些核心模塊可以是a.out或ELF文件格式。insmod將執(zhí)行一個(gè)特權(quán)級(jí)系統(tǒng)調(diào)用來找到核心的輸出符號(hào)。這些都以符號(hào)名以及數(shù)值形式,如地址值成對(duì)保存。核心輸出符號(hào)表被保存在核心維護(hù)的模塊鏈表的第一個(gè)module結(jié)構(gòu)中,同時(shí)module_list指針指向此結(jié)構(gòu)。只有特殊符號(hào)被添加到此表中,它們?cè)诤诵木幾g與連接時(shí)確定,不是核心每個(gè)符號(hào)都被輸出到其模塊中。例如設(shè)備驅(qū)動(dòng)為了控制某個(gè)特定系統(tǒng)中斷而由核心例程調(diào)用的"request_irq"符號(hào)。在我的系統(tǒng)中,其值為0x0010cd30。我們可以通過使用ksyms工具或者查看/proc/ksyms來觀看當(dāng)前核心輸出符號(hào)。ksyms工具既可以顯示所有核心輸出符號(hào)也可以只顯示那些已加載模塊的符號(hào)。insmod將模塊讀入虛擬內(nèi)存并通過使用來自核心輸出符號(hào)來修改其未解析的核心例程和資源的引用地址。這些修改工作采取由insmod程序直接將符號(hào)的地址寫入模塊中相應(yīng)地址來修改內(nèi)存中的模塊映象。
當(dāng)insmod修改完模塊對(duì)核心輸出符號(hào)的引用后,它將再次使用特權(quán)級(jí)系統(tǒng)調(diào)用來申請(qǐng)足夠的空間來容納新核 心。核心將為其分配一個(gè)新的module結(jié)構(gòu)以及足夠的核心內(nèi)存來保存新模塊, 并將它放到核心模塊鏈表的尾部。 然后將其新模塊標(biāo)志為UNINITIALIZED。
圖1給出了一個(gè)加載兩個(gè)模塊:VFAT和FAT后的核心鏈表示意圖。不過圖中沒有畫出鏈表中的第一個(gè)模塊: 用來存放核心輸出符號(hào)表的一個(gè)偽模塊。lsmod可以幫助我們列出系統(tǒng)中所有已加載的核心模塊以及相互間 依賴關(guān)系。它是通過重新格式化從核心module結(jié)構(gòu)中建立的/proc/modules來進(jìn)行這項(xiàng)工作的。核心為其分配的內(nèi)存被映射到insmod的地址空間, 這樣它就能訪問核心空間。insmod將模塊拷貝到已分配空間中, 如果為它分配的核心內(nèi)存已用完,則它將再次申請(qǐng)。不過不要指望多次將加載模塊到相同地址,更不用說在兩個(gè)不同 Linux系統(tǒng)的相同位置。另外此重定位工作包括使用適當(dāng)?shù)刂穪硇薷哪K映象。
這個(gè)新模塊也希望將其符號(hào)輸出到核心中,insmod將為其構(gòu)造輸出符號(hào)映象表。每個(gè)核心模塊必須包含模塊 初始化和模塊清除例程,它們的符號(hào)被設(shè)計(jì)成故意不輸出, 但是insmod必須知道這些地址, 這樣它可以將它們傳遞給核心。所有這些工作做完之后,insmod將調(diào)用初始化代碼并執(zhí)行一個(gè)特權(quán)級(jí)系統(tǒng)調(diào)用將模塊的初始化與清除例程地址傳遞給核心。
當(dāng)將一個(gè)新模塊加載到核心中間時(shí),核心必須更新其符號(hào)表并修改那些被新模塊使用的老模塊。那些依賴于其他模塊的模塊必須維護(hù)在其符號(hào)表尾部維護(hù)一個(gè)引用鏈表并在其module數(shù)據(jù)結(jié)構(gòu)中指向它。圖12.1中VFAT 依賴于FAT文件系統(tǒng)模塊。所以FAT模塊包含一個(gè)對(duì)VFAT模塊的引用;這個(gè)引用在加載VFAT模塊時(shí)添加。核心調(diào)用模塊的初始化例程,如果成功它將安裝此模塊。模塊的清除例程地址被存儲(chǔ)在其module結(jié)構(gòu)中,它將在 模塊卸載時(shí)由核心調(diào)用。最后模塊的狀態(tài)被設(shè)置成RUNNING。
2 模塊的卸載
模塊可以通過使用rmmod命令來刪除, 但是請(qǐng)求加載模塊將被kerneld在其使用記數(shù)為0時(shí)自動(dòng)從系統(tǒng)中刪除。 kerneld在其每次idle定時(shí)器到期時(shí)都執(zhí)行一個(gè)系統(tǒng)調(diào)用以將系統(tǒng)中所有不再使用的請(qǐng)求加載模塊從系統(tǒng)中 刪除。這個(gè)定時(shí)器的值在啟動(dòng)kerneld時(shí)設(shè)置;我系統(tǒng)上的值為180秒。這樣如果你安裝一個(gè)iso9660 CDROM并且你的iso9660文件系統(tǒng)是一個(gè)可加載模塊, 則在卸載CD ROM后的很短時(shí)間內(nèi)此iso9660模塊將從核心中刪除。
如果核心中的其他部分還在使用某個(gè)模塊, 則此模塊不能被卸載。例如如果你的系統(tǒng)中安裝了多個(gè)VFAT文件系統(tǒng)則你將不能卸載VFAT模塊。執(zhí)行l(wèi)smod我們將看到每個(gè)模塊的引用記數(shù)。如:
Module: #pages: Used by:
msdos 5 1
vfat 4 1 (autoclean)
fat 6 [vfat msdos] 2 (autoclean)
此記數(shù)表示依賴此模塊的核心實(shí)體個(gè)數(shù)。在上例中VFAT和msdos模塊都依賴于fat模塊, 所以fat模塊的引用記數(shù)為2。vfat和msdos模塊的引用記數(shù)都為1,表示各有一個(gè)已安裝文件系統(tǒng)。如果我們安裝另一個(gè)VFAT文件系統(tǒng)則vfat模塊的引用記數(shù)將為2。模塊的引用記數(shù)被保存在其映象的第一個(gè)長(zhǎng)字中。這個(gè)字同時(shí)還包含AUTOCLEAN和VISITED標(biāo)志。請(qǐng)求加載模塊使用這兩個(gè)標(biāo)志域。如果模塊被標(biāo)記成AUTOCLEAN則核心知道此模 塊可以自動(dòng)卸載。VISITED標(biāo)志表示此模塊正被一個(gè)或多個(gè)文件系統(tǒng)部分使用;只要有其他部分使用此模塊則這個(gè)標(biāo)志被置位。每次系統(tǒng)被kerneld要求將沒有誰使用的請(qǐng)求模塊刪除時(shí),核心將在所有模塊中掃描可能的候選者。但是一般只查看那些被標(biāo)志成AUTOCLEAN并處于RUNNING狀態(tài)的模塊。如果某模塊的VISITED 標(biāo)記被清除則它將被刪除出去。如果某模塊可以卸載,則可以調(diào)用其清除例程來釋放掉分配給它的核心資源。它所對(duì)應(yīng)的module結(jié)構(gòu)將被標(biāo)記成DELETED并從核心模塊鏈表中斷開。其他依賴于它的模塊將修改它們各自的引用域來表示它們間的依賴關(guān)系不復(fù)存在。此模塊需要的核心內(nèi)存都將被回收。
編程實(shí)現(xiàn)
模塊的組織結(jié)構(gòu)
2.0/2.2
至少需要兩個(gè)函數(shù):init_module()和cleanup_module()。一般在init_module()完成初始化工作,例如內(nèi)存分配(kmalloc);在cleanup_module()中完成回收工作。
/* The necessary header files */
/* Standard in kernel modules */
#include /* We e doing kernel work */
#include /* Specifically, a module */
/* Deal with CONFIG_MODVERSIONS */
#if CONFIG_MODVERSIONS==1
#define MODVERSIONS /這兩行應(yīng)該修改為#ifdef MODVERSIONS
#include
#endif
/* Initialize the module */
int init_module()
{
Printk("Hello, world - this is the kernel speakingn");
/* If we return a non zero value, it means that
* init_module failed and the kernel module
* can be loaded */
Return 0;
}
/* Cleanup - undid whatever init_module did */
void cleanup_module()
{
Printk("Short is the life of a kernel modulen");
}
2.3.*/2.4.*中的用法:
2.3/2.4中的用法不同,應(yīng)該使用:
module_init(init_proc_fs)
module_exit(exit_proc_fs)
優(yōu)點(diǎn):有了返回值。
static int __init init_proc_fs(void)
{
int err = register_filesystem(proc_fs_type);
if (!err) {
proc_mnt = kern_mount(proc_fs_type);
err = PTR_ERR(proc_mnt);
if (IS_ERR(proc_mnt))
unregister_filesystem(proc_fs_type);
Else
err = 0;
}
return err;
}
static void __exit exit_proc_fs(void)
{
unregister_filesystem(proc_fs_type);
kern_umount(proc_mnt);
}
module_init(init_proc_fs)
module_exit(exit_proc_fs)
模塊的Makefile
Options:
-D__KERNEL__ / in kernel space
-DMODULE / create module
-DLINUX / which can be compiled on more than one operating system
-D__SMP__ / Symmetrical MultiProcessing
-DCONFIG_MODVERSIONS / should include /usr/include/linux/modversions.h
fomit-frame-pointer告訴gcc不要為那些不需要的函數(shù)保存頁面指針.這會(huì)使得我們的寄存器在調(diào)用init_module以后保持不變.
多個(gè)源程序組成的模塊的Makefile
1.在除了一個(gè)以外的所有源文件中,增加一行#define __NO_VERSION__。這是很重要的,因?yàn)閙odule.h一般包括kernel_version的定義,這是一個(gè)全局變量,包含模塊編譯的內(nèi)核版本。如果你需要version.h,你需要把自己把它包含進(jìn)去,因?yàn)槿绻衉_NO_VERSION__的話module.h不會(huì)自動(dòng)包含。
2.象通常一樣編譯源文件。
3.把所有目標(biāo)文件聯(lián)編成一個(gè)。在X86下,用ld –m elf_i386 –r –o .o 1st source file>
printk
printk是內(nèi)核空間中printf的替代品,其用法為:printk("format string", var)。n定義了日志等級(jí),范圍從0到7,其中0到4是緊急事件,5和6是普通信息,7為調(diào)試(例如SMP_DEBUG)。其它用戶空間的函數(shù)也不能使用;程序員在內(nèi)核空間中可以使用的函數(shù)都記錄在/proc/ksyms中,也可以使用ksyms -a命令查看。
注:在終端下insmod模塊,printk信息送往終端
By the way, the reason why the Makefile recommends against doing insmod from X is because when the kernel has a message to print with printk, it sends it to the console. When you don use X, it just goes to the virtual terminal you e using (the one you chose with Alt-F) and you see it. When you do use X, on the other hand, there are two possibilities. Either you have a console open with xterm -C, in which case the output will be sent there, or you don , in which case the output will go to virtual terminal 7 -- the one `covered by X.
函數(shù)/變量和內(nèi)核版本的關(guān)系
剛才我們說到 kernel 本身會(huì) export 出一些 function 或 variable 來讓 module 使用,但是,我們不是萬能的,我們?cè)蹒壑?kernel 有開放那里東西讓我們使用呢 ? Linux 提供一個(gè) command,叫 ksyms,你只要執(zhí)行 ksyms -a 就可以知道 kernel 或目前載到 kernel 里的 module 提供了那些 function 或 variable。底下是我的系統(tǒng)的情形:
c0216ba0 drive_info_R744aa133
c01e4a44 boot_cpu_data_R660bd466
c01e4ac0 EISA_bus_R7413793a
c01e4ac4 MCA_bus_Rf48a2c4c
c010cc34 __verify_write_R203afbeb
. . . . .
在 kernel 里,有一個(gè) symbol table 是用來記錄 export 出去的 function 或 variable。除此之外,也會(huì)記錄著那個(gè) module export 那些 function。上面幾行中,表示 kernel 提供了 drive_info 這個(gè) function/variable。所以,我們可以在 kernel 里直接使用它,等載到 kernel 里時(shí),會(huì)自動(dòng)做好 link 的動(dòng)作。由此,我們可以知道,module 本身其實(shí)是還沒做 link 的一些 object code。一切都要等到 module 被加載 kernel 之后,link 才會(huì)完成。各位應(yīng)該可以看到 drive_info 后面還接著一些奇怪的字符串。_R744aa133,這個(gè)字符串是根據(jù)目前 kernel 的版本再做些 encode 得出來的結(jié)果。為什幺額外需要這一個(gè)字符串呢 ?
Linux 不知道從那個(gè)版本以來,就多了一個(gè) config 的選項(xiàng),叫做 Set version number in symbols of module。這是為了避免對(duì)系統(tǒng)造成不穩(wěn)定。我們知道 Linux 的 kernel 更新的很快。在 kernel 更新的過程,有時(shí)為了效率起見,會(huì)對(duì)某些舊有的 data structure 或 function 做些改變,而且一變可能有的 variable 被拿掉,有的 function 的 prototype 跟原來的都不太一樣。如果這種情形發(fā)生的時(shí)候,那可能以前 2.0.33 版本的 module 拿到 2.2.1 版本的 kernel 使用,假設(shè)原來 module 使用了 2.0.33 kernel 提供的變量叫 A,但是到了 2.2.1 由于某些原因必須把 A 都設(shè)成 NULL。那當(dāng)此 module 用在 2.2.1 kernel 上時(shí),如果它沒去檢查 A 的值就直接使用的話,就會(huì)造成系統(tǒng)的錯(cuò)誤。也許不會(huì)整個(gè)系統(tǒng)都死掉,但是這個(gè) module 肯定是很難發(fā)揮它的功能。為了這個(gè)原因,Linux 就在 compile module 時(shí),把 kernel 版本的號(hào)碼 encode 到各個(gè) exported function 和 variable 里。
所以,剛才也許我們不應(yīng)該講 kernel 提供了 drive_info,而應(yīng)該說 kernel 提供了 driver_info_R744aa133 來讓我們使用。這樣也許各位會(huì)比較明白。也就是說,kernel 認(rèn)為它提供的 driver_info_R744aa133 這個(gè)東西,而不是 driver_info。所以,我們可以發(fā)現(xiàn)有的人在加載 module 時(shí),系統(tǒng)都一直告訴你某個(gè) function 無法 resolved。這就是因?yàn)?kernel 里沒有你要的 function,要不然就是你的 module 里使用的 function 跟 kernel encode 的結(jié)果不一樣。所以無法 resolve。解決方式,要嘛就是將 kernel 里的 set version 選項(xiàng)關(guān)掉,要嘛就是將 module compile 成 kernel 有辦法接受的型式。
那有人就會(huì)想說,如果 kernel 認(rèn)定它提供的 function 名字叫做 driver_info_R744aa133 的話,那我們寫程序時(shí),是不是用到這個(gè) funnction 的地方都改成 driver_info_R744aa133 就可以了。答案是 Yes。但是,如果每個(gè) function 都要你這樣寫,你不會(huì)覺得很煩嗎 ? 比方說,我們?cè)趯?driver 時(shí),很多人都會(huì)用到 printk 這個(gè) function。這是 kernel 所提供的 function。它的功能跟 printf 很像。用法也幾乎都一樣。是 debug 時(shí)很好用的東西。如果我們 module 里用了一百次 printk,那是不是我們也要打一百次的 printk_Rdd132261 呢 ? 當(dāng)然不是,聰明的人馬上會(huì)想到用 #define printk printk_Rdd132261 就好了嘛。所以啰,Linux 很體貼的幫我們做了這件事。
如果各位的系統(tǒng)有將set version的選項(xiàng)打開的話,那大家可以到/usr/src/linux/include/linux/modules這個(gè)目錄底下。這個(gè)目錄底下有所多的..ver檔案。這些檔案其實(shí)就是用來做#define用的。我們來看看ksyms.ver 這個(gè)檔案里,里面有一行是這樣子的 :
#define printk _set_ver(printk)
set_ver是一個(gè)macro,就是用來在printk后面加上version number的。有興趣的朋友可以自行去觀看這個(gè)macro的寫法。用了這些ver檔,我們就可以在module里直接使用printk這樣的名字了。而這些ver檔會(huì)自動(dòng)幫我們做好#define的動(dòng)作??墒牵覀兛梢园l(fā)現(xiàn)這個(gè)目錄有很多很多的ver檔。有時(shí)候,我們?cè)蹒壑牢覀円艚械膄unction是在那個(gè)ver檔里有定義呢?Linux又幫我們做了一件事。/usr/src/linux/include/linux/modversions.h這個(gè)檔案已經(jīng)將全部的ver檔都加進(jìn)來了。所以在我們的module里只要include這個(gè)檔,那名字的問題都解決了。但是,在此,我們奉勸各位一件事,不要將modversions.h這個(gè)檔在module里include進(jìn)來,如果真的要,那也要加上以下數(shù)行:
#ifdef MODVERSIONS
#include linux/modversions.h
#endif
加入這三行的原因是,避免這個(gè) module 在沒有設(shè)定 kernel version 的系統(tǒng)上,將 modversions.h 這個(gè)檔案 include 進(jìn)來。各位可以去試試看,當(dāng)你把 set version 的選項(xiàng)關(guān)掉時(shí),modversions.h 和 modules 這個(gè)目錄都會(huì)不見。如果沒有上面三行,那 compile 就不會(huì)過關(guān)。所以一般來講,modversions.h 我們會(huì)選擇在 compile 時(shí)傳給 gcc 使用。就像下面這個(gè)樣子。
gcc -c -D__KERNEL__ -DMODULE -DMODVERSIONS main.c
-include usr/src/linux/include/linux/modversions.h
在這個(gè) command line 里,我們看到了 -D__KERNEL__,這是說要定義 __KERNEL__ 這個(gè) constant。很多跟 kernel 有關(guān)的 header file,都必須要定義這個(gè) constant 才能 include 的。所以建議你最好將它定義起來。另外還有一個(gè) -DMODVERSIONS。這個(gè) constant 我剛才忘了講。剛才我們說要解決 fucntion 或 variable 名字 encode 的方式就是要 include modversions.h,其實(shí)除此之外,你還必須定義 MODVERSIONS 這個(gè) constant。再來就是 MODULE 這個(gè) constant。其實(shí),只要是你要寫 module 就一定要定義這個(gè)變量。而且你還要 include module.h 這個(gè)檔案,因?yàn)?_set_ver 就是定義在這里的。
講到這里,相信各位應(yīng)該對(duì) module 有一些認(rèn)識(shí)了,以后遇到 module unresolved 應(yīng)該不會(huì)感到困惑了,應(yīng)該也有辦法解決了。
變量/函數(shù)的導(dǎo)出(export)
剛才講的都是使用別人的 function 上遇到的名字 encode 問題。但是,如果我們自己的 module 想要 export 一些東西讓別的 module 使用呢。很簡(jiǎn)單。在 default 上,在你的 module 里所有的 global variable 和 function 都會(huì)被認(rèn)定為你要 export 出去的。所以,如果你的 module 里有 10 個(gè) global variable,經(jīng)由 ksyms,你可以發(fā)現(xiàn)這十個(gè) variable 都會(huì)被 export 出去。這當(dāng)然是個(gè)很方便的事啦,但是,你知道,有時(shí)候我們根本不想把所有的 variable 都 export 出去,萬一有個(gè) module 沒事亂改我們的 variable 怎幺辦呢 ? 所以,在很多時(shí)候,我們都只會(huì)限定幾個(gè)必要的東西 export 出去。在 2.2.1 之前的 kernel (不是很確定) 可以利用 register_symtab 來幫我們。但是,現(xiàn)在更新的版本早就出來了。所以,在此,我會(huì)介紹 kernel 2.2.1 里所提供的。kernel 2.2.1 里提供了一個(gè) macro,叫做 EXPORT_SYMBOL,這是用來幫我們選擇要 export 的 variable 或 function。比方說,我要 export 一個(gè)叫 full 的 variable,那我只要在 module 里寫:
EXPORT_SYMBOL(full);
就會(huì)自動(dòng)將 full export 出去,你馬上就可以從 ksyms 里發(fā)現(xiàn)有 full 這個(gè)變量被 export 出去。在使用 EXPORT_SYMBOL 之前,要小心一件事,就是必須在 gcc 里定義 EXPORT_SYMTAB 這個(gè) constant,否則在 compile 時(shí)會(huì)發(fā)生 parser error。所以,要使用 EXPORT_SYMBOL 的話,那 gcc 應(yīng)該要下:
gcc -c -D__KERNEL__ -DMODULE -DMODVERSIONS -DEXPORT_SYMTAB
main.c -include /usr/src/linux/include/linux/modversions.h
如果我們不想 export 任何的東西,那我們只要在 module 里下
EXPORT_NO_SYMBOLS;
就可以了。使用 EXPORT_NO_SYMBOLS 用不著定義任何的 constant。其實(shí),如果各位使用過舊版的 register_symbol 的話,一定會(huì)覺得新版的方式比較好用。至少我是這樣覺得啦。因?yàn)槭褂?register_symbol 還要先定義出自己的 symbol_table,感覺有點(diǎn)麻煩。
當(dāng)我們使用 EXPORT_SYMBOL 把一些 function 或 variable export 出來之后,我們使用 ksyma -a 去看一些結(jié)果。我們發(fā)現(xiàn) EXPORT_SYMBOL(full) 的確是把 full export出來了 :
c8822200 full [my_module]
c01b8e08 pci_find_slot_R454463b5
. . .
但是,結(jié)果怎幺跟我們想象中的不太一樣,照理說,應(yīng)該是 full_Rxxxxxx 之類的東西才對(duì)啊,怎幺才出現(xiàn) full 而已呢 ? 奇怪,問題在那里呢 ?
其實(shí),問題就在于我們沒有對(duì)本身的 module 所 export 出來的 function 或 variable 的名字做 encode。想想,如果在 module 的開頭。我們加入一行
#define full full_Rxxxxxx
之后,我們?cè)僦匦?compile module 一次,載到 kernel 之后,就可以發(fā)現(xiàn) ksyms -a 顯示的是
c8822200 full_Rxxxxxx [my_module]
c01b8e08 pci_find_slot_R454463b5
. . . . .
了。那是不是說,我們要去對(duì)每一個(gè) export 出來的 variable 和 function 做 define 的動(dòng)作呢 ? 當(dāng)然不是啰。記得嗎,前頭我們講去使用 kernel export 的 function 時(shí),由于 include 了一些 .ver 的檔案,以致于我們不用再做 define 的動(dòng)作。現(xiàn)在,我們也要利用 .ver 的檔案來幫我們,使我們 module export 出來的 function 也可以自動(dòng)加入 kernel version 的 information。也就是變成 full_Rxxxxxx 之類的東西。
Linux 里提供了一個(gè) command,叫 genksyms,就是用來幫我們產(chǎn)生這種 .ver 的檔案的。它會(huì)從 stdin 里讀取 source code,然后檢查 source code 里是否有 export 的 variable 或 function。如果有,它就會(huì)自動(dòng)為每個(gè) export 出來的東西產(chǎn)生一些 define。這些 define 就是我們之前說的。等我們有了這些 define 之后,只要在我們的 module 里加入這些 define,那 export 出來的 function 或 variable 就會(huì)變成上面那個(gè)樣子。
假設(shè)我們的程序都放在一個(gè)叫 main.c 的檔案里,我們可以使用下列的方式產(chǎn)生這些 define。
gcc -E -D__GENKSYMS__ main.c | genksyms -k 2.2.1 > main.ver
gcc 的 -E 參數(shù)是指將 preprocessing 的結(jié)果 show 出來。也就是說將它 include 的檔案,一些 define 的結(jié)果都展開。-D__GENKSYMS__ 是一定要的。如果沒有定義這個(gè) constant,你將不會(huì)看到任何的結(jié)果。用一個(gè)管線是因?yàn)?genksyms 是從 stdin 讀資料的,所以,經(jīng)由管線將 gcc 的結(jié)果傳給 genksyms。-k 2.2.1 是指目前使用的 kernel 版本是 2.2.1,如果你的 kernel 版本不一樣,必須指定你的 kernel 的版本。產(chǎn)生的 define 將會(huì)被放到 main.ver 里。產(chǎn)生完 main.ver 檔之后,在 main.c 里將它 include 進(jìn)來,那一切就 OK 了。有件事要告訴各位的是,使用這個(gè)方式產(chǎn)生的 module,其 export 出來的東西會(huì)經(jīng)由 main.ver 的 define 改頭換面。所以如果你要讓別人使用,那你必須將 main.ver 公開,不然,別人就沒辦法使用你 export 出來的東西了。
內(nèi)核模塊和系統(tǒng)的交互
/proc和設(shè)備驅(qū)動(dòng)程序是內(nèi)核和系統(tǒng)進(jìn)行交互的兩種方式。
/proc
/proc的使用:
defined in /kerneltree/include/linux/proc_fs.h
2.4.0:
struct proc_dir_entry {
unsigned short low_ino;
unsigned short namelen;
const char *name;
mode_t mode;
nlink_t nlink;
uid_t uid;
gid_t gid;
unsigned long size;
struct inode_operations * proc_iops;
struct file_operations * proc_fops;
get_info_t *get_info;
struct module *owner;
struct proc_dir_entry *next, *parent, *subdir;
void *data;
read_proc_t *read_proc;
write_proc_t *write_proc;
atomic_t count; /* use count */
int deleted; /* delete flag */
kdev_t rdev;
};
extern struct proc_dir_entry *proc_sys_root;
#ifdef CONFIG_SYSCTL
EXPORT_SYMBOL(proc_sys_root);
#endif
EXPORT_SYMBOL(proc_symlink);
EXPORT_SYMBOL(proc_mknod);
EXPORT_SYMBOL(proc_mkdir);
EXPORT_SYMBOL(create_proc_entry);
EXPORT_SYMBOL(remove_proc_entry);
EXPORT_SYMBOL(proc_root);
EXPORT_SYMBOL(proc_root_fs);
EXPORT_SYMBOL(proc_net);
EXPORT_SYMBOL(proc_bus);
EXPORT_SYMBOL(proc_root_driver);
2.2.18
struct proc_dir_entry {
unsigned short low_ino;
unsigned short namelen;
const char *name;
mode_t mode;
nlink_t nlink;
uid_t uid;
gid_t gid;
unsigned long size;
struct inode_operations * ops;
int (*get_info)(char *, char **, off_t, int, int);
void (*fill_inode)(struct inode *, int);
struct proc_dir_entry *next, *parent, *subdir;
void *data;
int (*read_proc)(char *page, char **start, off_t off,
int count, int *eof, void *data);
int (*write_proc)(struct file *file, const char *buffer,
unsigned long count, void *data);
int (*readlink_proc)(struct proc_dir_entry *de, char *page);
unsigned int count; /* use count */
int deleted; /* delete flag */
};
#ifdef CONFIG_SYSCTL
EXPORT_SYMBOL(proc_sys_root);
#endif
EXPORT_SYMBOL(proc_register);
EXPORT_SYMBOL(proc_unregister);
EXPORT_SYMBOL(create_proc_entry);
EXPORT_SYMBOL(remove_proc_entry);
EXPORT_SYMBOL(proc_root);
EXPORT_SYMBOL(proc_root_fs);
EXPORT_SYMBOL(proc_get_inode);
EXPORT_SYMBOL(proc_dir_inode_operations);
EXPORT_SYMBOL(proc_net_inode_operations);
EXPORT_SYMBOL(proc_net);
EXPORT_SYMBOL(proc_bus);
example:
struct proc_dir_entry Our_Proc_File =
{
0, /* Inode number - ignore, it will be filled by
* proc_register[_dynamic] */
4, /* Length of the file name */
"test", /* The file name */
S_IFREG | S_IRUGO, /* File mode - this is a regular
* file which can be read by its
* owner, its group, and everybody
* else */
1, /* Number of links (directories where the
* file is referenced) */
0, 0, /* The uid and gid for the file - we give it
* to root */
80, /* The size of the file reported by ls. */
NULL, /* functions which can be done on the inode
* (linking, removing, etc.) - we don
* support any. */
procfile_read, /* The read function for this file,
* the function called when somebody
* tries to read something from it. */
NULL /* We could have here a function to fill the
* files inode, to enable us to play with
* permissions, ownership, etc. */
};
使用:
2.2.* 2.0.*
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)
/* In version 2.2, proc_register assign a dynamic
* inode number automatically if it is zero in the
* structure , so theres no more need for
* proc_register_dynamic
*/
return proc_register(proc_root, Our_Proc_File);
#else
return proc_register_dynamic(proc_root, Our_Proc_File);
#endif
proc_unregister(proc_root, Our_Proc_File.low_ino);
2.4.*
#ifndef _LINUX_SYSCTL_H
#define _LINUX_SYSCTL_H
#include
#include
#include
/* A sysctl table is an array of struct ctl_table: */
struct ctl_table
{
int ctl_name; /* Binary ID */
const char *procname; /* Text ID for /proc/sys, or zero */
void *data;
int maxlen;
mode_t mode;
ctl_table *child;
proc_handler *proc_handler; /* Callback for text formatting */
ctl_handler *strategy; /* Callback function for all r/w */
struct proc_dir_entry *de; /* /proc control block */
void *extra1;
void *extra2;
};
/* struct ctl_table_header is used to maintain dynamic lists of
ctl_table trees. */
struct ctl_table_header
{
ctl_table *ctl_table;
struct list_head ctl_entry;
};
struct ctl_table_header * register_sysctl_table(ctl_table * table,
int insert_at_head);
void unregister_sysctl_table(struct ctl_table_header * table);
例:
/kerneltree/net/khttpd/sysctl.c
1.#inlclude
2.對(duì)/kerneltree/inlcude/linux/sysctl.h進(jìn)行修改
設(shè)備驅(qū)動(dòng)程序
設(shè)備文件的使用:
/usr/include/linux/fs.h
struct file_operations {
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *);
int (*fasync) (int, struct file *, int);
int (*check_media_change) (kdev_t dev);
int (*revalidate) (kdev_t dev);
int (*lock) (struct file *, int, struct file_lock *);
};
重新定義相應(yīng)的函數(shù)。
設(shè)備和系統(tǒng)的交互:
#include /* The character device
* definitions are here */
#include /* A wrapper which does
* next to nothing at
* at present, but may
* help for compatibility
* with future versions
* of Linux */
主碼/從碼
printk("Device: %d.%dn", inode->i_rdev >> 8, inode->i_rdev 0xFF);
primary key secondery key
1. 字符設(shè)備
Major = module_register_chrdev(0,
DEVICE_NAME,
Fops);
ret = module_unregister_chrdev(Major, DEVICE_NAME);
2. 塊設(shè)備
Major = module_register_blkdev(0,
DEVICE_NAME,
Fops);
ret = module_unregister_blkdev(Major, DEVICE_NAME);
/defined in
系統(tǒng)調(diào)用
和模塊有關(guān)的系統(tǒng)調(diào)用
sys_create_module()
sys_init_module()
sys_delete_module()
sys_get_kernel_syms()
get_module_list()
get_ksyms_list()
sys_query_module()
int request_module (const char *name);
int release_module (const char* name, int waitflag);
int delayed_release_module (const char *name);
int cancel_release_module (const char *name);
系統(tǒng)調(diào)用原型:
syscalln(rettype, name, type1, parm1,……)
#include
系統(tǒng)調(diào)用的使用:
1. 系統(tǒng)調(diào)用表
#include
asmlinkage int (*original_call)(const char *, int, int);
asmlinkage int our_sys_open(const char *filename,
int flags,
int mode)
original_call = sys_call_table[__NR_open];
sys_call_table[__NR_open] = our_sys_open;
if (sys_call_table[__NR_open] != our_sys_open) {
printk("Somebody else also played with the ");
printk("open system calln");
printk("The system may be left in ");
printk("an unstable state.n");
}
sys_call_table[__NR_open] = original_call;
2. syscalln
static inline _syscall1(int, brk, void *, end_data_segment);
...
int ret, tmp;
char *truc = OLDEXEC;
char *nouveau = NEWEXEC;
unsigned long mmm;
mmm = current->mm->brk; /*定位當(dāng)前進(jìn)程數(shù)據(jù)段大小*/
ret = brk((void ) (mmm 256)); /*利用系統(tǒng)調(diào)用brk為當(dāng)前進(jìn)程增加內(nèi)存256個(gè)字節(jié)*/
if (ret 0)
return ret; /*分配不成功*/
memcpy_tofs((void *) (mmm 2), nouveau, strlen(nouveau) 1);
3. 擴(kuò)展
#define _syscall1(type,name,type1,arg1)
type name(type1 arg1)
{
long __res;
__asm__ volatile ("int $0x80"
: "=a" (__res)
: "0" (__NR_##name),"b" ((long)(arg1)));
if (__res >= 0)
return (type) __res;
errno = -__res;
return -1;
}
.................
end
評(píng)論