なぜ file_operations の open の対は release なの?

file_operations 構造体

file_operations 構造体は system call の界面が受けた要求をデバイスドライバに渡すために使われる構造体です。この構造体のメンバの殆どは関数を指すポインタで構成されています。次のコード片は file_operations 構造体です。

fileOpenGrok(linux-4.1.27/include/linux/fs.h:1589)
Expand allFold all
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
        ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
        int (*iterate) (struct file *, struct dir_context *);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*mremap)(struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **, void **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
                          loff_t len);
        void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
        unsigned (*mmap_capabilities)(struct file *);
#endif
};

VFS 周辺の file_operations 構造体の使われ方の方が面白いかも...

VFS(Virtual File System) から各種 FS(File System) の処理関数を呼び出す所でも使わています。ここでは VFS と各種 FS 廻りには触れません。

メンバ名は system call の関数名とほぼ一致しています。メンバ名と処理する内容も system call の名前のとおりに一致しています。メンバの中で open の対になるはずの close が見つかりません。それらしいメンバは release です。ここではなぜ release なのか背景を探ることにします。

release の特徴 - デバイスドライバ側で意識しておく必要があること

先に file_operations の release メソッドの特徴を挙げます。次の通りです。

  • user space の application が close() を使った時に呼ばれるとは限りません。参照を減らし(fput())、参照されなくなった時に呼び出されます。参照を増やす system call で主にデバイス・ドライバに関係する API は dup(), dup2() (これに伴う処理は get_file())、poll() (これに伴う処理は get_file())、mmap() (これに伴う処理は get_file()) です。これらの対になる system call または segmentation fault などにより強制終了する状況で参照カウンタは減ります。その他にも fget(), __fget(), fget_raw(), get_file(), get_file_rcu() を呼び出している所で参照を増やしています。
  • task work または delayed work (delayed_fput_work)から呼び出されます。task work からの呼び出しは x86(i686 など), x86_64 アーキテクチャでは system call 割り込みの後半処理(call do_notify_resume from entry_32.S,call do_notify_resume from entry_64.S)から呼び出しになります。

close 処理を追跡する

kernel の中で close() の処理を追跡する適当な関数として filp_close() を見てみます。ここから file_operations 構造体の release が呼ばれるところまで追跡します。先に filp_close() から user space 側の実装(close()の実装側)に辿っておきましょう。__close_fd(), SYSCALL_DEFINE1 close となります。

kernel 内でファイルを扱う filp_xxx()

filp_close() が有るということは filp_open() も有ります。これらの関数を使用している周辺コードを読むと、kernel 内から file を扱う方法が分かります。

fileOpenGrok(linux-4.1.27/fs/open.c:1058)
Expand allFold all
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
-
|
|
!
 
-
|
|
-
|
|
!
|
|
|
|
-
|
|
!
|
|
!
/*
 * "id" is the POSIX thread ID. We use the
 * files pointer for this..
 */
int filp_close(struct file *filp, fl_owner_t id)
{
        int retval = 0;
 
        if (!file_count(filp)) {
                printk(KERN_ERR "VFS: Close: file count is 0\n");
                return 0;
        }
 
        if (filp->f_op->flush)
                retval = filp->f_op->flush(filp, id);
 
        if (likely(!(filp->f_mode & FMODE_PATH))) {
                dnotify_flush(filp, id);
                locks_remove_posix(filp, id);
        }
        fput(filp);
        return retval;
}

filp_close() の中に fput() を呼び出すところが見つかります。fput() ではファイルの参照カウントを減じて 0 になったら、ファイルを開いた後に確保して使用していたリソースを開放する処理を始めます。殆どの場合 init_task_work(), task_work_add()____fput(), (____fput() の中から __fput()) を呼び出す処理を task work としてリンクリストに連結します。task_work_add() の最後の引数が true となっているので task_work_add() のなかで set_notify_resume() を呼び出します。いくつかの関数をネストした先で thread_info 構造体の flag に TIF_NOTIFY_RESUME をセットします。system call の後半処理で task work が実行されるようになります。

先に触れたように x86 では call do_notify_resume from entry_32.S、x86_64 では call do_notify_resume from entry_64.S の部分が system call が後半処理部分です。do_notify_resume() のなかから tracehook_notify_resume() 呼び出し、さらに task_work_run() を呼び出して task work に連結された関数を実行しています。途中 TIF_NOTIFY_RESUME が呼び出し条件として判定されています。

__fput() を追跡します。__fput() のなかで f_op->fasync() と f_op->release() を呼び出す箇所が見つかります。fasync() の方は何かの事象で signal を発生する (kill() 相当) 処理が実装されている場合、それを止める様にデバイス・ドライバを呼び出します。続けて release() を呼び出しています。release() は close() を呼んだ時に呼び出されるのではなく、「参照されなくなった」時に呼ばれることが分かります。

fileOpenGrok(linux-4.1.27/fs/file_table.c:186)
Expand allFold all
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
 
-
|
|
|
|
|
|
|
-
|
|
!
|
|
|
-
|
|
!
|
|
|
|
|
-
|
!
|
|
|
|
-
|
|
!
|
|
|
|
|
|
!
static void __fput(struct file *file)
{
        struct dentry *dentry = file->f_path.dentry;
        struct vfsmount *mnt = file->f_path.mnt;
        struct inode *inode = file->f_inode;
 
        might_sleep();
 
        fsnotify_close(file);
        /*
         * The function eventpoll_release() should be the first called
         * in the file cleanup chain.
         */
        eventpoll_release(file);
        locks_remove_file(file);
 
        if (unlikely(file->f_flags & FASYNC)) {
                if (file->f_op->fasync)
                        file->f_op->fasync(-1, file, 0);
        }
        ima_file_free(file);
        if (file->f_op->release)
                file->f_op->release(inode, file);
        security_file_free(file);
        if (unlikely(S_ISCHR(inode->i_mode) && inode->i_cdev != NULL &&
                     !(file->f_mode & FMODE_PATH))) {
                cdev_put(inode->i_cdev);
        }
        fops_put(file->f_op);
        put_pid(file->f_owner.pid);
        if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
                i_readcount_dec(inode);
        if (file->f_mode & FMODE_WRITER) {
                put_write_access(inode);
                __mnt_drop_write(mnt);
        }
        file->f_path.dentry = NULL;
        file->f_path.mnt = NULL;
        file->f_inode = NULL;
        file_free(file);
        dput(dentry);
        mntput(mnt);
}

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-11-07 (火) 12:29:30 (2365d)