udev
udev 是一个用户空间系统,它允许操作系统管理员为事件注册用户空间处理程序。 udev 守护进程接收的事件主要由(Linux)内核响应与外围设备相关的物理事件而生成。 因此,udev 的主要目的是对外围设备检测和热插拔做出反应,包括将控制权返回给内核的操作,例如,加载内核模块或设备固件。 此检测的另一个组成部分是调整设备的权限,使其可供非 root 用户和组访问。
作为 devfsd 和 hotplug 的继任者,udev 还通过添加、符号链接和重命名来管理 /dev
目录中的设备节点。 udev 取代了 hotplug 和 hwdetect 的功能。
udev 并发(并行)处理单独的事件,与旧系统相比,这可能会提高性能。 同时,这可能会使系统管理变得复杂,例如,内核模块加载顺序在启动之间不会保留。 如果机器有多个块设备,这可能会以设备节点在重启后更改名称的形式表现出来。 例如,如果机器有两个硬盘驱动器,则 /dev/sda
可能在下次启动时变为 /dev/sdb
。 有关此的更多信息,请参见下文。
安装
udev 是 systemd 的一部分,因此默认安装。 有关信息,请参见 systemd-udevd.service(8)。
关于 udev 规则
管理员编写的 udev 规则位于 /etc/udev/rules.d/
中,其文件名必须以 .rules 结尾。 随各种软件包提供的 udev 规则位于 /usr/lib/udev/rules.d/
中。 如果 /usr/lib
和 /etc
下有两个同名的文件,则 /etc
中的文件优先。
要了解 udev 规则,请参阅 udev(7) 手册。 另请参阅 编写 udev 规则,并且在本指南中提供了一些实用示例:编写 udev 规则 - 示例。
udev 规则示例
以下是一个规则示例,该规则在连接网络摄像头时创建符号链接 /dev/video-cam
。
假设此摄像头当前已连接并已加载,设备名称为 /dev/video2
。 编写此规则的原因是,下次启动时,该设备可能会以不同的名称显示,例如 /dev/video0
。
$ udevadm info --attribute-walk --path=$(udevadm info --query=path --name=/dev/video2)
Udevadm info starts with the device specified by the devpath and then walks up the chain of parent devices. It prints for every device found, all possible attributes in the udev rules key format. A rule to match, can be composed by the attributes of the device and the attributes from one single parent device. looking at device '/devices/pci0000:00/0000:00:04.1/usb3/3-2/3-2:1.0/video4linux/video2': KERNEL=="video2" SUBSYSTEM=="video4linux" ... looking at parent device '/devices/pci0000:00/0000:00:04.1/usb3/3-2/3-2:1.0': KERNELS=="3-2:1.0" SUBSYSTEMS=="usb" ... looking at parent device '/devices/pci0000:00/0000:00:04.1/usb3/3-2': KERNELS=="3-2" SUBSYSTEMS=="usb" ATTRS{idVendor}=="05a9" ATTRS{manufacturer}=="OmniVision Technologies, Inc." ATTRS{removable}=="unknown" ATTRS{idProduct}=="4519" ATTRS{bDeviceClass}=="00" ATTRS{product}=="USB Camera" ...
为了识别网络摄像头,我们从 video4linux 设备中使用 KERNEL=="video2"
和 SUBSYSTEM=="video4linux"
,然后向上两级,我们使用 usb 父级的供应商和产品 ID 匹配网络摄像头 SUBSYSTEMS=="usb"
、ATTRS{idVendor}=="05a9"
和 ATTRS{idProduct}=="4519"
。 请注意,此匹配区分大小写,因此在本示例中不能使用 "05A9"
来匹配 idVendor
。
现在,我们可以为此设备创建一个规则匹配,如下所示
/etc/udev/rules.d/83-webcam.rules
KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ATTRS{idVendor}=="05a9", ATTRS{idProduct}=="4519", SYMLINK+="video-cam"
在此处,我们使用 SYMLINK+="video-cam"
创建符号链接,但我们可以轻松地使用 OWNER="john"
设置用户,或使用 GROUP="video"
设置组,或使用 MODE="0660"
设置权限。
如果您打算编写一个规则来在设备移除时执行某些操作,请注意设备属性可能不可访问。 在这种情况下,您将必须使用预设的设备环境变量。 要监视这些环境变量,请在拔下设备时执行以下命令
$ udevadm monitor --property --udev
在此命令的输出中,您将看到值对,例如 ID_VENDOR_ID
和 ID_MODEL_ID
,它们与先前使用的属性 idVendor
和 idProduct
匹配。 使用设备环境变量而不是设备属性的规则可能如下所示
/etc/udev/rules.d/83-webcam-removed.rules
ACTION=="remove", SUBSYSTEM=="usb", ENV{ID_VENDOR_ID}=="05a9", ENV{ID_MODEL_ID}=="4519", RUN+="/path/to/your/script"
列出设备的属性
要获取可用于编写规则的设备的所有属性的列表,请运行以下命令
$ udevadm info --attribute-walk --name=device_name
将 device_name
替换为系统中存在的设备,例如 /dev/sda
或 /dev/ttyUSB0
。
如果您不知道设备名称,也可以列出特定系统路径的所有属性
$ udevadm info --attribute-walk --path=/sys/class/backlight/acpi_video0
要缩小设备搜索范围,请找出类并运行
$ ls /dev/class/by-id
您可以直接使用符号链接或它指向的内容作为 --name
的输入。 例如
$ udevadm info --attribute-walk --name=/dev/input/by-id/usb-foostan_Corne-event-kbd
要获取未填充任何从属设备的裸 USB 设备的路径,您必须使用完整的 USB 设备路径。 启动监视模式,然后插入 USB 设备以获取它
$ udevadm monitor
... KERNEL[26652.638931] add /devices/pci0000:00/0000:00:01.2/0000:02:00.0/0000:03:05.0/0000:05:00.0/usb1/1-3 (usb) KERNEL[26652.639153] add /devices/pci0000:00/0000:00:01.2/0000:02:00.0/0000:03:05.0/0000:05:00.0/usb1/1-3/1-3:1.0 (usb) ...
您可以只选择最深的路径,--attribute-walk
无论如何都会显示所有父级的属性
$ udevadm info --attribute-walk --path=/devices/pci0000:00/0000:00:01.2/0000:02:00.0/0000:03:05.0/0000:05:00.0/usb1/1-3/1-3:1.0
加载前测试规则
# udevadm test $(udevadm info --query=path --name=device_name) 2>&1
这不会执行新规则中的所有操作,但它会在现有设备上处理符号链接规则,如果您无法以其他方式加载它们,这可能会派上用场。 您还可以直接提供要测试 udev 规则的设备的路径
# udevadm test /sys/class/backlight/acpi_video0/
加载新规则
udev 会自动检测规则文件的更改,因此更改会立即生效,而无需重启 udev。 但是,规则不会在已存在的设备上自动重新触发。 热插拔设备(例如 USB 设备)可能需要重新连接才能使新规则生效,或者至少卸载并重新加载 ohci-hcd 和 ehci-hcd 内核模块,从而重新加载所有 USB 驱动程序。
如果规则无法自动重新加载
# udevadm control --reload
要手动强制 udev 触发您的规则
# udevadm trigger
udisks
参见 udisks。
技巧和窍门
在规则中挂载驱动器
要挂载可移动驱动器,请不要从 udev 规则中调用 mount
。 出于以下两个原因,不建议这样做
- systemd 默认情况下使用单独的“挂载命名空间”运行
systemd-udevd.service
(参见 namespaces(7)),这意味着挂载对系统的其余部分不可见。 - 即使您更改服务参数以解决此问题(注释掉
PrivateMounts
和MountFlags
行),仍然存在另一个问题,即从 Udev 启动的进程会在几秒钟后被终止。 对于 FUSE 文件系统(例如 NTFS-3G),mount 启动一个用户空间进程来处理文件系统内部; 当此进程被终止时,如果您尝试访问文件系统,您将收到Transport endpoint not connected
错误。
以下是一些可行的选项
- 从 Udev 规则启动自定义 systemd 服务; systemd 服务可以调用一个脚本,该脚本可以启动任意数量的长时间运行的进程(例如 FUSE)。 一个简洁的示例,可自动将 USB 磁盘挂载到
/media
下,是 udev-media-automount。 在 这篇博客文章 中解释了相同想法的变体。 - 在您的 Udev 规则中使用
systemd-mount
而不是mount
。 这是 systemd 开发人员推荐的。 例如,此 Udev 规则应将 USB 磁盘挂载到/media
下
ACTION=="add", SUBSYSTEMS=="usb", SUBSYSTEM=="block", ENV{ID_FS_USAGE}=="filesystem", RUN{program}+="/usr/bin/systemd-mount --no-block --automount=yes --collect $devnode /media"
生成长时间运行的进程
由 udev 启动的程序将阻止来自该设备的进一步事件,并且从 udev 规则生成的任何任务将在事件处理完成后被终止。 如果您需要在 udev 中生成长时间运行的进程,则预期的方法是拥有一个 systemd 单元来处理运行实际命令,以及一个 udev 规则,该规则仅发出信号表明应运行此单元。 但是,不鼓励在 udev 规则中使用 systemctl
,因为它旨在用于用户交互,并且可能会阻塞等。
执行此操作的正确方法是让规则将设备标记为需要 systemd 设备单元(参见 systemd.device(5)),方法是使用 TAG+="systemd"
并添加设备属性,对于使用 systemctl --system
运行的服务,添加 ENV{SYSTEMD_WANTS}=
,对于应使用 systemctl --user
运行的服务,添加 ENV{SYSTEMD_USER_WANTS}=
。 例如
SUBSYSTEM=="tty" ACTION=="add" ATTRS{manufacturer}=="Pulse-Eight" ATTRS{product}=="CEC Adapter" TAG+="systemd" ENV{SYSTEMD_WANTS}="inputattach-cec@$devnode.service"
/etc/systemd/system/inputattach-cec@.service
[Unit] Description=Configure USB serial device at %I [Service] Type=simple ExecStart=/usr/bin/inputattach --pulse8-cec %I
SYSTEMD_WANTS
等同于 systemd 中其他位置的 Wants=
指令,这意味着如果服务失败、不存在或在任何时候成功完成,设备都不会受到影响。
允许普通用户使用设备
当 内核 驱动程序初始化设备时,设备节点的默认状态是归 root:root
所有,权限为 600
。 [1] 这使得普通用户无法访问设备,除非驱动程序更改默认值,或者用户空间中的 udev 规则更改权限。
OWNER
、GROUP
和 MODE
udev 值可用于提供访问权限,但人们会遇到如何使设备对所有用户都可用而又不会过度放宽模式的问题。 Ubuntu 的方法是创建一个 plugdev
组,设备会被添加到该组中,但这种做法不仅受到 systemd 开发人员的反对,[2] 而且在 Arch 上的 udev 规则中发布时被认为是错误 (FS#35602)。 历史上采用的另一种方法(如 用户和组#Pre-systemd 组 中所述)是让不同的组对应于设备类别。
systemd 系统的现代推荐方法是使用 MODE
660
让组使用该设备,然后附加一个名为 uaccess
的 TAG
[3]。 这个特殊标签使 udev 将 动态用户 ACL 应用于设备节点,该 ACL 与 systemd-logind(8) 协调,以使设备可供登录用户使用。 以下是实现此目的的 udev 规则示例
/etc/udev/rules.d/71-device-name.rules
SUBSYSTEMS=="usb", ATTRS{idVendor}=="vendor_id", ATTRS{idProduct}=="product_id", MODE="0660", TAG+="uaccess"
当 HDMI 线缆插入或拔出时执行
创建规则 /etc/udev/rules.d/95-hdmi-plug.rules
,内容如下
ACTION=="change", SUBSYSTEM=="drm", ENV{DISPLAY}=":0", ENV{XAUTHORITY}="/home/username/.Xauthority", RUN+="/path/to/script.sh"
在 VGA 线缆插入时执行
创建规则 /etc/udev/rules.d/95-monitor-hotplug.rules
,内容如下,以在 VGA 监视器线缆插入时启动 arandr
KERNEL=="card0", SUBSYSTEM=="drm", ENV{DISPLAY}=":0", ENV{XAUTHORITY}="/home/username/.Xauthority", RUN+="/usr/bin/arandr"
一些显示管理器将 .Xauthority
存储在用户主目录之外。 您将需要相应地更新 ENV{XAUTHORITY}
。 例如,GNOME 显示管理器 如下所示
$ printenv XAUTHORITY
/run/user/1000/gdm/Xauthority
检测新的 eSATA 驱动器
如果您的 eSATA 驱动器在插入时未被检测到,您可以尝试以下几种方法。 您可以插入 eSATA 后重启。 或者您可以尝试
# echo 0 0 0 | tee /sys/class/scsi_host/host*/scan
或者您可以安装 scsiaddAUR(来自 AUR)并尝试
# scsiadd -s
希望现在您的驱动器在 /dev
中。 如果不是,您可以尝试在运行以下命令时使用上述命令
# udevadm monitor
以查看是否实际发生了任何事情。
将内部 SATA 端口标记为 eSATA
如果您连接了 eSATA 托架或其他 eSATA 适配器,系统仍会将此磁盘识别为内部 SATA 驱动器。 GNOME 和 KDE 会一直要求您输入 root 密码。 以下规则会将指定的 SATA 端口标记为外部 eSATA 端口。 这样,普通的 GNOME 用户就可以像连接 USB 驱动器一样将 eSATA 驱动器连接到该端口,而无需任何 root 密码等等。
/etc/udev/rules.d/10-esata.rules
DEVPATH=="/devices/pci0000:00/0000:00:1f.2/host4/*", ENV{UDISKS_SYSTEM}="0"
DEVPATH
可以在连接 eSATA 驱动器后通过以下命令找到(相应地替换 sdb
)$ udevadm info --query=path /dev/sdb
/devices/pci0000:00/0000:00:1f.2/host4/target4:0:0/4:0:0:0/block/sdb
$ find /sys/devices/ -name sdb
/sys/devices/pci0000:00/0000:00:1f.2/host4/target4:0:0/4:0:0:0/block/sdb
设置静态设备名称
由于 udev 异步加载所有模块,因此它们的初始化顺序不同。 这可能导致设备随机切换名称。 可以添加 udev 规则以使用静态设备名称。 另请参阅 持久块设备命名 以了解块设备,以及 网络配置#更改接口名称 以了解网络设备。
视频设备
有关首次设置网络摄像头的信息,请参阅 网络摄像头设置。
使用多个网络摄像头将在启动时随机分配视频设备为 /dev/video*
。 建议的解决方案是使用 udev 规则创建符号链接,如 #udev 规则示例 中所示
/etc/udev/rules.d/83-webcam.rules
KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ATTRS{idVendor}=="05a9", ATTRS{idProduct}=="4519", SYMLINK+="video-cam1" KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="08f6", SYMLINK+="video-cam2"
/dev/video*
以外的名称会破坏 v4l1compat.so
甚至可能是 v4l2convert.so
的预加载打印机
如果您使用多台打印机,则 /dev/lp[0-9]
设备将在启动时随机分配,这将破坏例如 CUPS 配置。
您可以创建以下规则,该规则将在 /dev/lp/by-id
和 /dev/lp/by-path
下创建符号链接,类似于 持久块设备命名 方案
/etc/udev/rules.d/60-persistent-printer.rules
ACTION=="remove", GOTO="persistent_printer_end" # This should not be necessary #KERNEL!="lp*", GOTO="persistent_printer_end" SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id" ENV{ID_TYPE}!="printer", GOTO="persistent_printer_end" ENV{ID_SERIAL}=="?*", SYMLINK+="lp/by-id/$env{ID_BUS}-$env{ID_SERIAL}" IMPORT{builtin}="path_id" ENV{ID_PATH}=="?*", SYMLINK+="lp/by-path/$env{ID_PATH}" LABEL="persistent_printer_end"
通过序列号识别磁盘
要在特定的磁盘设备 /dev/sdX
上执行某些操作,该设备通过其唯一的序列号 ID_SERIAL_SHORT
永久标识,如 udevadm info /dev/sdX
显示的那样,可以使用以下规则。 它将找到的设备名称作为参数传递,以进行说明
/etc/udev/rules.d/69-disk.rules
ACTION=="add", KERNEL=="sd[a-z]", ENV{ID_SERIAL_SHORT}=="X5ER1ALX", RUN+="/path/to/script /dev/%k"
使用 USB 设备从挂起状态唤醒
udev 规则可用于启用 USB 设备(如鼠标或键盘)的唤醒触发器,以便可以使用它将系统从睡眠状态唤醒。
cat /proc/acpi/wakeup
检查状态。 在这种情况下,以下规则不是必需的,但可以用作执行其他操作(例如禁用唤醒功能)的模板。首先,识别 USB 设备的供应商和产品标识符。 它们将用于在 udev 规则中识别它。 例如
$ lsusb | grep Logitech
Bus 007 Device 002: ID 046d:c52b Logitech, Inc. Unifying Receiver
然后,使用以下命令查找设备连接的位置
$ grep c52b /sys/bus/usb/devices/*/idProduct
/sys/bus/usb/devices/1-1.1.1.4/idProduct:c52b
现在创建规则,以在添加设备时更改设备及其连接的 USB 控制器的 power/wakeup
属性
/etc/udev/rules.d/50-wake-on-device.rules
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c52b", ATTR{power/wakeup}="enabled", ATTR{driver/1-1.1.1.4/power/wakeup}="enabled"
触发事件
触发各种 udev 事件可能很有用。 例如,您可能想要在远程计算机上模拟 USB 设备断开连接。 在这种情况下,请使用 udevadm trigger
# udevadm trigger --verbose --type=subsystems --action=remove --subsystem-match=usb --attr-match="idVendor=abcd"
此命令将在所有供应商 ID 为 abcd
的 USB 设备上触发 USB 移除事件。
从 udev 规则触发桌面通知
要从 udev 规则触发桌面通知,请使用 systemd-run(1),如 桌面通知#向其他用户发送通知 中所述
创建文件
/etc/udev/rules.d/99-powersupply_notification.rules
# Rule for when switching to battery ACTION=="change", SUBSYSTEM=="power_supply", ATTRS{type}=="Mains", ATTRS{online}=="0", RUN+="/usr/bin/systemd-run --machine=target_user@.host --user notify-send 'Changing Power States' 'Using battery power'" # Rule for when switching to AC ACTION=="change", SUBSYSTEM=="power_supply", ATTRS{type}=="Mains", ATTRS{online}=="1", RUN+="/usr/bin/systemd-run --machine=target_user@.host --user notify-send 'Changing Power States' 'Using AC power'"
要启动多个或长时间运行的命令,可以将可执行脚本提供给 systemd-run
/usr/local/bin/from_battery.sh
#!/bin/sh paplay /usr/share/sounds/freedesktop/stereo/power-unplug.oga notify-send --icon=/usr/share/icons/Adwaita/symbolic/legacy/battery-good-symbolic.svg 'Changing Power States' 'Using battery power' --expire-time=4000
故障排除
模块黑名单
在极少数情况下,udev 可能会出错并加载错误的模块。 为了防止它这样做,您可以黑名单模块。 一旦列入黑名单,udev 将永远不会加载该模块 – 无论是在启动时还是在稍后收到热插拔事件时(例如,您插入 USB 闪存驱动器)。
调试输出
要获取硬件调试信息,请使用 内核参数 udev.log-priority=debug
。 或者,您可以设置
/etc/udev/udev.conf
udev_log="debug"
也可以通过将配置文件添加到您的 FILES
数组中,将此选项编译到您的 initramfs 中
/etc/mkinitcpio.conf
FILES=(... /etc/udev/udev.conf)
udevd 在启动时挂起
迁移到 LDAP 或更新 LDAP 支持的系统后,udevd 可能会在启动时挂起,并显示消息“Starting UDev Daemon”。 这通常是由 udevd 尝试从 LDAP 查找名称但失败引起的,因为网络尚未启动。 解决方案是确保所有系统组名称都在本地存在。
提取 udev 规则中引用的组名称和系统上实际存在的组名称
# grep -Fr GROUP /etc/udev/rules.d/ /usr/lib/udev/rules.d/ | sed 's:.*GROUP="\([-a-z_]\{1,\}\)".*:\1:' | sort -u >udev_groups # cut -d: -f1 /etc/gshadow /etc/group | sort -u >present_groups
要查看差异,请进行并排比较
# diff -y present_groups udev_groups ... network < nobody < ntp < optical optical power | pcscd rfkill < root root scanner scanner smmsp < storage storage ...
在这种情况下,pcscd
组由于某种原因在系统中不存在。 添加缺少的组。 此外,请确保在诉诸 LDAP 之前先查找本地资源。 /etc/nsswitch.conf
应包含以下行
group: files ldap
某些应该被视为可移动的设备未被识别
您需要为该特定设备创建自定义 udev 规则。 要获取设备的明确信息,您可以使用 ID_SERIAL
或 ID_SERIAL_SHORT
(如果需要,请记住更改 /dev/sdb
)
$ udevadm info /dev/sdb | grep ID_SERIAL
然后,我们设置 UDISKS_AUTO="1"
以将设备标记为自动挂载,并设置 UDISKS_SYSTEM="0"
以将设备标记为“可移动”。 有关详细信息,请参见 udisks(8)。
/etc/udev/rules.d/99-removable.rules
ENV{ID_SERIAL_SHORT}=="serial_number", ENV{UDISKS_AUTO}="1", ENV{UDISKS_SYSTEM}="0"
记住使用 udevadm control --reload
重新加载 udev 规则。 下次您插入设备时,它将被视为外部驱动器。
某些模块未自动加载导致声音问题
一些用户已将此问题追溯到 /etc/modprobe.d/sound.conf
中的旧条目。 尝试清除该文件并重试。
udev>=171
以来,默认情况下不会自动加载 OSS 仿真模块(snd_seq_oss
、snd_pcm_oss
、snd_mixer_oss
)。光驱的组 ID 设置为 "disk"
如果您的光驱的组 ID 设置为 disk
并且您希望将其设置为 optical
,则必须创建自定义 udev 规则
/etc/udev/rules.d
# permissions for SCSI CD devices SUBSYSTEMS=="scsi", KERNEL=="s[rg][0-9]*", ATTRS{type}=="5", GROUP="optical"
没有 X 服务器时,RUN 规则中的 X 程序挂起
当 xrandr 或另一个基于 X 的程序尝试连接到 X 服务器时,它会在失败时回退到 TCP 连接。 但是,由于 systemd-udev 服务配置中的 IPAddressDeny
,这会挂起。 最终,程序将被终止,事件处理将恢复。
如果规则是针对 drm 设备的,并且挂起导致事件处理在 X 服务器启动后完成,则这可能会导致 3D 加速停止工作,并出现 failed to authenticate magic
错误。