前面介绍了文件描述符的相关知识,下面我们将介绍缓冲区的相关知识。
本质上来说,缓冲区就是一块内存区域,因为内核上的缓冲区较复杂,所以本文主要介绍C语言的缓冲区。
目录
1.为什么要有缓冲区
2.应用层缓冲区的默认刷新策略
3.如何证明缓冲区的存在?
1.为什么要有缓冲区
缓冲区的存在就是为了提高使用者的效率,我们可以以物流公司为例,如果每次物流公司送快递都一件一件送,那么效率就会变得非常底下,所以在日常生活中物流公司都是等到货物积攒到一定数量时再进行发送。同理,缓冲区的存在就相当于物流公司,缓冲区能够聚集数据,在需要时一次拷贝,提高整体的效率。拷贝的一次就叫刷新。
在正式介绍缓冲区前,我们需要知道的是,我们常说的缓冲区和内核中的缓冲区没有关系,我们常说的缓冲区是语言层面的缓冲区,我们熟知的C语言就自带缓冲区。因为调用系统调用是有成本的,所以我们在语言层面上创建了缓冲区,这样就不会造成我们输入一次就调用相关的系统调用向内核写入一次,而是等到数据量足够多时,再调用系统调用接口,向内核中写入。 这样就避免了频繁调用系统调用,减少了系统开销。
2.应用层缓冲区的默认刷新策略
<1>无刷新,无缓冲
<2>行刷新,遇到\n时就刷新
<3>全缓冲,全部刷新 -- 普通文件里面,缓冲区写满才刷新。
当然,我们也可以通过强制刷新函数fflush,直接刷新缓冲区。在进程退出时,缓冲区也会刷新。
缓冲区被谁维护?
前面我们了解到FILE其实是一个结构体,里面不仅仅封装了fd,还封装了其他的内容,其中就包括了文件缓冲区,并且每一个文件都包含一个缓冲区。所以我们在实施写入文件操作函数时,都要传递一个FILE*的指针,其实我们写入内容时,就是将我们所写的内容拷贝到FILE* 的指针里面维护的缓冲区,
3.如何证明缓冲区的存在?
下面为了验证缓冲区的存在,我们先通过一段代码来验证。
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#define filename "file.txt"
int main()
{
//使用系统调用
const char* str = "hello world\n";
write(1,str,strlen(str));
//使用C语言的相关接口
const char* str1 = "hello fwrite\n";
fwrite(str1,strlen(str1),1,stdout);
const char* str2 = "hello fprintf\n";
fprintf(stdout,"%s",str2);
fork();
return 0;
}
正常执行结果
将执行结果重定向到文件里面的结果
我们可以发现向显示器打印,只会打印三条语句,这是因为刷新策略是行刷新,而这也符合我们的认知。而我们实行重定向时,对log.txt这个普通文件的刷新策略就变成了全缓冲,此时C语言的缓冲区就存放了两条语句“hello fwrite\n” 和 “hello fprintf\n”。因为这里我们使用了fork创建了子进程,所以这里就存在了两个进程。而前面我们提到过,当进程退出时,缓冲区会被强制地刷新。这里无论哪个进程先退出,缓冲区都会被刷新两次,将缓冲区保存地数据拷贝到操作系统。正常来说缓冲区的刷新会导致缓冲区内容清空,但是因为写时拷贝的存在,所以才会造成刷新两次相同的数据。而系统调用打印的数据只有一次的原因是系统调用写入数据是直接写入操作系统,一旦数据写入操作系统,数据就和进程无关了。
通过对上述内容的了解,我们可以回顾一下printf和scanf函数,scanf又叫格式化输入,printf叫格式化输出。键盘和显示器都是字符设备,其实我们无论是从显示器还是键盘上输入时,都是以字符的方式进行的。但是我们可以通过这两个函数从缓冲区读取或输出数据,并格式化成对应的数据类型。
以上就是全部内容,文章如有不对之处,还望各位大佬指正,谢谢!!!