linux中引入模塊機制的好處:1、應用程序在退出時,可以不管資源的釋放或者其他的清除工作,但是模塊的退出函數卻必須仔細此撤銷初始化函數所作的一切;2、該機制有助于縮短模塊的開發周期,即注冊和卸載都很靈活方便。
本教程操作環境:linux7.3系統、Dell G3電腦。
Linux中引入模塊機制有什么好處?
首先,模塊是預先注冊自己以便服務于將來的某個請求,然后他的初始化函數就立即結束。換句話說,模塊初始化函數的任務就是為以后調用函數預先作準備。
好處:
-
1) 應用程序在退出時,可以不管資源的釋放或者其他的清除工作,但是模塊的退出函數卻必須仔細此撤銷初始化函數所作的一切。
-
2) 該機制有助于縮短模塊的開發周期。即:注冊和卸載都很靈活方便。
Linux模塊機制淺析
Linux允許用戶通過插入模塊,實現干預內核的目的。一直以來,對linux的模塊機制都不夠清晰,因此本文對內核模塊的加載機制進行簡單地分析。
模塊的Hello World!
我們通過創建一個簡單的模塊進行測試。首先是源文件main.c和Makefile。
florian@florian-pc:~/module$ cat main.c
#include<linux> #include<linux> ? static?int?__init?init(void) { ????printk("Hi?module!n"); ????return?0; } ? static?void?__exit?exit(void) { ????printk("Bye?module!n"); } ? module_init(init); module_exit(exit);</linux></linux>
其中init為模塊入口函數,在模塊加載時被調用執行,exit為模塊出口函數,在模塊卸載被調用執行。
florian@florian-pc:~/module$ cat Makefile
obj-m?+=?main.o #generate?the?path CURRENT_PATH:=$(shell?pwd) #the?current?kernel?version?number LINUX_KERNEL:=$(shell?uname?-r) #the?absolute?path LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL) #complie?object all: ????make?-C?$(LINUX_KERNEL_PATH)?M=$(CURRENT_PATH)?modules #clean clean: ????make?-C?$(LINUX_KERNEL_PATH)?M=$(CURRENT_PATH)?clean
其中,obj-m指定了目標文件的名稱,文件名需要和源文件名相同(擴展名除外),以便于make自動推導。
然后使用make命令編譯模塊,得到模塊文件main.ko。
florian@florian-pc:~/module$ make
make?-C?/usr/src/linux-headers-2.6.35-22-generic?M=/home/florian/module?modules make[1]:?正在進入目錄?`/usr/src/linux-headers-2.6.35-22-generic' ??Building?modules,?stage?2. ??MODPOST?1?modules make[1]:正在離開目錄?`/usr/src/linux-headers-2.6.35-22-generic'
使用insmod和rmmod命令對模塊進行加載和卸載操作,并使用dmesg打印內核日志。
florian@florian-pc:~/module$?sudo?insmod?main.ko;dmesg?|?tail?-1 [31077.810049]?Hi?module!
florian@florian-pc:~/module$?sudo?rmmod?main.ko;dmesg?|?tail?-1 [31078.960442]?Bye?module!
通過內核日志信息,可以看出模塊的入口函數和出口函數都被正確調用執行。
模塊文件
使用readelf命令查看一下模塊文件main.ko的信息。
florian@florian-pc:~/module$ readelf -h main.ko
ELF?Header: ??Magic:???7f?45?4c?46?01?01?01?00?00?00?00?00?00?00?00?00 ??Class:?????????????????????????????ELF32 ??Data:??????????????????????????????2's?complement,?little?endian ??Version:???????????????????????????1?(current) ??OS/ABI:????????????????????????????UNIX?-?System?V ??ABI?Version:???????????????????????0 ??Type:??????????????????????????????REL?(Relocatable?file) ??Machine:???????????????????????????Intel?80386 ??Version:???????????????????????????0x1 ??Entry?point?address:???????????????0x0 ??Start?of?program?headers:??????????0?(bytes?into?file) ??Start?of?section?headers:??????????1120?(bytes?into?file) ??Flags:?????????????????????????????0x0 ??Size?of?this?header:???????????????52?(bytes) ??Size?of?program?headers:???????????0?(bytes) ??Number?of?program?headers:?????????0 ??Size?of?section?headers:???????????40?(bytes) ??Number?of?section?headers:?????????19 ??Section?header?string?table?index:?16
我們發現main.ko的文件類型為可重定位目標文件,這和一般的目標文件格式沒有任何區別。我們知道,目標文件是不能直接執行的,它需要經過鏈接器的地址空間分配、符號解析和重定位的過程,轉化為可執行文件才能執行。
那么,內核將main.ko加載后,是否對其進行了鏈接呢?
模塊數據結構
首先,我們了解一下模塊的內核數據結構。
linux3.5.2/kernel/module.h:220
struct?module { ????…… ????/*?Startup?function.?*/ ????int?(*init)(void); ????…… ????/*?Destruction?function.?*/ ????void?(*exit)(void); ????…… };
模塊數據結構的init和exit函數指針記錄了我們定義的模塊入口函數和出口函數。
模塊加載
模塊加載由內核的系統調用init_module完成。
linux3.5.2/kernel/module.c:3009
/*?This?is?where?the?real?work?happens?*/ SYSCALL_DEFINE3(init_module,?void?__user?*,?umod, ???????unsigned?long,?len,?const?char?__user?*,?uargs) { ????struct?module?*mod; ????int?ret?=?0; ????…… ????/*?Do?all?the?hard?work?*/ ????mod?=?load_module(umod,?len,?uargs);//模塊加載 ????…… ????/*?Start?the?module?*/ ????if?(mod->init?!=?NULL) ???????ret?=?do_one_initcall(mod->init);//模塊init函數調用 ????…… ????return?0; }
系統調用init_module由SYSCALL_DEFINE3(init_module…)實現,其中有兩個關鍵的函數調用。load_module用于模塊加載,do_one_initcall用于回調模塊的init函數。
函數load_module的實現為。
linux3.5.2/kernel/module.c:2864
/*?Allocate?and?load?the?module:?note?that?size?of?section?0?is?always ???zero,?and?we?rely?on?this?for?optional?sections.?*/ static?struct?module?*load_module(void?__user?*umod, ????????????????unsigned?long?len, ????????????????const?char?__user?*uargs) { ????struct?load_info?info?=?{?NULL,?}; ????struct?module?*mod; ????long?err; ????…… ????/*?Copy?in?the?blobs?from?userspace,?check?they?are?vaguely?sane.?*/ ????err?=?copy_and_check(&info,?umod,?len,?uargs);//拷貝到內核 ????if?(err) ???????return?ERR_PTR(err); ????/*?Figure?out?module?layout,?and?allocate?all?the?memory.?*/ ????mod?=?layout_and_allocate(&info);//地址空間分配 ????if?(IS_ERR(mod))?{ ???????err?=?PTR_ERR(mod); ???????goto?free_copy; ????} ????…… ????/*?Fix?up?syms,?so?that?st_value?is?a?pointer?to?location.?*/ ????err?=?simplify_symbols(mod,?&info);//符號解析 ????if?(err?
函數load_module內有四個關鍵的函數調用。copy_and_check將模塊從用戶空間拷貝到內核空間,layout_and_allocate為模塊進行地址空間分配,simplify_symbols為模塊進行符號解析,apply_relocations為模塊進行重定位。
由此可見,模塊加載時,內核為模塊文件main.ko進行了鏈接的過程!
至于函數do_one_initcall的實現就比較簡單了。
linux3.5.2/kernel/init.c:673
int?__init_or_module?do_one_initcall(initcall_t?fn) { ????int?count?=?preempt_count(); ????int?ret; ????if?(initcall_debug) ???????ret?=?do_one_initcall_debug(fn); ????else ???????ret?=?fn();//調用init?module ????…… ????return?ret; }
即調用了模塊的入口函數init。
模塊卸載
模塊卸載由內核的系統調用delete_module完成。
linux3.5.2/kernel/module.c:768
SYSCALL_DEFINE2(delete_module,?const?char?__user?*,?name_user, ????????unsigned?int,?flags) { ????struct?module?*mod; ????char?name[MODULE_NAME_LEN]; ????int?ret,?forced?=?0; ????…… ????/*?Final?destruction?now?no?one?is?using?it.?*/ ????if?(mod->exit?!=?NULL) ???????mod->exit();//調用exit?module ????…… ????free_module(mod);//卸載模塊 ????…… }
通過回調exit完成模塊的出口函數功能,最后調用free_module將模塊卸載。
結論
如此看來,內核模塊其實并不神秘。傳統的用戶程序需要編譯為可執行程序才能執行,而模塊程序只需要編譯為目標文件的形式便可以加載到內核,有內核實現模塊的鏈接,將之轉化為可執行代碼。同時,在內核加載和卸載的過程中,會通過函數回調用戶定義的模塊入口函數和模塊出口函數,實現相應的功能。
相關推薦:《Linux視頻教程》