linux-关闭文件
1.打开参考:
http://q.cnblogs.com/q/39275/
http://hi.baidu.com/auxor/item/49b6e929fdf16dc7ed10f197
2.关闭参考:
(google:linux close 实现)
http://blog.csdn.net/lkqboy2599/article/details/9978561
http://blog.chinaunix.net/uid-28362602-id-3426896.html
http://hi.baidu.com/mystone7/item/71f341714eb76a46ee1e5332
http://eastsun.blogbus.com/logs/7873908.html
3.传统关闭--2.6.11
3.1. 函数原型与参数
int close (int f i l e d e s)
关闭一个文件时也释放该进程加在该文件上的所有记录锁。当一个进程终止时,它所有的打开文件都由内核自动关闭。很多程序都使用这一功能而不显式地用c l o s e关闭打开的文件。对于对个任务同时操作该文件时,需要先释放该句柄,才能关闭。
3.2. 主要函数调用关系图
sys_close
| ------------- filp_close
SYSCALL_DEFINE1(close, unsigned int, fd)
{{//这里SYSCALL_DEFINE1 close到sys_close的转换请参看前面的文章Linux 编程中的API函数和系统调用的关系
struct file * filp;
struct files_struct *files = current->files;
struct fdtable *fdt;
int retval;
spin_lock(&files->file_lock);
fdt = files_fdtable(files);
if (fd >= fdt->max_fds)
goto out_unlock;
filp = fdt->fd[fd];
if (!filp)
goto out_unlock;
rcu_assign_pointer(fdt->fd[fd], NULL);
__clear_close_on_exec(fd, fdt);
__put_unused_fd(files, fd);
spin_unlock(&files->file_lock);
retval = filp_close(filp, files);
…//省略次要
}
函数不长,流程主要如下
(1)根据用户空间传入的文件描述符fd取出对应的struct file结构体方便理解,我写成Struct file filt=current->files_struct->fdtable->files[fd]; (current是当前task_struct)
(2)清空进程的文件描述符fd所对应的标准位,如果要关闭的这个的文件描述符对应的fd小于下一次的文件描述符起点,则根系下一次本进程的文件描述符起点为fd__clear_close_on_exec(fd, fdt);//执行__clear_bit(fd, fdt->close_on_exec);操作,即在close_on_exec中把fd位的设置为0
__put_unused_fd(files, fd);//执行__clear_open_fd(fd, fdt);,进一步执行__clear_bit(fd, fdt->open_fds);,即在open_fds位图中把这个fd位也设置为0;并且还会执行if (fd < files->next_fd)files->next_fd = fd;
至此,进程关联的所有文件描述符中已经不存在这个文件描述符了(根据不存在struct file及其相关dentry,inode,vfsmount了)
- void fput(struct file *file)
- {
- if (atomic_long_dec_and_test(&file->f_count)) {
- struct task_struct *task = current;
- file_sb_list_del(file);
- if (unlikely(in_interrupt() || task->flags & PF_KTHREAD)) {
- unsigned long flags;
- spin_lock_irqsave(&delayed_fput_lock, flags);
- list_add(&file->f_u.fu_list, &delayed_fput_list);
- schedule_work(&delayed_fput_work);
- spin_unlock_irqrestore(&delayed_fput_lock, flags);
- return;
- }
- init_task_work(&file->f_u.fu_rcuhead, ____fput);
- task_work_add(task, &file->f_u.fu_rcuhead, true);
- }
- }
这里如果当前进程在检查struct file的引用等于1,那么就把这个struct file结构体从超级块的文件链表中也删除掉(但是struct file结构体此时还没有从内存中释放)。
释放操作实际只是注册了一个回调函数,通过下面两行
init_task_work(&file->f_u.fu_rcuhead, ____fput); //____fput是一个实际释放操作的回调函数
task_work_add(task, &file->f_u.fu_rcuhead, true);
其中,____fput函数会释放struct file结构体,以及尝试释放起对应的dentry,mnt(之所以叫尝试是因为调用dput(dentry),dput(mnt),而dput(denty),dput(mnt)会继续检查dentry,mnt是否还在被使用,如果没有任何引用则真正释放所占内存,否则仅减少其引用计数)。
init_task_work中,file->f_u.fu_rcuhead是一个rcu_head节点,内核中
struct callback_head {
struct callback_head *next;
void (*func)(struct callback_head *head);
};
#define rcu_head callback_head
而task_work_add(task, &file->f_u.fu_rcuhead, true);会吧这个rcu_head节点加入task->task_works, 并且会调用set_notify_resume(task)把进程的thread_info的标识里设置上TIF_NOTIFY_RESUME
这样,在进程从内核态返回用户态的时候会调用tracehook_notify_resume把task->task_works链表中的所有注册好的函数都会执行一遍(此时___fput函数就会被调用到了),并且清除TIF_NOTIFY_RESUME标识位
所以,struct file结构体要释放也是在内核返回用户态的时候才执行的,在内核态的时候一直还保留着。
注意,这里__fput中执行的释放操作并没有把进程所拥有的这个文件描述符及其在位图中的占位清空,如果执行了__fput只是这个文件描述符对应的的struct file=NULL了而已,文件描述符还站着呢。这需要后面用户空间再发个sys_close调用才能完成后续清除文件描述符等任务。详见下一篇
注意,这些释放都是内存操作,磁盘上面的文件,inode等并没有释放。
1.调用层次
1.1close——》sys_close——》SYSCALL_DEFINE1——》_close_fd——》 filp_close——》 fput
1.2关键代码及注释(linux-3.1.1)
1.2.1
//Open.c
SYSCALL_DEFINE1(close, unsigned int, fd)
{
int retval = __close_fd(current->files, fd);
/* can't restart close syscall because file table entry was cleared */
if (unlikely(retval == -ERESTARTSYS ||
retval == -ERESTARTNOINTR ||
retval == -ERESTARTNOHAND ||
retval == -ERESTART_RESTARTBLOCK))
retval = -EINTR;
return retval;
}
1.2.2
//File.c
int __close_fd(struct files_struct *files, unsigned fd)
{
struct file *file;
struct fdtable *fdt;
spin_lock(&files->file_lock);
fdt = files_fdtable(files);
//参数有效性判断
if (fd >= fdt->max_fds)
goto out_unlock;
//取得文件描述符对应的file
file = fdt->fd[fd];
if (!file)
goto out_unlock;
//将文件描述符对应的file置空
rcu_assign_pointer(fdt->fd[fd], NULL);
//清除close_on_exec的标志位,表示进程结束时不应该关闭对应位的文件描述对象
__clear_close_on_exec(fd, fdt);
//清除文件描述的分配位图
__put_unused_fd(files, fd);
spin_unlock(&files->file_lock);
return filp_close(file, files);
out_unlock:
spin_unlock(&files->file_lock);
return -EBADF;
}
1.2.3
//Open.c
int filp_close(struct file *filp, fl_owner_t id)
{
int retval = 0;
//file引用计数为零.已经无效了
if (!file_count(filp)) {
printk(KERN_ERR "VFS: Close: file count is 0\n");
return 0;
}
//如果文件对象有flush()操作,调用之
if (filp->f_op && filp->f_op->flush)
retval = filp->f_op->flush(filp, id);
if (likely(!(filp->f_mode & FMODE_PATH))) {
//发出flush通告
dnotify_flush(filp, id);
//文件要关闭了,将进程拥有的文件的强制锁清除掉
locks_remove_posix(filp, id);
}
//释放file对象
fput(filp);
return retval;
}
1.2.4
//File_table.c
void fput(struct file *file)
{
if (atomic_long_dec_and_test(&file->f_count)) {
struct task_struct *task = current;
file_sb_list_del(file);
if (likely(!in_interrupt() && !(task->flags & PF_KTHREAD))) {
init_task_work(&file->f_u.fu_rcuhead, ____fput);
if (!task_work_add(task, &file->f_u.fu_rcuhead, true))
return;
/*
* After this task has run exit_task_work(),
* task_work_add() will fail. free_ipc_ns()->
* shm_destroy() can do this. Fall through to delayed
* fput to avoid leaking *file.
*/
}
if (llist_add(&file->f_u.fu_llist, &delayed_fput_list))
schedule_work(&delayed_fput_work);
}
}
2.分析
2.1主要流程:
2.1.1根据用户空间传入的文件描述符fd取出对应的struct file结构体
便于理解(详见1.2.2),可以写成Struct file filt=current->files_struct->fdtable->files[fd]; (current是当前task_struct)
2.1.2清空进程的文件描述符fd所对应的标准位,如果要关闭的这个的文件描述符对应的fd小于下一次的文件描述符起点,则根系下一次本进程的文件描述符起点为fd
__clear_close_on_exec(fd, fdt);//执行__clear_bit(fd, fdt->close_on_exec);操作,即在close_on_exec中把fd位的设置为0
__put_unused_fd(files, fd);//执行__clear_open_fd(fd, fdt);,进一步执行__clear_bit(fd, fdt->open_fds);,即在open_fds位图中把这个fd位也设置为0;并且还会执行if (fd < files->next_fd)files->next_fd = fd;
至此,进程关联的所有文件描述符中已经不存在这个文件描述符了(根据不存在struct file及其相关dentry,inode,vfsmount了)
2.2对于fput(filp)的理解:
2.2.1这里如果当前进程在检查struct file的引用等于1,那么就把这个struct file结构体从超级块的文件链表中也删除掉(但是struct file结构体此时还没有从内存中释放)。
释放操作实际只是注册了一个回调函数,通过下面两行
init_task_work(&file->f_u.fu_rcuhead,
____fput); //____fput是一个实际释放操作的回调函数
task_work_add(task,
&file->f_u.fu_rcuhead, true);
其中,____fput函数会释放struct file结构体,以及尝试释放起对应的dentry,mnt(之所以叫尝试是因为调用dput(dentry),dput(mnt),而dput(denty),dput(mnt)会继续检查dentry,mnt是否还在被使用,如果没有任何引用则真正释放所占内存,否则仅减少其引用计数)。
init_task_work中,file->f_u.fu_rcuhead是一个rcu_head节点,内核中
struct
callback_head {
struct callback_head *next;
void (*func)(struct
callback_head *head);
};
#define rcu_head callback_head
总之,init_task_work把____fput函数进行file->f_u.fu_rcuhead->func=___fput设置
而task_work_add(task, &file->f_u.fu_rcuhead, true);会把这个rcu_head节点加入task->task_works, 并且会调用set_notify_resume(task)把进程的thread_info的标识里设置上TIF_NOTIFY_RESUME
这样,在进程从内核态返回用户态的时候会调用tracehook_notify_resume把task->task_works链表中的所有注册好的函数都会执行一遍(此时___fput函数就会被调用到了),并且清除TIF_NOTIFY_RESUME标识位
所以,struct file结构体要释放也是在内核返回用户态的时候才执行的,在内核态的时候一直还保留着。 注意,这里__fput中执行的释放操作并没有把进程所拥有的这个文件描述符及其在位图中的占位清空,如果执行了__fput只是这个文件描述符对应的的struct file=NULL了而已,文件描述符还站着呢。这需要后面用户空间再发个sys_close调用才能完成后续清除文件描述符等任务。
2.2.2只有调用了sys_close系统调用,进程的所有文件列表中才会把这个文件删除Sys_read(v),sys_write(v)可能调用了fput(filp),也可能没有调用fput(filp).但是sys_read(v),sys_write(v)调用了fput(filp)也不影响close中的这个地方的调用fput(filp),因为fput(filp)本身会先执行if(atomic_long_dec_and_test(&file->f_count)) 条件判断,如果不成立,则什么都不用做。