3. 设备节点创建机制:udev/mdev 自动创建设备节点,手动 mknod 创建节点

在 Linux 系统中,设备节点(device node)是用户空间访问内核驱动的文件接口。设备节点位于 /dev 目录下,每个节点对应一个主设备号(major number)和次设备号(minor number)。HAL 驱动移植过程中,设备节点的创建方式直接影响上层应用与底层硬件的交互效率。本章深入分析三种节点创建机制:udev(用户空间设备管理)、mdev(嵌入式轻量方案)和 mknod(手动创建),并结合工程案例说明其适用场景。

3.1 设备节点与内核设备模型

设备节点本质上是 sysfs 文件系统与 devtmpfs 协同工作的产物。内核在注册设备时,通过 device_create() 或 class_create() 在 /sys/class/ 下生成设备信息,用户空间守护进程监听这些事件并创建节点。

关键概念: 设备节点 = 主设备号(驱动标识) + 次设备号(设备实例) + 文件权限(用户空间访问控制)。
设备节点创建机制架构图 内核驱动注册 device_create() / class_create() sysfs 导出 /sys/class/<class>/<device> 用户空间守护进程 udev / mdev / mknod /dev/<device_node> 主次设备号 + 权限 说明: udev/mdev 监听 sysfs 的 uevent 事件自动创建节点;mknod 绕过事件机制直接手动创建。

3.2 udev:桌面/服务器标准方案

udev 是 systemd 生态下的设备管理器,广泛用于桌面 Linux 和服务器。它通过 netlink 套接字接收内核的 uevent,并根据 /etc/udev/rules.d/ 中的规则文件创建设备节点、设置权限、加载固件。

3.2.1 udev 工作流程

  1. 内核事件触发: 设备插入/移除时,内核通过 kobject_uevent() 发送 uevent。
  2. udevd 接收: udev 守护进程通过 netlink 接收事件。
  3. 规则匹配: udev 按规则文件顺序匹配设备属性(如 SUBSYSTEM、ATTR{name})。
  4. 节点创建: 调用 mknod 系统调用创建 /dev/ 节点,并应用权限。
  5. 通知 HAL: 通过 libudev 接口通知上层 HAL 层设备就绪。
工程提示: 在移植 HAL 驱动时,若目标系统使用 systemd,优先使用 udev。规则文件示例:
SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", MODE="0666"

3.2.2 udev 规则优先级

优先级规则路径说明
最高/etc/udev/rules.d/系统管理员自定义规则,覆盖默认
中/run/udev/rules.d/运行时规则,临时生效
最低/usr/lib/udev/rules.d/发行版默认规则,不应直接修改

3.3 mdev:嵌入式轻量方案

mdev 是 BusyBox 提供的 udev 简化版,专为嵌入式系统设计。它不依赖 systemd,通过 sysfs 的 uevent 文件监听事件,占用资源极低(通常 < 10KB)。

3.3.1 mdev 配置与使用

# 内核配置:确保 CONFIG_UEVENT_HELPER 指向 mdev
CONFIG_UEVENT_HELPER_PATH="/sbin/mdev"

# 启动时挂载 sysfs 和 tmpfs
mount -t sysfs sysfs /sys
mount -t tmpfs tmpfs /dev

# 启动 mdev 守护进程
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s   # 扫描现有设备并创建节点

3.3.2 mdev 配置文件

mdev 通过 /etc/mdev.conf 控制节点创建行为,每行格式:

<device_regex> <uid>:<gid> <permissions> [<command>]

# 示例:为 USB 串口设备设置 666 权限
ttyUSB[0-9]  root:root 0666
ttyACM[0-9]  root:root 0666
注意: mdev 不支持复杂的规则匹配(如 udev 的 ATTRS{idVendor}),仅支持设备名正则。在 HAL 移植中,若需要基于硬件属性区分设备,建议使用 udev 或手动 mknod。

3.4 mknod:手动创建节点

mknod 是最底层的节点创建方式,直接调用系统调用 mknod() 生成设备文件。在嵌入式系统启动早期、udev/mdev 尚未运行时,或需要固定节点路径的场景下,手动 mknod 是可靠的后备方案。

3.4.1 mknod 命令语法

mknod <设备路径> <类型> <主设备号> <次设备号>

# 类型:b(块设备)、c(字符设备)、p(命名管道)

# 示例:创建字符设备 /dev/mydevice,主号 240,次号 0
mknod /dev/mydevice c 240 0

# 设置权限
chmod 0666 /dev/mydevice

3.4.2 主次设备号分配策略

分配方式命令/函数说明
静态分配register_chrdev_region()驱动指定主设备号,需避免冲突
动态分配alloc_chrdev_region()内核自动分配主设备号,需通过 /proc/devices 查询
工程案例: 在某 ARM 嵌入式项目中,系统未集成 udev/mdev,HAL 层通过启动脚本手动创建节点:
mknod /dev/sensor c 242 0 && chmod 0666 /dev/sensor
主设备号 242 由驱动在 module_init 中通过 alloc_chrdev_region() 动态分配,启动脚本从 /proc/devices 解析获取。

3.5 三种机制对比与选型

特性udevmdevmknod
依赖systemd、libudevBusyBox、sysfs无(仅内核支持)
资源占用较大(~1MB)极小(<10KB)无运行时开销
规则灵活性高(支持硬件属性匹配)低(仅设备名正则)无(手动指定)
自动创建是(热插拔)是(热插拔)否(需脚本触发)
适用场景桌面/服务器/复杂嵌入式资源受限嵌入式早期启动/固定节点/调试
设备节点创建机制选型决策流程 选择节点创建机制 系统使用 systemd? 资源受限嵌入式? 需要固定节点? 使用 udev 使用 mdev 使用 mknod

3.6 HAL 驱动移植中的节点创建实践

在 HAL 驱动移植工程中,节点创建机制的选择直接影响驱动加载时序和上层应用兼容性。以下为常见场景的推荐方案:

  • 场景一:Android HAL 移植 — Android 使用 ueventd(类似 udev),需在 ueventd.rc 中配置设备权限。推荐使用 udev 兼容规则。
  • 场景二:Yocto 嵌入式 Linux — 默认使用 udev(systemd)或 mdev(BusyBox)。若系统资源 > 64MB,优先 udev;否则 mdev。
  • 场景三:裸机过渡到 Linux — 早期启动阶段使用 mknod 创建关键节点(如 console、tty),待文件系统挂载后切换至 udev/mdev。
  • 场景四:调试与开发 — 手动 mknod 可快速验证驱动设备号是否正确,避免 udev 规则干扰。
调试技巧: 使用 cat /proc/devices 查看已注册的设备号;使用 ls -l /dev/ 确认节点主次设备号与驱动匹配。若节点未自动创建,检查 dmesg | grep uevent 确认内核事件是否发出。

3.7 本章小结

设备节点创建是 HAL 驱动移植中连接内核与用户空间的桥梁。udev 提供最完善的规则引擎,适合复杂系统;mdev 以极低资源消耗满足嵌入式需求;mknod 作为手动后备方案,在调试和早期启动中不可替代。实际工程中,应根据系统资源、启动时序和 HAL 层接口要求,灵活组合使用这三种机制。