大部分驅動需要 -- 除了讀寫設備的能力 -- 通過設備驅動進行各種硬件控制的能力. 大 部分設備可進行超出簡單的數據傳輸之外的操作; 用戶空間必須常常能夠請求, 例如, 設 備鎖上它的門, 彈出它的介質, 報告錯誤信息, 改變波特率, 或者自我銷毀. 這些操作常 常通過 ioctl 方法來支持, 它通過相同名子的系統調用來實現.
在用戶空間, ioctl 系統調用有下面的原型: int ioctl(int fd, unsigned long cmd, ...);
這個原型由於這些點而凸現於 Unix 系統調用列表, 這些點常常表示函數有數目不定的參 數. 在實際系統中, 但是, 一個系統調用不能真正有變數目的參數. 系統調用必須有一個 很好定義的原型, 因為用戶程序可存取它們只能通過硬件的"門". 因此, 原型中的點不表 示一個變數目的參數, 而是一個單個可選的參數, 傳統上標識為 char *argp. 這些點在 那里只是為了阻止在編譯時的類型檢查. 第 3 個參數的實際特點依賴所發出的特定的控 制命令( 第 2 個參數 ). 一些命令不用參數, 一些用一個整數值, 以及一些使用指向其 他數據的指針. 使用一個指針是傳遞任意數據到 ioctl 調用的方法; 設備接着可與用戶 空間交換任何數量的數據.
ioctl 調用的非結構化特性使它在內核開發者中失寵. 每個 ioctl 命令, 基本上, 是一 個單獨的, 常常無文檔的系統調用, 並且沒有方法以任何類型的全面的方式核查這些調用. 也難於使非結構化的 ioctl 參數在所有系統上一致工作; 例如, 考慮運行在 32-位模式 的一個用戶進程的 64-位 系統. 結果, 有很大的壓力來實現混雜的控制操作, 只通過任 何其他的方法. 可能的選擇包括嵌入命令到數據流(本章稍后我們將討論這個方法)或者使 用虛擬文件系統, 要么是 sysfs 要么是設備特定的文件系統. (我們將在 14 章看看 sysfs). 但是, 事實是 ioctl 常常是最容易的和最直接的選擇,對於真正的設備操作.
ioctl 驅動方法有和用戶空間版本不同的原型:
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
inode 和 filp 指針是對應應用程序傳遞的文件描述符 fd 的值, 和傳遞給 open 方法的 相同參數. cmd 參數從用戶那里不改變地傳下來, 並且可選的參數 arg 參數以一個 unsigned long 的形式傳遞, 不管它是否由用戶給定為一個整數或一個指針. 如果調用程 序不傳遞第 3 個參數, 被驅動操作收到的 arg 值是無定義的. 因為類型檢查在這個額外 參數上被關閉, 編譯器不能警告你如果一個無效的參數被傳遞給 ioctl, 並且任何關聯的 錯誤將難以查找.
如果你可能想到的, 大部分 ioctl 實現包括一個大的 switch 語句來根據 cmd 參數, 選 擇正確的做法. 不同的命令有不同的數值, 它們常常被給予符號名來簡化編碼. 符號名通 過一個預處理定義來安排. 定制的驅動常常聲明這樣的符號在它們的頭文件中; scull.h 為 scull 聲明它們. 用戶程序必須, 當然, 包含那個頭文件來存取這些符號.