IO_FILE fread
Posted Nullan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IO_FILE fread相关的知识,希望对你有一定的参考价值。
@TOC
前言
跟着raycp师傅学IO_FILE。原文链接
提示:以下是本篇文章正文内容,下面案例可供参考
流程
整体流程为fread调用_IO_sgetn,_IO_sgetn调用vtable中的_IO_XSGETN也就是_IO_file_xsgetn,_IO_file_xsgetn是fread实现的核心函数。
判断fp->_IO_buf_base输入缓冲区是否为空,如果为空则调用的_IO_doallocbuf去初始化输入缓冲区。
在分配完输入缓冲区或输入缓冲区不为空的情况下,判断输入缓冲区是否存在数据。
如果输入缓冲区有数据则直接拷贝至用户缓冲区,如果没有或不够则调用__underflow函数执行系统调用读取数据到输入缓冲区,再拷贝到用户缓冲区。
源码分析
对缓冲区初始化
首先是第一部分,在fp->_IO_buf_base为空时,也就是输入缓冲区未建立时,代码调用_IO_doallocbuf函数去建立输入缓冲区。
void
_IO_doallocbuf (_IO_FILE *fp)
{
if (fp->_IO_buf_base) # 如何输入缓冲区不为空,直接返回
return;
if (!(fp->_flags & _IO_UNBUFFERED) || fp->_mode > 0) #检查标志位
if (_IO_DOALLOCATE (fp) != EOF) ## 调用vtable函数
return;
_IO_setb (fp, fp->_shortbuf, fp->_shortbuf+1, 0);
}
libc_hidden_def (_IO_doallocbuf)
函数先检查fp->_IO_buf_base是否为空,如果不为空的话表明该输入缓冲区已被初始化,直接返回。如果为空,则检查fp->_flags看它是不是_IO_UNBUFFERED或者fp->_mode大于0,如果满足条件调用FILE的vtable中的_IO_file_doallocate
_IO_file_doallocate (_IO_FILE *fp)
{
_IO_size_t size;
char *p;
struct stat64 st;
...
size = _IO_BUFSIZ;
...
if (fp->_fileno >= 0 && __builtin_expect (_IO_SYSSTAT (fp, &st), 0) >= 0) # 调用`_IO_SYSSTAT`获取FILE信息
{
...
if (st.st_blksize > 0)# 修改相应需要申请的size。
size = st.st_blksize;
...
}
p = malloc (size);
...
_IO_setb (fp, p, p + size, 1); # 调用`_IO_setb`设置FILE缓冲区
return 1;
}
libc_hidden_def (_IO_file_doallocate)
_IO_file_doallocate函数是分配输入缓冲区的实现函数,首先调用_IO_SYSSTAT去获取文件信息,_IO_SYSSTAT函数是vtable中的 __stat函数。获取文件信息,修改相应需要申请的size。
_IO_file_doallocate函数是分配输入缓冲区的实现函数,首先调用_IO_SYSSTAT去获取文件信息,_IO_SYSSTAT函数是vtable中的 __stat函数,获取文件信息,修改相应需要申请的size。
空间申请出来后,调用_IO_setb
void
_IO_setb (_IO_FILE *f, char *b, char *eb, int a)
{
...
f->_IO_buf_base = b; # 设置_IO_buf_base
f->_IO_buf_end = eb; # 设置_IO_buf_end
...
}
libc_hidden_def (_IO_setb)
设置了_IO_buf_base和_IO_buf_end,可以预料到_IO_setb函数执行完后,fp的这两个指针被赋上值了
就是_IO_buf_base和_IO_buf_end
初始化缓冲区就完成了,函数返回_IO_file_doallocate后,接着_IO_file_doallocate也返回,回到_IO_file_xsgetn函数中。
拷贝输入缓冲区数据
程序就进入到第二部分:拷贝输入缓冲区数据,如果输入缓冲区里存在已输入的数据,则把它直接拷贝到目标缓冲区里。fp->_IO_read_ptr指向的是输入缓冲区的起始地址,fp->_IO_read_end指向的是输入缓冲区的结束地址。
将fp->_IO_read_end-fp->_IO_read_ptr之间的数据通过memcpy拷贝到目标缓冲区里。
执行系统调用读取数据
在输入缓冲区为0或者是不能满足需求的时候则会执行最后一步__underflow去执行系统调用read读取数据,并放入到输入缓冲区里。
因为demo里第一次读取数据,此时的fp->_IO_read_end以及fp->_IO_read_ptr都是0,因此会进入到__underflow
int
__underflow (_IO_FILE *fp)
{
# 额外的检查
...
if (fp->_IO_read_ptr < fp->_IO_read_end)
return *(unsigned char *) fp->_IO_read_ptr;
...
# 调用_IO_UNDERFLOW
return _IO_UNDERFLOW (fp);
}
libc_hidden_def (__underflow)
函数稍微做一些检查就会调用_IO_UNDERFLOW函数,其中一个检查是如果fp->_IO_read_ptr小于fp->_IO_read_end则表明输入缓冲区里存在数据,可直接返回,否则则表示需要继续读入数据。
检查都通过的话就会调用_IO_UNDERFLOW函数,该函数是FILE结构体vtable里的_IO_new_file_underflow
int
_IO_new_file_underflow (_IO_FILE *fp)
{
_IO_ssize_t count;
...
## 如果存在_IO_NO_READS标志,则直接返回
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;
...
## 如果没有输入缓冲区,则调用_IO_doallocbuf分配输入缓冲区
if (fp->_IO_buf_base == NULL)
{
...
_IO_doallocbuf (fp);
}
...
## 设置FILE结构体指针
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;
##调用_IO_SYSREAD函数最终执行系统调用读取数据
count = _IO_SYSREAD (fp, fp->_IO_buf_base,
fp->_IO_buf_end - fp->_IO_buf_base);
...
## 设置结构体指针
fp->_IO_read_end += count;
...
return *(unsigned char *) fp->_IO_read_ptr;
}
libc_hidden_ver (_IO_new_file_underflow, _IO_file_underflow)
这个_IO_new_file_underflow函数,是最终调用系统调用的地方,在最终执行系统调用之前,仍然有一些检查
检查FILE结构体的_flag标志位是否包含_IO_NO_READS,如果存在这个标志位则直接返回EOF,其中_IO_NO_READS标志位的定义是#define _IO_NO_READS 4 /* Reading not allowed */。
如果fp->_IO_buf_base位null,则调用_IO_doallocbuf分配输入缓冲区。
接着初始化设置FILE结构体指针,将他们都设置成fp->_IO_buf_base
调用_IO_SYSREAD(vtable中的_IO_file_read函数),该函数最终执行系统调用read,读取文件数据,数据读入到fp->_IO_buf_base中,读入大小为输入缓冲区的大小fp->_IO_buf_end - fp->_IO_buf_base。
设置输入缓冲区已有数据的size,即设置fp->_IO_read_end为fp->_IO_read_end += count。
第二步里面的如果fp->_IO_buf_base位null,则调用_IO_doallocbuf分配输入缓冲区,似乎有点累赘,因为之前已经分配了,这个原因在最后会说明。
其中第四步的_IO_SYSREAD(vtable中的_IO_file_read函数)的源码比较简单,就是执行系统调用函数read去读取文件数据
_IO_ssize_t
_IO_file_read (_IO_FILE *fp, void *buf, _IO_ssize_t size)
{
return (__builtin_expect (fp->_flags2 & _IO_FLAGS2_NOTCANCEL, 0)
? read_not_cancel (fp->_fileno, buf, size)
: read (fp->_fileno, buf, size));
}
_IO_file_underflow函数执行完毕以后,FILE结构体中各个指针已被赋值,且文件数据已读入,输入缓冲区里已经有数据,结构体值如下,其中fp->_IO_read_ptr指向输入缓冲区数据的开始位置,fp->_IO_read_end指向输入缓冲区数据结束的位置
_IO_read_ptr和_IO_buf_base一定相等,但是end不相等,因为数据不一定有缓冲区长,read是要读入的数据长度
函数执行完后,返回到_IO_file_xsgetn函数中,由于while循环的存在,重新执行第二部分,此时将输入缓冲区拷贝至目标缓冲区,最终返回。
以上是关于IO_FILE fread的主要内容,如果未能解决你的问题,请参考以下文章