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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
config space (硬件)

│ pci_setup_device() 读出 vendor/device/subsystem_*/class

pci_dev { .vendor, .device, .subsystem_vendor, .subsystem_device, .class }

│ device_add → kobject_uevent → bus->uevent = pci_uevent (pci-driver.c:1587)
│ 用固定 printf 模板拼出 MODALIAS=pci:v%08Xd%08Xsv%08Xsd%08Xbc%02Xsc%02Xi%02X

netlink uevent 消息 (含 MODALIAS=pci:v0000...i00)

│ systemd-udevd 收到,注入环境变量

udev 规则 RUN{builtin}+="kmod load $env{MODALIAS}"

│ fnmatch 匹配 /lib/modules/$(uname -r)/modules.alias
(该文件由 depmod 扫描各 ko .modinfo 的 alias= 汇总而成,
│ 而每条 alias 来自驱动源码的 MODULE_DEVICE_TABLE(pci, ...) 宏展开)

modprobe <modname>


ko 加载 → pci_register_driver → bus_add_driver → driver_attach → 回补 probe

核心事实: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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1587	static int pci_uevent(const struct device *dev, struct kobj_uevent_env *env)
1588 {
...
1599 if (add_uevent_var(env, "PCI_CLASS=%04X", pdev->class))
1600 return -ENOMEM;
1601
1602 if (add_uevent_var(env, "PCI_ID=%04X:%04X", pdev->vendor, pdev->device))
1603 return -ENOMEM;
1604
1605 if (add_uevent_var(env, "PCI_SUBSYS_ID=%04X:%04X", pdev->subsystem_vendor,
1606 pdev->subsystem_device))
1607 return -ENOMEM;
1608
1609 if (add_uevent_var(env, "PCI_SLOT_NAME=%s", pci_name(pdev)))
1610 return -ENOMEM;
1611
1612 if (add_uevent_var(env, "MODALIAS=pci:v%08Xd%08Xsv%08Xsd%08Xbc%02Xsc%02Xi%02X",
1613 pdev->vendor, pdev->device,
1614 pdev->subsystem_vendor, pdev->subsystem_device,
1615 (u8)(pdev->class >> 16), (u8)(pdev->class >> 8),
1616 (u8)(pdev->class)))
1617 return -ENOMEM;
1618
1619 return 0;
1620 }

该函数注册为 pci_bus_type.ueventpci-driver.c:1730)。触发时机:

  • device_add 里发 KOBJ_ADD uevent;
  • 用户手动 echo add > /sys/bus/pci/devices/<BDF>/uevent
  • hotplug/rescan 路径上的其他 kobject_uevent() 调用。

内核在把消息发给 netlink 之前,会调这个 .uevent 回调让 bus 补字段。PCI 侧就在这里生成:

1
2
3
4
5
PCI_CLASS=020000
PCI_ID=8086:1533
PCI_SUBSYS_ID=8086:0001
PCI_SLOT_NAME=0000:03:00.0
MODALIAS=pci:v00008086d00001533sv00008086sd00000001bc02sc00i00

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
2
3
4
5
6
7
8
9
10
11
12
308	static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
309 char *buf)
310 {
311 struct pci_dev *pci_dev = to_pci_dev(dev);
312
313 return sysfs_emit(buf, "pci:v%08Xd%08Xsv%08Xsd%08Xbc%02Xsc%02Xi%02X\n",
314 pci_dev->vendor, pci_dev->device,
315 pci_dev->subsystem_vendor, pci_dev->subsystem_device,
316 (u8)(pci_dev->class >> 16), (u8)(pci_dev->class >> 8),
317 (u8)(pci_dev->class));
318 }
319 static DEVICE_ATTR_RO(modalias);

用同一个模板打印。可以随时验证:

1
2
cat /sys/bus/pci/devices/0000:03:00.0/uevent
cat /sys/bus/pci/devices/0000:03:00.0/modalias

前者是 uevent 的持久化视图,后者是 modalias_show 单独打印,格式完全一致。

0.2.3. 2.3 MODALIAS 字段模板拆解

1
2
3
4
5
6
7
8
9
10
pci:v00008086d00001533sv00008086sd00000001bc02sc00i00
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ └─ i Programming Interface (class >> 0 & 0xFF)
│ │ │ │ │ │ └───── sc Sub-class (class >> 8 & 0xFF)
│ │ │ │ │ └───────── bc Base class (class >> 16 & 0xFF)
│ │ │ │ └────────────────── sd Subsystem Device ID
│ │ │ └─────────────────────────── sv Subsystem Vendor ID
│ │ └──────────────────────────────────── d Device ID
│ └──────────────────────────────────────────── v Vendor ID
└─────────────────────────────────────────────── 前缀,标识总线类型

其他 bus 的 modalias 前缀不同:usb:platform:of:acpi:i2c: ……格式各由对应 bus 的 .uevent 决定。


0.3.1. 3.1 消息通路

kobject_uevent_envnetlink_broadcast 通过 NETLINK_KOBJECT_UEVENT(family 15)把消息广播到用户态。systemd-udevd(或 eudev/mdev)监听该 netlink socket。

消息体大致像这样(ACTION + 若干 KEY=VALUE):

1
2
3
4
5
6
7
8
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:1c.0/0000:03:00.0
SUBSYSTEM=pci
PCI_CLASS=020000
PCI_ID=8086:1533
PCI_SUBSYS_ID=8086:0001
PCI_SLOT_NAME=0000:03:00.0
MODALIAS=pci:v00008086d00001533sv00008086sd00000001bc02sc00i00

0.3.2. 3.2 udev 规则驱动 modprobe

systemd-udevd 收到事件后,把这些 KEY=VALUE 注入 .rules 的匹配环境,然后运行匹配到的规则。发行版通用规则(Debian/Ubuntu/Fedora 都一样)里有这样一行:

1
2
# /usr/lib/udev/rules.d/80-drivers.rules
ENV{MODALIAS}=="?*", RUN{builtin}+="kmod load $env{MODALIAS}"

含义:只要 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
2
3
4
5
alias pci:v00008086d00001533sv*sd*bc*sc*i*  igb
alias pci:v00008086d00001521sv*sd*bc*sc*i* igb
alias pci:v000010ECd00008168sv*sd*bc*sc*i* r8169
alias pci:v0000144Dd0000A804sv*sd*bc*sc*i* nvme
...

匹配采用 fnmatch 风格通配* 匹配任意串)。上例 pci:v00008086d00001533sv...bc02sc00i00 匹配到第一条,模块名 igb

0.4.2. 4.2 匹配到之后

kmod 内部相当于执行 modprobe igb

  1. 递归解析依赖(modules.dep),加载所有 prerequisite;
  2. init_module / finit_module 系统调用把 ko 装入内核;
  3. ko 的初始化函数(module_init)跑起来,其中通常包含 pci_register_driver(&igb_driver)

0.4.3. 4.3 触发路径 B(回补 probe)

pci_register_driver 展开:

1
2
3
4
5
6
7
8
9
10
11
12
pci_register_driver(&igb_driver)
└─ __pci_register_driver
└─ driver_register
└─ bus_add_driver drivers/base/bus.c:732
├─ klist_add_tail(&priv->knode_bus, &sp->klist_drivers)
└─ if (sp->drivers_autoprobe)
driver_attach drivers/base/dd.c:1307
└─ bus_for_each_dev(pci_bus_type, __driver_attach)
└─ __driver_attach drivers/base/dd.c:1232
├─ driver_match_device → pci_bus_match
└─ driver_probe_device → really_probe
└─ pci_device_probe → drv->probe

枚举时挂上去、当时没找到驱动的那个 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
2
3
4
5
6
7
static const struct pci_device_id igb_pci_tbl[] = {
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I350_COPPER) },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_COPPER) },
...
{ 0, }
};
MODULE_DEVICE_TABLE(pci, igb_pci_tbl);

PCI_VDEVICE(INTEL, x) 展开为:

1
2
3
{ .vendor = 0x8086, .device = x,
.subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID,
.class = 0, .class_mask = 0 }

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
alias=pci:v00008086d00001533sv*sd*bc*sc*i*

写进 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
2
cat /sys/bus/pci/devices/0000:03:00.0/modalias
# pci:v00008086d00001533sv00008086sd00000001bc02sc00i00

0.7.2. 7.2 查该 MODALIAS 会加载哪个 ko

1
2
modprobe -R pci:v00008086d00001533sv00008086sd00000001bc02sc00i00
# igb

modprobe -R 只解析、不加载,用来验证匹配结果。

0.7.3. 7.3 查 ko 里都声明了哪些 alias

1
2
3
4
modinfo igb | grep alias
# alias: pci:v00008086d00001533sv*sd*bc*sc*i*
# alias: pci:v00008086d00001521sv*sd*bc*sc*i*
# ...

0.7.4. 7.4 手动重放 uevent(触发 udev 走一遍)

1
echo add > /sys/bus/pci/devices/0000:03:00.0/uevent

内核会重发一次 uevent,包含最新的 MODALIAS 字段。适合调试自定义 udev 规则。

0.7.5. 7.5 屏蔽自动加载

1
2
# /etc/modprobe.d/blacklist-igb.conf
blacklist igb

或全局关掉 PCI 的 autoprobe:

1
echo 0 > /sys/bus/pci/drivers_autoprobe

前者只是让 modprobe 拒绝加载 igb,pci_dev 仍会枚举出来;后者则让内核侧的路径 A 完全不跑device_initial_probe 里的开关,见姊妹篇),但 pci_register_driver 走的路径 B 仍会触发 probe——这就是 SR-IOV 场景里”先起 VF、再手动 bind driver”的常用姿势。


0.8. 八、小结

  1. PCI 侧 MODALIAS 是内核 pci_ueventpci-driver.c:1587)用固定 printf 模板从 pci_dev 字段拼出来的,udev 收到时就是现成字符串,不做任何转换
  2. udev 通用规则一句 RUN{builtin}+="kmod load $env{MODALIAS}" 把 MODALIAS 传给 kmod。
  3. kmod 用 fnmatch 在 modules.alias(由 depmod 汇总各 ko .modinfo 生成)里查通配匹配,找到 modname。
  4. modules.alias 里的每条 pattern 都来源于驱动源码 MODULE_DEVICE_TABLE(pci, ...) 宏展开,PCI_ANY_ID 在 modpost 阶段被替换成 *
  5. modprobe 加载 ko;ko module_init 里的 pci_register_driver 触发路径 Bbus_for_each_dev__driver_attachpci_bus_matchreally_probepci_device_probedrv->probe),把先前就位、当时无驱动的 pci_dev 补绑上。
  6. 非 PCI 总线(USB / Platform / ACPI / I2C / virtio)机制完全一致,只是 modalias 前缀和字段不同。

0.9. 参考


Linux 驱动 ko 动态加载:uevent、udev、kmod 协作机制
https://martinbj2008.github.io/2026/07/09/2026-07-09-linux-ko-dynamic-load.ai/
Author
Martinbj2008
Posted on
July 9, 2026
Licensed under