Linux驱动中module_init宏的解析

本文深入解析了Linux内核中module_init宏的工作原理及其在不同阶段的展开过程。从宏定义开始,逐步展示如何将初始化函数放置到特定内存段中,并在内核启动时按顺序调用这些函数。
内核include/linux/init.h中,module_init展开如下:
#define module_init(x) __initcall(x);
__initcall(x)展开如下:
#define __initcall(fn) device_initcall(fn)
device_initcall(fn)展开如下:
#define device_initcall(fn)        __define_initcall(fn, 6)
__define_initcall(fn, 6)展开:
#define __define_initcall(fn, id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" #id ".init"))) = fn

所以,module_init最终会被展开为:
#define module_init(x)   static initcall_t __initcall_x6 __used \
    __attribute__((__section__(".initcall6.init"))) = fn;

被module_init宏修饰的函数会被放在.initcall6.init段中,在arch/arm/kernel/vmlinux.lds.S中的init.data段中:
    .init.data : {
#ifndef CONFIG_XIP_KERNEL
        INIT_DATA
#endif
        INIT_SETUP(16)
        INIT_CALLS
        CON_INITCALL
        SECURITY_INITCALL
        INIT_RAM_FS
    }

而INIT_CALLS定义在include/asm-generic/vmlinux.lds.h中:
#define INIT_CALLS                            \
        VMLINUX_SYMBOL(__initcall_start) = .;            \
        *(.initcallearly.init)                    \
        INIT_CALLS_LEVEL(0)                    \
        INIT_CALLS_LEVEL(1)                    \
        INIT_CALLS_LEVEL(2)                    \
        INIT_CALLS_LEVEL(3)                    \
        INIT_CALLS_LEVEL(4)                    \
        INIT_CALLS_LEVEL(5)                    \
        INIT_CALLS_LEVEL(rootfs)                \
        INIT_CALLS_LEVEL(6)                    \
        INIT_CALLS_LEVEL(7)                    \
        VMLINUX_SYMBOL(__initcall_end) = .;

 INIT_CALLS_LEVEL(n)展开:
#define INIT_CALLS_LEVEL(level)                        \
        VMLINUX_SYMBOL(__initcall##level##_start) = .;        \
        *(.initcall##level##.init)                \
        *(.initcall##level##s.init)                \

所以,被module_init修饰的宏最终会被放在.init.data段中,这个段在内核初始化的时候会被调用。在init/main.c中通过调用do_initcalls函数来实现对module_init宏的调用:
int __init_or_module do_one_initcall(initcall_t fn)
{
    int count = preempt_count();
    int ret;
    char msgbuf[64];

    if (initcall_debug)
        ret = do_one_initcall_debug(fn);
    else
        ret = fn();

    msgbuf[0] = 0;

    if (preempt_count() != count) {
        sprintf(msgbuf, "preemption imbalance ");
        preempt_count_set(count);
    }
    if (irqs_disabled()) {
        strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));
        local_irq_enable();
    }
    WARN(msgbuf[0], "initcall %pF returned with %s\n", fn, msgbuf);

    return ret;
}

static initcall_t *initcall_levels[] __initdata = {
    __initcall0_start,
    __initcall1_start,
    __initcall2_start,
    __initcall3_start,
    __initcall4_start,
    __initcall5_start,
    __initcall6_start,
    __initcall7_start,
    __initcall_end,
};

static void __init do_initcall_level(int level)
{
    extern const struct kernel_param __start___param[], __stop___param[];
    initcall_t *fn;

    strcpy(initcall_command_line, saved_command_line);
    parse_args(initcall_level_names[level],
           initcall_command_line, __start___param,
           __stop___param - __start___param,
           level, level,
           &repair_env_string);

    for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
        do_one_initcall(*fn);
}

static void __init do_initcalls(void)
{
    int level;

    for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
        do_initcall_level(level);
}
<think>嗯,用户这次问的是Linux内核中module_platform_driver和module_init之间的关系与区别,这是个很具体的驱动开发问题。结合用户之前的提问记录,ta似乎正在研究内核模块的加载机制,而且已经查阅过平台驱动的实现示例。用户提到的两个确实容易混淆。module_init是内核模块的通用入口点,而module_platform_driver是专门为平台驱动设计的简化。从ta给出的代码示例看,在平台驱动场景中基本都会优先使用后者。考虑到用户可能正在写驱动,需要明确使用场景:-当开发标准字符设备/块设备驱动时,仍然需要module_init配合自定义初始化函数-但如果是访问内存映射IO资源的平台设备,用module_platform_driver可以省去大量模板代码特别要注意的是驱动加载顺序问题。在之前的代码示例中,platform驱动都是最后执行的,因为businitcall优先级更高。如果用户错误地在非平台驱动中使用module_platform_driver,可能会导致设备资源未被初始化就尝试probe的灾难性后果。用户贴出的引用文献里,第二篇关于bus_type匹配机制的内容很有价值,应该引用在回答里说明为什么平台驱动需要特殊处理。另外可以结合内核源码片段展示这个的展开过程,这样更直观。对于进阶问题,用户可能还想知道如何修改初始化优先级,或者混合使用两种机制的情况。这些问题可以作为补充内容放在最后,毕竟ta的关注点主要在区别而非高级用法。</think> `module_platform_driver` 和 `module_init` 都是 Linux 内核驱动程序的入口点声明,但面向的场景和抽象层级不同: --- ### 核心关系与区别 | **特性** | `module_platform_driver` | `module_init` | |-------------------------|----------------------------------------------|------------------------------------------| | **用途** | 专为 Platform Bus 驱动设计的**简化** | 通用内核模块的**标准入口** | | **内部实现** | 封装了 `module_init` 和 `module_exit` | 直接定义模块的初始化/退出函数 | | **适用对象** | Platform Driver (`struct platform_driver`) | 所有类型驱动(字符设备、块设备、平台设备等)| | **初始化逻辑** | 自动注册 Platform Driver 到 Platform Bus | 需手动调用总线注册接口(如 `platform_driver_register()`)| | **资源管理** | 隐含总线匹配和设备树支持 | 需开发者自行实现匹配逻辑 | --- ### 源码级关系解析 #### 1. **`module_platform_driver` 本质是 `module_init` 的封装** 展开定义(Linux 5.10+): ```c // 简化为: #define module_platform_driver(__platform_driver) \ module_driver(__platform_driver, platform_driver_register, \ platform_driver_unregister) ``` 其中 `module_driver` 进一步展开: ```c #define module_driver(__driver, __register, __unregister) \ static int __init __driver##_init(void) \ { return __register(&(__driver)); } \ module_init(__driver##_init); \ // ... 对称定义 module_exit ``` 最终仍通过 `module_init` 注册初始化函数[^2]。 --- #### 2. **使用场景对比** ##### 标准 Platform Driver 写法(用 `module_platform_driver`) ```c static struct platform_driver my_driver = { .probe = my_probe, .remove = my_remove, .driver = { .name = "example" } }; module_platform_driver(my_driver); // 一行搞定 ``` ##### 等效的手动实现(用 `module_init`) ```c static int __init my_init(void) { return platform_driver_register(&my_driver); // 需手动注册 } module_init(my_init); static void __exit my_exit(void) { platform_driver_unregister(&my_driver); // 需手动注销 } module_exit(my_exit); ``` --- ### 关键流程差异 ```mermaid graph TD A[内核加载模块] --> B{入口声明方式} B -->|module_platform_driver| C[自动注册到Platform Bus] B -->|module_init| D[需调用注册函数] C --> E[总线触发设备匹配] D --> F[需手动调用如 platform_driver_register()] F --> E E --> G[执行probe函数] ``` --- ### 何时选择? | **场景** | **推荐** | |-------------------------------|--------------------------| | Platform 设备驱动开发 | `module_platform_driver` | | 非总线型驱动(如纯字符设备) | `module_init` | | 需自定义初始化流程 | `module_init` | | 标准 Platform 驱动开发 | `module_platform_driver` | --- ### 典型问题分析 **Q:混用二者导致重复注册?** 若在 Platform 驱动中同时写: ```c module_platform_driver(my_driver); module_init(my_init); // 错误!导致platform_driver_register()被调用两次 ``` 将触发内核错误:`platform_driver_register() failed with -EEXIST`。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值