Linux模块化机制和module_init

        致谢:

微信公众号:嵌入式企鹅圈 每天都新增爱好者关注,感谢大家的支持和大牛们的建议。

本人将竭力出品很多其它优质的原创文章回馈大家的厚爱。


引子:模块化机制长处


模块化机制(module)是Linux系统的一大创新。是Linux驱动开发和执行的基础(当然,module并不不过支撑驱动)。

其长处在于:


1.在系统执行动态载入模块。扩充内核的功能。

不须要时能够卸载。

2. 改动内核功能,不必又一次所有编译整改内核,仅仅须要编译对应模块就可以。

3.模块目标代码一旦被载入重定位到内核,其作用域和静态链接的代码全然等价。

本文重点阐述Linux module载入的来龙去脉,当中的奥秘就在于对宏module_init的解读。


一、模块样例

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);


二、模块编程要点

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 <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。



五、动态链接载入和初始化

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.

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

<think>好的,用户想了解Linux内核编程中module_init__init的关系及用法。首先,我需要回忆这两个宏的具体作用它们在内核模块加载过程中的角色。 首先,module_init是一个宏,用于指定模块的初始化函数。当模块被加载时,内核会调用这个函数。而__init是一个属性宏,用于标记函数或变量仅在初始化阶段使用,之后内存会被释放。这可能涉及到内核的内存管理机制,比如将这部分代码或数据放在特定的段中,初始化完成后回收资源。 接下来,我需要确认两者的使用场景。module_init用于定义模块的入口点,无论是编译进内核还是作为可加载模块,都需要用它来声明初始化函数。而__init则用于优化内存使用,标记那些只在初始化阶段需要的函数变量,避免长期占用内存。 用户可能还会混淆这两个宏的具体用法,比如是否必须同时使用。例如,模块的初始化函数是否需要用__init修饰。根据经验,是的,通常module_init声明的函数会同时使用__init,这样在模块内置入内核时,初始化完成后可以释放内存。但如果是可加载模块,可能不需要,因为模块卸载时内存会被释放。不过需要进一步确认内核的处理机制。 另外,用户可能想知道如果不使用__init会有什么后果。可能会导致内存浪费,因为初始化代码在模块加载后不再需要,但未被释放。而module_init如果不使用,模块将无法正确注册初始化函数,导致加载失败。 需要查阅相关文档或代码示例来验证这些信息。例如,Linux内核源码中的模块通常会看到如下结构: static int __init my_module_init(void) { // 初始化代码 } module_init(my_module_init); 这里,__init修饰了初始化函数,而module_init将其注册为入口点。当模块编译进内核时,__init标记的部分会在初始化后被释放;如果是可加载模块,则在卸载时释放。 还要注意,__initdata用于初始化阶段的数据,而__exit__exitdata用于退出时的清理函数数据,类似机制。 可能用户的问题还涉及到模块的生命周期管理,以及内核如何优化资源使用。需要解释清楚这两个宏如何协作,确保模块正确初始化资源释放。 最后,生成相关问题时,需要考虑用户可能进一步想了解的内容,比如其他类似的宏(如__exit),或者模块加载卸载的具体过程,以及如何调试模块初始化的问题等。</think>在Linux内核编程中,`module_init``__init`是密切相关的宏,用于管理内核模块的初始化资源优化。以下是它们的详细关系用法: --- ### 1. `module_init` 的作用 `module_init`宏用于**指定模块的初始化函数**,该函数在模块加载时由内核自动调用。它的主要特点包括: - 当模块编译为可动态加载(`.ko`文件)时,初始化函数在`insmod`或`modprobe`时执行 - 当模块直接编译进内核时,初始化函数在内核启动时执行 - 初始化函数需要返回`int`类型值,成功返回`0`,失败返回负数错误码 示例: ```c static int __init my_module_init(void) { printk(KERN_INFO "Module initialized\n"); return 0; } module_init(my_module_init); // 注册初始化函数 ``` --- ### 2. `__init` 的作用 `__init`宏是**内存优化标记**,用于标识只在初始化阶段使用的函数或数据: - 被`__init`修饰的函数会被放入`.init.text`段 - 被`__init`修饰的变量会被放入`.init.data`段 - 内核在完成初始化后,会释放这些内存区域以节省空间[^1] 示例: ```c static int __initdata init_count = 0; // __initdata用于初始化阶段变量 static int __init my_init(void) { init_count++; return 0; } ``` --- ### 3. 两者的协作关系 | 特性 | `module_init` | `__init` | |---------------------|----------------------------|--------------------------| | 主要目的 | 注册模块入口点 | 标记初始化专用资源 | | 作用阶段 | 模块加载/内核启动时 | 编译阶段划分内存区域 | | 内存管理 | 不直接管理内存 | 优化内核内存使用 | | 典型组合 | 常与`__init`联合使用 | 常修饰`module_init`函数 | **典型代码模式**: ```c static int __init my_module_init(void) { // 用__init修饰初始化函数 /* 初始化逻辑 */ } module_init(my_module_init); // 注册为模块入口 ``` --- ### 4. 关键区别 - **编译处理差异**: - 若模块编译进内核(built-in),`__init`标记的函数/数据会在初始化后**被内存管理器自动释放** - 若模块为动态加载(loadable),`__init`标记的内容会在模块卸载时释放 - **必要性差异**: - `module_init`是**强制要求**的模块入口声明 - `__init`是**可选优化**,但强烈推荐使用 --- ### 5. 扩展应用 - `__exit``module_exit`:与初始化对应的清理机制 ```c static void __exit my_module_exit(void) { printk(KERN_INFO "Module removed\n"); } module_exit(my_module_exit); ``` - `__initdata`:专门用于初始化阶段的数据标记 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值