首页 > 编程语言 >C++学习---cstdio的源码学习分析07-刷新文件流函数fflush

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

时间:2022-12-17 22:32:47浏览次数:60  
标签:fp write 07 do FILE --- 源码 IO flush

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.c33 #define fflush(s) _IO_fflush (s)//glibc/libio/iofflush.c30 int31 _IO_fflush (FILE *fp)
32 {

函数逻辑分析

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

  • 如果fp==NULL,则调用_IO_flush_all,flush所有的文件;
  • 否则调用_IO_SYNC对指定的流进行写入flush操作
30 int31 _IO_fflush (FILE *fp)                                                 
32 {
33 if (fp == NULL)
34 return _IO_flush_all ();
35 else36 {
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.c723 int724 _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 int685 _IO_flush_all_lockp (int do_lock)
686 {
687 int result = 0;
688 FILE *fp;
689
690 #ifdef _IO_MTSAFE_IO691 _IO_cleanup_region_start_noarg (flush_cleanup);
692 _IO_lock_lock (list_all_lock);
693 #endif694
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) == 0703 && 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_IO715 _IO_lock_unlock (list_all_lock);
716 _IO_cleanup_region_end (0);
717 #endif718
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.c729 int730 _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 int791 _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 else808 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写入到文件中。

标签:fp,write,07,do,FILE,---,源码,IO,flush
From: https://blog.51cto.com/u_15830688/5950174

相关文章