这是IO FILE系列的第二篇文章,主要写的是对于fread函数的源码分析,描述fread读取文件流的主要流程以及函数对IO FILE结构体以及结构体中的vtable的操作。流程有点小复杂,入坑需谨慎。
总体流程
第一篇文章fopen
[1]的分析,讲述了系统如何为FILE结构体分配内存并将其链接进入_IO_list_all
的。
这篇文章则是说在创建了文件FILE以后,fread如何实现从文件中读取数据的。在开始源码分析之前,我先把fread的流程图给贴出来,后面在分析源码的时候,可以适时的参考下流程图,增进理解:
从图中可以看到,整体流程为fread
调用_IO_sgetn
,_IO_sgetn
调用vtable中的_IO_XSGETN
也就是_IO_file_xsgetn
,_IO_file_xsgetn
是fread
实现的核心函数。它的流程简单总结为:
1.判断fp->_IO_buf_base
输入缓冲区是否为空,如果为空则调用的_IO_doallocbuf
去初始化输入缓冲区。2.在分配完输入缓冲区或输入缓冲区不为空的情况下,判断输入缓冲区是否存在数据。3.如果输入缓冲区有数据则直接拷贝至用户缓冲区,如果没有或不够则调用__underflow
函数执行系统调用读取数据到输入缓冲区,再拷贝到用户缓冲区。
源码分析
仍然是基于glibc2.23的源码分析,使用带符号的glibc对程序进行调试。
fread的函数原型是:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);The function fread() reads nmemb items of data, each size bytes long, from the stream pointed to by stream, storing them at the location given by ptr.
demo程序如下:
#includeint main(){
char data[20]; FILE*fp=fopen("test","rb"); fread(data,1,20,fp); return 0;}
要让程序可以运行,执行命令echo 111111>test
,然后gdb加载程序,断点下在fread,开始一边看源码,一边动态跟踪流程。
程序运行起来后,可以看到断在_IO_fread
函数。在开始之前我们先看下FILE结构体fp的内容,从图里可以看到此时的_IO_read_ptr
和_IO_buf_base
等指针都还是空的,后面的分析一个很重要的步骤也是看这些指针是如何被赋值以及发挥作用的:
vtable中的指针内容如下:
fread实际上是_IO_fread
函数,文件目录为/libio/iofread.c
:
_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; ... # 调用_IO_sgetn函数 bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested); ... return bytes_requested == bytes_read ? count : bytes_read / size;}libc_hidden_def (_IO_fread)
_IO_fread
函数调用_IO_sgetn
函数,跟进去该函数:
_IO_size_t_IO_sgetn (_IO_FILE *fp, void *data, _IO_size_t n){
/* FIXME handle