4. devtmpfs与设备节点自动创建

在早期的 Linux 系统中,/dev 目录下的设备节点需要由发行版维护者预先创建,或者由用户手动执行 mknod 命令生成。这种方式存在两个明显问题:一是内核新增设备后,用户空间无法立即感知;二是静态创建的节点数量庞大、主次设备号容易冲突。为了解决这些问题,Linux 内核引入了 devtmpfs,并与用户空间的 udev 配合,形成了今天主流的设备节点自动创建机制。

一、devtmpfs 的核心原理

devtmpfs 是一种基于内存的虚拟文件系统,通常挂载在 /dev 目录。它由内核直接维护,而不是由用户空间程序维护。当内核检测到新设备注册时,会在 devtmpfs 中自动创建对应的设备节点,包括字符设备节点和块设备节点,并赋予正确的设备号。

与传统的 tmpfs 或磁盘文件系统不同,devtmpfs 的节点创建动作发生在内核态。设备驱动调用 device_register() 或 cdev_add() 等接口后,内核的设备模型核心会触发 devtmpfs 的创建逻辑,最终在 /dev 下生成节点。这意味着即使 udev 尚未启动,系统也已经拥有基本的设备节点,从而保证根文件系统挂载、控制台访问等早期启动流程能够正常工作。

关键点:devtmpfs 提供的是“内核自动创建的基础节点”,它保证设备节点在用户空间服务启动前就已存在,是启动可靠性的重要保障。

二、内核自动创建节点的过程

内核自动创建节点的过程可以概括为“设备注册 → 设备模型通知 → devtmpfs 创建节点”。当驱动注册一个设备时,内核会为该设备分配 struct device,并把它加入设备层次结构。随后,内核通过 kobject_uevent() 向用户空间发送 uevent 事件,同时 devtmpfs 根据设备的主次设备号创建设备文件。

下面是一个简化的观察过程,展示如何确认 devtmpfs 是否挂载,以及如何查看内核创建的节点:

# 查看 /dev 的挂载类型
mount | grep ' /dev '
# 典型输出:
# devtmpfs on /dev type devtmpfs (rw,nosuid,relatime,size=...)

# 查看内核创建的设备节点
ls -l /dev/null /dev/tty /dev/sda
# crw-rw-rw- 1 root root 1, 3 ... /dev/null
# crw-rw-rw- 1 root root 5, 0 ... /dev/tty
# brw-rw---- 1 root disk 8, 0 ... /dev/sda

# 查看内核 uevent 事件(需要 root 权限)
udevadm monitor --kernel --property

从输出可以看到,/dev/null 的主次设备号为 1,3,/dev/tty 为 5,0,/dev/sda 为 8,0。这些节点由内核在设备注册时自动创建,不需要用户手动执行 mknod。

三、devtmpfs 与 udev 的协作流程

虽然 devtmpfs 能够自动创建节点,但它只负责“创建基础节点”,并不负责复杂的命名、权限、属主和符号链接管理。这些工作由用户空间的 udev 完成。两者的协作流程如下:

  1. 内核检测到设备,注册 struct device,并在 devtmpfs 中创建初始设备节点。
  2. 内核通过 netlink 套接字发送 uevent 事件,事件中包含设备路径、主次设备号、子系统等信息。
  3. udevd 守护进程接收 uevent,读取 /sys 下对应的设备属性。
  4. udevd 根据 /etc/udev/rules.d/ 和 /lib/udev/rules.d/ 中的规则,决定是否重命名节点、修改权限、创建符号链接或执行额外程序。
  5. 最终在 /dev 下形成符合系统策略的设备节点和链接。

下面的 SVG 展示了从内核设备注册到 udev 完成节点管理的完整信号与数据流关系:

devtmpfs 与 udev 协作流程 内核设备模型 device_register() cdev_add() / add_disk() kobject_uevent() devtmpfs 内核态自动创建节点 分配主次设备号 挂载于 /dev udevd 守护进程 接收 netlink uevent 读取 /sys 设备属性 匹配 rules.d 规则 创建节点 netlink uevent 事件 基础节点 最终 /dev 目录 设备节点 + 符号链接 + 权限策略 例如 /dev/disk/by-uuid/、/dev/input/ 初始节点 重命名/权限/链接 协作要点:devtmpfs 负责“从无到有”创建基础节点,保证早期启动可用; udev 负责“从有到优”管理命名、权限、属主和符号链接,二者互补而非替代。

四、典型场景与配置示例

在实际系统中,devtmpfs 与 udev 的协作非常常见。例如插入一个 USB 存储设备时,内核首先识别设备并创建 /dev/sdb 节点,随后 udev 根据规则创建 /dev/disk/by-uuid/ 下的符号链接,并设置正确的权限。这样应用程序既可以使用稳定的 UUID 路径,也可以使用传统的 /dev/sdb 路径。

下面是一个 udev 规则示例,用于为特定 USB 设备创建固定名称的符号链接:

# /etc/udev/rules.d/99-usb-disk.rules
SUBSYSTEM=="block", ATTRS{idVendor}=="1234", ATTRS{idProduct}=="5678", \
  SYMLINK+="myusbdisk", MODE="0660", GROUP="disk"

加载规则后,重新插拔设备,即可看到 /dev/myusbdisk 符号链接指向实际设备节点。这个过程中,devtmpfs 已经创建了 /dev/sdb,而 udev 在此基础上增加了符号链接和权限控制。

五、常见误区与复盘

误区 正确理解
认为 udev 负责创建所有设备节点 基础节点由内核 devtmpfs 创建,udev 负责后续管理
认为 devtmpfs 可以替代 udev devtmpfs 不处理命名策略、权限和符号链接,无法替代 udev
手动 mknod 创建节点后,udev 会覆盖 udev 可能根据规则重新调整,手动节点应避免与规则冲突
禁用 devtmpfs 后系统仍能正常启动 禁用后需要依赖静态节点或 initramfs 中的其他机制,风险较高
注意:如果内核配置中关闭了 CONFIG_DEVTMPFS,系统可能无法自动创建 /dev 下的基础节点,导致启动过程中找不到控制台或根设备。现代发行版通常默认启用该选项。

总结来说,devtmpfs 解决了“设备节点何时创建”的问题,udev 解决了“设备节点如何管理”的问题。两者分工明确:内核负责基础可用性,用户空间负责策略与灵活性。理解这一协作流程,是掌握 Linux 用户空间设备管理的关键一步。