Linux 内核的 Initcall 机制 是 Linux 系统从内核启动到用户空间初始化过程中,执行模块化初始化代码的核心手段。简单来说,它允许内核开发者通过宏定义,将不同的驱动、子系统初始化函数放置在内核镜像的特定内存段中,从而让内核在启动时按预定优先级自动调用它们。
模块化解耦:驱动开发者不需要修改内核的启动主逻辑,只需在驱动代码中添加 module_init() 或 device_initcall(),内核构建系统就会自动将其集成。
解决依赖性:例如,文件系统驱动必须在块设备驱动初始化之后加载,Initcall 的分层机制保证了这种顺序。
内存优化:这些初始化函数在执行完成后,会被内核从内存中释放(__init 修饰符),从而节省宝贵的 RAM。
Initcall 机制的核心在于链接器脚本和代码段归类。
3.1. 宏定义 (The Macros)
内核提供了多个宏来标记初始化函数,例如:
early_initcall
core_initcall
postcore_initcall
arch_initcall
subsys_initcall
fs_initcall
device_initcall (最常用)
late_initcall
这些宏本质上是将函数地址放置到一个特定的代码段(Section)中。例如,__define_initcall(level, fn, id) 宏会将函数 fn 存入名为 .initcall<level>.init 的段中。
3.2. 链接器脚本
内核编译时,链接器(ld)会按照预定义的顺序,将所有这些分散在不同模块(. o 文件)中的 .initcall*.init 段,按优先级顺序排列并合并到内核镜像的特定内存区域。
当内核启动到 kernel_init() 函数时,它会依次调用 do_initcalls()。
分段扫描:do_initcalls() 会遍历预先定义好的各个 Initcall 级别。
按序调用:它会遍历 .initcall0.init 到 .initcall7.init(或其他级别)对应的内存地址区间。
循环执行:在每个级别内,内核会取出该段内的所有函数指针,并循环执行每一个函数。
不同的级别决定了初始化的时机,这对于存在依赖关系的子系统至关重要。
提示:驱动中的 module_init(),默认会映射到 device_initcall