hotplug 次系统核心,以动态方式加载适当的驱动程且 (在 udevd
的协助下)添加对应的设备文件,处理加入与移除设备的作业。当代的硬件与虚拟化,几乎每个对象都是热插拔:从常见的 USB/PCMCIA/IEEE 1394 周边到 SATA 硬盘,以及 CPU 与内存。
核心内的数据库有每个设备的 ID 及其驱动程序。在启动阶段加载此数据库,侦测各接口的周边设备,并在运行中侦测热插入的设备。接收到插入的设备后,送出消息给 udevd
,让其添加对应的条目于 /dev/
内。
在热插拔连接出现之前,很容易给设备赋予一个固定的名字。可以通过设备在总线上的位置简单命名。但是,如果设备能够在总线上来去自如,这就行不通了。典型的例子是数码相机和U 盘,两者都表现为磁盘驱动器。前者可能链接为 /dev/sdb
,后者可能是 /dev/sdc
(假设/dev/sda
代表计算机自身的硬盘)。设备名是不固定的;它取决于设备连接的顺序。
另外,越来越多的驱动使用动态值作为设备的主、次设备号,这样就更不可能给设备赋予静态入口,而重启之后这些基本的特性也随之变化。
udev 正是为了解决该问题而创立的。
当 udev 被核心告知有个新的设备,它参考 /sys/
里对应的条目,搜集该设备的信息,尤其是那些足辨别的独特信息 (网卡的 MAC 地址、某些 USB 设备的序号)。
有了这些信息之后,然后 udev 会查阅/etc/udev/rules.d/
和 /lib/udev/rules.d/
中包含的所有规则。在这个过程中,它会决定如何命名设备,创建什么样的符号连接(赋予设备另外的名字),执行什么命令。查询所有的文件,顺序(除非文件中使用“GOTO”指令)检查所有的规则。这样,有可能一个事件(event)对应多个规则。
规则文件的语法很简单:每行包含选择规则和变量赋值。前者用于需要对那些事件作出相应,后者定义采取何种行动。它们通过逗号分隔,用运算符区分选择规则(使用比较运算符,例如==
or !=
)或赋值指令(使用 =
, +=
或 :=
运算符)。
比较运算符用于如下变量:
内核
:内核赋予设备的名字;
行动
:与事件相对应的行动(“add”当设备被添加,“remove”当设备被移除);
DEVPATH
:设备在 /sys/
记录中的路径;
SUBSYSTEM
:产生请求的内核子系统(有很多这样的子系统,少数的例子是“usb”,“usb”,“net”,“firmware”,等);
ATTR{属性}
:属性 文件的内容在设备的 /sys/$devpath/
文件夹内。可在此找到 MAC 地址及其他辨识用的总线;
KERNELS
, SUBSYSTEMS
和 ATTRS{attributes}
是用来匹配当前设备父设备的选项变量;
PROGRAM
:指明要运行的测试程序(真则返回0)。程序的输出内容会被储存以便 RESULT
测试重用;
RESULT
:对最后一次调用 PROGRAM
产生的结果进行检查。
右操作数可以使用模式表达式来同时匹配几个值。比如, *
匹配任何字符串(甚至是空字符串); ?
匹配任何一个字符, []
匹配方括号中间的字符集(如果首字符是惊叹号标示求反集,连续的字符集可表示为如 a-z
)。
关于赋值操作符, =
用来赋值(并取代当前值);如果用在列表上,列表被清空并只包含赋予的新值。 :=
做同样工作,但是它会阻止随后对该变量的更改。至于 +=
,则是给列表添加新项目。如下的变量可以被更改:
赋予这些变量的值可以使用一系列的替代表示:
$kernel
或者 %k
:等价于 KERNEL
;
$number
或者 %n
:设备的顺序号码,例如,对 sda3
,它就是“3”;
$devpath
或 %p
:等价于 DEVPATH
;
$attr{attribute}
或 %s{attribute}
: 等价于 ATTRS{attribute}
;
$major
或 %M
:设备的内核主设备号;
$minor
或 %m
:内核次设备号码;
$result
或 %c
:PROGRAM
设定的最后一个程序输出的字符串;
最后, %%
和 $$
相应代表百分号和美元符号。
以上的清单仍不完备 (只包括最重要的参数),详细的数据在 udev(7) 手册页面。
我们来考虑一个U盘并给它指派固定名字的例子。首先,必须要找到能唯一识别它的元素。可以插入并运行 udevadm info -a -n /dev/sdc
( 用指派给U盘的名字代替/dev/sdc )。
#
udevadm info -a -n /dev/sdc
[...]
looking at device '/devices/pci0000:00/0000:00:10.3/usb1/1-2/1-2.2/1-2.2:1.0/host9/target9:0:0/9:0:0:0/block/sdc':
KERNEL=="sdc"
SUBSYSTEM=="block"
DRIVER==""
ATTR{range}=="16"
ATTR{ext_range}=="256"
ATTR{removable}=="1"
ATTR{ro}=="0"
ATTR{size}=="126976"
ATTR{alignment_offset}=="0"
ATTR{capability}=="53"
ATTR{stat}==" 51 100 1208 256 0 0 0 0 0 192 25 6"
ATTR{inflight}==" 0 0"
[...]
looking at parent device '/devices/pci0000:00/0000:00:10.3/usb1/1-2/1-2.2/1-2.2:1.0/host9/target9:0:0/9:0:0:0':
KERNELS=="9:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{device_blocked}=="0"
ATTRS{type}=="0"
ATTRS{scsi_level}=="3"
ATTRS{vendor}=="I0MEGA "
ATTRS{model}=="UMni64MB*IOM2C4 "
ATTRS{rev}==" "
ATTRS{state}=="running"
[...]
ATTRS{max_sectors}=="240"
[...]
looking at parent device '/devices/pci0000:00/0000:00:10.3/usb1/1-2/1-2.2':
KERNELS=="9:0:0:0"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{configuration}=="iCfg"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
ATTRS{bmAttributes}=="80"
ATTRS{bMaxPower}=="100mA"
ATTRS{urbnum}=="398"
ATTRS{idVendor}=="4146"
ATTRS{idProduct}=="4146"
ATTRS{bcdDevice}=="0100"
[...]
ATTRS{manufacturer}=="USB Disk"
ATTRS{product}=="USB Mass Storage Device"
ATTRS{serial}=="M004021000001"
[...]
可以通过检测设备变量,还有父设备变量创建新的规则。上面的例子运行我们创建两个这样的规则:
KERNEL=="sd?", SUBSYSTEM=="block", ATTRS{serial}=="M004021000001", SYMLINK+="usb_key/disk"
KERNEL=="sd?[0-9]", SUBSYSTEM=="block", ATTRS{serial}=="M004021000001", SYMLINK+="usb_key/part%n"
这些规则在文件中设定,例如该文件名为 /etc/udev/rules.d/010_local.rules
,就可以移除和重新插入U盘了。可以看到文件/dev/usb_key/disk
代表和U盘相关联的磁盘,/dev/usb_key/part1
是它的第一个扇区。