各种initcall的执行先后顺序(module_init、postcore_initcall、arch_initcall、subsys_initcall、 fs_initcall)

 

现在以module_init为例分析initcall在内核中的调用顺序

在头文件init.h中,有如下定义:

#define module_init(x)     __initcall(x);

很明显,module_init()只是一个面具而已,揭开这个面具,下面藏着的是__initcall()

__initcall()又是何方神圣呢?继续揭露真相:

#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的真身:__define_initcall(level,fn,id),仔细推敲这个真身,知道这是个宏,它把传给module_init的函数名组装成以__initcall为前缀的、以6为后缀的函数名,并把这个函数定义到代码段.initcall6.init里面。

在代码段.initcall6.init里面?这函数躲在这里干嘛,啥时候才轮得到它出头啊!找到有此字符串的文件vmlinux.lds.h,相关代码如下所示:

#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)

要命,又是一个陌生的宏,不过还好的是他样子看起来还不难看,而且好找规律,看看这是个什么样的东西呢?

字符串.initcall6.init夹杂在这个宏的第n行,具体自己数,他们挨个挨个有顺序的组成一个整体,此整体又构成一个用大写字母写的宏INITCALLS,从气势看这个东西给人牛逼的感觉,这么神奇,那他在那里高就呢?

坑爹的,踏破铁鞋无觅处,得来非常费工夫,他竟然在一个偏僻的vmlinux.lds.S里面!估计超出来好多童鞋可以接受的范围吧,这还不算,这还是个汇编文件!哎,不管怎样,追根述源顺藤摸瓜找到了INITCALLS的娘家,却发现这是个完全陌生的世界!蛋蛋为此疼了好久,最终还是鼓起来武松打虎的勇气依然闯了进去。人说绝望之后就会有希望,柳暗之后又是一村,哥哥我满眼噙着泪水的发现他们是有人情味的!这个激动啊,不是三言两语可以说得清的,来看看INITCALLS她娘家房子咋样:

              …………省略一大段………….

__initcall_start = .;

                     INITCALLS

              __initcall_end = .;

              __con_initcall_start = .;

                     *(.con_initcall.init)

              __con_initcall_end = .;

              __security_initcall_start = .;

                     *(.security_initcall.init)

              __security_initcall_end = .;

              …………省略一大段……………

是不是觉得这还是有点人道主义的,不会像阿拉伯为或藏文一样让你想跳楼吧,来认识一下它的三大姑二大婆吧,对于像__initcall_start = .与__initcall_end = .之类狐假虎威的家伙咱们初来乍到时吃过他不少亏,印象是相当深刻的,一眼就瞅见它的衰样了,这里的小点不就是代表当前地址吗,一个等号不就是把点代表的当前地址付给了左边的变量啦,这难不倒已经有两把刷子的我的,照此看来,估摸着__initcall_start与__initcall_end会有同伙在.c文件里面和他们暗通款曲狼狈为奸,待会再好好戏耍它一番,先pass了,接着看看还有啥新鲜的,嗯?没了,还是回头吧,没苦我一般是不会自找来尝的,甜头嘛另当别论啦,哈哈哈,

言归正传,INITCALLS在__initcall_start = .与__initcall_end = .之间,表示INITCALLS宏内涵的相关段代码顺序存放在这里,module_init所代表段的镶嵌其中,等候挨个轮到自己被光顾,从INITCALLS的内容看它被访问的时刻排得还是挺后的,那么其他的又是何方圣神呢?其看下面分解:

#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)

至此我们应该明白了各个initcall是如何来的以及在代码存储空间上是怎么组织的了吧,下面来看看内核是什么时候调用它的。

内核启动流程如下所示

Main()àlinux_main()àstart_uml()àstart_kernel_proc()àstart_kernelàrest_inità kernel_inità do_basic_setupà do_initcalls

do_initcalls的代码如下所示

static void __init do_initcalls(void)

{

       initcall_t *call;

 

       for (call = __early_initcall_end; call < __initcall_end; call++)

              do_one_initcall(*call);

 

       /* Make sure there is no pending stuff from the initcall sequence */

       flush_scheduled_work();

}

上面的代码中,__early_initcall_end在INITCALLS内定义,__initcall_end在文件vmlinux.lds.S中定义,他们代表的是一些初始化函数的指针数组起始与结束地址,执行函数do_initcalls时,包含在这各指针数组里面的函数顺序的被调用以执行一些必要的初始化工作。至此,明白的各initcall的执行时刻了吧。

 

<think>好的,我现在需要帮助用户了解Linux内核模块中module_init和late_initcall的用法及区别。首先,我要回忆一下内核初始化的相关知识。记得内核模块的初始化函数是通过宏来定义的,这些宏决定了函数在内核启动过程中的调用顺序。 用户问的是module_init和late_initcall,所以需要明确这两者的不同之处。可能还有其他类似的宏,比如__initcall,但用户主要关注这两个。首先,我应该解释每个宏的基本用法,然后比较它们的执行顺序和用途差异。 我记得内核的初始化分为多个阶段,不同的初始化宏对应不同的优先级。module_init通常用于设备驱动,而late_initcall可能用于那些需要在内核初始化后期执行的模块。需要确认这一点是否正确。例如,module_init对应的初始化级别可能比late_initcall更早,或者更晚? 接下来,我需要查阅内核源码中的相关信息,或者参考内核文档。比如,在include/linux/init.h中,定义了不同级别的初始化宏,如core_initcallpostcore_initcallarch_initcallsubsys_initcallfs_initcall、device_initcall、late_initcall等,它们的顺序是依次递增的,即优先级依次降低。而module_init宏会根据模块是否内置到内核还是作为可加载模块来区分:如果是内置的,它等同于device_initcall;如果是可加载模块,则会被标记为模块的初始化函数,在模块加载时由insmod或modprobe调用。 所以,当模块被编译进内核时,module_init对应的初始化函数会在device_initcall阶段执行,而late_initcall则是在更晚的阶段执行。因此,它们的执行顺序不同,导致用途也不同。late_initcall通常用于那些需要等待其他子系统初始化完成后再执行的代码。 需要确保这些信息准确无误,可能需要引用相关的内核文档或书籍。例如,《Linux设备驱动程序》一书中可能提到这些初始化宏的使用方法。另外,应该给出示例代码,说明如何在实际模块中使用这两个宏,以及它们在内核启动时的执行顺序差异。 用户可能还想知道为什么会有不同的初始化宏,以及如何选择合适的宏。这时候需要解释内核初始化的阶段划分,以及不同子系统之间的依赖关系。例如,某些驱动可能依赖于其他子系统已经初始化完成,因此需要将它们的初始化函数放在较晚的阶段。 最后,总结两者的主要区别:执行顺序不同,使用场景不同。同时,提醒用户注意模块是内置还是可加载的情况,因为module_init的行为会有所不同。如果是可加载模块,其初始化函数在模块插入时执行,而内置模块则在对应的初始化阶段执行。 在生成相关问题时,需要考虑用户可能进一步的问题,例如其他初始化宏的区别,或者如何调试初始化顺序问题。这样可以帮助用户更深入地理解内核模块的初始化机制。</think>### module_init和late_initcall的区别与用法 #### 一、基本定义 1. **module_init** 是Linux内核模块最常用的初始化宏,用于声明模块的入口函数。根据模块类型(内置/可加载)有不同的行为: - 当模块**编译进内核**时,初始化函数在`device_initcall`阶段执行(第6级) - 当模块**动态加载**时,初始化函数在`insmod/modprobe`时执行 2. **late_initcall** 属于内核初始化阶段中**最后一级**的初始化宏(第7级),专门用于需要延迟执行的初始化操作[^1] #### 二、执行顺序对比 $$ \begin{array}{|c|c|} \hline \text{初始化阶段} & \text{优先级} \\ \hline core\_initcall & 1 \\ postcore\_initcall & 2 \\ arch\_initcall & 3 \\ subsys\_initcall & 4 \\ fs\_initcall & 5 \\ device\_initcall & 6 \\ late\_initcall & 7 \\ \hline \end{array} $$ #### 三、典型使用场景 1. **module_init** 设备驱动初始化(如注册PCI驱动)、子系统注册等需要较早初始化的操作 ```c // 网络驱动示例 static int __init mynet_init(void) { register_netdev(net_device); } module_init(mynet_init); ``` 2. **late_initcall** 需要等待其他子系统就绪的操作,例如: - 依赖文件系统初始化的模块 - 需要最后初始化的硬件组件 - 系统状态监控模块 ```c // 延迟初始化示例 static int __init late_demo(void) { printk("This runs after all regular initcalls"); } late_initcall(late_demo); ``` #### 四、关键差异总结 | 特性 | module_init | late_initcall | |---------------------|--------------------------------|-----------------------| | 执行阶段 | 第6级(内置模块)或动态加载时 | 第7级 | | 模块类型 | 支持动态和静态模块 | 仅静态编译进内核的模块| | 典型延迟 | 10ms级 | 100ms级 | | 依赖关系处理 | 常规依赖 | 强延迟依赖 | #### 五、调试技巧 1. 查看初始化顺序: ```bash dmesg | grep "initcall" ``` 2. 通过`/sys/module/`查看模块初始化状态: ```bash ls /sys/module/<module_name>/initstate ```
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值