Product SiteDocumentation Site

9.11. 热插拔: 热插拔

9.11.1. 介绍

hotplug 次系统核心,以动态方式加载适当的驱动程且 (在 udevd 的协助下)添加对应的设备文件,处理加入与移除设备的作业。当代的硬件与虚拟化,几乎每个对象都是热插拔:从常见的 USB/PCMCIA/IEEE 1394 周边到 SATA 硬盘,以及 CPU 与内存。
核心内的数据库有每个设备的 ID 及其驱动程序。在启动阶段加载此数据库,侦测各接口的周边设备,并在运行中侦测热插入的设备。接收到插入的设备后,送出消息给 udevd,让其添加对应的条目于 /dev/ 内。

9.11.2. 命名问题

在热插拔连接出现之前,很容易给设备赋予一个固定的名字。可以通过设备在总线上的位置简单命名。但是,如果设备能够在总线上来去自如,这就行不通了。典型的例子是数码相机和U 盘,两者都表现为磁盘驱动器。前者可能链接为 /dev/sdb ,后者可能是 /dev/sdc (假设/dev/sda 代表计算机自身的硬盘)。设备名是不固定的;它取决于设备连接的顺序。
另外,越来越多的驱动使用动态值作为设备的主、次设备号,这样就更不可能给设备赋予静态入口,而重启之后这些基本的特性也随之变化。
udev 正是为了解决该问题而创立的。

9.11.3. 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 地址及其他辨识用的总线;
  • KERNELSSUBSYSTEMSATTRS{attributes} 是用来匹配当前设备父设备的选项变量;
  • PROGRAM:指明要运行的测试程序(真则返回0)。程序的输出内容会被储存以便 RESULT 测试重用;
  • RESULT:对最后一次调用 PROGRAM产生的结果进行检查。
右操作数可以使用模式表达式来同时匹配几个值。比如, * 匹配任何字符串(甚至是空字符串); ? 匹配任何一个字符, [] 匹配方括号中间的字符集(如果首字符是惊叹号标示求反集,连续的字符集可表示为如 a-z)。
关于赋值操作符, =用来赋值(并取代当前值);如果用在列表上,列表被清空并只包含赋予的新值。 := 做同样工作,但是它会阻止随后对该变量的更改。至于 +=,则是给列表添加新项目。如下的变量可以被更改:
  • NAME:将在 /dev/中创建的设备文件名。只有第一次赋值起作用;其它的会被忽略;
  • SYMLINK:指向同一个设备的符号列表清单;
  • OWNER, GROUPMODE 指示拥有该设备的用户和组,还有相关的访问许可;
  • RUN:响应事件时执行的程序清单。
赋予这些变量的值可以使用一系列的替代表示:
  • $kernel 或者 %k:等价于 KERNEL
  • $number 或者 %n:设备的顺序号码,例如,对 sda3,它就是“3”;
  • $devpath%p:等价于 DEVPATH
  • $attr{attribute}%s{attribute}: 等价于 ATTRS{attribute}
  • $major%M:设备的内核主设备号;
  • $minor%m:内核次设备号码;
  • $result%cPROGRAM设定的最后一个程序输出的字符串;
  • 最后, %%$$ 相应代表百分号和美元符号。
以上的清单仍不完备 (只包括最重要的参数),详细的数据在 udev(7) 手册页面。

9.11.4. 一个具体例子

我们来考虑一个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 是它的第一个扇区。