🔥作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习
🎬擅长领域:驱动开发,嵌入式软件开发,BSP开发
❄️作者主页:一个平凡而乐于分享的小比特的个人主页
✨收录专栏:Linux,本专栏目的在于,记录学习Linux操作系统的总结
欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖

module_init(hello_init) 宏展开深度剖析
这是一个关于 Linux 内核模块初始化的深度技术问题。我来详细分析 module_init(hello_init) 如何展开为最终的复杂定义。
1. 宏观流程概览
module_init(hello_init)
↓ 宏展开(多级展开)
↓ __define_initcall(level, fn, id)
↓ ____define_initcall(fn, id, __sec)
↓ 最终:声明一个特定section的函数指针变量
2. 逐层展开分析
第一层:module_init 宏定义
在 include/linux/module.h 中:
c
#define module_init(initfn) \
__module_init_call(initfn, __i686) /* 针对不同架构 */
展开后变成:
c
__module_init_call(hello_init, __i686)
第二层:__module_init_call
c
#define __module_init_call(fn, secid) \
__module_init_call_name(fn, __initcall_##fn##secid)
展开为:
c
__module_init_call_name(hello_init, __initcall_hello_init__i686)
第三层:__module_init_call_name
c
#define __module_init_call_name(fn, __name) \
static initcall_t __name __used \
__attribute__((__section__(".initcall" #fn ".init"))) = fn
这里开始变得复杂:
__name=__initcall_hello_init__i686initcall_t是typedef int (*initcall_t)(void);__used确保编译器不优化掉这个变量
展开为:
c
static initcall_t __initcall_hello_init__i686 __used \
__attribute__((__section__(".initcall" "hello_init" ".init"))) = hello_init
第四层:更现代的版本(当前内核)
实际当前内核中,module_init 的定义更简洁:
c
#define module_init(fn) \
__initcall(fn, 6)
因为 6 对应 device_initcall 级别,这是大多数驱动模块使用的初始化级别。
第五层:__initcall 宏
c
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall(fn, 6)
所以:
c
module_init(hello_init) → __define_initcall(hello_init, 6)
第六层:核心的 __define_initcall
c
#define __define_initcall(fn, id) \
____define_initcall(fn, \
__initcall_##fn##id, \
__initcall_##fn##id##_used, \
id, \
__initcall_##fn##id##_unused)
展开为:
c
____define_initcall(hello_init,
__initcall_hello_init6, /* 变量名 */
__initcall_hello_init6_used, /* 用于__used */
id, /* 6 */
__initcall_hello_init6_unused) /* 未使用参数 */
第七层:最终的 ____define_initcall
c
#define ____define_initcall(fn, __name, __unused, __id, __unused2) \
static initcall_t __name __used \
__attribute__((__section__(".initcall" #__id ".init"))) = fn
替换所有参数:
c
static initcall_t __initcall_hello_init6 __used \
__attribute__((__section__(".initcall" "6" ".init"))) = hello_init
第八层:__used 的展开
__used 的定义:
c
#define __used __attribute__((__used__))
所以最终成为:
c
static initcall_t __initcall_hello_init6 __attribute__((__used__)) \
__attribute__((__section__(".initcall" "6" ".init"))) = hello_init
3. 关键机制解析
3.1 Section 名称生成
.initcall6.init是最终的 section 名称- 数字
6表示初始化优先级(1-7,数字越小优先级越高)
各级初始化顺序:
1: .initcall1.init - pure_early_initcall
2: .initcall2.init - core_early_initcall
3: .initcall3.init - postcore_early_initcall
4: .initcall4.init - arch_initcall
5: .initcall5.init - subsys_initcall
6: .initcall6.init - device_initcall(大部分驱动)
7: .initcall7.init - late_initcall
3.2 变量命名规则
- 变量名:
__initcall_hello_init6 - 加上
_used后缀是旧版本机制 - 现在的命名更统一:
__initcall_<函数名><优先级>
3.3 内存布局
内核链接时,所有 .initcall6.init section 的内容会被收集到一起:
.initcall6.init section:
__initcall_hello_init6 → 指向 hello_init 函数
__initcall_other_driver6 → 指向其他驱动初始化函数
...
3.4 初始化调用流程
内核启动时,在 do_initcalls() 中:
c
static void __init do_initcalls(void)
{
initcall_t *fn;
// 遍历所有优先级
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) {
// 遍历该优先级的所有初始化函数
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++) {
(*fn)(); // 执行初始化函数
}
}
}
4. 完整展开示例
假设我们有:
c
// 模块代码
static int __init hello_init(void) {
printk(KERN_INFO "Hello world\n");
return 0;
}
module_init(hello_init);
展开过程:
第1步:module_init(hello_init)
第2步:__initcall(hello_init, 6)
第3步:__define_initcall(hello_init, 6)
第4步:____define_initcall(hello_init, __initcall_hello_init6, ...)
第5步:static initcall_t __initcall_hello_init6 __used \
__attribute__((__section__(".initcall6.init"))) = hello_init
最终结果:
c
static int (*initcall_t)(void)
__initcall_hello_init6 // 变量名
__attribute__((__used__)) // 确保不被优化
__attribute__((__section__(".initcall6.init"))) // 放入特定段
= hello_init; // 初始化为hello_init函数
5. 设计目的和优势
- 自动排序:通过section机制,链接器自动按优先级排序初始化函数
- 零运行时开销:排序在链接时完成,运行时直接遍历执行
- 模块化:每个模块独立,不需要中心注册表
- 优先级控制:不同子系统可以指定不同的初始化顺序
- 内存优化 :初始化完成后,整个
.initsection 可以被释放
6. 调试信息
查看实际的内存布局:
bash
# 查看内核符号
readelf -s vmlinux | grep initcall | head -20
# 查看section内容
objdump -j .initcall6.init -s vmlinux
这个机制是Linux内核模块化架构的基石之一,通过巧妙的编译器和链接器特性,实现了高效、灵活的初始化系统。