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

266 阅读6分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第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.h
226 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的含义,参考: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 int
422 _IO_new_do_write (FILE *fp, const char *data, size_t to_do)
423 {
424   return (to_do == 0
425       || (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_t
430 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_DEBUG
866 # 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 structures
212    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.h
507 #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写入到文件中。