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/ 下生成设备信息,用户空间守护进程监听这些事件并创建节点。
3.2 udev:桌面/服务器标准方案
udev 是 systemd 生态下的设备管理器,广泛用于桌面 Linux 和服务器。它通过 netlink 套接字接收内核的 uevent,并根据 /etc/udev/rules.d/ 中的规则文件创建设备节点、设置权限、加载固件。
3.2.1 udev 工作流程
- 内核事件触发: 设备插入/移除时,内核通过
kobject_uevent()发送 uevent。 - udevd 接收: udev 守护进程通过 netlink 接收事件。
- 规则匹配: udev 按规则文件顺序匹配设备属性(如
SUBSYSTEM、ATTR{name})。 - 节点创建: 调用
mknod系统调用创建/dev/节点,并应用权限。 - 通知 HAL: 通过
libudev接口通知上层 HAL 层设备就绪。
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
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 查询 |
mknod /dev/sensor c 242 0 && chmod 0666 /dev/sensor主设备号 242 由驱动在
module_init 中通过 alloc_chrdev_region() 动态分配,启动脚本从 /proc/devices 解析获取。
3.5 三种机制对比与选型
| 特性 | udev | mdev | mknod |
|---|---|---|---|
| 依赖 | systemd、libudev | BusyBox、sysfs | 无(仅内核支持) |
| 资源占用 | 较大(~1MB) | 极小(<10KB) | 无运行时开销 |
| 规则灵活性 | 高(支持硬件属性匹配) | 低(仅设备名正则) | 无(手动指定) |
| 自动创建 | 是(热插拔) | 是(热插拔) | 否(需脚本触发) |
| 适用场景 | 桌面/服务器/复杂嵌入式 | 资源受限嵌入式 | 早期启动/固定节点/调试 |
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 层接口要求,灵活组合使用这三种机制。