Linux内核initcall初始化机制
本文将介绍 Linux 内核的初始化函数结构,其通过 initcall 机制来实现,其核心是一系列优先级不同的宏,用于控制初始化函数的执行顺序。本文的实验的内核环境基于 Linux 5.10。
initcall 的级别与执行顺序
Linux 内核定义了多个初始化级别(按照执行顺序从高到低排列):
宏定义 | 级别名 | 优先级值 | 用途说明 |
---|---|---|---|
early_initcall(fn) |
early | - | 早期的初始化,在 SMP 初始化之前 |
pure_initcall(fn) |
pure | 0 | 用于初始化不能被静态初始化的变量 |
core_initcall(fn) |
core | 1 | 核心子系统初始化 |
postcore_initcall(fn) |
postcore | 2 | 核心子系统之后的初始化 |
arch_initcall(fn) |
arch | 3 | 架构相关初始化 |
subsys_initcall(fn) |
subsys | 4 | 子系统初始化 |
fs_initcall(fn) |
fs | 5 | 文件系统初始化 |
rootfs_initcall(fn) |
rootfs | - | 根文件系统初始化 |
device_initcall(fn) |
device | 6 | 设备驱动初始化(最常用) |
late_initcall(fn) |
late | 7 | 晚期初始化(依赖其他子系统就绪) |
console_initcall(fn) |
console | - | 终端初始化 |
每个级别都有对应的
_sync
版本(如core_initcall_sync
),用于等待该级别所有异步初始化完成。
为了直观的看到不同 initcall 的先后顺序,可以编写一个简单的测试程序加以验证。
目标架构以 RISC-V 为例,新建一个 C 源文件 arch/riscv/kernel/initcall_test.c
,代码如下:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
static int __init early_test_init(void)
{
printk(KERN_INFO "[RISC-V Initcall] early_initcall executed\n");
return 0;
}
early_initcall(early_test_init);
static int __init pure_test_init(void)
{
printk(KERN_INFO "[RISC-V Initcall] pure_initcall executed\n");
return 0;
}
pure_initcall(pure_test_init);
static int __init core_test_init(void)
{
printk(KERN_INFO "[RISC-V Initcall] core_initcall executed\n");
return 0;
}
core_initcall(core_test_init);
static int __init postcore_test_init(void)
{
printk(KERN_INFO "[RISC-V Initcall] postcore_initcall executed\n");
return 0;
}
postcore_initcall(postcore_test_init);
static int __init arch_test_init(void)
{
printk(KERN_INFO "[RISC-V Initcall] arch_initcall executed\n");
return 0;
}
arch_initcall(arch_test_init);
static int __init subsys_test_init(void)
{
printk(KERN_INFO "[RISC-V Initcall] subsys_initcall executed\n");
return 0;
}
subsys_initcall(subsys_test_init);
static int __init fs_test_init(void)
{
printk(KERN_INFO "[RISC-V Initcall] fs_initcall executed\n");
return 0;
}
fs_initcall(fs_test_init);
static int __init rootfs_test_init(void)
{
printk(KERN_INFO "[RISC-V Initcall] rootfs_initcall executed\n");
return 0;
}
rootfs_initcall(rootfs_test_init);
static int __init device_test_init(void)
{
printk(KERN_INFO "[RISC-V Initcall] device_initcall executed\n");
return 0;
}
device_initcall(device_test_init);
static int __init late_test_init(void)
{
printk(KERN_INFO "[RISC-V Initcall] late_initcall executed\n");
return 0;
}
late_initcall(late_test_init);
static int __init console_test_init(void)
{
printk(KERN_INFO "[RISC-V Initcall] console_initcall executed\n");
return 0;
}
console_initcall(console_test_init);
同时在 arch/riscv/kernel/Makefile
中新增该目标文件:
--- a/arch/riscv/kernel/Makefile
+++ b/arch/riscv/kernel/Makefile
@@ -29,6 +29,7 @@ obj-y += riscv_ksyms.o
obj-y += stacktrace.o
obj-y += cacheinfo.o
obj-y += patch.o
+obj-y += initcall_test.o
obj-$(CONFIG_MMU) += vdso.o vdso/
修改完成后编译内核,并使用 QEMU 模拟器进行启动,内核启动的打印信息如下:
...
[ 0.001681] [⭐RISC-V Initcall] console_initcall executed
[ 0.003134] Console: colour dummy device 80x25
[ 0.010536] printk: console [tty0] enabled
[ 0.013249] Calibrating delay loop (skipped), value calculated using timer frequency.. 20.00 BogoMIPS (lpj=40000)
[ 0.013440] pid_max: default: 32768 minimum: 301
[ 0.014683] Mount-cache hash table entries: 16384 (order: 5, 131072 bytes, linear)
[ 0.014842] Mountpoint-cache hash table entries: 16384 (order: 5, 131072 bytes, linear)
[ 0.037256] [⭐RISC-V Initcall] early_initcall executed
[ 0.037908] rcu: Hierarchical SRCU implementation.
[ 0.039356] EFI services will not be available.
[ 0.041471] smp: Bringing up secondary CPUs ...
[ 0.049906] smp: Brought up 1 node, 4 CPUs
[ 0.061418] devtmpfs: initialized
[ 0.066783] [⭐RISC-V Initcall] pure_initcall executed
[ 0.068512] [⭐RISC-V Initcall] core_initcall executed
[ 0.070325] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
[ 0.070544] futex hash table entries: 1024 (order: 4, 65536 bytes, linear)
[ 0.074988] NET: Registered protocol family 16
[ 0.076708] [⭐RISC-V Initcall] postcore_initcall executed
[ 0.079466] [⭐RISC-V Initcall] arch_initcall executed
[ 0.088406] [⭐RISC-V Initcall] subsys_initcall executed
[ 0.128276] vgaarb: loaded
[ 0.129447] SCSI subsystem initialized
[ 0.131491] usbcore: registered new interface driver usbfs
[ 0.132061] usbcore: registered new interface driver hub
[ 0.132356] usbcore: registered new device driver usb
[ 0.139530] [⭐RISC-V Initcall] fs_initcall executed
[ 0.140768] clocksource: Switched to clocksource riscv_clocksource
[ 0.171715] NET: Registered protocol family 2
...
[ 0.190188] PCI: CLS 0 bytes, default 64
[ 0.194343] [⭐RISC-V Initcall] rootfs_initcall executed
[ 0.194487] [⭐RISC-V Initcall] device_initcall executed
[ 0.196698] workingset: timestamp_bits=62 max_order=21 bucket_order=0
[ 0.208411] NFS: Registering the id_resolver key type
...
[ 0.561583] 9pnet: Installing 9P2000 support
[ 0.562203] Key type dns_resolver registered
[ 0.562514] [⭐RISC-V Initcall] late_initcall executed
[ 0.563478] debug_vm_pgtable: [debug_vm_pgtable ]: Validating architecture page table helpers
...
有关 Linux 内核如何实现这样的初始化机制,可以参考这篇文章:Linux 各种 initcall 的调用原理-CSDN博客。
初始化级别的选择
选择 initcall 级别的核心原则是:你的初始化函数所依赖的子系统必须已经初始化完成。
对于个人当前可能涉及到的内核开发工作来说,device_initcall
最常用,适用于大多数设备驱动程序的初始化,此时核心子系统、总线都已准备就绪。其次是 arch_initcall
,进行处理器架构相关的初始化。如果初始化时机并不那么重要,又为了保险起见,可以选择 late_initcall
,此时模块所依赖的子系统已经完全初始化完成。至于其他的一些初始化接口,可能一般不太会用到,等需要的时候再来研究吧。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Lordaeron_ESZ's blog!
评论