Product SiteDocumentation Site

9.11. 熱插拔:hotplug

9.11.1. 介绍

hotplug 次系統核心,以動態方式載入適當的驅動程且 (在 udevd 的協助下)新增對應的設備檔案,處理加入與移除設備的作業。當代的硬體與虛擬化,幾乎每個物件都是熱插拔:從常見的 USB/PCMCIA/IEEE 1394 週邊到 SATA 硬碟,以及 CPU 與記憶體。
核心內的資料庫有每個設備的 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 最後執行程式的輸出字串;
  • 最後,%%$$ 分別是百分號及錢號。
以上的清單仍不完備 (祗包括最重要的參數),詳細的資料在 udev(7) 手冊頁面。

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.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,就可以移除和再連結 USB 隨身碟。可看到 /dev/usb_key/disk 代表與 USB 隨身碟連結的磁碟,/dev/usb_key/part1 是其第一個分區。