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/:0/stats
BdiWriteback: kB
BdiReclaimable: kB
BdiDirtyThresh: kB
DirtyThresh: kB
BackgroundThresh: kB
BdiDirtied: kB
BdiWritten: kB
BdiWriteBandwidth: kBps
b_dirty:
b_io:
b_more_io:
bdi_list:
state:

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  > /proc/sys/vm/dirty_background_ratio 

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

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

echo  > /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。

上一篇:EasyUI中, datagrid用loadData方法绑定数据。


下一篇:微软BI 之SSIS 系列 - 在 SSIS 中导入 ACCESS 数据库中的数据