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.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 {
复制代码
函数逻辑分析
可以看到,基本逻辑与我们上面说到的函数用途一致:
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 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 写入到文件中。
评论