module_init解析

本文详细介绍了Linux内核驱动模块的初始化过程,包括init.h、kernel.h、module.h等头文件的作用,以及如何使用module_init宏进行模块初始化与加载。

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

像你写C程序需要包含C库的头文件那样,Linux内核编程也需要包含Kernel头文件,大多的Linux驱动程序需要包含下面三个头文件:  
 
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
其中,init.h 定义了驱动的初始化和退出相关的函数,kernel.h 定义了经常用到的函数原型及宏定义,module.h 定义了内核模块相关的函数、变量及宏。  
 
      几乎每个linux驱动都有个module_init(与module_exit的定义在Init.h (\include\linux) 中)。没错,驱动的加载就靠它。为什么需要这样一个宏?原因是按照一般的编程想法,各部分的初始化函数会在一个固定的函数里调用比如:
 
void init(void)
 
{
 
    init_a();
 
    init_b();
 
}
 
如果再加入一个初始化函数呢,那么在init_b()后面再加一行:init_c();这样确实能完成我们的功能,但这样有一定的问题,就是不能独立的添加初始化函数,每次添加一个新的函数都要修改init函数。可以采用另一种方式来处理这个问题,只要用一个宏来修饰一下:
 
void init_a(void)
 
{
 
}
 
__initlist(init_a, 1);
 
它是怎么样通过这个宏来实现初始化函数列表的呢?先来看__initlist的定义:
 
#define __init __attribute__((unused, __section__(".initlist")))
 
#define __initlist(fn, lvl) \
static initlist_t __init_##fn __init = { \
 magic:    INIT_MAGIC, \
 callback: fn, \
 level:   lvl }
 
请注意:__section__(".initlist"),这个属性起什么作用呢?它告诉连接器这个变量存放在.initlist区段,如果所有的初始化函数都是用这个宏,那么每个函数会有对应的一个initlist_t结构体变量存放在.initlist区段,也就是说我们可以在.initlist区段找到所有初始化函数的指针。怎么找到.initlist区段的地址呢?
 
extern u32 __initlist_start;
extern u32 __initlist_end;
 
这两个变量起作用了,__initlist_start是.initlist区段的开始,__initlist_end是结束,通过这两个变量我们就可以访问到所有的初始化函数了。这两个变量在那定义的呢?在一个连接器脚本文件里
 
 . = ALIGN(4);
 .initlist : {
  __initlist_start = .;
  *(.initlist)
  __initlist_end = .;
 }
这两个变量的值正好定义在.initlist区段的开始和结束地址,所以我们能通过这两个变量访问到所有的初始化函数。
 
      与此类似,内核中也是用到这种方法,所以我们写驱动的时候比较独立,不用我们自己添加代码在一个固定的地方来调用我们自己的初始化函数和退出函数,连接器已经为我们做好了。先来分析一下module_init。定义如下:
 
#define module_init(x)     __initcall(x);              //include\linux\init.h
 
#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
 
      如果某驱动想以func作为该驱动的入口,则可以如下声明:module_init(func);被上面的宏处理过后,变成__initcall_func6 __used加入到内核映像的".initcall"区。内核的加载的时候,会搜索".initcall"中的所有条目,并按优先级加载它们,普通驱动程序的优先级是6。其它模块优先级列出如下:值越小,越先加载。
 
#define pure_initcall(fn)           __define_initcall("0",fn,0)
 
#define core_initcall(fn)            __define_initcall("1",fn,1)
 
#define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)
 
#define postcore_initcall(fn)             __define_initcall("2",fn,2)
 
#define postcore_initcall_sync(fn)  __define_initcall("2s",fn,2s)
 
#define arch_initcall(fn)            __define_initcall("3",fn,3)
 
#define arch_initcall_sync(fn)          __define_initcall("3s",fn,3s)
 
#define subsys_initcall(fn)                 __define_initcall("4",fn,4)
 
#define subsys_initcall_sync(fn)      __define_initcall("4s",fn,4s)
 
#define fs_initcall(fn)                          __define_initcall("5",fn,5)
 
#define fs_initcall_sync(fn)               __define_initcall("5s",fn,5s)
 
#define rootfs_initcall(fn)                  __define_initcall("rootfs",fn,rootfs)
 
#define device_initcall(fn)                 __define_initcall("6",fn,6)
 
#define device_initcall_sync(fn)       __define_initcall("6s",fn,6s)
 
#define late_initcall(fn)             __define_initcall("7",fn,7)
 
#define late_initcall_sync(fn)           __define_initcall("7s",fn,7s)
 
可以看到,被声明为pure_initcall的最先加载。
 
      module_init除了初始化加载之外,还有后期释放内存的作用。linux kernel中有很大一部分代码是设备驱动代码,这些驱动代码都有初始化和反初始化函数,这些代码一般都只执行一次,为了有更有效的利用内存,这些代码所占用的内存可以释放出来。
 
      linux就是这样做的,对只需要初始化运行一次的函数都加上__init属性,__init 宏告诉编译器如果这个模块被编译到内核则把这个函数放到(.init.text)段,module_exit的参数卸载时同__init类似,如果驱动被编译进内核,则__exit宏会忽略清理函数,因为编译进内核的模块不需要做清理工作,显然__init和__exit对动态加载的模块是无效的,只支持完全编译进内核。
 
      在kernel初始化后期,释放所有这些函数代码所占的内存空间。连接器把带__init属性的函数放在同一个section里,在用完以后,把整个section释放掉。当函数初始化完成后这个区域可以被清除掉以节约系统内存。Kenrel启动时看到的消息“Freeing unused kernel memory: xxxk freed”同它有关。
 
 
      我们看源码,init/main.c中start_kernel是进入kernel()的第一个c函数,在这个函数的最后一行是rest_init();
 
static void rest_init(void)
{
     .....
 
     kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
     unlock_kernel();
     cpu_idle();
 
     .....
}
创建了一个内核线程,主函数kernel_init末尾有个函数:
 
 /*
  * Ok, we have completed the initial bootup, and
  * we're essentially up and running. Get rid of the
  * initmem segments and start the user-mode stuff..
  */
 init_post();
 
这个init_post中的第一句就是free_initmem();就是用来释放初始化代码和数据的。
 
void free_initmem(void)
{
    if (!machine_is_integrator() && !machine_is_cintegrator()) {
    free_area((unsigned long)(&__init_begin),
     (unsigned long)(&__init_end),
     "init");
     }
}
 
接下来就是kernel内存管理的事了。

其中do_basic_setup()在init_post前调用,完成初始化


在Linux底下写过driver模块的对这个宏一定不会陌生。module_init宏在MODULE宏有没有定义的情况下展开的内容是不同的,如果这个宏没有定义,基本上表明阁下的模块是要编译进内核的(obj-y)。

1.在MODULE没有定义这种情况下,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
所以,module_init(x)最终展开为:
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
更直白点,假设阁下driver所对应的模块的初始化函数为int gpio_init(void),那么module_init(gpio_init)实际上等于:
static initcall_t  __initcall_gpio_init_6 __used __attribute__((__section__(".initcall6.init"))) = gpio_init;
就是声明一类型为initcall_t(typedef int (*initcall_t)(void))函数指针类型的变量__initcall_gpio_init_6并将gpio_init赋值与它。
这里的函数指针变量声明比较特殊的地方在于,将这个变量放在了一名为".initcall6.init"节中。接下来结合vmlinux.lds中的
.initcall.init : AT(ADDR(.initcall.init) - (0xc0000000 -0x00000000)) {
   __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 = .;
   }
以及do_initcalls:
static void __init do_initcalls(void)
{
initcall_t *call;
for (call = __initcall_start; call < __initcall_end; call++)
do_one_initcall(*call);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}

那么就不难理解阁下模块中的module_init中的初始化函数何时被调用了:在系统启动过程中start_kernel()->rest_init()->kernel_init()->do_basic_setup()->do_initcalls()


//第二中情况在Android中没用到

2.在MODULE被定义的情况下(大部分可动态加载的driver模块都属于此, obj-m),module_init定义如下:
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
这段宏定义关键点是后面一句,通过alias将initfn变名为init_module。前面那个__inittest的定义其实是种技巧,用来对initfn进行某种静态的类型检查,如果阁下将模块初始化函数定义成,比如,void gpio_init(void)或者是int gpio_init(int),那么在编译时都会有类似下面的warning:
GPIO/fsl-gpio.c: In function '__inittest':
GPIO/fsl-gpio.c:46: warning: return from incompatible pointer type
通过module_init将模块初始化函数统一别名为init_module,这样以后insmod时候,在系统内部会调用sys_init_module()去找到init_module函数的入口地址。
如果objdump -t gpio.ko,就会发现init_module和gpio_init位于相同的地址偏移处。简言之,这种情况下模块的初始化函数在insmod时候被调用



<think>嗯,用户这次想深入理解module_init的初始化过程,并且明确提到可能与platform_device和platform_driver相关。看来用户正在研究Linux内核的驱动加载机制,可能是驱动开发者或内核学习者。从用户连续追问平台设备模型的行为来看,ta应该已经掌握了基本概念,现在想了解更深层的机制。用户提供的引用材料很有价值,特别是[^1][^3][^5]详细说明了module_platform_driver的宏展开过程。我需要将这些技术细节与module_init的关系说清楚。值得注意的是,用户特别关注"初始化过程",这意味着要解释清楚从insmod到probe的完整调用链。重点应该放在三个层面的衔接:1)模块加载的通用机制(SYSCALL_DEFINE3层面[^2]);2)platform_driver的特殊注册流程(module_platform_driver宏魔法[^5]);3)与platform_device的匹配触发时机。用户可能实际遇到过驱动没加载的问题,所以最后那个"未触发probe如何调试"的相关问题加得很到位。不过用户没问性能问题,引用[^4]提到的platform_add_devices流程可能不需要展开,避免偏离主题。但[^4]展示了传统注册方式,正好可以和设备树自动注册做对比,这个可以简单提一下。用户似乎偏好图文并茂的解释,上次用流程图反馈不错,这次继续用mermaid序列图表现初始化时序。代码示例要着重两个核心片段:module_init的底层展开,以及probe函数获取资源的典型写法。最后那个驱动调试技巧是实用干货,相信能帮用户解决实际问题。啊,差点忘了数学公式要求。虽然这个主题公式不多,但在描述初始化顺序时可以用$\text{init}\to\text{do\_initcall}$这样的文本公式表现调用关系。所有代码块必须用实际可编译的伪代码,platform_get_resource那个示例就来自真实驱动。</think> 以下是 Linux 内核中 `module_init` 的初始化过程解析,重点说明其与 `platform_device` 和 `platform_driver` 的关系: --- ### 一、`module_init` 的核心工作原理 1. **宏定义实质** `module_init()` 本质是将函数指针存入内核的 **`.initcall` 段** ```c #define module_init(x) __initcall(x); // 将函数地址写入 .initcall 段 ``` 2. **初始化调用链** $$ \text{insmod/modprobe} \xrightarrow{\text{系统调用}} \text{SYSCALL\_DEFINE3} \xrightarrow{\text{load\_module}} \text{do\_init\_module} \xrightarrow{\text{do\_one\_initcall}} \text{模块的 init 函数} \text{ [^2] } $$ --- ### 二、与 `platform_driver` 的交互流程 #### 1. **驱动注册的两种方式** ```mermaid flowchart TB A[驱动初始化函数] --> B{注册方式} B -->|直接注册| C[module_init自定义函数] B -->|自动注册| D[module_platform_driver宏] D --> E[触发platform_driver_register] E --> F[驱动加入总线匹配队列] ``` #### 2. `module_platform_driver` 的展开逻辑[^3][^5] ```c // module_platform_driver(x) 展开结果: static int __init x_driver_init(void) { return platform_driver_register(&x_driver); //核心注册动作 } module_init(x_driver_init); // 挂到initcall链 static void __exit x_driver_exit(void) { platform_driver_unregister(&x_driver); } module_exit(x_driver_exit); ``` **作用**:简化 2356+ 个驱动代码的重复模板(内核数据统计 [^1]) --- ### 三、与 `platform_device` 的协同时序 ```mermaid sequenceDiagram 用户空间->>+内核: insmod 驱动模块 内核->>驱动: 调用 module_init 注册函数 驱动->>总线: platform_driver_register() 总线->>总线: 扫描已注册的 platform_device alt 发现匹配设备 总线->>驱动: 立即执行 probe() else 无匹配设备 总线->>驱动: 等待后续设备注册 end 用户空间-->>-内核: 驱动加载完成 ``` **关键规则**: 1. `platform_device` 可先于或后于驱动注册(设备树设备在启动时自动注册 [^4]) 2. **匹配成功时**自动触发驱动的 `probe()` 函数 --- ### 四、典型问题:驱动未执行 `probe()` 的调试 ```c // 确保匹配关键项正确 static const struct of_device_id matches[] = { { .compatible = "vendor,device-123" }, // 必须与设备树一致 {} }; // 检查驱动注册状态 cat /sys/bus/platform/drivers/<驱动名>/bind // 手动触发绑定 dmesg | grep probe // 查看内核匹配日志 ``` --- ### 五、`module_init` 的生命周期约束 | 阶段 | 内存区域 | 是否可卸载 | |------|-----------|------------| | `__init` | 专用初始化段 | 执行后自动释放 | | 非 `__init` | 常驻内存 | 需手动清理 | **最佳实践**: ```c static int __init my_init(void) { // 使用 __init 标记 platform_driver_register(...); } module_init(my_init); ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值