参考文章

概述

udev (user /dev) 是 systemd 的 Linux 内核设备管理器

udev 用于管理 /dev 目录下的设备节点,udev 依赖于 sysfs 提供的信息与用户提供的规则的匹配信息。

sysfs 文件系统由内核管理,挂载在 /sys 目录下,用于导出系统中设备的基本信息。

udev 使用此信息创建与硬件对应的 /dev 目录下的设备节点。

目录

规则文件保存在以下目录,以 .rules 结尾:

  • /etc/udev/rules.d,管理员编写的规则文件,高优先级,同名则覆盖 /lib 中的规则

  • /lib/udev/rules.d/usr/lib/udev/rules.d,各种软件包附带的规则,用户通常不应更改它们

要禁用规则文件,可以在 /etc 中创建一个与 /lib 中的 rules 文件同名的符号链接,指向 /dev/null

规则语法

  • 每个规则都由一系列键值对构成,键值对之间用逗号分隔。

  • 每个规则应包含至少一个匹配键和至少一个分配键

  • 匹配键是用于标识规则所作用于的设备的条件。

  • 当规则中的所有匹配键都与正在处理的设备相对应时,将应用该规则并调用分配键的操作。

KERNEL=="hdb", NAME="my_spare_disk"

上述规则包含一个匹配键 KERNEL 和一个分配键 NAME。 匹配键通过比较相等运算符 == 与其值相关,而分配键通过赋值运算符 = 与其值相关。

注意:udev 不支持任何形式的换行。 不要在规则中插入任何换行符,因为这会导致 udev 将你的一个规则视为多个规则,并且不会按预期工作。

操作符

==比较相等
!=比较不相等
=为键分配值,表示列表的键将被重置,并且仅分配此单个值
+=将值添加到包含条目列表的键中
-=从包含条目列表的键中删除该值
:=为键赋值,并禁止之后对键的改动

基本规则

常用的匹配键:

  • ACTION:匹配设备事件,如设备连接/断开(add/remove)
  • KERNEL:与设备的 KERNEL 名称匹配
  • SUBSYSTEM:与设备的 SUBSYSTEM 名称匹配
  • DRIVER:与支持设备的 DRIVER 名称匹配

常用的分配键:

  • NAME:应用于设备节点的名称
  • SYMLINK:用作设备节点的替代名称的符号链接列表
KERNEL=="hdb", NAME="my_spare_disk"

上面的规则是:匹配一个被内核命名为 hdb 的设备, 并将设备节点重命名为 my_spare_disk。设备节点显示在 /dev/my_spare_disk 中。

KERNEL=="hdb", DRIVER=="ide-disk", SYMLINK+="sparedisk"

上面的规则是:匹配一个被内核命名为 hdb 且驱动程序为 ide-disk 的设备。 使用默认名称命名设备节点,并创建一个名为 /dev/sparedisk 的符号链接。

KERNEL=="hdc", SYMLINK+="cdrom cdrom0"

上面的规则是:在 /dev/cdrom/dev/cdrom0 处创建两个符号链接,这两个链接都指向 /dev/hdc

匹配 sysfs 属性

驱动程序会将设备的高级属性信息(如供应商代码、编号、序列号等)导出到 sysfs 中, udev 通过 ATTR 键匹配这些属性以实现更精细的控制,如下:

SUBSYSTEM=="block", ATTR{size}=="234441648", SYMLINK+="my_disk"

查看设备属性

如果你知道设备的名称,如 /dev/sda

# udevadm info --attribute-walk --name=/dev/sda
looking at device '/devices/pci0000:00/0000:00:08.1/0000:c6:00.3/usb2/2-1/2-1.2/2-1.2:1.0/host0/target0:0:0/0:0:0:0/block/sda':
    KERNEL=="sda"
    SUBSYSTEM=="block"
    DRIVER==""
    ATTR{alignment_offset}=="0"
    ATTR{capability}=="0"
    ...

如果不知道设备名称,使用以下命令监听,并插入设备,获取 sys 路径(即 DEVPATH):

# udevadm monitor
UDEV  [44538.479869] add      /devices/pci0000:00/0000:00:08.1/0000:c6:00.3/usb2/2-1/2-1.2/2-1.2:1.0/host0/target0:0:0/0:0:0:0/block/sda (block)
UDEV  [44538.501998] add      /devices/pci0000:00/0000:00:08.1/0000:c6:00.3/usb2/2-1/2-1.2/2-1.2:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda1 (block)
UDEV  [44538.502199] add      /devices/pci0000:00/0000:00:08.1/0000:c6:00.3/usb2/2-1/2-1.2/2-1.2:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda2 (block)
...
# udevadm info --attribute-walk --path=/devices/pci0000:00/0000:00:08.1/0000:c6:00.3/usb2/2-1/2-1.2/2-1.2:1.0/host0/target0:0:0/0:0:0:0/block/sda

其中 add 表示设备连接事件,可以用 ACTION 匹配这个事件

更多方法参考:List the attributes of a device

查看设备信息

# udevadm info --query=all --name=/dev/sda
P: /devices/pci0000:00/0000:00:08.1/0000:c6:00.3/usb2/2-1/2-1.2/2-1.2:1.0/host0/target0:0:0/0:0:0:0/block/sda
M: sda
U: block
T: disk
D: b 8:0
N: sda
L: 0
S: disk/by-diskseq/6
S: disk/by-path/pci-0000:c6:00.3-usb-0:1.2:1.0-scsi-0:0:0:0
S: disk/by-id/usb-Kingston_HyperX_Fury_3.0_60A44C426695BF513B4E6EBC-0:0
S: my_disk
S: disk/by-path/pci-0000:c6:00.3-usbv3-0:1.2:1.0-scsi-0:0:0:0
Q: 6
E: DEVPATH=/devices/pci0000:00/0000:00:08.1/0000:c6:00.3/usb2/2-1/2-1.2/2-1.2:1.0/host0/target0:0:0/0:0:0:0/block/sda
E: DEVNAME=/dev/sda
E: DEVTYPE=disk
E: DISKSEQ=6
E: MAJOR=8
...

设备层次结构

Linux 内核以树状结构表示设备,这些信息通过 sysfs 公开,在 /sys 目录下可以找到对应的设备文件。

例如下面命令可以看出 sda 有许多 parent device

# udevadm info --attribute-walk --name=/dev/sda
looking at device '/devices/pci0000:00/0000:00:08.1/0000:c6:00.3/usb2/2-1/2-1.2/2-1.2:1.0/host0/target0:0:0/0:0:0:0/block/sda':
    KERNEL=="sda"
    SUBSYSTEM=="block"
    DRIVERS==""
    ...
looking at parent device '/devices/pci0000:00/0000:00:08.1/0000:c6:00.3/usb2/2-1/2-1.2/2-1.2:1.0/host0/target0:0:0/0:0:0:0':
    KERNELS=="0:0:0:0"
    SUBSYSTEMS=="scsi"
    DRIVERS=="sd"
    ...
looking at parent device '/devices/pci0000:00/0000:00:08.1/0000:c6:00.3/usb2/2-1/2-1.2/2-1.2:1.0/host0/target0:0:0':
    KERNELS=="target0:0:0"
    SUBSYSTEMS=="scsi"
    DRIVERS==""
    ...
# ls /sys/devices/pci0000:00/0000:00:08.1/0000:c6:00.3/usb2/2-1/2-1.2/2-1.2:1.0/host0/target0:0:0/0:0:0:0/block/sda
alignment_offset  discard_alignment  ext_range  partscan   ro      stat
bdi               diskseq            hidden     power      sda1    subsystem
...

以下匹配键可以在设备树中向上搜索,匹配设备或其任何父设备:

  • KERNELS:与设备的内核名称或任何父设备的内核名称匹配
  • SUBSYSTEMS:与设备的 subsystem 或任何父设备的 subsystem 匹配
  • DRIVERS:与支持设备的驱动程序的名称或支持任何父设备的驱动程序的名称匹配
  • ATTRS:匹配设备的 sysfs 属性或任何父设备的 sysfs 属性

字符串替换

类似 printf 的字符串格式化输出:

KERNEL=="mice", NAME="input/%k"
KERNEL=="loop0", NAME="loop/%n", SYMLINK+="%k"
  • $kernel, %k:此设备的内核名称
  • $number, %n:此设备的内核编号;例如 sda3 的内核编号为 3
  • $devpath, %p:设备的 devpath
  • $result, %c:使用 PROGRAM 请求的外部程序返回的字符串

更多参考:man udev

字符串匹配

  • *:匹配任何字符,零次或多次
  • ?:匹配一个任何字符
  • []:匹配括号中指定的任何单个字符,也允许范围
KERNEL=="fd[0-9]*", NAME="floppy/%n", SYMLINK+="%k"
KERNEL=="hiddev*", NAME="usb/%k"

第一条规则匹配所有软盘驱动器,并确保设备节点位于 /dev/floppy 目录中,并从默认名称创建符号链接。

第二条规则确保 hiddev 设备仅存在于 /dev/usb 目录中。

控制权限与所有权

GROUP 定义设备属组:

KERNEL=="fb[0-9]*", NAME="fb/%n", SYMLINK+="%k", GROUP="video"

OWNER 定义设备所有者:

KERNEL=="fd[0-9]*", OWNER="john"

MODE 定义设备权限:

KERNEL=="inotify", NAME="misc/%k", SYMLINK+="%k", MODE="0666"

使用外部程序命名设备

%cPROGRAM 请求的外部程序返回的字符串:

KERNEL=="hda", PROGRAM="/bin/device_namer %k", SYMLINK+="%c"

假定 device_namer 输出两个部分,第一个部分是设备名称,第二个部分是其他符号链接的名称。 可以使用 %c{N} 替换,表示输出的第 N 部分:

KERNEL=="hda", PROGRAM="/bin/device_namer %k", NAME="%c{1}", SYMLINK+="%c{2}"

假设 device_namer 输出 device name%c{1} 部分,后跟任意数量的部分,使用 %c{N+} 替换:

KERNEL=="hda", PROGRAM="/bin/device_namer %k", NAME="%c{1}", SYMLINK+="%c{2+}"

在某些事件上运行外部程序

KERNEL 匹配成功则执行 my_program,设备连接或断开都会触发:

KERNEL=="sdb", RUN+="/usr/bin/my_program"

仅当设备连接时触发:

ACTION=="add", KERNEL=="sdb", RUN+="/usr/bin/my_program"

仅当设备断开时触发:

ACTION=="remove", KERNEL=="sdb", RUN+="/usr/bin/my_program"

不要将 RUN 与 PROGRAM 功能混淆。 PROGRAM 用于运行生成设备名称的程序(它们不应该做任何其他事情)。 PROGRAM 执行时,尚未创建设备节点,因此无法以任何方式对设备执行操作。

环境变量

为环境变量 some_var 赋值 value

KERNEL=="fd0", SYMLINK+="floppy", ENV{some_var}="value"

匹配环境变量 an_env_var 等于 yes

KERNEL=="fd0", ENV{an_env_var}=="yes", SYMLINK+="floppy"

测试规则

指定设备的路径:

# udevadm test /sys/class/backlight/acpi_video0/

加载规则

重新加载 udev 规则:

# udevadm control --reload

手动触发规则:

# udevadm trigger

实例

u盘 插入时以 larry 用户身份执行程序。

/etc/udev/rules.d/10-usb.rules

KERNEL=="sd*", SUBSYSTEM=="block", RUN+="/usr/bin/su larry -c /usr/local/bin/my_program"