Linux模塊化機制和module_init

        致謝: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.

模塊的傳參、符號導出、模塊依賴等機制之後再另文描寫敘述

相關文章
相關標籤/搜索