cstdio 中的文件访问函数
stdio.h 中定义了一系列文件访问函数(fopen,fclose,fflush,freopen,setbuf,setvbuf),接下来我们一起来分析一下 fflush 对应的源码实现。
刷新文件流函数 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 {
复制代码
函数逻辑分析
可以看到,基本逻辑与我们上面说到的函数用途一致:
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 写入到文件中。
评论