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);
函数中先维护了进程的打开文件描述符表,相关标记,也没有耗时过程。最后把后续工作交给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 }
函数内会尝试调用文件对象的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 }
这个函数比较关键主要由几个步骤
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返回时间较长。
____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。