内核几乎可以在正在运行的系统中添加或移除任何设备。设备状态的变化(设备是否插入或移除)需要传播到用户空间。设备在插入和识别时需要进行配置。特定设备的用户需要了解该设备识别状态的任何变化。udev 提供了所需的 инфраструктуру 来动态维护 /dev 目录中的设备节点文件和符号链接。udev 规则提供了一种将外部工具插入到内核设备事件处理中的方式。这允许您通过添加某些脚本作为内核设备处理的一部分来执行,或者请求并导入附加数据以在设备处理期间进行评估,从而自定义 udev 设备处理。
/dev 目录中的设备节点提供对相应内核设备的访问。使用 udev,/dev 目录反映了内核的当前状态。每个内核设备都有一个对应的设备文件。如果设备从系统中断开,则会移除设备节点。
/dev 目录的内容保存在临时文件系统上,所有文件在每次系统启动时都会呈现。手动创建或修改的文件,根据设计,在重新启动后不会保留。无论相应内核设备的状态如何,都应始终存在于 /dev 目录中的静态文件和目录可以使用 systemd-tmpfiles 创建。配置文件位于 /usr/lib/tmpfiles.d/ 和 /etc/tmpfiles.d/ 中;有关更多信息,请参阅 systemd-tmpfiles(8) 手册页。
所需的设备信息由 sysfs 文件系统导出。对于内核已检测并初始化的每个设备,都会创建一个以设备名称命名的目录。它包含具有设备特定属性的属性文件。
每次添加或移除设备时,内核都会发送一个 uevent 通知 udev 发生更改。udev 守护程序在启动时读取并解析 /usr/lib/udev/rules.d/*.rules 和 /etc/udev/rules.d/*.rules 文件中的所有规则,并将其保存在内存中。如果规则文件被更改、添加或移除,守护程序可以使用命令 udevadm control --reload 重新加载其内存表示。有关 udev 规则及其语法的更多详细信息,请参阅 第 16.6 节,“使用 udev 规则影响内核设备事件处理”。
每个收到的事件都与提供的规则集进行匹配。这些规则可以添加或更改事件环境变量键,请求创建设备节点的特定名称,添加指向该节点的符号链接,或者添加在创建设备节点后运行的程序。驱动程序核心 uevents 是从内核 netlink 套接字接收的。
内核总线驱动程序探测设备。对于每个检测到的设备,内核都会创建一个内部设备结构,同时驱动程序核心向 udev 守护程序发送一个 uevent。总线设备通过一个特殊格式的 ID 来标识自己,该 ID 指示它是哪种设备。这些 ID 由供应商 ID、产品 ID 和其他子系统特定值组成。每个总线都有自己的 ID 方案,称为 MODALIAS。内核获取设备信息,从中组成一个 MODALIAS ID 字符串,并随事件发送该字符串。对于 USB 鼠标,它看起来像这样
MODALIAS=usb:v046DpC03Ed2000dc00dsc00dp00ic03isc01ip02
每个设备驱动程序都带有一个它能处理的已知设备别名列表。该列表包含在内核模块文件本身中。程序 depmod 读取 ID 列表并为所有当前可用的模块在内核的 /lib/modules 目录中创建 modules.alias 文件。通过此基础设施,模块加载就像为每个带有 MODALIAS 键的事件调用 modprobe 一样简单。如果调用 modprobe $MODALIAS,它会将为设备组成的设备别名与模块提供的别名进行匹配。如果找到匹配项,则加载该模块。所有这些都由 udev 自动触发。
udev 守护程序运行之前在引导过程中发生的所有设备事件都会丢失,因为处理这些事件的基础设施位于根文件系统上,并且当时不可用。为了弥补这种损失,内核提供了一个 uevent 文件,位于 sysfs 文件系统中每个设备的设备目录中。通过向该文件写入 add,内核会重新发送与引导期间丢失的事件相同的事件。对 /sys 中所有 uevent 文件进行简单循环会再次触发所有事件,以创建设备节点并执行设备设置。
例如,引导过程中存在的 USB 鼠标可能不会被早期引导逻辑初始化,因为当时驱动程序不可用。设备发现事件丢失,未能找到设备的内核模块。为了解决这个问题,udev 在根文件系统可用后从内核请求所有设备事件,因此 USB 鼠标设备的事件再次运行。现在它在挂载的根文件系统上找到了内核模块,USB 鼠标可以被初始化。
从用户空间看,设备热插拔序列和运行时设备发现之间没有可见的区别。在这两种情况下,都使用相同的规则进行匹配,并运行相同的配置程序。
程序 udevadm monitor 可用于可视化驱动程序核心事件和 udev 事件处理的时间。
UEVENT[1185238505.276660] add /devices/pci0000:00/0000:00:1d.2/usb3/3-1 (usb) UDEV [1185238505.279198] add /devices/pci0000:00/0000:00:1d.2/usb3/3-1 (usb) UEVENT[1185238505.279527] add /devices/pci0000:00/0000:00:1d.2/usb3/3-1/3-1:1.0 (usb) UDEV [1185238505.285573] add /devices/pci0000:00/0000:00:1d.2/usb3/3-1/3-1:1.0 (usb) UEVENT[1185238505.298878] add /devices/pci0000:00/0000:00:1d.2/usb3/3-1/3-1:1.0/input/input10 (input) UDEV [1185238505.305026] add /devices/pci0000:00/0000:00:1d.2/usb3/3-1/3-1:1.0/input/input10 (input) UEVENT[1185238505.305442] add /devices/pci0000:00/0000:00:1d.2/usb3/3-1/3-1:1.0/input/input10/mouse2 (input) UEVENT[1185238505.306440] add /devices/pci0000:00/0000:00:1d.2/usb3/3-1/3-1:1.0/input/input10/event4 (input) UDEV [1185238505.325384] add /devices/pci0000:00/0000:00:1d.2/usb3/3-1/3-1:1.0/input/input10/event4 (input) UDEV [1185238505.342257] add /devices/pci0000:00/0000:00:1d.2/usb3/3-1/3-1:1.0/input/input10/mouse2 (input)
UEVENT 行显示内核通过 netlink 发送的事件。UDEV 行显示完成的 udev 事件处理程序。时间以微秒打印。UEVENT 和 UDEV 之间的时间是 udev 处理此事件所用的时间,或者 udev 守护程序延迟了其执行以将此事件与相关且已运行的事件同步。例如,硬盘分区事件总是等待主磁盘设备事件完成,因为分区事件可能依赖于主磁盘事件从硬件查询到的数据。
udevadm monitor --env 显示完整的事件环境
ACTION=add DEVPATH=/devices/pci0000:00/0000:00:1d.2/usb3/3-1/3-1:1.0/input/input10 SUBSYSTEM=input SEQNUM=1181 NAME="Logitech USB-PS/2 Optical Mouse" PHYS="usb-0000:00:1d.2-1/input0" UNIQ="" EV=7 KEY=70000 0 0 0 0 REL=103 MODALIAS=input:b0003v046DpC03Ee0110-e0,1,2,k110,111,112,r0,1,8,amlsfw
udev 还会将消息发送到 syslog。控制哪些消息发送到 syslog 的默认 syslog 优先级在 udev 配置文件 /etc/udev/udev.conf 中指定。可以使用 udevadm control --log_priority=LEVEL/NUMBER 更改正在运行的守护程序的日志优先级。
一个 udev 规则可以匹配内核添加到事件本身的任何属性或内核导出到 sysfs 的任何信息。该规则还可以从外部程序请求附加信息。事件与 /usr/lib/udev/rules.d/ (默认规则) 和 /etc/udev/rules.d (系统特定配置) 目录中提供的所有规则进行匹配。
规则文件中的每一行至少包含一个键值对。有两种类型的键,匹配键和赋值键。如果所有匹配键都匹配其值,则应用该规则并为赋值键分配指定的值。匹配规则可以指定设备节点的名称,添加指向节点的符号链接,或运行指定程序作为事件处理的一部分。如果未找到匹配规则,则使用默认设备节点名称创建设备节点。有关规则语法以及用于匹配或导入数据的可用键的详细信息,请参阅 udev 手册页。以下示例规则提供了 udev 规则语法的基本介绍。示例规则均取自 udev 默认规则集 /usr/lib/udev/rules.d/50-udev-default.rules。
udev 规则 ## console
KERNEL=="console", MODE="0600", OPTIONS="last_rule"
# serial devices
KERNEL=="ttyUSB*", ATTRS{product}=="[Pp]alm*Handheld*", SYMLINK+="pilot"
# printer
SUBSYSTEM=="usb", KERNEL=="lp*", NAME="usb/%k", SYMLINK+="usb%k", GROUP="lp"
# kernel firmware loader
SUBSYSTEM=="firmware", ACTION=="add", RUN+="firmware.sh"console 规则由三个键组成:一个匹配键 (KERNEL) 和两个赋值键 (MODE、OPTIONS)。KERNEL 匹配规则在设备列表中搜索类型为 console 的任何项。只有精确匹配才有效并触发此规则的执行。MODE 键为设备节点分配特殊权限,在本例中,只读写权限授予此设备的拥有者。OPTIONS 键使此规则成为应用于此类型任何设备的最后一条规则。任何以后匹配此特定设备类型的规则都不会产生任何效果。
serial devices 规则不再在 50-udev-default.rules 中可用,但仍然值得考虑。它由两个匹配键 (KERNEL 和 ATTRS) 和一个赋值键 (SYMLINK) 组成。KERNEL 键搜索所有 ttyUSB 类型的设备。使用 * 通配符,此键匹配多个此类设备。第二个匹配键 ATTRS 检查 sysfs 中任何 ttyUSB 设备的 product 属性文件是否包含某个字符串。赋值键 (SYMLINK) 触发在 /dev/pilot 下为此设备添加符号链接。此键中使用的操作符 (+=) 告诉 udev 额外执行此操作,即使先前或后来的规则添加了其他符号链接。由于此规则包含两个匹配键,因此仅当两个条件都满足时才应用。
printer 规则处理 USB 打印机,包含两个匹配键,这两个键都必须应用才能应用整个规则 (SUBSYSTEM 和 KERNEL)。三个赋值键处理此设备类型的命名 (NAME)、符号设备链接的创建 (SYMLINK) 和此设备类型的组员资格 (GROUP)。在 KERNEL 键中使用 * 通配符使其匹配多个 lp 打印机设备。在 NAME 和 SYMLINK 键中都使用了替换来通过内部设备名称扩展这些字符串。例如,第一个 lp USB 打印机的符号链接将是 /dev/usblp0。
kernel firmware loader 规则使得 udev 在运行时通过外部辅助脚本加载额外的固件。SUBSYSTEM 匹配键搜索 firmware 子系统。ACTION 键检查是否已添加属于 firmware 子系统的任何设备。RUN+= 键触发 firmware.sh 脚本的执行,以定位要加载的固件。
所有规则都具有共同的一般特性
每个规则由一个或多个用逗号分隔的键值对组成。
键的操作由操作符确定。udev 规则支持多个操作符。
每个给定值都必须用引号括起来。
规则文件的每一行代表一个规则。如果规则超过一行,请使用 \ 将不同的行连接起来,就像在 shell 语法中那样。
udev 规则支持 shell 风格的模式,该模式匹配 *、? 和 [] 模式。
udev 规则支持替换。
创建键时,您可以根据要创建的键的类型选择多个操作符。匹配键通常用于查找与搜索值匹配或明确不匹配的值。匹配键包含以下任何操作符
==
比较是否相等。如果键包含搜索模式,则所有匹配此模式的结果都有效。
!=
比较是否不相等。如果键包含搜索模式,则所有匹配此模式的结果都有效。
以下任何操作符都可以与赋值键一起使用
=
将值赋给键。如果键之前由值列表组成,则键将重置并且只分配单个值。
+=
将值添加到包含条目列表的键。
:=
分配最终值。不允许以后通过后来的规则进行任何更改。
udev 规则支持使用占位符和替换。使用它们的方式与在任何其他脚本中类似。以下替换可与 udev 规则一起使用
%r, $root设备目录,默认是 /dev。
%p, $devpathDEVPATH 的值。
%k, $kernelKERNEL 的值或内部设备名称。
%n, $number设备号。
%N, $tempnode设备文件的临时名称。
%M, $major设备的主设备号。
%m, $minor设备的次设备号。
%s{ATTRIBUTE}, $attr{ATTRIBUTE}sysfs 属性(由 ATTRIBUTE 指定)的值。
%E{VARIABLE}, $env{VARIABLE}环境变量(由 VARIABLE 指定)的值。
%c, $resultPROGRAM 的输出。
%%
% 字符。
$$
$ 字符。
匹配键描述了在应用 udev 规则之前必须满足的条件。以下匹配键可用
ACTION
事件操作的名称,例如,添加或移除设备时的 add 或 remove。
DEVPATH
事件设备的设备路径,例如,DEVPATH=/bus/pci/drivers/ipw3945 用于搜索与 ipw3945 驱动程序相关的所有事件。
KERNEL
事件设备的内部 (内核) 名称。
SUBSYSTEM
事件设备的子系统,例如,SUBSYSTEM=usb 表示所有与 USB 设备相关的事件。
ATTR{FILENAME}
事件设备的 sysfs 属性。例如,要匹配 vendor 属性文件中包含的字符串,您可以使用 ATTR{vendor}=="On[sS]tream"。
KERNELS
让 udev 向上搜索设备路径以查找匹配的设备名称。
SUBSYSTEMS
让 udev 向上搜索设备路径以查找匹配的设备子系统名称。
DRIVERS
让 udev 向上搜索设备路径以查找匹配的设备驱动程序名称。
ATTRS{FILENAME}
让 udev 向上搜索设备路径以查找具有匹配 sysfs 属性值的设备。
ENV{KEY}
环境变量的值,例如,ENV{ID_BUS}="ieee1394 用于搜索与 FireWire 总线 ID 相关的所有事件。
PROGRAM
让 udev 执行外部程序。要成功,程序必须返回退出代码零。程序的输出,打印到 STDOUT,可用于 RESULT 键。
RESULT
匹配上一个 PROGRAM 调用的输出字符串。将此键包含在与 PROGRAM 键相同的规则中,或在后续规则中。
与上面描述的匹配键不同,赋值键不描述必须满足的条件。它们将值、名称和操作分配给 udev 维护的设备节点。
NAME
要创建的设备节点的名称。在规则设置节点名称后,所有其他带有此节点 NAME 键的规则都将被忽略。
SYMLINK
与要创建的节点相关的符号链接的名称。多个匹配规则可以添加要与设备节点一起创建的符号链接。您还可以在一个规则中为单个节点指定多个符号链接,使用空格字符分隔符号链接名称。
OWNER, GROUP, MODE
新设备节点的权限。这里指定的值会覆盖已编译的任何内容。
ATTR{KEY}
指定要写入事件设备的 sysfs 属性的值。如果使用 == 运算符,此键也用于匹配 sysfs 属性的值。
ENV{KEY}
告诉 udev 将变量导出到环境变量中。如果使用 == 运算符,此键也用于匹配环境变量。
RUN
告诉 udev 将程序添加到为此设备执行的程序列表中。请记住将此限制为简短任务,以避免阻塞此设备的其他事件。
LABEL
添加一个标签,供 GOTO 跳转。
GOTO
告诉 udev 跳过几条规则,并继续执行带有 GOTO 键引用的标签的规则。
IMPORT{TYPE}
将变量加载到事件环境中,例如外部程序的输出。udev 导入多种类型的变量。如果未指定类型,udev 会尝试根据文件权限的可执行位自行确定类型。
program 告诉 udev 执行外部程序并导入其输出。
file 告诉 udev 导入一个文本文件。
parent 告诉 udev 导入来自父设备的存储键。
WAIT_FOR_SYSFS
告诉 udev 等待为某个设备创建指定的 sysfs 文件。例如,WAIT_FOR_SYSFS="ioerr_cnt" 通知 udev 等待直到 ioerr_cnt 文件被创建。
OPTIONS
OPTION 键可以有多个值
last_rule 告诉 udev 忽略所有后续规则。
ignore_device 告诉 udev 忽略此事件。
ignore_remove 告诉 udev 忽略此设备的所有后续移除事件。
all_partitions 告诉 udev 为块设备上所有可用的分区创建设备节点。
动态设备目录和 udev 规则基础设施使得为所有磁盘设备提供稳定名称成为可能——无论其识别顺序或用于设备的连接如何。内核创建的每个适当的块设备都会被具有关于特定总线、驱动器类型或文件系统的特殊知识的工具检查。除了动态内核提供的设备节点名称,udev 还维护指向设备的持久符号链接类
/dev/disk
|-- by-id
| |-- scsi-SATA_HTS726060M9AT00_MRH453M4HWHG7B -> ../../sda
| |-- scsi-SATA_HTS726060M9AT00_MRH453M4HWHG7B-part1 -> ../../sda1
| |-- scsi-SATA_HTS726060M9AT00_MRH453M4HWHG7B-part6 -> ../../sda6
| |-- scsi-SATA_HTS726060M9AT00_MRH453M4HWHG7B-part7 -> ../../sda7
| |-- usb-Generic_STORAGE_DEVICE_02773 -> ../../sdd
| `-- usb-Generic_STORAGE_DEVICE_02773-part1 -> ../../sdd1
|-- by-label
| |-- Photos -> ../../sdd1
| |-- SUSE10 -> ../../sda7
| `-- devel -> ../../sda6
|-- by-path
| |-- pci-0000:00:1f.2-scsi-0:0:0:0 -> ../../sda
| |-- pci-0000:00:1f.2-scsi-0:0:0:0-part1 -> ../../sda1
| |-- pci-0000:00:1f.2-scsi-0:0:0:0-part6 -> ../../sda6
| |-- pci-0000:00:1f.2-scsi-0:0:0:0-part7 -> ../../sda7
| |-- pci-0000:00:1f.2-scsi-1:0:0:0 -> ../../sr0
| |-- usb-02773:0:0:2 -> ../../sdd
| |-- usb-02773:0:0:2-part1 -> ../../sdd1
`-- by-uuid
|-- 159a47a4-e6e6-40be-a757-a629991479ae -> ../../sda7
|-- 3e999973-00c9-4917-9442-b7633bd95b9e -> ../../sda6
`-- 4210-8F8C -> ../../sdd1/sys/*
由 Linux 内核提供的虚拟文件系统,导出所有当前已知设备。udev 使用此信息在 /dev 中创建设备节点
/dev/*
动态创建的设备节点和使用 systemd-tmpfiles 创建的静态内容;有关更多信息,请参阅 systemd-tmpfiles(8) 手册页。
以下文件和目录包含 udev 基础设施的关键元素
/etc/udev/udev.conf
主 udev 配置文件。
/etc/udev/rules.d/*
系统特定的 udev 事件匹配规则。您可以在此处添加自定义规则以修改或覆盖 /usr/lib/udev/rules.d/* 中的默认规则。
文件按字母数字顺序解析。优先级较高的文件中的规则会修改或覆盖优先级较低的规则。数字越小,优先级越高。
/usr/lib/udev/rules.d/*
默认 udev 事件匹配规则。此目录中的文件归软件包所有,并会被更新覆盖。请勿在此处添加、移除或编辑文件,请改用 /etc/udev/rules.d。
/usr/lib/udev/*
从 udev 规则调用的辅助程序。
/usr/lib/tmpfiles.d/ 和 /etc/tmpfiles.d/负责静态 /dev 内容。