[linux]内核启动加载驱动文件的流程

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 进程,完成整个系统的启动。


总结:完整流程一览

  1. 开机 → BIOS/UEFI 自检,加载 Bootloader (GRUB)。
  2. Bootloader 将内核 和 initramfs 加载到内存。
  3. 内核解压并运行,执行 do_initcalls(),严格按照 initcall 优先级 列表调用所有内置驱动的初始化函数。数字越小,执行越早。
  4. 在调用到 subsys_initcall 级别时,会初始化各类总线驱动(如 pci_bus_type),为设备匹配做好准备。
  5. 总线驱动注册时,或设备被枚举到时,会触发 match 和 probe 机制,完成设备与驱动的绑定,设备由此可用。
  6. 为了加载根文件系统 ,内核利用 initramfs 中的驱动,并挂载真正的根文件系统。
  7. 根文件系统挂载后,内核启动 /sbin/init 进程(通常是 systemd),系统进入用户空间启动阶段。
  8. 用户空间的 udev 守护进程根据内核发现的硬件事件,动态调用 modprobe 加载 /lib/modules/ 下的可加载模块,完成后续所有硬件的驱动加载。
相关推荐
HideF5 小时前
Linux
linux·服务器·网络
Ruiery7 小时前
Linux 6.6内核内存管理深度解析(十三):slab/slub 分配器
linux·运维·服务器
丰锋ff9 小时前
压缩命令笔记
linux
小李不想当小白10 小时前
SPI通信外设与W25Q64 (硬件SPI)(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
GeW11 小时前
产业新优势基础软件的人才底盘,从Linux与数据库开始
linux·数据库
by2099911 小时前
Linux GCC/G++ 编译与 Makefile 构建
linux·运维·服务器·经验分享
cq林志炫12 小时前
两个Linux服务器之间同步文件
linux·运维·服务器
无人生还别怕13 小时前
如何搭建k8s服务(containerd部署)
linux·云原生·容器·kubernetes·openanolisos
企鹅侠客14 小时前
Linux CPU 负载平均值(Load Average)详解:从原理到实践
linux·运维·服务器·服务器负载
易点云徐源14 小时前
HP 暗影精灵 PRO 16 安装双系统后,开机直接进入 Windows,按 F9 才能进入 Ubuntu:GRUB 菜单不显示的处理方法
linux·运维·ubuntu