Linux 文件缓存 (二)

close系统调用入口
1. 首先来到系统调用入口,主要使用__close_fd进行了具体的处理过程,并没有耗时操作。
(current->files表示进程当前打开文件表信息,fd为需要关闭的文件索引)


1048
/* 1049 * Careful here! We test whether the file pointer is NULL before 1050 * releasing the fd. This ensures that one clone task can't release 1051 * an fd while another clone is opening it. 1052 */ 1053 SYSCALL_DEFINE1(close, unsigned int, fd) 1054 { 1055 int retval = __close_fd(current->files, fd); 1056 1057 /* can't restart close syscall because file table entry was cleared */ 1058 if (unlikely(retval == -ERESTARTSYS || 1059 retval == -ERESTARTNOINTR || 1060 retval == -ERESTARTNOHAND || 1061 retval == -ERESTART_RESTARTBLOCK)) 1062 retval = -EINTR; 1063 1064 return retval; 1065 } 1066 EXPORT_SYMBOL(sys_close);

__close_fd函数

函数中先维护了进程的打开文件描述符表,相关标记,也没有耗时过程。最后把后续工作交给filp_close

570 /*
571  * The same warnings as for __alloc_fd()/__fd_install() apply here...
572  */
573 int __close_fd(struct files_struct *files, unsigned fd)
574 {
575         struct file *file;
576         struct fdtable *fdt;
577 
578         spin_lock(&files->file_lock);
579         fdt = files_fdtable(files);
580         if (fd >= fdt->max_fds)
581                 goto out_unlock;
582         file = fdt->fd[fd];
583         if (!file)
584                 goto out_unlock;
585         rcu_assign_pointer(fdt->fd[fd], NULL);
586         __clear_close_on_exec(fd, fdt);
587         __put_unused_fd(files, fd);
588         spin_unlock(&files->file_lock);
589         return filp_close(file, files);
590 
591 out_unlock:
592         spin_unlock(&files->file_lock);
593         return -EBADF;
594 }

filp_close函数

函数内会尝试调用文件对象的f_op->flush函数,但是ext4文件系统中,没有为该函数指针赋值,kgdb调试时也不会进入该分支。dnotify_flush函数也没有进行耗时操作,剩下就是fput这个函数了。

1022 /*
1023  * "id" is the POSIX thread ID. We use the
1024  * files pointer for this..
1025  */
1026 int filp_close(struct file *filp, fl_owner_t id)
1027 {
1028         int retval = 0;
1029 
1030         if (!file_count(filp)) {
1031                 printk(KERN_ERR "VFS: Close: file count is 0\n");
1032                 return 0;
1033         }
1034 
1035         if (filp->f_op->flush)
1036                 retval = filp->f_op->flush(filp, id);
1037 
1038         if (likely(!(filp->f_mode & FMODE_PATH))) {
1039                 dnotify_flush(filp, id);
1040                 locks_remove_posix(filp, id);
1041         }
1042         fput(filp);
1043         return retval;
1044 }

fput函数

这个函数比较关键主要由几个步骤

1. 减少文件的使用计数变量。这个计数变量是共享一个打开文件描述符的进程(共享文件描述符的父子进程)数。

    两个不相干的进程分别调用open打开同一个文件,并不会共用这个变量,他们有自己的各自的文件描述对象。

2. 如果f_count减一后达到了0,那么说明这个文件描述符已经没有人需要使用了,执行相关的释放/回写工作。

    如果不为0,则不做任何回写释放动作,直接返回。

  (当然调用fput之前的那些操作已经把进程内的文件描述符表中的位置给清除了)

272 void fput(struct file *file)
273 {
274         if (atomic_long_dec_and_test(&file->f_count)) {
275                 struct task_struct *task = current;
276 
277                 if (likely(!in_interrupt() && !(task->flags & PF_KTHREAD))) {
278                         init_task_work(&file->f_u.fu_rcuhead, ____fput);
279                         if (!task_work_add(task, &file->f_u.fu_rcuhead, true))
280                                 return;
281                         /*
282                          * After this task has run exit_task_work(),
283                          * task_work_add() will fail.  Fall through to delayed
284                          * fput to avoid leaking *file.
285                          */
286                 }
287 
288                 if (llist_add(&file->f_u.fu_llist, &delayed_fput_list))
289                         schedule_delayed_work(&delayed_fput_work, 1);
290         }
291 }

3. 如果不在中断中且不是内核线程的话(为什么做这个判断,待考察),将执行___fput函数的一个任务添加到当前进程的任务列表。当前进程的任务列表中的函数会在返回用户态时(前)得到执行。所以有理由相信,在____fput中进行了相关的释放或者回写耗时操作,使得close返回时间较长。

[task_work_add与执行的相关机制不在这里展开],见task_work机制

____fput 与 __fput

____fput立马调用了__fput真是扯,走了那么多终于来到"the real"

250 static void ____fput(struct callback_head *work)
251 {
252         __fput(container_of(work, struct file, f_u.fu_rcuhead));
253 }

192 /* the real guts of fput() - releasing the last reference to file
193  */
194 static void __fput(struct file *file)
195 {
196         struct dentry *dentry = file->f_path.dentry;
197         struct vfsmount *mnt = file->f_path.mnt;
198         struct inode *inode = file->f_inode;
199 
200         might_sleep();
201 
202         fsnotify_close(file);
203         /*
204          * The function eventpoll_release() should be the first called
205          * in the file cleanup chain.
206          */
207         eventpoll_release(file);
208         locks_remove_file(file);
209 
210         if (unlikely(file->f_flags & FASYNC)) {
211                 if (file->f_op->fasync)
212                         file->f_op->fasync(-1, file, 0);
213         }
214         ima_file_free(file);
215         if (file->f_op->release)
216                 file->f_op->release(inode, file);
217         security_file_free(file);
218         if (unlikely(S_ISCHR(inode->i_mode) && inode->i_cdev != NULL &&
219                      !(file->f_mode & FMODE_PATH))) {
220                 cdev_put(inode->i_cdev);
221         }
222         fops_put(file->f_op);
223         put_pid(file->f_owner.pid);
224         if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
225                 i_readcount_dec(inode);
226         if (file->f_mode & FMODE_WRITER) {
227                 put_write_access(inode);
228                 __mnt_drop_write(mnt);
229         }
230         file->f_path.dentry = NULL;
231         file->f_path.mnt = NULL;
232         file->f_inode = NULL;
233         file_free(file);
234         dput(dentry);
235         mntput(mnt);
236 }

 着其中file->f_op->release对应的就是ext4_release_file函数(文件系统是ext4的情况下)。看了下也没发现什么。

关于回写过程可能并不是在close调用中(虽然有阻塞的现象,后来在虚拟机里试了一下又没了。。。)。根据深入Linux架构中所说,赃页的回写在另外的内核线程中独立定期的进行。

通过内核一下命令可以看到指定块设备上目前赃页的情况:

root@controller:~# cat /sys/kernel/debug/bdi/252:0/stats 
BdiWriteback:            37248 kB
BdiReclaimable:          41472 kB
BdiDirtyThresh:       10344360 kB
DirtyThresh:          10344360 kB
BackgroundThresh:      5172180 kB
BdiDirtied:         2598642432 kB
BdiWritten:         2547982848 kB
BdiWriteBandwidth:      394308 kBps
b_dirty:                    10
b_io:                        0
b_more_io:                   0
bdi_list:                    1
state:                       8

 252:0是对应设备的主设备号和从设备号可以从ls -l /dev/中获取。

 27 /*
 28  * Bits in backing_dev_info.state
 29  */
 30 enum bdi_state {
 31         BDI_wb_alloc,           /* Default embedded wb allocated */
 32         BDI_async_congested,    /* The async (write) queue is getting full */
 33         BDI_sync_congested,     /* The sync queue is getting full */
 34         BDI_registered,         /* bdi_register() was done */
 35         BDI_writeback_running,  /* Writeback is in progress */
 36         BDI_unused,             /* Available bits start here */
 37 };

 state = 8即BDI_registered标记是置位的。在写数目比较大得时候可以看到 state = 18即BDI_writeback_running也被置位了。

BackgroundThresh:表示赃页数据量大于这个值的时候唤醒回写内核线程进行回写操作可以通过如下进行设置:

# echo 15 > /proc/sys/vm/dirty_background_ratio 

15表示当赃数据量达到内存容量的15%时开始启动回写内核线程进行回写操作。

DirtyThresh:当赃页数据量大于这个数值时写操作伴随赃页刷出过程(开始磁盘I/O)。类似可以设置:

echo 20 > /proc/sys/vm/dirty_ratio

这两个参数的具体使用参见:http://blog.sina.com.cn/s/blog_448574810101k1va.html

 dirty_background_ratio的值应该要比dirty_ratio的值要低,它们有各自的byte值dirty_background_bytes和dirty_bytes,byte和ratio两种模式只能选择一种,另外一个会被置位零,默认使用ratio。

posted @ 2015-05-17 14:24  卖程序的小歪  阅读(1852)  评论(0编辑  收藏  举报