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等并没有释放。
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))
条件判断,如果不成立,则什么都不用做。
linux-关闭文件的更多相关文章
- 深入理解linux关闭文件和删除文件
背景介绍 最近看了linux系统编程(linux system programming)一书,结合深入理解linux内核(understanding the linux kernel)一书,深入理解了 ...
- Linux C 文件与目录2 文件的打开与关闭
文件的打开与关闭 open和close 文件的打开指的是从磁盘中找到一个文件,返回一个整形的打开文件顺序的编号.打开的文件处于可读.可写状态.文件的关闭指的是释放打开的文件,是文件处于不可读写的状态. ...
- linux文件操作篇 (二) 打开和关闭文件
2.1 打开文件和关闭文件 #include <sys/types.h>#include <sys/stat.h>#include <fcntl.h> 头文件 i ...
- Linux C 文件操作,系统调用 -- open()、read() 和 标准I/O库 -- fopen()、fread()
函数汇总: open().write().read().close() fopen().fwrite().fread().fclose() 一.什么是文件 在讲述文件操作之前,我们首先要知道什么是文件 ...
- Linux - 日志文件
Linux日志文件绝大多数存放在/var/log目录,其中一些日志文件由应用程序创建,其他的则通过syslog来创建. Linux系统日志文件通过syslog守护程序在syslog套接字/dev/lo ...
- Linux大文件已删除,但df查看已使用的空间并未减少解决
在我的生活当中遇到磁盘快满了,这时候准备去删除一些大文件 于是我使用ncdu 查看了一下当前系统占用资源比较多的是那些文件,结果一看是elasticsearch的日志文件,好吧,竟然找到源头了,那就把 ...
- Linux core 文件介绍
Linux core 文件介绍 http://www.cnblogs.com/dongzhiquan/archive/2012/01/20/2328355.html 1. core文件的简单介绍在一个 ...
- Linux中文件描述符fd和文件指针flip的理解
转自:http://www.cnblogs.com/Jezze/archive/2011/12/23/2299861.html 简单归纳:fd只是一个整数,在open时产生.起到一个索引的作用,进程通 ...
- linux库文件编写入门(笔记)
linux库文件的编写 作者: laomai地址: http://blog.csdn.net/laomai 本文主要参考了如下资料⑴hcj写的"Linux静态/动态链接库的创建和使用&quo ...
- Linux关闭防火墙、SELinux
使用root权限: Linux关闭防火墙: 1. chkconfig –list|grep iptables 2. chkconfig iptables off 永久关闭防火墙 3. chkconfi ...
随机推荐
- filter(expr|obj|ele|fn)筛选出与指定表达式匹配的元素集合。
filter(expr|obj|ele|fn) 概述 筛选出与指定表达式匹配的元素集合. 这个方法用于缩小匹配的范围.用逗号分隔多个表达式 参数 exprStringV1.0 字符串值,包含供匹配当前 ...
- cookie与session的区别?
一.cookie机制和session机制的区别 具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案.同时我们也看到,由于才服务器端保持状态的方 ...
- Linux网络命令——ifconfig、ifup、ifdown
这三个命令的用途都是启动网络接口,不过,ifup 与 ifdown 仅就 /etc/sysconfig/network- scripts 内的 ifcfg-ethx(x为数字)进行启动或关闭的操作,并 ...
- 集合家族——ArrayList
一.概述: ArrayList 是实现 List 接口的动态数组,所谓动态就是它的大小是可变的.实现了所有可选列表操作,并允许包括 null 在内的所有元素.除了实现 List 接口外,此类还提供一些 ...
- 删除线性表中为x的元素的三种简单算法。
//删除线性表中不为x的元素. void delete_list(Sqlist &L,int x){ ; ;i < L.length;i++){ if(L.data[i] != x){ ...
- ARTS打卡计划第十周
Algorithms: https://leetcode-cn.com/problems/next-greater-node-in-linked-list/ 链表中下一个更大的值,双层循环及优化,后面 ...
- HTTP缓存机制和原理
前言 Http 缓存机制作为 web 性能优化的重要手段,对于从事 Web 开发的同学们来说,应该是知识体系库中的一个基础环节,同时对于有志成为前端架构师的同学来说是必备的知识技能.但是对于很多前端同 ...
- FFmpeg之av_register_all()
1. av_register_all() 该函数位于 libavformat/allformats.c 中. 该函数主要是注册所有的编解码器.复用/解复用组件等. /* * Initialize li ...
- docker安装并设置开机启动(CentOS7/8)
CentOS7.2 docker分为CE和EE版本,EE版本收费,一般我们使用CE版本就满足要求了 docker安装及启动 docker安装很简单,直接使用如下命令安装即可,安装后的docker版本即 ...
- 1.分布式配置中心 spring-cloud-config
pring Cloud 版本:2.1.0.RELEASE 一.server端 1.maven依赖 <dependency> <groupId>org.springframewo ...