写点什么

C++ 学习 ---cstdio 的源码学习分析 07- 刷新文件流函数 fflush

作者:桑榆
  • 2022 年 10 月 07 日
    广东
  • 本文字数:4267 字

    阅读完需:约 14 分钟

cstdio 中的文件访问函数

stdio.h 中定义了一系列文件访问函数(fopen,fclose,fflush,freopen,setbuf,setvbuf),接下来我们一起来分析一下 fflush 对应的源码实现。

  • fopen:打开文件

  • fclose:关闭文件

  • fflush:刷新文件流

  • freopen:重新打开文件流(不同的文件或访问模式)

  • setbuf:设置 stream buf

  • setvbuf:改变 stream buf

刷新文件流函数 fflush

给定需要刷新的 FILE 指针,关闭成功返回 0,失败返回 EOF(-1)。

int fflush ( FILE * stream );
复制代码

如果当前的 stream 是为写入打开的,或者为了更新打开的且最后一个 io 操作是 output,那么任何在 outbuffer 中未写入的数据都将会被写入到文件中;如果 stream 是空指针,那么所有的 stream 将会被 flush。

函数入口分析

可以看到,这里实际上是通过_IO_fflush 来完成的,_IO_fflush 定义在 glibc/libio/iofflush.c 中

//glibc/include/stdio.h226 libc_hidden_proto (fflush)227 libc_hidden_proto (fflush_unlocked)228 extern __typeof (fflush_unlocked) __fflush_unlocked;229 libc_hidden_proto (__fflush_unlocked)
//glibc/assert/assert.c 33 #define fflush(s) _IO_fflush (s)
//glibc/libio/iofflush.c 30 int 31 _IO_fflush (FILE *fp) 32 {
复制代码

函数逻辑分析

可以看到,基本逻辑与我们上面说到的函数用途一致:

  • 如果 fp==NULL,则调用_IO_flush_all,flush 所有的文件;

  • 否则调用_IO_SYNC 对指定的流进行写入 flush 操作

 30 int 31 _IO_fflush (FILE *fp)                                                  32 { 33   if (fp == NULL) 34     return _IO_flush_all (); 35   else 36     { 37       int result; 38       CHECK_FILE (fp, EOF); 39       _IO_acquire_lock (fp); 40       result = _IO_SYNC (fp) ? EOF : 0; 41       _IO_release_lock (fp); 42       return result; 43     } 44 }
复制代码

分支一:_IO_flush_all 逻辑

调用_IO_flush_all_lockp 实现,这里面是加锁的实现,传入的 1 为参数 do_lock。

//glibc/libio/genops.c 723 int 724 _IO_flush_all (void)                                                 725 { 726   /* We want locking.  */ 727   return _IO_flush_all_lockp (1); 728 } 729 libc_hidden_def (_IO_flush_all)
复制代码

1._IO_flush_all_lockp

函数源码如下,除去前后的 list_all_lock 加锁解锁处理,我们可以看到逻辑如下:

  • 通过_IO_list_all(还记得之前每次打开我们都会将 stream 挂到这条链表上吗?)访问每一个 stream 对象;

  • 如果设置了 do_lock,那就需要调用_IO_flockfile(fp),同样的,后面需要解锁;

  • 检查当前 FILE 对象的情况,如果是以下两种情况:(fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)(_IO_vtable_offset (fp) == 0&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr> fp->_wide_data->_IO_write_base))

这里我们需要回顾一下之前初始化时填入信息时 fp->_mode 的含义,参考:https://xie.infoq.cn/article/ebc2af6fa385c99400d2c9c2e 6._IO_no_init 初始化 ,是对 orientation 信息的记录,与宽字节数据的处理相关,实际上就是用来初始化和区分宽字节数据的,那么第一个条件就是:如果不是宽字节数据,那么我们需要查看 fp->_IO_write_ptr > fp->_IO_write_base,即当前写入的指针是否大于 put 区域的起点,是否还有数据没有写入;同理的第二个判断是针对宽字节数据的判断。

 684 int 685 _IO_flush_all_lockp (int do_lock) 686 { 687   int result = 0; 688   FILE *fp; 689  690 #ifdef _IO_MTSAFE_IO 691   _IO_cleanup_region_start_noarg (flush_cleanup); 692   _IO_lock_lock (list_all_lock); 693 #endif 694  695   for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain) 696     { 697       run_fp = fp; 698       if (do_lock) 699     _IO_flockfile (fp); 700  701       if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) 702        || (_IO_vtable_offset (fp) == 0 703            && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr 704                     > fp->_wide_data->_IO_write_base)) 705        ) 706       && _IO_OVERFLOW (fp, EOF) == EOF) 707     result = EOF; 708  709       if (do_lock) 710     _IO_funlockfile (fp); 711       run_fp = NULL; 712     }                                                        713  714 #ifdef _IO_MTSAFE_IO 715   _IO_lock_unlock (list_all_lock); 716   _IO_cleanup_region_end (0); 717 #endif 718  719   return result; 720 }
复制代码

如果上述两种情况有一种说明还有字节数据没有写入文件之中,此时,我们都调用_IO_OVERFLOW,从函数定义的描述不难看出,该函数将会 flush buffer

142 /* The 'overflow' hook flushes the buffer.143    The second argument is a character, or EOF.144    It matches the streambuf::overflow virtual function. */145 typedef int (*_IO_overflow_t) (FILE *, int); 146 #define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)147 #define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
复制代码

2.__overflow

这里的__overflow 就会调用对应 fp 的函数指针实现对应的写入功能,最后实际调用到了_IO_do_write,将 f->_IO_write_base 开始,长度为(f->_IO_write_ptr - f->_IO_write_base)的数据写入,具体实际写入部分就不深入分析了,感兴趣的同学可以查看一下源码。

vtable的实现中1432 const struct _IO_jump_t _IO_file_jumps libio_vtable =1433 {1434   JUMP_INIT_DUMMY,1435   JUMP_INIT(finish, _IO_file_finish),1436   JUMP_INIT(overflow, _IO_file_overflow),
1426 versioned_symbol (libc, _IO_new_file_overflow, _IO_file_overflow, GLIBC_2_1);
//所以实际上使用的是_IO_new_file_overflow
//glibc/libio/fileops.c 729 int 730 _IO_new_file_overflow (FILE *f, int ch) 731 { ... 774 if (ch == EOF) 775 return _IO_do_write (f, f->_IO_write_base, 776 f->_IO_write_ptr - f->_IO_write_base); ... }
复制代码

3._IO_do_write

可以看到,依次往下调用到_IO_SYSWRITE,使用系统调用实现文件写入 data

1418 versioned_symbol (libc, _IO_new_do_write, _IO_do_write, GLIBC_2_1);
416 static size_t new_do_write (FILE *, const char *, size_t);417 418 /* Write TO_DO bytes from DATA to FP.419 Then mark FP as having empty buffers. */420 421 int422 _IO_new_do_write (FILE *fp, const char *data, size_t to_do)423 {424 return (to_do == 0425 || (size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;426 }427 libc_hidden_ver (_IO_new_do_write, _IO_do_write)428 429 static size_t430 new_do_write (FILE *fp, const char *data, size_t to_do)431 {...448 count = _IO_SYSWRITE (fp, data, to_do);...}
复制代码

分支二:_IO_SYNC 逻辑

1.CHECK_FILE

检查 FILE 对象是否合法,包括是否空指针,_flags 是否在合法范围内。

865 #ifdef IO_DEBUG866 # define CHECK_FILE(FILE, RET) do {             \                                                                                                    867     if ((FILE) == NULL                      \868     || ((FILE)->_flags & _IO_MAGIC_MASK) != _IO_MAGIC)  \869       {                             \870     __set_errno (EINVAL);                   \871     return RET;                     \872       }                             \873   } while (0)874 #else 875 # define CHECK_FILE(FILE, RET) do { } while (0)876 #endif
复制代码

2._IO_SYNC 逻辑

211 /* The 'sync' hook attempts to synchronize the internal data structures212    of the file with the external state.213    It matches the streambuf::sync virtual function. */214 typedef int (*_IO_sync_t) (FILE *);215 #define _IO_SYNC(FP) JUMP0 (__sync, FP)                                   216 #define _IO_WSYNC(FP) WJUMP0 (__sync, FP)
复制代码

实际调用的还是__sync 逻辑,逻辑与上面 overflow 的一致,但是上面调用的是_IO_do_write,这里核心的函数是_IO_do_flush

1425 versioned_symbol (libc, _IO_new_file_sync, _IO_file_sync, GLIBC_2_1);
790 int 791 _IO_new_file_sync (FILE *fp) 792 { 793 ssize_t delta; 794 int retval = 0; 795 796 /* char* ptr = cur_ptr(); */ 797 if (fp->_IO_write_ptr > fp->_IO_write_base) 798 if (_IO_do_flush(fp)) return EOF; 799 delta = fp->_IO_read_ptr - fp->_IO_read_end; 800 if (delta != 0) 801 { 802 off64_t new_pos = _IO_SYSSEEK (fp, delta, 1); 803 if (new_pos != (off64_t) EOF) 804 fp->_IO_read_end = fp->_IO_read_ptr; 805 else if (errno == ESPIPE) 806 ; /* Ignore error from unseekable devices. */ 807 else 808 retval = EOF; 809 } 810 if (retval != EOF) 811 fp->_offset = _IO_pos_BAD; 812 /* FIXME: Cleanup - can this be shared? */ 813 /* setg(base(), ptr, ptr); */ 814 return retval; 815 } 816 libc_hidden_ver (_IO_new_file_sync, _IO_file_sync)
复制代码

3._IO_do_flush

原来这里也是通过_IO_do_write 进行调用的,根本原因是这里需要区分宽字节数据和普通数据

//glibc/libio/libioP.h507 #define _IO_do_flush(_f) \508   ((_f)->_mode <= 0                               \509    ? _IO_do_write(_f, (_f)->_IO_write_base,                   \510           (_f)->_IO_write_ptr-(_f)->_IO_write_base)           \511    : _IO_wdo_write(_f, (_f)->_wide_data->_IO_write_base,              \512            ((_f)->_wide_data->_IO_write_ptr               \513             - (_f)->_wide_data->_IO_write_base)))
复制代码

总结

fflush 函数通过对输入 FILE 对象是否是空指针的判断,决定是 flush 所有文件流(空指针)还是 flush 特定文件流,实际上最后的实现都是通过_IO_do_write 将 fp->_IO_write_base 到 fp->_IO_write_ptr 之前的内容通过_IO_SYSWRITE 写入到文件中。

发布于: 刚刚阅读数: 3
用户头像

桑榆

关注

北海虽赊,扶摇可接;东隅已逝,桑榆非晚! 2020.02.29 加入

Android手机厂商-相机软件系统工程师 爬山/徒步/Coding

评论

发布
暂无评论
C++学习---cstdio的源码学习分析07-刷新文件流函数fflush_c++_桑榆_InfoQ写作社区