selph
selph
Published on 2024-03-01 / 89 Visits
0
0

[libc 2.35 源码学习] IO_FILE 篇 - fclose

简介&前言

这是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)

这里的主要流程:

  1. _IO_list_all 链表中断链FILE结构
  2. 调用_IO_file_close_it关闭文件流
  3. 调用_IO_deallocate_file释放内存

下面分析每一步进行的操作

这是断链函数,位于: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)

主要流程:

  1. 如果文件是可写的,且flags里有_IO_CURRENTLY_PUTTING(0x0800),就调用_IO_do_flush刷新缓冲区写入数据到文件
  2. 检查flags2的_IO_FLAGS2_NOCLOSE标志(关闭流但不关闭底层文件描述符),如果没有设置就使用虚函数_IO_SYSCLOSE关闭文件描述符
  3. 释放缓冲区,其中setb是释放缓冲区的函数,setg和setp分配是更新读和写的缓冲区指针,都设置为0,_mode>0意味着使用了宽字节,就需要单独把宽字节的缓冲区也释放了
  4. 断链,更新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结构体

总结

关闭文件流的操作总体流程大致如下:

  1. 刷新写缓冲区,写入数据到文件
  2. 释放文件流缓冲区
  3. 从IO链表中断链
  4. 关闭文件描述符fd
  5. 释放FILE结构

参考资料

  • [0] glibc 2.35 源码

Comment