主要內容:
- 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}=="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 $(設備路徑),這將查詢這個設備路徑,把結果信息輸出來:如下:
主要內容:
- 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}=="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}=="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==""
查找上面信息,就可以利用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},但匹配失敗,所以上面這條規則不執行