致謝:linux
微信公衆號:嵌入式企鵝圈 天天都新增愛好者關注,感謝你們的支持和大牛們的建議。編程
本人將竭力出品不少其它優質的原創文章回饋你們的厚愛。
微信
引子:模塊化機制長處
模塊化機制(module)是Linux系統的一大創新。是Linux驅動開發和執行的基礎(固然,module並不不過支撐驅動)。數據結構
其長處在於:模塊化
1.在系統執行動態載入模塊。擴充內核的功能。函數
不需要時可以卸載。post
2. 改動內核功能,沒必要又一次全部編譯整改內核,僅僅需要編譯對應模塊就能夠。this
3.模塊目標代碼一旦被載入重定位到內核,其做用域和靜態連接的代碼全然等價。spa
本文重點闡述Linux module載入的前因後果,當中的奧祕就在於對宏module_init的解讀。指針
1、模塊樣例
hello_module.c代碼例如如下:
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_ALERT */
#include <linux/init.h> /*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);
2、模塊編程要點
1.頭文件 linux/module.h、linux/kernel.h、linux/init.h
2. 定義模塊的初始化函數test_init(名字隨意)和卸載函數test_exit(名字隨意)。
3. 用宏module_init聲明初始化函數,用宏module_exit聲明卸載函數。
3、模塊執行
模塊代碼有兩種執行的方式:
1. 編譯成可動態載入的module。並經過insmod來動態載入,接着進行初始化。
2. 靜態編譯連接進內核,在系統啓動過程當中進行初始化。
有些模塊是必須要編譯到內核。和內核一塊兒執行的。從不卸載,如vfs、platform_bus等等。
4、靜態連接和初始化
Make menuconfig時選擇將模塊編譯到內核即爲靜態連接,或者直接在makefile文件裏指定爲obj-y +=hello_module.o
1 module 宏展開
頭文件路徑: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).
2 linux 連接腳本
路徑 arch/arm/kernel/vmlinux.ld.S
#include <asm-generic/vmlinux.lds.h>
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 < __initcall_end; fn++)
do_one_initcall(*fn);
}
即取出函數指針__initcall_test_init_6的值並進行調用,即運行test_init。
5、動態連接載入和初始化
Make menuconfig時選擇將模塊編譯成模塊即爲動態連接。或者直接在makefile文件裏指定爲obj-m +=hello_module.o
編譯成模塊的命令是:
make –C $KERNEL_PATH M=$MODULE_PATH modules
即便用linux根文件夾下的makefile,運行該makefile下的modules僞目標。對當前模塊進行編譯。編譯的結果是可重定位文件,insmod載入時才完畢終於的連接動做。
1 Module 編譯選項
Linux根文件夾下的makefile定義了modules僞目標會用到的編譯選項。
//即定義宏MODULE,-D是GCC定義宏的語法。
MODFLAGS = -DMODULE
//GCC編譯模塊代碼時會用到該選項,即定義宏MODULE。這與在頭文件裏用#define MODULE是同樣的。
CFLAGS_MODULE = $(MODFLAGS)
2 Module_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是同樣的。
3 Hello_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.
模塊的傳參、符號導出、模塊依賴等機制之後再另文描寫敘述