fflush的实现源码解读

源码定位

当我们使用file的时候


FILE *f = fopen("out.txt", "w");
fprintf(f, "hello, world
");
fflush(f); // 确保数据立刻写入磁盘,而不是等 fclose()
// 此时fflush内部到底发生了什么?

file的结构体


/* The tag name of this struct is _IO_FILE to preserve historic
   C++ mangled names for functions taking FILE* arguments.
   That name should not be used in new code.  */
// 它配合 _IO_jump_t(虚表)实现了面向对象风格的操作,如 fread, fwrite, fflush 等最终都会操作这个结构体
struct _IO_FILE
{
/*
    _IO_NO_READ:不能读
    _IO_NO_WRITE:不能写
    _IO_EOF_SEEN:见过 EOF
    _IO_ERR_SEEN:发生过错误
    _IO_UNBUFFERED:非缓冲流(如 stderr)
*/
  // 高位是 _IO_MAGIC(标识结构有效性),低位是各种流的状态标志
  int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
 
  /* The following pointers correspond to the C++ streambuf protocol. */
  // 这部分是实现 fread() / fwrite() 时的数据缓冲区,通常只有在缓冲模式下(默认是全缓冲或行缓冲)才使用
  // 读写缓冲区
  char *_IO_read_ptr;   // 当前读指针
  char *_IO_read_end;   // 读缓冲区结束
  char *_IO_read_base;  // 读缓冲区起始
 
  char *_IO_write_base; // 写缓冲区起始
  char *_IO_write_ptr;  // 当前写指针
  char *_IO_write_end;  // 写缓冲区结束
 
  char *_IO_buf_base;   // 整个缓冲区的起始
  char *_IO_buf_end;    // 整个缓冲区的结束
 
  /* The following fields are used to support backing up and undo. */
  // 用于 ungetc、错误恢复等功能,允许从缓冲区中“反向读取”
  char *_IO_save_base;     // 保存缓冲区开始
  char *_IO_backup_base;   // 回退缓冲区起始,用于 ungetc
  char *_IO_save_end;      // 保存缓冲区结束
 
  struct _IO_marker *_markers; // // 标记结构,记录读写位置等
 
  // _chain 主要用于 glibc 管理所有打开的文件流,例如 flush 所有流时会遍历这个链表
  struct _IO_FILE *_chain; // 多个 FILE 形成链表(如 _IO_list_all)
 
  // 文件标识
  int _fileno;         // 文件描述符,例如 stdout 是 1
  int _flags2;         // 扩展标志位
  __off_t _old_offset; // 旧的偏移,已废弃
  __off64_t _offset;   // 当前文件偏移(64位)
 
  unsigned short _cur_column;  // 当前列数(主要用于宽字符或格式化输出)
  signed char _vtable_offset;  // 用于支持虚函数表的偏移
  char _shortbuf[1];           // 用于未分配缓冲区时的短缓冲(通常 stderr 使用)
 
  // 线程安全支持
  _IO_lock_t *_lock; // 指向锁结构的指针,实现 FILE* 的线程安全访问
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt; // 字符集转换支持
  struct _IO_wide_data *_wide_data; // 宽字符流数据支持
 
  // 资源释放管理(glibc 的“延迟释放”机制)
  struct _IO_FILE *_freeres_list; // 空闲资源链表
  void *_freeres_buf;             // 释放用的缓冲区
  // 填充和兼容字段
  size_t __pad5;
  int _mode; // 流的编码模式:0 普通, -1 无效, 1 宽字符
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];  // 保留空间,避免结构体大小变化影响 ABI
};
 
int
_IO_fflush (FILE *fp)
{
  if (fp == NULL)
    return _IO_flush_all ();
  else
    {
      int result;
      CHECK_FILE (fp, EOF);
      _IO_acquire_lock (fp);
      result = _IO_SYNC (fp) ? EOF : 0;
      _IO_release_lock (fp);
      return result;
    }
}
libc_hidden_def (_IO_fflush)
 
weak_alias (_IO_fflush, fflush)
 
/*
展开_IO_SYNC来看
*/
 
      // 校验是否合法,紧接着调用vtable的__sync函数,传入fp
      result = ((IO_validate_vtable((*(
                     __typeof__(((struct _IO_FILE_plus){}).vtable)
                         *)(((char *)((fp))) +
                            __builtin_offsetof(struct _IO_FILE_plus, vtable)))))
                    ->__sync)(fp)
                   ? EOF
                   : 0;
 
// 看看_IO_FILE_plus的结构体是如何设计的
 
/* We always allocate an extra word following an _IO_FILE.
   This contains a pointer to the function jump table used.
   This is for compatibility with C++ streambuf; the word can
   be used to smash to a pointer to a virtual function table. */
 
struct _IO_FILE_plus
{
  FILE file;
  const struct _IO_jump_t *vtable;
};
 
// 取出vtable指针,并且调用函数IO_validate_vtable(*vtable)
/* Perform vtable pointer validation.  If validation fails, terminate
   the process.  */
// 确保这个 vtable 指针是合法的、位于 glibc 所允许的静态虚表段 __libc_IO_vtables 中,以防止攻击者伪造 FILE 对象或劫持 vtable 造成任意代码执行(典型的漏洞利用手段)
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
  /* Fast path: The vtable pointer is within the __libc_IO_vtables section.  */
  // 根据注释,这个vtable的指针在__libc_IO_vtables 部分
  // 获取 vtable 合法段 .rodata.__libc_IO_vtables 的大小
  // 这两个符号是链接器符号,表示段的起始和终止地址
  // 这个段中存放的是所有合法的 glibc 内建虚表对象
  uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
  uintptr_t ptr = (uintptr_t) vtable;
  // 计算当前虚表指针距离合法段起始的偏移量
  uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
  // 如果虚表指针 vtable 不在合法段内(即超出了 vtable 区间),调用 _IO_vtable_check() 终止程序
  // 这个检查防止了伪造的 FILE* 使用了任意地址的虚表,从而执行恶意代码
  if (__glibc_unlikely (offset >= section_length))
    /* The vtable pointer is not in the expected section.  Use the
       slow path, which will terminate the process if necessary.  */
    _IO_vtable_check ();
  // 返回原始指针,表示验证通过
  return vtable;
}
/*
攻击的例子:
FILE *fp = malloc(sizeof(_IO_FILE_plus));
fp->vtable = attacker_controlled_address;
fclose(fp);  // 控制程序流程
 
为了防止 fclose 等函数通过伪造 FILE* 执行攻击者构造的 vtable,glibc 在关键函数如 fclose() 内部调用了
const struct _IO_jump_t *vtable = IO_validate_vtable(fp->vtable);
*/
 
// 看看虚函数表是如何设计的呢
struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
};

以下是基于提供的函数列表整理的Markdown表格,按功能分类展示标准I/O文件操作的核心方法及其作用:

文件操作核心方法

方法名 实际调用/实现 功能描述
close
_IO_file_close
调用
close(fd)
关闭文件描述符
doallocate
_IO_file_doallocate
为文件流分配内部缓冲区
finish
_IO_file_finish
释放资源(如
fclose
中调用),清理缓冲区、锁、关闭文件描述符
overflow
_IO_file_overflow
写缓冲区满时触发(如
putc
需刷新),将缓冲数据写入文件
read
_IO_file_read
底层调用
read(fd, …)
读取文件数据
write
_IO_new_file_write
底层调用
write(fd, …)
写入数据到文件

文件定位控制

方法名 实际调用/实现 功能描述
seek
_IO_file_seek
调用
lseek(fd, …)
调整文件指针
seekoff
_IO_new_file_seekoff
处理相对偏移定位(如
fseek(fp, offset, SEEK_CUR)
seekpos
_IO_default_seekpos
默认实现的绝对定位(如
SEEK_SET
),可能调用
seekoff

缓冲与同步

方法名 实际调用/实现 功能描述
setbuf
_IO_new_file_setbuf
设置自定义缓冲区(如
setvbuf()
调用)
sync
_IO_new_file_sync
强制缓冲数据写入文件系统(如
fflush

读取相关扩展

方法名 实际调用/实现 功能描述
underflow
_IO_file_underflow
读缓冲区空时触发,从文件读取新数据填充缓冲
uflow
_IO_default_uflow
简化版
underflow
(返回首个字符,通常不用于文件流)
xsgetn
_IO_file_xsgetn
批量读取接口(如
fread
直接调用)
xsputn
_IO_file_xsputn
批量写入接口(如
fwrite
直接调用)

辅助功能

方法名 实际调用/实现 功能描述
pbackfail
_IO_default_pbackfail

ungetc()
失败时的回退处理(文件流默认实现)
stat
_IO_file_stat
获取文件状态(调用
fstat
返回
struct stat
showmanyc
_IO_default_showmanyc
预估可读字节数(如C++的
in_avail()
imbue
_IO_default_imbue
国际化相关(如切换locale,glibc默认空实现)
JUMP_INIT_DUMMY 占位项,用于版本兼容,不参与实际调度

说明

标有
_IO_default_*
的方法为默认实现,文件流可能重写关键方法(如读写定位)。涉及缓冲区的操作(如
overflow
/
underflow
)是性能优化的核心环节。close _IO_file_close 真正调用 close(fd) 的实现
doallocate _IO_file_doallocate 为流分配缓冲区
finish _IO_file_finish 清理资源,比如 fclose 中被调用,释放缓冲、锁、关闭文件描述符
imbue _IO_default_imbue 国际化相关,切换 locale,glibc 一般为默认空实现
JUMP_INIT_DUMMY 占位项 glibc 约定的 dummy 项,通常是一个 int,用于版本兼容,不参与调度
overflow _IO_file_overflow 写缓冲区满时触发(如 putc 触发 flush);把缓冲区数据写入文件
pbackfail _IO_default_pbackfail ungetc() 失败时调用;对文件流一般为默认实现
read _IO_file_read 真正调用 read(fd, …) 的实现
seek _IO_file_seek 真正调用 lseek(fd, …) 的实现
seekoff _IO_new_file_seekoff 相对位置偏移;fseek(fp, offset, SEEK_CUR) 走这里
seekpos _IO_default_seekpos 绝对位置定位;如 fseek(fp, offset, SEEK_SET);默认实现可能会调用 seekoff
setbuf _IO_new_file_setbuf 设置缓冲区,如 setvbuf() 走这里
showmanyc _IO_default_showmanyc 用于估计可读取字节数(如 C++ 中的 in_avail())
stat _IO_file_stat 对底层文件调用 fstat 并返回 struct stat
sync _IO_new_file_sync 把缓冲写入文件系统;用于 fflush
uflow _IO_default_uflow 相当于 underflow 后再返回第一个字符;简化版(不用于文件)
underflow _IO_file_underflow 读缓冲区空时调用,尝试从文件读入更多数据
write _IO_new_file_write 真正调用 write(fd, …) 的实现
xsgetn _IO_file_xsgetn 一次性读取多个字符的接口(如 fread)
xsputn _IO_file_xsputn 一次性写多个字符的接口(如 fwrite)

实现方式


// 实现了几种,比如
/*
_IO_file_jumps_mmap
_IO_file_jumps
_IO_file_jumps_maybe_mmap
_IO_wfile_jumps
具体的是前边还有一层函数internal,每次会具体的判断用哪一个,这里用_IO_file_jumps来做示范,最常用的
*/
 
const struct _IO_jump_t _IO_file_jumps libio_vtable =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_file_finish),
  JUMP_INIT(overflow, _IO_file_overflow),
  JUMP_INIT(underflow, _IO_file_underflow),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_default_pbackfail),
  JUMP_INIT(xsputn, _IO_file_xsputn),
  JUMP_INIT(xsgetn, _IO_file_xsgetn),
  JUMP_INIT(seekoff, _IO_new_file_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_new_file_setbuf),
  JUMP_INIT(sync, _IO_new_file_sync),
  JUMP_INIT(doallocate, _IO_file_doallocate),
  JUMP_INIT(read, _IO_file_read),
  JUMP_INIT(write, _IO_new_file_write),
  JUMP_INIT(seek, _IO_file_seek),
  JUMP_INIT(close, _IO_file_close),
  JUMP_INIT(stat, _IO_file_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};
libc_hidden_data_def (_IO_file_jumps)
 
 
// 跳转到_IO_new_file_sync函数

代码调用


/*
刷新写缓冲区(写入到文件)
同步读缓冲区的位置(处理读指针偏移)
0成功 -1失败
*/
int
_IO_new_file_sync (FILE *fp)
{
  ssize_t delta; // 用于记录读指针与读缓冲区结束的偏移
  int retval = 0; // 返回的结果
 
  /*    char* ptr = cur_ptr(); */
  // 当前写指针 - 写缓冲起始位置
  if (fp->_IO_write_ptr > fp->_IO_write_base)
    // 说明有内容可以写,调用_IO_do_flush函数
    if (_IO_do_flush(fp)) return EOF;
  // 读指针同步(如果读缓冲未完全消费)
  // 把文件偏移同步到当前读指针位置
  delta = fp->_IO_read_ptr - fp->_IO_read_end;
  if (delta != 0)
    {
      off64_t new_pos = _IO_SYSSEEK (fp, delta, 1);
      if (new_pos != (off64_t) EOF)
    fp->_IO_read_end = fp->_IO_read_ptr;
      else if (errno == ESPIPE)
    ; /* Ignore error from unseekable devices. */
      else
    retval = EOF;
    }
  if (retval != EOF)
    // 说明如果同步成功,就将 FILE* 的缓存 offset 标记为无效(以后要重新用 lseek 获取)
    fp->_offset = _IO_pos_BAD;
  /* FIXME: Cleanup - can this be shared? */
  /*    setg(base(), ptr, ptr); */
  return retval;
}
libc_hidden_ver (_IO_new_file_sync, _IO_file_sync)

最终调用的_IO_do_flush函数


/*
整体的流程
new_do_write(fp, data, to_do)
 ├── is_append? → 标记 offset 为无效
 ├── 否 → 判断是否需要 seek(读写切换)
 │     └── 如果 _IO_read_end ≠ _IO_write_base → 计算 delta → lseek
 ├── write(fd, data, to_do)
 ├── 更新列号(格式化支持)
 ├── 清空读缓冲(读写切换)
 └── 归位写缓冲区指针
*/
static size_t
new_do_write (FILE *fp, const char *data, size_t to_do)
{
  size_t count;
  if (fp->_flags & _IO_IS_APPENDING)
    /* On a system without a proper O_APPEND implementation,
       you would need to sys_seek(0, SEEK_END) here, but is
       not needed nor desirable for Unix- or Posix-like systems.
       Instead, just indicate that offset (before and after) is
       unpredictable. */
    fp->_offset = _IO_pos_BAD;
  else if (fp->_IO_read_end != fp->_IO_write_base)
    {
      off64_t new_pos
    = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
      if (new_pos == _IO_pos_BAD)
    return 0;
      fp->_offset = new_pos;
    }
  count = _IO_SYSWRITE (fp, data, to_do);
  if (fp->_cur_column && count)
    fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
  _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
  fp->_IO_write_end = (fp->_mode <= 0
               && (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
               ? fp->_IO_buf_base : fp->_IO_buf_end);
  return count;
}
 
// 最后调用了write的系统调用
/* Write NBYTES of BUF to FD. Return the number written, or -1. */
ssize_t __libc_write (int fd, const void *buf, size_t nbytes)
{
  return SYSCALL_CANCEL (write, fd, buf, nbytes);
}
 
libc_hidden_def (__libc_write)
weak_alias (__libc_write, __write)
 
 
//展开
 
/*
这段代码就是用内联汇编实现 Linux x86_64 上的 write 系统调用:
用寄存器传递参数
调用 syscall 指令
处理 syscall 返回值的错误码,设置 errno
返回实际写入的字节数或者 -1(错误)
它是 write 函数的核心实现,glibc 通过这个函数来完成真正的写文件操作
*/
  return ({
    long int sc_ret;
    if ((1))
      sc_ret = ({
        unsigned long int resultvar = ({
          unsigned long int resultvar;
          __typeof__((nbytes) - (nbytes)) __arg3 =
              ((__typeof__((nbytes) - (nbytes)))(nbytes));
          __typeof__((buf) - (buf)) __arg2 = ((__typeof__((buf) - (buf)))(buf));
          __typeof__((fd) - (fd)) __arg1 = ((__typeof__((fd) - (fd)))(fd));
          register __typeof__((nbytes) - (nbytes)) _a3 asm("rdx") = __arg3;
          register __typeof__((buf) - (buf)) _a2 asm("rsi") = __arg2;
          register __typeof__((fd) - (fd)) _a1 asm("rdi") = __arg1;
          asm volatile("syscall
	"
                       : "=a"(resultvar)
                       : "0"(1), "r"(_a1), "r"(_a2), "r"(_a3)
                       : "memory", "cc", "r11", "cx");
          (long int)resultvar;
        });
        if (__builtin_expect(
                (((unsigned long int)(long int)(resultvar) >= -4095L)), 0)) {
          ((*__errno_location()) = ((-(resultvar))));
          resultvar = (unsigned long int)-1;
        }
        (long int)resultvar;
      });
    else {
      int sc_cancel_oldtype = 0;
      sc_ret = ({
        unsigned long int resultvar = ({
          unsigned long int resultvar;
          __typeof__((nbytes) - (nbytes)) __arg3 =
              ((__typeof__((nbytes) - (nbytes)))(nbytes));
          __typeof__((buf) - (buf)) __arg2 = ((__typeof__((buf) - (buf)))(buf));
          __typeof__((fd) - (fd)) __arg1 = ((__typeof__((fd) - (fd)))(fd));
          register __typeof__((nbytes) - (nbytes)) _a3 asm("rdx") = __arg3;
          register __typeof__((buf) - (buf)) _a2 asm("rsi") = __arg2;
          register __typeof__((fd) - (fd)) _a1 asm("rdi") = __arg1;
          asm volatile("syscall
	"
                       : "=a"(resultvar)
                       : "0"(1), "r"(_a1), "r"(_a2), "r"(_a3)
                       : "memory", "cc", "r11", "cx");
          (long int)resultvar;
        });
        if (__builtin_expect(
                (((unsigned long int)(long int)(resultvar) >= -4095L)), 0)) {
          ((*__errno_location()) = ((-(resultvar))));
          resultvar = (unsigned long int)-1;
        }
        (long int)resultvar;
      });
      ((void)(sc_cancel_oldtype));
    }
    sc_ret;
  });
}

不主动调用fflush的时候,触发write操作的函数是(写缓冲区写满了)


int
_IO_new_file_overflow (FILE *f, int ch)
{
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      f->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
  /* If currently reading or no buffer allocated. */
  if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
    {
      /* Allocate a buffer if needed. */
      if (f->_IO_write_base == NULL)
    {
      _IO_doallocbuf (f);
      _IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
    }
      /* Otherwise must be currently reading.
     If _IO_read_ptr (and hence also _IO_read_end) is at the buffer end,
     logically slide the buffer forwards one block (by setting the
     read pointers to all point at the beginning of the block).  This
     makes room for subsequent output.
     Otherwise, set the read pointers to _IO_read_end (leaving that
     alone, so it can continue to correspond to the external position). */
      if (__glibc_unlikely (_IO_in_backup (f)))
    {
      size_t nbackup = f->_IO_read_end - f->_IO_read_ptr;
      _IO_free_backup_area (f);
      f->_IO_read_base -= MIN (nbackup,
                   f->_IO_read_base - f->_IO_buf_base);
      f->_IO_read_ptr = f->_IO_read_base;
    }
 
      if (f->_IO_read_ptr == f->_IO_buf_end)
    f->_IO_read_end = f->_IO_read_ptr = f->_IO_buf_base;
      f->_IO_write_ptr = f->_IO_read_ptr;
      f->_IO_write_base = f->_IO_write_ptr;
      f->_IO_write_end = f->_IO_buf_end;
      f->_IO_read_base = f->_IO_read_ptr = f->_IO_read_end;
 
      f->_flags |= _IO_CURRENTLY_PUTTING;
      if (f->_mode <= 0 && f->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
    f->_IO_write_end = f->_IO_write_ptr;
    }
  if (ch == EOF)
    return _IO_do_write (f, f->_IO_write_base,
             f->_IO_write_ptr - f->_IO_write_base);
  // buffer真的已经满了,调用_IO_do_flush,也就是执行上述的逻辑
  if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
    if (_IO_do_flush (f) == EOF)
      return EOF;
  *f->_IO_write_ptr++ = ch;
  if ((f->_flags & _IO_UNBUFFERED)
      || ((f->_flags & _IO_LINE_BUF) && ch == '
'))
    if (_IO_do_write (f, f->_IO_write_base,
              f->_IO_write_ptr - f->_IO_write_base) == EOF)
      return EOF;
  return (unsigned char) ch;
}
libc_hidden_ver (_IO_new_file_overflow, _IO_file_overflow)
© 版权声明

相关文章

暂无评论

none
暂无评论...