有 2 個(gè)不同方法來(lái)看熱插拔. 內(nèi)核看待熱插拔為硬件, 內(nèi)核和內(nèi)核驅(qū)動(dòng)之間的交互. 用戶看待熱插拔是內(nèi)核和用戶空間的通過(guò)稱為 /sbin/hotplug 的程序的交互. 這個(gè)程序被內(nèi)核調(diào)用, 當(dāng)它想通知用戶空間某種熱插拔事件剛剛在內(nèi)核中發(fā)生.
術(shù)語(yǔ)"熱插拔"最普遍使用的意義產(chǎn)生于當(dāng)討論這樣的事實(shí)時(shí), 幾乎所有的計(jì)算機(jī)系統(tǒng)現(xiàn)在能夠處理當(dāng)系統(tǒng)有電時(shí)設(shè)備的出現(xiàn)或消失. 這非常不同于只是幾年前的計(jì)算機(jī)系統(tǒng), 那時(shí)程序員知道他們只需要在啟動(dòng)時(shí)掃描所有的設(shè)備, 并且他們從不必?fù)?dān)心他們的設(shè)備消失直到整個(gè)機(jī)器被關(guān)電. 現(xiàn)在, 隨著 USB 的出現(xiàn), CardBus, PCMCIA, IEEE1394, 和 PCI 熱插拔控制器, Linux 內(nèi)核需要能夠可靠地運(yùn)行不管什么硬件從系統(tǒng)中增加或去除. 這產(chǎn)生了一個(gè)額外的負(fù)擔(dān)給設(shè)備驅(qū)動(dòng)作者, 因?yàn)楝F(xiàn)在他們必須一直處理一個(gè)沒(méi)有任何通知而突然從地下冒出來(lái)的設(shè)備.
每個(gè)不同的總線類(lèi)型以不同方式處理一個(gè)設(shè)備的消失. 例如, 當(dāng)一個(gè) PCI , CardBus, 或者 PCMCIA 設(shè)備從系統(tǒng)中去除, 在驅(qū)動(dòng)通過(guò)它的去除函數(shù)被通知之前常常是一會(huì)兒. 在發(fā)生這個(gè)前, 所有的從 PCI 的讀返回所有的位集合. 這意味著驅(qū)動(dòng)需要一直檢查它們從 PCI 總線讀取的值并且能夠正確處理 0xff 值.
這個(gè)的一個(gè)例子可在 drivers/usb/host/ehci-hcd.c 驅(qū)動(dòng)中見(jiàn)到, 它是一個(gè) PCI 驅(qū)動(dòng)給一個(gè) UBS 2.0(高速)控制卡. 它有下面的代碼在它的主握手循環(huán)中來(lái)探測(cè)是否控制塊已經(jīng)從系統(tǒng)中去除.
result = readl(ptr);
if (result == ~(u32)0) /* card removed */
return -ENODEV;
對(duì)于 USB 驅(qū)動(dòng), 當(dāng)一個(gè) USB 驅(qū)動(dòng)被綁定到的設(shè)備被從系統(tǒng)中去除, 任何掛起的已被提交給設(shè)備的 urbs 以錯(cuò)誤 -ENODEV 失敗. 如果發(fā)生這個(gè)情況, 驅(qū)動(dòng)需要識(shí)別這個(gè)錯(cuò)誤并且正確清理任何掛起的 I/O .
可熱插拔的設(shè)備不只限于傳統(tǒng)的設(shè)備, 例如鼠標(biāo), 鍵盤(pán), 和網(wǎng)卡. 有大量的系統(tǒng)現(xiàn)在支持整個(gè) CPU 和內(nèi)存條的移出. 幸運(yùn)地, Linux 內(nèi)核正確處理這些核心"系統(tǒng)"設(shè)備的加減, 以至于單個(gè)設(shè)備驅(qū)動(dòng)不需要注意這些事情.
如同本章中前面提過(guò)的, 無(wú)論何時(shí)一個(gè)設(shè)備從系統(tǒng)中增刪, 都產(chǎn)生一個(gè)"熱插拔事件". 這意味著內(nèi)核調(diào)用用戶空間程序 /sbin/hotplug. 這個(gè)程序典型地是一個(gè)非常小的 bash 腳本, 只傳遞執(zhí)行給一系列其他的位于 /etc/hot-plug.d/ 目錄樹(shù)的程序. 對(duì)于大部分的 Linux 發(fā)布, 這個(gè)腳本看來(lái)如下:
DIR="/etc/hotplug.d"
for I in "${DIR}/$1/"*.hotplug "${DIR}/"default/*.hotplug ; do
if [ -f $I ]; then
test -x $I && $I $1 ;
fi
done
exit 1
換句話說(shuō), 這個(gè)腳本搜索所有的有 .hotplug 后綴的可能對(duì)這個(gè)事件感興趣的程序并調(diào)用它們, 傳遞給它們?cè)S多不同的環(huán)境變量, 這些環(huán)境變量已經(jīng)被內(nèi)核設(shè)置. 更多關(guān)于 /sbin/hotplug 腳本如何工作的細(xì)節(jié)可在程序的注釋中找到, 以及在 hotplug(8)手冊(cè)頁(yè)中.
如同前面提到的, /sbin/hotplug 被調(diào)用無(wú)論何時(shí)一個(gè) kobject 被創(chuàng)建或銷(xiāo)毀. 熱插拔程序被用一個(gè)提供事件名子的單個(gè)命令行參數(shù)調(diào)用. 核心內(nèi)核和涉及到的特定子系統(tǒng)也設(shè)定一系列帶有關(guān)于發(fā)生了什么的信息的環(huán)境變量(下面描述). 這些變量被熱插拔程序使用來(lái)判定剛剛在內(nèi)核發(fā)生了什么, 以及是否有任何特定的動(dòng)作應(yīng)當(dāng)采取.
傳遞給 /sbin/hotplug 的命令行參數(shù)是關(guān)聯(lián)這個(gè)熱插拔事件的名子, 如同分配給 kobject 的 kset 所決定的. 這個(gè)名子可通過(guò)一個(gè)對(duì)屬于本章前面描述過(guò)的 kset 的 hotplug_ops 結(jié)構(gòu)的 name 函數(shù)的調(diào)用來(lái)設(shè)定; 如果那個(gè)函數(shù)不存在或者從未被調(diào)用, 名子是 kset 自身的名子.
一直為 /sbin/hotplug 設(shè)定的缺省的環(huán)境變量是:
ACTION
這個(gè)字符串 add 或 remove, 只根據(jù)是否這個(gè)對(duì)象是被創(chuàng)建或者銷(xiāo)毀.
DEVPATH
一個(gè)目錄路徑, 在 sysfs 文件系統(tǒng)中, 它指向在被創(chuàng)建或銷(xiāo)毀的 kobject. 注意 sysfs 文件系統(tǒng)的加載點(diǎn)不是添加到這路徑, 因此是由用戶空間程序來(lái)決定這個(gè).
SEQNUM
這個(gè)熱插拔事件的順序號(hào). 順序號(hào)是一個(gè) 64-位 數(shù), 它每次產(chǎn)生熱插拔事件都遞增. 這允許用戶空間以內(nèi)核產(chǎn)生它們的順序來(lái)排序熱插拔事件, 因?yàn)閷?duì)一個(gè)用戶空間程序可能亂序運(yùn)行.
SUBSYSTEM
同樣的字符串作為前面描述的命令行參數(shù)傳遞.
許多不同的總線子系統(tǒng)都添加它們自己的環(huán)境變量到 /sbin/hotplug 調(diào)用中, 當(dāng)關(guān)聯(lián)到總線的設(shè)備被添加或從系統(tǒng)中去除. 它們?cè)谒鼈兊臒岵灏位卣{(diào)中做這個(gè), 這個(gè)回調(diào)在分配給它們的總線(如同在"熱插拔操作"一節(jié)中描述的)的 struct kset_hotplug_ops 中指定. 這允許用戶空間能夠自動(dòng)加載必要的可能需要來(lái)控制這個(gè)被總線發(fā)現(xiàn)的設(shè)備的模塊. 這里是一個(gè)不同總線類(lèi)型的列表以及它們添加到 /sbin/hotplug 調(diào)用中的環(huán)境變量.
任何在 IEEE1394 總線, 也是火線, 上的設(shè)備, 由 /sbin/hotplug 參數(shù)名和 SUBSYSTEM 環(huán)境變量設(shè)置為值 ieee1394. ieee1394 子系統(tǒng)也總是添加下列 4 個(gè)環(huán)境變量:
VENDOR_ID
IEEE1394 的 24-位 供應(yīng)者 ID.
MODEL_ID
IEEE1394 的 24-位型號(hào) ID.
GUID
設(shè)備的 64-位 GUID.
SPECIFIER_ID
24-位值, 指定設(shè)備的協(xié)議規(guī)格的擁有者.
VERSION
指定設(shè)備協(xié)議規(guī)格的版本的值
所有的網(wǎng)絡(luò)設(shè)備都創(chuàng)建一個(gè)熱插拔事件, 當(dāng)設(shè)備注冊(cè)或者注銷(xiāo)在內(nèi)核. /sbin/hotplug 調(diào)用有參數(shù) name 和 SUBSYSTEM 環(huán)境變量設(shè)置為 net, 并且只添加下列環(huán)境變量:
INTERFACE
已經(jīng)從內(nèi)核注冊(cè)或注銷(xiāo)的接口的名子. 這個(gè)的例子是 lo 和 eth0.
任何在 PCI 總線上的設(shè)備有參數(shù) name 和 SUBSYSTEM 環(huán)境變量設(shè)置為值 pci. PCI 子系統(tǒng)也一直添加下面 4 個(gè)環(huán)境變量:
PCI_CLASS
設(shè)備的 PCI 類(lèi)號(hào), 16 進(jìn)制.
PCI_ID
設(shè)備的 PCI 供應(yīng)商和設(shè)備 ID, 16進(jìn)制, 結(jié)合成這樣的格式 供應(yīng)者:設(shè)備.
PCI_SUBSYS_ID
PCI 子系統(tǒng)供應(yīng)商和子系統(tǒng)設(shè)備 ID, 以 子系統(tǒng)供應(yīng)者:子系統(tǒng)設(shè)備 的格式結(jié)合.
PCI_SLOT_NAME
PCI 插口"名", 內(nèi)核給予這個(gè)設(shè)備的. 它以這樣的格式 域:總線:插口:功能. 一個(gè)例子可能是: 0000:00:0d.0.
對(duì)所有的輸入設(shè)備(鼠標(biāo), 鍵盤(pán), 游戲桿, 等等), 一個(gè)熱插拔事件當(dāng)設(shè)備從內(nèi)核增減時(shí)產(chǎn)生. /sbin/hotplug 參數(shù)和 SUBSYSTEM 環(huán)境變量被設(shè)置為值 input. 輸入子系統(tǒng)也總是添加下面的環(huán)境變量:
PRODUCT
一個(gè)多值字符串, 用 16 進(jìn)制列出值沒(méi)有前導(dǎo) 0. 它的格式是 bustype:vender:product:version.
下列環(huán)境變量可能出現(xiàn), 如果設(shè)備支持它:
NAME
輸入設(shè)備的名子, 如同設(shè)備給定的.
PHYS
輸入子系統(tǒng)給這個(gè)設(shè)備的設(shè)備的物理地址. 它假定是穩(wěn)定的, 依賴設(shè)備所插入的總線的位置.
EVKEYRELABSMSCLEDSNDFF
這些都來(lái)自輸入設(shè)備描述符并且被設(shè)置為合適的值如果特定的輸入設(shè)備支持它.
任何在 USB 總線上的設(shè)備有參數(shù) name 和 SUBSYSTEM 環(huán)境變量設(shè)置為 usb. USB 子系統(tǒng)也總是一直添加下列的環(huán)境變量:
PRODUCT
一個(gè)字符串, idVendor/idProduct/bcdDevice 的格式, 來(lái)指定這些 USB 設(shè)備特定的成員.
TYPE
一個(gè) bDeviceClass/bDeviceSubClass/bDeviceProtocol 格式的字符串, 指定這些 USB 設(shè)備特定的成員.
如果 bDeviceClass 成員設(shè)置為 0, 下列的環(huán)境變量也被設(shè)置:
INTERFACE
一個(gè) bInterfaceClass/bInterfaceSubClass/bInterfaceProtocol 格式的字符串, 指定這些 USB 設(shè)備特定成員.
如果這個(gè)內(nèi)核建立選項(xiàng), CONFIG_USB_DEVICEFS, 它選擇 usbfs 文件系統(tǒng)來(lái)在內(nèi)核中建立, 被選中, 下列環(huán)境變量也被設(shè)置:
DEVICE
一個(gè)字符串, 在設(shè)備所在的 usbfs 文件系統(tǒng)中出現(xiàn). 這個(gè)字串以 /proc/bus/usb/USB_BUS_NUMBER/USB_DEVICE_NUMBER 的格式, 其中 USB_BUS_NUMBER 是這個(gè)設(shè)備所在的 USB 總線的 3 個(gè)數(shù), USB_DEVICE_NUMBER 是已由內(nèi)核分配給 USB 設(shè)備的 3 位數(shù).
所有的 SCSI 設(shè)備創(chuàng)建一個(gè)熱插拔事件當(dāng) SCSI 設(shè)備從內(nèi)核中創(chuàng)建或去除. /sbin/hotplug 調(diào)用有參數(shù) name 和 SUBSYSTEM 環(huán)境變量設(shè)置為 scsi 給每個(gè)添加或去除自系統(tǒng)的 SCSI 設(shè)備. 沒(méi)有額外的環(huán)境變量由 SCSI 系統(tǒng)添加, 但是它被在此提及因?yàn)橛幸粋€(gè) SCSI 特定的用戶空間腳本來(lái)決定什么 SCSI 驅(qū)動(dòng)( 磁盤(pán), 磁帶, 通用, 等等)應(yīng)當(dāng)給這個(gè)特定 SCSI 設(shè)備加載.
如果一個(gè)支持即插即用的膝上電腦塢站被從運(yùn)行中的 Linux 系統(tǒng)中添加或去除( 通過(guò)插入膝上電腦到塢站中, 或者去除它), 一個(gè)熱插拔事件被產(chǎn)生. /sbin/hotplug 調(diào)用有參數(shù) name 和 SUBSYSTEM 環(huán)境變量設(shè)為 dock. 沒(méi)有其他的環(huán)境變量被設(shè)置.
在 S/390 體系中, 通道總線結(jié)構(gòu)支持很廣范圍的硬件, 所有產(chǎn)生 /sbin/hotplug 事件當(dāng)它們從 Linux 虛擬系統(tǒng)被添加或去除時(shí)的硬件. 這些設(shè)備都有 /sbin/hotplug 參數(shù) name 和 SUBSYSTEM 環(huán)境變量設(shè)置為 dasd. 沒(méi)有其他環(huán)境變量被設(shè)置.
現(xiàn)在 Linux 內(nèi)核在調(diào)用 /sbin/hotplug 為每個(gè)設(shè)備, 添加和刪除自內(nèi)核, 許多非常有用的工具在用戶空間已被創(chuàng)建來(lái)利用這一點(diǎn). 2 個(gè)最常用的工具是 Linux 熱插拔腳本和 udev.
Linux 熱插拔腳本作為 /sbin/hotplug 調(diào)用的第一個(gè)用戶而啟動(dòng). 這些腳本查看內(nèi)核設(shè)置的來(lái)描述剛剛發(fā)現(xiàn)的設(shè)備的不同的環(huán)境變量, 并接著試圖發(fā)現(xiàn)一個(gè)匹配這個(gè)設(shè)備的內(nèi)核模塊.
如同前面描述的, 當(dāng)一個(gè)驅(qū)動(dòng)使用 MODULE_DEVICE_TABLE 宏, 程序 depmod 采用這個(gè)信息并創(chuàng)建位于 /lib/module/KERNEL_VERSION/modules.map 的文件. 這個(gè) 是不同的, 根據(jù)驅(qū)動(dòng)支持的總線類(lèi)型. 當(dāng)前, 模塊 map 文件為使用設(shè)備的驅(qū)動(dòng)而產(chǎn)生, 這些設(shè)備支持 PCI, USB, IEEE1394, INPUT, ISAPNP, 和 CCW 子系統(tǒng).
熱插拔腳本使用這些模塊映射文本文件, 來(lái)決定試圖加載什么模塊來(lái)支持內(nèi)核剛剛發(fā)現(xiàn)的設(shè)備. 它們加載所有的模塊, 在第一次匹配時(shí)不停止, 為了使內(nèi)核發(fā)現(xiàn)那個(gè)模塊工作得最好. 這些腳本不加載任何模塊當(dāng)驅(qū)動(dòng)被去除時(shí). 如果它們要試圖做這個(gè), 它們可能偶然地關(guān)閉被同一個(gè)要被去除的驅(qū)動(dòng)控制的設(shè)備.
注意, 現(xiàn)在 modprobe 程序能直接從模塊中讀 MODULE_DEVICE_TABLE 信息而不需要模塊 map 文件, 熱插拔腳本可能被刪減為一個(gè)小的在 modprobe 程序周?chē)陌b.
在內(nèi)核中創(chuàng)建統(tǒng)一的驅(qū)動(dòng)模型的一個(gè)主要原因是允許用戶空間動(dòng)態(tài)管理 /dev 樹(shù). 這之前已使用 devfs 的實(shí)現(xiàn)在用戶空間實(shí)現(xiàn), 但是那個(gè)代碼底線已慢慢消失, 由于缺少一個(gè)活躍的維護(hù)者以及一些無(wú)法修正的核心 bug. 許多內(nèi)核開(kāi)發(fā)者認(rèn)識(shí)到如果所有的設(shè)備信息被輸出給用戶空間, 它可能進(jìn)行所有的必要的 /dev 樹(shù)的管理.
devfs 在它的設(shè)計(jì)中有一些非?;A(chǔ)的缺陷. 它需要每個(gè)設(shè)備驅(qū)動(dòng)被修改來(lái)支持它, 并且它要求設(shè)備驅(qū)動(dòng)來(lái)指定名子和在它所在的 /dev 樹(shù)中的位置. 它也沒(méi)有正確處理動(dòng)態(tài)主次編號(hào), 并且它不允許用戶空間以簡(jiǎn)單方式覆蓋設(shè)備的命名, 這樣來(lái)強(qiáng)制設(shè)備命名策略于內(nèi)核中而不是在用戶空間. Linux 內(nèi)核開(kāi)發(fā)中非常厭惡使策略在內(nèi)核中, 并且因?yàn)?devfs 命名策略不遵循 Linux 標(biāo)準(zhǔn)基礎(chǔ)規(guī)格, 它確實(shí)困擾他們.
隨著 Linux 內(nèi)核開(kāi)始安裝到大型服務(wù)器, 許多用戶遇到如何管理大量設(shè)備的問(wèn)題. 超過(guò) 10,000 個(gè)單一設(shè)備的磁盤(pán)驅(qū)動(dòng)陣列提出了非常困難的任務(wù), 保證一個(gè)特定磁盤(pán)一直使用相同的名子命名, 不管它在磁盤(pán)陣列的哪里或者它什么時(shí)候被內(nèi)核發(fā)現(xiàn). 同樣的問(wèn)題也折磨著桌面用戶, 想插入 2 個(gè) USB 打印機(jī)到他們的系統(tǒng), 并且接著發(fā)現(xiàn)它們沒(méi)有辦法保證已知為 /dev/lpt0 的打印機(jī)不會(huì)改變并分配給其他的打印機(jī)如果系統(tǒng)重啟.
因此, udev 被創(chuàng)建. 它依靠所有通過(guò) sysfs 輸出給用戶空間的設(shè)備信息, 并且依靠被 /sbin/hotplug 通知有設(shè)備添加或去除. 策略決策, 例如給一個(gè)設(shè)備什么名子, 可在用戶空間指定, 內(nèi)核之外. 這保證了命名策略被從內(nèi)核中去除并且允許大量每個(gè)設(shè)備名子的靈活性.
對(duì)更多的關(guān)于如何使用 udev 和如何配置它的信息, 請(qǐng)看在你的發(fā)布中和 udev 軟件包一起的文檔.
所有的一個(gè)設(shè)備驅(qū)動(dòng)需要做的, 為 udev 正確使用它, 是確保任何分配給一個(gè)驅(qū)動(dòng)控制的設(shè)備的主次編號(hào)通過(guò) sysfs 輸出到用戶空間. 對(duì)任何使用一個(gè)子系統(tǒng)來(lái)安排它一個(gè)主次編號(hào)的驅(qū)動(dòng), 這已經(jīng)由子系統(tǒng)完成, 并且驅(qū)動(dòng)不必做任何工作. 做這個(gè)的子系統(tǒng)的例子是 tty, misc, usb, input, scsi, block, i2c, network, 和 frame buffer 子系統(tǒng). 如果你的驅(qū)動(dòng)自己獲得一個(gè)主次編號(hào), 通過(guò)對(duì) cdev_init 函數(shù)的調(diào)用或者更老的 register_chrdev 函數(shù), 驅(qū)動(dòng)需要被修改以便 udev 能夠正確使用它.
udev 查找一個(gè)稱為 dev 的文件在 sysfs 的 /class/ 樹(shù)中, 為了決定分配什么主次編號(hào)給一個(gè)特定設(shè)備當(dāng)它被內(nèi)核通過(guò) /sbin/hotplug 接口調(diào)用時(shí). 一個(gè)設(shè)備驅(qū)動(dòng)只要為每個(gè)它控制的設(shè)備創(chuàng)建這個(gè)文件. class_simple 接口常常是最易的做這個(gè)的方法.
如同" class_simple 接口"一節(jié)中提過(guò)的, 使用 class_simple 接口的第一步是調(diào)用 class_simple_create 函數(shù)來(lái)創(chuàng)建一個(gè) struct class_simple.
static struct class_simple *foo_class;
...
foo_class = class_simple_create(THIS_MODULE, "foo");
if (IS_ERR(foo_class)) {
printk(KERN_ERR "Error creating foo class.\n");
goto error;
}
這個(gè)代碼創(chuàng)建一個(gè)目錄在 sysfs 中 /sys/class/foo.
無(wú)論何時(shí)你的驅(qū)動(dòng)發(fā)現(xiàn)一個(gè)新設(shè)備, 并且你如第 3 章描述的分配它一個(gè)次編號(hào), 驅(qū)動(dòng)應(yīng)當(dāng)調(diào)用 class_simple_device_add 函數(shù):
class_simple_device_add(foo_class, MKDEV(FOO_MAJOR, minor), NULL, "foo%d", minor);
這個(gè)代碼導(dǎo)致在 /sys/class/foo 創(chuàng)建一個(gè)子目錄稱為 fooN, 這里 N 是這個(gè)設(shè)備的次編號(hào). 在這個(gè)目錄里創(chuàng)建有一個(gè)文件, dev, 它恰好是 udev 為你的設(shè)備創(chuàng)建一個(gè)設(shè)備節(jié)點(diǎn)需要的.
當(dāng)你的驅(qū)動(dòng)從一個(gè)設(shè)備解除, 并且你放棄它所依附的次編號(hào), 需要調(diào)用 class_simple_device_remove 來(lái)去除這個(gè)設(shè)備的 sysfs 入口.
class_simple_device_remove(MKDEV(FOO_MAJOR, minor));
之后, 當(dāng)你的整個(gè)驅(qū)動(dòng)被關(guān)閉, 需要調(diào)用 class_simple_destroy 來(lái)去除你起初調(diào)用 class_simple_create 創(chuàng)建的 class.
class_simple_destroy(foo_class);
同樣 class_simple_device_add 創(chuàng)建的 dev 文件包括主次編號(hào), 由一個(gè) : 隔開(kāi). 如果你的驅(qū)動(dòng)不想使用 class_simple 接口因?yàn)槟阆胩峁┢渌谧酉到y(tǒng)的類(lèi)目錄中的文件, 使用 print_dev_t 函數(shù)來(lái)正確格式化特定設(shè)備的主次編號(hào).
更多建議: