IO_file fopen

Posted Nullan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IO_file fopen相关的知识,希望对你有一定的参考价值。

@TOC


前言

跟着raycp师傅学习IO_FILE。原文链接


提示:以下是本篇文章正文内容,下面案例可供参考

概述


缓冲区建立函数_IO_doallocbuf(上一篇详细描述过)会在里面建立输入输出缓冲区,并把基地址保存在_IO_buf_base中,结束地址保存在_IO_buf_end中。
如果缓冲区作为输出缓冲区使用,会将基址址给_IO_write_base,结束地址给_IO_write_end,同时_IO_write_ptr表示为已经使用的地址。
即_IO_write_base到_IO_write_ptr之间的空间是已经使用的缓冲区,_IO_write_ptr到_IO_write_end之间为剩余的输出缓冲区。

首先判断输出缓冲区还有多少剩余,如果有剩余则将目标输出数据拷贝至输出缓冲区。
如果输出缓冲区没有剩余(输出缓冲区未建立也是没有剩余)或输出缓冲区不够则调用_IO_OVERFLOW建立输出缓冲区或刷新输出缓冲区。
输出缓冲区刷新后判断剩余的目标输出数据是否超过块的size,如果超过块的size,则不通过输出缓冲区直接以块为单位,使用new_do_write输出目标数据。
如果按块输出数据后还剩下一点数据则调用_IO_default_xsputn将数据拷贝至输出缓冲区

函数原型

 size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
   The function fwrite() writes nmemb items of data, each size bytes long, to the stream pointed to by stream, obtaining them from the location given by ptr.

源码

代码如下(示例):

data = pd.read_csv(
    'https://labfile.oss.aliyuncs.com/courses/1283/adult.data.csv')
print(data.head())
_IO_size_t
_IO_fwrite (const void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
  _IO_size_t request = size * count;
  ...
  if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1)
    written = _IO_sputn (fp, (const char *) buf, request);
  ...
}
libc_hidden_def (_IO_fwrite)

没有做过多的操作就调用了_IO_sputn函数,该函数是vtable中的__xsputn (_IO_new_file_xsputn)在文件/libio/fileops.c中,这里就不一次性把函数的所有源码都贴在这里,而是按部分贴在下面每个部分的开始的地方,不然感觉有些冗余。

如流程所示,源码分析分四个部分进行,与流程相对应,其中下面每部分刚开始的代码都是_IO_new_file_xsputn函数中的源码。


## 将目标输出数据拷贝至输出缓冲区
_IO_size_t
_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{ 

    _IO_size_t count = 0;
...
    ## 判断输出缓冲区还有多少空间
    else if (f->_IO_write_end > f->_IO_write_ptr)
    count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */

  ## 如果输出缓冲区有空间,则先把数据拷贝至输出缓冲区
  if (count > 0)
    {
      if (count > to_do)
  count = to_do;
  ...
      memcpy (f->_IO_write_ptr, s, count);
      f->_IO_write_ptr += count;
    ## 计算是否还有目标输出数据剩余
      s += count;
      to_do -= count;

主要功能就是判断输出缓冲区还有多少空间,其中像demo中的程序所示的f->_IO_write_end以及f->_IO_write_ptr均为0,此时的输出缓冲区为0。

另一部分则是如果输出缓冲区如果仍有剩余空间的话,则将目标输出数据拷贝至输出缓冲区,并计算在输出缓冲区填满后,是否仍然剩余目标输出数据。

如果f->_IO_write_end以及f->_IO_write_ptr均为0,此时的输出缓冲区为0。

另一部分则是如果输出缓冲区如果仍有剩余空间的话,则将目标输出数据拷贝至输出缓冲区,并计算在输出缓冲区填满后,是否仍然剩余目标输出数据。

建立输出缓冲区或flush输出缓冲区,并以缓冲区方式输出

## 如果还有目标数据剩余,此时则表明输出缓冲区未建立或输出缓冲区已经满了
  if (to_do + must_flush > 0)
    {
      _IO_size_t block_size, do_write;
      ## 函数实现清空输出缓冲区或建立缓冲区的功能
      if (_IO_OVERFLOW (f, EOF) == EOF)
  
  return to_do == 0 ? EOF : n - to_do;

      ## 检查输出数据是否是大块
      block_size = f->_IO_buf_end - f->_IO_buf_base;
      do_write = to_do - (block_size >= 128 ? to_do % block_size : 0);

经过了上一步骤后,如果还有目标输出数据,表明输出缓冲区未建立或输出缓冲区已经满了,此时调用_IO_OVERFLOW函数,该函数功能主要是实现刷新输出缓冲区或建立缓冲区的功能,该函数是vtable函数中的__overflow(_IO_new_file_overflow)

int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
  ## 判断标志位是否包含_IO_NO_WRITES
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      f->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
  
  ## 判断输出缓冲区是否为空
  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);
  }
     
     ## 初始化指针
      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);
  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 == '\\n'))
    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)

__overflow函数首先检测IO FILE的_flags是否包含_IO_NO_WRITES标志位,如果包含的话则直接返回。

接着判断f->_IO_write_base是否为空,如果为空的话表明输出缓冲区尚未建立,就调用_IO_doallocbuf函数去分配输出缓冲区,_IO_doallocbuf函数源码在上一篇fread中已经分析过了就不跟过去了,它的功能是分配输入输出缓冲区并将指针 _IO_buf_base 和 _IO_buf_end 赋值。在执行完 _IO_doallocbuf分配空间后调用 _IO_setg 宏,该宏的定义为如下,它将输入相关的缓冲区指针赋值为 _IO_buf_base 指针

#define _IO_setg(fp, eb, g, eg)  ((fp)->_IO_read_base = (eb),\\
  (fp)->_IO_read_ptr = (g), (fp)->_IO_read_end = (eg))

_IO_buf_base和_IO_buf_end被赋值,且输入缓冲区相关指针被赋值为_IO_buf_base

将f->_IO_write_base以及将f->_IO_write_ptr设置成f->_IO_read_ptr指针(read都被赋值为buf_base);将f->_IO_write_end赋值为f->_IO_buf_end指针。

int
_IO_new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
  return (to_do == 0
    || (_IO_size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;
}
libc_hidden_ver (_IO_new_do_write, _IO_do_write)

该函数调用了new_do_write

static
_IO_size_t
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
  _IO_size_t count;
  ...
  ## 额外判断
  else if (fp->_IO_read_end != fp->_IO_write_base)
    {
      _IO_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);
  ...
  ## 刷新设置缓冲区指针
  _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;
}

进行一个判断,判断fp->_IO_read_end是否等于fp->_IO_write_base,如果不等的话,调用_IO_SYSSEEK去调整文件偏移,这个函数就不跟进去了,正常执行流程不会过去这里。

接着就调用_IO_SYSWRITE函数,该函数是vtable中的__write(_IO_new_file_write)函数,也是最终执行系统调用的地方

_IO_ssize_t
_IO_new_file_write (_IO_FILE *f, const void *data, _IO_ssize_t n)
{
  _IO_ssize_t to_do = n;
  while (to_do > 0)
    {
    ## 系统调用write输出
      _IO_ssize_t count = (__builtin_expect (f->_flags2
               & _IO_FLAGS2_NOTCANCEL, 0)
         ? write_not_cancel (f->_fileno, data, to_do)
         : write (f->_fileno, data, to_do));
  ...   
  return n;
}

以大块输出

运行到此处,此时已经经过了_IO_OVERFLOW函数(对输出缓冲区进行了初始化或者刷新),也就是说此时的IO FILE缓冲区指针的状态是处于刷新的初始化状态,输出缓冲区中也没有数据。

上面这部分代码检查剩余目标输出数据大小,如果超过输入缓冲区f->_IO_buf_end - f->_IO_buf_base的大小,则为了提高效率,不再使用输出缓冲区,而是以块为基本单位直接将缓冲区调用new_do_write输出。new_do_write函数在上面已经跟过了就是输出,并刷新指针设置。

剩余目标放入缓冲区中

在以大块为基本单位把数据直接输出后可能还剩余小块数据,IO采用的策略则是将剩余目标输出数据放入到输出缓冲区里面

 ## 剩余的数据拷贝至输出缓冲区
      if (to_do)
  to_do -= _IO_default_xsputn (f, s+do_write, to_do);

程序调用_IO_default_xsputn函数对剩下的s+do_write数据进行操作,跟进去该函数

_IO_size_t
_IO_default_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{
  const char *s = (char *) data;
  _IO_size_t more = n;
  if (more <= 0)
    return 0;
  for (;;)
    {
      /* Space available. */
      if (f->_IO_write_ptr < f->_IO_write_end)
  {
    _IO_size_t count = f->_IO_write_end - f->_IO_write_ptr;
    if (count > more)
      count = more;
    if (count > 20)
      {
        ## 输出长度大于20,则调用memcpy拷贝
        memcpy (f->_IO_write_ptr, s, count);
        f->_IO_write_ptr += count;
#endif
        s += count;
      }
    else if (count)
      {
        ## 小于20则直接赋值
        char *p = f->_IO_write_ptr;
        _IO_ssize_t i;
        for (i = count; --i >= 0; )
    *p++ = *s++;
        f->_IO_write_ptr = p;
      }
    more -= count;
  }
  ## 如果输出缓冲区为空,则调用`_IO_OVERFLOW`直接输出。
      if (more == 0 || _IO_OVERFLOW (f, (unsigned char) *s++) == EOF)
  break;
      more--;
    }
  return n - more;
}
libc_hidden_def (_IO_default_xsputn)

可以看到函数最主要的作用就是将剩余的目标输出数据拷贝到输出缓冲区里。为了性能优化,当长度大于20时,使用memcpy拷贝,当长度小于20时,使用for循环赋值拷贝。如果输出缓冲区为空,则调用_IO_OVERFLOW进行输出。

以上是关于IO_file fopen的主要内容,如果未能解决你的问题,请参考以下文章

PHP常用代码片段

IO_FILE hack FSOP

IO_FILE fread

IO_File

_IO_FILE 的 Pybind11 文件指针包装器

IO_FILE hack 修改vtable