linux_udev机制实现-1

liaocj 2022-09-20 16:04:03
Categories: > > > Tags:

linux udev 机制实现-1-规则文件

在上一章中对 udev 的执行机制以及 udev 的规则文件做了一个简要的介绍,在这一章节,则着重将焦点放在如何编写规则文件上,毕竟,这才是系统管理员最常用到的知识点。

udev 规则

正如前文中所说,规则文件通常以 .rules 结尾,实际上,规则文件并本身没有太多的意义,最重要的是规则文件中的规则,而 udevd 守护进程也是逐条地去匹配规则,而规则文件主要作用就是对规则进行组织,方便管理。

udev 的规则遵循以下的原则:

对于每一条规则来说,match 和 action 没有特定的区分标志,而是通过 operator(操作符) 进行区分,在大部分的编程语言中,”==” 表示逻辑判断,而 “=” 或 “+=” 之类的属于赋值,所以使用 “==” 或者 “!=” 这种逻辑判断操作符的就是 match 类的条目,而使用赋值类操作符的就是 action 部分。

规则中的每个条目中的键用于匹配或者作为关键字被赋值,一个非常重要的问题就是:它是怎么来的,我们怎么知道它的值是多少?只有知道它的由来,才能针对性地编写对应的规则。

实际上,我们可以把所有的键看成是 udev 的关键字。

一方面,从内核发送到用户空间的信息通常是这样的(以 RTC 为例):

change@/devices/platform/ocp/44e3e000.rtc/rtc/rtc0/omap_rtc_scratch0 ACTION=change DEVPATH=/devices/platform/ocp/44e3e000.rtc/rtc/rtc0 omap_rtc_scratch0 SUBSYSTEM=nvmemSYNTH_UUID=0SEQNUM=4266

这是 beaglebone 平台上触发 rtc 时从内核发送到用户空间的设备信息,在内核信息中,DEVPATH、SUBSYSTEM、SYNTH_UUID 会被自动添加为关键字。

另一方面,udevd 默认提供了许多关键字,比如 SYMLINK 表示软链接,RUN 表示需要执行的程序,同时用户也可以自定义关键字,直接使用 KEY=”foo” 就可以定义一个 KEY 关键字。

看看下面这一条规则示例:

SUBSYSTEM=="rtc", KERNEL=="rtc0", SYMLINK+="rtc", OPTIONS+="link_priority=-100"

这是 50-udev-default.rules 中一条针对 rtc 设备的规则,其中:

SUBSYSTEM=="rtc", KERNEL=="rtc0"

这是规则中的 match 部分,判断当前内核设备消息的 SUBSYSTEM 是否为 “rtc”,同时判断 KERNEL(设备内核名) 是否为 “rtc0”,如果两者都满足条件,则执行后面的 action 部分:

SYMLINK+="rtc", OPTIONS+="link_priority=-100"

第一条 action 条目为 SYMLINK+=”rtc”,表示为设备 rtc0 创建一个软链接,名为 rtc,同时设置 OPTION 属性,这个属性将会在其它地方被使用到。

在你的 linux 系统上,你可以通过 ls -l /dev/rtc* 来查看系统中的 rtc 相关信息,通常是这样的:

lrwxrwxrwx ... /dev/rtc -> rtc0
crw------- ... /dev/rtc0

如果你没有手动地更改系统 rtc 设置,可以看到,/dev/rtc 是指向 /dev/rtc0 的软链接,这个软链接的创建正是由上述的规则所创建,而 /dev/rtc0 则是由内核默认创建的。

udev 规则文件的编写

udev 操作符

在 udev 中,支持的操作符有:

udev 关键字

udev 定义了多个关键字,这些关键字用于匹配或者赋值,在实际的 udev 匹配过程中,还支持针对父级设备进行匹配,而不仅仅是生成设备事件的设备自身,linux 中大量的设备由各类总线进行连接,形成树状的设备拓扑结构,所以在新内核中使用设备树在软件上对所有设备进行描述,而总线本身也被视为设备的一种,父子设备之间通常具有强相关性,因此针对父级(或者祖父级)进行模糊地匹配是合理的。

udev 匹配关键字

ACTION

设备事件的操作类型,有以下几种:

在内核中使用一个枚举结构来定义设备操作类型:

enum kobject_action {
    KOBJ_ADD,
    KOBJ_REMOVE,
    KOBJ_CHANGE,
    KOBJ_MOVE,
    KOBJ_ONLINE,
    KOBJ_OFFLINE,
    KOBJ_BIND,
    KOBJ_UNBIND,
    KOBJ_MAX
};

其中,KOBJ_MAX 表示设备支持操作的最大值,用于判断指定的设备操作是否超出限制。

DEVPATH

设备对应的路径,该关键字以及对应的值通常包含在内核传递给用户空间的 netlink 消息中。

KERNEL

设备在内核中的名称,内核名称是指设备在sysfs里的名称,也就是默认的设备文件名称,例如”sda”,这个值通常不包含在 netlink 消息中。

NAME

匹配网络接口的名称。仅在先前的规则中已将 NAME 键赋值的前提下,才可将此键用于匹配,也就是默认情况下,NAME 不会被自动赋值。

匹配指向此设备节点的软连接的名称。 仅在先前的规则中已将 SYMLINK 键赋值的前提下,才可将此键用于匹配。 可能有多个软连接指向同一个设备节点,但只需其中的一个匹配成功即可。

SUBSYSTEM

匹配设备所属的子系统,该关键字以及对应的值通常包含在内核传递给用户空间的 netlink 消息中。

DRIVER

匹配设备的驱动程序名称。仅在设备事件发生时,此设备确实正好绑定着一个驱动程序情况下,此键才会被设置。因此 netlink 消息中只是可能存在该关键字。

ATTR{file}

匹配设备在sysfs中的属性值。属性值中的尾部空白会被忽略,除非指定的值自身就包含尾部空白。大括号中的”文件”是指设备路径(devpath)下的文件。 例如,对于 /dev/sda1 来说,ATTR{size} 的含义其实是指 /sys/block/sda/sda1/size 文件的内容。

SYSCTL{kern_param}

匹配”内核参数”的值。所谓”内核参数”其实是指 /proc/sys/ 中的”内核参数”。例如,可以用 SYSCTL{kernel/hostname} 匹配 /proc/sys/kernel/hostname 的值。

ENV{attr}

设备属性,这些属性是由规则文件或者 udevd 程序中定义的,比如 “DEVTYPE”, “ID_PATH”, “SYSTEMD_WANTS” 等等,相当于环境变量。

PROGRAM

执行指定的程序并检查返回值, 如果返回值为零,则匹配成功,否则匹配失败。 设备的属性会转化为该程序的环境变量供其使用。 同时该程序的标准输出会被 自动保存在 RESULT 键中。

注意,仅可用于执行时间很短的前台程序。

RESULT

匹配最近一次 PROGRAM 程序的输出字符串, 必须位于 PROGRAM 之后(但可出现在同一条规则中)。

TAG

匹配设备的标签,udev 规则中可以为设备指定标签。

上述的关键字中,同时存在多种带后缀 “S” 的版本,即 “KERNELS”,”SUBSYSTEMS”,”DRIVERS”,”ATTRS”,”TAGS”等(具体参考官方文档),这些带后缀 S 的关键字和不带 S 的实现同样的功能,唯一的区别在于:带后缀 “S” 的关键字可以针对父级进行匹配,比如 KERNELS=”foo”,只要当前设备的任一级父级设备满足该条件,即匹配成功。

统配符

udev 的匹配规则中支持统配符的使用:

“*”

匹配任意数量的字符(包括零个)

“?”

匹配单独一个字符

“[]”

匹配中括号内的任意一个字符。 例如 “tty[SR]” 可以匹配 “ttyS” 或 “ttyR” 。 还可以使用 “-“ 符号表示一个区间。 例如 “[0-9]” 可以匹配任意数字。 如果在左括号 “[“ 后紧接着一个 “!” 则表示匹配非括号内的字符。

“|”

用于分隔两个可相互替代的匹配模式(也就是”或”的意思)。 例如 “abc|x*” 的意思是匹配 “abc” 或 “x*”


udev 赋值关键字

NAME

设置网络接口的名称。实际上,udev 并不能直接修改设备节点的名称, 它只能为设备节点创建额外的符号链接(相当于添加了别名)。

设置指向此设备节点的 软连接名称。

只需在多个名称之间使用空格分隔,即可一次指定多个软连接名称。 如果为多个不同的设备指定了相同的软连接, 那么实际的软连接将指向 link_priority 值最高的设备。 如果 link_priority 值最高的设备被移除, 那么该软连接将重新指向下一个 link_priority 值最高的设备,以此类推。 对于未指定 link_priority 值或者 link_priority 值相等的设备, 它们之间的顺序是不确定的。

符号连接的名称必须不能与内核的默认名称相同, 否则会得到无法预知的结果。

OWNER, GROUP, MODE

设置设备节点的属主、属组、权限。会覆盖内置的默认值。

ATTR{filename}

设置在sysfs中的设备属性。大括号中的”文件”是指设备路径(devpath)下的文件。 例如,对于 /dev/sda1 来说,ATTR{size} 的含义其实是指 /sys/block/sda/sda1/size 文件的内容。

ENV{attr}

设置设备的属性。例如 “DEVTYPE”, “ID_PATH”, “SYSTEMD_WANTS” 等等。可以通过 udevadm info –query=property /dev/sda 命令查看 /dev/sda 的所有属性。 如果属性名以 “.” 开头,那么此属性将不会被记录到udev数据库中,也不会被导出为环境变量(例如 PROGRAM)。

TAG

设置设备的标签。 用于为libudev监视(monitor)功能的用户过滤事件或者枚举已标记的设备。 标签仅在与特殊的设备过滤器一起使用时才有意义,千万不要用于常规目的。 滥用标签将会导致设备事件处理效率显著下降, 所以应该尽量避免为设备设置标签。

RUN{type}

对于每一个设备事件来说,在处理完所有udev规则之后, 都可以再接着执行一个由此键设置的程序列表(默认为空)。 不同的”类型”含义如下:

LABEL

设置一个可用作 GOTO 跳转目标的标签。

GOTO

跳转到下一个匹配的 LABEL 标签所在的规则。

IMPORT{type}

将一组变量导入为设备的属性。不同的”类型”含义如下:

OPTIONS

规则与设备的选项:

字符替换

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 成功匹配到的设备的设备名称

$driver

被 SUBSYSTEMS, KERNELS, DRIVERS, ATTRS 成功匹配到的设备的驱动名称

$attr{文件}, %s{文件}

在规则匹配成功时, 设备路径(devpath)下”文件”的内容(用于表示设备的属性)。 如果该设备路径下没有此文件,则从先前 KERNELS, SUBSYSTEMS, DRIVERS, ATTRS 匹配的父设备中提取。

如果”文件”是一个软连接, 则一直追踪软连接到最终的实际文件。

$env{属性}, %E{属性}

设备的属性值。例如 “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规则修改, 那么等于该设备的内核名称。

一个空格分隔的软链接名称列表,这些软链接都指向该设备的节点。 该值仅在两种情况下存在:(1)发生”remove”事件;(2)先前的规则已对 SYMLINK 赋值。

$root, %r

udev_root 的值

$sys, %S

sysfs 文件系统的挂载点

$devnode, %N

设备节点的名称(也就是设备文件的名称)

%%

百分号 “%” 自身

$$

美元符号 “$” 自身

尽管规则只能被写作一行,并不代表设备的匹配只能使用一行,udev 也可以通过条规则来处理一个设备事件,比如第二条依赖于第一条的执行结果,以实现更灵活的配置,自然地,也就出现了很多赋值关键字和匹配关键字相同的情况。

比如,当系统中增加了一个 rtc 设备时,匹配到的第一条规则读取相应的信息,比如 /sys 目录下的文件、内核传递的信息等,然后把某个有用的信息赋值给某个关键字,在后续的规则中,通过判断该关键字的值再执行其它的逻辑,udev 中这种做法很常见。