缓冲区是什么呢?
我们理解的缓冲区:其实就是一部分内存。(有谁提供呢?)
为什么要有缓冲区呢?
下面我们讲一个小故事将缓冲区引入进来。
故事时刻:假设你在云南,你有一个非常好的朋友在北京,然后呢你朋友马上准备过生日了,你准备送他一个你新买的键盘,有一种方式是你自己坐火车坐飞机过了挺长一段时间才到北京去,然后走到你朋友楼下说:"我给你送的新键盘作为生日礼物。"? 然后你还需要大量时间折返回学校,这种方式可不可行呢?当然可行,但是效率太低下了。假如你楼下如果有一个菜鸟驿站,你朋友楼下也有一个菜鸟驿站,那么你就可以通过直接把键盘交给菜鸟驿站,然后直接上楼,这回你室友问你,你键盘送给你朋友了么? 你会说:"送了",所以这种方式就提高了你的效率,而缓冲区的存在就是为了提高效率----提高使用者的效率,那么菜鸟驿站会不会直接一收到你一个人的键盘就马上给你安排发车直接寄过去到你朋友那里呢?答案是不会,如果只给你一个人送键盘就去跑一趟这么远的路程那相比于等一段时间积累一些其他人的快递来说前者成本会更高,而后者降低了成本,所以说因为有缓冲区的存在,我们可以积累一部分在统一发送,这提高了发送的效率,所以总结下来就是缓冲区可以提高效率。
缓冲区因为能够暂存数据,必定有一定的刷新方式:
1.无缓冲(立即刷新)
2.行缓存(行刷新)
3.全缓冲(缓冲区满了,再刷新)
一般策略,特殊情况:
1.强制刷新
2.进程退出的时候,一般都要进行刷新缓冲区
一般对于显示器文件,行刷新(行缓冲)
对于磁盘上的文件,全缓冲(缓冲写满,再刷新)
我们用以下代码来进行测试:
运行之后:
目前是正常打印的,下面我们把fork()的注释去了:
重新运行之后:
我们发现系统调用接口只在log.txt上打印了一次,而C中的函数都打印了两次。
下面我们来理解一下这个样例:
1.当我们直接向显示器打印的时候,显示器文件的刷新方式是行刷新!而且我写的代码输出的所有字符串都有\n,fork之前,数据全部已经被刷新,包括系统调用 system call
2.重定向到log.txt,本质是向磁盘文件中写入(不再是显示了),我们系统对于数据的刷新方式已经由行刷新变成了全缓冲!
3.全缓冲意味着缓冲区变大,实际写入的简单数据,不足以把缓冲区写满,fork执行的时候,数据依旧在缓冲区中!
4.我们目前所谈的"缓冲区"和操作系统是没有关系的,因为我们所用到的c中的printf,fprintf,fputs底层都是封装了write()的,如果与操作系统有关的话就不会出现系统调用只调用了一次,而C函数都调用了两次。如果有关系的话,系统调用也会调用两次,所以我们目前谈及的“缓冲区”其实只能和C语言本身有关!
5.C/C++提供的缓冲区里面一定保存的是用户的数据,属于当前进程在运行时自己的数据,但是如果我们把数据交给了操作系统,那么这个数据就属于操作系统了,不再属于我们用户了。
6.当我们的进程退出的时候一般要进行刷新缓冲区 ,即便你的数据没有满足刷新条件,而这个刷新缓冲区算"写入"操作,fork立马退出的时候,刷新缓冲区,就要发生写时拷贝,所以我们的上述样例的数据出现了两份,系统调用只有一份也就说明了write系统调用没有使用C的缓冲区,write系统调用刷新缓冲区是直接写入到操作系统中,不再属于进程了!
我们上面一直在说刷新缓冲区,那么到底什么叫做刷新呢?
我们日常用的最多的其实是C/C++提供的语言级别的缓冲区(即用户缓冲区),而我们的操作系统则有我们的内核缓冲区,我们上面提到缓冲区的存在就是为了提高效率,而从C缓冲区写入操作系统这个工作就叫做刷新。
这个缓冲区是在哪里呢?
这些接口里面基本上都要用到FILE,也就是说任何情况下,我们输入输出的时候都要有一个FILE,FILE是一个结构体,FILE里面包含了fd.? ?FILE提供了一段缓冲区。
下面我们想一想,当我们在键盘上按123的时候,我们按的是什么呢?也就是说当我们按123的时候按的是'1','2','3'还是123呢?在硬件上哪有存整数这样的概念,键盘显示器上你按的全是字符,所以当我们在键盘上按123的时候输入的是'1','2','3'。所以这个'1','2','3'就被放在了stdin的缓冲区里,也就是这个FILE结构体对应的缓冲区,然后你用的scanf通过%d,%c,%s这是在干什么呢?这其实是在做格式化输入,所以实际上scanf通过%d来取值是通过把'1','2','3'进行合并成一个字符串,然后把字符串转化成整数写到你取地址的那个变量里,那么最终你就拿到一个整数值,所以我们喜欢把键盘显示器这样的设备叫做字符设备。
上述其实都是在谈理论,所以我们下面进行实际操作以下来对C标准库进行一下模拟实现。
首先咱们先创建三个文件:
然后分别在三个文件中编写如下代码:
mystdio.h
mystdio.c
main.c
我们来编译一下:
运行:
监视窗口情况:
一开始是2条内容,随着不断的写入,后续内容越来越多,因为我们用的是行刷新。
如果我们把代码中选中的这一行的/n去了之后:
重新编译运行:
我们发现刚开始我们的log.txt变成什么都没有了,因为刚开始的时候没有刷新,等到进程退出了之后内容被一次性刷新出来了。