Linux内核启动时,加载驱动文件并非简单地"扫描目录",而是遵循一个分阶段、有优先级的精密流程。
整个过程大致可以分为两大阶段:首先是内置驱动的静态初始化 ,然后是可加载模块的动态按需加载。

第一阶段:内置驱动的静态"点名"
这部分驱动在编译内核时就被直接整合进了内核镜像 (vmlinuz)。它们会在内核启动的早期,按一个预先设定好的"点名册"顺序,被依次调用初始化函数。
1. 核心机制:initcall 优先级
Linux 内核定义了一个包含 14 个等级的优先级表(数字越小,优先级越高),每个驱动初始化函数在编写时,都会被指定一个等级。
| 优先级 | 初始化宏 | 典型用途 |
|---|---|---|
| 最高 | pure_initcall |
最基础的、不依赖其他服务的初始化 |
| ↓ | core_initcall |
核心子系统(如调度、内存管理) |
| ↓ | postcore_initcall |
核心子系统之后的初始化 |
| ↓ | arch_initcall |
架构相关的特定初始化 |
| ↓ | subsys_initcall |
各子系统(如 PCI, USB 总线驱动) |
| ↓ | fs_initcall |
文件系统相关的初始化 |
| ↓ | device_initcall |
绝大多数设备驱动的默认等级 |
| 最低 | late_initcall |
所有其他初始化完成后才执行的驱动 |
你平时编写驱动时使用的 module_init(foo),在没有特别指定的情况下,就等同于 device_initcall,处于第 6 级。这就保证了像 USB 总线驱动这种核心框架(通常使用 subsys_initcall),一定会在具体的 USB 鼠标驱动(使用 device_initcall)之前被加载和初始化。
2. 同一优先级内的顺序:链接顺序决定
优先级相同的驱动,加载顺序由编译和链接的顺序 决定。内核构建系统 (Makefile) 中 obj-y 列表里排在前面的文件,其初始化函数会更早地被放入内存中的特定段里,因此也会更早被调用。所以,调整 Makefile 中文件的顺序,是精细控制同级别驱动加载顺序的有效方法。
第二阶段:模块的动态"按需加载"
对于大量的设备驱动(尤其是非启动必需的硬件),Linux 会将其编译成独立的 .ko 文件 (kernel object),存放在 /lib/modules/ 目录下。这些模块由用户空间的 udev (设备管理器) 配合内核的热插拔机制来动态加载。
当内核检测到一个新设备(例如,你插入一个 U 盘)时,会通过热插拔事件 通知用户空间的 udevd 守护进程。udevd 根据预设的规则,调用 modprobe 命令去加载相应的驱动模块。modprobe 很智能,它会分析模块间的依赖关系,自动按正确的顺序加载所有必需的依赖模块。这种方式确保了只有在硬件实际存在时,其驱动才会被加载,极大地减少了内核的运行时体积。
阶段衔接:initramfs 的桥梁作用
这里存在一个"先有鸡还是先有蛋"的问题:根文件系统(存放 /lib/modules 和 /sbin/init 等关键程序)本身就可能位于一个需要特殊驱动的设备上(如 SCSI 硬盘或 LVM 逻辑卷)。内核在没有加载根文件系统驱动的情况下,是无法挂载它并读取其中的模块的。
解决方案就是 initramfs (早期叫 initrd)。它本质上是一个很小的、包含必要驱动模块和初始化脚本的 cpio 压缩包。Bootloader (如 GRUB) 会将内核和 initramfs 一同加载到内存中。内核会先挂载这个内存中的虚拟根文件系统,在这个"迷你系统"里加载 SCSI 等必要的驱动模块,然后才能找到并挂载真正的根文件系统,最后启动 /sbin/init 进程,完成整个系统的启动。
总结:完整流程一览
- 开机 → BIOS/UEFI 自检,加载 Bootloader (GRUB)。
- Bootloader 将内核 和
initramfs加载到内存。 - 内核解压并运行,执行
do_initcalls(),严格按照initcall优先级 列表调用所有内置驱动的初始化函数。数字越小,执行越早。 - 在调用到
subsys_initcall级别时,会初始化各类总线驱动(如pci_bus_type),为设备匹配做好准备。 - 总线驱动注册时,或设备被枚举到时,会触发
match和probe机制,完成设备与驱动的绑定,设备由此可用。 - 为了加载根文件系统 ,内核利用
initramfs中的驱动,并挂载真正的根文件系统。 - 根文件系统挂载后,内核启动
/sbin/init进程(通常是systemd),系统进入用户空间启动阶段。 - 用户空间的
udev守护进程根据内核发现的硬件事件,动态调用modprobe加载/lib/modules/下的可加载模块,完成后续所有硬件的驱动加载。