编写本文的目的:
看完本章,朋友们可以进行随心所欲的运用udev,编写udev规则,更好的管理Linux设备。例如,把硬盘sda根据盘的SN码生成一个软连接,使得SN码当做sda一样使用。
在文章结束后,既可以有方案可以实现。
udev是什么?
udev 是 Linux2.6 内核里的一个功能,它替代了原来的 devfs,成为当前 Linux 默认的设备管理工具。udev 以守护进程的形式运行,通过侦听内核发出来的 uevent 来管理 /dev目录下的设备文件。不像之前的设备管理工具,udev 在用户空间 (user space) 运行,而不在内核空间 (kernel space) 运行。
devfs 概述:
linux下有专门的文件系统用来对设备进行管理,devfs和sysfs就是其中两种。在2.6内核以前一直使用的是devfs,devfs挂载于/dev目录下,提供了一种类似于文件的方法来管理位于/dev目录下的所有设备,我们知道/dev目录下的每一个文件都对应的是一个设备,至于当前该设备存在与否先且不论,而且这些特殊文件是位于根文件系统上的,在制作文件系统的时候我们就已经建立了这些设备文件,因此通过操作这些特殊文件,可以实现与内核进行交互。但是devfs文件系统有一些缺点,例如:不确定的设备映射,有时一个设备映射的设备文件可能不同,例如我的U盘可能对应sda有可能对应sdb;没有足够的主/辅设备号,当设备过多的时候,显然这会成为一个问题;/dev目录下文件太多而且不能表示当前系统上的实际设备;命名不够灵活,不能任意指定等等。
sysfs 概述:
正因为上述这些问题的存在,在linux2.6内核以后,引入了一个新的文件系统sysfs,它挂载于/sys目录下,跟devfs一样它也是一个虚拟文件系统,也是用来对系统的设备进行管理的,它把实际连接到系统上的设备和总线组织成一个分级的文件,用户空间的程序同样可以利用这些信息以实现和内核的交互,该文件系统是当前系统上实际设备树的一个直观反应,它是通过kobject子系统来建立这个信息的,当一个kobject被创建的时候,对应的文件和目录也就被创建了,位于/sys下的相关目录下,既然每个设备在sysfs中都有唯一对应的目录,那么也就可以被用户空间读写了。用户空间的工具udev就是利用了sysfs提供的信息来实现所有devfs的功能的,但不同的是udev运行在用户空间中,而devfs却运行在内核空间,而且udev不存在devfs那些先天的缺陷。很显然,sysfs将是未来发展的方向。
udev工作流程图:
udev规则文件:
规则文件,在udev中至关重要,这些规则文件,默认是放在/etc/udev/rules.d/下,都是以 *.rules命名格式,例如10-dm.rules,60-persistent-storage.rules,执行规则文件,是根据开头的数字大小,同数字,根据字母顺序执行,且后面的规则文件会覆盖前面的规则;('NAME'动作除外,因为'NAME'只能操作一次,后续NAME操作无用,后面会描述。) 先举一个例子:
ACTION=="add", SUBSYSTEM=="net", DRIVERS=="?*", ATTR{type}=="1", PROGRAM="/lib/udev/rename_device", RESULT=="?*", NAME="$result"
该条例子,就是重命名网卡,在centos 7中,添加了这条网卡重命名的规则,把eth0,这种网卡名,通过程序rename_device进行修改名称,返回结果作为新名称。
编写udev规则文件:
要编写规则文件,要先了解规则文件的一些基础语法。上个例子中,也有一些描述,除了以“#”开头的行(注释),所有的非空行都被视为一条规则,但是一条规则不能扩展到多行。规则都是由多个 键值对(key-value pairs)组成,并由逗号隔开,键值对可以分为 条件匹配键值对( 以下简称“匹配键 ”) 和 赋值键值对( 以下简称“赋值键 ”),一条规则可以有多条匹配键和多条赋值键。匹配键是匹配一个设备属性的所有条件,当一个设备的属性匹配了该规则里所有的匹配键,就认为这条规则生效,然后按照赋值键的内容,执行该规则的赋值。
匹配键和赋值键操作符解释见下表:
操作符 匹配或赋值 解释
----------------------------------------
== 匹配 相等比较
!= 匹配 不等比较
= 赋值 分配一个特定的值给该键,他可以覆盖之前的赋值.
+= 赋值 追加特定的值给已经存在的键
:= 赋值 分配一个特定的值给该键,后面的规则不可能覆盖它.
键 含义
----------------------------------------
ACTION 事件 (uevent) 的行为,例如:add( 添加设备 )、remove( 删除设备 ).
KERNEL 在内核里看到的设备名字,比如sd*表示任意SCSI磁盘设备
DEVPATH 内核设备录进,比如/devices/*
SUBSYSTEM 子系统名字,例如:sda 的子系统为 block.
BUS 总线的名字,比如IDE,USB
DRIVER 设备驱动的名字,比如ide-cdrom
ID 独立于内核名字的设备名字
SYSFS{ value} sysfs属性值,他可以表示任意
ENV{ key} 环境变量,可以表示任意
PROGRAM 可执行的外部程序,如果程序返回0值,该键则认为为真(true)
RESULT 上一个PROGRAM调用返回的标准输出.
NAME 根据这个规则创建的设备文件的文件名.
(注意:仅仅第一行的NAME描述是有效的,后面的均忽略.如果你想使用使用两个以上的名字来访问一个设备的话,可以考虑SYMLINK键.)
SYMLINK 为 /dev/下的设备文件产生符号链接.由于 udev 只能为某个设备产生一个设备文件,
(所以为了不覆盖系统默认的 udev 规则所产生的文件,推荐使用符号链接.)
OWNER 设备文件的属组
GROUP 设备文件所在的组.
MODE 设备文件的权限,采用8进制
RUN 为设备而执行的程序列表
LABEL 在配置文件里为内部控制而采用的名字标签(下下面的GOTO服务)
GOTO 跳到匹配的规则(通过LABEL来标识),有点类似程序语言中的GOTO
IMPORT{ type} 导入一个文件或者一个程序执行后而生成的规则集到当前文件
WAIT_FOR_SYSFS 等待一个特定的设备文件的创建.主要是用作时序和依赖问题.
PTIONS 特定的选项:
last_rule 对这类设备终端规则执行;
ignore_device 忽略当前规则;
ignore_remove 忽略接下来的并移走请求.
all_partitions 为所有的磁盘分区创建设备文件.
在键值对中的键和操作符都介绍完了,最后是值 (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 的内容.其实就是设备的属性值.
$env{key}, %E{key}: 一个环境变量的值.
$major, %M: 设备的 major 号.
$minor, %m: 设备的 minor 号.
$result, %c: PROGRAM 返回的结果
$parent, %P: 父设备的设备文件名.
$root, %r: udev_root的值,默认是 /dev/.
$tempnode, %N: 临时设备名.
%%: 符号 % 本身.
$$: 符号 $ 本身.
现在举刚开始的时候的,连接硬盘的SN的规则文件,如下,'ID_BUS'如何知道是'ata',还是'scsi' ?为何 sata盘的serial是"ID_SERIAL_SHORT", 而sas盘的serial是"ID_SCSI_SERIAL"那?下面会介绍三步骤,来实现这些信息如何获取。
KERNEL=="sd*", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-serial/$env{ID_SERIAL_SHORT}"
KERNEL=="sd*", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="scsi", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-serial/$env{ID_SCSI_SERIAL}"
[root/]#udevadm info -a -p /sys/block/sdb
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:01.0/0000:01:00.0/host0/port-0:0/expander-0:0/port-0:0:0/end_device-0:0:0/target0:0:0/0:0:0:0/block/sdb':
KERNEL=="sdb"
SUBSYSTEM=="block"
DRIVER==""
ATTR{ro}=="0"
ATTR{size}=="3907029168"
ATTR{stat}==" 5871 14262 161060 5586 7604 947711 7642102 5248132 0 41792 5253712"
ATTR{range}=="16"
ATTR{discard_alignment}=="0"
ATTR{events}==""
ATTR{ext_range}=="256"
ATTR{events_poll_msecs}=="-1"
ATTR{alignment_offset}=="0"
ATTR{inflight}==" 0 0"
ATTR{removable}=="0"
ATTR{capability}=="50"
ATTR{events_async}==""
looking at parent device '/devices/pci0000:00/0000:00:01.0/0000:01:00.0/host0/port-0:0/expander-0:0/port-0:0:0/end_device-0:0:0/target0:0:0/0:0:0:0':
KERNELS=="0:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{rev}=="1D01"
ATTRS{type}=="0"
ATTRS{scsi_level}=="7"
ATTRS{model}=="WDC WD2003FYYS-0"
ATTRS{state}=="running"
ATTRS{queue_type}=="none"
ATTRS{sas_address}=="0x500605b030013781"
ATTRS{iodone_cnt}=="0x4a58"
ATTRS{iorequest_cnt}=="0x4a58"
ATTRS{queue_ramp_up_period}=="120000"
ATTRS{evt_capacity_change_reported}=="0"
ATTRS{timeout}=="30"
ATTRS{evt_media_change}=="0"
ATTRS{ioerr_cnt}=="0x27e"
ATTRS{queue_depth}=="32"
ATTRS{vendor}=="ATA "
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{dh_state}=="detached"
ATTRS{iocounterbits}=="32"
ATTRS{eh_timeout}=="10"
ATTRS{sas_device_handle}=="0x000b"
SUBSYSTEMS=="scsi"
第二步,ENV的一些参数,进行规则判断:
[root/]#udevadm info -q env -p /sys/block/sdb
DEVLINKS=/dev/disk/by-id/ata-WDC_WD2003FYYS-02W0B0_WD-WMAY00564609 /dev/disk/by-id/wwn-0x50014ee0ad290c17 /dev/disk/by-path/pci-0000:01:00.0-sas-0x500605b030013781-lun-0 /dev/disk/by-serial/WD-WMAY00564609DEVNAME=/dev/sdbDEVPATH=/devices/pci0000:00/0000:00:01.0/0000:01:00.0/host0/port-0:0/expander-0:0/port-0:0:0/end_device-0:0:0/target0:0:0/0:0:0:0/block/sdbDEVTYPE=diskID_ATA=1ID_ATA_DOWNLOAD_MICROCODE=1ID_ATA_FEATURE_SET_AAM=1ID_ATA_FEATURE_SET_AAM_CURRENT_VALUE=254ID_ATA_FEATURE_SET_AAM_ENABLED=1ID_ATA_FEATURE_SET_AAM_VENDOR_RECOMMENDED_VALUE=128ID_ATA_FEATURE_SET_APM=1ID_ATA_FEATURE_SET_APM_CURRENT_VALUE=128ID_ATA_FEATURE_SET_APM_ENABLED=1ID_ATA_FEATURE_SET_HPA=1ID_ATA_FEATURE_SET_HPA_ENABLED=1ID_ATA_FEATURE_SET_PM=1ID_ATA_FEATURE_SET_PM_ENABLED=1ID_ATA_FEATURE_SET_PUIS=1ID_ATA_FEATURE_SET_PUIS_ENABLED=0ID_ATA_FEATURE_SET_SECURITY=1ID_ATA_FEATURE_SET_SECURITY_ENABLED=0ID_ATA_FEATURE_SET_SECURITY_ENHANCED_ERASE_UNIT_MIN=300ID_ATA_FEATURE_SET_SECURITY_ERASE_UNIT_MIN=300ID_ATA_FEATURE_SET_SMART=1ID_ATA_FEATURE_SET_SMART_ENABLED=1ID_ATA_ROTATION_RATE_RPM=7200ID_ATA_SATA=1ID_ATA_SATA_SIGNAL_RATE_GEN1=1ID_ATA_SATA_SIGNAL_RATE_GEN2=1ID_ATA_WRITE_CACHE=1ID_ATA_WRITE_CACHE_ENABLED=1ID_BUS=ataID_MODEL=WDC_WD2003FYYS-02W0B0ID_MODEL_ENC=WDC\x20WD2003FYYS-02W0B0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20ID_PART_TABLE_TYPE=gptID_PATH=pci-0000:01:00.0-sas-0x500605b030013781-lun-0ID_PATH_TAG=pci-0000_01_00_0-sas-0x500605b030013781-lun-0ID_REVISION=01.01D01ID_SERIAL=WDC_WD2003FYYS-02W0B0_WD-WMAY00564609ID_SERIAL_SHORT=WD-WMAY00564609ID_TYPE=diskID_WWN=0x50014ee0ad290c17ID_WWN_WITH_EXTENSION=0x50014ee0ad290c17MAJOR=8MINOR=16SUBSYSTEM=blockTAGS=:systemd:USEC_INITIALIZED=573368
第三步,一些SYSFS{xxx}的选项如何获取?
例如,我想把硬盘的block size为512/4096的硬盘进行区分,如何操作?
# cat /sys/block/sdb/queue/physical_block_size
512
可以知道,硬盘的block size是在上述路径中,则在规则文件中:只需$sysfs{queue/physical_block_size}既可以取的硬盘的block size
通过上述三步,基本上,一般的规则文件,都可以进行写了。
注意事项,我在实际工作中,发现的一些问题:
1、规则文件的结尾,要有回车符,不然会报错(查看是否有回车符,cat -A xxx 或者 od -cb xxx)
systemd-udevd[19730]: invalid key/value pair in file /etc/udev/rules.d/*** on line 27,starting at character 239 ('')
2、启动顺序,
在zfs导入池中,一直发现,导入硬盘失败(而建raid的盘是用的syslink生成的SN码的盘);查看问题发现:
添加的规则文件,并没有放在最小内核里进行生成,导致了导入的时候,最小内核里没有规则文件,并没有生成连接,当然失败。
查看文件:
lsinitrd -f usr/lib/udev/rules.d/60-persistent-storage.rules
果然里面并没有添加的规则文件!
修改方法:
dracut --fstab