linux2.6內核compat_ioctl函數


一、內核原型(linux2.6.28-7)

     long (*compat_ioctl)(struct tty_struct *tty, struct file * file,
                     unsigned int cmd, unsigned long arg);
     implement ioctl processing for 32 bit process on 64 bit system
     Optional

二、What is compat_ioctl


Consider a scenario where application is 32 bit and kernel as well as architecture is 64 bit. In this case, when an application calls ioctl(), there should be some mechanism in kernel to handle 32 bit to 64 bit conversion. This conversion is especially required when user passes objects of type "long" and "void *".

There is one more method called as "compat_ioctl()" that a 64 bit driver has to implement. It gets called when 64 bit kernel gets ioctl() call from 32 bit user.

Tasks to be done by compat_ioctl() :

1. Acquire BKL, since kernel calls compat_ioctl without BKL.
2. 32 to 64 bit conversion for long and pointer objects passed by user
3. Process input data, get results.
4. 64 to 32 bit conversion in order to pass the output data back to user
5. Release BKL

三、中文檔案


Linux 64Bit 下的 ioctl和compat_ioctl ioctl32 Unknown cmd fd

前段時間將我們的程序移植到Mips64的Linux 2.6環境下,做可行性試驗。
由於用戶態程程序規模太大,而且之前沒有對64bit的情況做考慮,
所以,用戶態程序任然使用32位模式編譯,內核運行在64bit。

我們有一個內核模塊之前是在2.4.21下的,拿到2.6後,把部分api做了些更改,直接編譯並加載。
用戶態程序在調用ioctl的時候,總是失敗。
dmesg看一下內核信息,有如下類似信息:
ioctl32(add_vopp:9767): Unknown cmd fd(3) cmd(80048f00){00} arg(ff240ae0)
後來在內核中的ioctl中添加debug代碼,發現根本沒調用到內核中的ioctl函數。
經過查找,發現了以下資源
The new way of ioctl()
32 bit user/64 bit kernel
What is compat_ioctl () 
more on compat_ioctl

產生問題的分析:
我們的程序通過Linux的
ssize_t read(int fd, void *buf, size_t count); 
系統調用從虛擬設備中讀取內核中。
使用
int ioctl(int fd, int request, ...);
系統調用來對設備進行控制,並傳遞一些數據。
ioctl是我們擴展非標準系統調用的手段。
read系統調用,對應內核中struct file_operations 結構中的
ssize_t (*read) (struct file *filp, char *buf, size_t count, loff_t *f_pos)
當用戶態位32bit, 內核位64bit的時候,
用戶態的程序,和內核態的程序,在數據類型不一致。
比如指針,用戶態的指針實際上是unsigned long 4Byte
內核態的指針是unsigned ong: 8Byte.
對於這種不一致,從用戶態陷入內核態的時候,大部分標準調用的參數已經做了相應的轉化。
不存在問題。比如ssize_t,內核態和用戶態不一致,
對於這種已知類型,內核代碼已經做了轉換,因爲他知道該怎麼轉,
所以我們的程序調用read,write,等系統調用,都能正確的返回結果。

再來看看ioctl系統調用,
用戶態系統調用,int ioctl(int fd, int request, ...);
內核中對應的函數
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
request是請求號,用來區分不同得到請求。後面的可變參數隨便選。
如果想傳入或傳出自定義結構,可以傳個指針類型。
如果沒數據傳遞,可以空着。
當然也可以傳這個int或char之類的類型來向內核傳遞數據。
問題來了,內核不知道你傳遞的是那種類型,他無法給你轉化。
總結一下:
1). Linux2.6對64bit kernel 在struct file_operation中增加了一個成員
long (*compat_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);
用來提供32位用戶態程序的方法,
可以通過HAVE_COMPAT_IOCTL宏來判斷該成員是否存在,並提供相應的操作方法。
2). 和int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)相比
compat_ioctl少了inode參數, 可以通過filp->f_dentry->d_inode方法獲得。
3). 關於ioctl的請求號,經常是通過宏定義生成的 舉例如下:
#define IoctlGetNetDeviceInfo    _IOWR(Ioctl_Magic, 1, struct_NetDeviceInfo)
#define IoctlNetQueueInit          _IOR(Ioctl_Magic, 4, struct_NetDeviceListen)
#define IoctlNetQueueDestroy     _IO(Ioctl_Magic, 5)
注意_IOWR和IOR宏, 他們的最後一個參數是一個數據類型,展開時會包含sizeof()操作,
於是32bit用戶態程序和64bit內核之間,生成的ioctl的request號很可能就會不同,
在compat函數中switch()的時候,就會switch不到。
要想辦法避免:
提供64bit和32bit大小一致的結構。
在用戶態下提供一個僞結構,僞結構和內核內的結構寬度一致。
用_IO宏,而不用_IOWR或_IOR宏,反正只是爲了得到一個號碼,實際傳輸數據大小,自己心理有數,內核代碼處理好就行了。

4). 如果compat收到的最後參數arg是一個用戶態指針, 它在用戶態是32位的,在內核中爲了保證安全,
可以使用compat_ptr(art)宏將其安全的轉化爲一個64位的指針(仍然是用戶指針)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章