linux入门---缓冲区

查看问题

首先大家来看看下面这段代码

1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 int main()
  5 {
  6     printf("i am printf\n");
  7     fprintf(stdout,"i am fprintf\n");
  8     fputs("i am fputs\n",stdout);
  9     const char arr[]="i am write\n";                                                                                                
 10     write(1,arr,strlen(arr));
 11     return 0;
 12 }

这段代码通过四种不同的函数向屏幕上打印内容代码的运行结果如下:
在这里插入图片描述

没有意外这段代码将四行数据全部输出到了屏幕上,但是这里可以使用输出重定向将原本输出到屏幕上的数据输出到文件里面,比如说下面的操作:
在这里插入图片描述
文件里面的内容也没有啥问题数据也只输出了一遍,那这里我们对代码做一下修改,在代码的后面添加一个fork函数:

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 #include<fcntl.h>
  5 #include<sys/types.h>
  6 #include<sys/stat.h>
  7 int main()
  8 {
  9     int fd=open("mytest1",O_WRONLY|O_CREAT|O_TRUNC,"w");
 11     printf("i am printf\n");
 12     fprintf(stdout,"i am fprintf\n");
 13     fputs("i am fputs\n",stdout);
 14     const char arr[]="i am write\n";
 15     write(1,arr,strlen(arr));
 16     fork();                                                                                                                         
 17     close(fd);
 18     return 0;
 19 }

运行一下上面的代码就会发现这里也没有出现问题,因为fork函数执行前其他的打印函数已经执行完了,所以所有的内容只打印了一遍:
在这里插入图片描述
但是将上述代码重定向输出到另外一个文件的时候我们发现这里出问题了:
在这里插入图片描述
为什么printf fprintf fputs函数会多打印一份而write函数却只打印一遍呢?原因是和缓冲区有关,首先缓冲区本质上就是一段内存,那既然是内存的话就会存在两个问题1.这个内存是谁申请的? 2.这个内存有什么意义?我们先来讨论第二个问题。

缓冲区的意义

这里通过一个小故事来带着大家理解一下缓冲区的作用。张三和李四是一对关系非常好的朋友,但是两个人因为一些原因相距非常的远,张三住在西藏而李四住在上海,

在这里插入图片描述

李四的家境十分的阔绰所以每次都想送给张三一些好吃的好玩的比如说送给张三一个4090的显卡一个华硕的显示器等等等,突然有一天李四放暑假了准备专门用暑假的时间来把这些东西送给张三,李四打电话给张三说:张三啊!你现在是在西藏吧,我现在放暑假了这样好吧我准备了一些小礼物给你,我准备花一整个暑假的时间骑电动车亲自给你送过去,我到你家楼下了给你发个消息你记得给我开门哈我送完东西就走不打扰你正常生化的。张三一听傻眼了西藏和上海相距十万八千里,你要亲自给我送过来这得花多少时间啊而且李四平时的工作还非常的繁忙送来东西又不在西藏玩个几天送完就走,所以张三立马跟李四说:李四啊你这么繁忙你为什么要花这么多时间亲自给我送东西,你亲自送就算了你送来了也不在西藏多玩几天送完就走这多浪费你的时间和精力啊对吧,你直接把东西放到楼下的快递站,让快递小哥帮你送不就可以了吗?把节省下来的时间 我们拿去打打游戏写写代码不更好些吗?李四一想好像非常的有道理于是就将东西放到了楼下的快递站,

在这里插入图片描述

看到这里大家应该能够知道快递在社会中存在的意义,他可以节省发送者的时间,而上述故事中的上海就是电脑中的内存,西藏就是电脑的硬盘,李四就相当于内存中的进程,张三就相当于硬盘中的文件,而李四送给张三的礼物就相当于内存中的进程要向文件中写入的数据,把快递交给快递小哥就相当于把进程的数据拷贝到缓冲区里面,而这里的拷贝用到的就是fwrite函数,与其理解fwrite函数理解成写入到文件的函数,倒不如理解fwrite函数是拷贝函数,将数据从进程拷贝到缓冲区或者外设当中,帮李四送东西的快递小哥就相当于内存中的缓冲区,快递的意义是帮助发送者节约时间,那缓冲区中的意义就是提高进程进行数据IO的时间。
在这里插入图片描述

缓冲区刷新策略

这里继续通过上面的例子带着大家了解缓冲区的刷新策略,李四将东西交给了外卖小哥可是第二天李四在手机上查快递的情况是发现快递还没有发出就感到十分的生气,他立马打电话给快递员说:为什么快递还没有发出去呢?你们在干什么啊?这时快递员非常的恼火怼了一句,你以为你是谁啊?你刚给我我就得把快递给你送出去,你等着吧等我快递能够堆满一车我就给你送出去,那这里的堆满一车就是操作系统一个刷新缓冲区的策略,当缓冲区的空间装满数据之后操作系统就会刷新缓冲区将里面的数据发到指定位置,在外设当中磁盘就是采用这样的刷新策略。在快递当中也不是非得装满一车才能发货,比如说有一些vip客户的重要快递,这些人寄快递花了很多的钱所以他们的快递会发的比较快一些,因为有个大大的vip标志,那么对于这种特殊的标志操作系统也有一个对应的缓冲区刷新策略:行缓冲当缓冲区中遇到了\n字符时就会立马刷新缓冲区将数据传送到指定位置,电脑的显示屏采用的就是这样的刷新策略,这也是为什么之前打印数据的时候不加\n就显示不出来打印的内容,操作系统中还有一个刷新策略就是立即缓冲,只要我们往缓冲区里面输入了数据就会立刻刷新缓冲区,这就相当于我们亲自把物品送给一个人不需要等待快递,因为使用电脑的过程肯定存在要写入一些十分重要的信息的情况,而数据是存储在内存上的,内存的特点就是掉电易失,所以为了减少数据写入时的意外就有了这样的刷新策略。

缓冲区在哪

根据上面的代码的运行情况我们首先能够确定俩件事第一:之所以会出现这样的情况肯定和缓冲区有关,并且还和写时拷贝有关,第二:缓冲区一定不在内核当中因为printf,fprintf,fputs函数,这三个函数都属于c语言提供的函数,而write函数是操作系统提供的函数,如果缓冲区在内核当中的话write函数也会打印两次内容,所以我们之前谈论的所有缓冲区,都指的是用户级语言层面给我们提供的缓冲区,我们知道stdout,stdin,stderr以及我们用c语言打开文件都得创建一个FILE指针来指向新打开的文件,FILE是一个结构体而缓冲区就在FILE结构体里面,也就是FILE结构体里面不仅含有一个字段记录文件在操作系统中的fd还有一块空间作为缓冲区,所以我们平时使用fflush函数刷新缓冲区,使用fclose函数关闭文件时为什么要传一个FILE指针,原因就是刷新FILE结构体里面的缓冲区。

上述问题的解释

我们再来看看这段代码:

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 #include<fcntl.h>
  5 #include<sys/types.h>
  6 #include<sys/stat.h>
  7 int main()
  8 {
  9     int fd=open("mytest1",O_WRONLY|O_CREAT|O_TRUNC,"w");
 11     printf("i am printf\n");
 12     fprintf(stdout,"i am fprintf\n");
 13     fputs("i am fputs\n",stdout);
 14     const char arr[]="i am write\n";
 15     write(1,arr,strlen(arr));
 16     fork();                                                                                                                         
 17     close(fd);
 18     return 0;
 19 }

在代码结束之前会创建子进程。如果没有进行>看到的四条信息,因为stdout默认使用的是行刷新,在进程fork之前三条输出函数已经将数据进行打印输出到显示器上(外设),FILE内部也就是进程内部不存在要打印的数据,如果我们进行了输出重定向写入文件就不再是显示器而是普通文件,文件采用的刷新策略是全缓冲,在fork函数之前printf,fprintf,fputs函数想要输出一些数据,但是这些数据不足以让stdout缓冲区写满,所以数据并没有被刷新输出到屏幕上面,执行完fork函数父进程就要创建子进程紧接着就是父进程和子进程都要退出,谁先退出不知道,但是退出的时候一定会进行缓冲区刷新,而缓冲区是在FILE结构体上,FILE结构体又是属于子进程和父进程的,所以一旦退出就会发生写时拷贝数据会变成两份,所以文件里面就会出现两份printf,fprintf,fputs函数打印的内容,而write函数为什么没有显示两份的原因是:上面的过程都和write函数无关,write函数没有FILE,他是一个系统调用函数通过fd来向文件里面打印的内容,就没有c语言提供的缓冲区,在fork函数执行之前就已经将内容输出到屏幕上了不会发生写时拷贝,那这就是对代码的解释希望大家能够理解。

缓冲区和操作系统的关系

通过上面的介绍,当使用c语言的文件函数往硬盘上输出数据时这些数据会先存储到FILE结构体里面:
在这里插入图片描述
我们知道c语言文件函数是基于操作系统提供的接口实现的,也就是说这里想将数据输出到磁盘上就得使用操作系统提供的write函数,但是这里的数据也不是一下子就通过write函数刷新到磁盘上,而是先将数据拷贝到内核结构体file里面的缓冲区中
在这里插入图片描述
等数据来到内核的缓冲区中就会由操作系统决定什么时候将数据刷新到磁盘中,这里的刷新规则就十分的复杂并不是我们之前说的全缓冲和行缓冲无缓冲那么,这些刷新策略是c语言提供的,而操作系统刷新缓冲区得权衡一下利弊再做出对应的决定,比如说隔一段时间将内核缓冲区的数据刷新到磁盘上,当内存不够时时将数据刷新指定位置上等等,所以大家在写代码刷新缓冲区时自认为调用fflush函数将缓冲区清空了,实际上这个清空的是语言层面上的缓冲区而不是内核中的缓冲区,这一点大家要知道。
在这里插入图片描述
以上就是本篇文章的内容希望大家能够理解。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶超凡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值