简介&前言
这是IO_FILE篇的第四个函数分析:fclose,文件流的关闭操作,一览文件流的关闭流程:先刷新写缓冲区,再断链,关闭文件描述符,释放文件结构体
到这里IO FILE常用的4个函数分析完了,下一篇开始将进入文件流利用相关的内容
源码分析
_IO_new_fclose
位于:libio/iofclose.c
int _IO_new_fclose(FILE *fp)
{
int status;
CHECK_FILE(fp, EOF);
#if SHLIB_COMPAT(libc, GLIBC_2_0, GLIBC_2_1)
/* We desperately try to help programs which are using streams in a
strange way and mix old and new functions. Detect old streams
here. */
if (_IO_vtable_offset(fp) != 0)
return _IO_old_fclose(fp);
#endif
/* First unlink the stream. */
if (fp->_flags & _IO_IS_FILEBUF) // 0x2000
// FILE 结构从链表中断链
_IO_un_link((struct _IO_FILE_plus *)fp);
_IO_acquire_lock(fp);
if (fp->_flags & _IO_IS_FILEBUF) // 需要关闭的文件流一定是有缓冲区的
// 关闭文件流
status = _IO_file_close_it(fp);
else
// 设置错误
status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
_IO_release_lock(fp);
// 虚函数 _finish 调用
_IO_FINISH(fp);
if (fp->_mode > 0)
{
/* This stream has a wide orientation. This means we have to free
the conversion functions. */
// mode > 0 意味着有宽字节转换,意味着我们需要释放转换函数
struct _IO_codecvt *cc = fp->_codecvt;
__libc_lock_lock(__gconv_lock);
__gconv_release_step(cc->__cd_in.step);
__gconv_release_step(cc->__cd_out.step);
__libc_lock_unlock(__gconv_lock);
}
else
{
// 非宽字节进入这里,就backup就释放backup区域
if (_IO_have_backup(fp))
_IO_free_backup_area(fp);
}
// 释放内存
_IO_deallocate_file(fp);
return status;
}
使用fopen打开的文件流通过fclose关闭,使用文件流会用到缓冲区,也就是flags的_IO_IS_FILEBUF
标志(0x2000)
这里的主要流程:
- 从
_IO_list_all
链表中断链FILE
结构 - 调用
_IO_file_close_it
关闭文件流 - 调用
_IO_deallocate_file
释放内存
下面分析每一步进行的操作
_IO_un_link
这是断链函数,位于:libio/genops.c
void _IO_un_link(struct _IO_FILE_plus *fp)
{
if (fp->file._flags & _IO_LINKED)
{
// 如果已经插入了
FILE **f;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg(flush_cleanup);
_IO_lock_lock(list_all_lock);
run_fp = (FILE *)fp;
_IO_flockfile((FILE *)fp);
#endif
if (_IO_list_all == NULL)
;
else if (fp == _IO_list_all)
_IO_list_all = (struct _IO_FILE_plus *)_IO_list_all->file._chain;
else
// 遍历链表找到该节点
for (f = &_IO_list_all->file._chain; *f; f = &(*f)->_chain)
if (*f == (FILE *)fp)
{
// 删除节点
*f = fp->file._chain;
break;
}
fp->file._flags &= ~_IO_LINKED;
#ifdef _IO_MTSAFE_IO
_IO_funlockfile((FILE *)fp);
run_fp = NULL;
_IO_lock_unlock(list_all_lock);
_IO_cleanup_region_end(0);
#endif
}
}
libc_hidden_def(_IO_un_link)
通过_chain
成员去遍历每一个FILE
结构,找到该结构,断链,设置断链的标志在flags
_IO_new_file_close_it
位于:libio/fileops.c
int _IO_new_file_close_it(FILE *fp)
{
int write_status;
// 检查fileno,文件描述符fd是否存在,存在意味着文件是打开的
if (!_IO_file_is_open(fp))
return EOF;
// 设置写状态,如果当前flags为允许写(0x0008)且正在写(0x0800)
if ((fp->_flags & _IO_NO_WRITES) == 0 && (fp->_flags & _IO_CURRENTLY_PUTTING) != 0)
// 刷新缓冲区完成一次写入,这是个宏
write_status = _IO_do_flush(fp);
else
write_status = 0;
_IO_unsave_markers(fp);
// 检查flags2的_IO_FLAGS2_NOCLOSE(关闭流但不关闭底层文件描述符),如果没有设置,就调用系统调用关闭文件描述符
int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0
? _IO_SYSCLOSE(fp) // 虚函数_close
: 0);
/* Free buffer. */
// 清空缓冲区
if (fp->_mode > 0) // 宽字节处理
{
if (_IO_have_wbackup(fp))
_IO_free_wbackup_area(fp);
_IO_wsetb(fp, NULL, NULL, 0); // 释放缓冲区
_IO_wsetg(fp, NULL, NULL, NULL);// 更新读指针
_IO_wsetp(fp, NULL, NULL); // 更新写指针
}
_IO_setb(fp, NULL, NULL, 0); // 释放缓冲区
_IO_setg(fp, NULL, NULL, NULL); // 更新读指针
_IO_setp(fp, NULL, NULL); // 更新写指针
// 再次断链
_IO_un_link((struct _IO_FILE_plus *)fp);
fp->_flags = _IO_MAGIC | CLOSED_FILEBUF_FLAGS; // 0x240c 关闭缓冲区的标志
fp->_fileno = -1;
fp->_offset = _IO_pos_BAD;
return close_status ? close_status : write_status;
}
libc_hidden_ver(_IO_new_file_close_it, _IO_file_close_it)
主要流程:
- 如果文件是可写的,且flags里有_IO_CURRENTLY_PUTTING(0x0800),就调用
_IO_do_flush
刷新缓冲区写入数据到文件 - 检查flags2的_IO_FLAGS2_NOCLOSE标志(关闭流但不关闭底层文件描述符),如果没有设置就使用虚函数_IO_SYSCLOSE关闭文件描述符
- 释放缓冲区,其中setb是释放缓冲区的函数,setg和setp分配是更新读和写的缓冲区指针,都设置为0,_mode>0意味着使用了宽字节,就需要单独把宽字节的缓冲区也释放了
- 断链,更新fileno,返回
其中的涉及几个函数见下文,逐个过一遍
_IO_do_flush
位于:libio/libioP.h
#define _IO_do_flush(_f) \
((_f)->_mode <= 0 \
? _IO_do_write(_f, (_f)->_IO_write_base, \
(_f)->_IO_write_ptr-(_f)->_IO_write_base) \
: _IO_wdo_write(_f, (_f)->_wide_data->_IO_write_base, \
((_f)->_wide_data->_IO_write_ptr \
- (_f)->_wide_data->_IO_write_base)))
_mode属性标识使用的是宽字节缓冲区还是普通的缓冲区
_mode<=0,就用_IO_do_write完成写入,否则就用_IO_wdo_write完成写入
_IO_unsave_markers
位于:libio/genops.c
void _IO_unsave_markers(FILE *fp)
{
struct _IO_marker *mark = fp->_markers;
if (mark)
{
fp->_markers = 0;
}
if (_IO_have_backup(fp))
_IO_free_backup_area(fp);
}
libc_hidden_def(_IO_unsave_markers)
清空 fp->_markers 字段
_IO_SYSCLOSE
位于:libio/fileops.c
int _IO_file_close(FILE *fp)
{
/* Cancelling close should be avoided if possible since it leaves an
unrecoverable state behind. */
return __close_nocancel(fp->_fileno);
}
#define __close_nocancel(fd) __close (fd)
使用系统调用关闭文件描述符
_IO_setb & _IO_wsetb
位于:libio/genops.c
void _IO_setb(FILE *f, char *b, char *eb, int a)
{
// 如果存在缓冲区就释放掉
if (f->_IO_buf_base && !(f->_flags & _IO_USER_BUF))
free(f->_IO_buf_base);
// 清空缓冲区指针
f->_IO_buf_base = b;
f->_IO_buf_end = eb;
// 清空标志
if (a)
f->_flags &= ~_IO_USER_BUF;
else
f->_flags |= _IO_USER_BUF;
}
libc_hidden_def(_IO_setb)
位于:libio/wgenops.c
void
_IO_wsetb (FILE *f, wchar_t *b, wchar_t *eb, int a)
{
if (f->_wide_data->_IO_buf_base && !(f->_flags2 & _IO_FLAGS2_USER_WBUF))
free (f->_wide_data->_IO_buf_base);
f->_wide_data->_IO_buf_base = b;
f->_wide_data->_IO_buf_end = eb;
if (a)
f->_flags2 &= ~_IO_FLAGS2_USER_WBUF;
else
f->_flags2 |= _IO_FLAGS2_USER_WBUF;
}
libc_hidden_def (_IO_wsetb)
释放缓冲区,清空缓冲区指针
_IO_FINISH -> _IO_new_file_finish
位于:libio/fileops.c
void _IO_new_file_finish(FILE *fp, int dummy)
{
if (_IO_file_is_open(fp))
{
// 刷新缓冲区,写入数据到文件
_IO_do_flush(fp);
if (!(fp->_flags & _IO_DELETE_DONT_CLOSE)) // 如果没有设置关闭时不调用close,就调用close
_IO_SYSCLOSE(fp);
}
_IO_default_finish(fp, 0);
}
libc_hidden_ver(_IO_new_file_finish, _IO_file_finish)
刷新缓冲区,写入数据到文件,然后根据flags判断是否调用close关闭文件描述符
_IO_deallocate_file
/* Deallocate a stream if it is heap-allocated. Preallocated
stdin/stdout/stderr streams are not deallocated. */
static inline void
_IO_deallocate_file(FILE *fp)
{
/* The current stream variables. */
if (fp == (FILE *)&_IO_2_1_stdin_ || fp == (FILE *)&_IO_2_1_stdout_ || fp == (FILE *)&_IO_2_1_stderr_)
return;
#if SHLIB_COMPAT(libc, GLIBC_2_0, GLIBC_2_1)
if (_IO_legacy_file(fp))
return;
#endif
// 释放内存
free(fp);
}
释放FILE结构体
总结
关闭文件流的操作总体流程大致如下:
- 刷新写缓冲区,写入数据到文件
- 释放文件流缓冲区
- 从IO链表中断链
- 关闭文件描述符fd
- 释放FILE结构
参考资料
- [0] glibc 2.35 源码