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

asmlinkage long sys_close(unsigned int fd)
{
     struct file * filp;
     struct files_struct *files = current->files;
 
     spin_lock(&files->file_lock);
     //参数有效性判断
     if (fd >= files->max_fds)
         goto out_unlock;
     //取得文件描述符对应的file
     filp = files->fd[fd];
     if (!filp)
         goto out_unlock;
     //将文件描述符对应的file置空
     files->fd[fd] = NULL;
     //清除close_on_exec的标志位,表示进程结束时不应该关闭对应位的文件描述对象
     FD_CLR(fd, files->close_on_exec);
     //清除文件描述的分配位图
     __put_unused_fd(files, fd);
     spin_unlock(&files->file_lock);
     return filp_close(filp, files);
 
out_unlock:
     spin_unlock(&files->file_lock);
     return -EBADF;
}
 
 sys_clsoe子函数filp_close
int filp_close(struct file *filp, fl_owner_t id)
{
     int retval;
 
     /* Report and clear outstanding errors */
     retval = filp->f_error;
     if (retval)
         filp->f_error = 0;
 
     //file引用计数为零.已经无效了
     if (!file_count(filp)) {
         printk(KERN_ERR "VFS: Close: file count is 0\n");
         return retval;
     }
 
     //如果文件对象有flush()操作,调用之
     if (filp->f_op && filp->f_op->flush) {
         int err = filp->f_op->flush(filp);
         if (!retval)
              retval = err;
     }
 
     //发出flush通告
     dnotify_flush(filp, id);
     //文件要关闭了,将进程拥有的文件的强制锁清除掉
     locks_remove_posix(filp, id);
     //释放file对象
     fput(filp);
     return retval;
}
 
4.传统关闭--3.6.7

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了)

 
子函数
释放struct file结构体
fput(filp);
代码:
    1. void fput(struct file *file)
    2. {
    3. if (atomic_long_dec_and_test(&file->f_count)) {
    4. struct task_struct *task = current;
    5. file_sb_list_del(file);
    6. if (unlikely(in_interrupt() || task->flags & PF_KTHREAD)) {
    7. unsigned long flags;
    8. spin_lock_irqsave(&delayed_fput_lock, flags);
    9. list_add(&file->f_u.fu_list, &delayed_fput_list);
    10. schedule_work(&delayed_fput_work);
    11. spin_unlock_irqrestore(&delayed_fput_lock, flags);
    12. return;
    13. }
    14. init_task_work(&file->f_u.fu_rcuhead, ____fput);
    15. task_work_add(task, &file->f_u.fu_rcuhead, true);
    16. }
    17. }
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等并没有释放。

&&&&&&&&&&&&&&&&&&&&   笔记  &&&&&&&&&&&&&&&&&&&&&&&&&&&&&
(1)
unistd.h:
unistd的意思是Unix Standard的意思,里面定义的宏一类的东西都是为了Unix标准服务的(一般来说包括了POSIX的一些常量……)
unistd是 unix 系统标准头文件,用于系统调用,相当于win32中的windows.h。
(http://hi.baidu.com/danielzou/item/76613cd4fbfa9ccc1a72b42e)
 

5.selff

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);

__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,那么就把这个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))
条件判断,如果不成立,则什么都不用做。

 

最新文章

  1. JSONModel对架构的影响及解决方案
  2. VS中附加进程的方式调试IIS页面,以及设置断点无效问题解决
  3. sharepoint 弹出窗口
  4. 预写式日志(Write-Ahead Logging (WAL))
  5. iftop
  6. c++之路进阶——codevs1286(郁闷的出纳员)
  7. 飞翔的圆(Flappy Bird)游戏源码
  8. Java并发编程:并发容器ConcurrentHashMap
  9. C语言随笔_printf输出多行
  10. Java基础学习 —— bat处理文件
  11. 同样是IT培训,为什么人家月薪过万,你才几千,问题在哪?!
  12. 【递推】Bzoj3612[Heoi2014]平衡
  13. 牛客网暑期ACM多校训练营(第七场)Bit Compression
  14. python -- ajax数组传递和后台接收
  15. Android清理设备内存具体完整演示样例(二)
  16. Face Recognition for the Happy House
  17. ndoejs处理编码的爬虫
  18. 无焦点下获取条码枪返回值的Hook(再次改良版)
  19. 使用gtest对DLL工程进行单元测试的实践
  20. LUN mask 和zone 区别

热门文章

  1. Nginx的TCP/UDP调度器
  2. 多个idea项目使用同一个tomcat
  3. 使用yum命令出错:SyntaxError: invalid syntax 由于用户取消而退出
  4. const 与指针 的用法
  5. EM算法 小结
  6. vue-cli 本地代理 造成session丢失 而登录不上去 解决办法
  7. Python回归分析五部曲(三)—一元非线性回归
  8. Pap.er 模仿 - 第二天
  9. django 快速实现注册(四)
  10. CTF中PHP反序列化和命令注入的一次简单利用