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. 命名問題

熱插拔技術出現前,很容易為設備指定名稱。根據設備所在的位置命名即可。就是設備所在的接口。但每個接口都能連結設備後,這件事就有點麻煩。以數位相機與 USB 碟為例,對電腦而言,它們都是磁碟機。數位相機可能是 /dev/sdb 而 USB 碟可能是 /dev/sdc (/dev/sda 代表電腦本身的硬式磁碟)。設備名稱不固定;依其連結的順序而命名。
此外,愈來愈多的驅動程式,以動態值指定設備的主要/次要編號,不可能再把固定款目指定給固定的設備,因為重新開機後,一切都變了。
udev 用以解決此問題。

9.11.3. udev 的運作

udev 被核心告知有個新的設備,它參考 /sys/ 裡對應的款目,搜集該設備的資訊,尤其是那些足辨別的獨特資訊 (網卡的 MAC 位址、某些 USB 設備的序號)。
有了這些資訊後,udev 會查閱 /etc/udev/rules.d//lib/udev/rules.d/ 中所有的規則。在此過程中,決定如何為設備命名、使用的連結符號 (給個其他名稱),以及執行的命令。查詢所有檔案後,依序評估該等規則 (除了使用 “GOTO”指令的文件)。如此一來,一個事件就可能對應多個規則。
規則檔案的語法很簡單;每列有選擇規矩與指定變數。前者用於選擇回應的事件,後者設定採取的行動。都以逗點區隔,以運算元區隔選定的範圍 (使用比較運算元,如 ==!=) 或指定變數 (使用 =+=:= 運算元)。
依下列變數使用比較運算元:
  • KERNEL:核心指定給設備的名稱;
  • ACTION:對應於事件的行動 (“add” 新增設備時,“remove” 移除設備時);
  • DEVPATH:設備在 /sys/ 裡的路徑;
  • SUBSYSTEM:產生請求的核心次系統 (很多這類次系統,包括“usb”、“ide”、“net”、“firmware”等);
  • ATTR{屬性}屬性 檔案的內容在設備的 /sys/$devpath/ 資料夾內。可在此找到 MAC 位址及其他辨識用的匯流排;
  • KERNELSSUBSYSTEMSATTRS{屬性} 係用於比較當前設備的選項變數;
  • PROGRAM:被測試的程式 (若為真,則送回 0)。程式的內容儲存在標準輸出以便被 RESULT 測試使用;
  • RESULT:對最後一次呼叫的 PROGRAM 產生的標準輸入進行測試。
右方的運算元可供模式表達同時匹配的多個值。例如,* 表示匹配所有的字元 (包括空字元);? 表示匹配一個字元,而 [] 表示匹配一組在方括號內的字元 (或若首字元為驚嘆號則做反義的表巧,以 a-z 表示連續的字元)。
對於指定的運算元,= 指定一個值 (並取代現在的值);用在清單時,清空原來的值祗剩指定的值。:= 功能相同,且不允許再更改原變數。至於 +=,新增一個項目在清單內。可以更改以下的變數:
  • NAME:在 /dev/ 新增設備名稱。祗計算第一次的名稱;忽略其他的;
  • SYMLINK:指向同一設備的符號清單;
  • OWNERGROUPMODE 擁有設備的使用者及群組,及其他權限;
  • RUN:回應此事件的執行程式清單。
指定給這些變數的值可以使用以下的替代品:
  • $kernel%k:相當於 KERNEL
  • $number%n:設備的序號,例如,sda3,就是 “3”;
  • $devpath%p:相當於 DEVPATH
  • $attr{屬性}%s{屬性}:相當於 ATTRS{屬性}
  • $major%M:設備的核心主要編號;
  • $minor%m:設備核心的次要編號;
  • $result%c:以 PROGRAM 最後執行程式的輸出字串;
  • 最後,%%$$ 分別是百分號及錢號。
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. 具體的例子

考慮給予 USB 隨身碟一個固定名稱的情況。首先,必須找到能夠識別的元素。因此,插入它並執行 udevadm info -a -n /dev/sdc (以指定給該隨身碟的實際名稱取代 /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,就可以移除和再連結 USB 隨身碟。可看到 /dev/usb_key/disk 代表與 USB 隨身碟連結的磁碟,/dev/usb_key/part1 是其第一個分區。