Linux内核模块的概念和基本的编程方法
Linux內(nèi)核模塊的概念和基本的編程方法
標(biāo)簽: Linux內(nèi)核模塊 2013-06-14 18:29 1864人閱讀 評論(0) 收藏 舉報 分類: linux內(nèi)核(34)版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。
Linux設(shè)備驅(qū)動會以內(nèi)核模塊的形式出現(xiàn),因此,學(xué)會編寫Linux內(nèi)核模塊編程是學(xué)習(xí)Linux設(shè)備驅(qū)動的先決條件。
4.1~4.2節(jié)講解了Linux內(nèi)核模塊的概念和結(jié)構(gòu),4.3~4.8節(jié)對Linux內(nèi)核模塊的各個組成部分進行了展現(xiàn),4.1~4.2與4.3~4.8節(jié)是整體與部分的關(guān)系。
4.9節(jié)說明了獨立存在的Linux內(nèi)核模塊的Makefile文件編寫方法和模塊的編譯方法。
4.1 Linux內(nèi)核模塊簡介Linux內(nèi)核的整體結(jié)構(gòu)已經(jīng)非常龐大,而其包含的組件也非常多。我們怎樣把需要的部分都包含在內(nèi)核中呢?
一種方法是把所有需要的功能都編譯到Linux內(nèi)核。這會導(dǎo)致兩個問題,一是生成的內(nèi)核會很大,二是如果我們要在現(xiàn)有的內(nèi)核中新增或刪除功能,將不得不重新編譯內(nèi)核。?
有沒有一種機制使得編譯出的內(nèi)核本身并不需要包含所有功能,而在這些功能需要被使用的時候,其對應(yīng)的代碼被動態(tài)地加載到內(nèi)核中呢?
答案是肯定的,Linux提供了這樣的一種機制,這種機制被稱為模塊(Module)。模塊具有這樣的特點:
·? 模塊本身不被編譯入內(nèi)核映像,這控制了內(nèi)核的大小。
·? 模塊一旦被加載,它就和內(nèi)核中的其它部分完全一樣。
為了建立讀者對模塊的初步感性認識,我們先來看一個最簡單的內(nèi)核模塊“Hello World”,如代碼清單4.1。
代碼清單4.1?一個最簡單的Linux內(nèi)核模塊
1? #include <linux/init.h>
2? #include <linux/module.h>
3? MODULE_LICENSE("Dual BSD/GPL");
4? static int hello_init(void)
5? {
6??? printk(KERN_INFO " Hello World enter\n");
7??? return 0;
8? }
9? static void hello_exit(void)
10 {
11?? printk(KERN_INFO " Hello World exit\n ");
12 }
13 module_init(hello_init);
14 module_exit(hello_exit);
15?
16 MODULE_AUTHOR("Song Baohua");
17 MODULE_DESCRIPTION("A simple Hello World Module");
18 MODULE_ALIAS("a simplest module");
這個最簡單的內(nèi)核模塊只包含內(nèi)核模塊加載函數(shù)、卸載函數(shù)和對Dual BSD/GPL許可權(quán)限的聲明以及一些描述信息。編譯它會產(chǎn)生hello.ko目標(biāo)文件,通過“insmod ./hello.ko”命令可以加載它,通過“rmmod hello”命令可以卸載它,加載時輸出“Hello World enter”,卸載時輸出“Hello World exit”。
內(nèi)核模塊中用于輸出的函數(shù)是內(nèi)核空間的printk()而非用戶空間的printf(),printk()的用法和printf()基本相似,但前者可定義輸出級別。printk()可作為一種最基本的內(nèi)核調(diào)試手段,在Linux驅(qū)動的調(diào)試章節(jié)中將詳細講解這個函數(shù)。
在Linux中,使用lsmod命令可以獲得系統(tǒng)中加載了的所有模塊以及模塊間的依賴關(guān)系,例如:
[root@localhost driver_study]# lsmod
Module????????????????? Size?? Used by
hello??????????????????? 1568??? 0?
ohci1394??????????????? 32716?? 0?
ide_scsi???????????????? 16708?? 0?
ide_cd????????????????? 39392?? 0?
cdrom????????????????? 36960?? 1 ide_cd
lsmod命令實際上讀取并分析“/proc/modules”文件,與上述lsmod命令結(jié)果對應(yīng)的“/proc/modules”文件如下:
[root@localhost driver_study]# cat /proc/modules?
hello 1568 0 - Live 0xc8859000
ohci1394 32716 0 - Live 0xc88c8000
ieee1394 94420 1 ohci1394, Live 0xc8840000
ide_scsi 16708 0 - Live 0xc883a000
ide_cd 39392 0 - Live 0xc882f000
cdrom 36960 1 ide_cd, Live 0xc8876000
內(nèi)核中已加載模塊的信息也存在于/sys/module目錄下,加載hello.ko后,內(nèi)核中將包含/sys/module/hello目錄,該目錄下又包含一個refcnt文件和一個sections目錄,在/sys/module/hello目錄下運行tree –a得到如下目錄樹:?
[root@localhost hello]# tree -a
.
|-- refcnt
`-- sections
??? |-- .bss
??? |-- .data
??? |-- .gnu.linkonce.this_module
??? |-- .rodata
??? |-- .rodata.str1.1
??? |-- .strtab
??? |-- .symtab
??? |-- .text
??? `-- __versions
?????? modprobe命令比insmod命令要強大,它在加載某模塊時,會同時加載該模塊所依賴的其它模塊。使用modprobe命令加載的模塊若以“modprobe -r filename”的方式卸載將同時卸載其依賴的模塊。
?????? 使用modinfo <模塊名>命令可以獲得模塊的信息,包括模塊作者、模塊的說明、模塊所支持的參數(shù)以及vermagic:
[root@localhost driver_study]# modinfo hello.ko
filename:?????? hello.ko
license:??????? Dual BSD/GPL
author:???????? Song Baohua
description:??? A simple Hello World Module
alias:????????? a simplest module
vermagic:?????? 2.6.15.5 686 gcc-3.2
depends:???
4.2 Linux內(nèi)核模塊程序結(jié)構(gòu)一個Linux內(nèi)核模塊主要由如下幾個部分組成:
?????? ·? 模塊加載函數(shù)(一般需要)
?????? 當(dāng)通過insmod或modprobe命令加載內(nèi)核模塊時,模塊的加載函數(shù)會自動被內(nèi)核執(zhí)行,完成本模塊的相關(guān)初始化工作。
·? 模塊卸載函數(shù)(一般需要)
當(dāng)通過rmmod命令卸載某模塊時,模塊的卸載函數(shù)會自動被內(nèi)核執(zhí)行,完成與模塊卸載函數(shù)相反的功能。
·? 模塊許可證聲明(必須)
許可證(LICENSE)聲明描述內(nèi)核模塊的許可權(quán)限,如果不聲明LICENSE,模塊被加載時,將收到內(nèi)核被污染 (kernel tainted)的警告。
在Linux 2.6內(nèi)核中,可接受的LICENSE包括“GPL”、“GPL v2”、“GPL and additional rights”、“Dual BSD/GPL”、“Dual MPL/GPL”和“Proprietary”。
大多數(shù)情況下,內(nèi)核模塊應(yīng)遵循GPL兼容許可權(quán)。Linux 2.6內(nèi)核模塊最常見的是以MODULE_LICENSE( "Dual BSD/GPL" )語句聲明模塊采用BSD/GPL雙LICENSE。
·? 模塊參數(shù)(可選)
模塊參數(shù)是模塊被加載的時候可以被傳遞給它的值,它本身對應(yīng)模塊內(nèi)部的全局變量。
·? 模塊導(dǎo)出符號(可選)
內(nèi)核模塊可以導(dǎo)出符號(symbol,對應(yīng)于函數(shù)或變量),這樣其它模塊可以使用本模塊中的變量或函數(shù)。
·? 模塊作者等信息聲明(可選)
4.3模塊加載函數(shù)Linux內(nèi)核模塊加載函數(shù)宜被以__init標(biāo)識聲明,典型的模塊加載函數(shù)的形式如代碼清單4.2所示。
代碼清單4.2?內(nèi)核模塊加載函數(shù)
1??? static int __init initialization_function(void)
2??? {?????
3??? /* 初始化代碼 */
4??? }
5??? module_init(initialization_function);
模塊加載函數(shù)必須以“module_init(函數(shù)名)”的形式被指定。它返回整型值,若初始化成功,應(yīng)返回0。而在初始化失敗時,應(yīng)該返回錯誤編碼。在Linux內(nèi)核里,錯誤編碼是一個負值,在<linux/errno.h>中定義,包含-ENODEV、-ENOMEM之類的符號值。總是返回相應(yīng)的錯誤編碼是種非常好的習(xí)慣,因為只有這樣,用戶程序才可以利用perror等方法把它們轉(zhuǎn)換成有意義的錯誤信息字符串。
在Linux 2.6內(nèi)核中,可以使用request_module(const char *fmt, …)函數(shù)加載內(nèi)核模塊,驅(qū)動開發(fā)人員可以通過調(diào)用
request_module(module_name);?
或
request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev));
這種靈活的方式加載其它內(nèi)核模塊。
在Linux中,所有標(biāo)識為__init的函數(shù)在連接的時候都放在.init.text這個區(qū)段內(nèi),此外,所有的__init函數(shù)在區(qū)段.initcall.init中還保存了一份函數(shù)指針,在初始化時內(nèi)核會通過這些函數(shù)指針調(diào)用這些__init函數(shù),并在初始化完成后,釋放init區(qū)段(包括.init.text,.initcall.init等)。
4.4模塊卸載函數(shù)?????? Linux內(nèi)核模塊加載函數(shù)宜被以__exit標(biāo)識聲明,典型的模塊卸載函數(shù)的形式如代碼清單4.3所示。
代碼清單4.3?內(nèi)核模塊卸載函數(shù)
1???? static void __exit cleanup_function(void)
2???? {
3???? /* 釋放代碼 */
4???? }
5???? module_exit(cleanup_function);
模塊卸載函數(shù)在模塊卸載的時候執(zhí)行,不返回任何值,必須以“module_exit(函數(shù)名)”的形式來指定。
通常來說,模塊卸載函數(shù)要完成與模塊加載函數(shù)相反的功能,例如:
·? 若模塊加載函數(shù)注冊了XXX,則模塊卸載函數(shù)應(yīng)該注銷XXX。
·? 若模塊加載函數(shù)動態(tài)申請了內(nèi)存,則模塊卸載函數(shù)應(yīng)釋放該內(nèi)存
·? 若模塊加載函數(shù)申請了硬件資源(中斷、DMA通道、I/O端口和I/O內(nèi)存等)的占用,則模塊卸載函數(shù)應(yīng)釋放這些硬件資源。
·? 若模塊加載函數(shù)開啟了硬件,則卸載函數(shù)中一般要關(guān)閉之。
和__init一樣,__exit也可以使對應(yīng)函數(shù)在運行完成后自動回收內(nèi)存。實際上,__init和__exit都是宏,其定義分別為:
#define __init??????? __attribute__ ((__section__ (".init.text")))
和
#ifdef MODULE
#define __exit??????? __attribute__ ((__section__(".exit.text")))
#else
#define __exit??????? __attribute_used__ __attribute__ ((__section__(".exit.text")))
#endif
數(shù)據(jù)也可以被定義為__initdata和__exitdata,這兩個宏分別為:
#define __initdata?? __attribute__ ((__section__ (".init.data")))
和
#define __exitdata? __attribute__ ((__section__(".exit.data")))
4.5模塊參數(shù)我們可以用“module_param(參數(shù)名,參數(shù)類型,參數(shù)讀/寫權(quán)限)”為模塊定義一個參數(shù),例如下列代碼定義了1個整型參數(shù)和1個字符指針參數(shù):
static char *book_name = "深入淺出Linux設(shè)備驅(qū)動";
static int num = 4000;
module_param(num, int, S_IRUGO);
module_param(book_name, charp, S_IRUGO);
在裝載內(nèi)核模塊時,用戶可以向模塊傳遞參數(shù),形式為“insmode(或modprobe)模塊名 參數(shù)名=參數(shù)值”,如果不傳遞,參數(shù)將使用模塊內(nèi)定義的缺省值。
參數(shù)類型可以是byte、short、ushort、int、uint、long、ulong、charp(字符指針)、bool 或invbool(布爾的反),在模塊被編譯時會將module_param中聲明的類型與變量定義的類型進行比較,判斷是否一致。
模塊被加載后,在/sys/module/目錄下將出現(xiàn)以此模塊名命名的目錄。當(dāng)“參數(shù)讀/寫權(quán)限”為0時,表示此參數(shù)不存在sysfs文件系統(tǒng)下對應(yīng)的文件節(jié)點,如果此模塊存在“參數(shù)讀/寫權(quán)限”不為0的命令行參數(shù),在此模塊的目錄下還將出現(xiàn)parameters目錄,包含一系列以參數(shù)名命名的文件節(jié)點,這些文件的權(quán)限值就是傳入module_param()的“參數(shù)讀/寫權(quán)限”,而文件的內(nèi)容為參數(shù)的值。
除此之外,模塊也可以擁有參數(shù)數(shù)組,形式為“module_param_array(數(shù)組名,數(shù)組類型,數(shù)組長,參數(shù)讀/寫權(quán)限)”。從2.6.0至2.6.10 版本,須將數(shù)組長變量名賦給“數(shù)組長”,從2.6.10 版本開始,須將數(shù)組長變量的指針賦給“數(shù)組長”,當(dāng)不需要保存實際輸入的數(shù)組元素個數(shù)時,可以設(shè)置“數(shù)組長”為NULL。
運行insmod或modprobe命令時,應(yīng)使用逗號分隔輸入的數(shù)組元素。
現(xiàn)在我們定義一個包含2個參數(shù)的模塊(如代碼清單4.4),并觀察模塊加載時被傳遞參數(shù)和不傳遞參數(shù)時的輸出。?
代碼清單4.4?帶參數(shù)的內(nèi)核模塊
1? #include <linux/init.h>????
2? #include <linux/module.h>?
3? MODULE_LICENSE("Dual BSD/GPL");????????????????????????????????
4??
5? static char *book_name = "dissecting Linux Device Driver";?????
6? static int num = 4000;????
7????????
8? static int book_init(void)???????????
9? {????????????????????????????????
10??? printk(KERN_INFO " book name:%s\n",book_name);????????????????????????
11??? printk(KERN_INFO " book num:%d\n",num);???????????????????????????????
12??? return 0;????????????????????????????????
13 }????????????????????????????????
14 static void book_exit(void)????????????????????????????????
15 {????????????????????????????????
16?? printk(KERN_INFO " Book module exit\n ");????????????????????????????
17 }????????????????????????????????
18 module_init(book_init);????????????????????????????????
19 module_exit(book_exit);????????????????????????????????
20 module_param(num, int, S_IRUGO);????????????????????????????????
21 module_param(book_name, charp, S_IRUGO);
22?????????????????????????????????
23 MODULE_AUTHOR("Song Baohua, author@linuxdriver.cn");
24 MODULE_DESCRIPTION("A simple Module for testing module params");
25 MODULE_VERSION("V1.0");
對上述模塊運行“insmod book.ko”命令加載,相應(yīng)輸出都為模塊內(nèi)的默認值,通過察看“/var/log/messages”日志文件可以看到內(nèi)核的輸出:
[root@localhost driver_study]# tail -n 2 /var/log/messages
Jul? 2 01:03:10 localhost kernel:? <6> book name:dissecting Linux Device Driver
Jul? 2 01:03:10 localhost kernel:? book num:4000
當(dāng)用戶運行“insmod book.ko book_name=’GoodBook’ num=5000”命令時,輸出的是用戶傳遞的參數(shù):
[root@localhost driver_study]# tail -n 2 /var/log/messages
Jul? 2 01:06:21 localhost kernel:? <6> book name:GoodBook
Jul? 2 01:06:21 localhost kernel:? book num:5000
4.6導(dǎo)出符號Linux 2.6的“/proc/kallsyms”文件對應(yīng)著內(nèi)核符號表,它記錄了符號以及符號所在的內(nèi)存地址。
模塊可以使用如下宏導(dǎo)出符號到內(nèi)核符號表:
EXPORT_SYMBOL(符號名);
EXPORT_SYMBOL_GPL(符號名);
?????? 導(dǎo)出的符號將可以被其它模塊使用,使用前聲明一下即可。EXPORT_SYMBOL_GPL()只適用于包含GPL許可權(quán)的模塊。代碼清單4.5給出了一個導(dǎo)出整數(shù)加、減運算函數(shù)符號的內(nèi)核模塊的例子(這些導(dǎo)出符號毫無實際意義,僅僅只是為了演示)。
代碼清單4.5?內(nèi)核模塊中的符號導(dǎo)出
1? #include <linux/init.h>????????????????????????????????
2? #include <linux/module.h>????????????????????????????????
3? MODULE_LICENSE("Dual BSD/GPL");????????????????????????????????
4??????????????????????????????????
5? int add_integar(int a,int b)????????????????????????????????
6? {????????????????????????????????
7? return a+b;?????????????????????????????
8? }?
9?????????????????????????????????
10 int sub_integar(int a,int b)????????????????????????????????
11 {????????????????????????????????
12?? return a-b;?????????????????????????????
13 }????????????????????????????
14?
15 EXPORT_SYMBOL(add_integar);
16 EXPORT_SYMBOL(sub_integar);
?????? 從“/proc/kallsyms”文件中找出add_integar、sub_integar相關(guān)信息:
[root@localhost driver_study]# cat /proc/kallsyms | grep integar
c886f050 r __kcrctab_add_integar??????? [export]
c886f058 r __kstrtab_add_integar??????? [export]
c886f070 r __ksymtab_add_integar??????? [export]
c886f054 r __kcrctab_sub_integar??????? [export]
c886f064 r __kstrtab_sub_integar??????? [export]
c886f078 r __ksymtab_sub_integar??????? [export]
c886f000 T add_integar? [export]
c886f00b T sub_integar? [export]
13db98c9 a __crc_sub_integar??? [export]
e1626dee a __crc_add_integar??? [export]
4.7模塊聲明與描述在Linux內(nèi)核模塊中,我們可以用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分別聲明模塊的作者、描述、版本、設(shè)備表和別名,例如:
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);
對于USB、PCI等設(shè)備驅(qū)動,通常會創(chuàng)建一個MODULE_DEVICE_TABLE,如代碼清單4.6。
代碼清單4.6?驅(qū)動所支持的設(shè)備列表
1 /* 對應(yīng)此驅(qū)動的設(shè)備表 */?
2 static struct usb_device_id skel_table [] = {?
3 { USB_DEVICE(USB_SKEL_VENDOR_ID,?
4??? USB_SKEL_PRODUCT_ID) },?
5?? { } /* 表結(jié)束 */?
6 };?
7?
8 MODULE_DEVICE_TABLE (usb, skel_table);
此時,并不需要讀者理解MODULE_DEVICE_TABLE的作用,后續(xù)相關(guān)章節(jié)會有詳細介紹。
4.8模塊的使用計數(shù)2.4內(nèi)核中,模塊自身通過MOD_INC_USE_COUNT、MOD_DEC_USE_COUNT宏來管理自己被使用的計數(shù)。
Linux 2.6內(nèi)核提供了模塊計數(shù)管理接口try_module_get(&module)和module_put (&module),從而取代2.4中的模塊使用計數(shù)管理宏。模塊的使用計數(shù)一般不必由模塊自身管理,而且模塊計數(shù)管理還考慮了SMP與PREEMPT機制的影響。
int try_module_get(struct module *module);
該函數(shù)用于增加模塊使用計數(shù);若返回為0,表示調(diào)用失敗,希望使用的模塊沒有被加載或正在被卸載中。
void module_put(struct module *module);
該函數(shù)用于減少模塊使用計數(shù)。
try_module_get ()與module_put()的引入與使用與2.6內(nèi)核下的設(shè)備模型密切相關(guān)。Linux 2.6內(nèi)核為不同類型的設(shè)備定義了struct module *owner域,用來指向管理此設(shè)備的模塊。當(dāng)開始使用某個設(shè)備時,內(nèi)核使用try_module_get(dev->owner)去增加管理此設(shè)備的owner模塊的使用計數(shù);當(dāng)不再使用此設(shè)備時,內(nèi)核使用module_put(dev->owner)減少對管理此設(shè)備的owner模塊的使用計數(shù)。這樣,當(dāng)設(shè)備在使用時,管理此設(shè)備的模塊將不能被卸載。只有當(dāng)設(shè)備不再被使用時,模塊才允許被卸載。
在Linux 2.6內(nèi)核下,對于設(shè)備驅(qū)動工程師而言,很少需要親自調(diào)用try_module_get()與module_put(),因為此時開發(fā)人員所寫的驅(qū)動通常為支持某具體設(shè)備的owner模塊,對此設(shè)備owner模塊的計數(shù)管理由內(nèi)核里更底層的代碼如總線驅(qū)動或是此類設(shè)備共用的核心模塊來實現(xiàn),從而簡化了設(shè)備驅(qū)動開發(fā)。
4.9模塊的編譯?????? 我們可以為代碼清單4.1的模板編寫一個簡單的Makefile:
obj-m := hello.o
并使用如下命令編譯Hello World模塊:
make -C /usr/src/linux-2.6.15.5/ M=/driver_study/ modules
?????? 如果當(dāng)前處于模塊所在的目錄,則以下命令與上述命令同等:
???????? make –C /usr/src/linux-2.6.15.5 M=$(pwd) modules
?????? 其中-C后指定的是Linux內(nèi)核源代碼的目錄,而M=后指定的是hello.c和Makefile所在的目錄,編譯結(jié)果如下:
[root@localhost driver_study]# make -C /usr/src/linux-2.6.15.5/ M=/driver_study/ modules
make: Entering directory `/usr/src/linux-2.6.15.5'
? CC
? /driver_study/hello.o
/driver_study/hello.c:18:35: warning: no newline at end of file
? Building modules, stage 2.
? MODPOST
? CC????? /driver_study/hello.mod.o
? LD
? /driver_study/hello.ko
make: Leaving directory `/usr/src/linux-2.6.15.5'
從中可以看出,編譯過程中,經(jīng)歷了這樣的步驟:先進入Linux內(nèi)核所在的目錄,并編譯出hello.o文件,運行MODPOST會生成臨時的hello.mod.c文件,而后根據(jù)此文件編譯出hello.mod.o,之后連接hello.o和hello.mod.o文件得到模塊目標(biāo)文件hello.ko,最后離開Linux內(nèi)核所在的目錄。
?????? 中間生成的hello.mod.c文件的源代碼如代碼清單4.7所示。
代碼清單4.7?模塊編譯時生成的.mod.c文件
1??? #include <linux/module.h>
2??? #include <linux/vermagic.h>
3??? #include <linux/compiler.h>
4????
5??? MODULE_INFO(vermagic, VERMAGIC_STRING);
6????
7??? struct module __this_module
8??? __attribute__((section(".gnu.linkonce.this_module"))) = {
9???? .name = KBUILD_MODNAME,
10??? .init = init_module,
11??? #ifdef CONFIG_MODULE_UNLOAD
12??? .exit = cleanup_module,
13??? #endif
14??? };
15????
16??? static const char __module_depends[]
17??? __attribute_used__
18??? __attribute__((section(".modinfo"))) =
19??? "depends=";
hello.mod.o產(chǎn)生了ELF(Linux所采用的可執(zhí)行/可連接的文件格式)的2個節(jié),即modinfo和.gun.linkonce.this_module。
如果一個模塊包括多個.c文件(如file1.c、file2.c),則應(yīng)該以如下方式編寫Makefile:
obj-m := modulename.o
modulename-objs := file1.o file2.o
4.10使用模塊繞開GPL?????? 對于企業(yè)自己編寫的驅(qū)動等內(nèi)核代碼,如果不編譯為模塊則無法繞開GPL,編譯為模塊后企業(yè)在產(chǎn)品中使用模塊,則公司對外不再需要提供對應(yīng)的源代碼,為了使公司產(chǎn)品所使用的Linux操作系統(tǒng)支持模塊,需要完成如下工作:
· 在內(nèi)核編譯時應(yīng)該選上“可以加載模塊”,嵌入式產(chǎn)品一般不需要動態(tài)卸載模塊,所以“可以卸載模塊”不用選,當(dāng)然選了也沒關(guān)系,如圖4.1。
圖4.1?內(nèi)核中支持模塊的編譯選項
如果有項目被選擇“M”,則編譯時除了make bzImage或zImage以外,也要make modules。
· 將我們編譯的內(nèi)核模塊.ko文件應(yīng)該放置在目標(biāo)文件系統(tǒng)的相關(guān)目錄中。
· 產(chǎn)品的文件系統(tǒng)中應(yīng)該包含了支持新內(nèi)核的insmod、lsmod、rmmod等工具,由于嵌入式產(chǎn)品中一般不需要建立模塊間依賴關(guān)系,所以modprobe可以不要,一般也不需要卸載模塊,所以rmmod也可以不要。?
· 在使用中用戶可使用insmod命令手動加載模塊,如insmod xxx.ko。
?????? · 但是一般而言,產(chǎn)品在啟動過程中應(yīng)該加載模塊,在嵌入式產(chǎn)品Linux的啟動過程中,加載企業(yè)自己的模塊的最簡單的方法是修改啟動過程的rc腳本,增加insmod /.../xxx.ko這樣的命令。
如某設(shè)備正在使用的Linux系統(tǒng)中的rc腳本是這樣的:?
mount /proc
mount /var
mount /dev/pts
mkdir /var/log
mkdir /var/run
mkdir /var/ftp
mkdir -p /var/spool/cron
mkdir /var/config
...
insmod /usr/lib/company_driver.ko 2> /dev/null
/usr/bin/userprocess
/var/config/rc
總結(jié)?????? 本章主要講解了Linux內(nèi)核模塊的概念和基本的編程方法。內(nèi)核模塊由加載/卸載函數(shù)、功能函數(shù)以及一系列聲明組成,它可以被傳入?yún)?shù),也可以導(dǎo)出符號供其它模塊使用。
由于Linux設(shè)備驅(qū)動以內(nèi)核模塊的形式而存在,因此,掌握這一章的內(nèi)容是編寫任何類型設(shè)備驅(qū)動的必須。在具體的設(shè)備驅(qū)動開發(fā)中,將驅(qū)動編譯為模塊也有很強的工程意義,因為如果將正在開發(fā)中的驅(qū)動直接編譯入內(nèi)核,而開發(fā)過程中會不斷修改驅(qū)動的代碼,則需要不斷的編譯內(nèi)核并重啟Linux,但是如果編譯為模塊,則只需要rmmod并insmod即可,開發(fā)效率為大為提高
在soundcore_open打開/dev/dsp節(jié)點函數(shù)中會調(diào)用到下面的:
??? request_module("sound-slot-%i", unit>>4);
函數(shù),這表示,讓linux系統(tǒng)的用戶空間調(diào)用/sbin/modprobe函數(shù)加載名為sound-slot-0.ko模塊
#define request_module(mod...) __request_module(true, mod)
#define request_module_nowait(mod...) __request_module(false, mod)
luther@gliethttp:~$ cat /proc/sys/kernel/modprobe?
/sbin/modprobe
我們也可以向/proc/sys/kernel/modprobe添加新的modprobe應(yīng)用程序路徑,這里的/sbin/modprobe是內(nèi)核默認路徑.
/**
?* __request_module - try to load a kernel module?????? // 嘗試加載一個ko模塊[luther.gliethttp]
?* @wait: wait (or not) for the operation to complete
?* @fmt: printf style format string for the name of the module
?* @...: arguments as specified in the format string
?*
?* Load a module using the user mode module loader. The function returns
?* zero on success or a negative errno code on failure. Note that a
?* successful module load does not mean the module did not then unload
?* and exit on an error of its own. Callers must check that the service
?* they requested is now available not blindly invoke it.
?*
?* If module auto-loading support is disabled then this function
?* becomes a no-operation.
?*/
int __request_module(bool wait, const char *fmt, ...)
{
?? ?va_list args;
?? ?char module_name[MODULE_NAME_LEN];
?? ?unsigned int max_modprobes;
?? ?int ret;
// char modprobe_path[KMOD_PATH_LEN] = "/sbin/modprobe";
?? ?char *argv[] = { modprobe_path, "-q", "--", module_name, NULL };
?? ?static char *envp[] = { "HOME=/",
?? ??? ??? ??? ?"TERM=linux",
?? ??? ??? ??? ?"PATH=/sbin:/usr/sbin:/bin:/usr/bin",
?? ??? ??? ??? ?NULL }; // 環(huán)境變量.
?? ?static atomic_t kmod_concurrent = ATOMIC_INIT(0);
#define MAX_KMOD_CONCURRENT 50?? ?/* Completely arbitrary value - KAO */
?? ?static int kmod_loop_msg;
?? ?va_start(args, fmt);
?? ?ret = vsnprintf(module_name, MODULE_NAME_LEN, fmt, args);?? // 生成module名,這里就是sound-slot-0
?? ?va_end(args);
?? ?if (ret >= MODULE_NAME_LEN)
?? ??? ?return -ENAMETOOLONG;
?? ?/* If modprobe needs a service that is in a module, we get a recursive? // 對遞歸循環(huán)的一個硬性限制
?? ? * loop.? Limit the number of running kmod threads to max_threads/2 or? // 最多同時運行max_modprobes個modprobe用戶程序
?? ? * MAX_KMOD_CONCURRENT, whichever is the smaller.? A cleaner method???? // 如果超過,那么__request_module返回-ENOMEM
?? ? * would be to run the parents of this process, counting how many times // [luther.gliethttp]
?? ? * kmod was invoked.? That would mean accessing the internals of the
?? ? * process tables to get the command line, proc_pid_cmdline is static
?? ? * and it is not worth changing the proc code just to handle this case.?
?? ? * KAO.
?? ? *
?? ? * "trace the ppid" is simple, but will fail if someone's
?? ? * parent exits.? I think this is as good as it gets. --RR
?? ? */
?? ?max_modprobes = min(max_threads/2, MAX_KMOD_CONCURRENT);??? // 最多同時運行max_modprobes個modprobe用戶程序
?? ?atomic_inc(&kmod_concurrent);
?? ?if (atomic_read(&kmod_concurrent) > max_modprobes) {
?? ??? ?/* We may be blaming an innocent here, but unlikely */
?? ??? ?if (kmod_loop_msg++ < 5)
?? ??? ??? ?printk(KERN_ERR
?? ??? ??? ??????? "request_module: runaway loop modprobe %s\n",
?? ??? ??? ??????? module_name);
?? ??? ?atomic_dec(&kmod_concurrent);?????????????????????????? // 消除前面atomic_inc產(chǎn)生的引用
?? ??? ?return -ENOMEM;???????????????????????????????????????? // 返回錯誤[luther.gliethttp]
?? ?}
?? ?ret = call_usermodehelper(modprobe_path, argv, envp,??????? // 執(zhí)行用戶空間的應(yīng)用程序/sbin/modprobe
?? ??? ??? ?wait ? UMH_WAIT_PROC : UMH_WAIT_EXEC);
?? ?atomic_dec(&kmod_concurrent);?????????????????????????????? // 消除atomic_inc產(chǎn)生的引用
?? ?return ret;
}
EXPORT_SYMBOL(__request_module);
1. MODULE_DEVICE_TABLE (usb, skel_table);
該宏生成一個名為__mod_pci_device_table的局部變量,該變量指向第二個參數(shù)。內(nèi)核構(gòu)建時,depmod程序會在所有模塊中搜索符號__mod_pci_device_table,把數(shù)據(jù)(設(shè)備列表)從模塊中抽出,添加到映射文件/lib/modules/KERNEL_VERSION/modules.pcimap中,當(dāng)depmod結(jié)束之后,所有的PCI設(shè)備連同他們的模塊名字都被該文件列出。當(dāng)內(nèi)核告知熱插拔系統(tǒng)一個新的PCI設(shè)備被發(fā)現(xiàn)時,熱插拔系統(tǒng)使用modules.pcimap文件來找尋恰當(dāng)?shù)尿?qū)動程序。?
MODULE_DEVICE_TABLE的第一個參數(shù)是設(shè)備的類型,如果是USB設(shè)備,那自然是usb(如果是PCI設(shè)備,那將是pci,這兩個子系統(tǒng)用同一個宏來注冊所支持的設(shè)備)。后面一個參數(shù)是設(shè)備表,這個設(shè)備表的最后一個元素是空的,用于標(biāo)識結(jié)束。例:假如代碼定義了USB_SKEL_VENDOR_ID是 0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是說,當(dāng)有一個設(shè)備接到集線器時,usb子系統(tǒng)就會檢查這個設(shè)備的 vendor?ID和product ID,如果他們的值是0xfff0時,那么子系統(tǒng)就會調(diào)用這個模塊作為設(shè)備的驅(qū)動。
2. 其他相關(guān)宏的定義?? ? ?
這些宏定義在<linux/module.h>下
1)MODULE_AUTHOR(name)?定義驅(qū)動的編程者,name為string
2)MODULE_LICENSE(license)?定義驅(qū)動的license,一般為GPL,或相關(guān)公司的license
3)MODULE_DESCRIPTION(desc)?對驅(qū)動程序的描述,string
4)MODULE_SUPPORTED_DEVICE(name)?驅(qū)動程序所支持的設(shè)備,string
5)MODULE_PARM(var,type)
提供在運行時通過控制臺將參數(shù)傳遞給模塊(在insmod時)。如果我們想用這個宏來傳遞命令行參數(shù),那么在我們的模塊中定義一個全局變量.在insmod模塊時,便可以用參數(shù)的形式,將具體的實參傳遞給模塊中的那個全局變量.
MODULE_PARM(name,type)有兩個參數(shù),一個是這個全局變量的名稱,另一個是這個全局變量的類型.
而他的類型有一下幾種:
b:比特型
h:短整型
i:整型
l:長整型
s:字符串型
在傳遞字符串型的參數(shù)時,這個全局變量需要在模塊中用Char *來聲明!insmod會自動為其分配內(nèi)存空間。
例如:
int a = 3;
char *st;
MODULE_PARM(a,”i”);
MODULE_PARM(st,”s”);
在insmod是我們加這樣的參數(shù):
insmode a.o “a = 3″, “st = hello world”
這里最重要的是,MODULE_PARM()也支持我們最常用的數(shù)組類型。用短線‘-’把兩個數(shù)字分開,分別表示數(shù)組參數(shù)中的最小位數(shù)和最大位數(shù)。例如:
int array[8];
MODULE_PARM(array,”1-8i”);
在命令行我們使用加這樣的參數(shù):
insmod a.o “array = 38745,123,4000″?
在那些模塊編程時,我們往往給這些全局變量以默認值,如果我們才insmod時沒有傳入?yún)?shù)時,模塊會使用這些默認值,而如果我們傳入?yún)?shù)時,這些默認值便被覆蓋掉。
6)MODULE_PARM_DESC(var,desc)?對變量的描述
7)GPL_HEADER()
8)THIS_MODULE?指向全局變量?__this_module?(struct module)的指針。
9)系統(tǒng)對每個模塊維護一個usage counter,以便決定何時可以安全的卸載模塊。
下面的宏用來對該usage counter操作,usage counter可以通過/proc/modules文件查看
MOD_INC_USE_COUNT?
MOD_DEC_USE_COUNT
MOD_IN_USE
MODULE_DEVICE_TABLE
10)EXPORT_SYMTAB?預(yù)處理宏,當(dāng)在程序中用EXPORT_SYMBOL等宏時需要定義該宏。例如,可以在Makefile中定義:-DEXPORT_SYMTAB
__EXPORT_SYMBOL(sym,str)
EXPORT_SYMBOL(var)
11)EXPORT_SYMBOL_NOVERS(var)?導(dǎo)出一個符合到內(nèi)核符號表,導(dǎo)出后,該符合可以供其他模塊使用。這個宏有助于編寫驅(qū)動程序時清楚的劃分出層次。可以通過/proc/ksyms文件或ksyms命令查看內(nèi)核符號表。EXPORT_SYMBOL_NOVERS(var),導(dǎo)出是不帶版本信息。在使用該宏時,需定義?EXPORT_SYMBOL_GPL(var)
12)EXPORT_NO_SYMBOLS?顯示指出,該模塊不向內(nèi)核符合表導(dǎo)出符號
13)SET_MODULE_OWNER總結(jié)
以上是生活随笔為你收集整理的Linux内核模块的概念和基本的编程方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 简单实例讲解linux的module模块
- 下一篇: linux文件管理服务,linux系统配