> 引子:模塊化機制優點
模塊化機制(module)是Linux系統的一大創新,是Linux驅動開發和運行的基礎(當然,module并不僅僅是支撐驅動)。其優點在于:
1.在系統運行動態加載模塊,擴充內核的功能。不需要時可以卸載。
2\. 修改內核功能,不必重新全部編譯整改內核,只需要編譯相應模塊即可。
3.模塊目標代碼一旦被加載重定位到內核,其作用域和靜態鏈接的代碼完全等價。
本文重點闡述Linux module加載的來龍去脈,其中的奧秘就在于對宏module_init的解讀。
> 一、模塊例子
hello_module.c代碼如下:
~~~
#include /* Needed by all modules */
#include /* Needed for KERN_ALERT */
#include /*Needed for __init */
static int __init test_init(void){
printk(KERN_ALERT"Hello world!\n");
return 0;
}
static void __exit test_exit(void){
printk(KERN_ALERT"Goodbye world!\n");
}
module_init(test_init);
module_exit(test_exit);
~~~
> 二、模塊編程要點
1.頭文件 linux/module.h、linux/kernel.h、linux/init.h
2.?定義模塊的初始化函數test_init(名字任意)和卸載函數test_exit(名字任意)。
3.?用宏module_init聲明初始化函數,用宏module_exit聲明卸載函數。
> 三、模塊運行
模塊代碼有兩種運行的方式:
1.?編譯成可動態加載的module,并通過insmod來動態加載,接著進行初始化。
2.?靜態編譯鏈接進內核,在系統啟動過程中進行初始化。
有些模塊是必須要編譯到內核,和內核一起運行的,從不卸載,如vfs、platform_bus等等。
> 四、靜態鏈接和初始化
Make menuconfig時選擇將模塊編譯到內核即為靜態鏈接,或者直接在makefile文件中指定為obj-y +=hello_module.o
1module宏展開
頭文件路徑:include/linux/init.h
//靜態編譯鏈接時沒有定義宏MODULE
~~~
#ifndef MODULE
typedef int (*initcall_t)(void);
#define?__define_initcall(level,fn,id) \
static?initcall_t?__initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
#define?device_initcall(fn)?__define_initcall("6",fn,6)
#define?__initcall(fn)?device_initcall(fn)
#define module_init(x)?__initcall(x);
~~~
所以:
~~~
module_init(test_init)展開為:
__initcall(test _init)->
device_initcall(test _init)->
__define_initcall("6", test _init,6)->
static?initcall_t?__initcall_test_init_6 __attribute__((__section__(".initcall6.init"))) = test_init;
~~~
即是定義了一個類型為initcall_t的函數指針變量__initcall_test_init_6,并賦值為test_init,該變量在鏈接時會鏈接到section(.initcall6.init).
2linux鏈接腳本
路徑 arch/arm/kernel/vmlinux.ld.S
~~~
#include
SECTIONS{
…
INIT_CALLS
…
}
~~~
路徑:include/ asm-generic/vmlinux.lds.h
~~~
#define INIT_CALLS \
VMLINUX_SYMBOL(__initcall_start) = .; \
INITCALLS \
VMLINUX_SYMBOL(__initcall_end) = .;
#define INITCALLS \
….
*(.initcall6.init) \
…
~~~
可見__initcall_test_init_6將會鏈接到section(.initcall6.init).
3初始化
在linux啟動的第三個階段kernel_init的函數里會調用:
路徑init/main.c
~~~
Kernel_init
do_basic_setup
do_initcalls
static void __init do_initcalls(void){
initcall_t *fn;
for (fn = __early_initcall_end; fn
do_one_initcall(*fn);
}
~~~
即取出函數指針__initcall_test_init_6的值并進行調用,即執行test_init。
> 五、動態鏈接加載和初始化
Make menuconfig時選擇將模塊編譯成模塊即為動態鏈接,或者直接在makefile文件中指定為obj-m +=hello_module.o
編譯成模塊的命令是:
make –C $KERNEL_PATH M=$MODULE_PATH modules
即使用linux根目錄下的makefile,執行該makefile下的modules偽目標,對當前模塊進行編譯。編譯的結果是可重定位文件,insmod加載時才完成最終的鏈接動作。
1Module編譯選項
Linux根目錄下的makefile定義了modules偽目標會用到的編譯選項。
//即定義宏MODULE,-D是GCC定義宏的語法。
MODFLAGS = -DMODULE
//GCC編譯模塊代碼時會用到該選項,即定義宏MODULE。這與在頭文件中用#define MODULE是一樣的。
CFLAGS_MODULE = $(MODFLAGS)
2Module_init宏展開
頭文件路徑:include/linux/init.h
~~~
#ifndef MODULE /*編譯成module時定義了宏MODULE*/
#else /* MODULE obj-m*/
typedef int (*initcall_t)(void);
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
~~~
__inittest僅僅是為了檢測定義的函數是否符合initcall_t類型,如果不是__inittest類型在編譯時將會報錯。所以真正的宏定義是:
~~~
#define module_init(initfn)
int init_module(void) __attribute__((alias(#initfn)));
~~~
alias屬性是GCC的特有屬性,將定義init_module為函數initfn的別名。所以module_init(test_init)的作用就是定義一個變量名init_module,其地址和test_init是一樣的。
3Hello_module.mod.c
編譯成module的模塊都會自動產生一個*.mod.c的文件,Hello_module.mod.c的內容如下:
~~~
struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};
~~~
即定義了一個類型為module的全局變量__this_module,其成員init即為init_module,也即是test_init.并且該變量會鏈接到section(".gnu.linkonce.this_module").
4動態加載
insmod是busybox提供的用戶層命令:
路徑busybox/modutils/ insmod.c
~~~
insmod_main
bb_init_module
init_module
路徑busybox/modutils/modutils.c:
# define init_module(mod, len, opts) .\
syscall(__NR_init_module, mod, len, opts)
~~~
該系統調用對應內核層的sys_init_module函數。
路徑:kernel/module.c
SYSCALL_DEFINE3(init_module,…)
//加載模塊的ko文件,并解釋各個section,重定位
mod = load_module(umod, len, uargs);
//查找section(".gnu.linkonce.this_module")
modindex = find_sec(hdr, sechdrs, secstrings,
".gnu.linkonce.this_module");
//找到Hello_module.mod.c定義的module數據結構
mod = (void *)sechdrs[modindex].sh_addr;
if (mod->init != NULL)
ret = do_one_initcall(mod->init); //調用test_init.
模塊的傳參、符號導出、模塊依賴等機制以后再另文描述
更多原創技術分享敬請關注微信公眾號:嵌入式企鵝圈
