udev规则以及编写

Posted 大海中的一粒沙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了udev规则以及编写相关的知识,希望对你有一定的参考价值。

主要内容:

  • 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工作流程

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}==为啥我的 udev 规则在运行的 docker 容器中不起作用?

为外部显示器添加 udev 规则

Cluster基础:配置iSCSI服务编写udev规则配置并访问NFS共享部署Multipath多路径环境

udev详解udev规则书写

我们可以在活动 xml 中编写 UI 以及在片段 xm 中编写 UI 吗?

具有少量父设备属性的 udev 规则