Product SiteDocumentation Site

9.11. 热插拔: hotplug

9.11.1. 介绍

The hotplug kernel subsystem dynamically handles the addition and removal of devices, by loading the appropriate drivers and by creating the corresponding device files (with the help of udevd). With modern hardware and virtualization, almost everything can be “hotplugged“: from the usual USB/PCMCIA/IEEE 1394 peripherals to SATA hard drives, but also the CPU and the memory.
核心内的数据库有每个设备的 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 !=)或赋值指令(使用 =, +=:=运算符)。
比较运算符用于如下变量:
  • KERNEL:内核赋予设备的名称;
  • ACTION:与事件相对应的行动(“add”当设备被添加,“remove”当设备被移除);
  • DEVPATH:设备在 /sys/ 记录中的路径;
  • SUBSYSTEM:产生请求的内核子系统(有很多这样的子系统,例如是“usb”,“ide”,“net”,“firmware”等);
  • ATTR{attribute}attribute 文件的内容在设备的 /sys/$devpath/ 文件夹内。可在此找到 MAC 地址及其他辨识用的总线;
  • KERNELSSUBSYSTEMSATTRS{attributes} 是用来匹配当前设备父设备的可选变量;
  • PROGRAM:指明要运行的测试程序(真则返回0)。程序的输出内容会被储存以便 RESULT 测试重用;
  • RESULT:对最后一次调用 PROGRAM产生的结果进行检查。
右操作数可以使用模式表达式来同时匹配几个值。比如, * 匹配任何字符串(甚至是空字符串); ? 匹配任何一个字符, [] 匹配方括号中间的字符集(如果首字符是惊叹号标示求反集,连续的字符集可表示为如 a-z)。
关于赋值操作符, =用来赋值(并取代当前值);如果用在列表上,列表被清空并只包含赋予的新值。 := 做同样工作,但是它会阻止随后对该变量的更改。至于 +=,则是给列表添加新项目。如下的变量可以被更改:
  • NAME:将在 /dev/中创建的设备文件名。只有第一次赋值起作用;其它的会被忽略;
  • SYMLINK:指向同一个设备的符号列表清单;
  • OWNERGROUPMODE 指示拥有该设备的用户和组,还有相关的访问许可;
  • RUN:响应事件时执行的程序清单。
赋予这些变量的值可以使用一系列的替代表示:
  • $kernel 或者 %k:等价于 KERNEL;
  • $number 或者 %n:设备的顺序号码,例如,对 sda3,它就是“3”;
  • $devpath%p:等价于 DEVPATH;
  • $attr{attribute}%s{attribute}: 等价于 ATTRS{attribute};
  • $major%M:设备的内核主设备号;
  • $minor%m:内核次设备号码;
  • $result%cPROGRAM设定的最后一个程序输出的字符串;
  • 最后, %%$$ 相应代表百分号和美元符号。
The above lists are not complete (they include only the most important parameters), but the udev(7) manual page should be exhaustive.

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.0/usb2/2-1/2-1:1.0/host4/target4:0:0/4:0:0:0/block/sdc':
    KERNEL=="sdc"
    SUBSYSTEM=="block"
    DRIVER==""
    ATTR{hidden}=="0"
    ATTR{events}=="media_change"
    ATTR{ro}=="0"
    ATTR{discard_alignment}=="0"
    ATTR{removable}=="1"
    ATTR{events_async}==""
    ATTR{alignment_offset}=="0"
    ATTR{capability}=="51"
    ATTR{events_poll_msecs}=="-1"
    ATTR{stat}=="130  0  6328  435  0  0  0  0  0  252  252  0  0  0  0"
    ATTR{size}=="15100224"
    ATTR{range}=="16"
    ATTR{ext_range}=="256"
    ATTR{inflight}=="0  0"
[...]

  looking at parent device '/devices/pci0000:00/0000:00:10.0/usb2/2-1/2-1:1.0/host4/target4:0:0/4:0:0:0':
[...]
    ATTRS{max_sectors}=="240"
[...]
  looking at parent device '/devices/pci0000:00/0000:00:10.0/usb2/2-1':
    KERNELS=="2-1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{busnum}=="2"
    ATTRS{quirks}=="0x0"
    ATTRS{authorized}=="1"
    ATTRS{ltm_capable}=="no"
    ATTRS{speed}=="480"
    ATTRS{product}=="TF10"
    ATTRS{manufacturer}=="TDK LoR"
[...]
    ATTRS{serial}=="07032998B60AB777"
[...]
可以通过检测设备变量,还有父设备变量创建新的规则。上面的例子运行我们创建两个这样的规则:
KERNEL=="sd?", SUBSYSTEM=="block", ATTRS{serial}=="07032998B60AB777", SYMLINK+="usb_key/disk"
KERNEL=="sd?[0-9]", SUBSYSTEM=="block", ATTRS{serial}=="07032998B60AB777", SYMLINK+="usb_key/part%n"
这些规则在文件中设定,例如该文件名为 /etc/udev/rules.d/010_local.rules,就可以移除和重新插入U盘了。可以看到文件/dev/usb_key/disk 代表和U盘相关联的磁盘,/dev/usb_key/part1 是它的第一个扇区。