大部分驅(qū)動需要 -- 除了讀寫設(shè)備的能力 -- 通過設(shè)備驅(qū)動進行各種硬件控制的能力. 大部分設(shè)備可進行超出簡單的數(shù)據(jù)傳輸之外的操作; 用戶空間必須常常能夠請求, 例如, 設(shè)備鎖上它的門, 彈出它的介質(zhì), 報告錯誤信息, 改變波特率, 或者自我銷毀. 這些操作常常通過 ioctl 方法來支持, 它通過相同名子的系統(tǒng)調(diào)用來實現(xiàn).
在用戶空間, ioctl 系統(tǒng)調(diào)用有下面的原型:
int ioctl(int fd, unsigned long cmd, ...);
這個原型由于這些點而凸現(xiàn)于 Unix 系統(tǒng)調(diào)用列表, 這些點常常表示函數(shù)有數(shù)目不定的參數(shù). 在實際系統(tǒng)中, 但是, 一個系統(tǒng)調(diào)用不能真正有變數(shù)目的參數(shù). 系統(tǒng)調(diào)用必須有一個很好定義的原型, 因為用戶程序可存取它們只能通過硬件的"門". 因此, 原型中的點不表示一個變數(shù)目的參數(shù), 而是一個單個可選的參數(shù), 傳統(tǒng)上標識為 char *argp. 這些點在那里只是為了阻止在編譯時的類型檢查. 第 3 個參數(shù)的實際特點依賴所發(fā)出的特定的控制命令( 第 2 個參數(shù) ). 一些命令不用參數(shù), 一些用一個整數(shù)值, 以及一些使用指向其他數(shù)據(jù)的指針. 使用一個指針是傳遞任意數(shù)據(jù)到 ioctl 調(diào)用的方法; 設(shè)備接著可與用戶空間交換任何數(shù)量的數(shù)據(jù).
ioctl 調(diào)用的非結(jié)構(gòu)化特性使它在內(nèi)核開發(fā)者中失寵. 每個 ioctl 命令, 基本上, 是一個單獨的, 常常無文檔的系統(tǒng)調(diào)用, 并且沒有方法以任何類型的全面的方式核查這些調(diào)用. 也難于使非結(jié)構(gòu)化的 ioctl 參數(shù)在所有系統(tǒng)上一致工作; 例如, 考慮運行在 32-位模式的一個用戶進程的 64-位 系統(tǒng). 結(jié)果, 有很大的壓力來實現(xiàn)混雜的控制操作, 只通過任何其他的方法. 可能的選擇包括嵌入命令到數(shù)據(jù)流(本章稍后我們將討論這個方法)或者使用虛擬文件系統(tǒng), 要么是 sysfs 要么是設(shè)備特定的文件系統(tǒng). (我們將在 14 章看看 sysfs). 但是, 事實是 ioctl 常常是最容易的和最直接的選擇,對于真正的設(shè)備操作.
ioctl 驅(qū)動方法有和用戶空間版本不同的原型:
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
inode 和 filp 指針是對應(yīng)應(yīng)用程序傳遞的文件描述符 fd 的值, 和傳遞給 open 方法的相同參數(shù). cmd 參數(shù)從用戶那里不改變地傳下來, 并且可選的參數(shù) arg 參數(shù)以一個 unsigned long 的形式傳遞, 不管它是否由用戶給定為一個整數(shù)或一個指針. 如果調(diào)用程序不傳遞第 3 個參數(shù), 被驅(qū)動操作收到的 arg 值是無定義的. 因為類型檢查在這個額外參數(shù)上被關(guān)閉, 編譯器不能警告你如果一個無效的參數(shù)被傳遞給 ioctl, 并且任何關(guān)聯(lián)的錯誤將難以查找.
如果你可能想到的, 大部分 ioctl 實現(xiàn)包括一個大的 switch 語句來根據(jù) cmd 參數(shù), 選擇正確的做法. 不同的命令有不同的數(shù)值, 它們常常被給予符號名來簡化編碼. 符號名通過一個預(yù)處理定義來安排. 定制的驅(qū)動常常聲明這樣的符號在它們的頭文件中; scull.h 為 scull 聲明它們. 用戶程序必須, 當然, 包含那個頭文件來存取這些符號.
在為 ioctl 編寫代碼之前, 你需要選擇對應(yīng)命令的數(shù)字. 許多程序員的第一個本能的反應(yīng)是選擇一組小數(shù)從0或1開始, 并且從此開始向上. 但是, 有充分的理由不這樣做. ioctl 命令數(shù)字應(yīng)當在這個系統(tǒng)是唯一的, 為了阻止向錯誤的設(shè)備發(fā)出正確的命令而引起的錯誤. 這樣的不匹配不會不可能發(fā)生, 并且一個程序可能發(fā)現(xiàn)它自己試圖改變一個非串口輸入系統(tǒng)的波特率, 例如一個 FIFO 或者一個音頻設(shè)備. 如果這樣的 ioctl 號是唯一的, 這個應(yīng)用程序得到一個 EINVAL 錯誤而不是繼續(xù)做不應(yīng)當做的事情.
為幫助程序員創(chuàng)建唯一的 ioctl 命令代碼, 這些編碼已被劃分為幾個位段. Linux 的第一個版本使用 16-位數(shù): 高 8 位是關(guān)聯(lián)這個設(shè)備的"魔"數(shù), 低 8 位是一個順序號, 在設(shè)備內(nèi)唯一. 這樣做是因為 Linus 是"無能"的(他自己的話); 一個更好的位段劃分僅在后來被設(shè)想. 不幸的是, 許多驅(qū)動仍然使用老傳統(tǒng). 它們不得不: 改變命令編碼會破壞大量的二進制程序,并且這不是內(nèi)核開發(fā)者愿意見到的.
根據(jù) Linux 內(nèi)核慣例來為你的驅(qū)動選擇 ioctl 號, 你應(yīng)當首先檢查 include/asm/ioctl.h 和 Documentation/ioctl-number.txt. 這個頭文件定義你將使用的位段: type(魔數(shù)), 序號, 傳輸方向, 和參數(shù)大小. ioctl-number.txt 文件列舉了在內(nèi)核中使用的魔數(shù),[20]?因此你將可選擇你自己的魔數(shù)并且避免交疊. 這個文本文件也列舉了為什么應(yīng)當使用慣例的原因.
定義 ioctl 命令號的正確方法使用 4 個位段, 它們有下列的含義. 這個列表中介紹的新符號定義在 .
type
魔數(shù). 只是選擇一個數(shù)(在參考了 ioctl-number.txt之后)并且使用它在整個驅(qū)動中. 這個成員是 8 位寬(_IOC_TYPEBITS).
number
序(順序)號. 它是 8 位(_IOC_NRBITS)寬.
direction
數(shù)據(jù)傳送的方向,如果這個特殊的命令涉及數(shù)據(jù)傳送. 可能的值是 _IOC_NONE(沒有數(shù)據(jù)傳輸), _IOC_READ, _IOC_WRITE, 和 _IOC_READ|_IOC_WRITE (數(shù)據(jù)在2個方向被傳送). 數(shù)據(jù)傳送是從應(yīng)用程序的觀點來看待的; _IOC_READ 意思是從設(shè)備讀, 因此設(shè)備必須寫到用戶空間. 注意這個成員是一個位掩碼, 因此 _IOC_READ 和 _IOC_WRITE 可使用一個邏輯 AND 操作來抽取.
size
涉及到的用戶數(shù)據(jù)的大小. 這個成員的寬度是依賴體系的, 但是常常是 13 或者 14 位. 你可為你的特定體系在宏 _IOC_SIZEBITS 中找到它的值. 你使用這個 size 成員不是強制的 - 內(nèi)核不檢查它 -- 但是它是一個好主意. 正確使用這個成員可幫助檢測用戶空間程序的錯誤并使你實現(xiàn)向后兼容, 如果你曾需要改變相關(guān)數(shù)據(jù)項的大小. 如果你需要更大的數(shù)據(jù)結(jié)構(gòu), 但是, 你可忽略這個 size 成員. 我們很快見到如何使用這個成員.
頭文件 , 它包含在 中, 定義宏來幫助建立命令號, 如下: _IO(type,nr)(給沒有參數(shù)的命令), _IOR(type, nre, datatype)(給從驅(qū)動中讀數(shù)據(jù)的), _IOW(type,nr,datatype)(給寫數(shù)據(jù)), 和 _IOWR(type,nr,datatype)(給雙向傳送). type 和 number 成員作為參數(shù)被傳遞, 并且 size 成員通過應(yīng)用 sizeof 到 datatype 參數(shù)而得到.
這個頭文件還定義宏, 可被用在你的驅(qū)動中來解碼這個號: _IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr), 和 _IOC_SIZE(nr). 我們不進入任何這些宏的細節(jié), 因為頭文件是清楚的, 并且在本節(jié)稍后有例子代碼展示.
這里是一些 ioctl 命令如何在 scull 被定義的. 特別地, 這些命令設(shè)置和獲得驅(qū)動的可配置參數(shù).
/* 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
真正的源文件定義幾個額外的這里沒有出現(xiàn)的命令.
我們選擇實現(xiàn) 2 種方法傳遞整數(shù)參數(shù): 通過指針和通過明確的值(盡管, 由于一個已存在的慣例, ioclt 應(yīng)當通過指針交換值). 類似地, 2 種方法被用來返回一個整數(shù)值:通過指針和通過設(shè)置返回值. 這個有效只要返回值是一個正的整數(shù); 如同你現(xiàn)在所知道的, 在從任何系統(tǒng)調(diào)用返回時, 一個正值被保留(如同我們在 read 和 write 中見到的), 而一個負值被看作一個錯誤并且被用來在用戶空間設(shè)置 errno.[21]
"exchange"和"shift"操作對于 scull 沒有特別的用處. 我們實現(xiàn)"exchange"來顯示驅(qū)動如何結(jié)合獨立的操作到單個的原子的操作, 并且"shift"來連接"tell"和"query". 有時需要象這樣的原子的測試-和-設(shè)置操作, 特別地, 當應(yīng)用程序需要設(shè)置和釋放鎖.
命令的明確的序號沒有特別的含義. 它只用來區(qū)分命令. 實際上, 你甚至可使用相同的序號給一個讀命令和一個寫命令, 因為實際的 ioctl 號在"方向"位是不同的, 但是你沒有理由這樣做. 我們選擇在任何地方不使用命令的序號除了聲明中, 因此我們不分配一個返回值給它. 這就是為什么明確的號出現(xiàn)在之前給定的定義中. 這個例子展示了一個使用命令號的方法, 但是你有自由不這樣做.
除了少數(shù)幾個預(yù)定義的命令(馬上就討論), ioctl 的 cmd 參數(shù)的值當前不被內(nèi)核使用, 并且在將來也很不可能. 因此, 你可以, 如果你覺得懶, 避免前面展示的復(fù)雜的聲明并明確聲明一組調(diào)整數(shù)字. 另一方面, 如果你做了, 你不會從使用這些位段中獲益, 并且你會遇到困難如果你曾提交你的代碼來包含在主線內(nèi)核中. 頭文件 是這個老式方法的例子, 使用 16-位的調(diào)整值來定義 ioctl 命令. 那個源代碼依靠調(diào)整數(shù)因為使用那個時候遵循的慣例, 不是由于懶惰. 現(xiàn)在改變它可能導(dǎo)致無理由的不兼容.
ioctl 的實現(xiàn)常常是一個 switch 語句, 基于命令號. 但是當命令號沒有匹配一個有效的操作時缺省的選擇應(yīng)當是什么? 這個問題是有爭議的. 幾個內(nèi)核函數(shù)返回 -ENIVAL("Invalid argument"), 它有意義是因為命令參數(shù)確實不是一個有效的. POSIX 標準, 但是, 說如果一個不合適的 ioctl 命令被發(fā)出, 那么 -ENOTTY 應(yīng)當被返回. 這個錯誤碼被 C 庫解釋為"設(shè)備的不適當?shù)?ioctl", 這常常正是程序員需要聽到的. 然而, 它仍然是相當普遍的來返回 -EINVAL, 對于響應(yīng)一個無效的 ioctl 命令.
盡管 ioctl 系統(tǒng)調(diào)用最常用來作用于設(shè)備, 內(nèi)核能識別幾個命令. 注意這些命令, 當用到你的設(shè)備時, 在你自己的文件操作被調(diào)用之前被解碼. 因此, 如果你選擇相同的號給一個你的 ioctl命令, 你不會看到任何的給那個命令的請求, 并且應(yīng)用程序獲得某些不期望的東西, 因為在 ioctl 號之間的沖突.
預(yù)定義命令分為 3 類:
可對任何文件發(fā)出的(常規(guī), 設(shè)備, FIFO, 或者 socket) 的那些.
只對常規(guī)文件發(fā)出的那些.
最后一類的命令由宿主文件系統(tǒng)的實現(xiàn)來執(zhí)行(這是 chattr 命令如何工作的). 設(shè)備驅(qū)動編寫者只對第一類命令感興趣, 它們的魔數(shù)是 "T". 查看其他類的工作留給讀者作為練習; ext2_ioctl 是最有趣的函數(shù)(并且比預(yù)期的要容易理解), 因為它實現(xiàn) append-only 標志和 immutable 標志.
下列 ioctl 命令是預(yù)定義給任何文件, 包括設(shè)備特殊的文件:
FIOCLEX
設(shè)置 close-on-exec 標志(File IOctl Close on EXec). 設(shè)置這個標志使文件描述符被關(guān)閉, 當調(diào)用進程執(zhí)行一個新程序時.
FIONCLEX
清除 close-no-exec 標志(File IOctl Not CLose on EXec). 這個命令恢復(fù)普通文件行為, 復(fù)原上面 FIOCLEX 所做的. FIOASYNC 為這個文件設(shè)置或者復(fù)位異步通知(如同在本章中"異步通知"一節(jié)中討論的). 注意直到 Linux 2.2.4 版本的內(nèi)核不正確地使用這個命令來修改 O_SYNC 標志. 因為兩個動作都可通過 fcntl 來完成, 沒有人真正使用 FIOASYNC 命令, 它在這里出現(xiàn)只是為了完整性.
FIOQSIZE
這個命令返回一個文件或者目錄的大小; 當用作一個設(shè)備文件, 但是, 它返回一個 ENOTTY 錯誤.
FIONBIO
"File IOctl Non-Blocking I/O"(在"阻塞和非阻塞操作"一節(jié)中描述). 這個調(diào)用修改在 filp->f_flags 中的 O_NONBLOCK 標志. 給這個系統(tǒng)調(diào)用的第 3 個參數(shù)用作指示是否這個標志被置位或者清除. (我們將在本章看到這個標志的角色). 注意常用的改變這個標志的方法是使用 fcntl 系統(tǒng)調(diào)用, 使用 F_SETFL 命令.
列表中的最后一項介紹了一個新的系統(tǒng)調(diào)用, fcntl, 它看來象 ioctl. 事實上, fcntl 調(diào)用非常類似 ioctl, 它也是獲得一個命令參數(shù)和一個額外的(可選地)參數(shù). 它保持和 ioctl 獨立主要是因為歷史原因: 當 Unix 開發(fā)者面對控制 I/O 操作的問題時, 他們決定文件和設(shè)備是不同的. 那時, 有 ioctl 實現(xiàn)的唯一設(shè)備是 ttys, 它解釋了為什么 -ENOTTY 是標準的對不正確 ioctl 命令的回答. 事情已經(jīng)改變, 但是 fcntl 保留為一個獨立的系統(tǒng)調(diào)用.
在看 scull 驅(qū)動的 ioctl 代碼之前, 我們需要涉及的另一點是如何使用這個額外的參數(shù). 如果它是一個整數(shù), 就容易: 它可以直接使用. 如果它是一個指針, 但是, 必須小心些.
當用一個指針引用用戶空間, 我們必須確保用戶地址是有效的. 試圖存取一個沒驗證過的用戶提供的指針可能導(dǎo)致不正確的行為, 一個內(nèi)核 oops, 系統(tǒng)崩潰, 或者安全問題. 它是驅(qū)動的責任來對每個它使用的用戶空間地址進行正確的檢查, 并且返回一個錯誤如果它是無效的.
在第 3 章, 我們看了 copy_from_user 和 copy_to_user 函數(shù), 它們可用來安全地移動數(shù)據(jù)到和從用戶空間. 這些函數(shù)也可用在 ioctl 方法中, 但是 ioctl 調(diào)用常常包含小數(shù)據(jù)項, 可通過其他方法更有效地操作. 開始, 地址校驗(不傳送數(shù)據(jù))由函數(shù) access_ok 實現(xiàn), 它定義在 :
int access_ok(int type, const void *addr, unsigned long size);
第一個參數(shù)應(yīng)當是 VERIFY_READ 或者 VERIFY_WRITE, 依據(jù)這個要進行的動作是否是讀用戶空間內(nèi)存區(qū)或者寫它. addr 參數(shù)持有一個用戶空間地址, size 是一個字節(jié)量. 例如, 如果 ioctl 需要從用戶空間讀一個整數(shù), size 是 sizeof(int). 如果你需要讀和寫給定地址, 使用 VERIFY_WRITE, 因為它是 VERIRY_READ 的超集.
不象大部分的內(nèi)核函數(shù), access_ok 返回一個布爾值: 1 是成功(存取沒問題)和 0 是失敗(存取有問題). 如果它返回假, 驅(qū)動應(yīng)當返回 -EFAULT 給調(diào)用者.
關(guān)于 access_ok有多個有趣的東西要注意. 首先, 它不做校驗內(nèi)存存取的完整工作; 它只檢查看這個內(nèi)存引用是在這個進程有合理權(quán)限的內(nèi)存范圍中. 特別地, access_ok 確保這個地址不指向內(nèi)核空間內(nèi)存. 第2, 大部分驅(qū)動代碼不需要真正調(diào)用 access_ok. 后面描述的內(nèi)存存取函數(shù)為你負責這個. 但是, 我們來演示它的使用, 以便你可見到它如何完成.
scull 源碼利用了 ioclt 號中的位段來檢查參數(shù), 在 switch 之前:
int err = 0, tmp;
int retval = 0;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC)
return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR)
return -ENOTTY;
/*
* the direction is a bitmask, and VERIFY_WRITE catches R/W
* transfers. `Type' is user-oriented, while
* access_ok is kernel-oriented, so the concept of "read" and
* "write" is reversed
*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;
在調(diào)用 access_ok 之后, 驅(qū)動可安全地進行真正的傳輸. 加上 copy_from_user 和 copy_touser 函數(shù), 程序員可利用一組為被最多使用的數(shù)據(jù)大小(1, 2, 4, 和 8 字節(jié))而優(yōu)化過的函數(shù). 這些函數(shù)在下面列表中描述, 它們定義在 :
put_user(datum, ptr)
__put_user(datum, ptr)
這些宏定義寫 datum 到用戶空間; 它們相對快, 并且應(yīng)當被調(diào)用來代替 copy_to_user 無論何時要傳送單個值時. 這些宏已被編寫來允許傳遞任何類型的指針到 put_user, 只要它是一個用戶空間地址. 傳送的數(shù)據(jù)大小依賴 prt 參數(shù)的類型, 并且在編譯時使用 sizeof 和 typeof 等編譯器內(nèi)建宏確定. 結(jié)果是, 如果 prt 是一個 char 指針, 傳送一個字節(jié), 以及對于 2, 4, 和 可能的 8 字節(jié).
put_user 檢查來確保這個進程能夠?qū)懭虢o定的內(nèi)存地址. 它在成功時返回 0, 并且在錯誤時返回 -EFAULT. put_user 進行更少的檢查(它不調(diào)用 access_ok), 但是仍然能夠失敗如果被指向的內(nèi)存對用戶是不可寫的. 因此, put_user 應(yīng)當只用在內(nèi)存區(qū)已經(jīng)用 access_ok 檢查過的時候.
作為一個通用的規(guī)則, 當你實現(xiàn)一個 read 方法時, 調(diào)用 __put_user 來節(jié)省幾個周期, 或者當你拷貝幾個項時, 因此, 在第一次數(shù)據(jù)傳送之前調(diào)用 access_ok 一次, 如同上面 ioctl 所示.
get_user(local, ptr)
__get_user(local, ptr)
這些宏定義用來從用戶空間接收單個數(shù)據(jù). 它們象 put_user 和 __put_user, 但是在相反方向傳遞數(shù)據(jù). 獲取的值存儲于本地變量 local; 返回值指出這個操作是否成功. 再次, __get_user 應(yīng)當只用在已經(jīng)使用 access_ok 校驗過的地址.
如果做一個嘗試來使用一個列出的函數(shù)來傳送一個不適合特定大小的值, 結(jié)果常常是一個來自編譯器的奇怪消息, 例如"coversion to non-scalar type requested". 在這些情況中, 必須使用 copy_to_user 或者 copy_from_user.
存取一個設(shè)備由設(shè)備文件上的許可權(quán)控制, 并且驅(qū)動正常地不涉及到許可權(quán)的檢查. 但是, 有些情形, 在保證給任何用戶對設(shè)備的讀寫許可的地方, 一些控制操作仍然應(yīng)當被拒絕. 例如, 不是所有的磁帶驅(qū)動器的用戶都應(yīng)當能夠設(shè)置它的缺省塊大小, 并且一個已經(jīng)被給予對一個磁盤設(shè)備讀寫權(quán)限的用戶應(yīng)當仍然可能被拒絕來格式化它. 在這樣的情況下, 驅(qū)動必須進行額外的檢查來確保用戶能夠進行被請求的操作.
傳統(tǒng)上 unix 系統(tǒng)對超級用戶帳戶限制了特權(quán)操作. 這意味著特權(quán)是一個全有-或-全無的東西 -- 超級用戶可能任意做任何事情, 但是所有其他的用戶被高度限制了. Linux 內(nèi)核提供了一個更加靈活的系統(tǒng), 稱為能力. 一個基于能力的系統(tǒng)丟棄了全有-或全無模式, 并且打破特權(quán)操作為獨立的子類. 這種方式, 一個特殊的用戶(或者是程序)可被授權(quán)來進行一個特定的特權(quán)操作而不必泄漏進行其他的, 無關(guān)的操作的能力. 內(nèi)核在許可權(quán)管理上排他地使用能力, 并且輸出 2 個系統(tǒng)調(diào)用 capget 和 capset, 來允許它們被從用戶空間管理.
全部能力可在 中找到. 這些是對系統(tǒng)唯一可用的能力; 對于驅(qū)動作者或者系統(tǒng)管理員, 不可能不修改內(nèi)核源碼而來定義新的. 設(shè)備驅(qū)動編寫者可能感興趣的這些能力的一個子集, 包括下面:
CAP_DAC_OVERRIDE
這個能力來推翻在文件和目錄上的存取的限制(數(shù)據(jù)存取控制, 或者 DAC).
CAP_NET_ADMIN
進行網(wǎng)絡(luò)管理任務(wù)的能力, 包括那些能夠影響網(wǎng)絡(luò)接口的.
CAP_SYS_MODULE
加載或去除內(nèi)核模塊的能力.
CAP_SYS_RAWIO
進行 "raw" I/O 操作的能力. 例子包括存取設(shè)備端口或者直接和 USB 設(shè)備通訊.
CAP_SYS_ADMIN
一個捕獲-全部的能力, 提供對許多系統(tǒng)管理操作的存取.
CAP_SYS_TTY_CONFIG
進行 tty 配置任務(wù)的能力.
在進行一個特權(quán)操作之前, 一個設(shè)備驅(qū)動應(yīng)當檢查調(diào)用進程有合適的能力; 不這樣做可能導(dǎo)致用戶進程進行非法的操作, 對系統(tǒng)的穩(wěn)定和安全有壞的后果. 能力檢查是通過 capable 函數(shù)來進行的(定義在 ):
int capable(int capability);
在 scull 例子驅(qū)動中, 任何用戶被許可來查詢 quantum 和 quantum 集的大小. 只有特權(quán)用戶, 但是, 可改變這些值, 因為不適當?shù)闹悼赡芎軌牡赜绊懴到y(tǒng)性能. 當需要時, ioctl 的 scull 實現(xiàn)檢查用戶的特權(quán)級別, 如下:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
在這個任務(wù)缺乏一個更加特定的能力時, CAP_SYS_ADMIN 被選擇來做這個測試.
ioctl 的 scull 實現(xiàn)只傳遞設(shè)備的配置參數(shù), 并且象下面這樣容易:
switch(cmd)
{
case SCULL_IOCRESET:
scull_quantum = SCULL_QUANTUM;
scull_qset = SCULL_QSET;
break;
case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_quantum, (int __user *)arg);
break;
case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_quantum = arg;
break;
case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
retval = __put_user(scull_quantum, (int __user *)arg);
break;
case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
return scull_quantum;
case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
retval = __get_user(scull_quantum, (int __user *)arg);
if (retval == 0)
retval = __put_user(tmp, (int __user *)arg);
break;
case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
scull_quantum = arg;
return tmp;
default: /* redundant, as cmd was checked against MAXNR */
return -ENOTTY;
}
return retval;
scull 還包含 6 個入口項作用于 scull_qset. 這些入口項和給 scull_quantum 的是一致的, 并且不值得展示出來.
從調(diào)用者的觀點看(即從用戶空間), 這 6 種傳遞和接收參數(shù)的方法看來如下:
int quantum;
ioctl(fd,SCULL_IOCSQUANTUM, &quantum); /* Set by pointer */
ioctl(fd,SCULL_IOCTQUANTUM, quantum); /* Set by value */
ioctl(fd,SCULL_IOCGQUANTUM, &quantum); /* Get by pointer */
quantum = ioctl(fd,SCULL_IOCQQUANTUM); /* Get by return value */
ioctl(fd,SCULL_IOCXQUANTUM, &quantum); /* Exchange by pointer */
quantum = ioctl(fd,SCULL_IOCHQUANTUM, quantum); /* Exchange by value */
當然, 一個正常的驅(qū)動不可能實現(xiàn)這樣一個調(diào)用模式的混合體. 我們這里這樣做只是為了演示做事情的不同方式. 但是, 正常地, 數(shù)據(jù)交換將一致地進行, 通過指針或者通過值, 并且要避免混合這 2 種技術(shù).
有時控制設(shè)備最好是通過寫控制序列到設(shè)備自身來實現(xiàn). 例如, 這個技術(shù)用在控制臺驅(qū)動中, 這里所謂的 escape 序列被用來移動光標, 改變?nèi)笔〉念伾? 或者進行其他的配置任務(wù). 這樣實現(xiàn)設(shè)備控制的好處是用戶可僅僅通過寫數(shù)據(jù)控制設(shè)備, 不必使用(或者有時候?qū)?只為配置設(shè)備而建立的程序. 當設(shè)備可這樣來控制, 發(fā)出命令的程序甚至常常不需要運行在和它要控制的設(shè)備所在的同一個系統(tǒng)上.
例如, setterm 程序作用于控制臺(或者其他終端)配置, 通過打印 escape 序列. 控制程序可位于和被控制的設(shè)備不同的一臺計算機上, 因為一個簡單的數(shù)據(jù)流重定向可完成這個配置工作. 這是每次你運行一個遠程 tty 會話時所發(fā)生的事情: escape 序列在遠端被打印但是影響到本地的 tty; 然而, 這個技術(shù)不局限于 ttys.
通過打印來控制的缺點是它給設(shè)備增加了策略限制; 例如, 它僅僅當你確信在正常操作時控制序列不會出現(xiàn)在正被寫入設(shè)備的數(shù)據(jù)中. 這對于 ttys 只是部分正確的. 盡管一個文本顯示意味著只顯示 ASCII 字符, 有時控制字符可潛入正被寫入的數(shù)據(jù)中, 并且可能, 因此, 影響控制臺的配置. 例如, 這可能發(fā)生在你顯示一個二進制文件到屏幕時; 產(chǎn)生的亂碼可能包含任何東西, 并且最后你常常在你的控制臺上出現(xiàn)錯誤的字體.
通過寫來控制是當然的使用方法了, 對于不用傳送數(shù)據(jù)而只是響應(yīng)命令的設(shè)備, 例如遙控設(shè)備.
例如, 被你們作者當中的一個編寫來好玩的驅(qū)動, 移動一個 2 軸上的攝像機. 在這個驅(qū)動里, 這個"設(shè)備"是一對老式步進電機, 它們不能真正讀或?qū)? 給一個步進電機"發(fā)送數(shù)據(jù)流"的概念沒有任何意義. 在這個情況下, 驅(qū)動解釋正被寫入的數(shù)據(jù)作為 ASCII 命令并且轉(zhuǎn)換這個請求為脈沖序列, 來操縱步進電機. 這個概念類似于, 有些, 你發(fā)給貓的 AT 命令來建立通訊, 主要的不同是和貓通訊的串口必須也傳送真正的數(shù)據(jù). 直接設(shè)備控制的好處是你可以使用 cat 來移動攝像機, 而不必寫和編譯特殊的代碼來發(fā)出 ioctl 調(diào)用.
當編寫面向命令的驅(qū)動, 沒有理由實現(xiàn) ioctl 命令. 一個解釋器中的額外命令更容易實現(xiàn)并使用.
有時, 然而, 你可能選擇使用其他的方法:不必轉(zhuǎn)變 write 方法為一個解釋器和避免 ioctl, 你可能選擇完全避免寫并且專門使用 ioctl 命令, 而實現(xiàn)驅(qū)動為使用一個特殊的命令行工具來發(fā)送這些命令到驅(qū)動. 這個方法轉(zhuǎn)移復(fù)雜性從內(nèi)核空間到用戶空間, 這里可能更易處理, 并且?guī)椭3烛?qū)動小, 而拒絕使用簡單的 cat 或者 echo 命令.
[20]?但是, 這個文件的維護在后來有些少見了.
[21]?實際上, 所有的當前使用的 libc 實現(xiàn)(包括 uClibc) 僅將 -4095 到 -1 的值當作錯誤碼. 不幸的是, 能夠返回大的負數(shù)而不是小的, 沒有多大用處.
更多建議: