构造IOCTL命令的学习心得-----_IO, _IOR, _IOW, _IOWR 幻数的理解
在編寫ioctl代碼之前,需要選擇對應不同命令的編號。為了防止對錯誤的設備使用正確的命令,命令號應該在系統范圍內唯一,這種錯誤匹配并不是不會發生,程序可能發現自己正在試圖對FIFO和audio等這類非串行設備輸入流修改波特率,如果每一個ioctl命令都是唯一的,應用程序進行這種操作時就會得到一個EINVAL錯誤,而不是無意間成功地完成了意想不到的操作。
??????? 在驅動程序里, ioctl() 函數上傳送的變量 cmd 是應用程序用于區別設備驅動程序請求處理內容的值。cmd除了可區別數字外,還包含有助于處理的幾種相應信息。 cmd的大小為 32位,共分 4 個域:
????????bit31~bit30? 2位為 “區別讀寫” 區,作用是區分是讀取命令還是寫入命令。
??????? bit29~bit15? 14位為 "數據大小" 區,表示 ioctl() 中的 arg 變量傳送的內存大小。
??????? bit20~bit08? 8位為 “魔數"(也稱為"幻數")區,這個值用以與其它設備驅動程序的 ioctl 命令進行區別。
??????? bit07~bit00? 8位為 "區別序號" 區,是區分命令的命令順序序號。
???????像命令碼中的 “區分讀寫區” 里的值可能是 _IOC_NONE (0值)表示無數據傳輸,_IOC_READ (讀), _IOC_WRITE (寫) , _IOC_READ|_IOC_WRITE (雙向)。
內核定義了 _IO() , _IOR() , IOW() 和 _IOWR() 這 4 個宏來輔助生成上面的 cmd 。下面分析 _IO() 的實現,其它的類似。
?
???要按Linux內核的約定方法為驅動程序選擇ioctl編號,應該首先看看include/asm/ioctl.h和Doucumention/ioctl-number.txt這兩個文件。頭文件定義了要使用的位字段:類型(幻數)、序數、傳送方向以及參數大小等。ioctl-number.txt文件中羅列了內核所使用的幻數,選擇自己的幻數要避免和內核沖突。以下是對include/asm/ioctl.h中定義的宏的注釋:
#define?????????_IOC_NRBITS??????????8???????????????????????????????//序數(number)字段的字位寬度,8bits
#define?????????_IOC_TYPEBITS??????8???????????????????????????????//幻數(type)字段的字位寬度,8bits
#define?????????_IOC_SIZEBITS???????14??????????????????????????????//大小(size)字段的字位寬度,14bits
#define?????????_IOC_DIRBITS?????????2???????????????????????????????//方向(direction)字段的字位寬度,2bits
? ?
#define?????????_IOC_NRMASK????????((1?<<?_IOC_NRBITS)-1)????//序數字段的掩碼,0x000000FF
#define?????????_IOC_TYPEMASK???((1?<<?_IOC_TYPEBITS)-1)??//幻數字段的掩碼,0x000000FF
#define?????????_IOC_SIZEMASK?????((1?<<?_IOC_SIZEBITS)-1)???//大小字段的掩碼,0x00003FFF
#define?????????_IOC_DIRMASK??????((1?<<?_IOC_DIRBITS)-1)????//方向字段的掩碼,0x00000003
? ?
#define????????_IOC_NRSHIFT???????0?????????????????????????????????????????????????????????//序數字段在整個字段中的位移,0
#define????????_IOC_TYPESHIFT???(_IOC_NRSHIFT+_IOC_NRBITS)?????????//幻數字段的位移,8
#define????????_IOC_SIZESHIFT????(_IOC_TYPESHIFT+_IOC_TYPEBITS)??//大小字段的位移,16
#define????????_IOC_DIRSHIFT??????(_IOC_SIZESHIFT+_IOC_SIZEBITS)????//方向字段的位移,30
? ?
/*
?*?Direction?bits.
?*/
#define?_IOC_NONE?????0U?????//沒有數據傳輸
#define?_IOC_WRITE???1U?????//向設備寫入數據,驅動程序必須從用戶空間讀入數據
#define?_IOC_READ?????2U?????//從設備中讀取數據,驅動程序必須向用戶空間寫入數據
? ?
? ?
/*
*_IOC?宏將dir,type,nr,size四個參數組合成一個cmd參數
#define?_IOC(dir,type,nr,size)?\
???????(((dir)??<<?_IOC_DIRSHIFT)?|?\
????????((type)?<<?_IOC_TYPESHIFT)?|?\
????????((nr)???<<?_IOC_NRSHIFT)?|?\
????????((size)?<<?_IOC_SIZESHIFT))
? ?
/*
*?used?to?create?numbers?
*/
//構造無參數的命令編號
#define?_IO(type,nr)?????????????_IOC(_IOC_NONE,(type),(nr),0)?
//構造從驅動程序中讀取數據的命令編號
? ?
#define?_IOR(type,nr,size)?????_IOC(_IOC_READ,(type),(nr),sizeof(size))?
//用于向驅動程序寫入數據命令
#define?_IOW(type,nr,size)????_IOC(_IOC_WRITE,(type),(nr),sizeof(size))
//用于雙向傳輸
#define?_IOWR(type,nr,size)?_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
? ?
/*?
*used?to?decode?ioctl?numbers..
?*/
//從命令參數中解析出數據方向,即寫進還是讀出
#define?_IOC_DIR(nr)??????????(((nr)?>>?_IOC_DIRSHIFT)?&?_IOC_DIRMASK)
//從命令參數中解析出幻數type
#define?_IOC_TYPE(nr)??????????????(((nr)?>>?_IOC_TYPESHIFT)?&?_IOC_TYPEMASK)
//從命令參數中解析出序數number
#define?_IOC_NR(nr)???????????(((nr)?>>?_IOC_NRSHIFT)?&?_IOC_NRMASK)
//從命令參數中解析出用戶數據大小
#define?_IOC_SIZE(nr)?????????(((nr)?>>?_IOC_SIZESHIFT)?&?_IOC_SIZEMASK)
? ?
/*?...and?for?the?drivers/sound?files...?*/
? ?
#define?IOC_IN????????????(_IOC_WRITE?<<?_IOC_DIRSHIFT)
#define?IOC_OUT?????????(_IOC_READ?<<?_IOC_DIRSHIFT)
#define?IOC_INOUT?????((_IOC_WRITE|_IOC_READ)?<<?_IOC_DIRSHIFT)
#define?IOCSIZE_MASK??????(_IOC_SIZEMASK?<<?_IOC_SIZESHIFT)
#define?IOCSIZE_SHIFT??????(_IOC_SIZESHIFT)
以上內容轉自:article.phpfans.net/
我的理解:
? ? ???假如我定義了一個命令MY_CMD:
#define MY_CMD_MAGIC 0xdf????????????? //type字段,由于字段寬度為8 bits,所以不能大于0xff
#define MY_CMD? _IOW(MY_CMD_MAGIC,0,unsigned int)????
?????于是有命令MY_CMD各組成字段(dir,size,type,nr)分別為:01(=_IOC_WRITE),00 0000 0000 0100(=sizeof(unsigned int)),1101 1111(=MY_CMD_MAGIC),0000 0000(=0)。用十六進制表示即0x4004df00。這個32 bits的"整數"就是該命令的編號(LDD3原文是command number),也就是命令MY_CMD在系統中的"身份證號碼"了!
?????為什么上面我用"身份證號碼"作比喻呢?眾所周知,一個國家里所有公民的身份證號都是各不相同的。身份證編號有一定的規則:即把身份證號劃分成幾個字段,各段位數可不等,每個字段編碼都有它的實際意義,例如我們現在用的的身份證前N位(不記得具體是多少了)表示一個具體的省、市、等地區,不同地區的人該字段肯定不同了;另外有個表示出生年月的字段(據說以前整個號碼最后一位偶數表示男性,奇數表示女性,現在貌似沒這個規則了,此為題外話)。
?????類似地,我們要為系統里所有的IOCTL命令編號。我們身份證用的是15(上一代是18位)位十進制數編碼(最后一位可能是拉丁字母);我們用32位二進制數為IOCTL命令編碼,把它劃分成4個字段,每段也有它的實際意義。要保證每個命令編號為系統唯一,主要靠命令的type和nr字段。我們稱type字段內容為magic number,即幻數,它表示命令的類型。
????看到上段紅色這句話,我想可能有細心的人會問:"那么命令到底有哪些類型呢?"老實說我也不知道正確答案。大概因為大家都知道基本數據類型有整形、浮點型、字符型...人的性格類型有外向型、內向型...這些我們常見的類型都是可以用文字來枚舉描述的,所以潛意識就覺得有類型就應該有文字可描述吧?;氐秸},我想每個magic number,就像上面的宏定義中:#define MY_CMD_MAGIC 0xdf,MY_CMD_MAGIC就是類型名了吧!不知道我的想法對不對?!反正大家知道一個magic number就對應唯一一種命令類型就是了。LDD原文中有一段:
type
The magic number. Just choose one number (after consulting ioctl-number.txt)
and use it throughout the driver. This field is eight bits wide (_IOC_TYPEBITS).
原文是說type的內容叫幻數(magic number),強調它是一個8位二進制數(number)。
因此不同type的命令就有不同的magic number,因此命令編碼自然就不同了。但如果兩個命令type相同,它們的magic number就相同,于是就不能僅靠type字段區分了。于是nr字段就起作用了:
number
The ordinal (sequential) number. It's eight bits (_IOC_NRBITS) wide.
nr(number)即序號,一般地我們從0開始編號。由于nr字段為8位二進制數,所以nr的取值范圍為0~255。同一種type的命令每個nr值對應唯一一個命令。
其他兩個字段在這里不作深究。
值得一提的是LDD3里這么一段話:
The header also defines macros that may be used in your driver to decode the num-
bers: _IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr), and _IOC_SIZE(nr). We won't go
into any more detail about these macros because the header file is clear, and sample
code is shown later in this section.
根據ioctl.h文件的定義,很明顯_IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)這幾個宏里面的參數就是一個IOCTL命令,即一個32位的二進制數,而文件中參數居然用nr表示!當然用nr表示不是邏輯上的錯誤。但是別忘了文件中還有_IOW(type,nr,size)這樣的定義IOCTL命令的宏,這其中的參數nr是IOCTL命令編號中的一個nr字段,一個8位的二進制數!我想很多新人都會對此產生莫大的疑惑!所以我認為把_IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)這幾個解碼宏的參數改用cmd表示更恰當!
但是,我知道這不是LDD3作者的錯,因為內核頭文件里面也是這么表示的。我想內核開發者不可能沒意識到這個問題。因此,我猜測這其中肯定有個歷史原因:大概以前版本的命令不管type是否一樣,nr字段的值都是唯一的,于是僅靠nr字段就可以解碼出一個IOCTL命令的其他字段吧?!但即使這樣_IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)也沒必要保留這種寫法啊!到底誰可以告訴我真相?
LDD3沒有對_IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)里面的nr作任何解釋,只是實例中有如下用法:
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
可見那個nr參數完全就是我所說的32位的IOCTL命令編碼???#xff0c;既然這樣好歹也對著個confusion作一下簡單的解釋啊!如果LDD3一書那"既不承認,也不否認"的曖昧態度讓我真讓我哭笑不得的話,那么國內某書(具體哪本我就不說了)簡直令我抓狂,我摘書中的兩段話如下:
_IO(type,nr):定義一個沒有數據傳輸的命令編號。type為幻數,nr為命令編號。...
...
_IOC_DIR(nr):?獲得命令編號的命令傳輸方向(direction)。這個宏的參數就是命令編號。
暈了沒有?紅色標記處的nr分明就是命令編號的nr(即序號)字段嘛!真XX@#¥&*$!
對國內的書越來越失望了!沒有多少自己的東西就罷了!連翻譯的都翻不好!誤人子弟!~
總結
以上是生活随笔為你收集整理的构造IOCTL命令的学习心得-----_IO, _IOR, _IOW, _IOWR 幻数的理解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux字符设备驱动的 ioctl 幻
- 下一篇: 《Linux设备驱动程序》学习2—高级字