1. 程式人生 > >struct file_operations中 ioctl 和 unlocked_ioctl

struct file_operations中 ioctl 和 unlocked_ioctl

轉載自 "http://blog.chinaunix.net/uid-20543672-id-3015637.html"

很久都沒有寫驅動程式碼了,對於一些驅動相關的核心變化也沒有怎麼關心。這次重遊《LDD3》獲益良多,其值對於struct file_operations中ioctl的消失也讓我長了不少見識。
當年看《LDD3》的時候已經注意到了書中對ioctl的評價不是很好:“ioctl呼叫的非結構化本質導致眾多核心開發者傾向於放棄它。” ,而在這次閱讀3.0程式碼的時候,這個成員在struct file_operations中早已消失了。這個激起了我學習的興趣,以下是對這個ioctl的學習小結:

1、消失的確切時間


    ioctl的消失到底是從哪個版本開始的?網上給出的時間是2.6.36開始。網上就是這麼說,但是自己必須找到程式碼中的證據。於是我通過git搜尋主線核心程式碼,找到的刪除ioctl的那個提交:

  1. commit b19dd42faf413b4705d4adb38521e82d73fa4249
  2. Author: Arnd Bergmann
  3. Date: Sun Jul 4 00:15:10 2010 +0200
  4. bkl: Remove locked .ioctl file operation
  5. The last user is gone, so we can safely remove this
  6. Signed-off-by: Arnd Bergmann
  7. Cc: John Kacur
  8. Cc: Al Viro
  9. Cc: Thomas Gleixner
  10. Signed-off-by: Frederic Weisbecker

好不容易找到了這個提交,好的,這樣就可以確定消失的時間了:

  1. git tag --contains b19dd42
  2. v2.6.36
  3. v2.6.36-rc1
  4. v2.6.36-rc2
  5. v2.6.36-rc3
  6. v2.6.36-rc4
  7. v2.6.36-rc5
  8. v2.6.36-rc6
  9. v2.6.36-rc7
  10. v2.6.36-rc8
  11. ......以下省略ooxx行

可以證明ioctl消失的版本是v2.6.35到v2.6.36-rc1間,於是我匯出了v2.6.35到v2.6.36-rc1的補丁,果真在其中!
git diff v2.6.35..v2.6.36-rc1 >

../temp.patch

補丁過大不易上傳,請自行生成。

2、消失的原因

   簡單的概括:這次ioctl的消失,並不是要把ioctl清理出去,而是要逐步的清理大核心鎖(BKL)。

   這個讓ioctl消失的過渡期長達5年,從2005年開始核心黑客就開始替換ioctl了。具體的原因lwn.net中有一篇很好的文章:The new way of ioctl()。我將他翻譯了一下:

3、ioctl的替代者

    對於原來的ioctl,其實可以叫做locked ioctl。這個其實是相對於他的替代方法來講的。我們來看看2.6.35以前在struct file_operations中有關ioctl的成員:

  1. /*
  2.  * NOTE:
  3.  * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl
  4.  * can be called without the big kernel lock heldin all filesystems.
  5.  */
  6. struct file_operations {
  7.     struct module *owner;
  8.     loff_t (*llseek)(struct file*, loff_t,int);
  9.     ssize_t (*read)(struct file*, char __user*, size_t, loff_t*);
  10.     ssize_t (*write)(struct file*,const char __user*, size_t, loff_t*);
  11.     ssize_t (*aio_read)(struct kiocb*,const struct iovec*, unsigned long, loff_t);
  12.     ssize_t (*aio_write)(struct kiocb*,const struct iovec*, unsigned long, loff_t);
  13.     int (*readdir)(struct file*, void*, filldir_t);
  14.     unsigned int (*poll)(struct file*, struct poll_table_struct*);
  15.     int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
  16.     long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
  17.     long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
  18.     int (*mmap)(struct file*, struct vm_area_struct*);
  19.     int (*open)(struct inode*, struct file*);
  20.     int (*flush)(struct file*, fl_owner_t id);
  21.     int (*release)(struct inode*, struct file*);
  22.     int (*fsync)(struct file*, struct dentry*,int datasync);
  23.     int (*aio_fsync)(struct kiocb*,int datasync);
  24.     int (*fasync)(int, struct file*,int);
  25.     int (*lock)(struct file*,int, struct file_lock*);
  26.     ssize_t (*sendpage)(struct file*, struct page*,int, size_t, loff_t*,int);
  27.     unsigned long (*get_unmapped_area)(struct file*, unsigned long, unsigned long, unsigned long, unsigned long);
  28.     int (*check_flags)(int);
  29.     int (*flock)(struct file*,int, struct file_lock*);
  30.     ssize_t (*splice_write)(struct pipe_inode_info*, struct file*, loff_t*, size_t, unsignedint);
  31.     ssize_t (*splice_read)(struct file*, loff_t*, struct pipe_inode_info*, size_t, unsignedint);
  32.     int (*setlease)(struct file*, long, struct file_lock**);
  33. };

這個結構體其實是在過渡期的結構體,unlocked_ioctl就是ioctl的替代者。對於新的驅動,不要再使用ioctl了,而是使用unlocked_ioctl。

4、呼叫ioctl與unlocked_ioctl在核心程式碼上的不同

   其實ioctl與unlocked_ioctl所對應的系統呼叫都是ioctl。但是在應用層呼叫ioctl的時候,對於我們實現ioctl或者unlocked_ioctl有什麼不同呢?這裡我們可以追溯一下ioctl系統呼叫程式碼的執行過程,這裡我簡單的寫出這個系統呼叫對於裝置驅動(一般是裝置驅動使用ioctl)的執行順序:(fs/ioctl.c)

 SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)--->do_vfs_ioctl--->vfs_ioctl

ioctl與unlocked_ioctl的區別就體現在了這個vfs_ioctl中,我們先來看ioctl被刪除前的函式(Linux-2.6.36之前的核心版本):

  1. /**
  2.  * vfs_ioctl -call filesystem specific ioctl methods
  3.  * @filp:    open fileto invoke ioctl methodon
  4.  * @cmd:    ioctl commandto execute
  5.  * @arg:    command-specific argumentfor ioctl
  6.  *
  7.  * Invokes filesystem specific ->unlocked_ioctl,if one exists; otherwise
  8.  * invokes filesystem specific ->ioctl method.If neither method exists,
  9.  * returns -ENOTTY.
  10.  *
  11.  * Returns 0 on success,-errnoon error.
  12.  */
  13. static long vfs_ioctl(struct file *filp, unsigned int cmd,
  14.          unsigned long arg)
  15. {
  16.     int error=-ENOTTY;
  17.     if (!filp->f_op)
  18.         goto out;
  19.     if (filp->f_op->unlocked_ioctl){
  20.         error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
  21.         if (error==-ENOIOCTLCMD)
  22.             error = -EINVAL;
  23.         goto out;
  24.     } elseif(filp->f_op->ioctl){
  25.         lock_kernel();
  26.         error = filp->f_op->ioctl(filp->f_path.dentry->d_inode,
  27.                      filp, cmd, arg);
  28.         unlock_kernel();
  29.     }
  30.  out:
  31.     return error;
  32. }

從這個函式中我們可以看出:

  1. ioctl是受到大核心鎖保護的,而unlocked_ioctl是直接執行的。
  2. unlocked_ioctl優先順序高於ioctl,如果存在unlocked_ioctl,則執行unlocked_ioctl,否則才執行ioctl。這個優先順序的產生明顯是為了過渡。

而在ioctl被刪除後,vfs_ioctl函式也做了相應的改變(Linux-2.6.36):

  1. /**
  2.  * vfs_ioctl -call filesystem specific ioctl methods
  3.  * @filp:    open fileto invoke ioctl methodon
  4.  * @cmd:    ioctl commandto execute
  5.  * @arg:    command-specific argumentfor ioctl
  6.  *
  7.  * Invokes filesystem specific ->unlocked_ioctl,if one exists; otherwise
  8.  * returns -ENOTTY.
  9.  *
  10.  * Returns 0 on success,-errnoon error.
  11.  */
  12. static long vfs_ioctl(struct file *filp, unsigned int cmd,
  13.          unsigned long arg)
  14. {
  15.     int error=-ENOTTY;
  16.     if (!filp->f_op||!filp->f_op->unlocked_ioctl)
  17.         goto out;
  18.     error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
  19.     if (error==-ENOIOCTLCMD)
  20.         error = -EINVAL;
  21.  out:
  22.     return error;
  23. }

5、在驅動程式設計時的注意事項

  • 在註冊檔案操作方法的結構體struct file_operations的時候原先的.ioctl=OOXX;替換為 .unlocked_ioctl=OOXX;
但是要注意ioctl和unlocked_ioctl的定義有一點不同:unlocked_ioctl少了一個inode引數。但是如果方法中真的需要其中的資料,可以通過filp->f_dentry->d_inode獲得。 由於失去了大核心鎖的保護,所以必須unlocked_ioctl方法中自行實現鎖機制,以保證不會在操作裝置的時候(特別在SMP系統中)產生競態。(也就實現了用小鎖替換大鎖)