跳转至内容

udev

来自 ArchWiki

udev (用户空间 /dev) 是一个用户空间系统,允许系统管理员注册用户空间事件处理程序。udev 的守护进程接收的事件主要由 Linux 内核 在响应外围设备的物理事件时生成。

因此,*udev* 的主要目的是处理外围设备检测和热插拔,包括返回控制给内核的操作,例如加载 内核模块或设备 固件。此检测的另一个组成部分是调整设备权限,使其可被非 root 用户和组访问。*udev* 通过添加、符号链接和重命名设备节点来管理 /dev 目录中的设备节点。

注意 内核模块的加载顺序在重启后不会保留,因为 *udev* 会并行处理独立的事件。例如,如果一台机器有多个块设备,设备节点在重启之间可能会更改名称——即下次启动时 /dev/sda 可能会变成 /dev/sdb
提示 依赖 …/by-id/…, …/by-path/…, …/by-uuid/… 和类似的持久标识符。

安装

*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 规则 - 示例

示例

下面是一个规则示例,该规则在连接网络摄像头时创建一个名为 /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_IDID_MODEL_ID 的值对,它们与之前使用的属性 idVendoridProduct 相匹配。使用设备环境变量而不是设备属性的规则可能如下所示

/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

udev 规则的组成部分

操作匹配

ACTION=="" 用于仅匹配发生特定事件的设备,通常是在设备出现或消失时。有八种类型的操作可以为 udev 规则触发匹配

add/remove
创建/删除设备节点(在 /dev/ 中)时
bind/unbind
驱动程序附加/分离到设备时
change
仅由驱动程序手动触发“设备状态更改”,无标准含义;请参阅 #change 操作
offline
当设备(通常是 内存CPU)被锁定以进行热拔出时
online
当之前离线的设备被解锁时
move
当设备(通常是网络接口)被重命名时,希望永远不会

change 操作

change 操作有些特殊,因为并非所有驱动程序都以相同的方式使用它,或者根本不使用。change 事件仅在驱动程序手动触发类型为 KOBJ_CHANGE 的用户空间事件(或 uevent)时发出。这通常表示该设备发生了*某件事*,但需要一些额外信息来确定具体是什么。

由于该事件仅在有限的情况下触发,以下是(尽力而为,非详尽)的子系统列表,它们会触发 change 事件,以及触发条件。

SUBSYSTEM 事件触发器 特定于事件的属性 文档
typec alt-mode、USB-PD 角色或数据角色发生变化
usb_role 角色更改(对于 USB OTG 或 Type-C 设备) USB_ROLE_SWITCH= 的值为 nonehostdevice,取决于新角色
block 光驱或 SD 卡读卡器的媒体更改 新光盘/卡时 DISK_MEDIA_CHANGE=1,或在媒体被卸载时 DISK_EJECT_REQUEST=1
drm 设备被检测为“卡住”(死机且驱动程序无法恢复) WEDGED= 带有可用重置方法的列表,如 rebindbus-reset 设备卡住
drm 监视热插拔(插入或拔出) HOTPLUG=1,有时也可能带有连接器的内部 ID 的 CONNECTOR=
drm 连接器属性更改(例如 HDCP 状态) CONNECTOR=PROPERTY= 及其各自的内部 ID
backlight 亮度已更改 SOURCE= 的值取决于触发更改的内容,为 sysfshotkeyunknown
power_supply 电源状态已更改(电源插入/拔出、电池充电/放电等)
rfkill 通过 killswitch、按钮或菜单打开或关闭的无线电
thunderbolt 隧道模式更改 TUNNEL_EVENT= 带有隧道状态,也可能带有 TUNNEL_DETAILS= Thunderbolt 隧道事件
任何具有 power/ 属性组的设备 获得或失去远程唤醒能力1(即,当整个 power/wakeup* 属性集出现或消失时) 设备电源管理基础,尽管 uevent 本身并未被记录
任何(合成 uevent) 向任何 sysfs 设备2 的 uevent 文件写入 change $(uuidgen) FOO=BAR HELLO=WORLD SYNTH_UUID= 带有写入文件的 UUID,以及 UUID 之后的任何键值对的 SYNTH_ARG_HELLO=WORLD sysfs-uevent
  1. 获得或失去远程唤醒能力在某些情况下很有趣,例如 USB,因为未配置的 USB 设备在此状态下无法发起唤醒。请参阅 #在挂起后重新添加 USB 设备特性
  2. udevadm triggerOPTIONS+="watch" udev 规则指令都会通过此机制触发合成 change 事件,在这两种情况下 ENV{SYNTH_UUID}=="0"

设备属性

udev 规则中的设备属性,以 ATTR{my_attr}=="..."(或对于父设备为 ATTRS{my_attr}=="...")的形式列出,以及 udevadm info --attribute-walk,对应于通过 sysfs 提供的所有设备信息。sysfs 暴露了内核用于跟踪设备状态的“kobject”类的信息。这意味着不需要 udev 特定的工具来检查这些信息,只需一个简单的 ls /sys/class/thermal/thermal_zone0/cat /sys/class/input/input0/name 即可探索设备属性,有些甚至可以通过 echo 1 > "/sys/class/leds/input2::capslock/brightness" 这样的命令进行更改。然而,使用 udevadm info --attribute-walk /sys/class/input/mouse0 会更方便,它的优点是一次性以 udev 规则期望的相同格式显示该设备的所有可匹配属性(以及任何父设备的属性,同样以易于使用的格式显示)。

这些属性中的大多数都有基于处理设备的内核子模块的明确文档说明的含义、行为和可接受值。例如,对于 USB 设备,有 sysfs-bus-usb;对于 USB type-C 端口,有 sysfs-class-typec。其他属性以子树的形式存在,例如 sysfs-devices-physical_locationsysfs-devices-power,它们使用斜杠分隔级别,就像目录一样,生成 ATTR{power/control}=="auto"

事件环境

事件的“环境”是一组设备属性和事件属性(以及很少全局属性),在 udev 规则中都写为 ENV{MY_PROPERTY}=="...",并且都通过 udevadm monitor --propertyudevadm info(不带 --attribute-walk)报告为 E: MY_PROPERTY=...。尽管称为“环境”,但它们与 环境变量 没有任何关系(尽管它们确实会作为环境变量传递给由 RUN+="..." 启动的程序)。它们包含由内核模块或其他 udev 规则添加到事件中的上下文信息,以使该信息可供下游规则或组件使用。事件属性和设备属性之间的唯一区别是,设备属性是存储的,可以通过 udevadm info 检查,而事件属性是暂时的,只有当事件被 udevadm monitor --property 捕获时才能看到。许多设备属性也以相似的名称作为设备属性可用。

与属性不同,udev 规则允许任意设置事件属性,并且没有“父属性”的概念可以检查(除了已设置在事件上的属性),因此没有 ENVS{...}=="" 指令。请注意,使用 udev 规则设置一个属性会设置一个设备属性,该属性将一直存储直到设备被移除,因此会出现在该设备引发的每个事件中(除非属性名称以句点开头,例如 ENV{.PART_SUFFIX},它将被添加到*事件属性*中,并在名称中保留前导句点,其他规则可用,但不会被存储)。

设备标签

标签(使用 TAG+="foo" 添加,使用 TAG-="foo" 删除,使用 TAG=="foo" 匹配)由与 udev 交互的用户空间软件使用,以列出和识别它们应执行操作的所有设备。这些也可以是任意的,但有一些是众所周知的。

标签 监听软件 含义 文档
systemd systemd Systemd 应为该设备创建设备单元 systemd.device(5)
seat systemd-logind 设备有资格分配给一个 seat,并且 ENV{ID_SEAT} 指示它当前分配到的 seat(如果有) sd-login(3)
master-of-seat systemd-logind sd-login(3)
uaccess 70-uaccess.rules73-seat-late.rules 当设备分配给用户的 seat 时,用户应对该设备拥有读写权限(通过 访问控制列表 请参阅 #允许普通用户使用设备
power-switch systemd-logind 将监视该设备以获取盖子和按键事件,以触发挂起、屏幕锁定等操作 logind.conf(5) (HandlePowerKey)

硬件数据库

udev 的硬件数据库(hwdb)包含特定于设备型号的配置数据,例如键盘扫描码、鼠标 DPI 和 USB 类/型号。数据库从位于 /usr/lib/udev/hwdb.d//run/udev/hwdb.d//etc/udev/hwdb.d/ 目录中的 .hwdb 文件编译而成。有关详细信息,请参阅 hwdb(7)

每个 .hwdb 文件可以根据硬件 ID glob 模式将属性应用于一个或多个设备。您可以通过运行 root 用户权限的 evemu-describe(1) 来获取设备标识信息。此命令由 evemu 包提供。

evdev 设备

evdev 设备(包括键盘和鼠标)可以使用 evdev: 前缀以以下格式之一进行匹配

evdev:input:b<bus_id>v<vendor_id>p<product_id>e<version_id>-<input_modalias>
使用以下 4 位十六进制大写字段
  • <vendor_id><product_id><version_id>:供应商、产品和版本 ID,匹配 lsusb 命令的输出。
  • <bus_id> 是 4 位十六进制总线 ID,应为 0003 表示 USB 设备。可能的 <bus_id> 值定义在 /usr/include/linux/input.h 中(您可以运行 awk '/BUS_/ {print $2, $3}' /usr/include/linux/input.h 获取列表)。
  • <input_modalias> 是一个任意长度的输入 modalias,描述设备的功能。其他字段足以唯一标识设备,因此您可以在此处使用 glob。
如果您当前已将设备插入计算机,则可以使用 sysfs 一次性获取整个 modalias,如 #重映射特定设备 中所示。
  • 输入驱动程序设备名称和 DMI 数据匹配
    evdev:name:<input device name>:dmi:bvn*:bvr*:bd*:svn<vendor>:pn*
    其中 <input_device_name> 是驱动程序指定的设备名称,<vendor> 是内核 DMI modalias 导出的固件提供的字符串。

键盘设备

*scancodes-to-keycodes* 映射是定义在 /usr/lib/udev/hwdb.d/60-keyboard.hwdb 中的。块体中每行的格式为 KEYBOARD_KEY_<scancode>=<keycode>,其中

  • <scancode> 的值是十六进制的,但不带前导 0x(即,指定 a0 而不是 0xa0)。
  • <keycode> 的值是 /usr/include/linux/input-event-codes.h 中列出的(请参阅 KEY_<KEYCODE> 变量)小写键码名称字符串,排序列表可在 [1] 获得。无法在 <keycode> 中指定十进制值。

示例

重映射特定设备

假设您想重映射当前已连接的键盘的扫描码。您应该已经拥有 evdev 路径(例如 /dev/input/event17),并且可以按照 Keyboard input#Identifying scancodes 来获取 *scancode*(例如,大写锁定键的 70039)。现在,使用事件编号,您可以查询 sysfs 以获取 modalias

cat /sys/class/input/event17/device/modalias
input:b0003v32ACp0012e0111-e0,1,4,1...

该设备可以与以下 hwdb 规则匹配

/etc/udev/hwdb.d/90-remap.hwdb
evdev:input:b0003v32ACp0012e0111*
 KEYBOARD_KEY_70039=rightctrl # This example maps the 70039 scancode to the "rightctrl" keycode.

重映射所有设备

假设我们想为所有 AT 键盘重映射几个常用键

/etc/udev/hwdb.d/90-custom-keyboard.hwdb
evdev:atkbd:*
 KEYBOARD_KEY_10=suspend
 KEYBOARD_KEY_a0=search

禁用按键

要阻止 Sleep 键,请将其绑定到“reserved”关键字。或者,您可以使用“unknown”将其映射到 NoSymbol 键。例如

/etc/udev/hwdb.d/90-block-sleep.hwdb
evdev:input:b0003v03F0p020C* # hp 5308 keyboard controller
 KEYBOARD_KEY_10082=reserved

更新硬件数据库索引

更改配置文件后,需要通过运行以下命令来重建硬件数据库索引 hwdb.bin

# systemd-hwdb update

这将创建 /etc/udev/hwdb.bin,该文件优先于现有的 /usr/lib/udev/hwdb.bin。在系统升级时,这两个文件将自动保持最新,内容相同

  • /usr/lib/udev/hwdb.bin 在每次 systemd 升级时由 30-systemd-hwdb.hook pacman hook 重建,并由 30-systemd-udev-reload.hook 立即生效。
  • /etc/udev/hwdb.bin 在升级后的下一次启动时由 systemd-hwdb-update.service 重建(请参阅 systemd-update-done.service(8)),因为它检测到我们已手动生成它。

重新加载硬件数据库索引

内核在启动过程中加载 hwdb.bin,重新启动系统将保证加载更新后的 hwdb.bin

使用 udevadm 可以通过运行以下命令加载更新的 hwdb.bin 中的新属性

# udevadm trigger

请注意,使用 udevadm 时只加载添加或更改的属性,因此如果我们从配置文件中删除一个属性,重建 hwdb.bin 并以 root 用户身份运行 udevadm trigger,则已删除的属性仍会被内核保留,至少在重启之前。

查询数据库

您可以通过按键或运行 udevadm info 来检查您的配置是否已加载。对于上面示例中的 USB 键盘,它会按如下方式输出我们配置的映射

# udevadm info /dev/input/by-path/*-usb-*-kbd | grep KEYBOARD_KEY
E: KEYBOARD_KEY_70039=leftalt
E: KEYBOARD_KEY_700e2=leftctrl

技巧与提示

在规则中挂载驱动器

要挂载可移动驱动器,请不要从 udev 规则中调用 mount。这有两个原因不被推荐

  1. systemd 默认使用独立的“挂载命名空间”(请参阅 namespaces(7))运行 systemd-udevd.service,这意味着挂载对系统其他部分不可见。
  2. 即使您更改服务参数来解决此问题(注释掉 PrivateMountsMountFlags 行),还存在另一个问题,即从 Udev 启动的进程在几秒钟后会被终止。对于 FUSE 文件系统,例如 NTFS-3Gmount 会启动一个用户空间进程来处理文件系统内部;当该进程被终止时,如果您尝试访问文件系统,您将收到 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"
  • 使用像 udisksudiskie 这样的软件包。它们功能强大,但设置复杂。此外,它们旨在用于单用户会话,因为它们使某些文件系统在当前活动用户的会话下可供未授权用户使用。

启动长时间运行的进程

由 udev 启动的程序会阻止来自该设备的后续事件,并且从 udev 规则派生的任何任务都将在事件处理完成后被终止。如果您需要使用 udev 启动一个长时间运行的进程,正确的方法是让一个 systemd 单元来处理实际命令的运行,而 udev 规则仅表示该单元应该运行。但是,在 udev 规则中使用 systemctl 是不被推荐的,因为它用于用户交互,并且可能会阻塞,等等。

正确的方法是让规则使用 TAG+="systemd" 将设备标记为需要 systemd 设备单元(请参阅 systemd.device(5)),并添加一个设备属性 ENV{SYSTEMD_WANTS}+= 用于使用 systemctl --system 运行的服务,或者 ENV{SYSTEMD_USER_WANTS}+= 用于使用 systemctl --user 运行的服务。例如

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= 指令,这意味着如果服务失败、不存在或在任何时候成功完成,设备都不会受到影响。

允许普通用户使用设备

警告 不要将 uaccess 标签应用于输入或块设备;这些设备有自己的访问控制机制,未授权用户不应该直接访问它们。例如,未授权的原始键盘访问会使键盘记录器变得微不足道,而未授权的原始磁盘访问将完全否定文件系统安全。

内核驱动程序初始化设备时,设备节点的默认状态是属于 root:root,权限为 600[2] 这使得普通用户无法访问设备,除非驱动程序更改了默认设置,或者用户空间的 udev 规则更改了权限。

OWNERGROUPMODE udev 值可用于提供访问权限,但会遇到如何使设备对所有用户可用而不使用过于宽松的模式的问题。Ubuntu 的方法是创建一个 plugdev 组,并将设备添加到该组中,但这种做法不仅不被 systemd 开发者推荐,[3],而且在 Arch 的 udev 规则中被视为错误(FS#35602),并且从 systemd 258 开始已损坏[4][5]。历史上采用的另一种方法,如 Users and groups#Pre-systemd groups 中所述,是拥有对应于设备类别的不同组。

对于 systemd 系统,现代推荐的方法是使用 MODE 660 来允许组使用设备,然后附加一个名为 uaccessTAG [6]。这个特殊的标签会使 udev 将一个 动态用户 ACL 应用到设备节点上,该节点与 systemd-logind(8) 协调,以便让已登录用户可以使用该设备。有关实现此目的的 udev 规则示例,请参见

/etc/udev/rules.d/71-device-name.rules
ACTION!="remove", SUBSYSTEMS=="usb", ATTRS{idVendor}=="vendor_id", ATTRS{idProduct}=="product_id", MODE="0660", TAG+="uaccess"
注意 为了使任何添加 uaccess 标签的规则生效,其定义所在文件的名称 必须在字典顺序上先于 /usr/lib/udev/rules.d/73-seat-late.rules

插入或拔出 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"
注意 如果规则在 X 服务器启动之前触发,它可能无法按预期工作。参见 #X programs in RUN rules hang when no X server is present

插入 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 Display Manager 的配置如下

$ printenv XAUTHORITY
/run/user/1000/gdm/Xauthority
注意 如果规则在 X 服务器启动之前触发,它可能无法按预期工作。参见 #X programs in RUN rules hang when no X server is present

检测新 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 驱动器。 GNOMEKDE 会不断要求输入 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"
注意 连接 eSATA 驱动器后,可以使用以下命令找到 DEVPATH(根据需要替换 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 规则来使用静态设备名称。有关块设备,请参见 Persistent block device naming;有关网络设备,请参见 Network configuration#Change interface name

视频设备

有关设置网络摄像头的初步信息,请参阅 Webcam setup

使用多个网络摄像头时,启动时会随机分配 /dev/video* 视频设备。推荐的解决方案是使用 udev 规则创建符号链接,如 #Example 所示。

/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 下创建符号链接,类似于 Persistent block device naming 方案。

/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 设备(如鼠标或键盘)的 唤醒触发器,以便用它将系统从睡眠状态唤醒。

注意 默认情况下,所有 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"

在挂起后强制重新添加 USB 设备功能

USB 设备在退出挂起状态后需要重置(无论是系统从睡眠中恢复还是设备空闲以节省电力时端口被关闭),Linux 内核 在很大程度上透明地处理 这个过程,称为 reset-resume,这样 USB 驱动器就不会像每次都被断开和重新连接一样。这在大多数情况下是可取的,但某些设备,如 USB TTY 接口,在断电重启后确实需要手动重新配置,而相关的驱动程序不会发出任何 uevent 来通知这一点。

然而,确实会触发的一个 uevent 是来自失去和恢复电源/唤醒的事件,因为 USB 设备在未配置时是无法使用的,并且在这种状态下无法唤醒系统。这两个事件没有唯一的事件属性,但第一个事件仍然可以轻松识别,因为 DEVNUM 被设置为零(这不是一个有效的设备号)在设备未配置并失去 power/wakeup 之前立即,触发 uevent。当发生这种情况时,可以通过触摸 bConfigurationValue sysfs 属性来强制系统重新配置设备非透明地,就像它在睡眠期间断开连接一样,这会解绑所有驱动程序并删除所有子设备,然后在设备准备好后重新添加它们。

# The actual rule to be run
ACTION=="add" SUBSYSTEM=="tty" SUBSYSTEMS="usb" ATTRS{manufacturer}=="Foo" ATTRS{product}=="Bar" TAG+="systemd" ENV{SYSTEMD_WANTS}="setup-usb-tty@$devnode.service"

# The rule that will get the above rule to re-run when the computer comes out of sleep
# Can't use $attr{bConfigurationValue} in ATTR{}, so make sure to replace
# both "1" by whatever configuration value is being used when
# the device is functional.
ACTION=="change" SUBSYSTEM=="usb" ENV{DEVNUM}=="000" ATTR{manufacturer}=="Foo" ATTR{product}=="Bar" ATTR{bConfigurationValue}=="1" ATTR{bConfigurationValue}="1"

触发事件

本文档或本节可能合并到 #Testing rules before loading

注意:类似的技巧(在 Talk:Udev 讨论)

触发各种 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),如 Desktop notifications#Send notifications to another user 中所述。

创建文件

/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
注意 如果规则在 X 服务器启动之前触发,它可能无法按预期工作。参见 #X programs in RUN rules hang when no X server is present

故障排除

黑名单模块

在极少数情况下,udev 可能会出错并加载错误的模块。为了防止这种情况发生,您可以 黑名单模块。一旦被黑名单,udev 将永远不会加载该模块——既不会在启动时加载,也不会在稍后收到热插拔事件时加载(例如,插入 USB 闪存驱动器)。

调试输出

要获取硬件 调试信息,请使用 udev.log-priority=debug 内核参数。或者您可以设置

/etc/udev/udev.conf
udev_log="debug"

您还可以通过将配置文件添加到 FILES 数组中,将此选项编译到您的 initramfs 中

/etc/mkinitcpio.conf
FILES=(... /etc/udev/udev.conf)

然后 重新生成 initramfs

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_SERIALID_SERIAL_SHORT(请记住在需要时更改 /dev/sdb

$ udevadm info --query=property --property=ID_SERIAL,ID_SERIAL_SHORT --name=/dev/sdb

然后我们将 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 中的旧条目。尝试清除该文件并重试。
  • OSS 仿真模块 snd_pcm_osssnd_seq_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 错误。

参见