cstdio中的文件访问函数
stdio.h中定义了一系列文件访问函数(fopen,fclose,fflush,freopen,setbuf,setvbuf),接下来我们一起来分析一下setvbuf对应的源码实现。
- fopen:打开文件
- fclose:关闭文件
- fflush:刷新文件流
- freopen:重新打开文件流(不同的文件或访问模式)
- setbuf:设置stream buf
- setvbuf:改变stream buf
改变文件流文件流buffer函数setvbuf
指定对应文件流stream的IO操作buffer,同时设定该块缓存buffer的操作mode和size大小,如果buffer指针是空指针,那么setvbuf函数将会自动分配一块size大小的buffer作为缓存使用。
int setvbuf ( FILE * stream, char * buffer, int mode, size_t size );
注意:上面的mode有以下的选择
- _IOFBF:Full Buffering:输出操作中,数据在buffer写满后写入物理文件;输入操作中,buffer只有在全为空时才被填写,填充的可能是多行数据;
- _IOLBF:Line Buffering:输出操作中,数据在新的一行插入FILE流对象或buffer写满时触发写入物理文件;输入操作中,buffer只有在buffer全为空时,写入新的一行到buffer中。
- _IONBF:No Buffering:不使用缓存buffer,所有输入输出操作都尽可能快地写入物理文件,当前模式下,buffer和size 参数将会被忽略
注意:setvbuf的调用时机,在一个文件流对象绑定到一个打开的文件之后,对该文件流对象进行文件读写操作之前。
我们可以看如下的例子:
我们打开了一个pFIle对象,并将其buffer设置为NULL(函数内部将自动生成一块大小为1024Byte大小的buffer),mode设置为_IOFBF。那么,在进行文件操作过程中,如向文件写入过程中,每写满1024字节才会触发一次将数据写入物理文件。
/* setvbuf example */
#include <stdio.h>int main ()
{
FILE *pFile;
pFile=fopen ("myfile.txt","w");
setvbuf ( pFile , NULL , _IOFBF , 1024 );
// File operations here
fclose (pFile);
return 0;
}
函数入口分析
函数作用描述与上面所说的基本一致,这里,我们也看到三个mode的宏定义。
在Glibc中,这个函数实际的实现是_IO_setvbuf,我们跳转到这个函数进行分析。
// glibc/libio/stdio.h
/* Make STREAM use buffering mode MODE.
If BUF is not NULL, use N bytes of it for buffering;
else allocate an internal buffer N bytes long. */
extern int setvbuf (FILE *__restrict __stream, char *__restrict __buf,
int __modes, size_t __n) __THROW;
/* The possibilities for the third argument to `setvbuf'. */
#define _IOFBF 0 /* Fully buffered. */
#define _IOLBF 1 /* Line buffered. */
#define _IONBF 2 /* No buffering. */
// glibc/libio/iosetvbuf.c
weak_alias (_IO_setvbuf, setvbuf)
函数逻辑分析
1.函数的大致框架
可以看到,整个函数的调用逻辑其实比较简单,使用switch-case,针对每一种设置模式进行处理,在之前进行参数检查和获取锁,在后面做释放锁和返回值的操作。
int
_IO_setvbuf (FILE *fp, char *buf, int mode, size_t size)
{
int result;
CHECK_FILE (fp, EOF);
_IO_acquire_lock (fp);
switch (mode)
{
case _IOFBF:
...
break;
case _IOLBF:
...
break;
case _IONBF:
...
break;
}
result = _IO_SETBUF (fp, buf, size) == NULL ? EOF : 0;
unlock_return:
_IO_release_lock (fp);
return result;
}
2.参数检查
检查fp指针是否有效,fp的_flags是否符合规范值
// glibc/libio/libioP.h
#ifdef IO_DEBUG
# define CHECK_FILE(FILE, RET) do { \
if ((FILE) == NULL \
|| ((FILE)->_flags & _IO_MAGIC_MASK) != _IO_MAGIC) \
{ \
__set_errno (EINVAL); \
return RET; \
} \
} while (0)
#else
# define CHECK_FILE(FILE, RET) do { } while (0)
#endif
3._IOFBF---full buffering的情况
这里主要做了以下几件事情:
- 首先将_IO_LINE_BUF和_IO_UNBUFFERED位置为0,因为目前是要求full buffering的;
- 然后我们检查输入参数buf,如果为空的话,我们要尝试进行分配buffer分配;
- 再次我们检查fp->_IO_buf_base参数,这里指向的是fp预先分配的缓存buffer,只有这里也为空,那就说明完全没有缓存buffer可用,那我们就真的需要进行分配了;
- 调用_IO_DOALLOCATE对fp进行buffer分配
- 根据分配buffer是否失败决定是直接返回错误EOF,还是重新只将_IO_LINE_BUF置为0(因为当前已经分配buffer成功了)
- 注意了,上面都是buf为空,需要重新分配的情况,如果buf不为空,那么我们会跳到
result = _IO_SETBUF (fp, buf, size) == NULL ? EOF : 0;
的执行中,进行buf设置;如果fp->_IO_buf_base不等于NULL,那我们实际上是默认使用这块buffer的,返回0,退出函数
case _IOFBF:
fp->_flags &= ~(_IO_LINE_BUF|_IO_UNBUFFERED);
if (buf == NULL)
{
if (fp->_IO_buf_base == NULL)
{
/* There is no flag to distinguish between "fully buffered
mode has been explicitly set" as opposed to "line
buffering has not been explicitly set". In both
cases, _IO_LINE_BUF is off. If this is a tty, and
_IO_filedoalloc later gets called, it cannot know if
it should set the _IO_LINE_BUF flag (because that is
the default), or not (because we have explicitly asked
for fully buffered mode). So we make sure a buffer
gets allocated now, and explicitly turn off line
buffering.
A possibly cleaner alternative would be to add an
extra flag, but then flags are a finite resource. */
if (_IO_DOALLOCATE (fp) < 0)
{
result = EOF;
goto unlock_return;
}
fp->_flags &= ~_IO_LINE_BUF;
}
result = 0;
goto unlock_return;
}
break;
4._IOLBF---line buffering的情况---_IO_DOALLOCATE
这个函数的核心作用就是为fp->_IO_buf_base分配一块合理大小的buffer用作缓存,我们来看看它的一些具体逻辑:
- 默认size大小是size = BUFSIZ (8192字节)
- 对fp指针状态进行设置,将_IO_LINE_BUF置位;
- 通过获取该IO 流的stat信息st,决定是否有必要采用其中st_blksize更新size(主要是考虑使用一个比8192更小的size,分配足够的就行,不一定要最大的size)
- 通过malloc分配对应大小的buffer,然后调用_IO_setb将fp->_IO_buf_base设置为刚才申请的地址
// glibc/libio/filedoalloc.c
/* Allocate a file buffer, or switch to unbuffered I/O. Streams for
TTY devices default to line buffered. */
int
_IO_file_doallocate (FILE *fp)
{
size_t size;
char *p;
struct __stat64_t64 st;
size = BUFSIZ;
if (fp->_fileno >= 0 && __builtin_expect (_IO_SYSSTAT (fp, &st), 0) >= 0)
{
if (S_ISCHR (st.st_mode))
{
/* Possibly a tty. */
if (
#ifdef DEV_TTY_P
DEV_TTY_P (&st) ||
#endif
local_isatty (fp->_fileno))
fp->_flags |= _IO_LINE_BUF;
}
#if defined _STATBUF_ST_BLKSIZE
if (st.st_blksize > 0 && st.st_blksize < BUFSIZ)
size = st.st_blksize;
#endif
}
p = malloc (size);
if (__glibc_unlikely (p == NULL))
return EOF;
_IO_setb (fp, p, p + size, 1);
return 1;
}
5._IOLBF---line buffering的情况
这种情况是按行使用buffer,主要做了以下操作:
- 设置tag,将_IO_UNBUFFERED置0,将_IO_LINE_BUF置位;
- 如果入参buf为空,那就直接返回0,结束函数;否则等待执行_IO_SETBUF (fp, buf, size)
思考:这里为什么不重新检查fp->_IO_buf_base然后分配内存呢?
从上一种情况中我们注意到,在分配buffer后我们都默认将_IO_LINE_BUF置位,即这是一种默认模式,所以我们无需检查fp->_IO_buf_base的状态
case _IOLBF:
fp->_flags &= ~_IO_UNBUFFERED;
fp->_flags |= _IO_LINE_BUF;
if (buf == NULL)
{
result = 0;
goto unlock_return;
}
break;
6._IONBF---no buffering的情况
这种情况的操作就更为简单了,禁用了buffer,我们将_IO_LINE_BUF置0,_IO_UNBUFFERED置位,然后将入参buf置为NULL,size置为0,等待调用_IO_SETBUF (fp, buf, size)
case _IONBF:
fp->_flags &= ~_IO_LINE_BUF;
fp->_flags |= _IO_UNBUFFERED;
buf = NULL;
size = 0;
break;
7.default情况
这种情况直接将result置为EOF(操作失败),退出函数
default:
result = EOF;
goto unlock_return;
8._IO_SETBUF调用
这里的调用其实与前文中setbuf函数的调用流程也就基本一致了,我们就不重复一遍了。
实际上做的操作就是使用给定的buffer去更新
- buffer基地址:_IO_buf_base,_IO_buf_end
- buffer写地址:_IO_write_base,_IO_write_ptr,_IO_write_end
- buffer读地址:_IO_read_base,_IO_read_ptr,_IO_read_end
result = _IO_SETBUF (fp, buf, size) == NULL ? EOF : 0;
FILE *
_IO_new_file_setbuf (FILE *fp, char *p, ssize_t len)
{
if (_IO_default_setbuf (fp, p, len) == NULL)
return NULL;
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end
= fp->_IO_buf_base;
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
return fp;
}
libc_hidden_ver (_IO_new_file_setbuf, _IO_file_setbuf)
总结
setvbuf函数针对_IOFBF,_IOLBF,_IONBF三种不同的模式,对缓存buffer进行不同的操作,若输入buf为空,则自动申请。这个函数能控制读写缓存buffer的更新机制,按buffer大小更新/按行更新/不使用buffer,使得程序员能都手动控制输入写出写回真实物理文件的频率。
标签:fp,文件,buffer,---,源码,IO,buf,setvbuf,size From: https://blog.51cto.com/u_15830688/5963260