IO之标准C库buffer

本文探讨了标准C库如何通过内部缓冲机制提高文件读写效率,并解释了fwrite与fread函数如何利用缓存来加速I/O操作。此外,还介绍了setvbuf、setbuf及fflush等函数的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在论述这个主题之前,先介绍一下标准C库和linux系统调用以及windows API之间的关系。

拿写文件来举个例子

linux下写文件用write()

windows下写文件用WriteFile()

这说明不同操作系统实现同样的系统功能的接口应该是不一样的。造成这种现状是操作系统发展的历史原因造成的,无法在操作系统的层面统一系统函数接口。同样功能的程序在linux上写一套,windows上又得写另外一套,毫无移植性可言。如果要开发一个既能在linux跑,又能在windows上跑的程序,开发成本飙升!

为了解决这个移植性的问题,标准C库利用了封装技术,扮演了一个重要的角色,统一了部分基本功能接口。

标准C规定的写文件的函数是fwrite(),就是不管在linux还是在windows上,各自都有一个标准C库,库函数封装的下层细节不一样,但是接口完全一样,提供的功能完全一样。

这是怎么做到的?猜一猜大致实现就知道了

在linux上,标准C接口fwrite()的实现伪代码

size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream){

   ...

   ...

   return write(stream->fd,buffer,count);

}

在windows上,标准C接口fwrite()的实现伪代码

size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream){

#define OUT 

   BOOL ret = false;

   OUT int optnum;

   ...

   ...

    ret = WriteFile(stream->filehandle, buffer, count, &optnum,...);

    if( ret == true)

       return optnum;

   else

       return -1;

}

 

内部实现不一致,没关系,接口一样就可以,不管在linux还是windows上,写文件都用fwrite(),分别在各自平台上编译就可以了。

标准C就是这样一个处于系统层面之上的应用层标准函数库,为了统一各个操作系统上的函数接口而生。

 

回到我们的主题----IO之应用层buffer

什么是应用层buffer?

回想一下我之前介绍的《IO之内核buffer"buffer cache"》,既然write()能把需要写文件的数据推送到一个内核buffer来偷工减料欺骗应用层(为了加速I/O),说“我已经写完文件并返回了”。那应用层的标准C库的fwrite()按道理也可以为了加速,在真正调用write()之前,把数据放到(FILE*)stream->buffer中,等到多次调用fwrite(),直至(FILE*)stream->buffer中积攒的数据量达到(FILE*)stream->bufferlen这么多的时候,一次性的把这些数据全部送入write()接口,写入内核,这是多么美妙啊。。。

实际上,标准C库就是这么做的!

把fwrite()的linux实现再细致一下

过程其实仍然很粗糙,为了突出buffer的重点,计算stream->buffer是否满,拷贝多少,填充多少这样的细节和主题无关的东西我略去了

size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream){

   ...

   if( stream->buffer满 ){

       write(stream->fd,stream->buffer,stream->bufferlen);

   } else{

       拷贝buffer内容至stream->buffer

   }

   ...

    return count;

   //过程很粗糙,为了突出buffer的重点,计算stream->buffer是否满,拷贝多少,填充多少这样的细节和主题无关的东西我略去了

}

fwrite()在windows平台的实现也基本上是这样的,也有buffer。

值得一说的是,fread()也有一个读cache来完成预读。

 

setvbuf()和setbuf()都是控制这个标准C库的buffer的。

还有fflush()是C库用于flush数据的函数。

以上三个函数,如果大家有兴趣,可以去看看linux上对应的man文档。

重点是要知道不仅系统的内核有buffer,应用层的C库同样也有buffer。这些buffer的唯一作用就是为了加速应用,不让应用老是卡在和磁盘交互上。

说个题外话,实际上对于磁盘、RAID卡、盘阵这样的外存介质而言,他们各自在硬件上也都有一层前端的buffer,有时也叫cache,用来缓冲读写加速。cache越多,价格越贵,性能越好。大型存储设备一般拥有多层cache,用的是昂贵的SSD。
需要分享的一点经验是,不管是标准C库的buffer也好,内核的"buffer cache"也罢,我们终究对它们的控制力度是有限的。我们在做服务器程序的时候,如果业务上涉及太大的I/O量,需要做服务整体加速的时候,我们一般自己在业务层做一层自己的"buffer",把业务数据buffer住,攒成以文件系统或者磁盘的block块单位的大块数据,然后集中写,然后集中写又有集中写的策略。。。
再引申一点内容,做高性能大流量的大站的架构,其中最重要几个架构角色之一就是cache。前端CDN、后端memcache、redis、mysql内部cache等等,都是cache的应用场景,可以说"buffer cache"在服务器领域从软件实现到硬件加速再到架构,真的是无处不在。

### 文件IO标准IO的详细介绍 #### 文件IO概述 文件IO涉及直接调用操作系统内核所提供的系统调用函数来执行文件操作,其主要通过文件描述符进行管理。在Linux环境中,所有打开的文件都关联有一个唯一的非负整数形式的文件描述符,在创建新文件连接时由系统自动分配[^2]。 对于文件读取、写入等具体动作,则需利用`open()`获取到相应文件的标识——即文件描述符之后再配合其他诸如`read()`, `write()`之类的底层API完成实际的数据交换过程。值得注意的是,在POSIX兼容平台下,默认情况下进程启动之初便已存在三个特殊的预定义文件描述符分别代表标准输入(通常是键盘)、标准输出以及标准错误流。 ```c #include <unistd.h> #include <fcntl.h> int main(){ int fd; char buffer[]="Hello File IO"; // 创建并打开一个名为example.txt的新文件用于只写模式 fd = open("example.txt", O_WRONLY | O_CREAT , S_IRUSR | S_IWUSR); if(fd != -1){ write(fd,buffer,sizeof(buffer)); close(fd); } } ``` #### 标准IO概述 相比之下,标准IO则提供了一层更高层次抽象封装后的接口集,它并不直接依赖特定OS实现细节而是基于C语言的标准构建而成。这意味着只要遵循ANSI C规范编写的应用程序无论在哪种支持此标准的操作系统上运行都能保持一致的行为特性[^3]。 除了简化编程模型外,标准IO还引入了缓冲机制以提高效率,并且默认初始化了三个全局对象:`stdin`(对应于终端输入),`stdout`(指向屏幕显示)还有`stderr`(专供异常情况报告)。当开发者想要处理外部资源比如磁盘上的文档时往往会选择借助`fopen()`,`fprintf()`,`fgets()`等一系列高级别的功能函数来进行交互。 ```c #include<stdio.h> int main(){ FILE *fp; const char* message ="Welcome to Standard IO"; fp=fopen("greeting.txt","w+"); if(fp!=NULL){ fprintf(fp,"%s\n",message ); fclose(fp); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值