缓冲区是一段内存空间。在计算机输入输出变量的时候,并不是一下子全部输出的,一般是先存储在缓冲区中,等遇到了合适的刷新策略再刷新。就好像店小二上菜,他可以一次只上一盘,也可以一次性等多个菜做好了再上。
从刚才的例子其实也可以清楚,第一点,缓冲区可以提高计算机对用户的响应速度,给用户一个良好的体验。想象一下,根据冯诺伊曼体系结构,计算机的IO是非常慢的。一次刷新数据其实代表的是一次写的过程,如果没有缓冲区,每次一接收到对应的数据就迫不及待地写出,那么肯定就大大降低了整机的效率
需要注意的是,影响计算机的效率的主要矛盾此时是IO的次数,而非数据的总量,也就是说,更少次的IO和更多次的IO可能刷新的数据量都是这么多。
一般缓冲区的刷新策略有这么几种:
一般输出到显示屏上的策略一般是行刷新
假设缓冲区buff中由1024个字节,虽然可能并没有将这个缓冲区全部写满,但是遇到了\n,缓冲区就会刷新了。
人阅读文字是一行一行读取的,这样子比较符合人的阅读习惯
并且在人读取的时候再刷新下一行的数据,其实数据刷新的速度是比人阅读的速度要快很多的,这样的话既可以满足刷新的一个效率,也可以兼顾人的阅读习惯
一般对于磁盘文件采取的策略是满刷新。
写满了对应的磁盘中的缓冲区的buff才会被刷新出来,此时就算是遇到了\n也不会刷新
计算机中比较倾向于满刷新,行刷新主要是为了给人提供一个良好的阅读体验的。但是磁盘这些文件就不用考虑这一点了,更明显的是采取满刷新效率更高。因为他需要更少次的IO就可以刷新相同的数据了
其他情况:
除了这些常规情况之外,程序退出或者执行完毕的话也会马上刷新。因为这个进程结束了之后可能就不会被访问了,那么就需要赶紧把这个东西刷新出来
fflush等接口可以实现强制刷新,就算没有碰到\n或者缓冲区没有写满也会强制刷新出来
这一般是针对特殊情况的,是用户自定义的一种刷新策略。可能有时候应用中会有需求


fork之后C语言接口刷新了两次,但是内核级别的接口只刷新一次?
我们在test.c中调用了一些C语言的接口也调用了一个系统调用接口,之后编译链接运行,发现如果是向显示屏上打印的话,那么每个接口都只输出一次
但是如果把结果重定向到了文件中,再观察的话,会被打印两次,相当于C语言层面的接口被打印了两次,系统调用的接口仍然只被打印一次。
为什么会这样呢?
如果没有fork的话,这个结果不管是向文件中还是向显示屏上打印,都是一样的。我们可以推测,这个结果是由fork引起的
fork创建了子进程。代码执行到fork的时候,上述的代码已经执行完毕了,但是还没有刷新。(满足刷新条件才会刷新)
我们也可以推测,这个和刷新策略有一定的关系
在了解这个现象之前,我们先了解一下OS是如何刷新的?
假设上层是C语言标准库中的内容,下层是操作系统内核级别的内容。我调用printf,fprintf等接口其实首先是向C语言标准库中的缓冲区输入内容,之后满足刷新条件的时候,这些数据才会被刷新到内核中,write一次。
调用系统调用接口write的话,相当于跳过了上述这样的一个过程,直接向内核中写入(虽然内核中也有对应的缓冲区的概念,内核级别的缓冲区,但是目前咱先别讨论这个问题)

所以说,在没有刷新之前,调用printf,fprintf,fputs的接口,对应的数据都先在C语言标准库的缓冲区中存储着
另外一点,我们发现fork之后,对于C语言标准库层面的这些函数,打印了两次,相当于发生了一次拷贝!我们之前学习fork的时候,父子进程为了保持各自的独立性,有一个现象叫做:写时拷贝,那么我们是不是可以推测,这里很有可能发生了写时拷贝!
综合上述两点,我们可以大致了解为什么向显示屏上打印和向log.txt这个文件中打印会出现这样两种不同的情况了
对于向显示屏上打印,采取的策略是行刷新,碰到\n自动刷新了,那么执行到fork的时候,C语言标准库中就没有对应的内容了,拷贝了个寂寞啊,拷贝了相当于没拷贝,数据都没了拷贝啥?
所以屏幕上自然而然只有四行数据
对于向普通文件中写入,采取的策略是满刷新,也就是说只有缓冲区都被写满了之后才会刷新,那么即使此时有\n,也不会刷新,也就是说执行到fork的时候,由于刷新策略是满刷新,因此C语言标准库的缓冲区中仍然是有数据的!
接下来,我们需要向内核中刷新,将缓冲区的数据write到内核中,这是一次写的过程。对应的,父子进程为了保持各自的独立性,会发生写时拷贝。(刷新其实也是一次写入,因为如果刷新的话,对应的缓冲区的数据就没了,父子进程为了保持各自的独立性会采取写时拷贝的策略)同样的数据就被拷贝了两次,但是如果只调用write的话,直接被写入到内核中去了,这一部分数据是不归父进程管的,因此写时拷贝这个现象也和他无关。
也就是说,此时再C语言标准库的缓冲区中存在着两份C语言接口对应的数据
当我们return的时候,会发生刷新,因为进程退出了,父子进程各自刷新各自的数据,将两份C语言标准库中的数据和系统调用的write都刷新出来,文件中就有这七行数据了
另外,如果我们直接在fork之前ffush了,那么缓冲区中就没有内容了,不管是向显示屏还是向文件中写入,刷新的结果都是一样的
但是我们再想一下,为什么ffush(stdout)就可以刷新对应的缓冲区了呢?
stdout是fd文件操作符,我们调用fopen函数的时候会返回一个FILE*类型的指针,指向struct FILE ,这个结构体中除了封装有fd之外,其实还封装了对应的缓冲区。
以c++中的cout<<为例子,cout其实是一个类,<<是运算符重载,里面实现的逻辑肯定也和缓冲区有关的。
里面存在着一个buff缓冲区,当我们遇到了对应的刷新条件之后就会将buff的内容刷新出来
由此我们可以初步了解缓冲区的刷新策略,C语言层面的缓冲区,用户级别的缓冲区,知道内核级别缓冲区的存在,以及由此引起的一些刷新相关的现象了。