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.

模块的传參、符号导出、模块依赖等机制之后再另文描写叙述

相关文章
相关标签/搜索