udev规则以及编写

时间:2022-02-13 22:56:50

主要内容:

  • udev简介
  • 如何配置和使用udev
  • 如何编写udev规则
  • 字符串替换和匹配
  • udev主要作用
  • 编写udev规则实例
  • 难点解析

1. udev简介

1.1 什么是udev?

udev是Linux(linux2.6内核之后)默认的设备管理工具。udev 以守护进程的形式运行,通过侦听内核发出来的 uevent 来管理 /dev目录下的设备文件。

如何理解udev是守护进程呢?即系统内核启动后init进程(比如busybox的init程序、sysinit、Upstart或systemd)根据runlevel运行等级进入某种模式,然后解析开启哪些服务进程。其中udev就是哪些服务进程中的一个,服务进程是在后台运行的。可以通过命令ps -aux来获取,比如在ubuntu终端中ps -aux | grep udev

root       328  0.0  0.0  52220   852 ?        Ss    2月23   0:00 /lib/systemd/systemd-udevd --daemon

所以只要有设备插入或删除,守护进程udev就会管理它。

也就是说使用了udev,所有的设备都能在/dev/目录下找到对应的设备文件。

1.2 使用udev的好处

动态管理:当设备添加 / 删除时,udev 的守护进程侦听来自内核的 uevent,以此添加或者删除 /dev下的设备文件,所以 udev 只为已经连接的设备产生设备文件,而不会在 /dev下产生大量虚无的设备文件。

自定义命名规则:通过 Linux 默认的规则文件,udev 在 /dev/ 里为所有的设备定义了内核设备名称,比如 /dev/sda、/dev/hda、/dev/fd等等。由于 udev 是在用户空间 (user space) 运行,Linux 用户可以通过自定义的规则文件,灵活地产生标识性强的设备文件名,比如 /dev/boot_disk、/dev/root_disk、/dev/color_printer等等。

设定设备的权限和所有者 / 组:udev 可以按一定的条件来设置设备文件的权限和设备文件所有者 / 组

1.3 udev工作流程

udev规则以及编写

2. 如何配置和使用udev

2.1 udev的配置文件(/etc/udev/udev.conf)

[root@HOST_RHEL4 dev]# cat /etc/udev/udev.conf
# udev.conf
# The main config file for udev
#
# This file can be used to override some of udev's default values
# for where it looks for files, and where it places device nodes.
#
# WARNING: changing any value, can cause serious system breakage!
# # udev_root - where in the filesystem to place the device nodes
udev_root="/dev/" # udev_db - The name and location of the udev database.
udev_db="/dev/.udev.tdb" # udev_rules - The name and location of the udev rules file
udev_rules="/etc/udev/rules.d/" # udev_permissions - The name and location of the udev permission file
udev_permissions="/etc/udev/permissions.d/" # default_mode - set the default mode for all nodes that have no
# explicit match in the permissions file
default_mode="" # default_owner - set the default owner for all nodes that have no
# explicit match in the permissions file
default_owner="root" # default_group - set the default group for all nodes that have no
# explicit match in the permissions file
default_group="root" # udev_log - set to "yes" if you want logging, else "no"
udev_log="no"

Linux 用户可以通过该文件设置以下参数:

udev_root:udev 产生的设备所存放的目录,默认值是 /dev/。建议不要修改该参数,因为很多应用程序默认会从该目录调用设备文件。
udev_db:udev 信息存放的数据库或者所在目录,默认值是 /dev/.udev.tdb
udev_rules:udev 规则文件的名字或者所在目录,默认值是 /etc/udev/rules.d/或/lib/udev/rules.d
udev_permissions:udev 权限文件的名字或者所在目录,默认值是 /etc/udev/permissions.d/。
default_mode/ default_owner/ default_group:如果设备文件的权限没有在权限文件里指定,就使用该参数作为默认权限,默认值分别是:0600/root/root。
udev_log:是否需要 syslog记录 udev 日志的开关,默认值是 no。syslog记录日志的级别,默认值是 err。如果改为 info 或者 debug 的话,会有冗长的 udev 日志被记录下来。

其实,在我们的终端上,udev配置文件没有那么复杂,因为大部分都是默认值,所以不用配置,看我ubuntu下的udev配置文件:

$cat /etc/udev/udev.conf 
# see udev() for details
#
# udevd is started in the initramfs, so when this file is modified the
# initramfs should be rebuilt. #udev_log="info"

该文件都是注释,说明都用默认值。

2.2 udev的规则和规则文件

规则文件是 udev 里最重要的部分,默认是存放在 /etc/udev/rules.d/下。所有的规则文件必须以“.rules”为后缀名。

规则文件里的规则有一系列的键/值对组成,键/值对之间用逗号(,)分割。每一个键或者是用户匹配键,或者是一个赋值键。匹配键确定规则是否被应用,而赋 值键表示分配某值给该键。这些值将影响udev创建的设备文件。赋值键可以处理一个多值列表。匹配键和赋值键操作符解释见下表:

2.2.1 udev 键/值对操作符

操作符   匹配或赋值   解释
----------------------------------------
==     匹配      相等比较
!=      匹配        不等比较
=     赋值      分配一个特定的值给该键,他可以覆盖之前的赋值。
+=     赋值             追加特定的值给已经存在的键
:=           赋值             分配一个特定的值给该键,后面的规则不可能覆盖它。

2.2.2 udev 规则的匹配键

ACTION: 事件 (uevent) 的行为,例如:add( 添加设备 )、remove( 删除设备 )。

KERNEL: 内核设备名称,例如:sda, cdrom。

DEVPATH:设备的 devpath 路径。

SUBSYSTEM: 设备的子系统名称,例如:sda 的子系统为 block。

BUS: 设备在 devpath 里的总线名称,例如:usb。

DRIVER: 设备在 devpath 里的设备驱动名称,例如:ide-cdrom。

ID: 设备在 devpath 里的识别号。

SYSFS{filename}: 设备的 devpath 路径下,设备的属性文件“filename”里的内容。

例如:SYSFS{model}==“ST936701SS”表示:如果设备的型号为 ST936701SS,则该设备匹配该 匹配键。

在一条规则中,可以设定最多五条 SYSFS 的 匹配键。

ENV{key}: 环境变量。在一条规则中,可以设定最多五条环境变量的 匹配键。

PROGRAM:调用外部命令。

RESULT: 外部命令 PROGRAM 的返回结果。例如:

PROGRAM=="/lib/udev/scsi_id -g -s $devpath", RESULT=="35000c50000a7ef67"

调用外部命令 /lib/udev/scsi_id查询设备的 SCSI ID,如果返回结果为 35000c50000a7ef67,则该设备匹配该 匹配键。

2.2.3 udev 的重要赋值键

NAME:在 /dev下产生的设备文件名。只有第一次对某个设备的 NAME 的赋值行为生效,之后匹配的规则再对该设备的 NAME 赋值行为将被忽略。如果没有任何规则对设备的 NAME 赋值,udev 将使用内核设备名称来产生设备文件。

SYMLINK:为 /dev/下的设备文件产生符号链接。由于 udev 只能为某个设备产生一个设备文件,所以为了不覆盖系统默认的 udev 规则所产生的文件,推荐使用符号链接。

OWNER, GROUP, MODE:为设备设定权限。

ENV{key}:导入一个环境变量。

2.2.4 udev 的值和可调用的替换操作符

在键值对中的键和操作符都介绍完了,最后是值 (value)。Linux 用户可以随意地定制 udev 规则文件的值。例如:my_root_disk, my_printer。同时也可以引用下面的替换操作符:

$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:临时设备名。

%%:符号 % 本身。

$$:符号 $ 本身。

3. 如何编写udev规则

其实,在安装udev时,会生成一系列的udev规则文件放在/etc/udev/rules.d/或/lib/udev/rules.d/目录下,比如/lib/udev/rules.d/50-udev-default.rules等默认的udev规则文件。

我们只需添加自己想改的udev规则文件让其做我们想做的事,比如当U盘插入时,我要自动挂载它(或者其他什么动作),其实安装了udev,当U盘插入时,它有相应的.rules规则文件去解析它(即udev会动态管理它),在/dev/目录下产生相应的设备文件,比如/dev/sda4.但现在我需要在U盘插入时,我自动挂载它(比如挂载到/mnt/udisk/目录下),那么我就可以编写自己的udev规则文件automount.rules。该文件如下:

# There are a number of modifiers that are allowed to be used in some
# of the different fields. They provide the following subsitutions:
#
# %n the "kernel number" of the device.
# For example, 'sda3' has a "kernel number" of ''
# %e the smallest number for that name which does not matches an existing node
# %k the kernel name for the device
# %M the kernel major number for the device
# %m the kernel minor number for the device
# %b the bus id for the device
# %c the string returned by the PROGRAM
# %s{filename} the content of a sysfs attribute
# %% the '%' char itself
# # Media automounting
SUBSYSTEM=="block", ACTION=="add" RUN+="/etc/udev/rules.d/mount.sh"
SUBSYSTEM=="block", ACTION=="remove" RUN+="/etc/udev/rules.d/mount.sh"
SUBSYSTEM=="block", ACTION=="change", ENV{DISK_MEDIA_CHANGE}=="" RUN+="/etc/udev/rules.d/mount.sh"

说明:当有U盘热插拔时都会去跑/etc/udev/rules.d/mount.sh脚本。其中SUBSYSTEM=="block",U盘sda的子系统就是block。

mount.sh如下:

MOUNT="/bin/mount"
PMOUNT="/usr/bin/pmount"
UMOUNT="/bin/umount"
for line in `grep -v ^# /etc/udev/mount.blacklist`
do
name="`basename "$DEVNAME"`"
if [ ` expr match "$DEVNAME" "$line" ` -gt ] || [ ` expr match "$name" "$line" ` -gt ]
then
logger "udev/mount.sh" "[$DEVNAME] is blacklisted, ignoring"
exit
fi
done automount() {
name="`basename "$DEVNAME"`" if [[ $name =~ sd ]];then
mount_dir=/mnt/udisk
! test -d $mount_dir && mkdir -p $mount_dir
elif [[ $name =~ mmcblk ]];then
mount_dir=/mnt/sdisk
! test -d $mount_dir && mkdir -p $mount_dir
fi # Silent util-linux's version of mounting auto
if [ "x`readlink $MOUNT`" = "x/bin/mount.util-linux" ] ;
then
MOUNT="$MOUNT -o silent"
fi # If filesystem type is vfat, change the ownership group to 'disk', and
# grant it with w/r/x permissions.
case $ID_FS_TYPE in
vfat|fat)
MOUNT="$MOUNT -o umask=007,gid=`awk -F':' '/^disk/{print $3}' /etc/group`"
;;
# TODO
*)
;;
esac if ! $MOUNT -t auto -o iocharset=cp936 $DEVNAME $mount_dir
then
logger "mount.sh/automount" "$MOUNT -t auto $DEVNAME $mount_dir failed!"
rm_dir "/run/media/$name"
else
logger "mount.sh/automount" "Auto-mount of [ $mount_dir] successful"
touch "/tmp/.automount-$name"
killall -USR1 adas.exe
fi
} rm_dir() {
# We do not want to rm -r populated directories
if test "`find "$" | wc -l | tr -d " "`" -lt -a -d "$1"
then
! test -z "$1" && rm -r "$1"
killall -USR1 adas.exe
else
logger "mount.sh/automount" "Not removing non-empty directory [$1]"
fi
} # No ID_FS_TYPE for cdrom device, yet it should be mounted
name="`basename "$DEVNAME"`"
[ -e /sys/block/$name/device/media ] && media_type=`cat /sys/block/$name/device/media` if [ "$ACTION" = "add" ] && [ -n "$DEVNAME" ] && [ -n "$ID_FS_TYPE" -o "$media_type" = "cdrom" ]; then
if [ -x "$PMOUNT" ]; then
$PMOUNT $DEVNAME > /dev/null
elif [ -x $MOUNT ]; then
$MOUNT $DEVNAME > /dev/null
fi # If the device isn't mounted at this point, it isn't
# configured in fstab (note the root filesystem can show up as
# /dev/root in /proc/mounts, so check the device number too)
if expr $MAJOR "*" + $MINOR != `stat -c %d /`; then
grep -q "^$DEVNAME " /proc/mounts || automount
fi
fi if [ "$ACTION" = "remove" ] || [ "$ACTION" = "change" ] && [ -x "$UMOUNT" ] && [ -n "$DEVNAME" ]; then
for mnt in `cat /proc/mounts | grep "$DEVNAME" | cut -f -d " " `
do
$UMOUNT $mnt
done # Remove empty directories from auto-mounter
name="`basename "$DEVNAME"`"
if [[ $name =~ sd ]];then
mount_dir=/mnt/udisk
elif [[ $name =~ mmcblk ]];then
mount_dir=/mnt/sdisk
fi
test -e "/tmp/.automount-$name" && rm_dir $mount_dir
fi

这里只是举一个例子而已。可以编写我们想要的规则文件。

3.1 如何编写规则文件呢?

其实就是两点:匹配键赋值键,只需完善这两点就可以编写我们想要的规则文件

比如:KERNEL=="tty", NAME="%k", GROUP="tty", MODE="0666", OPTIONS="last_rule"

该规则说明:如果有一个设备的内核设备名称为tty(KERNEL=="tty"),那么设置新的权限为0600(MODE="0666"),所在的组是tty(GROUP="tty")。它也设置了一个特别的设备文件名:%K。在这里例子里,%k代表设备的内核名字。那也就意味着内核识别出这些设备是什么名字,就创建什么样的设备文件名。

在这里就是要完善两点,匹配键KERNEL=="tty";赋值键NAME="%k", GROUP="tty", MODE="0666", OPTIONS="last_rule"

其实关键是要如何找到设备的属性呢,即拿什么区匹配,规则所需要的信息如何获取?

可以利用udev的命令:比如udevadm info -a -p $(udevadm info -q path -n /dev/sda4). 其中udevadm info -q path -n /dev/sda4返回sysfs中的设备路径;udevadm info -a -p $(设备路径),这将查询这个设备路径,把结果信息输出来:如下:

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device. looking at device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18/target18:0:0/18:0:0:0/block/sdb/sdb4':
KERNEL=="sdb4"
SUBSYSTEM=="block"
DRIVER==""
ATTR{ro}==""
ATTR{size}==""
ATTR{stat}==" 202 382 1562 212 0 0 0 0 0 164 212"
ATTR{partition}==""
ATTR{start}==""
ATTR{discard_alignment}==""
ATTR{alignment_offset}==""
ATTR{inflight}==" 0 0" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18/target18:0:0/18:0:0:0/block/sdb':
KERNELS=="sdb"
SUBSYSTEMS=="block"
DRIVERS==""
ATTRS{ro}==""
ATTRS{size}==""
ATTRS{stat}==" 295 382 2306 1208 0 0 0 0 0 1156 1204"
ATTRS{range}==""
ATTRS{discard_alignment}==""
ATTRS{events}=="media_change"
ATTRS{ext_range}==""
ATTRS{events_poll_msecs}==""
ATTRS{alignment_offset}==""
ATTRS{inflight}==" 0 0"
ATTRS{removable}==""
ATTRS{capability}==""
ATTRS{events_async}=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18/target18:0:0/18:0:0:0':
KERNELS=="18:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{rev}=="1.00"
ATTRS{type}==""
ATTRS{scsi_level}==""
ATTRS{model}==" "
ATTRS{state}=="running"
ATTRS{queue_type}=="none"
ATTRS{iodone_cnt}=="0x152"
ATTRS{iorequest_cnt}=="0x152"
ATTRS{device_busy}==""
ATTRS{evt_capacity_change_reported}==""
ATTRS{timeout}==""
ATTRS{evt_media_change}==""
ATTRS{max_sectors}==""
ATTRS{ioerr_cnt}=="0x1"
ATTRS{queue_depth}==""
ATTRS{vendor}==" "
ATTRS{evt_soft_threshold_reached}==""
ATTRS{device_blocked}==""
ATTRS{evt_mode_parameter_change_reported}==""
ATTRS{evt_lun_change_reported}==""
ATTRS{evt_inquiry_change_reported}==""
ATTRS{iocounterbits}==""
ATTRS{eh_timeout}=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18/target18:0:0':
KERNELS=="target18:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18':
KERNELS=="host18"
SUBSYSTEMS=="scsi"
DRIVERS=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0':
KERNELS=="1-8:1.0"
SUBSYSTEMS=="usb"
DRIVERS=="usb-storage"
ATTRS{bInterfaceClass}==""
ATTRS{bInterfaceSubClass}==""
ATTRS{bInterfaceProtocol}==""
ATTRS{bNumEndpoints}==""
ATTRS{supports_autosuspend}==""
ATTRS{bAlternateSetting}==""
ATTRS{bInterfaceNumber}=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8':
KERNELS=="1-8"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{bDeviceSubClass}==""
ATTRS{bDeviceProtocol}==""
ATTRS{devpath}==""
ATTRS{idVendor}==""
ATTRS{speed}==""
ATTRS{bNumInterfaces}==""
ATTRS{bConfigurationValue}==""
ATTRS{bMaxPacketSize0}==""
ATTRS{busnum}==""
ATTRS{devnum}==""
ATTRS{configuration}==""
ATTRS{bMaxPower}=="500mA"
ATTRS{authorized}==""
ATTRS{bmAttributes}==""
ATTRS{bNumConfigurations}==""
ATTRS{maxchild}==""
ATTRS{bcdDevice}==""
ATTRS{avoid_reset_quirk}==""
ATTRS{quirks}=="0x0"
ATTRS{version}==" 2.00"
ATTRS{urbnum}==""
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="SKYMEDI"
ATTRS{removable}=="removable"
ATTRS{idProduct}==""
ATTRS{bDeviceClass}==""
ATTRS{product}=="USB Drive" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1':
KERNELS=="usb1"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{bDeviceSubClass}==""
ATTRS{bDeviceProtocol}==""
ATTRS{devpath}==""
ATTRS{idVendor}=="1d6b"
ATTRS{speed}==""
ATTRS{bNumInterfaces}==""
ATTRS{bConfigurationValue}==""
ATTRS{bMaxPacketSize0}==""
ATTRS{authorized_default}==""
ATTRS{busnum}==""
ATTRS{devnum}==""
ATTRS{configuration}==""
ATTRS{bMaxPower}=="0mA"
ATTRS{authorized}==""
ATTRS{bmAttributes}=="e0"
ATTRS{bNumConfigurations}==""
ATTRS{maxchild}==""
ATTRS{bcdDevice}==""
ATTRS{avoid_reset_quirk}==""
ATTRS{quirks}=="0x0"
ATTRS{serial}=="0000:00:14.0"
ATTRS{version}==" 2.00"
ATTRS{urbnum}==""
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="Linux 4.2.0-42-generic xhci-hcd"
ATTRS{removable}=="unknown"
ATTRS{idProduct}==""
ATTRS{bDeviceClass}==""
ATTRS{product}=="xHCI Host Controller" looking at parent device '/devices/pci0000:00/0000:00:14.0':
KERNELS=="0000:00:14.0"
SUBSYSTEMS=="pci"
DRIVERS=="xhci_hcd"
ATTRS{irq}==""
ATTRS{subsystem_vendor}=="0x1028"
ATTRS{broken_parity_status}==""
ATTRS{class}=="0x0c0330"
ATTRS{driver_override}=="(null)"
ATTRS{consistent_dma_mask_bits}==""
ATTRS{dma_mask_bits}==""
ATTRS{local_cpus}=="f"
ATTRS{device}=="0x8c31"
ATTRS{enable}==""
ATTRS{msi_bus}==""
ATTRS{local_cpulist}=="0-3"
ATTRS{vendor}=="0x8086"
ATTRS{subsystem_device}=="0x05a5"
ATTRS{numa_node}=="-1"
ATTRS{d3cold_allowed}=="" looking at parent device '/devices/pci0000:00':
KERNELS=="pci0000:00"
SUBSYSTEMS==""
DRIVERS==""

从中可以得到KERNEL,SUBSYSTEM等信息,这样就可以利用这些属性值去匹配。其实我们可以从那里看到“looking at parent device”是一层往一层打印出该设备的信息。

这样我们就可以KERNEL=="sdb4", SUBSYSTEM=="block"去匹配,比如写udev规则如下:

KERNEL=="sdb4", SUBSYSTEM=="block", RUN+="/etc/udev/rules.d/mount.sh"

4. 字符串替换和匹配

4.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.2 字符串匹配

不仅有字符串精确匹配, udev也允许你使用shell风格的模式匹配. 支持的3种模式为:
* - 匹配任何字符, 匹配0次或多次
? - 匹配任何字符,但只匹配一次.
[] - 匹配任何单个字符, 这些字符在方括号里面指定, 范围是受限的.
这里有一些例子, 注意字符串替换符的使用:
KERNEL=="fd[0-9]*", NAME="floppy/%n", SYMLINK+="%k"
KERNEL=="hiddev*", NAME="usb/%k"
第一条规则匹配所有软盘驱动并确保设备节点放置在/dev/floppy目录下, 也创建一个缺省名字的符号链接. 第二条规则确保hiddev设备节点放在/dev/usb目录下面.

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,这样就可以满足要求(使用外部程序来命名设备)

4.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

6. 编写udev规则实例

USB打印机
我启动我的打印机, 它就被赋予了一个设备节点/dev/lp0. 我对这样的单调的名字不满意并打算使用udevinfo帮我写一个规则来提供一个可选名字:
# udevinfo -a -p $(udevinfo -q path -n /dev/lp0)
looking at device '/class/usb/lp0':
KERNEL=="lp0"
SUBSYSTEM=="usb"
DRIVER==""
ATTR{dev}=="180:0"

looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb1/1-1':
SUBSYSTEMS=="usb"
ATTRS{manufacturer}=="EPSON"
ATTRS{product}=="USB Printer"
ATTRS{serial}=="L72010011070626380"
我的规则变成了这样:
SUBSYSTEM=="usb", ATTRS{serial}=="L72010011070626380", SYMLINK+="epson_680"

7. 难点解析

7.1 ATTR

ATTR{value}  sysfs设备属性值,可以为任意值,用于匹配

可以利用udev的命令:比如udevadm info -a -p $(udevadm info -q path -n /dev/sda4). 其中udevadm info -q path -n /dev/sda4返回sysfs中的设备路径;udevadm info -a -p $(设备路径),这将查询这个设备路径,把结果信息输出来:如下:

主要内容:

  • udev简介
  • 如何配置和使用udev
  • 如何编写udev规则
  • 字符串替换和匹配
  • udev主要作用
  • 编写udev规则实例
  • 难点解析

1. udev简介

1.1 什么是udev?

udev是Linux(linux2.6内核之后)默认的设备管理工具。udev 以守护进程的形式运行,通过侦听内核发出来的 uevent 来管理 /dev目录下的设备文件。

如何理解udev是守护进程呢?即系统内核启动后init进程(比如busybox的init程序、sysinit、Upstart或systemd)根据runlevel运行等级进入某种模式,然后解析开启哪些服务进程。其中udev就是哪些服务进程中的一个,服务进程是在后台运行的。可以通过命令ps -aux来获取,比如在ubuntu终端中ps -aux | grep udev

root       328  0.0  0.0  52220   852 ?        Ss    2月23   0:00 /lib/systemd/systemd-udevd --daemon

所以只要有设备插入或删除,守护进程udev就会管理它。

也就是说使用了udev,所有的设备都能在/dev/目录下找到对应的设备文件。

1.2 使用udev的好处

动态管理:当设备添加 / 删除时,udev 的守护进程侦听来自内核的 uevent,以此添加或者删除 /dev下的设备文件,所以 udev 只为已经连接的设备产生设备文件,而不会在 /dev下产生大量虚无的设备文件。

自定义命名规则:通过 Linux 默认的规则文件,udev 在 /dev/ 里为所有的设备定义了内核设备名称,比如 /dev/sda、/dev/hda、/dev/fd等等。由于 udev 是在用户空间 (user space) 运行,Linux 用户可以通过自定义的规则文件,灵活地产生标识性强的设备文件名,比如 /dev/boot_disk、/dev/root_disk、/dev/color_printer等等。

设定设备的权限和所有者 / 组:udev 可以按一定的条件来设置设备文件的权限和设备文件所有者 / 组

1.3 udev工作流程

udev规则以及编写

2. 如何配置和使用udev

2.1 udev的配置文件(/etc/udev/udev.conf)

[root@HOST_RHEL4 dev]# cat /etc/udev/udev.conf
# udev.conf
# The main config file for udev
#
# This file can be used to override some of udev's default values
# for where it looks for files, and where it places device nodes.
#
# WARNING: changing any value, can cause serious system breakage!
# # udev_root - where in the filesystem to place the device nodes
udev_root="/dev/" # udev_db - The name and location of the udev database.
udev_db="/dev/.udev.tdb" # udev_rules - The name and location of the udev rules file
udev_rules="/etc/udev/rules.d/" # udev_permissions - The name and location of the udev permission file
udev_permissions="/etc/udev/permissions.d/" # default_mode - set the default mode for all nodes that have no
# explicit match in the permissions file
default_mode="0600" # default_owner - set the default owner for all nodes that have no
# explicit match in the permissions file
default_owner="root" # default_group - set the default group for all nodes that have no
# explicit match in the permissions file
default_group="root" # udev_log - set to "yes" if you want logging, else "no"
udev_log="no"

Linux 用户可以通过该文件设置以下参数:

udev_root:udev 产生的设备所存放的目录,默认值是 /dev/。建议不要修改该参数,因为很多应用程序默认会从该目录调用设备文件。
udev_db:udev 信息存放的数据库或者所在目录,默认值是 /dev/.udev.tdb
udev_rules:udev 规则文件的名字或者所在目录,默认值是 /etc/udev/rules.d/或/lib/udev/rules.d
udev_permissions:udev 权限文件的名字或者所在目录,默认值是 /etc/udev/permissions.d/。
default_mode/ default_owner/ default_group:如果设备文件的权限没有在权限文件里指定,就使用该参数作为默认权限,默认值分别是:0600/root/root。
udev_log:是否需要 syslog记录 udev 日志的开关,默认值是 no。syslog记录日志的级别,默认值是 err。如果改为 info 或者 debug 的话,会有冗长的 udev 日志被记录下来。

其实,在我们的终端上,udev配置文件没有那么复杂,因为大部分都是默认值,所以不用配置,看我ubuntu下的udev配置文件:

$cat /etc/udev/udev.conf 
# see udev(7) for details
#
# udevd is started in the initramfs, so when this file is modified the
# initramfs should be rebuilt. #udev_log="info"

该文件都是注释,说明都用默认值。

2.2 udev的规则和规则文件

规则文件是 udev 里最重要的部分,默认是存放在 /etc/udev/rules.d/下。所有的规则文件必须以“.rules”为后缀名。

规则文件里的规则有一系列的键/值对组成,键/值对之间用逗号(,)分割。每一个键或者是用户匹配键,或者是一个赋值键。匹配键确定规则是否被应用,而赋 值键表示分配某值给该键。这些值将影响udev创建的设备文件。赋值键可以处理一个多值列表。匹配键和赋值键操作符解释见下表:

2.2.1 udev 键/值对操作符

操作符   匹配或赋值   解释
----------------------------------------
==     匹配      相等比较
!=      匹配        不等比较
=     赋值      分配一个特定的值给该键,他可以覆盖之前的赋值。
+=     赋值             追加特定的值给已经存在的键
:=           赋值             分配一个特定的值给该键,后面的规则不可能覆盖它。

2.2.2 udev 规则的匹配键

ACTION: 事件 (uevent) 的行为,例如:add( 添加设备 )、remove( 删除设备 )。

KERNEL: 内核设备名称,例如:sda, cdrom。

DEVPATH:设备的 devpath 路径。

SUBSYSTEM: 设备的子系统名称,例如:sda 的子系统为 block。

BUS: 设备在 devpath 里的总线名称,例如:usb。

DRIVER: 设备在 devpath 里的设备驱动名称,例如:ide-cdrom。

ID: 设备在 devpath 里的识别号。

SYSFS{filename}: 设备的 devpath 路径下,设备的属性文件“filename”里的内容。

例如:SYSFS{model}==“ST936701SS”表示:如果设备的型号为 ST936701SS,则该设备匹配该 匹配键。

在一条规则中,可以设定最多五条 SYSFS 的 匹配键。

ENV{key}: 环境变量。在一条规则中,可以设定最多五条环境变量的 匹配键。

PROGRAM:调用外部命令。

RESULT: 外部命令 PROGRAM 的返回结果。例如:

PROGRAM=="/lib/udev/scsi_id -g -s $devpath", RESULT=="35000c50000a7ef67"

调用外部命令 /lib/udev/scsi_id查询设备的 SCSI ID,如果返回结果为 35000c50000a7ef67,则该设备匹配该 匹配键。

2.2.3 udev 的重要赋值键

NAME:在 /dev下产生的设备文件名。只有第一次对某个设备的 NAME 的赋值行为生效,之后匹配的规则再对该设备的 NAME 赋值行为将被忽略。如果没有任何规则对设备的 NAME 赋值,udev 将使用内核设备名称来产生设备文件。

SYMLINK:为 /dev/下的设备文件产生符号链接。由于 udev 只能为某个设备产生一个设备文件,所以为了不覆盖系统默认的 udev 规则所产生的文件,推荐使用符号链接。

OWNER, GROUP, MODE:为设备设定权限。

ENV{key}:导入一个环境变量。

2.2.4 udev 的值和可调用的替换操作符

在键值对中的键和操作符都介绍完了,最后是值 (value)。Linux 用户可以随意地定制 udev 规则文件的值。例如:my_root_disk, my_printer。同时也可以引用下面的替换操作符:

$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:临时设备名。

%%:符号 % 本身。

$$:符号 $ 本身。

3. 如何编写udev规则

其实,在安装udev时,会生成一系列的udev规则文件放在/etc/udev/rules.d/或/lib/udev/rules.d/目录下,比如/lib/udev/rules.d/50-udev-default.rules等默认的udev规则文件。

我们只需添加自己想改的udev规则文件让其做我们想做的事,比如当U盘插入时,我要自动挂载它(或者其他什么动作),其实安装了udev,当U盘插入时,它有相应的.rules规则文件去解析它(即udev会动态管理它),在/dev/目录下产生相应的设备文件,比如/dev/sda4.但现在我需要在U盘插入时,我自动挂载它(比如挂载到/mnt/udisk/目录下),那么我就可以编写自己的udev规则文件automount.rules。该文件如下:

# There are a number of modifiers that are allowed to be used in some
# of the different fields. They provide the following subsitutions:
#
# %n the "kernel number" of the device.
# For example, 'sda3' has a "kernel number" of '3'
# %e the smallest number for that name which does not matches an existing node
# %k the kernel name for the device
# %M the kernel major number for the device
# %m the kernel minor number for the device
# %b the bus id for the device
# %c the string returned by the PROGRAM
# %s{filename} the content of a sysfs attribute
# %% the '%' char itself
# # Media automounting
SUBSYSTEM=="block", ACTION=="add" RUN+="/etc/udev/rules.d/mount.sh"
SUBSYSTEM=="block", ACTION=="remove" RUN+="/etc/udev/rules.d/mount.sh"
SUBSYSTEM=="block", ACTION=="change", ENV{DISK_MEDIA_CHANGE}=="1" RUN+="/etc/udev/rules.d/mount.sh"

说明:当有U盘热插拔时都会去跑/etc/udev/rules.d/mount.sh脚本。其中SUBSYSTEM=="block",U盘sda的子系统就是block。

mount.sh如下:

MOUNT="/bin/mount"
PMOUNT="/usr/bin/pmount"
UMOUNT="/bin/umount"
for line in `grep -v ^# /etc/udev/mount.blacklist`
do
name="`basename "$DEVNAME"`"
if [ ` expr match "$DEVNAME" "$line" ` -gt 0 ] || [ ` expr match "$name" "$line" ` -gt 0 ]
then
logger "udev/mount.sh" "[$DEVNAME] is blacklisted, ignoring"
exit 0
fi
done automount() {
name="`basename "$DEVNAME"`" if [[ $name =~ sd ]];then
mount_dir=/mnt/udisk
! test -d $mount_dir && mkdir -p $mount_dir
elif [[ $name =~ mmcblk ]];then
mount_dir=/mnt/sdisk
! test -d $mount_dir && mkdir -p $mount_dir
fi # Silent util-linux's version of mounting auto
if [ "x`readlink $MOUNT`" = "x/bin/mount.util-linux" ] ;
then
MOUNT="$MOUNT -o silent"
fi # If filesystem type is vfat, change the ownership group to 'disk', and
# grant it with w/r/x permissions.
case $ID_FS_TYPE in
vfat|fat)
MOUNT="$MOUNT -o umask=007,gid=`awk -F':' '/^disk/{print $3}' /etc/group`"
;;
# TODO
*)
;;
esac if ! $MOUNT -t auto -o iocharset=cp936 $DEVNAME $mount_dir
then
logger "mount.sh/automount" "$MOUNT -t auto $DEVNAME $mount_dir failed!"
rm_dir "/run/media/$name"
else
logger "mount.sh/automount" "Auto-mount of [ $mount_dir] successful"
touch "/tmp/.automount-$name"
killall -USR1 adas.exe
fi
} rm_dir() {
# We do not want to rm -r populated directories
if test "`find "$1" | wc -l | tr -d " "`" -lt 2 -a -d "$1"
then
! test -z "$1" && rm -r "$1"
killall -USR1 adas.exe
else
logger "mount.sh/automount" "Not removing non-empty directory [$1]"
fi
} # No ID_FS_TYPE for cdrom device, yet it should be mounted
name="`basename "$DEVNAME"`"
[ -e /sys/block/$name/device/media ] && media_type=`cat /sys/block/$name/device/media` if [ "$ACTION" = "add" ] && [ -n "$DEVNAME" ] && [ -n "$ID_FS_TYPE" -o "$media_type" = "cdrom" ]; then
if [ -x "$PMOUNT" ]; then
$PMOUNT $DEVNAME 2> /dev/null
elif [ -x $MOUNT ]; then
$MOUNT $DEVNAME 2> /dev/null
fi # If the device isn't mounted at this point, it isn't
# configured in fstab (note the root filesystem can show up as
# /dev/root in /proc/mounts, so check the device number too)
if expr $MAJOR "*" 256 + $MINOR != `stat -c %d /`; then
grep -q "^$DEVNAME " /proc/mounts || automount
fi
fi if [ "$ACTION" = "remove" ] || [ "$ACTION" = "change" ] && [ -x "$UMOUNT" ] && [ -n "$DEVNAME" ]; then
for mnt in `cat /proc/mounts | grep "$DEVNAME" | cut -f 2 -d " " `
do
$UMOUNT $mnt
done # Remove empty directories from auto-mounter
name="`basename "$DEVNAME"`"
if [[ $name =~ sd ]];then
mount_dir=/mnt/udisk
elif [[ $name =~ mmcblk ]];then
mount_dir=/mnt/sdisk
fi
test -e "/tmp/.automount-$name" && rm_dir $mount_dir
fi

这里只是举一个例子而已。可以编写我们想要的规则文件。

3.1 如何编写规则文件呢?

其实就是两点:匹配键赋值键,只需完善这两点就可以编写我们想要的规则文件

比如:KERNEL=="tty", NAME="%k", GROUP="tty", MODE="0666", OPTIONS="last_rule"

该规则说明:如果有一个设备的内核设备名称为tty(KERNEL=="tty"),那么设置新的权限为0600(MODE="0666"),所在的组是tty(GROUP="tty")。它也设置了一个特别的设备文件名:%K。在这里例子里,%k代表设备的内核名字。那也就意味着内核识别出这些设备是什么名字,就创建什么样的设备文件名。

在这里就是要完善两点,匹配键KERNEL=="tty";赋值键NAME="%k", GROUP="tty", MODE="0666", OPTIONS="last_rule"

其实关键是要如何找到设备的属性呢,即拿什么区匹配,规则所需要的信息如何获取?

可以利用udev的命令:比如udevadm info -a -p $(udevadm info -q path -n /dev/sda4). 其中udevadm info -q path -n /dev/sda4返回sysfs中的设备路径;udevadm info -a -p $(设备路径),这将查询这个设备路径,把结果信息输出来:如下:

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device. looking at device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18/target18:0:0/18:0:0:0/block/sdb/sdb4':
KERNEL=="sdb4"
SUBSYSTEM=="block"
DRIVER==""
ATTR{ro}=="0"
ATTR{size}=="15177600"
ATTR{stat}==" 202 382 1562 212 0 0 0 0 0 164 212"
ATTR{partition}=="4"
ATTR{start}=="14880"
ATTR{discard_alignment}=="0"
ATTR{alignment_offset}=="0"
ATTR{inflight}==" 0 0" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18/target18:0:0/18:0:0:0/block/sdb':
KERNELS=="sdb"
SUBSYSTEMS=="block"
DRIVERS==""
ATTRS{ro}=="0"
ATTRS{size}=="15204352"
ATTRS{stat}==" 295 382 2306 1208 0 0 0 0 0 1156 1204"
ATTRS{range}=="16"
ATTRS{discard_alignment}=="0"
ATTRS{events}=="media_change"
ATTRS{ext_range}=="256"
ATTRS{events_poll_msecs}=="2000"
ATTRS{alignment_offset}=="0"
ATTRS{inflight}==" 0 0"
ATTRS{removable}=="1"
ATTRS{capability}=="51"
ATTRS{events_async}=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18/target18:0:0/18:0:0:0':
KERNELS=="18:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{rev}=="1.00"
ATTRS{type}=="0"
ATTRS{scsi_level}=="3"
ATTRS{model}==" "
ATTRS{state}=="running"
ATTRS{queue_type}=="none"
ATTRS{iodone_cnt}=="0x152"
ATTRS{iorequest_cnt}=="0x152"
ATTRS{device_busy}=="0"
ATTRS{evt_capacity_change_reported}=="0"
ATTRS{timeout}=="30"
ATTRS{evt_media_change}=="0"
ATTRS{max_sectors}=="240"
ATTRS{ioerr_cnt}=="0x1"
ATTRS{queue_depth}=="1"
ATTRS{vendor}==" "
ATTRS{evt_soft_threshold_reached}=="0"
ATTRS{device_blocked}=="0"
ATTRS{evt_mode_parameter_change_reported}=="0"
ATTRS{evt_lun_change_reported}=="0"
ATTRS{evt_inquiry_change_reported}=="0"
ATTRS{iocounterbits}=="32"
ATTRS{eh_timeout}=="10" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18/target18:0:0':
KERNELS=="target18:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18':
KERNELS=="host18"
SUBSYSTEMS=="scsi"
DRIVERS=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0':
KERNELS=="1-8:1.0"
SUBSYSTEMS=="usb"
DRIVERS=="usb-storage"
ATTRS{bInterfaceClass}=="08"
ATTRS{bInterfaceSubClass}=="06"
ATTRS{bInterfaceProtocol}=="50"
ATTRS{bNumEndpoints}=="02"
ATTRS{supports_autosuspend}=="1"
ATTRS{bAlternateSetting}==" 0"
ATTRS{bInterfaceNumber}=="00" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8':
KERNELS=="1-8"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bDeviceProtocol}=="00"
ATTRS{devpath}=="8"
ATTRS{idVendor}=="1516"
ATTRS{speed}=="480"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{busnum}=="1"
ATTRS{devnum}=="17"
ATTRS{configuration}==""
ATTRS{bMaxPower}=="500mA"
ATTRS{authorized}=="1"
ATTRS{bmAttributes}=="80"
ATTRS{bNumConfigurations}=="1"
ATTRS{maxchild}=="0"
ATTRS{bcdDevice}=="0100"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{quirks}=="0x0"
ATTRS{version}==" 2.00"
ATTRS{urbnum}=="1001"
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="SKYMEDI"
ATTRS{removable}=="removable"
ATTRS{idProduct}=="1226"
ATTRS{bDeviceClass}=="00"
ATTRS{product}=="USB Drive" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1':
KERNELS=="usb1"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bDeviceProtocol}=="01"
ATTRS{devpath}=="0"
ATTRS{idVendor}=="1d6b"
ATTRS{speed}=="480"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{authorized_default}=="1"
ATTRS{busnum}=="1"
ATTRS{devnum}=="1"
ATTRS{configuration}==""
ATTRS{bMaxPower}=="0mA"
ATTRS{authorized}=="1"
ATTRS{bmAttributes}=="e0"
ATTRS{bNumConfigurations}=="1"
ATTRS{maxchild}=="15"
ATTRS{bcdDevice}=="0402"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{quirks}=="0x0"
ATTRS{serial}=="0000:00:14.0"
ATTRS{version}==" 2.00"
ATTRS{urbnum}=="348"
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="Linux 4.2.0-42-generic xhci-hcd"
ATTRS{removable}=="unknown"
ATTRS{idProduct}=="0002"
ATTRS{bDeviceClass}=="09"
ATTRS{product}=="xHCI Host Controller" looking at parent device '/devices/pci0000:00/0000:00:14.0':
KERNELS=="0000:00:14.0"
SUBSYSTEMS=="pci"
DRIVERS=="xhci_hcd"
ATTRS{irq}=="27"
ATTRS{subsystem_vendor}=="0x1028"
ATTRS{broken_parity_status}=="0"
ATTRS{class}=="0x0c0330"
ATTRS{driver_override}=="(null)"
ATTRS{consistent_dma_mask_bits}=="64"
ATTRS{dma_mask_bits}=="64"
ATTRS{local_cpus}=="f"
ATTRS{device}=="0x8c31"
ATTRS{enable}=="1"
ATTRS{msi_bus}=="1"
ATTRS{local_cpulist}=="0-3"
ATTRS{vendor}=="0x8086"
ATTRS{subsystem_device}=="0x05a5"
ATTRS{numa_node}=="-1"
ATTRS{d3cold_allowed}=="1" looking at parent device '/devices/pci0000:00':
KERNELS=="pci0000:00"
SUBSYSTEMS==""
DRIVERS==""

从中可以得到KERNEL,SUBSYSTEM等信息,这样就可以利用这些属性值去匹配。其实我们可以从那里看到“looking at parent device”是一层往一层打印出该设备的信息。

这样我们就可以KERNEL=="sdb4", SUBSYSTEM=="block"去匹配,比如写udev规则如下:

KERNEL=="sdb4", SUBSYSTEM=="block", RUN+="/etc/udev/rules.d/mount.sh"

4. 字符串替换和匹配

4.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.2 字符串匹配

不仅有字符串精确匹配, udev也允许你使用shell风格的模式匹配. 支持的3种模式为:
* - 匹配任何字符, 匹配0次或多次
? - 匹配任何字符,但只匹配一次.
[] - 匹配任何单个字符, 这些字符在方括号里面指定, 范围是受限的.
这里有一些例子, 注意字符串替换符的使用:
KERNEL=="fd[0-9]*", NAME="floppy/%n", SYMLINK+="%k"
KERNEL=="hiddev*", NAME="usb/%k"
第一条规则匹配所有软盘驱动并确保设备节点放置在/dev/floppy目录下, 也创建一个缺省名字的符号链接. 第二条规则确保hiddev设备节点放在/dev/usb目录下面.

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,这样就可以满足要求(使用外部程序来命名设备)

4.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

6. 编写udev规则实例

USB打印机
我启动我的打印机, 它就被赋予了一个设备节点/dev/lp0. 我对这样的单调的名字不满意并打算使用udevinfo帮我写一个规则来提供一个可选名字:
# udevinfo -a -p $(udevinfo -q path -n /dev/lp0)
looking at device '/class/usb/lp0':
KERNEL=="lp0"
SUBSYSTEM=="usb"
DRIVER==""
ATTR{dev}=="180:0"

looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb1/1-1':
SUBSYSTEMS=="usb"
ATTRS{manufacturer}=="EPSON"
ATTRS{product}=="USB Printer"
ATTRS{serial}=="L72010011070626380"
我的规则变成了这样:
SUBSYSTEM=="usb", ATTRS{serial}=="L72010011070626380", SYMLINK+="epson_680"

7. 难点解析

7.1 ATTR

ATTR{value}  sysfs设备属性值,可以为任意值,用于匹配

可以利用udev的命令:比如udevadm info -a -p $(udevadm info -q path -n /dev/sda4). 其中udevadm info -q path -n /dev/sda4返回sysfs中的设备路径;udevadm info -a -p $(设备路径),这将查询这个设备路径,把结果信息输出来:如下:

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device. looking at device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18/target18:0:0/18:0:0:0/block/sdb/sdb4':
KERNEL=="sdb4"
SUBSYSTEM=="block"
DRIVER==""
ATTR{ro}==""
ATTR{size}==""
ATTR{stat}==" 202 382 1562 212 0 0 0 0 0 164 212"
ATTR{partition}==""
ATTR{start}==""
ATTR{discard_alignment}==""
ATTR{alignment_offset}==""
ATTR{inflight}==" 0 0" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18/target18:0:0/18:0:0:0/block/sdb':
KERNELS=="sdb"
SUBSYSTEMS=="block"
DRIVERS==""
ATTRS{ro}==""
ATTRS{size}==""
ATTRS{stat}==" 295 382 2306 1208 0 0 0 0 0 1156 1204"
ATTRS{range}==""
ATTRS{discard_alignment}==""
ATTRS{events}=="media_change"
ATTRS{ext_range}==""
ATTRS{events_poll_msecs}==""
ATTRS{alignment_offset}==""
ATTRS{inflight}==" 0 0"
ATTRS{removable}==""
ATTRS{capability}==""
ATTRS{events_async}=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18/target18:0:0/18:0:0:0':
KERNELS=="18:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{rev}=="1.00"
ATTRS{type}==""
ATTRS{scsi_level}==""
ATTRS{model}==" "
ATTRS{state}=="running"
ATTRS{queue_type}=="none"
ATTRS{iodone_cnt}=="0x152"
ATTRS{iorequest_cnt}=="0x152"
ATTRS{device_busy}==""
ATTRS{evt_capacity_change_reported}==""
ATTRS{timeout}==""
ATTRS{evt_media_change}==""
ATTRS{max_sectors}==""
ATTRS{ioerr_cnt}=="0x1"
ATTRS{queue_depth}==""
ATTRS{vendor}==" "
ATTRS{evt_soft_threshold_reached}==""
ATTRS{device_blocked}==""
ATTRS{evt_mode_parameter_change_reported}==""
ATTRS{evt_lun_change_reported}==""
ATTRS{evt_inquiry_change_reported}==""
ATTRS{iocounterbits}==""
ATTRS{eh_timeout}=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18/target18:0:0':
KERNELS=="target18:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0/host18':
KERNELS=="host18"
SUBSYSTEMS=="scsi"
DRIVERS=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8/1-8:1.0':
KERNELS=="1-8:1.0"
SUBSYSTEMS=="usb"
DRIVERS=="usb-storage"
ATTRS{bInterfaceClass}==""
ATTRS{bInterfaceSubClass}==""
ATTRS{bInterfaceProtocol}==""
ATTRS{bNumEndpoints}==""
ATTRS{supports_autosuspend}==""
ATTRS{bAlternateSetting}==""
ATTRS{bInterfaceNumber}=="" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1/1-8':
KERNELS=="1-8"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{bDeviceSubClass}==""
ATTRS{bDeviceProtocol}==""
ATTRS{devpath}==""
ATTRS{idVendor}==""
ATTRS{speed}==""
ATTRS{bNumInterfaces}==""
ATTRS{bConfigurationValue}==""
ATTRS{bMaxPacketSize0}==""
ATTRS{busnum}==""
ATTRS{devnum}==""
ATTRS{configuration}==""
ATTRS{bMaxPower}=="500mA"
ATTRS{authorized}==""
ATTRS{bmAttributes}==""
ATTRS{bNumConfigurations}==""
ATTRS{maxchild}==""
ATTRS{bcdDevice}==""
ATTRS{avoid_reset_quirk}==""
ATTRS{quirks}=="0x0"
ATTRS{version}==" 2.00"
ATTRS{urbnum}==""
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="SKYMEDI"
ATTRS{removable}=="removable"
ATTRS{idProduct}==""
ATTRS{bDeviceClass}==""
ATTRS{product}=="USB Drive" looking at parent device '/devices/pci0000:00/0000:00:14.0/usb1':
KERNELS=="usb1"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{bDeviceSubClass}==""
ATTRS{bDeviceProtocol}==""
ATTRS{devpath}==""
ATTRS{idVendor}=="1d6b"
ATTRS{speed}==""
ATTRS{bNumInterfaces}==""
ATTRS{bConfigurationValue}==""
ATTRS{bMaxPacketSize0}==""
ATTRS{authorized_default}==""
ATTRS{busnum}==""
ATTRS{devnum}==""
ATTRS{configuration}==""
ATTRS{bMaxPower}=="0mA"
ATTRS{authorized}==""
ATTRS{bmAttributes}=="e0"
ATTRS{bNumConfigurations}==""
ATTRS{maxchild}==""
ATTRS{bcdDevice}==""
ATTRS{avoid_reset_quirk}==""
ATTRS{quirks}=="0x0"
ATTRS{serial}=="0000:00:14.0"
ATTRS{version}==" 2.00"
ATTRS{urbnum}==""
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="Linux 4.2.0-42-generic xhci-hcd"
ATTRS{removable}=="unknown"
ATTRS{idProduct}==""
ATTRS{bDeviceClass}==""
ATTRS{product}=="xHCI Host Controller" looking at parent device '/devices/pci0000:00/0000:00:14.0':
KERNELS=="0000:00:14.0"
SUBSYSTEMS=="pci"
DRIVERS=="xhci_hcd"
ATTRS{irq}==""
ATTRS{subsystem_vendor}=="0x1028"
ATTRS{broken_parity_status}==""
ATTRS{class}=="0x0c0330"
ATTRS{driver_override}=="(null)"
ATTRS{consistent_dma_mask_bits}==""
ATTRS{dma_mask_bits}==""
ATTRS{local_cpus}=="f"
ATTRS{device}=="0x8c31"
ATTRS{enable}==""
ATTRS{msi_bus}==""
ATTRS{local_cpulist}=="0-3"
ATTRS{vendor}=="0x8086"
ATTRS{subsystem_device}=="0x05a5"
ATTRS{numa_node}=="-1"
ATTRS{d3cold_allowed}=="" looking at parent device '/devices/pci0000:00':
KERNELS=="pci0000:00"
SUBSYSTEMS==""
DRIVERS==""

查找上面信息,就可以利用ATTR任何值来匹配,比如ATTR{size}=="15177600", ATTR{start}=="14880"等等来匹配都可以

7.2 ENV

ENV{ key}  环境变量,可以表示任意

用于赋值和匹配都可以

例子如下:

先赋值一个ENV,然后再用它来匹配

赋值一个ENV:

KERNEL=="sda4", ENV{test_value}="value", SYMLINK+="udisk4"

这样就赋值了一个ENV{test_value}为value,那么可以在别的设备匹配时引用那个ENV进行匹配

匹配ENV:

SUBSYSTEM=="block", ENV{test_value}=="value", NAME="hda"

匹配ENV{test_value},并且匹配成功,所以会进行命名

若SUBSYSTEM=="block", ENV{test_value}=="test", NAME="hda"

匹配ENV{test_value},但匹配失败,所以上面这条规则不执行