首页 > 编程语言 >C++学习---cstdio的源码学习分析10-改变文件流文件流buffer函数setvbuf

C++学习---cstdio的源码学习分析10-改变文件流文件流buffer函数setvbuf

时间:2022-12-22 21:32:29浏览次数:36  
标签:fp 文件 buffer --- 源码 IO buf setvbuf size

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

相关文章

  • RCU-1——内核文档翻译——Data-Structures.rst
    翻译:kernel-5.10\Documentation\RCU\Design\Data-Structures\Data-Structures.rst=================================================TREE_RCU数据结构导览[LWN.net]=......
  • JavaScript-DOM基础【获取元素和对其属性/事件的操作】
     文档对象模型(DocumentObjectModel,简称DOM),是W3C组织推荐的处理可扩展标记语言(HTML或者XML)的标准编程接口。W3C已经定义了一系列的DOM接口,通过这些DOM接口可以......
  • vulnhub-LordOfTheRoot
    https://www.vulnhub.com/entry/lord-of-the-root-101,129/本机10.0.2.4靶机10.0.2.15靶机是桌面版ubuntu,提示了登录用户名smeagolnamp扫描,发现只有22端口开放。使......
  • 实验八-Web部署
    实验八-Web部署最开始,我用自己的云服务器进行实验步骤如图:  经过下载和解压,进行里面内容的编辑,copy上去即可。  之后在访问ip/wp-config.php时......
  • leetcode-回文数
    9.回文数给你一个整数x,如果x是一个回文整数,返回true;否则,返回false。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121是回文,而123不是。示例1......
  • Spring IOC源码(五):IOC容器之 beanFactory准备工作
    1、源码解析prepareBeanFactory(beanFactory)是beanFactory的准备工作,主要是对beanFactory的各种属性做填充。 1//beanFactory的准备工作,配置容器上下文,如容......
  • 解锁通达信金融终端Level-2功能
    外挂方式,不修改原程序。   ......
  • Django-中间件
    1.Django中间件及两个重要方法1.django有7个中间件,并且还支持用户自定义中间件。2.当我们完成一些全局相关的功能(例如用户访问频率)、权限(中间件会获取到用户的权限......
  • Django-cookie和session
    1.cookies与session简介1.最早期的互联网不需要保存用户信息,所有用户哪怕之前成功登陆每次用户登录也要重新输入账号和密码。2.cookie:保存在客户端与用户状态相关的数......
  • 渗透实录-02
    记录一次实战稍曲折拿下目标站点的过程。前期摸点IIS7.0+ASP.NET的组合,简单尝试发现前台登录页面可能存在SQL注入,数据包如下:为了节约时间直接祭出sqlmap,-rx.txt-v3......