module_init(hello_init) 宏展开深度剖析

🔥作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习

🎬擅长领域:驱动开发,嵌入式软件开发,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__i686
  • initcall_ttypedef 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. 设计目的和优势

  1. 自动排序:通过section机制,链接器自动按优先级排序初始化函数
  2. 零运行时开销:排序在链接时完成,运行时直接遍历执行
  3. 模块化:每个模块独立,不需要中心注册表
  4. 优先级控制:不同子系统可以指定不同的初始化顺序
  5. 内存优化 :初始化完成后,整个 .init section 可以被释放

6. 调试信息

查看实际的内存布局:

bash 复制代码
# 查看内核符号
readelf -s vmlinux | grep initcall | head -20

# 查看section内容
objdump -j .initcall6.init -s vmlinux

这个机制是Linux内核模块化架构的基石之一,通过巧妙的编译器和链接器特性,实现了高效、灵活的初始化系统。