Skip to content

第一百五十二章:热插拔

合道期

涉及内核源码:

林小源来到设备山脉的一处入口广场。广场上人来人往——每隔几息,就有一个新的设备从广场边缘的传送门中出现,或者一个已有设备被传送门吸走。

一个穿着橙色制服的哨兵站在广场中央,他的眼睛紧盯着每一个传送门。每当有新设备出现,他就吹响一声号角——号角声沿着广场扩散,触发一连串的连锁反应。

"我是 hotplug 机制。"哨兵说,"设备随时可能插入或拔出——USB 闪存、外接显示器、PCIe 网卡——我负责在设备出现或消失时通知整个系统。"

一个 USB 闪存盘从传送门中浮现。哨兵立刻吹响号角,号角声化作一道波纹向四面八方扩散。波纹首先到达内核空间的一个监听者,监听者开始检测设备类型、加载驱动、初始化硬件。然后波纹继续扩散到用户空间——一个叫 udev 的管家接收到波纹,根据预设的规则在 /dev/ 下创建了设备节点。

"从设备插入到可用,整个过程不超过一秒钟。"哨兵说。

林小源看着那些不断出现和消失的设备,感叹道:即插即用,说起来简单,背后可是一整套机制在撑着。


c
/*
 * 热插拔:
 *
 *   设备插入 → 内核检测 → 加载驱动 → 设备可用
 *   设备拔出 → 内核检测 → 卸载驱动 → 设备移除
 *
 * 热插拔的机制:
 *   1. 硬件检测(中断、轮询)
 *   2. 内核通知(kobject_uevent)
 *   3. 用户空间响应(udev)
 *
 * udev:
 *   用户空间设备管理器
 *   根据内核事件创建/删除设备节点
 *   根据规则设置权限
 *
 * 热插拔事件:
 *   KOBJ_ADD — 设备添加
 *   KOBJ_REMOVE — 设备移除
 *   KOBJ_CHANGE — 设备变化
 */

printf("=== 热插拔 — 动态管理设备 ===\n\n");

printf("热插拔:\n\n");
printf("  设备插入 → 内核检测 → 加载驱动\n");
printf("  设备拔出 → 内核检测 → 卸载驱动\n\n");

printf("--- 热插拔的机制 ---\n");
printf("1. 硬件检测:\n");
printf("   中断或轮询\n");
printf("   检测设备变化\n\n");
printf("2. 内核通知:\n");
printf("   kobject_uevent()\n");
printf("   发送 uevent 到用户空间\n\n");
printf("3. 用户空间响应:\n");
printf("   udev 接收事件\n");
printf("   创建/删除设备节点\n\n");

printf("--- udev ---\n");
printf("用户空间设备管理器:\n");
printf("  接收内核 uevent\n");
printf("  创建设备节点 /dev/*\n");
printf("  设置权限\n\n");
printf("udev 规则:\n");
printf("  /etc/udev/rules.d/\n");
printf("  根据设备属性匹配\n\n");

printf("--- 热插拔事件 ---\n");
printf("KOBJ_ADD:\n");
printf("  设备添加\n\n");
printf("KOBJ_REMOVE:\n");
printf("  设备移除\n\n");
printf("KOBJ_CHANGE:\n");
printf("  设备变化\n\n");

printf("--- 查看热插拔事件 ---\n");
printf("udevadm monitor\n");
printf("  显示内核事件\n\n");
printf("dmesg | grep -i usb\n");
printf("  查看 USB 热插拔日志\n");

林小源跟着 udev 管家走进了广场旁的一间规则室。规则室的墙壁上挂满了规则牌——每块规则牌上都写着匹配条件和执行动作。

"我就是 udev。"管家是个中年人,戴着一副圆框眼镜,手里拿着一本厚厚的规则手册,"内核通过 kobject_uevent() 发送事件给我——KOBJ_ADD 是设备添加,KOBJ_REMOVE 是设备移除,KOBJ_CHANGE 是设备状态变化。我收到事件后,根据这些规则来决定做什么。"

他翻开规则手册,指着一行规则:"比如这条——当一个 USB 设备的 vendor ID 是 0x1234 且 product ID 是 0x5678 时,在 /dev/ 下创建一个叫 'my_device' 的节点,并设置权限为 0666。"

林小源看到管家的工作台上有三个收集箱,分别标着 ADD、REMOVE、CHANGE。每当哨兵的号角声传来,一个事件令牌就从天花板上落下,落入对应的收集箱。管家捡起令牌,翻看规则手册,然后执行对应的动作。

"你不能自己决定规则吗?"林小源问。

"规则在 /etc/udev/rules.d/ 里,由管理员编写。"管家推了推眼镜,"我只负责执行。内核告诉我发生了什么,规则告诉我该怎么做。我只是一个忠实的执行者。"

远处传来一声低沉的号角——一个 USB 设备被拔出了。管家从 REMOVE 箱中捡起一个令牌,看了看设备名,然后走到 /dev/ 目录下,删除了对应的节点。整个过程干净利落。

udev 这个管家倒是省心——内核说啥它干啥,规则怎么写它就怎么执行。

林小源回到广场,仔细观察哨兵的号角波纹。他发现波纹的传播并不是简单的广播——它是通过 netlink socket 发送到用户空间的。

"kobject_uevent() 是关键函数,"哨兵说,"当一个 kobject 的状态发生变化时,内核调用 kobject_uevent() 构造一个 uevent 消息,然后通过 netlink 广播到所有监听者。"

林小源看到波纹中携带着丰富的信息——设备名、子系统名、设备属性、动作类型。这些信息被编码成键值对,附在 uevent 消息里。

"udev 不是唯一的监听者,"哨兵补充道,"任何程序都可以监听 netlink socket 来接收 uevent。有些嵌入式系统用 mdev 代替 udev——更轻量,但原理相同。"

广场边缘又出现了一个新设备——这次是一个 PCIe 网卡。哨兵没有吹号角——PCIe 的热插拔需要 BIOS 和内核的配合,不像 USB 那么简单。

"不是所有总线都支持热插拔,"哨兵说,"USB 天生就是为热插拔设计的——它的电气规范支持设备的动态连接和断开。PCIe 的热插拔需要额外的硬件支持。但无论是哪种总线,通知机制都是一样的:kobject_uevent → udev。"

他忽然明白,热插拔的核心不是物理上的插拔,而是那套事件通知机制——kobject_uevent 到 udev,一路传递下去。


道藏笔记

内核启示

热插拔的流程其实很直白:硬件检测到设备变化(中断或轮询),内核通过 kobject_uevent() 构造一条 uevent 消息,通过 netlink socket 广播到用户空间。udev 作为忠实的执行者,收到消息后查规则手册,该创建节点就创建、该删除就删除。KOBJ_ADD 是设备来了,KOBJ_REMOVE 是设备走了,KOBJ_CHANGE 是设备状态变了。不是所有总线都支持热插拔——USB 天生就是为这个设计的,PCIe 需要额外硬件支持。但不管哪种总线,通知机制都一样:内核发事件,用户空间响应。你可以用 udevadm monitor 实时查看这些事件。


破关试炼

热插拔之试

热插拔事件需要通知用户空间时,本章提到内核会发出哪个 uevent 接口?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道