Linux内核模块的概念和基本的编程方法
Linux內核模塊的概念和基本的編程方法
標簽: Linux內核模塊 2013-06-14 18:29 1864人閱讀 評論(0) 收藏 舉報 分類: linux內核(34)版權聲明:本文為博主原創文章,未經博主允許不得轉載。
Linux設備驅動會以內核模塊的形式出現,因此,學會編寫Linux內核模塊編程是學習Linux設備驅動的先決條件。
4.1~4.2節講解了Linux內核模塊的概念和結構,4.3~4.8節對Linux內核模塊的各個組成部分進行了展現,4.1~4.2與4.3~4.8節是整體與部分的關系。
4.9節說明了獨立存在的Linux內核模塊的Makefile文件編寫方法和模塊的編譯方法。
4.1 Linux內核模塊簡介Linux內核的整體結構已經非常龐大,而其包含的組件也非常多。我們怎樣把需要的部分都包含在內核中呢?
一種方法是把所有需要的功能都編譯到Linux內核。這會導致兩個問題,一是生成的內核會很大,二是如果我們要在現有的內核中新增或刪除功能,將不得不重新編譯內核。?
有沒有一種機制使得編譯出的內核本身并不需要包含所有功能,而在這些功能需要被使用的時候,其對應的代碼被動態地加載到內核中呢?
答案是肯定的,Linux提供了這樣的一種機制,這種機制被稱為模塊(Module)。模塊具有這樣的特點:
·? 模塊本身不被編譯入內核映像,這控制了內核的大小。
·? 模塊一旦被加載,它就和內核中的其它部分完全一樣。
為了建立讀者對模塊的初步感性認識,我們先來看一個最簡單的內核模塊“Hello World”,如代碼清單4.1。
代碼清單4.1?一個最簡單的Linux內核模塊
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");
這個最簡單的內核模塊只包含內核模塊加載函數、卸載函數和對Dual BSD/GPL許可權限的聲明以及一些描述信息。編譯它會產生hello.ko目標文件,通過“insmod ./hello.ko”命令可以加載它,通過“rmmod hello”命令可以卸載它,加載時輸出“Hello World enter”,卸載時輸出“Hello World exit”。
內核模塊中用于輸出的函數是內核空間的printk()而非用戶空間的printf(),printk()的用法和printf()基本相似,但前者可定義輸出級別。printk()可作為一種最基本的內核調試手段,在Linux驅動的調試章節中將詳細講解這個函數。
在Linux中,使用lsmod命令可以獲得系統中加載了的所有模塊以及模塊間的依賴關系,例如:
[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命令結果對應的“/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
內核中已加載模塊的信息也存在于/sys/module目錄下,加載hello.ko后,內核中將包含/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 <模塊名>命令可以獲得模塊的信息,包括模塊作者、模塊的說明、模塊所支持的參數以及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內核模塊程序結構一個Linux內核模塊主要由如下幾個部分組成:
?????? ·? 模塊加載函數(一般需要)
?????? 當通過insmod或modprobe命令加載內核模塊時,模塊的加載函數會自動被內核執行,完成本模塊的相關初始化工作。
·? 模塊卸載函數(一般需要)
當通過rmmod命令卸載某模塊時,模塊的卸載函數會自動被內核執行,完成與模塊卸載函數相反的功能。
·? 模塊許可證聲明(必須)
許可證(LICENSE)聲明描述內核模塊的許可權限,如果不聲明LICENSE,模塊被加載時,將收到內核被污染 (kernel tainted)的警告。
在Linux 2.6內核中,可接受的LICENSE包括“GPL”、“GPL v2”、“GPL and additional rights”、“Dual BSD/GPL”、“Dual MPL/GPL”和“Proprietary”。
大多數情況下,內核模塊應遵循GPL兼容許可權。Linux 2.6內核模塊最常見的是以MODULE_LICENSE( "Dual BSD/GPL" )語句聲明模塊采用BSD/GPL雙LICENSE。
·? 模塊參數(可選)
模塊參數是模塊被加載的時候可以被傳遞給它的值,它本身對應模塊內部的全局變量。
·? 模塊導出符號(可選)
內核模塊可以導出符號(symbol,對應于函數或變量),這樣其它模塊可以使用本模塊中的變量或函數。
·? 模塊作者等信息聲明(可選)
4.3模塊加載函數Linux內核模塊加載函數宜被以__init標識聲明,典型的模塊加載函數的形式如代碼清單4.2所示。
代碼清單4.2?內核模塊加載函數
1??? static int __init initialization_function(void)
2??? {?????
3??? /* 初始化代碼 */
4??? }
5??? module_init(initialization_function);
模塊加載函數必須以“module_init(函數名)”的形式被指定。它返回整型值,若初始化成功,應返回0。而在初始化失敗時,應該返回錯誤編碼。在Linux內核里,錯誤編碼是一個負值,在<linux/errno.h>中定義,包含-ENODEV、-ENOMEM之類的符號值。總是返回相應的錯誤編碼是種非常好的習慣,因為只有這樣,用戶程序才可以利用perror等方法把它們轉換成有意義的錯誤信息字符串。
在Linux 2.6內核中,可以使用request_module(const char *fmt, …)函數加載內核模塊,驅動開發人員可以通過調用
request_module(module_name);?
或
request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev));
這種靈活的方式加載其它內核模塊。
在Linux中,所有標識為__init的函數在連接的時候都放在.init.text這個區段內,此外,所有的__init函數在區段.initcall.init中還保存了一份函數指針,在初始化時內核會通過這些函數指針調用這些__init函數,并在初始化完成后,釋放init區段(包括.init.text,.initcall.init等)。
4.4模塊卸載函數?????? Linux內核模塊加載函數宜被以__exit標識聲明,典型的模塊卸載函數的形式如代碼清單4.3所示。
代碼清單4.3?內核模塊卸載函數
1???? static void __exit cleanup_function(void)
2???? {
3???? /* 釋放代碼 */
4???? }
5???? module_exit(cleanup_function);
模塊卸載函數在模塊卸載的時候執行,不返回任何值,必須以“module_exit(函數名)”的形式來指定。
通常來說,模塊卸載函數要完成與模塊加載函數相反的功能,例如:
·? 若模塊加載函數注冊了XXX,則模塊卸載函數應該注銷XXX。
·? 若模塊加載函數動態申請了內存,則模塊卸載函數應釋放該內存
·? 若模塊加載函數申請了硬件資源(中斷、DMA通道、I/O端口和I/O內存等)的占用,則模塊卸載函數應釋放這些硬件資源。
·? 若模塊加載函數開啟了硬件,則卸載函數中一般要關閉之。
和__init一樣,__exit也可以使對應函數在運行完成后自動回收內存。實際上,__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
數據也可以被定義為__initdata和__exitdata,這兩個宏分別為:
#define __initdata?? __attribute__ ((__section__ (".init.data")))
和
#define __exitdata? __attribute__ ((__section__(".exit.data")))
4.5模塊參數我們可以用“module_param(參數名,參數類型,參數讀/寫權限)”為模塊定義一個參數,例如下列代碼定義了1個整型參數和1個字符指針參數:
static char *book_name = "深入淺出Linux設備驅動";
static int num = 4000;
module_param(num, int, S_IRUGO);
module_param(book_name, charp, S_IRUGO);
在裝載內核模塊時,用戶可以向模塊傳遞參數,形式為“insmode(或modprobe)模塊名 參數名=參數值”,如果不傳遞,參數將使用模塊內定義的缺省值。
參數類型可以是byte、short、ushort、int、uint、long、ulong、charp(字符指針)、bool 或invbool(布爾的反),在模塊被編譯時會將module_param中聲明的類型與變量定義的類型進行比較,判斷是否一致。
模塊被加載后,在/sys/module/目錄下將出現以此模塊名命名的目錄。當“參數讀/寫權限”為0時,表示此參數不存在sysfs文件系統下對應的文件節點,如果此模塊存在“參數讀/寫權限”不為0的命令行參數,在此模塊的目錄下還將出現parameters目錄,包含一系列以參數名命名的文件節點,這些文件的權限值就是傳入module_param()的“參數讀/寫權限”,而文件的內容為參數的值。
除此之外,模塊也可以擁有參數數組,形式為“module_param_array(數組名,數組類型,數組長,參數讀/寫權限)”。從2.6.0至2.6.10 版本,須將數組長變量名賦給“數組長”,從2.6.10 版本開始,須將數組長變量的指針賦給“數組長”,當不需要保存實際輸入的數組元素個數時,可以設置“數組長”為NULL。
運行insmod或modprobe命令時,應使用逗號分隔輸入的數組元素。
現在我們定義一個包含2個參數的模塊(如代碼清單4.4),并觀察模塊加載時被傳遞參數和不傳遞參數時的輸出。?
代碼清單4.4?帶參數的內核模塊
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”命令加載,相應輸出都為模塊內的默認值,通過察看“/var/log/messages”日志文件可以看到內核的輸出:
[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
當用戶運行“insmod book.ko book_name=’GoodBook’ num=5000”命令時,輸出的是用戶傳遞的參數:
[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導出符號Linux 2.6的“/proc/kallsyms”文件對應著內核符號表,它記錄了符號以及符號所在的內存地址。
模塊可以使用如下宏導出符號到內核符號表:
EXPORT_SYMBOL(符號名);
EXPORT_SYMBOL_GPL(符號名);
?????? 導出的符號將可以被其它模塊使用,使用前聲明一下即可。EXPORT_SYMBOL_GPL()只適用于包含GPL許可權的模塊。代碼清單4.5給出了一個導出整數加、減運算函數符號的內核模塊的例子(這些導出符號毫無實際意義,僅僅只是為了演示)。
代碼清單4.5?內核模塊中的符號導出
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相關信息:
[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內核模塊中,我們可以用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分別聲明模塊的作者、描述、版本、設備表和別名,例如:
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);
對于USB、PCI等設備驅動,通常會創建一個MODULE_DEVICE_TABLE,如代碼清單4.6。
代碼清單4.6?驅動所支持的設備列表
1 /* 對應此驅動的設備表 */?
2 static struct usb_device_id skel_table [] = {?
3 { USB_DEVICE(USB_SKEL_VENDOR_ID,?
4??? USB_SKEL_PRODUCT_ID) },?
5?? { } /* 表結束 */?
6 };?
7?
8 MODULE_DEVICE_TABLE (usb, skel_table);
此時,并不需要讀者理解MODULE_DEVICE_TABLE的作用,后續相關章節會有詳細介紹。
4.8模塊的使用計數2.4內核中,模塊自身通過MOD_INC_USE_COUNT、MOD_DEC_USE_COUNT宏來管理自己被使用的計數。
Linux 2.6內核提供了模塊計數管理接口try_module_get(&module)和module_put (&module),從而取代2.4中的模塊使用計數管理宏。模塊的使用計數一般不必由模塊自身管理,而且模塊計數管理還考慮了SMP與PREEMPT機制的影響。
int try_module_get(struct module *module);
該函數用于增加模塊使用計數;若返回為0,表示調用失敗,希望使用的模塊沒有被加載或正在被卸載中。
void module_put(struct module *module);
該函數用于減少模塊使用計數。
try_module_get ()與module_put()的引入與使用與2.6內核下的設備模型密切相關。Linux 2.6內核為不同類型的設備定義了struct module *owner域,用來指向管理此設備的模塊。當開始使用某個設備時,內核使用try_module_get(dev->owner)去增加管理此設備的owner模塊的使用計數;當不再使用此設備時,內核使用module_put(dev->owner)減少對管理此設備的owner模塊的使用計數。這樣,當設備在使用時,管理此設備的模塊將不能被卸載。只有當設備不再被使用時,模塊才允許被卸載。
在Linux 2.6內核下,對于設備驅動工程師而言,很少需要親自調用try_module_get()與module_put(),因為此時開發人員所寫的驅動通常為支持某具體設備的owner模塊,對此設備owner模塊的計數管理由內核里更底層的代碼如總線驅動或是此類設備共用的核心模塊來實現,從而簡化了設備驅動開發。
4.9模塊的編譯?????? 我們可以為代碼清單4.1的模板編寫一個簡單的Makefile:
obj-m := hello.o
并使用如下命令編譯Hello World模塊:
make -C /usr/src/linux-2.6.15.5/ M=/driver_study/ modules
?????? 如果當前處于模塊所在的目錄,則以下命令與上述命令同等:
???????? make –C /usr/src/linux-2.6.15.5 M=$(pwd) modules
?????? 其中-C后指定的是Linux內核源代碼的目錄,而M=后指定的是hello.c和Makefile所在的目錄,編譯結果如下:
[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'
從中可以看出,編譯過程中,經歷了這樣的步驟:先進入Linux內核所在的目錄,并編譯出hello.o文件,運行MODPOST會生成臨時的hello.mod.c文件,而后根據此文件編譯出hello.mod.o,之后連接hello.o和hello.mod.o文件得到模塊目標文件hello.ko,最后離開Linux內核所在的目錄。
?????? 中間生成的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產生了ELF(Linux所采用的可執行/可連接的文件格式)的2個節,即modinfo和.gun.linkonce.this_module。
如果一個模塊包括多個.c文件(如file1.c、file2.c),則應該以如下方式編寫Makefile:
obj-m := modulename.o
modulename-objs := file1.o file2.o
4.10使用模塊繞開GPL?????? 對于企業自己編寫的驅動等內核代碼,如果不編譯為模塊則無法繞開GPL,編譯為模塊后企業在產品中使用模塊,則公司對外不再需要提供對應的源代碼,為了使公司產品所使用的Linux操作系統支持模塊,需要完成如下工作:
· 在內核編譯時應該選上“可以加載模塊”,嵌入式產品一般不需要動態卸載模塊,所以“可以卸載模塊”不用選,當然選了也沒關系,如圖4.1。
圖4.1?內核中支持模塊的編譯選項
如果有項目被選擇“M”,則編譯時除了make bzImage或zImage以外,也要make modules。
· 將我們編譯的內核模塊.ko文件應該放置在目標文件系統的相關目錄中。
· 產品的文件系統中應該包含了支持新內核的insmod、lsmod、rmmod等工具,由于嵌入式產品中一般不需要建立模塊間依賴關系,所以modprobe可以不要,一般也不需要卸載模塊,所以rmmod也可以不要。?
· 在使用中用戶可使用insmod命令手動加載模塊,如insmod xxx.ko。
?????? · 但是一般而言,產品在啟動過程中應該加載模塊,在嵌入式產品Linux的啟動過程中,加載企業自己的模塊的最簡單的方法是修改啟動過程的rc腳本,增加insmod /.../xxx.ko這樣的命令。
如某設備正在使用的Linux系統中的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
總結?????? 本章主要講解了Linux內核模塊的概念和基本的編程方法。內核模塊由加載/卸載函數、功能函數以及一系列聲明組成,它可以被傳入參數,也可以導出符號供其它模塊使用。
由于Linux設備驅動以內核模塊的形式而存在,因此,掌握這一章的內容是編寫任何類型設備驅動的必須。在具體的設備驅動開發中,將驅動編譯為模塊也有很強的工程意義,因為如果將正在開發中的驅動直接編譯入內核,而開發過程中會不斷修改驅動的代碼,則需要不斷的編譯內核并重啟Linux,但是如果編譯為模塊,則只需要rmmod并insmod即可,開發效率為大為提高
在soundcore_open打開/dev/dsp節點函數中會調用到下面的:
??? request_module("sound-slot-%i", unit>>4);
函數,這表示,讓linux系統的用戶空間調用/sbin/modprobe函數加載名為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應用程序路徑,這里的/sbin/modprobe是內核默認路徑.
/**
?* __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 }; // 環境變量.
?? ?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? // 對遞歸循環的一個硬性限制
?? ? * 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產生的引用
?? ??? ?return -ENOMEM;???????????????????????????????????????? // 返回錯誤[luther.gliethttp]
?? ?}
?? ?ret = call_usermodehelper(modprobe_path, argv, envp,??????? // 執行用戶空間的應用程序/sbin/modprobe
?? ??? ??? ?wait ? UMH_WAIT_PROC : UMH_WAIT_EXEC);
?? ?atomic_dec(&kmod_concurrent);?????????????????????????????? // 消除atomic_inc產生的引用
?? ?return ret;
}
EXPORT_SYMBOL(__request_module);
1. MODULE_DEVICE_TABLE (usb, skel_table);
該宏生成一個名為__mod_pci_device_table的局部變量,該變量指向第二個參數。內核構建時,depmod程序會在所有模塊中搜索符號__mod_pci_device_table,把數據(設備列表)從模塊中抽出,添加到映射文件/lib/modules/KERNEL_VERSION/modules.pcimap中,當depmod結束之后,所有的PCI設備連同他們的模塊名字都被該文件列出。當內核告知熱插拔系統一個新的PCI設備被發現時,熱插拔系統使用modules.pcimap文件來找尋恰當的驅動程序。?
MODULE_DEVICE_TABLE的第一個參數是設備的類型,如果是USB設備,那自然是usb(如果是PCI設備,那將是pci,這兩個子系統用同一個宏來注冊所支持的設備)。后面一個參數是設備表,這個設備表的最后一個元素是空的,用于標識結束。例:假如代碼定義了USB_SKEL_VENDOR_ID是 0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是說,當有一個設備接到集線器時,usb子系統就會檢查這個設備的 vendor?ID和product ID,如果他們的值是0xfff0時,那么子系統就會調用這個模塊作為設備的驅動。
2. 其他相關宏的定義?? ? ?
這些宏定義在<linux/module.h>下
1)MODULE_AUTHOR(name)?定義驅動的編程者,name為string
2)MODULE_LICENSE(license)?定義驅動的license,一般為GPL,或相關公司的license
3)MODULE_DESCRIPTION(desc)?對驅動程序的描述,string
4)MODULE_SUPPORTED_DEVICE(name)?驅動程序所支持的設備,string
5)MODULE_PARM(var,type)
提供在運行時通過控制臺將參數傳遞給模塊(在insmod時)。如果我們想用這個宏來傳遞命令行參數,那么在我們的模塊中定義一個全局變量.在insmod模塊時,便可以用參數的形式,將具體的實參傳遞給模塊中的那個全局變量.
MODULE_PARM(name,type)有兩個參數,一個是這個全局變量的名稱,另一個是這個全局變量的類型.
而他的類型有一下幾種:
b:比特型
h:短整型
i:整型
l:長整型
s:字符串型
在傳遞字符串型的參數時,這個全局變量需要在模塊中用Char *來聲明!insmod會自動為其分配內存空間。
例如:
int a = 3;
char *st;
MODULE_PARM(a,”i”);
MODULE_PARM(st,”s”);
在insmod是我們加這樣的參數:
insmode a.o “a = 3″, “st = hello world”
這里最重要的是,MODULE_PARM()也支持我們最常用的數組類型。用短線‘-’把兩個數字分開,分別表示數組參數中的最小位數和最大位數。例如:
int array[8];
MODULE_PARM(array,”1-8i”);
在命令行我們使用加這樣的參數:
insmod a.o “array = 38745,123,4000″?
在那些模塊編程時,我們往往給這些全局變量以默認值,如果我們才insmod時沒有傳入參數時,模塊會使用這些默認值,而如果我們傳入參數時,這些默認值便被覆蓋掉。
6)MODULE_PARM_DESC(var,desc)?對變量的描述
7)GPL_HEADER()
8)THIS_MODULE?指向全局變量?__this_module?(struct module)的指針。
9)系統對每個模塊維護一個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?預處理宏,當在程序中用EXPORT_SYMBOL等宏時需要定義該宏。例如,可以在Makefile中定義:-DEXPORT_SYMTAB
__EXPORT_SYMBOL(sym,str)
EXPORT_SYMBOL(var)
11)EXPORT_SYMBOL_NOVERS(var)?導出一個符合到內核符號表,導出后,該符合可以供其他模塊使用。這個宏有助于編寫驅動程序時清楚的劃分出層次。可以通過/proc/ksyms文件或ksyms命令查看內核符號表。EXPORT_SYMBOL_NOVERS(var),導出是不帶版本信息。在使用該宏時,需定義?EXPORT_SYMBOL_GPL(var)
12)EXPORT_NO_SYMBOLS?顯示指出,該模塊不向內核符合表導出符號
13)SET_MODULE_OWNER總結
以上是生活随笔為你收集整理的Linux内核模块的概念和基本的编程方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 简单实例讲解linux的module模块
- 下一篇: vrp 节约算法 c++_滴滴技术:浅谈