Linux是單內(nèi)核系統(tǒng),可通用計(jì)算平臺的外圍設(shè)備是頻繁變化的,不可能將所有的(包括將來即將出現(xiàn)的)設(shè)備的驅(qū)動程序都一次性編譯進(jìn)內(nèi)核,為了解決這個問題,Linux提出了可加載內(nèi)核模塊(Loadable Kernel Module,LKM)的概念,允許一個設(shè)備驅(qū)動通過模塊加載的方式,在內(nèi)核運(yùn)行起來之后"融入"內(nèi)核,加載進(jìn)內(nèi)核的模塊和本身就編譯進(jìn)內(nèi)核的模塊一模一樣。
一個程序在編譯的地址的相對關(guān)系就已經(jīng)確定了,運(yùn)行的時候只是進(jìn)行簡單的偏移,為了使模塊加載進(jìn)內(nèi)核后能夠被放置在正確的地址,并正確的調(diào)用系統(tǒng)的運(yùn)行的導(dǎo)出符號表,編譯模塊的時候必須要使用系統(tǒng)的編譯地址,并調(diào)用系統(tǒng)編譯出得靜態(tài)的導(dǎo)出符號表。即模塊必須使用系統(tǒng)的配置環(huán)境:Makefile+.config,一旦這兩個文件任意一個發(fā)生了變化,都很可能導(dǎo)致模塊的編譯地址與系統(tǒng)的編譯地址不匹配,造成運(yùn)行時的錯誤甚至宕機(jī)。
導(dǎo)出符號表
從提供系統(tǒng)運(yùn)行效率的角度,一個模塊不是也不應(yīng)該是完全獨(dú)立的,即一個模塊往往會調(diào)用其他模塊提供的功能來實(shí)現(xiàn)自己的功能,這樣做能更好實(shí)現(xiàn)系統(tǒng)的分工并提高效率。Linux為了實(shí)現(xiàn)模塊間的相互調(diào)用,設(shè)計(jì)了導(dǎo)出符號表,每個模塊都可以將自己的一個私有的標(biāo)號導(dǎo)出到系統(tǒng)層級,以使該標(biāo)號對其他模塊可見,系統(tǒng)在編譯一個模塊的時候會自動導(dǎo)出這個模塊的導(dǎo)出符號表到modules.syms文件(如果沒有導(dǎo)出任何符號,可以為空),并在加載一個模塊的時候會自動將該模塊的導(dǎo)出符號表與系統(tǒng)自身的導(dǎo)出符號表合并。一個系統(tǒng)的源碼的導(dǎo)出符號表一般在源碼頂層目錄的modules.syms文件中,查看正在運(yùn)行的系統(tǒng)導(dǎo)出符號表使用cat /proc/kallsyms。注意,正如前面解釋的,我們的模塊之所以能夠正常運(yùn)行,一個重要原因就是編譯我們模塊使用的符號地址就是編譯內(nèi)核時使用的符號地址,所以運(yùn)行起來雖然地址會有偏移,但是模塊中相關(guān)的符號的地址也會和內(nèi)核地址一起偏移,也就還能找得到?;谶@種思想,我們也可以直接查看系統(tǒng)當(dāng)前運(yùn)行的地址,將地址賦值給一個函數(shù)指針并使用,也是沒有問題的,當(dāng)然,這只是闡述原理,并不建議這么寫模塊。
下面這個例子可以看出編譯出的地址和運(yùn)行時的地址是不一樣的:
導(dǎo)出符號表可以大大的提高系統(tǒng)的運(yùn)行效率,這也是只有開源系統(tǒng)才能提供的一個強(qiáng)大的功能,但是,導(dǎo)出符號表的引入會導(dǎo)致一個小小的麻煩--模塊的依賴,當(dāng)我們使用lsmod的時候,就可以查看系統(tǒng)當(dāng)前的模塊,其最后兩列分別是該模塊被引用的次數(shù)以及引用該模塊的內(nèi)核模塊,當(dāng)一個模塊被其他模塊引用時,我們是不能進(jìn)行卸載的,同樣,如果模塊A依賴于模塊B,那么如果模塊B不加載的時候模塊A也加載不了。在編寫多模塊的時候尤其要注意這個問題,可以寫一個腳本管理多個依賴模塊。Linux內(nèi)核使用兩個宏來導(dǎo)出一個模塊的符號
EXPORT_SYMBOL(符號名)EXPORT_SYMBOL_GPL(符號名)
模塊編譯方法
借助內(nèi)核的Makefile,編譯出的XXX.ko(Kernel Object)就是可加載到該內(nèi)核的外部模塊,為了利用內(nèi)核的Makefile,我們可以將編譯外部模塊的Makefile寫成如下的格式:
ifneq ($(KERNELRELEASE),) export-objs = demo.o obj-m = extern.oelse KERNELDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd)endifall: $(MAKE) -C $(KERNELDIR) M=$(PWD) modulesclean: .tmp_versions Module.symvers modules.order .tmp_versions .*.cmd *.o *.ko *.mod.c
這個簡單的Makfile是利用ubuntu主機(jī)的源碼Makefile來編譯模塊,學(xué)習(xí)模塊編程的開始階段在主機(jī)進(jìn)行編譯調(diào)試更方便一點(diǎn),下面我解釋一下這個Makefile,首先,我們的思路還是通過內(nèi)核的Makefile來準(zhǔn)備我們的模塊,而內(nèi)核的Makefile一旦執(zhí)行,就會給KERNELRELEASE這個變量賦值,所以第一次進(jìn)入我們這個Makfile的時候,這個變量還是空,所以執(zhí)行else的部分——給相關(guān)的變量賦值,make默認(rèn)編譯第一個目標(biāo)all,make -C $(KERNELRELEASE)就是進(jìn)入到KERNELRELEASE指定的目錄并執(zhí)行里面的Makefile,顯然,這就是我們內(nèi)核源碼的頂層Makefile,接下來的選項(xiàng)M=$(PWD) modules都是傳入這個頂層Makefile的參數(shù),表示我要編譯一個模塊,這個模塊位于M指定的目錄,所以內(nèi)核會進(jìn)行相關(guān)的配置并最終進(jìn)入到"這個模塊所在的目錄",此時,我們的這個Makefile會再被進(jìn)入一次,這一次是從內(nèi)核Makefile中跳入這里的,,KERNELRELEASE已經(jīng)被定義過,內(nèi)核Makefile想要的就是obj-m后面指定的要編譯的目標(biāo)文件,所以內(nèi)核Makfile就會找到我們寫的模塊源文件進(jìn)行編譯。如此我們就得到了能在ubuntu下執(zhí)行的xxx.ko文件,如果需要在開發(fā)板上運(yùn)行,只需要將內(nèi)核路徑改成開發(fā)板運(yùn)行系統(tǒng)的源碼路徑即可,同時記得要導(dǎo)出相關(guān)的環(huán)境變量( ARCH, CROSS_COMPILE )
注冊/注銷模塊
Linux為每個模塊都預(yù)留了相應(yīng)的地址,注冊模塊即讓該模塊對內(nèi)核可見,這也是模塊工作的先決條件。注冊之后,我們就可以通過查看內(nèi)核輸出信息dmesg命令來查看模塊的運(yùn)行情況。經(jīng)常使用內(nèi)核函數(shù)printk()來輸出系統(tǒng)信息進(jìn)行打印調(diào)試。使用insmod XXX.ko加載一個模塊,使用rmmod XXX.ko卸載一個模塊,使用lsmod查看當(dāng)前系統(tǒng)中的模塊及其引用情況
insmod使用的是init_module()系統(tǒng)調(diào)用,這個系統(tǒng)調(diào)用的實(shí)現(xiàn)是sys_init_module()
rmmod使用delete_module()系統(tǒng)調(diào)用,這個系統(tǒng)調(diào)用的實(shí)現(xiàn)是sys_delete_module()
模塊的程序框架
#include #include #include /* 構(gòu)造/析構(gòu)函數(shù) */static int __init mydemo_init(void){ //構(gòu)造設(shè)備/驅(qū)動對象 //初始化設(shè)備/驅(qū)動對象 //注冊設(shè)備/驅(qū)動對象 //必要的硬件初始化} static void __exit mydemo_exit(void){ //回收資源 //注銷設(shè)備/驅(qū)動對象} /* 加載/卸載模塊 */module_init(mydemo_init); module_exit(mydemo_exit); /* 授權(quán) */MODULE_LICENSE("GPL"); MODULE_AUTHOR("XJ");MODULE_DESCRIPTIPON("mymydemo");/* 導(dǎo)出符號 */EXPORT_SYMBOL(data);
注意這里的授權(quán)是必須的,如果一個模塊沒有授權(quán),那么很多需要該授權(quán)的函數(shù)甚至都不能使用,同理,不合適的授權(quán)也會導(dǎo)致模塊運(yùn)行或加載的錯誤,所以初學(xué)者一定不要忽視這個授權(quán),相關(guān)授權(quán)的選項(xiàng)在"linux/module.h"中,這里我把相關(guān)的說明貼出來供大家參考
/* * The following license idents are currently accepted as indicating free * software modules * * "GPL" [GNU Public License v2 or later] * "GPL v2" [GNU Public License v2] * "GPL and additional rights" [GNU Public License v2 rights and more] * "Dual BSD/GPL" [GNU Public License v2 * or BSD license choice] * "Dual MIT/GPL" [GNU Public License v2 * or MIT license choice] * "Dual MPL/GPL" [GNU Public License v2 * or Mozilla license choice] * * The following other idents are available * * "Proprietary" [Non free products] */
另一個細(xì)節(jié)是Linux內(nèi)核源碼的默認(rèn)頭文件路徑是頂層目錄的include目錄,所以包含頭文件的時候include可以省略,
第一個Linux模塊
#include #include #include static int __init demo_init(void){ printk(KERN_INFO"demo_init:%s,%s,%d"__FILE__,__func__,__LINE__); return 0;}static void __exit demo_exit(void){ printk(KERN_INFO"demo_exit:%s,%s,%d"__FILE__,__func__,__LINE__);}module_init(demo_init);module_exit(demo_exit);MODULE_LICENSE("GPL");
執(zhí)行insmod xjDemo.ko,查看執(zhí)行結(jié)果
模塊傳參
我們編寫的模塊還可以在insmod的時候傳入?yún)?shù),Linux提供了幾個宏(函數(shù))用于接收外部的參數(shù)。模塊內(nèi)部使用這些函數(shù),只需執(zhí)行insmod xjDemo.ko num=2,insmod mydemo.ko i=10,insmod mydemo.ko extstr="hello"?等命令就可以將參數(shù)傳入模塊
module_param(num,type,perm); //接收一個傳入的int數(shù)據(jù)module_param(num,type,perm); //接收一個傳入的charp數(shù)據(jù)module_param_array(num,type,nump,perm); //接收一個數(shù)組module_param_string(name,string,len,perm); //接收一個字符串MODULE_PARAM_DESC("parameter description");
?
評論
查看更多