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


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位用戶態程序的方法,
可以通過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位的指針(仍然是用戶指針)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM