module_init宏分析

本文详细解析了Linux内核中用于模块初始化和退出的module_init和module_exit宏的具体实现,包括宏展开过程、类型定义、段加载及链接脚本配置,以及insmod过程中的地址获取与函数调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一直在使用module_init和module_exit宏,但是对于这两个宏依然比较陌生,不知道它到底为程序员做了什么东西,今天闲来无事,就打开source insight分析了下它的具体实现,瞬间明白了,现在将笔记整理下。以经典的hello_world模块为例,在模块实现文件中,我们编写了如下语句:

module_init(hello_init);

那么它会被编译器展开成什么样子呢?

在文件linux/init.h中定义了module_init.

#define module_init(x) __initcall(x);
#define __initcall(fn) device_initcall(fn)

#define device_initcall(fn) __define_initcall("6",fn,6)

#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn

那么在编译时刻,编译器会将其转化为如下语句
static initcall_t __initcall_hello_init6 __used __attribute__((__section__(".initcall6 .init"))) = hello_init;
实际上定义了一个类型为函数指针的变量__initcall_hello_init6,存放的是hello_init函数地址.
其中initcall_t是一个函数指针 typedef int (*initcall_t)(void); //linux/init.h中定义,
__used也是一个宏,在使用GCC3.4之前的编译器被展开成__attribute__((unused))来禁止编译器弹出有关函数没有被用到的的警告信息。在3.4之后被展开成__attribute__((used))功能一样
__attribute__((__section__(".initcall6 .init")))将该变量加载到段.initcall6.init上,在链接脚本头文件上可以找到如下语句:

#define INITCALLS \
*(.initcallearly.init)\
VMLINUX_SYMBOL(__early_initcall_end) = .;\
  *(.initcall0.init)\
  *(.initcall0s.init)\
  *(.initcall1.init)\
  *(.initcall1s.init)\
  *(.initcall2.init)\
  *(.initcall2s.init)\
  *(.initcall3.init)\
  *(.initcall3s.init)\
  *(.initcall4.init)\
  *(.initcall4s.init)\
  *(.initcall5.init)\
  *(.initcall5s.init)\
*(.initcallrootfs.init)\
  *(.initcall6.init)\
  *(.initcall6s.init)\
  *(.initcall7.init)\
  *(.initcall7s.init)

编译过内核后,生成的vmlinux.lds文件中有如下语句:
  
__initcall_start = .; *(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
当insmod的时候,内核从initcall6.init段中读取到该地址,然后跳转到该地址去执行,所以就打印出hello_init语句中实现的输出.


<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、付费专栏及课程。

余额充值