分类: LINUX
2012-11-30 15:07:45
目录
close系统调用在内核里面的入口函数为sys_close. 1
根据用户空间传入的文件描述符fd取出对应的struct file结构体... 1
清空进程的文件描述符fd所对应的标准位,如果要关闭的这个的文件描述符对应的fd小于下一次的文件描述符起点,则根系下一次本进程的文件描述符起点为fd. 2
所以,只有调用了sys_close系统调用,进程的所有文件列表中才会把这个文件删除... 3
WORD里面的目录复制过来似乎不能直接用。。还是放在这里当主线看吧..
系统调用在内核里面的入口函数为sys_close
[root@syslab ~]# grep close /usr/include/asm/unistd_64.h
#define __NR_close 3
__SYSCALL(__NR_close, sys_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);
…//省略次要
}
函数不长,流程主要如下
fd取出对应的struct file结构体方便理解,我写成Struct file filt=current->files_struct->fdtable->files[fd]; (current是当前task_struct)
所对应的标准位,如果要关闭的这个的文件描述符对应的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了)
结构体
fput(filp);
这个函数执行的和sys_read中释放struct file结构体一样的操。
点击(此处)折叠或打开
但是sys_read中是按需调用此fput(filp)函数,sys_read是按需释放(有if判断),详见Linux VFS中read系统调用实现原理 ,这里是直接调用fput(filp)
Fput(filp)执行下面的操作
(如果sys_read或sys_write里面已经把这个结构体释放掉了或者struct file结构体的引用计数大于1,sys_close调用fput(filp)函数里面就会什么都不做,这里并不冲突,文章结尾会再次说这个问题)
这里如果当前进程在检查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调用才能完成后续清除文件描述符等任务。详见下一篇
注意,这些释放都是内存操作,磁盘上面的文件,inode等并没有释放。
系统调用,进程的所有文件列表中才会把这个文件删除
Sys_read(v),sys_write(v)可能调用了fput(filp)(调用条件如Linux VFS中read系统调用实现原理所述);也可能没有调用fput(filp).
但是sys_read(v),sys_write(v)调用了fput(filp)也不影响close中的这个地方的调用fput(filp),因为fput(filp)本身会先执行if(atomic_long_dec_and_test(&file->f_count)) 条件判断,如果不成立,则什么都不用做。
参考:kernel 3.6.7 源码