1. UDEV介绍
1.1. udev概要
在传统的Linux系统中,/dev目录下的设备节点为一系列静态存在的文件,而udev则动态提供了在系统中实际存在的设备节点。虽然devfs提供了类似功能,但udev有比devfs更加好的地方:
udev支持设备的固定命名,而并不依赖于设备插入系统的顺序。默认的udev设置提供了存储设备的固定命名。可以使用其vid(vendor)、pid(device)、设备名称(model)等属性或其父设备的对应属性来确认某一设备。
udev完全在用户空间执行,而不是像devfs在内核空间一样执行。结果就是udev将命名策略从内核中移走,并可以在节点创建前用任意程序在设备属性中为设备命名。
1.2. udev运行方式
udev是一个通用的内核设备管理器。它以守护进程的方式运行于Linux系统,并监听在新设备初始化或设备从系统中移除时,内核(通过netlink socket)所发出的uevent。
系统提供了一套规则用于匹配可发现的设备事件和属性的导出值。匹配规则可能命名并创建设备节点,并运行配置程序来对设备进行设置。udev规则可以匹配像内核子系统、内核设备名称、设备的物理等属性,或设备序列号的属性。规则也可以请求外部程序提供信息来命名设备,或指定一个永远一样的自定义名称来命名设备,而不管设备什么时候被系统发现.
1.3. udev系统架构:
udev系统可以分为三个部分:
libudev函数库,可以用来获取设备的信息。
udevd守护进程,处于用户空间,用于管理虚拟/dev
管理命令udevadm,用来诊断出错情况。
系统获取内核通过netlink socket发出的信息。早期的版本使用hotplug,并在/etc/hotplug.d/default添加一个链接到自身来达到目的。
2. udevadm 命令介绍
在Linux man page 中它是这么描述的。udevadm - udev management tool 。也就是说udevadm命令是管理udev的一个工具。实际我们如果要实现设备的重命名或是设备的自动挂载,我们也是使用udevadm来查看和跟踪udev的信息。
udevadm可以用来监视和控制udev运行时的行为,请求内核事件,管理事件队列,以及提供简单的调试机制。
2.1. udevadm主命令
- info 查询sysfs或者udev的数据库
- trigger 从内核请求events
- settle 查看udev事件队列,如果所有的events已处理则退出
- control 修改udev后台的内部状态信息
- monitor 监控内核的uevents
- hwdb 处理硬件数据库索引
- test 调试
2.2. 命令应用:
2.2.1. 查看设备信息:
- 查询sda的所有信息
➜ ~ udevadm info --query=all --name=sda
P: /devices/pci0000:00/0000:00:05.0/virtio2/block/vda
N: vda
L: 0
S: disk/by-path/pci-0000:00:05.0
S: disk/by-id/virtio-UCLOUD_DISK_VDA
S: disk/by-path/virtio-pci-0000:00:05.0
E: DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2/block/vda
E: DEVNAME=/dev/vda
E: DEVTYPE=disk
E: MAJOR=252
E: MINOR=0
E: SUBSYSTEM=block
E: USEC_INITIALIZED=1694686
E: ID_SERIAL=UCLOUD_DISK_VDA
E: ID_PATH=pci-0000:00:05.0
E: ID_PATH_TAG=pci-0000_00_05_0
E: ID_PART_TABLE_UUID=88a0f3ae-6a43-463d-becf-3b7da32bdff1
E: ID_PART_TABLE_TYPE=gpt
E: DEVLINKS=/dev/disk/by-path/pci-0000:00:05.0 /dev/disk/by-id/virtio-UCLOUD_DISK_VDA /dev/disk/by-path/virtio-pci-0000:00:05.0
E: TAGS=:systemd:
- 查看sda的path
➜ ~ udevadm info --query=path --name=vda
/devices/pci0000:00/0000:00:05.0/virtio2/block/vda
- 查看nvme0n1的所有父设备一直到sysfs的根节点
udevadm info --attribute-walk --name=/dev/nvme0n1
- 其他参数
--query=type 从数据库中查询指定类型的设备。需要--path和--name来指定设备。合法的查询文件是:设备名,链接,路径,属性
--path=devpath 设备的路径
--name=file 设备节点或者链接
--attribute-walk 打印指定设备的所有sysfs记录的属性,以用来udev规则匹配特殊的设备。该选项打印链上的所有设备信息,最大可能到sys目录。
--device-id-of-file=file 打印主/从设备号
--export-db 输出udev数据库中的内容
2.2.2. 监控设备事件:
udevadm monitor [options] 监听内核事件和udev发送的events事件。打印事件发出的设备。可以通过比较内核或者udev事件的时间戳来分析事件时序。
udevadm monitor --property 输出事件的属性
udevadm monitor --kernel --property --subsystem-match=usb 过滤监听符合条件的时间
--kernel 输出内核事件
--udev 输出udev规则执行时的udev事件
--property 输出事件的属性
--subsystem-match=string 通过子系统或者设备类型过滤事件。只有匹配了子系统值的udev设备事件通过。
--tag-match=string 通过属性过滤事件,只有匹配了标签的udev事件通过。
2.2.3. 模拟一个udev事件
udevadm test [options] devpath 模拟一个udev事件,打印出debug信息。
2.2.4. 接收内核发送来的设备事件
udevadm trigger [options] 接收内核发送来的设备事件。主要用于重放coldplug事件信息
(内核在启动时已经检测到了系统的硬件设备,并把硬件设备信息通过sysfs内核虚拟文件系统导出。udev扫描sysfs文件系统,根据硬件设备信息生成热插拔(hotplug)事件,udev再读取这些事件,生成对应的硬件设备文件。由于没有实际的硬件插拔动作,所以这一过程被称为coldplug。)
--verbose 输出将要被触发的设备列表。
--dry-run 不真的触发事件
--type=type 触发一个特殊的设备。合法的类型:devices,subsystem,failed.默认是devices
--action=action 被触发的事件,默认是change
--subsystem-match=subsystem 触发匹配子系统的设备事件。这个选项可以被多次指定,并且支持shell模式匹配。
--attr-match=attribute=value 触发匹配sysfs属性的设备事件。如果属性值和属性一起指定,属性的值可以使用shell模式匹配。如果没有指定值,会重新确认现有属性。这个选项可以被多次指定。
--attr-nomatch=attribute=value 不要触发匹配属性的设备事件。如果可以使用模式匹配。也可以多次指定
--property-match=property=value 匹配属性吻合的设备。可以多次指定支持模式匹配
--tag-match=property 匹配标签吻合的设备。可以多次指定。
--sysname-match=name 匹配sys设备名相同的设备。可以多次指定支持模式匹配。
2.2.5. 查看udev事件队列
udevadm settle [options] 查看udev事件队列,如果所有事件全部处理完就退出。
--timeout=seconds 等待事件队列空的最大时间。默认是180秒。如果是0则立即退出。
--seq-start=seqnum 只等待到给定的顺序号。
--seq-end=seqnum 只等待到给定顺序号之前。
--exit-if-exists=file 如果文件存在就退出
--quiet 不输出任何信息
3. udev规则
3.1 简单USB例子
3.1.1. 规则:automount-usb.rules
#usb自动挂载
ACTION!="add",GOTO="farsight"
KERNEL=="sd[a-z][0-9]",RUN+="/etc/udev/mount-usb.sh %k"
KERNEL=="sd[a-z]",RUN+="/etc/udev/mount-usb.sh %k"
LABEL="farsight"
3.1.2. 脚本:mount-usb
#!/bin/sh
logger "mount -t vfat /dev/$1 /mnt/usb"
3.1.3. 测试规则是否合法
root@ubuntu:/etc/udev# udevadm info -q path -n /dev/sdn1
/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host45/target45:0:0/45:0:0:0/block/sdn/sdn1
root@ubuntu:/etc/udev/rules.d# udevadm test --action="add" /devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host45/target45:0:0/45:0:0:0/block/sdn/sdn1
输出信息:
... ...
AGS=:systemd:
USEC_INITIALIZED=10078754482
run: '/usr/bin/unshare -m /usr/bin/snap auto-import --mount=/dev/sdn1'
run: '/etc/udev/mount-usb.sh'
Unload module index
Unloaded link configuration context.
1.1.4. 重新加载规则
udevadm control --reload-rules
1.1.5. 查看规则信息
udevadm info -ap /devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host45/target45:0:0/45:0:0:0/block/sdn/sdn1
looking at device '/devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host45/target45:0:0/45:0:0:0/block/sdn/sdn1':
KERNEL=="sdn1"
SUBSYSTEM=="block"
DRIVER==""
ATTR{discard_alignment}=="0"
ATTR{start}=="2048"
ATTR{ro}=="0"
ATTR{size}=="60491776"
ATTR{stat}==" 226 14735 23246 1516 1 0 1 17 0 1160 1534 0 0 0 0 0 0"
ATTR{partition}=="1"
ATTR{inflight}==" 0 0"
ATTR{alignment_offset}=="0"
. 修改规则
#usb自动挂载
ACTION!="add",GOTO="farsight"
KERNEL=="sd[a-z][0-9]" SUBSYSTEM=="block" RUN+="/etc/udev/mount-usb.sh %k"
LABEL="farsight"
4. udev规则和规则文件
4.1. udev 规则的匹配键
1.2.1. udev 的规则配置文件
在规则文件里,除了以“#”开头的行(注释),所有的非空行都被视为一条规则,但是一条规则不能扩展到多行。规则都是由多个 键值对(key-value pairs)组成,并由逗号隔开,键值对可以分为 条件匹配键值对( 以下简称“匹配键 ”) 和 赋值键值对( 以下简称“赋值键 ”),一条规则可以有多条匹配键和多条赋值键。匹配键是匹配一个设备属性的所有条件,当一个设备的属性匹配了该规则里所有的匹配键,就认为这条规则生效,然后按照赋值键的内容,执行该规则的赋值。
规则文件里的规则有一系列的键/值对组成,键/值对之间用逗号(,)分割。
通过上面例子中也能看出,这些配置,但我想大家可能会产生疑惑,为什么 KERNEL 是匹配键,而 NAME 和 MODE 是赋值键呢?这由中间的操作符 (operator) 决定。
仅当操作符是“==”或者“!=”时,其为匹配键;若为其他操作符时,都是赋值键。
4.2. udev 规则的匹配键
键 | 含义 |
---|---|
ACTION | 事件 (uevent) 的行为,例如:add( 添加设备 )、remove( 删除设备 )。 |
KERNEL | 在内核里看到的设备名字,比如sd*表示任意SCSI磁盘设备 |
DEVPATH | 内核设备录进,比如/devices/* |
SUBSYSTEM | 子系统名字,例如:sda 的子系统为 block。 |
BUS | 总线的名字,比如IDE,USB |
DRIVER | 设备驱动的名字,比如ide-cdrom |
ID | 独立于内核名字的设备名字 |
SYSFS{filename | 设备的 devpath 路径下,设备的属性文件“filename”里的内容 |
ENV{key} | 环境变量,可以表示任意 |
PROGRAM | 可执行的外部程序,如果程序返回0值,该键则认为为真(true) |
RESULT | 上一个PROGRAM调用返回的标准输出。 |
NAME | 根据这个规则创建的设备文件的文件名。 **注意:**仅仅第一行的NAME描述是有效的,后面的均忽略。如果你想使用使用两个以上的名字来访问一个设备的话,可以考虑SYMLINK键。 |
SYMLINK | 为/dev/下的设备文件产生符号链接。由于udev只能为某个设备产生一个设备文件,所以为了不覆盖系统默认的 udev 规则所产生的文件,推荐使用符号链接。 |
OWNER | 设备文件的属组 |
GROUP | 设备文件所在的组。 |
MODE | 设备文件的权限,采用8进制 |
RUN | 为设备而执行的程序列表 |
LABEL | 在配置文件里为内部控制而采用的名字标签(下下面的GOTO服务) |
GOTO | 跳到匹配的规则(通过LABEL来标识),有点类似程序语言中的GOTO |
IMPORT{type} | 导入一个文件或者一个程序执行后而生成的规则集到当前文件 |
WAIT_FOR_SYSFS | 等待一个特定的设备文件的创建。主要是用作时序和依赖问题。 |
OPTIONS | 特定的选项:last_rule 对这类设备终端规则执行; ignore_device 忽略当前规则; ignore_remove 忽略接下来的并移走请求。 all_partitions 为所有的磁盘分区创建设备文件。 |
4.2.1. 查看以上变量值
root@ubuntu:/etc/udev/rules.d# udevadm info -ap /devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/host45/target45:0:0/45:0:0:0/block/sdn/sdn1
KERNEL=="sdn1"
SUBSYSTEM=="block"
DRIVER==""
ATTR{ro}=="0"
ATTR{alignment_offset}=="0"
ATTR{start}=="2048"
ATTR{inflight}==" 0 0"
ATTR{size}=="60491776"
ATTR{discard_alignment}=="0"
4.2.2. ENV、RUN、KERNEL、ACTION和SYMLINK
KERNEL=="sdc1" ACTION=="add" ENV{usb_flag}="KIOXIA-USB" NAME="test-usb$i" RUN+="/etc/udev/usb-add.sh" SYMLINK="test-usb"
root@ubuntu:/etc/udev/rules.d# ls /dev/test-usb -al
lrwxrwxrwx 1 root root 4 Jun 29 18:37 /dev/test-usb -> sdb1
TAGS=:systemd:
usb_flag=KIOXIA-USB
USEC_INITIALIZED=546487870
run: '/usr/bin/unshare -m /usr/bin/snap auto-import --mount=/dev/sdb1'
run: '/etc/udev/usb-add.sh'
Unload module index
4.2.3 . SYSFS
KERNEL=="sdb1" ACTION=="add" ENV{usb_flag}="KIOXIA-USB" SYSFS{removable}=="1" NAME="test-usb$i" RUN+="/etc/udev/usb-add.sh" SYMLINK="test-usb"
SYSFS{removable}为
test@ubuntu:~/Desktop$ cat /sys/block/sdb/removable
1`
4.2.4 . PROGRAM
KERNEL=="sdb1" ACTION=="add" ENV{usb_flag}="KIOXIA-USB" PROGRAM="/etc/udev/usb-add.sh usb" SYMLINK="test-usb$result"
/etc/udev/usb-add.sh返回值为100,即echo 100
root@ubuntu:/sys/block/sdb# ls /dev/test-usb100 -al
lrwxrwxrwx 1 root root 4 Jun 29 19:59 /dev/test-usb100 -> sdb1
4.2.5. IMPORT{类型}
将一组变量导入为设备的属性。不同的"类型"含义如下:
-
“program” 执行一个外部程序,并且当其返回值为零时导入其输出内容。注意,输出内容的每一行都必须符合"key=value"格式。关于程序 路径、命令与参数分隔符、 引号的使用规则、程序执行时间,等等,都与RUN相同。
-
“builtin” 与"program"类似,但是仅用于执行内置的程序。
-
“file” 导入一个文本文件的内容。该文本文件的每一行都必须符合"key=value"格式。以"#"开头的行将被视为注释而忽略。
-
“db” 从当前已有的udev数据库中导入一个单独的属性。仅可用于udev数据库确实已经被早先的设备事件所填充的情形。
-
“cmdline” 从内核引导选项导入一个单独的属性。对于那些仅有单独的标记而没有值的属性, 其值将被指定为 “1” 。
-
“parent” 从父设备导入已有的属性(包括对应的值)。可以将IMPORT{parent}赋值为shell风格的匹配模式,以导入多个属性名称与匹配模式相符的属性。仅可使用运行时间非常短的前台程序,切勿设置任何后台守护进程或者长时间运行的程序。 参见 RUN
4.3. 字符串替换和匹配
4.3.1. 字符串替换
$kernel, %k:设备的内核设备名称,例如:sda、cdrom。
$number, %n:设备的内核号码,例如:sda3 的内核号码是 3。
$devpath, %p:设备的 devpath路径。
$id, %b:设备在 devpath里的 ID 号。
$sysfs{file}, %s{file}:设备的 sysfs里 file 的内容。其实就是设备的属性值。
例如:$sysfs{size} 表示该设备 ( 磁盘 ) 的大小。
$env{key}, %E{key}:一个环境变量的值。
$major, %M:设备的 major 号。
$minor %m:设备的 minor 号。
$result, %c:PROGRAM 返回的结果
$parent, %P:父设备的设备文件名。
$root, %r:udev_root的值,默认是 /dev/。
$tempnode, %N:临时设备名。
%%:符号 % 本身。
$$:符号 $ 本身。
例子如下:
KERNEL=="fb[0-9]*", NAME="fb/%n", SYMLINK+="%k"
规则意思是:匹配一个内核命名为fb[0-9]的设备,然后给它命名为fb/%n,符号链接为%k
比如有一个fb3设备,匹配成功后那么久有一个名为fb/3,符号链接为fb3
4.3.2. 字符串匹配
不仅有字符串精确匹配, udev也允许你使用shell风格的模式匹配. 支持的3种模式为:
* - 匹配任何字符, 匹配0次或多次
? - 匹配任何字符,但只匹配一次.
[] - 匹配任何单个字符, 这些字符在方括号里面指定, 范围是受限的.
这里有一些例子, 注意字符串替换符的使用:
KERNEL=="fd[0-9]*", NAME="floppy/%n", SYMLINK+="%k"
KERNEL=="hiddev*", NAME="usb/%k"
第一条规则匹配所有软盘驱动并确保设备节点放置在/dev/floppy目录下, 也创建一个缺省名字的符号链接. 第二条规则确保hiddev设备节点放在/dev/usb目录下面.
4.4. udev 键/值对操作符
操作符 | 匹配或赋值 | 解释 |
---|---|---|
== | 匹配 | 相等比较 |
!= | 匹配 | 不等比较 |
= | 赋值 | 分配一个特定的值给该键,他可以覆盖之前的赋值。 |
+= | 赋值 | 追加特定的值给已经存在的键 |
:= | 赋值 | 分配一个特定的值给该键,后面的规则不可能覆盖它。 |
5. udev主要作用
- 重命名设备节点的缺省名字为其他名字
- 通过创建符号链接到缺省设备节点来提供一个可选的固定的设备节点名字
- 基于程序的输出命名设备节点
- 改变设备节点的权限和所有权
- 在设备节点被创建或删除时(通常是添加设备或拔出设备时)执行一个脚本
- 重命名网络接口
5.1 重命名设备节点的缺省名字为其他名字
此时使用NAME赋值键,例子如下:
一个硬盘,它的设备属性KERNEL是hdb,在/dev/目录下是/dev/hdb,那么我们可以给他重命名为
KERNEL=="hdb", NAME="my_spare_disk"
规则意思是:匹配一个设备命名为hdb的设备,把它重新命名为my_spare_disk. 设备节点出现在/dev/my_spare_disk
执行以下命令:ls /dev/my_spare_disk -l
/dev/my_spare_disk —> /dev/hdb产生一个符号链接指向/dev/hdb
注意:仅仅第一行的NAME描述是有效的,后面的均忽略。即udev按顺序解析udev规则文件时,第一个NAME赋值键的名字有用,假如后面对同一个设备还有NAME赋值键,那么那个赋值的名称将被忽略。如果你想使用使用两个以上的名字来访问一个设备的话,可以考虑SYMLINK键。
如果你想你命名的名字得到实现,你必须把你的规则文件命名顺序在前面。
5.2.通过创建符号链接到缺省设备节点来提供一个可选的固定的设备节点名字
例子如下:
KERNEL=="hdb", DRIVER=="ide-disk", SYMLINK+="sparedisk"
规则意思是:匹配一个内核命名为hdb以及驱动为ide-disk的设备,命名设备节点为缺省名字并创建一个指向它的sparedisk符号链接,设备节点出现在/dev/sparedisk
注意:符号链接可以是多个,这些符号链接都指向/dev/hdb
5.3 基于程序的输出命名设备节点
某些情况下你可能要求比udev标准规则提供的更多弹性, 这种情况下你可以请求udev运行一个程序并运用程序的标准输出来提供设备命名.
要使用这个功能,你只需简单的在PROGRAM赋值中指定要运行程序(以及任何阐述)的完整路径, 然后在NAME/SYMLINK赋值中使用一些%c替换.
例子如下:
引用一个位于/bin/device_namer的虚构程序. device_namer带一个表示内核名字的命令行参数, 基于内核名device_namer做一些变化然后输出
KERNEL=="hda", PROGRAM="/bin/device_namer %k", SYMLINK+="%c"
规则意思是:匹配一个内核命名为hdb的设备,然后运行一个/bin/device_name程序,这个程序需要带一个表示内核名字的命令行参数即%k。然后这个程序运行的结果(即输出)把它赋值给SYMLINK,这样就可以满足要求(使用外部程序来命名设备)
5.4 改变设备节点的权限和所有权
udev允许你在规则中使用另外的赋值来控制每个设备的所有权和权限属性.
例子如下:
KERNEL=="fb[0-9]*", NAME="fb/%n", SYMLINK+="%k", GROUP="video", MODE="0666"
规则意思是:匹配一个内核命名为fb[0-9]的设备,然后给它命名为fb/%n,符号链接为%k, 属于video组, 权限为0666
比如有一个fb3设备,匹配成功后那么久有一个名为fb/3,符号链接为fb3,属组为video,权限为0666
5.5. 在设备节点被创建或删除时(通常是添加设备或拔出设备时)执行一个脚本
特别针对热插拔的设备,目的是为了在设备连接或者断开时运行一个特定程序. 例如, 你可能想在你的数码相机连到系统时执行一个脚本来自动下载相机里面的所有照片.
例子如下:
KERNEL=="sdb", ACTION=="add", RUN+="/usr/bin/my_program"
规则意思是:匹配一个内核名为sdb的设备,当插入时,执行程序/usr/bin/my_program
5.6 重命名网络接口
在规则中简单的匹配网卡MAC地址是有意义的,因为它们是唯一的.
# udevadm info -a -p /sys/class/net/eth0
looking at class device '/sys/class/net/eth0':
KERNEL=="eth0"
ATTR{address}=="00:52:8b:d5:04:48"
规则如下:
KERNEL=="eth*", ATTR{address}=="00:52:8b:d5:04:48", NAME="lan"
这样就重命名了eth*为lan
https://blog.csdn.net/li_wen01/article/details/89435306