Linux内核模块学习笔记(转载)
Linux內(nèi)核模塊 ?? Linux設(shè)備驅(qū)動(dòng)會(huì)以內(nèi)核模塊的形式出現(xiàn),因此學(xué)會(huì)編寫Linux內(nèi)核模塊編程是學(xué)習(xí)linux設(shè)備驅(qū)動(dòng)的先決條件。
1.1linux內(nèi)核模塊簡介
? ? Linux內(nèi)核的整體結(jié)構(gòu)非常龐大,其包含的組件非常多。我們?nèi)绾伟研枰牟糠侄及趦?nèi)核中呢? ●把需要的功能都編譯到linux內(nèi)核。 ●以模塊方式擴(kuò)展內(nèi)核功能。
??? 為了使學(xué)生對(duì)模塊建立初步的感性認(rèn)識(shí),我們先來看一個(gè)最簡單的內(nèi)核模塊”hello world”,代碼如下: #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void) { ??? printk("hello world\n”); ??? return 0; } static void hello_exit(void) { ??? printk(1? "hello module exit\n "); } module_init(hello_init); module_exit(hello_exit);
MODULE_AUTHOR("zky"); MODULE_DESCRIPTION("A simple? hello Module "); MODULE_VERSION("V1.0");
?? 這個(gè)最簡單的內(nèi)核模塊只包含內(nèi)核加載函數(shù)、卸載函數(shù)和對(duì)Dual BSD/GPL許可權(quán)限的聲明以及一些描述信息。編譯會(huì)產(chǎn)生hello.ko目標(biāo)文件,通過”insmod ./hello.ko”命令可以加載它,通過”rmmod hello”命令可以卸載它,加載時(shí)輸出”hello world”, 卸載時(shí)輸出”hello module exit”,查看輸出信息可通過dmesg命令。
??? 內(nèi)核模塊中用于輸出的函數(shù)式內(nèi)核空間的printk()而非用戶空間的printf(),printk()的用法和printf()相似,但前者可定義輸出級(jí)別。printk()可作為一種最基本的內(nèi)核調(diào)試手段。
printk有8個(gè)loglevel,定義在<linux/kernel.h>中: #define KERN_EMERG????????? "<0>"??? /* system is unusable */ #define KERN_ALERT????????? "<1>"??? /* action must be taken immediately */ #define KERN_CRIT???????????? "<2>"??? /* critical conditions */ #define KERN_ERR?????????????? "<3>"??? /* error conditions */ #define KERN_WARNING??? ? "<4>"??? /* warning conditions */ #define KERN_NOTICE???????? "<5>"??? /* normal but significant condition */ #define KERN_INFO???????????? "<6>"??? /* informational */ #define KERN_DEBUG????????? "<7>"??? /* debug-level messages */
??? 未指定優(yōu)先級(jí)的默認(rèn)級(jí)別定義在/kernel/printk.c中: #define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */
??? 當(dāng)優(yōu)先級(jí)的值小于console_loglevel這個(gè)整數(shù)變量的值,信息才能顯示出來。而console_loglevel的初始值DEFAULT_CONSOLE_LOGLEVEL也定義在/kernel/printk.c中: #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */
在linux系統(tǒng)中,使用lsmod命令可以獲得系統(tǒng)中加載了的所有模塊以及模塊間的依賴關(guān)系,例如:
lsmod命令實(shí)際上讀取并分析/proc/modules文件,與上述lsmod命令結(jié)果對(duì)應(yīng)的/proc/modules文件如下:
??? 內(nèi)核中已加載模塊的信息也存在于/sys/module目錄下,加載hello.ko后,內(nèi)核中將包含/sys/module/hello目錄,該目錄下又包含一個(gè)refcnt文件和一個(gè)sections目錄,在/sys/module/hello目錄下運(yùn)行”tree -a”得到如下目錄樹:
? ? Modprobe命令比insmod命令要強(qiáng)大,它在加載某模塊時(shí)會(huì)同時(shí)加載該模塊所依賴的其他模塊。使用modprobe命令加載的模塊若以”modprobe –r? filename”的方式卸載將同時(shí)其他依賴的模塊。
使用modinfo <模塊名>命令可以獲得模塊的信息,包括模塊的作者、模塊的說明、模塊所支持的參數(shù)以及vermagic,如下所示:
1.2模塊的編譯
??? 接下來我們來簡單看看模塊是如何構(gòu)造的,模塊的構(gòu)造和用戶空間應(yīng)用程序的構(gòu)造過程有很大的不同。實(shí)際上對(duì)本章先前給出的”hello world”示例來說,下面一行就足以了: Obj-m? := hello.o
??? 如果大家熟悉make但對(duì)2.6內(nèi)核構(gòu)造系統(tǒng)還不熟悉的話,則可能會(huì)對(duì)此makefile的工作方式感到疑惑。畢竟上面這行并不是makefile文件的常見格式。問題的答案就是內(nèi)核的構(gòu)造系統(tǒng)處理了其余的問題。上面的賦值語句說明了又一個(gè)模塊需要從目標(biāo)文件hello.o中構(gòu)造,而從目標(biāo)文件中構(gòu)造的模塊名稱為hello.ko。
??? 如果我們要構(gòu)造的模塊名稱為module.ko,并有兩個(gè)源文件生成(比如file1.c和file2.c),則正確的makefile可如下編寫: Obj-m? := module.o Module-objs :=file1.o file2.o
??? 為了讓上面這種類型的makefile文件正常工作,必須在大的內(nèi)核構(gòu)造系統(tǒng)環(huán)境中調(diào)用它們。如果內(nèi)核源碼保存在~/kernel-2.6目錄中,則用來構(gòu)造模塊的make命令應(yīng)該為(在包含模塊源代碼和makefile的目錄中鍵入): Make –C? ~/kernel-2.6 M=`pwd` modules
??? 上述命令首先改變目錄到-C選項(xiàng)指定的位置(既內(nèi)核源代碼目錄),其中保存有內(nèi)核的頂層makefile文件,M=選項(xiàng)讓該makefile在構(gòu)造modules目標(biāo)之前返回到模塊源代碼目錄。然后modules目標(biāo)指向obj-m變量中設(shè)定的模塊;在上面的例子中,我們將該變量設(shè)置成了module.o。
??? 上面這樣的make命令還是有些煩人,因此內(nèi)核開發(fā)者又開發(fā)了一種makefile方法,這種方法將使得內(nèi)核樹之外的模塊構(gòu)造變得更加容易,其技巧就是用下面的方法來編寫makefile: #如果已定義KERNELRELEASE,則說明是從內(nèi)核構(gòu)造系統(tǒng)調(diào)用的,因此可利用其內(nèi)建語句。 Ifneq($(KERNELRELEASE),) obj-m :=hello.o #否則,是直接從命令行調(diào)用的,這時(shí)要調(diào)用內(nèi)核構(gòu)造系統(tǒng) Else KERNELDIR? ?=~/KERNEL-2.6 PWD? := $(shell pwd) Default: $(MAKE) –C $(KERNELDIR) M=$(PWD) modules endif
?? 需要注意的是上面的makefile文件并不完整;一個(gè)真正的makefile文件應(yīng)該包含通常用來清除無用文件的目標(biāo),安裝模塊的目標(biāo)等等。
1.3linux內(nèi)核模塊的結(jié)構(gòu)
??? 一個(gè)linux內(nèi)核模塊主要由以下幾個(gè)部分組成: ●模塊加載函數(shù)(必須) ??? 當(dāng)通過insmod或modprobe命令加載內(nèi)核模塊時(shí),模塊的加載函數(shù)會(huì)自動(dòng)被內(nèi)核執(zhí)行,完成本模塊相關(guān)初始化工作。 ●模塊卸載函數(shù)(必須) ??? 當(dāng)通過rmmod命令卸載模塊時(shí),模塊的卸載函數(shù)會(huì)自動(dòng)被內(nèi)核執(zhí)行,完成與模塊加載函數(shù)相反的功能。 ●模塊許可證聲明(必須) ??? 模塊許可證(LICENCE)聲明描述內(nèi)核模塊的許可權(quán)限,如果不聲明LICENCE,模塊被加載時(shí)將收到內(nèi)核被污染的警告。
??? 在2.6內(nèi)核中,可接受得LICENSE包括“GPL”、“GPL v2”、“GPL and additional right”、“Dual BSD/GPL”、“Dual MPL/GPL和“Proprietary”。
??? 大多數(shù)情況下,內(nèi)核模塊應(yīng)遵循GPL兼容許可權(quán)。Linux2.6內(nèi)核模塊最常見的是以MODULE_LICENSE(“Dual BSD/GPL”)語句聲明模塊采用BSD/GPL雙LICENSE。 ●模塊參數(shù)(可選) 模塊參數(shù)是模塊被加載的時(shí)候可以被傳遞給他的值,它本身對(duì)應(yīng)模塊內(nèi)部的全局變量。 ●模塊導(dǎo)出符號(hào)(可選) 內(nèi)核模塊可以導(dǎo)出符號(hào)(symbol,對(duì)應(yīng)于函數(shù)或變量),這樣其他模塊可以使用本模塊中的變量或函數(shù)。 ●模塊作者等信息聲明(可選)。
1.4模塊加載函數(shù)
??? Linux內(nèi)核模塊加載函數(shù)一般以__init標(biāo)識(shí)聲明,典型的模塊加載函數(shù)的形式如下: Static? int? __init? initialization_function(void) { ???? //初始化代碼 } Module_init(initialization_function);
??? 模塊加載函數(shù)必須以“module_init(函數(shù)名)”的形式指定。它返回整形值,若初始化成功,應(yīng)返回0。而在初始化失敗時(shí)。應(yīng)該返回錯(cuò)誤編碼。在linux內(nèi)核里,錯(cuò)誤編碼是一個(gè)負(fù)值,在<linux/errno.h>中定義,包含-ENODEV、-ENOMEM之類的符號(hào)值。返回相應(yīng)的錯(cuò)誤編碼是種非常好的習(xí)慣,因?yàn)橹挥羞@樣,用戶程序才可以利用perror等方法把它們轉(zhuǎn)換成有意義的錯(cuò)誤信息字符串。
??? 在linux2.6內(nèi)核中,所有標(biāo)識(shí)為__init的函數(shù)在連接的時(shí)候都會(huì)放在.init.text(這是module_init宏在目標(biāo)代碼中增加的一個(gè)特殊區(qū)段,用于說明內(nèi)核初始化函數(shù)的所在位置)這個(gè)區(qū)段中,此外,所有的__init函數(shù)在區(qū)段.initcall.init中還保存著一份函數(shù)指針,在初始化時(shí)內(nèi)核會(huì)通過這些函數(shù)指針調(diào)用這些__init函數(shù),并在初始化完成后釋放init區(qū)段(包括.init.text和.initcall.init等)。所以大家應(yīng)注意不要在結(jié)束初始化后仍要使用的函數(shù)上使用這個(gè)標(biāo)記。
1.5模塊卸載函數(shù)
??? Linux內(nèi)核卸載模塊函數(shù)一般以__exit標(biāo)識(shí)聲明,典型的模塊卸載函數(shù)的形式如下: Satic void __exit cleanup_function(void) { ?? //釋放代碼 } Module_exit(cleanup_function);
??? 模塊卸載函數(shù)在模塊卸載時(shí)被調(diào)用,不返回任何值,必須以”module_exit(函數(shù)名)”的形式來指定。
??? 一般來說,模塊卸載函數(shù)完成與模塊加載函數(shù)相反的功能: ●如果模塊加載函數(shù)注冊(cè)了 XXX模塊,則模塊卸載函數(shù)應(yīng)注銷XXX。 ●若模塊加載函數(shù)動(dòng)體申請(qǐng)了內(nèi)存,則模塊卸載函數(shù)應(yīng)釋放該內(nèi)存。 ●若模塊加載函數(shù)申請(qǐng)了硬件資源,則模塊卸載函數(shù)應(yīng)釋放這些硬件資源。 ●若模塊加載函數(shù)開啟了硬件,則模塊卸載函數(shù)應(yīng)關(guān)閉硬件。
??? 和__init一樣__exit也可以使對(duì)應(yīng)函數(shù)在運(yùn)行完成后自動(dòng)回收內(nèi)存。
1.6模塊參數(shù)
??? 我們可以用”module_param(參數(shù)名,參數(shù)類型,參數(shù)讀/寫權(quán)限)”為模塊定義一個(gè)參數(shù),例如下列代碼定義了一個(gè)整形參數(shù)和一個(gè)字符指針參數(shù): Static? char *book_name = “l(fā)inux 模塊”; Static?? int? num = 4000; Module_param(num, int, S_IRUGO); Module_param(book_name, charp, S_IRUGO);
??? 在裝載內(nèi)核模塊時(shí),用戶可以向模塊傳遞參數(shù),形式為”insmod (或 modprobe) 模塊名 參數(shù)名=參數(shù)值”,如果不傳遞,參數(shù)將使用模塊內(nèi)定義的默認(rèn)值。
??? 參數(shù)類型可以是byte、short、ushort、int、uint、long、ulong、charp、bool、或invbool(布爾的反),在模塊被編譯時(shí)會(huì)將module_param中聲明的類型與變量定義的類型進(jìn)行比較,判斷是否一致。
?? 模塊被加載后,在/sys/module/目錄下將出現(xiàn)以此模塊命名的目錄。當(dāng)“參數(shù)讀/寫權(quán)限”為0時(shí),表示此參數(shù)不存在sysfs文件系統(tǒng)下對(duì)應(yīng)的文件節(jié)點(diǎn),如果此模塊存在“參數(shù)讀/寫權(quán)限”不為0的命令行參數(shù),在此模塊的目錄下還將出現(xiàn)parameters目錄,包含一系列以參數(shù)名命名的文件節(jié)點(diǎn),這些文件的權(quán)限值就是傳入module_param()的“參數(shù)讀/寫權(quán)限”,而文件的內(nèi)容為參數(shù)的值。
?? 現(xiàn)在我們定義一個(gè)包含兩個(gè)參數(shù)的模塊,并觀察模塊加載時(shí)被傳遞參數(shù)和不傳遞參數(shù)時(shí)的輸出。 #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL");
static char *book_name = "dissecting Linux Device Driver"; static int num = 4000;
static int book_init(void)
{ printk(KERN_INFO " book name:%s\n",book_name); printk(KERN_INFO " book num:%d\n",num); return 0; } static void book_exit(void) { printk(KERN_INFO " Book module exit\n "); } module_init(book_init); module_exit(book_exit); module_param(num, int, S_IRUGO); module_param(book_name, charp, S_IRUGO);
MODULE_AUTHOR("zky"); MODULE_DESCRIPTION("A simple Module for testing module params"); MODULE_VERSION("V1.0");
??? 對(duì)上述模塊運(yùn)新“insmod? book.ko”命令加載,相應(yīng)輸出都為模塊內(nèi)的默認(rèn)值,通過查看“/var/log/messages”日志文件可以看到內(nèi)核的輸出,如下所示:
??? 當(dāng)用戶運(yùn)行“insmod book.ko book_name=’mybook’ num=3000”命令時(shí),輸出的是用戶傳遞的參數(shù),如下所示:
1.7導(dǎo)出符號(hào)
??? Linux2.6的/proc/kallsyms文件對(duì)應(yīng)著內(nèi)核符號(hào)表,它記錄了符號(hào)以及符號(hào)所在的內(nèi)存地址。模塊可使用如下宏導(dǎo)出符號(hào)到內(nèi)核符號(hào)表: EXPORT_SYMBOL(符號(hào)名); EXPORT_SYMBOL_GPL(符號(hào)名);
??? 導(dǎo)出的符號(hào)將可以被其他模塊使用,使用前聲明一下即可。EXPORT_SYMBOL_GPL()只適用于包含GPL許可權(quán)的模塊。如下代碼給出了一個(gè)導(dǎo)出整數(shù)加、減運(yùn)算函數(shù)符號(hào)的內(nèi)核模塊的例子。 #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL");
int add_integar(int a,int b) { return a+b;
}
int sub_integar(int a,int b) { return a-b; }
EXPORT_SYMBOL(add_integar); EXPORT_SYMBOL(sub_integar);
??? 從/proc/kallsyms文件中找出add_integar、sub_integar相關(guān)信息:
1.8模塊聲明與描述
?? 在linux模塊中,我們可以使用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分別聲明模塊的作者、描述、版本、設(shè)備表和別名,例如: MODULE_AUTHOR(author); MODULE_DESCRIPTION(description); MODULE_VERSION(version); MODULE_DEVICE_TABLE(device table); MODULE_ALIAS(alternate_name);
??? 對(duì)于USB、PCI等設(shè)備驅(qū)動(dòng),通常會(huì)創(chuàng)建一個(gè)MODULE_DEVICE_TABLE,如下所示:
1.9模塊與GPL
??? 對(duì)于自己編寫的驅(qū)動(dòng)等內(nèi)核代碼,如果不編譯為模塊則無法繞開GPL,編譯為模塊后企業(yè)在產(chǎn)品中使用模塊,則公司對(duì)外不需要提供對(duì)應(yīng)的源代碼,為了使國內(nèi)公司產(chǎn)品所使用的Linux操作系統(tǒng)支持模塊,需要完成如下工作。 ●在內(nèi)核編譯時(shí)應(yīng)該選上”Enable loadble module support”,嵌入式產(chǎn)品一般不需要?jiǎng)討B(tài)卸載模塊,所以“可以卸載模塊”不用選,當(dāng)然選了也沒有關(guān)系,如圖:
??? 如果有項(xiàng)目被選擇”M”,則編譯時(shí)除了make bzImage以外,也要make modules. ●將我們編譯的內(nèi)核模塊.ko文件放置在目標(biāo)文件系統(tǒng)的相關(guān)目錄中。 ●產(chǎn)品的文件系統(tǒng)中應(yīng)該包含了新內(nèi)核的insmod、lsmod、rmmod等工具。 ●在使用中用戶可使用insmod 命令手動(dòng)加載模塊,如insmod xxx.ko
轉(zhuǎn)載于:https://www.cnblogs.com/sn-dnv-aps/archive/2012/11/04/2754276.html
總結(jié)
以上是生活随笔為你收集整理的Linux内核模块学习笔记(转载)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 弱类型语言的优势:C#的委托概念在Jav
- 下一篇: 无支付宝怎么领健康码