Linux 驱动 ko 动态加载:uevent、udev、kmod 协作机制
姊妹篇 Linux 内核 PCI:pci_dev 是如何与 driver 建立绑定的 提到:当 pci_dev 枚举出来时,若对应驱动的 ko 还没加载,路径 A 会”空手而归”,pci_dev 只是”就位”等待。真正让这条链闭合的,是内核 → udev → kmod → modprobe → pci_register_driver → 路径 B 回补 probe 这一套用户态协作。
本文专门梳理这条链,特别是 MODALIAS 是如何生成、如何被用来找到正确的 ko 的。
本文中所有代码引用均基于内核 v7.2-rc2-22-g0e35b9b6ec0f 的源码。【Claude-4.7-Opus】
0.1. 一、总链路概览
1 | |
核心事实:MODALIAS 不是 udev 拼的,是内核直接拼好塞给 udev 的。udev/kmod 只做”字符串匹配 → 加载”,不做任何 PCI ID 到 MODALIAS 的转换。
下面各节逐段展开。
0.2. 二、内核在 uevent 里就已经写好 MODALIAS
0.2.1. 2.1 pci_uevent 回调
drivers/pci/pci-driver.c
1 | |
该函数注册为 pci_bus_type.uevent(pci-driver.c:1730)。触发时机:
device_add里发KOBJ_ADDuevent;- 用户手动
echo add > /sys/bus/pci/devices/<BDF>/uevent; - hotplug/rescan 路径上的其他
kobject_uevent()调用。
内核在把消息发给 netlink 之前,会调这个 .uevent 回调让 bus 补字段。PCI 侧就在这里生成:
1 | |
MODALIAS 里的字段来源就是 pci_setup_device 阶段从 config space 读到的 vendor id、device id、subsystem id 和 class code,按 %08X / %02X 直接字符串化,没有别的转换。
0.2.2. 2.2 sysfs 的 modalias 属性同格式
drivers/pci/pci-sysfs.c
1 | |
用同一个模板打印。可以随时验证:
1 | |
前者是 uevent 的持久化视图,后者是 modalias_show 单独打印,格式完全一致。
0.2.3. 2.3 MODALIAS 字段模板拆解
1 | |
其他 bus 的 modalias 前缀不同:usb:、platform:、of:、acpi:、i2c: ……格式各由对应 bus 的 .uevent 决定。
0.3. 三、netlink uevent 消息与 udev 的处理
0.3.1. 3.1 消息通路
kobject_uevent_env → netlink_broadcast 通过 NETLINK_KOBJECT_UEVENT(family 15)把消息广播到用户态。systemd-udevd(或 eudev/mdev)监听该 netlink socket。
消息体大致像这样(ACTION + 若干 KEY=VALUE):
1 | |
0.3.2. 3.2 udev 规则驱动 modprobe
systemd-udevd 收到事件后,把这些 KEY=VALUE 注入 .rules 的匹配环境,然后运行匹配到的规则。发行版通用规则(Debian/Ubuntu/Fedora 都一样)里有这样一行:
1 | |
含义:只要 uevent 里 MODALIAS 非空,就调用 udev 的 kmod builtin,把 MODALIAS 字符串作为参数传进去。
0.3.3. 3.3 coldplug:udevadm trigger
系统启动或运维手动 udevadm trigger --action=add,udev 会遍历 /sys 下的每个 kobject,往每个 uevent 文件写 add,从而让内核为每个已存在的设备重新发一次 uevent——这就是所谓 coldplug:把开机前就已经存在的硬件的 uevent 补跑一遍,触发对应 ko 加载。
0.4. 四、kmod load 如何找到目标 ko
0.4.1. 4.1 modules.alias
kmod 拿到 pci:v00008086d00001533sv00008086sd00000001bc02sc00i00 这个字符串,去查 /lib/modules/$(uname -r)/modules.alias(由 depmod -a 生成)。文件内容形如:
1 | |
匹配采用 fnmatch 风格通配(* 匹配任意串)。上例 pci:v00008086d00001533sv...bc02sc00i00 匹配到第一条,模块名 igb。
0.4.2. 4.2 匹配到之后
kmod 内部相当于执行 modprobe igb:
- 递归解析依赖(
modules.dep),加载所有 prerequisite; init_module/finit_module系统调用把 ko 装入内核;- ko 的初始化函数(
module_init)跑起来,其中通常包含pci_register_driver(&igb_driver)。
0.4.3. 4.3 触发路径 B(回补 probe)
pci_register_driver 展开:
1 | |
枚举时挂上去、当时没找到驱动的那个 pci_dev,就在这里被 bus_for_each_dev 遍历到、匹配上、跑 igb_probe。整条链闭合。
0.5. 五、modules.alias 里的通配从何而来
0.5.1. 5.1 驱动源码的 MODULE_DEVICE_TABLE
驱动源码里典型写法(drivers/net/ethernet/intel/igb/igb_main.c):
1 | |
PCI_VDEVICE(INTEL, x) 展开为:
1 | |
PCI_ANY_ID 是 (~0)。
0.5.2. 5.2 编译期展开
MODULE_DEVICE_TABLE(pci, tbl) 是宏,本质是让内核构建脚本 scripts/mod/file2alias.c 在 modpost 阶段扫描每个 ko 的 __mod_pci__*_device_table(对应 tbl),按 PCI 的 alias 生成规则打印字符串,塞进 ko 的 .modinfo 段。规则就是:
vendor == PCI_ANY_ID→ 输出v*;否则v%08X;device == PCI_ANY_ID→ 输出d*;否则d%08X;subvendor/subdevice同理;- class 按
class_mask决定每个字节是数值还是*。
所以上面 igb_pci_tbl 里 { vendor=0x8086, device=0x1533, subvendor=ANY, subdevice=ANY, class=0, class_mask=0 } 展开成:
1 | |
写进 igb.ko 的 .modinfo。
0.5.3. 5.3 depmod 汇总
depmod -a 扫 /lib/modules/$(uname -r)/kernel/**/*.ko,抽出 .modinfo 里每条 alias=...,按 alias <pattern> <modname> 一行写进 modules.alias。同时生成 modules.dep(依赖关系)、modules.symbols 等。
发行版打包时会预先跑 depmod;内核升级后 postinst 也会重跑。手动改了 /lib/modules/xxx/extra/*.ko 后必须 depmod -a 才能被 kmod 发现。
0.6. 六、非 PCI 总线也是同一套路
不同 bus 的 modalias 前缀和字段不同,但机制一致:
| bus | .uevent 回调 |
modalias 前缀 | 关键字段 |
|---|---|---|---|
| PCI | pci_uevent |
pci: |
vendor/device/subsystem/class |
| USB | usb_uevent |
usb: |
vendor/product/class/interface |
| Platform (DT) | of_device_uevent |
of: |
compatible 字符串列表 |
| ACPI | acpi_device_uevent |
acpi: |
HID/CID 列表 |
| I2C | i2c_device_uevent |
i2c: |
name |
| virtio | virtio_uevent |
virtio: |
device/vendor |
驱动源码都用同一个 MODULE_DEVICE_TABLE(<bustype>, tbl);modpost 根据 <bustype> 走 file2alias.c 里对应的 handler 生成 alias 字符串。udev + kmod 无差别对待。
0.7. 七、几个实用调试点
0.7.1. 7.1 手动确认 MODALIAS 是否正确
1 | |
0.7.2. 7.2 查该 MODALIAS 会加载哪个 ko
1 | |
modprobe -R 只解析、不加载,用来验证匹配结果。
0.7.3. 7.3 查 ko 里都声明了哪些 alias
1 | |
0.7.4. 7.4 手动重放 uevent(触发 udev 走一遍)
1 | |
内核会重发一次 uevent,包含最新的 MODALIAS 字段。适合调试自定义 udev 规则。
0.7.5. 7.5 屏蔽自动加载
1 | |
或全局关掉 PCI 的 autoprobe:
1 | |
前者只是让 modprobe 拒绝加载 igb,pci_dev 仍会枚举出来;后者则让内核侧的路径 A 完全不跑(device_initial_probe 里的开关,见姊妹篇),但 pci_register_driver 走的路径 B 仍会触发 probe——这就是 SR-IOV 场景里”先起 VF、再手动 bind driver”的常用姿势。
0.8. 八、小结
- PCI 侧 MODALIAS 是内核
pci_uevent(pci-driver.c:1587)用固定 printf 模板从 pci_dev 字段拼出来的,udev 收到时就是现成字符串,不做任何转换。 - udev 通用规则一句
RUN{builtin}+="kmod load $env{MODALIAS}"把 MODALIAS 传给 kmod。 - kmod 用 fnmatch 在
modules.alias(由depmod汇总各 ko.modinfo生成)里查通配匹配,找到 modname。 modules.alias里的每条 pattern 都来源于驱动源码MODULE_DEVICE_TABLE(pci, ...)宏展开,PCI_ANY_ID在 modpost 阶段被替换成*。modprobe加载 ko;komodule_init里的pci_register_driver触发路径 B(bus_for_each_dev→__driver_attach→pci_bus_match→really_probe→pci_device_probe→drv->probe),把先前就位、当时无驱动的 pci_dev 补绑上。- 非 PCI 总线(USB / Platform / ACPI / I2C / virtio)机制完全一致,只是 modalias 前缀和字段不同。