对于fread,它实际上是_IO_fread的宏,这个函数包装了_IO_sgetn,主要是添加fp的检查(然而你跟一下CHECK_FILE会发现当前版本的glibc并没有做任何检查,??),还有加锁。里面仍然是一层包装,调用了_IO_XSGETN这个宏,它实际上是执行当前_IO_FILE对应的vtable里的__xsgetn函数指针(_IO_xsgetn_t),对于默认的vtable _IO_file_jumps来说,实际上调用的就是_IO_file_xsgetn

// stdio-common/getw.c, line #21
#define fread(p, m, n, s) _IO_fread (p, m, n, s)

// libio/iofread.c, line #29
_IO_size_t
_IO_fread (void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
  _IO_size_t bytes_requested = size * count;
  _IO_size_t bytes_read;
  CHECK_FILE (fp, 0);
  if (bytes_requested == 0)
    return 0;
  _IO_acquire_lock (fp);
  bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);
  _IO_release_lock (fp);
  return bytes_requested == bytes_read ? count : bytes_read / size;
}
libc_hidden_def (_IO_fread)

// libio/genops.c, line #463
_IO_size_t
_IO_sgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
  /* FIXME handle putback buffer here! */
  return _IO_XSGETN (fp, data, n);
}
libc_hidden_def (_IO_sgetn)
    先检查`_IO_buf_base`是否为NULL,即该fp是否已经分配过缓冲区,若没有这直接`_IO_doallocbuf`申请空间,

    接着是循环尝试读取,首先检查缓冲区里的剩余数据(`_IO_read_end-_IO_read_ptr`),如果比申请读取的多,则直接拷贝`n`个字节到`data`后return,否则将其全部拷贝到`data`后继续尝试读取。

    如果还需要读取的字节数小于缓冲区大小(`_IO_buf_end-_IO_buf_base`),则调用`__underflow`将数据先读取到缓冲区里,否则直接调用`_IO_SYSREAD`(`sys_read`的简单包装)将数据读取到`data`中,为了减少系统调用次数,这里还把直接用`_IO_SYSREAD`读取的字符数按缓冲区大小对齐(n*block_size),剩下零碎数据的再用`__underflow`读取
// libio/fileops.c, line #1358
_IO_size_t
_IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
  _IO_size_t want, have;
  _IO_ssize_t count;
  char *s = data;

  want = n;

  if (fp->_IO_buf_base == NULL)
    {
      /* Maybe we already have a push back pointer.  */
      if (fp->_IO_save_base != NULL)
        {
          free (fp->_IO_save_base);
          fp->_flags &= ~_IO_IN_BACKUP;
        }
      _IO_doallocbuf (fp);
    }

  while (want > 0)
    {
      have = fp->_IO_read_end - fp->_IO_read_ptr;
      if (want <= have)
        {
          memcpy (s, fp->_IO_read_ptr, want);
          fp->_IO_read_ptr += want;
          want = 0;
        }
      else
        {
          if (have > 0)
            {
#ifdef _LIBC
              s = __mempcpy (s, fp->_IO_read_ptr, have);
#else
              memcpy (s, fp->_IO_read_ptr, have);
              s += have;
#endif
              want -= have;
              fp->_IO_read_ptr += have;
            }

          /* Check for backup and repeat */
          if (_IO_in_backup (fp))
            {
              _IO_switch_to_main_get_area (fp);
              continue;
            }

          /* If we now want less than a buffer, underflow and repeat
             the copy.  Otherwise, _IO_SYSREAD directly to
             the user buffer. */
          if (fp->_IO_buf_base
              && want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))
            {
              if (__underflow (fp) == EOF)
                break;

              continue;
            }

          /* These must be set before the sysread as we might longjmp out
             waiting for input. */
          _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
          _IO_setp (fp, fp->_IO_buf_base, fp->_IO_buf_base);

          /* Try to maintain alignment: read a whole number of blocks.  */
          count = want;
          // 按block处理,没对齐的交给下一次循环处理(实际上相当于让__underflow处理)
          if (fp->_IO_buf_base)
            {
              _IO_size_t block_size = fp->_IO_buf_end - fp->_IO_buf_base;
              if (block_size >= 128)
                count -= want % block_size;
            }

          count = _IO_SYSREAD (fp, s, count);
          if (count <= 0)
            {
              if (count == 0)
                fp->_flags |= _IO_EOF_SEEN;
              else
                fp->_flags |= _IO_ERR_SEEN;

              break;
            }

          s += count;
          want -= count;
          if (fp->_offset != _IO_pos_BAD)
            _IO_pos_adjust (fp->_offset, count);
        }
    }

  return n - want;
}
libc_hidden_def (_IO_file_xsgetn)
    这个__underflow实际上包装了`_IO_UNDERFLOW`,检查缓冲区是否为空,切换fp为get模式(`_IO_switch_to_get_mode`实际上是调用了`_IO_OVERFLOW`,清空输出缓冲区,并调整ptr,但实际并没有判断对应的fd是否可读),检查fp是否在backup模式(~~这东西我也没搞明白是啥~~是处理 `_IO_sputbackc`用的),然后`_IO_UNDERFLOW`调了vtable里面的`__underflow`,对于默认的`_IO_file_jumps`来说就是`_IO_file_underflow`,实际上又是`_IO_new_file_underflow`的宏定义(glibc传统艺能)
// libio/genops.c, line #314
int
__underflow (_IO_FILE *fp)
{
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  if (_IO_vtable_offset (fp) == 0 && _IO_fwide (fp, -1) != -1)
    return EOF;
#endif

  if (fp->_mode == 0)
    _IO_fwide (fp, -1);
  if (_IO_in_put_mode (fp))
    if (_IO_switch_to_get_mode (fp) == EOF)
      return EOF;
  if (fp->_IO_read_ptr < fp->_IO_read_end)
    return *(unsigned char *) fp->_IO_read_ptr;
  if (_IO_in_backup (fp))
    {
      _IO_switch_to_main_get_area (fp);
      if (fp->_IO_read_ptr < fp->_IO_read_end)
    return *(unsigned char *) fp->_IO_read_ptr;
    }
  if (_IO_have_markers (fp))
    {
      if (save_for_backup (fp, fp->_IO_read_end))
    return EOF;
    }
  else if (_IO_have_backup (fp))
    _IO_free_backup_area (fp);
  return _IO_UNDERFLOW (fp);
}
libc_hidden_def (__underflow)

// libio/libioP.h, line #168
#define _IO_UNDERFLOW(FP) JUMP0 (__underflow, FP)
    `_IO_new_file_underflow`先检查了是否可以读取(`fp->_flags & _IO_NO_READS`),检查缓冲区是否为空,检查缓冲区是否已经申请,调用`_IO_OVERFLOW`来flush stdout保持同步,切换fp到get模式(`_IO_switch_to_get_mode`),把各个read/write指针都改为`_IO_buf_base`(注释说的原因是可能会longjmp到其他地方导致问题,但我也不清楚具体有什么问题,不过既然已经调用了`_IO_switch_to_get_mode`,write_buf已经flush,而且前面判断也保证了read_buf里面也没有东西,这些指针的具体内容也就不再重要),然后调用`_IO_SYSREAD`(`sys_read`简单包装),尝试读取一整个缓冲区大小的内容,最后用实际读取进来的字节数更新`_IO_read_end`
// libio/fileops.c, line #529
int
_IO_new_file_underflow (_IO_FILE *fp)
{
  _IO_ssize_t count;
#if 0
  /* SysV does not make this test; take it out for compatibility */
  if (fp->_flags & _IO_EOF_SEEN)
    return (EOF);
#endif

  if (fp->_flags & _IO_NO_READS)
    {
      fp->_flags |= _IO_ERR_SEEN;
      __set_errno (EBADF);
      return EOF;
    }
  if (fp->_IO_read_ptr < fp->_IO_read_end)
    return *(unsigned char *) fp->_IO_read_ptr;

  if (fp->_IO_buf_base == NULL)
    {
      /* Maybe we already have a push back pointer.  */
      if (fp->_IO_save_base != NULL)
        {
          free (fp->_IO_save_base);
          fp->_flags &= ~_IO_IN_BACKUP;
        }
      _IO_doallocbuf (fp);
    }

  /* Flush all line buffered files before reading. */
  /* FIXME This can/should be moved to genops ?? */
  if (fp->_flags & (_IO_LINE_BUF|_IO_UNBUFFERED))
    {
#if 0
      _IO_flush_all_linebuffered ();
#else
      /* We used to flush all line-buffered stream.  This really isn't
         required by any standard.  My recollection is that
         traditional Unix systems did this for stdout.  stderr better
         not be line buffered.  So we do just that here
         explicitly.  --drepper */
      _IO_acquire_lock (_IO_stdout);

      if ((_IO_stdout->_flags & (_IO_LINKED | _IO_NO_WRITES | _IO_LINE_BUF))
          == (_IO_LINKED | _IO_LINE_BUF))
        _IO_OVERFLOW (_IO_stdout, EOF);

      _IO_release_lock (_IO_stdout);
#endif
    }

  _IO_switch_to_get_mode (fp);

  /* This is very tricky. We have to adjust those
     pointers before we call _IO_SYSREAD () since
     we may longjump () out while waiting for
     input. Those pointers may be screwed up. H.J. */
  fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base;
  fp->_IO_read_end = fp->_IO_buf_base;
  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end
    = fp->_IO_buf_base;

  count = _IO_SYSREAD (fp, fp->_IO_buf_base,
                       fp->_IO_buf_end - fp->_IO_buf_base);
  if (count <= 0)
    {
      if (count == 0)
        fp->_flags |= _IO_EOF_SEEN;
      else
        fp->_flags |= _IO_ERR_SEEN, count = 0;
  }
  fp->_IO_read_end += count;
  if (count == 0)
    {
      /* If a stream is read to EOF, the calling application may switch active
         handles.  As a result, our offset cache would no longer be valid, so
         unset it.  */
      fp->_offset = _IO_pos_BAD;
      return EOF;
    }
  if (fp->_offset != _IO_pos_BAD)
    _IO_pos_adjust (fp->_offset, count);
  return *(unsigned char *) fp->_IO_read_ptr;
}
libc_hidden_ver (_IO_new_file_underflow, _IO_file_underflow)

fread就是这样(