## 14.7.?熱插拔
有 2 個不同方法來看熱插拔. 內核看待熱插拔為硬件, 內核和內核驅動之間的交互. 用戶看待熱插拔是內核和用戶空間的通過稱為 /sbin/hotplug 的程序的交互. 這個程序被內核調用, 當它想通知用戶空間某種熱插拔事件剛剛在內核中發生.
### 14.7.1.?動態設備
術語"熱插拔"最普遍使用的意義產生于當討論這樣的事實時, 幾乎所有的計算機系統現在能夠處理當系統有電時設備的出現或消失. 這非常不同于只是幾年前的計算機系統, 那時程序員知道他們只需要在啟動時掃描所有的設備, 并且他們從不必擔心他們的設備消失直到整個機器被關電. 現在, 隨著 USB 的出現, CardBus, PCMCIA, IEEE1394, 和 PCI 熱插拔控制器, Linux 內核需要能夠可靠地運行不管什么硬件從系統中增加或去除. 這產生了一個額外的負擔給設備驅動作者, 因為現在他們必須一直處理一個沒有任何通知而突然從地下冒出來的設備.
每個不同的總線類型以不同方式處理一個設備的消失. 例如, 當一個 PCI , CardBus, 或者 PCMCIA 設備從系統中去除, 在驅動通過它的去除函數被通知之前常常是一會兒. 在發生這個前, 所有的從 PCI 的讀返回所有的位集合. 這意味著驅動需要一直檢查它們從 PCI 總線讀取的值并且能夠正確處理 0xff 值.
這個的一個例子可在 drivers/usb/host/ehci-hcd.c 驅動中見到, 它是一個 PCI 驅動給一個 UBS 2.0(高速)控制卡. 它有下面的代碼在它的主握手循環中來探測是否控制塊已經從系統中去除.
~~~
result = readl(ptr);
if (result == ~(u32)0) /* card removed */
return -ENODEV;
~~~
對于 USB 驅動, 當一個 USB 驅動被綁定到的設備被從系統中去除, 任何掛起的已被提交給設備的 urbs 以錯誤 -ENODEV 失敗. 如果發生這個情況, 驅動需要識別這個錯誤并且正確清理任何掛起的 I/O .
可熱插拔的設備不只限于傳統的設備, 例如鼠標, 鍵盤, 和網卡. 有大量的系統現在支持整個 CPU 和內存條的移出. 幸運地, Linux 內核正確處理這些核心"系統"設備的加減, 以至于單個設備驅動不需要注意這些事情.
### 14.7.2.?/sbin/hotplug 工具
如同本章中前面提過的, 無論何時一個設備從系統中增刪, 都產生一個"熱插拔事件". 這意味著內核調用用戶空間程序 /sbin/hotplug. 這個程序典型地是一個非常小的 bash 腳本, 只傳遞執行給一系列其他的位于 /etc/hot-plug.d/ 目錄樹的程序. 對于大部分的 Linux 發布, 這個腳本看來如下:
~~~
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
~~~
換句話說, 這個腳本搜索所有的有 .hotplug 后綴的可能對這個事件感興趣的程序并調用它們, 傳遞給它們許多不同的環境變量, 這些環境變量已經被內核設置. 更多關于 /sbin/hotplug 腳本如何工作的細節可在程序的注釋中找到, 以及在 hotplug(8)手冊頁中.
如同前面提到的, /sbin/hotplug 被調用無論何時一個 kobject 被創建或銷毀. 熱插拔程序被用一個提供事件名子的單個命令行參數調用. 核心內核和涉及到的特定子系統也設定一系列帶有關于發生了什么的信息的環境變量(下面描述). 這些變量被熱插拔程序使用來判定剛剛在內核發生了什么, 以及是否有任何特定的動作應當采取.
傳遞給 /sbin/hotplug 的命令行參數是關聯這個熱插拔事件的名子, 如同分配給 kobject 的 kset 所決定的. 這個名子可通過一個對屬于本章前面描述過的 kset 的 hotplug_ops 結構的 name 函數的調用來設定; 如果那個函數不存在或者從未被調用, 名子是 kset 自身的名子.
一直為 /sbin/hotplug 設定的缺省的環境變量是:
ACTION
這個字符串 add 或 remove, 只根據是否這個對象是被創建或者銷毀.
DEVPATH
一個目錄路徑, 在 sysfs 文件系統中, 它指向在被創建或銷毀的 kobject. 注意 sysfs 文件系統的加載點不是添加到這路徑, 因此是由用戶空間程序來決定這個.
SEQNUM
這個熱插拔事件的順序號. 順序號是一個 64-位 數, 它每次產生熱插拔事件都遞增. 這允許用戶空間以內核產生它們的順序來排序熱插拔事件, 因為對一個用戶空間程序可能亂序運行.
SUBSYSTEM
同樣的字符串作為前面描述的命令行參數傳遞.
許多不同的總線子系統都添加它們自己的環境變量到 /sbin/hotplug 調用中, 當關聯到總線的設備被添加或從系統中去除. 它們在它們的熱插拔回調中做這個, 這個回調在分配給它們的總線(如同在"熱插拔操作"一節中描述的)的 struct kset_hotplug_ops 中指定. 這允許用戶空間能夠自動加載必要的可能需要來控制這個被總線發現的設備的模塊. 這里是一個不同總線類型的列表以及它們添加到 /sbin/hotplug 調用中的環境變量.
#### 14.7.2.1.?IEEE1394(火線)
任何在 IEEE1394 總線, 也是火線, 上的設備, 由 /sbin/hotplug 參數名和 SUBSYSTEM 環境變量設置為值 ieee1394. ieee1394 子系統也總是添加下列 4 個環境變量:
VENDOR_ID
IEEE1394 的 24-位 供應者 ID.
MODEL_ID
IEEE1394 的 24-位型號 ID.
GUID
設備的 64-位 GUID.
SPECIFIER_ID
24-位值, 指定設備的協議規格的擁有者.
VERSION
指定設備協議規格的版本的值
#### 14.7.2.2.?網絡
所有的網絡設備都創建一個熱插拔事件, 當設備注冊或者注銷在內核. /sbin/hotplug 調用有參數 name 和 SUBSYSTEM 環境變量設置為 net, 并且只添加下列環境變量:
INTERFACE
已經從內核注冊或注銷的接口的名子. 這個的例子是 lo 和 eth0.
#### 14.7.2.3.?PCI 總線
任何在 PCI 總線上的設備有參數 name 和 SUBSYSTEM 環境變量設置為值 pci. PCI 子系統也一直添加下面 4 個環境變量:
PCI_CLASS
設備的 PCI 類號, 16 進制.
PCI_ID
設備的 PCI 供應商和設備 ID, 16進制, 結合成這樣的格式 供應者:設備.
PCI_SUBSYS_ID
PCI 子系統供應商和子系統設備 ID, 以 子系統供應者:子系統設備 的格式結合.
PCI_SLOT_NAME
PCI 插口"名", 內核給予這個設備的. 它以這樣的格式 域:總線:插口:功能. 一個例子可能是: 0000:00:0d.0.
#### 14.7.2.4.?輸入
對所有的輸入設備(鼠標, 鍵盤, 游戲桿, 等等), 一個熱插拔事件當設備從內核增減時產生. /sbin/hotplug 參數和 SUBSYSTEM 環境變量被設置為值 input. 輸入子系統也總是添加下面的環境變量:
PRODUCT
一個多值字符串, 用 16 進制列出值沒有前導 0. 它的格式是 bustype:vender:product:version.
下列環境變量可能出現, 如果設備支持它:
NAME
輸入設備的名子, 如同設備給定的.
PHYS
輸入子系統給這個設備的設備的物理地址. 它假定是穩定的, 依賴設備所插入的總線的位置.
EVKEYRELABSMSCLEDSNDFF
這些都來自輸入設備描述符并且被設置為合適的值如果特定的輸入設備支持它.
#### 14.7.2.5.?USB 總線
任何在 USB 總線上的設備有參數 name 和 SUBSYSTEM 環境變量設置為 usb. USB 子系統也總是一直添加下列的環境變量:
PRODUCT
一個字符串, idVendor/idProduct/bcdDevice 的格式, 來指定這些 USB 設備特定的成員.
TYPE
一個 bDeviceClass/bDeviceSubClass/bDeviceProtocol 格式的字符串, 指定這些 USB 設備特定的成員.
如果 bDeviceClass 成員設置為 0, 下列的環境變量也被設置:
INTERFACE
一個 bInterfaceClass/bInterfaceSubClass/bInterfaceProtocol 格式的字符串, 指定這些 USB 設備特定成員.
如果這個內核建立選項, CONFIG_USB_DEVICEFS, 它選擇 usbfs 文件系統來在內核中建立, 被選中, 下列環境變量也被設置:
DEVICE
一個字符串, 在設備所在的 usbfs 文件系統中出現. 這個字串以 /proc/bus/usb/USB_BUS_NUMBER/USB_DEVICE_NUMBER 的格式, 其中 USB_BUS_NUMBER 是這個設備所在的 USB 總線的 3 個數, USB_DEVICE_NUMBER 是已由內核分配給 USB 設備的 3 位數.
#### 14.7.2.6.?SCSI 總線
所有的 SCSI 設備創建一個熱插拔事件當 SCSI 設備從內核中創建或去除. /sbin/hotplug 調用有參數 name 和 SUBSYSTEM 環境變量設置為 scsi 給每個添加或去除自系統的 SCSI 設備. 沒有額外的環境變量由 SCSI 系統添加, 但是它被在此提及因為有一個 SCSI 特定的用戶空間腳本來決定什么 SCSI 驅動( 磁盤, 磁帶, 通用, 等等)應當給這個特定 SCSI 設備加載.
#### 14.7.2.7.?膝上電腦塢站
如果一個支持即插即用的膝上電腦塢站被從運行中的 Linux 系統中添加或去除( 通過插入膝上電腦到塢站中, 或者去除它), 一個熱插拔事件被產生. /sbin/hotplug 調用有參數 name 和 SUBSYSTEM 環境變量設為 dock. 沒有其他的環境變量被設置.
#### 14.7.2.8.?S/390 和 zSeries
在 S/390 體系中, 通道總線結構支持很廣范圍的硬件, 所有產生 /sbin/hotplug 事件當它們從 Linux 虛擬系統被添加或去除時的硬件. 這些設備都有 /sbin/hotplug 參數 name 和 SUBSYSTEM 環境變量設置為 dasd. 沒有其他環境變量被設置.
### 14.7.3.?使用 /sbin/hotplug
現在 Linux 內核在調用 /sbin/hotplug 為每個設備, 添加和刪除自內核, 許多非常有用的工具在用戶空間已被創建來利用這一點. 2 個最常用的工具是 Linux 熱插拔腳本和 udev.
#### 14.7.3.1.?Linux 熱插拔腳本
Linux 熱插拔腳本作為 /sbin/hotplug 調用的第一個用戶而啟動. 這些腳本查看內核設置的來描述剛剛發現的設備的不同的環境變量, 并接著試圖發現一個匹配這個設備的內核模塊.
如同前面描述的, 當一個驅動使用 MODULE_DEVICE_TABLE 宏, 程序 depmod 采用這個信息并創建位于 /lib/module/KERNEL_VERSION/modules.*map 的文件. 這個 * 是不同的, 根據驅動支持的總線類型. 當前, 模塊 map 文件為使用設備的驅動而產生, 這些設備支持 PCI, USB, IEEE1394, INPUT, ISAPNP, 和 CCW 子系統.
熱插拔腳本使用這些模塊映射文本文件, 來決定試圖加載什么模塊來支持內核剛剛發現的設備. 它們加載所有的模塊, 在第一次匹配時不停止, 為了使內核發現那個模塊工作得最好. 這些腳本不加載任何模塊當驅動被去除時. 如果它們要試圖做這個, 它們可能偶然地關閉被同一個要被去除的驅動控制的設備.
注意, 現在 modprobe 程序能直接從模塊中讀 MODULE_DEVICE_TABLE 信息而不需要模塊 map 文件, 熱插拔腳本可能被刪減為一個小的在 modprobe 程序周圍的包裝.
#### 14.7.3.2.?udev 啥?
在內核中創建統一的驅動模型的一個主要原因是允許用戶空間動態管理 /dev 樹. 這之前已使用 devfs 的實現在用戶空間實現, 但是那個代碼底線已慢慢消失, 由于缺少一個活躍的維護者以及一些無法修正的核心 bug. 許多內核開發者認識到如果所有的設備信息被輸出給用戶空間, 它可能進行所有的必要的 /dev 樹的管理.
devfs 在它的設計中有一些非常基礎的缺陷. 它需要每個設備驅動被修改來支持它, 并且它要求設備驅動來指定名子和在它所在的 /dev 樹中的位置. 它也沒有正確處理動態主次編號, 并且它不允許用戶空間以簡單方式覆蓋設備的命名, 這樣來強制設備命名策略于內核中而不是在用戶空間. Linux 內核開發中非常厭惡使策略在內核中, 并且因為 devfs 命名策略不遵循 Linux 標準基礎規格, 它確實困擾他們.
隨著 Linux 內核開始安裝到大型服務器, 許多用戶遇到如何管理大量設備的問題. 超過 10,000 個單一設備的磁盤驅動陣列提出了非常困難的任務, 保證一個特定磁盤一直使用相同的名子命名, 不管它在磁盤陣列的哪里或者它什么時候被內核發現. 同樣的問題也折磨著桌面用戶, 想插入 2 個 USB 打印機到他們的系統, 并且接著發現它們沒有辦法保證已知為 /dev/lpt0 的打印機不會改變并分配給其他的打印機如果系統重啟.
因此, udev 被創建. 它依靠所有通過 sysfs 輸出給用戶空間的設備信息, 并且依靠被 /sbin/hotplug 通知有設備添加或去除. 策略決策, 例如給一個設備什么名子, 可在用戶空間指定, 內核之外. 這保證了命名策略被從內核中去除并且允許大量每個設備名子的靈活性.
對更多的關于如何使用 udev 和如何配置它的信息, 請看在你的發布中和 udev 軟件包一起的文檔.
所有的一個設備驅動需要做的, 為 udev 正確使用它, 是確保任何分配給一個驅動控制的設備的主次編號通過 sysfs 輸出到用戶空間. 對任何使用一個子系統來安排它一個主次編號的驅動, 這已經由子系統完成, 并且驅動不必做任何工作. 做這個的子系統的例子是 tty, misc, usb, input, scsi, block, i2c, network, 和 frame buffer 子系統. 如果你的驅動自己獲得一個主次編號, 通過對 cdev_init 函數的調用或者更老的 register_chrdev 函數, 驅動需要被修改以便 udev 能夠正確使用它.
udev 查找一個稱為 dev 的文件在 sysfs 的 /class/ 樹中, 為了決定分配什么主次編號給一個特定設備當它被內核通過 /sbin/hotplug 接口調用時. 一個設備驅動只要為每個它控制的設備創建這個文件. class_simple 接口常常是最易的做這個的方法.
如同" class_simple 接口"一節中提過的, 使用 class_simple 接口的第一步是調用 class_simple_create 函數來創建一個 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;
}
~~~
這個代碼創建一個目錄在 sysfs 中 /sys/class/foo.
無論何時你的驅動發現一個新設備, 并且你如第 3 章描述的分配它一個次編號, 驅動應當調用 class_simple_device_add 函數:
~~~
class_simple_device_add(foo_class, MKDEV(FOO_MAJOR, minor), NULL, "foo%d", minor);
~~~
這個代碼導致在 /sys/class/foo 創建一個子目錄稱為 fooN, 這里 N 是這個設備的次編號. 在這個目錄里創建有一個文件, dev, 它恰好是 udev 為你的設備創建一個設備節點需要的.
當你的驅動從一個設備解除, 并且你放棄它所依附的次編號, 需要調用 class_simple_device_remove 來去除這個設備的 sysfs 入口.
~~~
class_simple_device_remove(MKDEV(FOO_MAJOR, minor));
~~~
之后, 當你的整個驅動被關閉, 需要調用 class_simple_destroy 來去除你起初調用 class_simple_create 創建的 class.
~~~
class_simple_destroy(foo_class);
~~~
同樣 class_simple_device_add 創建的 dev 文件包括主次編號, 由一個 : 隔開. 如果你的驅動不想使用 class_simple 接口因為你想提供其他在子系統的類目錄中的文件, 使用 print_dev_t 函數來正確格式化特定設備的主次編號.
- Linux設備驅動第三版
- 第 1 章 設備驅動簡介
- 1.1. 驅動程序的角色
- 1.2. 劃分內核
- 1.3. 設備和模塊的分類
- 1.4. 安全問題
- 1.5. 版本編號
- 1.6. 版權條款
- 1.7. 加入內核開發社團
- 1.8. 本書的內容
- 第 2 章 建立和運行模塊
- 2.1. 設置你的測試系統
- 2.2. Hello World 模塊
- 2.3. 內核模塊相比于應用程序
- 2.4. 編譯和加載
- 2.5. 內核符號表
- 2.6. 預備知識
- 2.7. 初始化和關停
- 2.8. 模塊參數
- 2.9. 在用戶空間做
- 2.10. 快速參考
- 第 3 章 字符驅動
- 3.1. scull 的設計
- 3.2. 主次編號
- 3.3. 一些重要數據結構
- 3.4. 字符設備注冊
- 3.5. open 和 release
- 3.6. scull 的內存使用
- 3.7. 讀和寫
- 3.8. 使用新設備
- 3.9. 快速參考
- 第 4 章 調試技術
- 4.1. 內核中的調試支持
- 4.2. 用打印調試
- 4.3. 用查詢來調試
- 4.4. 使用觀察來調試
- 4.5. 調試系統故障
- 4.6. 調試器和相關工具
- 第 5 章 并發和競爭情況
- 5.1. scull 中的缺陷
- 5.2. 并發和它的管理
- 5.3. 旗標和互斥體
- 5.4. Completions 機制
- 5.5. 自旋鎖
- 5.6. 鎖陷阱
- 5.7. 加鎖的各種選擇
- 5.8. 快速參考
- 第 6 章 高級字符驅動操作
- 6.1. ioctl 接口
- 6.2. 阻塞 I/O
- 6.3. poll 和 select
- 6.4. 異步通知
- 6.5. 移位一個設備
- 6.6. 在一個設備文件上的存取控制
- 6.7. 快速參考
- 第 7 章 時間, 延時, 和延后工作
- 7.1. 測量時間流失
- 7.2. 獲知當前時間
- 7.3. 延后執行
- 7.4. 內核定時器
- 7.5. Tasklets 機制
- 7.6. 工作隊列
- 7.7. 快速參考
- 第 8 章 分配內存
- 8.1. kmalloc 的真實故事
- 8.2. 后備緩存
- 8.3. get_free_page 和其友
- 8.4. 每-CPU 的變量
- 8.5. 獲得大量緩沖
- 8.6. 快速參考
- 第 9 章 與硬件通訊
- 9.1. I/O 端口和 I/O 內存
- 9.2. 使用 I/O 端口
- 9.3. 一個 I/O 端口例子
- 9.4. 使用 I/O 內存
- 9.5. 快速參考
- 第 10 章 中斷處理
- 10.1. 準備并口
- 10.2. 安裝一個中斷處理
- 10.3. 前和后半部
- 10.4. 中斷共享
- 10.5. 中斷驅動 I/O
- 10.6. 快速參考
- 第 11 章 內核中的數據類型
- 11.1. 標準 C 類型的使用
- 11.2. 安排一個明確大小給數據項
- 11.3. 接口特定的類型
- 11.4. 其他移植性問題
- 11.5. 鏈表
- 11.6. 快速參考
- 第 12 章 PCI 驅動
- 12.1. PCI 接口
- 12.2. 回顧: ISA
- 12.3. PC/104 和 PC/104+
- 12.4. 其他的 PC 總線
- 12.5. SBus
- 12.6. NuBus 總線
- 12.7. 外部總線
- 12.8. 快速參考
- 第 13 章 USB 驅動
- 13.1. USB 設備基礎知識
- 13.2. USB 和 sysfs
- 13.3. USB 的 Urbs
- 13.4. 編寫一個 USB 驅動
- 13.5. 無 urb 的 USB 傳送
- 13.6. 快速參考
- 第 14 章 Linux 設備模型
- 14.1. Kobjects, Ksets 和 Subsystems
- 14.2. 低級 sysfs 操作
- 14.3. 熱插拔事件產生
- 14.4. 總線, 設備, 和驅動
- 14.5. 類
- 14.6. 集成起來
- 14.7. 熱插拔
- 14.8. 處理固件
- 14.9. 快速參考
- 第 15 章 內存映射和 DMA
- 15.1. Linux 中的內存管理
- 15.2. mmap 設備操作
- 15.3. 進行直接 I/O
- 15.4. 直接內存存取
- 15.5. 快速參考
- 第 16 章 塊驅動
- 16.1. 注冊
- 16.2. 塊設備操作
- 16.3. 請求處理
- 16.4. 一些其他的細節
- 16.5. 快速參考
- 第 17 章 網絡驅動
- 17.1. snull 是如何設計的
- 17.2. 連接到內核
- 17.3. net_device 結構的詳情
- 17.4. 打開與關閉
- 17.5. 報文傳送
- 17.6. 報文接收
- 17.7. 中斷處理
- 17.8. 接收中斷緩解
- 17.9. 連接狀態的改變
- 17.10. Socket 緩存
- 17.11. MAC 地址解析
- 17.12. 定制 ioctl 命令
- 17.13. 統計信息
- 17.14. 多播
- 17.15. 幾個其他細節
- 17.16. 快速參考
- 第 18 章 TTY 驅動
- 18.1. 一個小 TTY 驅動
- 18.2. tty_driver 函數指針
- 18.3. TTY 線路設置
- 18.4. ioctls 函數
- 18.5. TTY 設備的 proc 和 sysfs 處理
- 18.6. tty_driver 結構的細節
- 18.7. tty_operaions 結構的細節
- 18.8. tty_struct 結構的細節
- 18.9. 快速參考