udev
udev - Dynamic device management 动态地址管理
概述
udev 能够处理设备事件、管理设备文件的权限、 在 /dev
目录中创建额外的符号链接、重命名网络接口,等等。 内核通常仅根据设备被发现的先后顺序给设备文件命名, 因此很难在设备文件与物理硬件之间建立稳定的对应关系。 而根据设备的物理属性或配置特征创建有意义的符号链接名称或网络接口名称, 就可以在物理设备与设备文件名称之间建立稳定的对应关系。
udev守护进程(systemd-udevd.service(8)) 直接从内核接收设备的插入、拔出、改变状态等事件, 并根据这些事件的各种属性, 到规则库中进行匹配,以确定触发事件的设备。 被匹配成功的规则有可能提供额外的设备信息,这些信息可能会被记录到udev数据库中, 也可能会被用于创建符号链接。
udev处理的所有设备信息都存储在udev数据库中, 并且会发送给可能的设备事件的订阅者。 可以通过 libudev 库访问udev数据库以及设备事件源。
规则文件
可以直接看示例,参照示例来匹配规则说明
规则文件分别位于: 系统规则目录(/usr/lib/udev/rules.d
)、 运行时规则目录(/run/udev/rules.d
)、 本机规则目录(/etc/udev/rules.d
)。 无论位于哪个目录中,所有的规则文件统一按照文件名的字典顺序处理。 对于不同目录下的同名规则文件,仅以优先级最高的目录中的那一个为准。 也就是说3个目录按照a-z的顺序: /etc/
的优先级最高、 /run/
的优先级居中、 /usr/lib/
的优先级最低。 如果系统管理员想要屏蔽 /usr/lib/
目录中的某个规则文件, 那么最佳做法是在 /etc/
目录中创建一个指向 /dev/null
的同名符号链接, 即可彻底屏蔽 /usr/lib/
目录中的同名文件。 注意,规则文件必须以 .rules
作为后缀名,否则将被忽略。
规则文件中以 "#
" 开头的行以及空行将被忽略, 其他不以 "#
" 开头的非空行,每行必须至少包含一个"键-值"对。 "键"有两种类型:匹配与赋值。 如果某条规则的所有匹配键的值都匹配成功,那么就表示此条规则匹配成功, 也就是此条规则中的所有赋值键都会被赋予指定的值。
一条匹配成功的规则可以 重命名一个网络接口、为某个设备文件添加一个软连接、运行一个指定的程序作为事件处理的一部分。
每条规则都是由一系列逗号分隔的"键-值"对组成。 根据操作符的不同,每个键都对应着一个唯一的操作。
shell
$ ls /usr/lib/udev/rules.d
50-firmware.rules 60-input-id.rules 60-serial.rules 73-special-net-names.rules 80-net-setup-link.rules
50-kdump-tools.rules 60-persistent-alsa.rules 64-btrfs.rules 73-usb-net-by-mac.rules 85-hdparm.rules
50-udev-default.rules 60-persistent-input.rules 69-lvm-metad.rules 75-net-description.rules 85-hwclock.rules
55-dm.rules 60-persistent-storage-dm.rules 70-joystick.rules 75-persistent-net-generator.rules 90-console-setup.rules
56-lvm.rules 60-persistent-storage.rules 70-mouse.rules 75-probe_mtd.rules 90-rdma-umad.rules
60-block.rules 60-persistent-storage-tape.rules 70-power-switch.rules 75-rdma-description.rules 95-dm-notify.rules
60-bridge-network-interface.rules 60-persistent-v4l.rules 70-touchpad.rules 78-sound-card.rules 99-systemd.rules
60-cdrom_id.rules 60-rdma-ndd.rules 70-uaccess.rules 80-debian-compat.rules
60-drm.rules 60-rdma-persistent-naming.rules 71-seat.rules 80-drivers.rules
60-evdev.rules 60-sensor.rules 73-seat-late.rules 80-ifupdown.rules
匹配/赋值操作符
匹配
- "
==
":(匹配)"等于" - "
!=
":(匹配)"不等于"
赋值
- "
=
":(赋值)为键赋予指定的值。 此键之前的值(可能是个列表)将被丢弃。 - "
+=
":(赋值)在键的现有值列表中增加此处指定的值。 - "
-=
":(赋值)在键的现有值列表中删除此处指定的值。- Added in version 217 - "
:=
":(赋值)为键赋予指定的值,并视为最终值,也就是禁止被继续修改。- Added in version 247
shell
$ systemd --version
systemd 247 (247.3-7+deb11u2)
+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +ZSTD +SECCOMP +BLKID +ELFUTILS +KMOD +IDN2 -IDN +PCRE2 default-hierarchy=unified
Values
值需要用双引号包裹,例如:"string"
,如果值需要使用双引号,则使用\"
,除此(\"
)之外的其他反斜杠后面的字符都不会被还原成其原始的形式。也就是说,"\t\n"
会被视为4个字符:反斜杠、小写字母t、反斜杠、小写字母n。
字符串可以以小写字母e作为前缀(e"string\n"
)来标记字符串为C风格转义字符串,参见C中的转义序列。例如,e"string\n"
会被解析为7个字符:6个小写字母和一个换行符。这对于在内核驱动程序需要特殊字符时是有用的。
请注意,在任何字符串变体中都不允许使用NUL
。
Keys
以下Keys可用于与设备属性进行匹配。其中一些键也可以与sysfs中的父设备属性匹配,而不仅仅是生成事件的设备。如果在单个规则中指定了与父设备匹配的多个键,则所有这些键都必须在同一个父设备上匹配。
-
ACTION
:匹配事件的动作。例如"add"
表示插入一个设备。 -
DEVPATH
:匹配设备的路径(也就是该设备在sysfs文件系统下的相对路径)。例如:/dev/sda1
对应的 devpath 是/sys/block/sda/sda1
。 -
KERNEL
:匹配设备的内核名称。"内核名称"是指设备在sysfs里的名称,也就是默认的设备文件名称,例如"sda"。 -
NAME
:匹配网络接口的名称。 仅在先前的规则中已将 NAME 键赋值的前提下,才可将此键用于匹配。 -
SYMLINK
:匹配指向节点的符号链接的名称。一旦在先前的规则中设置了SYMLINK键,就可以使用它。可能有多个符号链接;只需要一个匹配。如果操作符是"!=",则只有在没有匹配到符号链接时,该标记才返回true。 -
SUBSYSTEM
:匹配设备所属的子系统。例如"sound"或"net" -
SUBSYSTEMS
:沿着devpath向上搜索匹配的SUBSYSTEM名称。 -
DRIVER
:匹配设备的驱动程序名称。 仅在设备事件发生时,此设备确实正好绑定着一个驱动程序情况下,此键才会被设置 -
DRIVERS
:沿着devpath向上搜索匹配的DRIVER名称。 -
ATTR{filename}
:匹配设备在sysfs中的属性值。属性值中的尾部空白会被忽略,除非指定的值自身就包含尾部空白。大括号中的"文件"是指设备路径(devpath)下的文件。 例如,对于/dev/sda1
来说,ATTR{size}
的含义其实是指/sys/block/sda/sda1/size
文件的内容。 -
ATTRS{filename}
:沿着devpath向上搜索具有匹配sysfs属性值的设备。如果指定了多个ATTRS匹配,那么所有这些匹配都必须在同一个设备上匹配。 -
SYSCTL{kernel参数}
:匹配"内核参数"的值。所谓"内核参数"其实是指/proc/sys/
中的"内核参数"。例如,可以用SYSCTL{kernel/hostname}
匹配/proc/sys/kernel/hostname
的值。- Added in version 240 -
ENV{key}
:匹配设备的属性。可以通过udevadm info --query=property /dev/sda
命令查看 /dev/sda 的所有属性,例如 "DEVTYPE", "ID_PATH", "SYSTEMD_WANTS" 等等。 -
CONST{key}
:匹配系统范围的常量。支持的键包括:- "arch":系统的架构。有关可能的值,请参阅systemd.unit(5)中的
ConditionArchitecture=values
。 - "virt":系统的虚拟化环境。有关可能的值,请参阅systemd-detect-virt(1)。
- "cvm":系统的保密虚拟化技术。有关可能的值,请参阅systemd-detect-virt(1)。
未知的键将永远不会匹配。
- "arch":系统的架构。有关可能的值,请参阅systemd.unit(5)中的
-
TAG
:根据设备标签中的一个进行匹配。只要前面的规则中设置了一个TAG键,就可以使用该功能。可能有多个标签;只需要一个匹配。如果运算符是"!=",则仅当没有标签匹配时,该标记才返回true。 -
TAGS
:沿着devpath向上搜索匹配的TAG名称。 -
TEST{八进制模式掩码}
:检测指定的文件是否存在。如果有必要,还可以额外指定一个八进制的访问模式掩码。 -
PROGRAM
:执行指定的程序并检查返回值, 如果返回值为零,则匹配成功,否则匹配失败。 设备的属性会转化为该程序的环境变量供其使用。 同时该程序的标准输出会被 自动保存在RESULT
键中。注意,仅可用于执行时间很短的前台程序。 参见RUN
-
RESULT
:匹配最近一次PROGRAM
程序的输出字符串, 必须位于PROGRAM
之后(但可出现在同一条规则中)。
可以在用于匹配的"值"中使用shell风格的通配符, 具体说来就是:
-
"
*
":匹配任意数量的字符(包括零个) -
"
?
":匹配单独一个字符 -
"
[]
":匹配中括号内的任意一个字符。 例如 "tty[SR]
" 可以匹配 "ttyS
" 或 "ttyR
" 。 还可以使用 "-
" 符号表示一个区间。 例如 "[0-9]
" 可以匹配任意数字。 如果在左括号 "[
" 后紧接着一个 "!
" 则表示匹配非括号内的字符。 -
"
|
":用于分隔两个可相互替代的匹配模式(也就是"或"的意思)。 例如 "abc|x*
" 的意思是匹配 "abc
" 或 "x*
"
下面的keys可用于赋值
-
NAME
:设置网络接口的名称。参见 systemd.link(5) 以了解设置网络接口名称的高级机制。 实际上,udev 并不能直接修改设备节点的名称, 它只能为设备节点创建额外的符号链接(相当于添加了别名)。 -
SYMLINK
:设置指向此设备节点的 软连接名称。- 软连接的名字中仅允许使用下列字符: "
0-9A-Za-z#+-.:=@_/
" 、有效的UTF-8字符、 "\x00
" 风格的十六进制编码(实际的文件名并不转码)。 其他字符将被替换为 "_
" 字符。 - 只需在多个名称之间使用空格分隔,即可一次指定多个软连接名称。 如果为多个不同的设备指定了相同的软连接, 那么实际的软连接将指向 link_priority 值最高的设备。 如果 link_priority 值最高的设备被移除, 那么该软连接将重新指向下一个 link_priority 值最高的设备,以此类推。 对于未指定 link_priority 值或者 link_priority 值相等的设备, 它们之间的顺序是不确定的。
- 符号连接的名称必须不能与内核的默认名称相同, 否则会得到无法预知的结果。
- 软连接的名字中仅允许使用下列字符: "
-
OWNER
,GROUP
, `MODE:设置设备节点的属主、属组、权限。 会覆盖内置的默认值。 -
SECLABEL{模块}
:设置设备节点的Linux安全模块标签。- Added in version 209 -
ATTR{key}
:应写入到事件设备的sysfs属性的数值。 -
SYSCTL{kernel parameter}
:应写入到内核参数的数值。- Added in version 220 -
ENV{key}
:设置设备属性值。具有前缀"."的属性名称既不存储在数据库中,也不导出到事件或外部工具(例如,由PROGRAM匹配键运行)。 -
TAG
:将标签附加到设备。用于过滤libudev监视功能的用户的事件,或者用于枚举带有标签的设备组。实现仅在将少量标签附加到设备时才能有效工作。仅应在具有特定设备过滤要求的上下文中使用,而不是作为通用标志。过度使用可能导致事件处理效率低下。 -
RUN{type}
:指定在处理事件的所有规则之后要执行的程序。使用"+="将此调用添加到列表中,使用"="或":="替换列表的任何先前内容。请注意,下面描述的"program"和"builtin"类型共享一个公共列表,因此使用":="和"="清除列表会影响两种类型。 type可以是:-
"program":执行分配值指定的外部程序。如果没有给出绝对路径,则预期程序位于/usr/lib/udev;否则,必须指定绝对路径。如果未指定类型,则此为默认设置。
-
"builtin":与"program"相同,但使用内置程序之一,而不是外部程序。
程序名称和后续参数由空格分隔。可以使用单引号指定带有空格的参数。
这仅可用于非常短暂的前台任务。长时间运行事件进程可能会阻塞此设备或依赖设备的所有进一步事件。
请注意,在udev规则内部不允许运行访问网络或挂载/卸载文件系统的程序,这是由于systemd-udevd.service上强制执行的默认沙箱。
不允许启动守护程序或其他长时间运行的进程;分叉的进程,无论是否分离,都将在事件处理完成后被无条件终止。为了从udev规则激活长时间运行的进程,提供一个服务单元,并使用SYSTEMD_WANTS设备属性从udev设备中引入它。有关详细信息,请参阅systemd.device(5)。
-
-
LABEL
:设置一个可用作 GOTO 跳转目标的标签。 -
GOTO
:跳转到下一个匹配的 LABEL 标签所在的规则。 -
IMPORT{type}
:将一组变量导入为设备的属性。 -
OPTIONS
:规则与设备的选项link_priority=value
:指定创建符号链接时的优先级。 数值越大优先级越高。默认值是"0"。string_escape=none|replace
:在对设备进行命名时,如何处理设备名字中的非常规字符(比如控制字符与不安全的字符)。 none 表示不做处理,保持原样; replace 表示将这些非常规字符替换为"_"(下划线)。static_node=
:将本条规则设定的权限 应用到此选项指定的静态设备节点上。 同时,如果在本规则中指定了标签(tag), 那么还会在/run/udev/static_node-tags/*
tag*
目录中创建一个指向该静态设备节点的软连接。 注意,在 systemd-udevd 启动之前, 静态设备节点就已经由 systemd-tmpfiles 创建完成了。 创建静态设备节点时,并不要求存在对应的内核设备, 因为当这些设备节点被访问时,会触发内核模块的自动加载功能。watch
:使用文件系统的 inotify 功能监视设备节点。 当节点被打开并写入之后又被关闭, 将会触发一个"设备状态已变化"的事件。nowatch
:禁用针对设备节点的 inotify 监视功能。db_persist
:在事件设备的 udev 数据库项上设置 粘滞位(sticky bit)。 这样,即使调用了 udevadm info --cleanup-db 命令, 设备的属性也依然会保存在数据库中。 在某些情况下(例如 Device Mapper 设备), 此选项可用于从 initramfs 切换至真实的根文件系统时,依然保持设备的状态。- Added in version 241log_level=level
:允许设置日志级别名称,如"debug"或"info",或特殊值"reset"。当指定日志级别名称时,最大日志级别将更改为该级别。设置为"reset"时,则撤销先前指定的日志级别。默认情况下,它使用systemd-udevd的主进程的日志级别。此功能对于调试与特定设备相关的事件非常有用。注意,日志级别是在行时应用的 包含此规则进行处理。因此,对于调试,它建议在较早的地方指定,例如00-debug.rules的第一行:SUBSYSTEM=="net", OPTIONS="log_level=debug"
。- Added in version 248
NAME
, SYMLINK
, PROGRAM
, OWNER
, GROUP
, MODE
, SECLABEL
, RUN
都支持简单的字符串替换。 RUN
的替换发生在 所有规则全部处理完成之后、程序将要执行之前, 因此可以使用由匹配成功的规则所设置的设备属性。 而其他键的替换发生在该键所在规则被处理完成的当时。 可用的替换标记如下:
$kernel, %k
:设备的内核名称$number, %n
:设备在内核中的序号。例如,对于 "sda3
" 来说,此值为 "3
"$devpath, %p
:设备路径(devpath)。也就是该设备在sysfs文件系统下的相对路径。例如,/dev/sda1 对应的设备路径是 /block/sda/sda1 (一般对应着 /sys/block/sda/sda1 目录)。$id, %b
:被SUBSYSTEMS
,KERNELS
,DRIVERS
,ATTRS
成功匹配到的设备的设备名称(The name of the device)$driver
:被SUBSYSTEMS
,KERNELS
,DRIVERS
,ATTRS
成功匹配到的设备的驱动名称(The driver name of the device)$attr{file}, %s{file}
:在规则匹配成功时, 设备路径(devpath)下"文件"的内容(用于表示设备的属性)。 如果该设备路径下没有此文件,则从先前 KERNELS, SUBSYSTEMS, DRIVERS, ATTRS 匹配的父设备中提取。如果"文件"是一个软连接, 则一直追踪软连接到最终的实际文件。$env{key}, %E{key}
:备的属性值。例如 "DEVTYPE", "ID_PATH", "SYSTEMD_WANTS" 等等。[提示]可以通过 udevadm info --query=property /dev/sda 命令查看 /dev/sda 的所有属性。$major, %M
:设备的主设备号$minor, %m
:设备的次设备号$result, %c
:外部程序 PROGRAM 的输出字符串。 可以使用 "%c{N}" 提取第N个子字符串(以空格为分隔符,从"1"开始计数)。 也可以通过 "%c{N+}"(也就是在数字后附加一个 "+")提取 从第N个子字符串开始一直到结尾的部分。$parent, %P
:父设备的节点名称$name
:设备的当前名称。如果没有被任何udev规则修改, 那么等于该设备的内核名称。$links
:一个空格分隔的软链接名称列表,这些软链接都指向该设备的节点。 该值仅在两种情况下存在:(1)发生"remove"事件;(2)先前的规则已对 SYMLINK 赋值。$root, %r
:udev_root 的值$sys, %S
:sysfs 文件系统的挂载点$devnode, %N
:设备节点的名称(也就是设备文件的名称)%%
:"%
" 自身$$
:"$
" 自身
示例
systemd-udevd.service可以从内核监测网卡状态的改变,根据事件来触发相应的操作。例如我们常用的修改网卡名称规则
shell
$ cat /etc/udev/rules.d/70-persistent-net.rules
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="e8:eb:d3:36:b3:1a", ATTR{dev_id}=="0x0", ATTR{type}=="1", NAME="eth0"
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="e8:eb:d3:36:b3:1b", ATTR{dev_id}=="0x0", ATTR{type}=="1", NAME="eth1"
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="9c:c2:c4:0e:24:e0", ATTR{dev_id}=="0x0", ATTR{type}=="1", NAME="eth2"
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="9c:c2:c4:0e:24:e1", ATTR{dev_id}=="0x0", ATTR{type}=="1", NAME="eth3"
关键点
- /etc/udev/rules.d 优先级最高
- 所有带
==
都是匹配,最后的=
才是赋值
shell
$ cat /usr/lib/udev/rules.d/75-net-description.rules
# do not edit this file, it will be overwritten on update
ACTION=="remove", GOTO="net_end"
SUBSYSTEM!="net", GOTO="net_end"
IMPORT{builtin}="net_id"
SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
SUBSYSTEMS=="usb", GOTO="net_end"
SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
SUBSYSTEMS=="pci", IMPORT{builtin}="hwdb --subsystem=pci"
LABEL="net_end"
关键点
- ACTION为remove、非net就跳转到net_end,啥都不做
- IMPORT{builtin}="net_id":执行net_id载入变量
- 如果子系统是USB,则导入内置的"usb_id"和"hwdb --subsystem=usb"规则,并跳转到标签net_end。
- 如果子系统是PCI,则设置环境变量"ID_BUS"为"pci",并设置"ID_VENDOR_ID"和"ID_MODEL_ID"为设备的供应商ID和设备ID。同时执行内置的"hwdb --subsystem=pci"规则。