【Linux】用户级缓冲区

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


一、缓冲区现象

以下代码使用了C语言库函数如printffprintffwrite以及系统调用接口write,它们都是向显示器文件打印相应的语句,最后在程序结束之前将显示器文件关闭。

请添加图片描述

请添加图片描述

由上图我们发现:而我们非常好奇的是,首先能保证先是调用打印语句,最后再关闭显示器文件,那么为什么调用C语言的函数没有在显示器上显示结果(重定向也一样)?而程序却打印出系统调用接口write对应的语句!

而且非常奇怪的是:以上printf等C语言库函数不是封装了系统调用write吗?按道理write打印出结果,那么C语言函数也会打印出结果呀?

因此,我们能肯定的是:数据存储在缓冲区中,只是没有被刷新出来!并且这个缓冲区一定不在操作系统内部(不是系统级缓冲区),如果在的话,printf底层调用write就把结果显示出来了。因此,这个缓冲区是用户级缓冲区

请添加图片描述

  • 当调用系统调用接口 write 时,实际上是向操作系统发出了一个写入文件的请求,而操作系统会负责将数据直接写入到文件中。此过程仍然会经过操作系统内核的缓冲区,这部分由操作系统帮我们做的。

  • printf 等接口不会直接将内容直接写入文件中,而是将数据写入到用户空间的缓冲区,然后通过一定条件,再将缓冲区的内容写入到文件中。主要是提高IO效率。

因此我们就可以解释为什么C语言接口打印不出结果的原因了:首先调用printf等接口是将数据写到用户级缓冲区了,而缓冲区正在等待条件将内容通过write写入到文件中,可是半路上遇到了close(1),即显示器文件关闭,而数据还在缓冲区没有被刷新出来,自然而然就无法在显示器上显示。

二、缓冲区的刷新策略

  • 无缓冲:直接刷新。即直接调用write接口将缓冲区的内容写在文件缓冲区(操作系统)中,如直接调用fflush函数刷新缓冲区。
  • 行缓冲:缓冲区不刷新,直到遇到'\n'才刷新缓冲区。默认向显示器文件打印的刷新策略就是行刷新。
  • 全缓冲:缓冲区不刷新,直到缓冲区满了才刷新缓冲区。向文件写入的刷新策略就是全缓冲。
  • 进程退出会强制刷新缓冲区。(特殊情况)

因此,刷新本质就是缓冲区满足某个条件后,可以通过调用write函数写入到文件中

三、为什么要有缓冲区

如果用户想要向一个文件读写数据,由于文件存储在磁盘中,那么要访问文件必定要访问磁盘。然而根据存储金字塔原理,磁盘访问的速度非常慢!若每次向文件读写一次内容,就要和磁盘进行一次IO,效率就十分低下。

因此就有了缓冲区的概念:缓冲区其实就是一段内存空间,用于存储临时数据。需要注意的是,用户级缓冲区通常是通过动态内存分配函数(如 mallocnew)在堆区分配的,大小是不固定的。

当向文件写入时,会将数组先存储到缓冲区中,当达到一定条件,才会写入到文件中。这样就可以减少对磁盘的访问,大大提高了读写效率。

感性理解:如果这个世界上没有快递公司,那么你寄东西就要亲自寄,浪费的是你的时间;而如果有快递公司,你只需要把快递交给快递公司即可,让快递公司帮你寄,转而你可以做你自己的事情。因此,你就相当于进程,而快递就相当于缓冲区。因此,缓冲区的出现大大提高了用户效率。而快递公司并不是拿到你的快递就马上派送,而是等待一定的条件(如用户加急、仓库满了等情况)才寄出。因此,缓冲区的出现可以集中处理数据,减少IO的次数

四、缓冲区在哪

以C语言为例(每个编程语言都一样),我们知道C语言中文件操作通常都是通过 FILE 结构体来进行的,因此,FILE 结构体一定包含了有关文件的信息,包括文件描述符、缓冲区等。

我们可以打开/usr/include/libio.h来看看源码

请添加图片描述

五、缓冲区 + fork

以下有一个非常有趣的代码

请添加图片描述

程序直接运行的话是这样的

请添加图片描述

但如果对进程实现输出重定向呢? 我们发现结果变成了:

请添加图片描述

我们发现:C语言调用的函数均输出了2次,而系统调用write只输出了一次。为什么呢?这肯定和fork有关!

  • 当程序运行时,是直接向显示器文件写入,而写入显示器文件的刷新策略是行缓冲,即遇到\n就会刷新。

  • 当重定向到普通文件时,C库函数写入文件由行缓冲变为全缓冲,因此虽然有\n,但全缓冲的特点是缓冲区满了才会刷新(无视\n)。

  • fork()系统调用会创建一个子进程,该子进程是父进程的副本,所以在fork()之后,子进程会与父进程共享相同的内存空间。这意味着fork()调用后,子进程会继承父进程的用户级缓冲区。当子进程退出时,强制刷新用户层缓冲区[子进程],此时会触发写时拷贝机制;当父进程结束后,强制刷新用户层缓冲区[父进程],因此,C语言调用的函数均输出了2次。而write只打印了一份,也证明了它不经过用户级缓冲区。

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值