?? (ldd) ch05-字符設備驅動程序的擴展操作.txt
字號:
(LDD) Ch05-字符設備驅動程序的擴展操作
第5章 字符設備驅動程序的擴展操作
在關于字符設備驅動程序的那一章中,我們構建了一個完整的設備驅動程序,從中用戶
可以讀也可以寫。但實際一個驅動程序通常會提供比同步read和write更多的功能?,F在
如果出了什么毛病,我已經配備了調試工具,我們可以大膽的實驗并實現新操作。
通過補充設備讀寫操作的功能之一就是控制硬件,最常用的通過設備驅動程序完成控制
動作的方法就是實現ioctl方法。另一種方法是檢查寫到設備中的數據流,使用特殊序列
做為控制命令。盡管有時也使用后者,但應該盡量避免這樣使用。不過稍后我們還是會
在本章的“非ioctl設備控制”一節中介紹這項技術。
正如我在前一章中所猜想的,ioctl系統調用為驅動程序執行“命令”提供了一個設備相
關的入口點。與read和其他方法不同,ioctl是設備相關的,它允許應用程序訪問被驅動
硬件的特殊功能――配置設備以及進入或退出操作模式。這些“控制操作”通常無法通
硬件的特殊功能――配置設備以及進入或退出操作模式。這些“控制操作”通常無法通
過read/write文件操作完成。例如,你向串口寫的所有數據都通過串口發送出去了,你
無法通過寫設備改變波特率。這就是ioctl所要做的:控制I/O通道。
實際設備(與scull不同)的另一個重要功能是,讀或寫的數據需要同其他硬件交互,需
要某些同步機制。阻塞型I/O和異步觸發的概念將滿足這些需求,本章將通過一個改寫的
scull設備介紹這些內容。驅動程序利用不同進程間的交互產生異步事件。與最初的scul
l相同,你無需特殊硬件來測試驅動程序是否可以工作。直到第8章“硬件管理”我才會
真正去與硬件打交道。
ioctl
在用戶空間內調用ioctl函數的原型大致如下:
(代碼)
由于使用了一連串的“.”的緣故,該原型在Unix系統調用列表之中非常突出,這些點代
表可變數目參數。但是在實際系統中,系統調用實際上不會有可變數目個參數。因為用
戶程序只能通過第2章“編寫和運行模塊”的“用戶空間和內核空間”一節中介紹的硬件
“門”才能訪問內核,系統調用必須有精確定義的參數個數。因此,ioctl的第3個參數
事實上只是一個可選參數,這里用點只是為了在編譯時防止編譯器進行類型檢查。第3個
參數的具體情況與要完成的控制命令(第2個參數)有關。某些命令不需要參數,某些需
要一個整數做參數,而某些則需要一個指針做參數。使用指針通常是可以用來向ioctl傳
遞任意數目數據;設備可以從用戶空間接收任意大小的數據。
遞任意數目數據;設備可以從用戶空間接收任意大小的數據。
系統調用的參數根據方法的聲明傳遞給驅動程序方法:
(代碼)
inode和filp指針是根據應用程序傳遞的文件描述符fd計算而得的,與read和write的用
法一致。參數cmd不經修改地傳遞給驅動程序,可選的arg參數無論是指針還是整數值,
它都以unsigned long的形式傳遞給驅動程序。如果調用程序沒有傳遞第3個參數,驅動
程序所接收的arg沒有任何意義。
由于附加參數的類型檢查被關閉了,如果非法參數傳遞給ioctl,編譯器無法向你報警,
程序員在運行前是無法注意這個錯誤的。這是我所見到的ioctl語義方面的唯一一個問題
。
如你所想,大多數ioctl實現都包括一個switch語句來根據cmd參數選擇正確的操作。不
同的命令對應不同的數值,為了簡化代碼我們通常會使用符號名代替數值。這些符號名
都是在預處理中賦值的。不同的驅動程序通常會在它們的頭文件中聲明這些符號;scull
就在scull.h中聲明了這些符號。
選擇ioctl命令
在編寫ioctl代碼之前,你需要選擇對應不同命令的命令號。遺憾的是,簡單地從1開始
選擇號碼是不能奏效的。
選擇號碼是不能奏效的。
為了防止對錯誤的設備使用正確的命令,命令號應該在系統范圍內是唯一的。這種失配
并不是不很容易發生,程序可能發現自己正在對象FIFO和kmouse這類非串口輸入流修改
波特率。如果每一個ioctl命令都是唯一的,應用程序就會獲得一個EINVAL錯誤,而不是
無意間成功地完成了操作。
為了達到唯一性的目的,每一個命令號都應該由多個位字段組成。Linux的第一版使用了
一個16位整數:高8位是與設備相關的“幻”數,低8位是一個序列號碼,在設備內是唯
一的。這是因為,用Linus的話說,他有點“無頭緒”,后來才接收了一個更好的位字段
分割方案。遺憾的是,很少有驅動程序使用新的約定,這就挫傷了程序員使用新約定的
熱情。在我的源碼中,為了發掘這種約定都提供了那些功能,同時防止被其他開發人員
當成異教徒而禁止,我使用了新的定義命令的方法。
為了給我的驅動程序選擇ioctl號,你應該首先看看include/asm/ioctl.h和Documentati
on/ioctl-number.txt這兩個文件。頭文件定義了位字段:類型(幻數),基數,傳送方
向,參數的尺寸等等。ioctl-number.txt文件中羅列了在內核中使用的幻數。這個文件
的新版本(2.0以及后繼內核)也給出了為什么應該使用這個約定的原因。
很不幸,在1.2.x中發行的頭文件沒有給出切分ioctl位字段宏的全集。如果你需要象我
的scull一樣使用這種新方法,同時還要保持向后兼容性,你使用scull/sysdep.h中的若
干代碼行,我在那里給出了解決問題的文檔的代碼。
現在已經不贊成使用的選擇ioctl號碼的舊方法非常簡單:選擇一個8位幻數,比如“k”
(十六進制為0x6b),然后加上一個基數,就象這樣:
(代碼)
如果應用程序和驅動程序都使用了相同的號碼,你只要在驅動程序里實現switch語句就
可以了。但是,這種在傳統Unix中有基礎的定義ioctl號碼的方法,不應該再在新約定中
使用。這里我介紹就方法只是想給你看看一個ioctl號碼大致是個什么樣子的。
新的定義號碼的方法使用了4個位字段,它們有如下意義。下面我所介紹的新符號都定義
在<linux/ioctl.h>中。
類型
幻數。選擇一個號碼,并在整個驅動程序中使用這個號碼。這個字段有8位寬(_IOC_TYP
EBITS)。
號碼
基(序列)數。它也是8位寬(_IOC_NRBITS)。
方向
方向
如果該命令有數據傳輸,它定義數據傳輸的方向??梢允褂玫闹涤?,_IOC_NONE(沒有數
據傳輸),_IOC_READ,_IOC_WRITE和_IOC_READ | _IOC_WRITE(雙向傳輸數據)。數據
傳輸是從應用程序的角度看的;IOC_READ意味著從設備中讀數據,驅動程序必須向用戶
空間寫數據。注意,該字段是一個位屏蔽碼,因此可以用邏輯AND操作從中分解出_IOC_R
EAD和_IOC_WRITE。
尺寸
所涉及的數據大小。這個字段的寬度與體系結構有關,當前的范圍從8位到14位不等。你
可以在宏_IOC_SIZEBITS中找到某種體系結構的具體數值。不過,如果你想要你的驅動程
序可移植,你只能認為最大尺寸可達255個字節。系統并不強制你使用這個字段。如果你
需要更大尺度的數據傳輸,你可以忽略這個字段。下面我們將介紹如何使用這個字段。
包含在<linux/ioctl.h>之中的頭文件<asm/ioctl.h>定義了可以用來構造命令號碼的宏
:_IO(type,nr),_IOR(type,nr,size),_IOW(type,nr,size)和IOWR(type,nr,size)。
每一個宏都對應一種可能的數據傳輸方向,其他字段通過參數傳遞。頭文件還定義了解
碼宏:_IOC_DIR(nr),_IOC_TYPE(nr),_IOC_NR(nr)和_IOC_SIZE(nr)。我不打算詳細介
紹這些宏,頭文件里的定義已經足夠清楚了,本節稍后會給出樣例。
這里是scull中如果定義ioctl命令的。特別地,這些命令設置并獲取驅動程序的配置參
數。在標準的宏定義中,要傳送的數據項的尺寸有數據項自身的實例代表,而不是sizeo
數。在標準的宏定義中,要傳送的數據項的尺寸有數據項自身的實例代表,而不是sizeo
f(item),這是因為sizeof是宏擴展后的一部分。
(代碼)
最后一條命令,HARDRESET,用來將模塊使用計數器復位為0,這樣就可以在計數器發生
錯誤時就可以卸載模塊了。實際的源碼定義了從IOCHQSET到HARDRESET間的所有命令,但
這里沒有列出。
我選擇用兩種方法實現整數參數傳 莰D―通過指針和顯式數值,盡管根據已有的約定,i
octl應該使用指針完成數據交換。同樣,這兩種方法還用于返回整數:通過指針和設置
返回值。如果返回值是正的,這就可以工作;對與任何一個系統調用的返回值,正值是
受保護的(如我們在read和write所見到的),而負值則被認為是一個錯誤值,用其設置
用戶空間中的errno變量。
“交換”和“移位”操作并不專用于scull設備。我實現“交換”操作是為了給出“方向
”字段的所有可能值,而“移位”操作則將“告知”和“查詢”操作組合在一起。某些
時候是需要原子性*測試兼設置這類操作的――特別是當應用程序需要加鎖和解鎖時。
顯式的命令基數沒有什么特殊意義。它只是用來區分命令的。事實上,由于ioctl號碼的
“方向”為會有所不同,你甚至可以在讀命令和寫命令中使用同一個基數。我選擇除了
在聲明中使用基數外,其他地方都不使用它,這樣我就不必為符號值賦值了。這也是為
什么顯式的號碼出現在上面的定義中。我只是向你介紹一種使用命令號碼的方法,你可
什么顯式的號碼出現在上面的定義中。我只是向你介紹一種使用命令號碼的方法,你可
以自由地采用不同的方法使用它。
當前,參數cmd的值內核并沒有使用,而且以后也不可能使用。因此,如果你想偷懶,你
可以省去上面那些復雜的聲明,而直接顯式地使用一組16位數值。但另一方面,如果你
這樣做了,你就無法從使用位字段中受益了。頭文件<linux/kd.h>就是這種舊風格方法
的例子,但是它們并不是因為偷懶才這樣做的。修改這個文件需要重新編譯許多應用程
序。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -