4. udev 与设备节点管理:规则编写、动态创建与权限控制
摄像头插上 USB 口,系统怎么知道它来了?怎么给它一个名字?怎么让普通用户也能访问它?
这些事,都是 udev 在背后干的。说白了,udev 就是 Linux 内核和用户空间之间的一个“设备管家”。
我刚开始做嵌入式 Linux 时,总觉得 udev 规则是玄学。后来被坑了几次,才老老实实把这块啃透。今天咱们就把 udev 的规则编写、节点创建、权限控制一次讲清楚。
4.1 udev 是什么?为什么需要它?
udev 是 Linux 的设备管理器。它负责:
- 检测设备的热插拔事件
- 在
/dev下动态创建设备节点 - 设置设备节点的权限、属主、属组
- 根据规则执行自定义脚本
你想想看,如果没有 udev,每次插个摄像头都得手动 mknod 创建设备文件,还得算主次设备号。那得多痛苦?
4.2 udev 规则文件的结构
规则文件放在 /etc/udev/rules.d/ 目录下。文件名以数字开头,数字越小优先级越高。我习惯用 99- 开头,确保我的规则最后加载,覆盖系统默认行为。
一条规则由两部分组成:
- 匹配键(match keys):用来识别设备,比如
SUBSYSTEM、ATTRS、KERNEL - 赋值键(assignment keys):指定要执行的动作,比如
NAME、SYMLINK、MODE、OWNER
基本语法长这样:
# 匹配条件, 动作
SUBSYSTEM=="video4linux", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="0825", MODE="0666"
注意:匹配键用 ==,赋值键用 =。这个别搞反了,我见过有人写错导致规则死活不生效。
4.3 如何获取设备的属性信息?
写规则之前,得先知道设备长什么样。我常用的命令是 udevadm info:
# 查看所有属性
udevadm info -a -n /dev/video0
# 查看设备路径
udevadm info -q path -n /dev/video0
# 监控热插拔事件
udevadm monitor --property
举个例子,插上 USB 摄像头后,执行 udevadm info -a -n /dev/video0,你会看到类似这样的输出:
looking at device '/devices/pci0000:00/.../video4linux/video0':
KERNEL=="video0"
SUBSYSTEM=="video4linux"
...
looking at parent device '/devices/pci0000:00/.../usb1/1-1':
ATTRS{idVendor}=="046d"
ATTRS{idProduct}=="0825"
ATTRS{serial}=="ABCD1234"
...
这些 ATTRS 就是你的匹配依据。我一般用 idVendor + idProduct 组合,基本不会误匹配。
4.4 实战:为摄像头编写 udev 规则
假设我们有两款摄像头:
| 摄像头型号 | idVendor | idProduct | 期望设备名 |
|---|---|---|---|
| 罗技 C920 | 046d | 0825 | /dev/cam_front |
| 奥尼 A30 | 0c45 | 6366 | /dev/cam_rear |
规则文件 /etc/udev/rules.d/99-camera.rules:
# 罗技 C920 - 前摄像头
SUBSYSTEM=="video4linux", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="0825", \
SYMLINK+="cam_front", MODE="0666"
# 奥尼 A30 - 后摄像头
SUBSYSTEM=="video4linux", ATTRS{idVendor}=="0c45", ATTRS{idProduct}=="6366", \
SYMLINK+="cam_rear", MODE="0666"
写完后,重载规则:
sudo udevadm control --reload-rules
sudo udevadm trigger
再插拔摄像头,ls -l /dev/cam_* 就能看到符号链接了。
SYMLINK 而不是 NAME。因为 NAME 会覆盖内核默认的设备名,容易搞乱系统。符号链接既保留了原始节点,又给了你一个友好的名字。
4.5 权限控制:让普通用户也能用摄像头
默认情况下,/dev/video0 的权限是 660,属主是 root:video。普通用户不在 video 组里,就打不开设备。
解决方案有三种:
- 改 MODE:
MODE="0666",所有用户可读写。简单粗暴,但不安全。 - 改 GROUP:
GROUP="video",然后把用户加到video组。推荐做法。 - 用 ACL:
RUN+="/usr/bin/setfacl -m u:myuser:rw /dev/video0",更精细。
我个人推荐第二种:
SUBSYSTEM=="video4linux", ATTRS{idVendor}=="046d", MODE="0660", GROUP="video"
然后执行 sudo usermod -aG video $USER,退出重新登录即可。
RUN+= 执行脚本,结果脚本里调用了 sleep,导致 udev 事件处理超时,设备节点创建失败。记住,udev 规则里的 RUN 要尽量轻量,别搞复杂逻辑。
4.6 多摄像头场景下的设备节点管理
多摄像头同时接入时,内核分配 video0、video1、video2 的顺序是不确定的。今天插 A 摄像头是 video0,明天可能就变成 video1 了。
怎么固定?用 物理端口路径 来区分。
查看 KERNELS 属性:
udevadm info -a -n /dev/video0 | grep KERNELS
输出类似:
KERNELS=="1-1.3:1.0"
KERNELS=="1-1.3"
这个 1-1.3 表示 USB 总线 1、端口 1、子端口 3。只要摄像头插在同一个 USB 口,这个路径就是固定的。
规则可以写成:
SUBSYSTEM=="video4linux", KERNELS=="1-1.3", SYMLINK+="cam_fixed_left", MODE="0666"
SUBSYSTEM=="video4linux", KERNELS=="1-1.4", SYMLINK+="cam_fixed_right", MODE="0666"
这样,左边 USB 口插的永远是 cam_fixed_left,右边是 cam_fixed_right。换摄像头也不影响。
4.7 udev 规则调试技巧
规则不生效?别慌,按这个流程排查:
- 检查语法:
udevadm verify /etc/udev/rules.d/99-camera.rules(新版本支持) - 查看匹配:
udevadm test $(udevadm info -q path -n /dev/video0),模拟规则匹配过程 - 看日志:
journalctl -f -t udev,实时观察 udev 事件 - 手动触发:
udevadm trigger -v -n /dev/video0
我曾经遇到一个诡异问题:规则写对了,但就是不创建符号链接。查了半天,发现是文件名没以 .rules 结尾。嗯,这种低级错误,犯过一次就记住了。
4.8 本章知识结构图
下面这张图总结了 udev 设备节点管理的核心流程:
4.9 常见问题与避坑指南
- 规则不生效:检查文件名是否以
.rules结尾,检查==和=是否用对。 - 符号链接冲突:两个设备匹配到同一个
SYMLINK名,后插的会覆盖先插的。用SYMLINK+="unique_name"避免。 - 权限改了还是打不开:确认用户是否在对应组里,
groups $USER查看。改完组要重新登录。 - 热插拔不触发:检查内核是否支持
CONFIG_UEVENT_HELPER,或者 udev 服务是否在运行:systemctl status systemd-udevd。
udev 规则写好了,设备节点管理就稳了。多摄像头同步方案里,这一步是基础中的基础。节点名字固定了,权限对了,后面的应用层才能安心干活。