在為 ioctl 編寫代碼之前, 你需要選擇對應命令的數字. 許多程序員的第一個本能的反 應是選擇一組小數從0或1 開始, 並且從此開始向上. 但是, 有充分的理由不這樣做. ioctl 命令數字應當在這個系統是唯一的, 為了阻止向錯誤的設備發出正確的命令而引起 的錯誤. 這樣的不匹配不會不可能發生, 並且一個程序可能發現它自己試圖改變一個非串 口輸入系統的波特率, 例如一個 FIFO 或者一個音頻設備. 如果這樣的 ioctl 號是唯一 的, 這個應用程序得到一個 EINVAL 錯誤而不是繼續做不應當做的事情.
為幫助程序員創建唯一的 ioctl 命令代碼, 這些編碼已被划分為幾個位段. Linux 的第 一個版本使用 16-位數: 高 8 位是關聯這個設備的"魔"數, 低 8 位是一個順序號, 在設 備內唯一. 這樣做是因為 Linus 是"無能"的(他自己的話); 一個更好的位段划分僅在后 來被設想. 不幸的是, 許多驅動仍然使用老傳統. 它們不得不: 改變命令編碼會破壞大量 的二進制程序,並且這不是內核開發者願意見到的.
根據 Linux 內核慣例來為你的驅動選擇 ioctl 號, 你應當首先檢查 include/asm/ioctl.h 和 Documentation/ioctl-number.txt. 這個頭文件定義你將使用 的位段: type(魔數), 序號, 傳輸方向, 和參數大小. ioctl-number.txt 文件列舉了在 內核中使用的魔數,[20]20 因此你將可選擇你自己的魔數並且避免交疊. 這個文本文件也列 舉了為什么應當使用慣例的原因.
定義 ioctl 命令號的正確方法使用 4 個位段, 它們有下列的含義. 這個列表中介紹的新 符號定義在 <linux/ioctl.h>.
但是, 這個文件的維護在后來有些少見了.
魔數. 只是選擇一個數(在參考了 ioctl-number.txt 之后)並且使用它在整個驅動 中. 這個成員是 8 位寬(_IOC_TYPEBITS).
number
序(順序)號. 它是 8 位(_IOC_NRBITS)寬. direction
數據傳送的方向,如果這個特殊的命令涉及數據傳送. 可能的值是 _IOC_NONE(沒有 數據傳輸), _IOC_READ, _IOC_WRITE, 和 _IOC_READ|_IOC_WRITE (數據在 2 個方 向被傳送). 數據傳送是從應用程序的觀點來看待的; _IOC_READ 意思是從設備讀, 因此設備必須寫到用戶空間. 注意這個成員是一個位掩碼, 因此 _IOC_READ 和
_IOC_WRITE 可使用一個邏輯 AND 操作來抽取.
size
涉及到的用戶數據的大小. 這個成員的寬度是依賴體系的, 但是常常是 13 或者 14 位. 你可為你的特定體系在宏 _IOC_SIZEBITS 中找到它的值. 你使用這個 size 成員不是強制的 - 內核不檢查它 -- 但是它是一個好主意. 正確使用這個成 員可幫助檢測用戶空間程序的錯誤並使你實現向后兼容, 如果你曾需要改變相關數 據項的大小. 如果你需要更大的數據結構, 但是, 你可忽略這個 size 成員. 我們 很快見到如何使用這個成員.
頭文件 <asm/ioctl.h>, 它包含在 <linux/ioctl.h> 中, 定義宏來幫助建立命令號, 如 下: _IO(type,nr)(給沒有參數的命令), _IOR(type, nre, datatype)(給從驅動中讀數據 的), _IOW(type,nr,datatype)(給寫數據), 和 _IOWR(type,nr,datatype)(給雙向傳送). type 和 number 成員作為參數被傳遞, 並且 size 成員通過應用 sizeof 到 datatype 參數而得到.
這個頭文件還定義宏, 可被用在你的驅動中來解碼這個號: _IOC_DIR(nr),
_IOC_TYPE(nr), _IOC_NR(nr), 和 _IOC_SIZE(nr). 我們不進入任何這些宏的細節, 因為 頭文件是清楚的, 並且在本節稍后有例子代碼展示.
這里是一些 ioctl 命令如何在 scull 被定義的. 特別地, 這些命令設置和獲得驅動的可 配置參數.
/* Use 'k' as magic number */
#define SCULL_IOC_MAGIC 'k'
/* Please use a different 8-bit number in your code */
#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)
/*
* S means "Set" through a ptr,
* T means "Tell" directly with the argument value
* G means "Get": reply by setting through a pointer
* Q means "Query": response is on the return value
* X means "eXchange": switch G and S atomically
* H means "sHift": switch T and Q atomically
*/
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)
#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)
#define SCULL_IOC_MAXNR 14 真正的源文件定義幾個額外的這里沒有出現的命令.
我們選擇實現 2 種方法傳遞整數參數: 通過指針和通過明確的值(盡管, 由於一個已存在 的慣例, ioclt 應當通過指針交換值). 類似地, 2 種方法被用來返回一個整數值:通過指 針和通過設置返回值. 這個有效只要返回值是一個正的整數; 如同你現在所知道的, 在從 任何系統調用返回時, 一個正值被保留(如同我們在 read 和 write 中見到的), 而一個 負值被看作一個錯誤並且被用來在用戶空間設置 errno.[21] 21
"exchange"和"shift"操作對於 scull 沒有特別的用處. 我們實現"exchange"來顯示驅動 如何結合獨立的操作到單個的原子的操作, 並且"shift"來連接"tell"和"query". 有時需 要象這樣的原子的測試-和-設置操作, 特別地, 當應用程序需要設置和釋放鎖.
命令的明確的序號沒有特別的含義. 它只用來區分命令. 實際上, 你甚至可使用相同的序 號給一個讀命令和一個寫命令, 因為實際的 ioctl 號在"方向"位是不同的, 但是你沒有 理由這樣做. 我們選擇在任何地方不使用命令的序號除了聲明中, 因此我們不分配一個返 回值給它. 這就是為什么明確的號出現在之前給定的定義中. 這個例子展示了一個使用命 令號的方法, 但是你有自由不這樣做.
除了少數幾個預定義的命令(馬上就討論), ioctl 的 cmd 參數的值當前不被內核使用, 並且在將來也很不可能. 因此, 你可以, 如果你覺得懶, 避免前面展示的復雜的聲明並明 確聲明一組調整數字. 另一方面, 如果你做了, 你不會從使用這些位段中獲益, 並且你會 遇到困難如果你曾提交你的代碼來包含在主線內核中. 頭文件<linux/kd.h> 是這個老式 方法的例子, 使用 16-位的調整值來定義 ioctl 命令. 那個源代碼依靠調整數因為使用 那個時候遵循的慣例, 不是由於懶惰. 現在改變它可能導致無理由的不兼容.