Linux 内核驱动机制总结:模块、注册与加载流程
本文档总结了关于 Linux 驱动程序的存在形式、注册时机、硬件发现机制以及文件存储结构的对话要点。
1. 核心概念辨析:驱动 vs 模块
驱动(Driver)和模块(Module)是两个维度不同的概念,但在 Linux 中常被混用。
| 概念 | 定义 | 关系 |
|---|---|---|
| 驱动 (Driver) | 功能层面。指控制特定硬件的代码逻辑(如“怎么读写网卡寄存器”)。 | 驱动代码向内核子系统(Bus)注册 driver 结构体。 |
| 模块 (Module) | 部署层面。指可动态加载/卸载的二进制容器(.ko 文件)。 | 模块是驱动的容器。一个模块可以包含一个或多个驱动,也可以不包含驱动(如文件系统)。 |
存在的两种形式
- 内建 (Built-in):编译进内核镜像 (
vmlinux)。启动时自动初始化,常用于核心硬件(CPU、根文件系统、SATA 控制器)。 - 可加载模块 (Loadable):编译为独立的
.ko文件。按需加载,常用于外设(USB、显卡、声卡)。
2. 驱动的注册与硬件发现 (Discovery & Probe)
Linux 驱动模型遵循 总线 (Bus) - 设备 (Device) - 驱动 (Driver) 的分离设计。
A. 什么时候注册?
- 内建驱动:在内核启动极早期 (
start_kernel->do_initcalls) 按优先级顺序执行init函数注册。 - 模块驱动:在用户态执行
insmod或modprobe加载模块时,执行module_init进行注册。
B. 内核如何发现硬件?
内核通过 枚举 (Enumeration) 过程发现硬件,生成 struct device 对象。
- 智能总线 (PCI, USB):具有自举能力。总线控制器扫描物理插槽,硬件上报 Vendor/Device ID。内核主动探测。
- 静态总线 (Platform, I2C, SPI):硬件无法自述。依赖固件提供的信息:
- x86/服务器:UEFI 提供的 ACPI 表。
- 嵌入式/ARM:设备树 (Device Tree / .dtb)。
C. UEFI 的角色澄清
- 误区:UEFI 负责注册驱动(如 perf 计数器)。
- 真相:UEFI 仅负责硬件初始化(Power on)和提供硬件信息表(ACPI)。驱动的注册完全由 Linux 内核代码完成。UEFI 可通过锁死寄存器来阻止内核使用某些硬件。
D. 匹配过程 (Match & Probe)
当总线上同时存在“设备对象”和“驱动对象”时,触发匹配:
- Match:总线比对设备的 ID 和驱动支持的 ID 表。
- Probe:匹配成功后,调用驱动的
probe()函数。此时驱动正式接管硬件(申请中断、映射 IO 内存等)。
3. 模块文件存储与 udev 自动加载机制
文件存储路径
所有模块文件存放在:
text
/lib/modules/$(uname -r)/结构通常与内核源码目录一致(如 /lib/modules/.../kernel/drivers/net/)。
udev 查找流程
当硬件插入时,内核发送 uevent,udev 根据硬件 ID 自动加载模块,无需遍历文件系统。
- 索引文件:
depmod命令会在模块根目录下生成索引。modules.alias:硬件 ID (Modalias) -> 模块名 (Module Name) 的映射。modules.dep:模块名 -> 具体.ko文件路径及依赖关系的映射。
- 查找逻辑:
- 硬件上报 ID (如
pci:v00008086...)。 - 查
modules.alias找到对应的模块名(如e1000e)。 - 查
modules.dep找到.ko文件路径。 - 执行
modprobe加载。
- 硬件上报 ID (如
4. 常用调试命令速查
| 目的 | 命令 |
|---|---|
| 查看已加载模块 | lsmod |
| 查看模块详细信息 | modinfo <模块名> (可看 ko 路径、作者、参数) |
| 查看设备对应的驱动 | lspci -k 或 ethtool -i <接口名> |
| 查看驱动注册日志 | `dmesg |
| 查找硬件ID对应关系 | grep "<硬件ID>" /lib/modules/$(uname -r)/modules.alias |
| 查看总线上的绑定情况 | ls -l /sys/bus/pci/drivers/ |