2015年5月5日 星期二

[轉] ioctl,unlocked_ioctl和compat_ioctl

kernel 2.6.35 及之前的版本中struct file_operations 一共有3個ioctl :
ioctl
unlocked_ioctl
compat_ioctl
現在只有unlocked_ioctl和compat_ioctl 了

在kernel 2.6.36 中已經完全刪除了struct file_operations 中的ioctl 函數指針,取而代之的是unlocked_ioctl。

這個指針函數變了之後最大的影響是參數中少了inode,不過這個不是問題。
因為用戶程序中的ioctl對應的系統調用接口沒有變化,所以用戶程序不需要改變,一切都交給內核處理了。

如果想在 unlocked_ioctl 中獲得 inode 等信息可以用如下方法:
struct inode *inode = file->f_mapping->host;
struct block_device *bdev = inode->i_bdev;
struct gendisk *disk = bdev->bd_disk;
fmode_t mode = file->f_mode;
struct backing_dev_info *bdi;

這次內核函數的變化引出了一個問題,從ioctl系統調用往後,真正的ioctl調用順序是什麼?為什麼compat_ioctl 不被調用?
compat_ioctl被使用在用戶空間為32位模式,而內核運行在64位模式時。這時候,需要將64位轉成32位。
以下是2.6.36的情況:
SYSCALL_DEFINE3(ioctl ...) compat_sys_ioctl (是否直接調用compat_ioctl 取決於compat_ioctl 是否存在) 
| | |-----> compat_ioctl
| | |------>do_vfs_ioctl (下一步的調用取決於file->f_path.dentry->d_inode->i_node) 
| |------>file_ioctl
| | |---------------------->vfs_ioctl
|------->unlock_ioctl

其實compat_ioctl 沒有被調用的原因是compat_sys_ioctl 沒有被調用,
而它沒有被調用的原因似乎是壓根就沒有編譯到內核中,因為我沒有找到調用這個函數的代碼。
unlocked_ioctl 實際上取代了用了很久的ioctl,主要的改進就是不再需要上大內核鎖(調用之前不再先調用lock_kernel()然後再unlock_kernel())
總的來說kernel 開發者正在試圖朝移除大內核鎖的方向努力,ioctl的移除就是被革命了。
相信以後越來越多的內核函數會擺脫大內核鎖的依賴,並且大內核鎖最終會被移除。

轉載自:http://lp007819.wordpress.com/2011/01/06/kernel-2-6-36-ioctl-%E5%8F%98%E6%9B%B4/一、內核原型(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 下的 ioctlcompat_ioctl ioctl32 Unknown cmd fd
前段時間將我們的程式移植到Mips64Linux 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,內核態和用戶態不一致,對於這種已知類型,內核代碼已經做了轉換,因為他知道該怎麼轉,
所以我們的程式調用readwrite,等系統調用,都能正確的返回結果。
再來看看ioctl系統調用,
使用者態系統調用,int ioctl(int fd, int request, ...);內核中對應的函數
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
request
是請求號,用來區分不同得到請求。後面的可變參數隨便選。
如果想傳入或傳出自訂結構,可以傳個指標類型。
如果沒資料傳遞,可以空著。
當然也可以傳這個intchar之類的類型來向內核傳遞資料。
問題來了,內核不知道你傳遞的是那種類型,他無法給你轉化。
總結一下:
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)
注意_IOWRIOR他們的最後一個參數是一個資料類型,展開時會包含sizeof()操作,
於是32bit使用者態程式和64bit內核之間,生成的ioctlrequest號很可能就會不同,
compat函數中switch()的時候,就會switch不到。
要想辦法避免:
提供64bit32bit大小一致的結構。
在使用者態下提供一個偽結構,偽結構和內核內的結構寬度一致。
_IO巨集,而不用_IOWR_IOR宏,反正只是為了得到一個號碼,實際傳輸資料大小,自己心理有數,內核代碼處理好就行了。
4). 
如果compat收到的最後參數arg是一個用戶態指標它在用戶態是32位的,在內核中為了保證安全,
可以使用compat_ptr(art)宏將其安全的轉化為一個64位的指標(仍然是用戶指標)


問題:

ioctl32: Unknown cmd

如果遇到這個錯誤 ... 都是在64/32轉換的問題
在你的 driver 加上 compat_ioctl .. 他會自己轉換到 compat_ioctl去

沒有留言:

張貼留言