理解缓冲区

这篇具有很好参考价值的文章主要介绍了理解缓冲区。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一.缓冲区

int main()
{
	printf("hello linux");
	sleep(2);
	return 0;
}

对于这样的代码,首先可以肯定的是printf语句先于sleep执行,既然如此那么就应该是先打印语句然后进行休眠,下面看看结果:

理解缓冲区

但这里却是先休眠以后再打印语句,这是因为存在一个叫缓冲区的东西,当我们要向外设写入数据(让显示器显示就是向显示器写入数据)时会将数据暂存至缓冲区,然后在根据缓冲区的刷新策略刷新。

先休眠再显示数据是因为我们并不是直接向外设写入数据,而休眠以后还能刷出数据是因为有缓冲区暂存数据。下面就来谈谈缓冲区。

1.什么是缓冲区

缓冲区的本质就是一块内存(物理内存)

2.缓冲区的意义

我是一个奇思妙想的手艺人,我有一个好朋友叫泰裤辣。每当我打造出一个东西的时候我都会骑着自行车跨越一百多公里去送给他。后来有一天,快递行业兴起了,我有新发明就不用再自己骑着自行车跨越山和大海去给他送了,我只要将我的东西交给快递点,就可以继续回家搞发明,东西有快递公司去给我送,这样就节省了我大量的时间。

那么我就是进程,我的好朋友泰裤辣就是文件,而我的新发明就是数据,缓冲区就是快递点。所以说缓冲区最大意义就在于节省发送者的时间,也就是节省进程的时间。因为外设是一个很慢的东西,当我们访问外设的时候大部分时间都是在等外设准备好,真正写入的时间占比很少。如果有缓冲区的存在,那么进程只要将数据交给缓冲区以后就可以返回去执行后续的代码,缓冲区帮进程承担了等外设准备好的时间代价。

3.缓冲区的刷新策略

但我去寄快递,往往都是我将东西交给快递点一段时间后我的东西才被快递点发出,因为如果一有人寄东西快递点就派车去送这样效率太低百分百亏钱。但是如果是在淡季,等了很长也没有多少人寄快递,快递点也不会说将你的东西留在他那里好几个月。而且如果你是寄一辆轿车大小或者等级的东西,快递点也是会根据你这个情况单次的将你的快递发出。所以虽然快递公司正常情况下是等货物累计要一定数量才发送,但是也会有特殊情况。

同理,缓冲区刷新也是一样,虽然效率最高的是缓冲区满了以后再一次将整个缓冲区中的数据刷新出去(又称全缓冲),但是这个刷新方式只在将数据刷新到磁盘文件上的时候才使用。

向显示器写入数据时,缓冲区采用的方式是行刷新(行缓冲)。这是因为显示器是给用户看的,而我们人的阅读习惯是按行从左到右读取,计算机本质就是给人使用的工具,所以在给显示器刷新的时候采用行刷新。

除了全缓冲和行缓冲以外,还有一种很少见的刷新方式叫无缓冲,也就是说一有数据写入就立马刷新出去。比如printf立马fflush

此外还有两种特殊的刷新方式:

1.用户强制刷新

2.进程退出;进程在退出之前为了防止缓冲区还有数据没被刷新出去导致数据丢失会再刷新一次缓冲区


4.我们目前谈论的缓冲区在哪里

  #include<stdio.h>    
  #include<unistd.h>    
  #include<string.h>    
      
  int main()    
  {    
      
    //先写一批C语言函数接口    
    printf("hello printf\n");    
    fprintf(stdout,"hello fprintf\n");    
    fputs("hello fputs\n",stdout);    
      
    //再写一个系统调用    
    const char*s="hello write\n";    
    write(1,s,strlen(s));    
  	fork();                                                                                                                                                                                               
    return 0;    
  }    
 

上面的代码在直接将结果显示到屏幕中和将结果重定向到文件中是两种不同的结果:
理解缓冲区

根据上图可以看到,当我们直接将结果输出到屏幕上,一共打印了四条语句这很符合我们的推测。但是一旦将这个输出结果重定向到文件中,就变成了打印七条语句,其中C语言的函数接口被打印了两次。首先这个现象的原因和缓冲区有关,其次和fork有关。

上述现象可以说明我们目前为止在谈论的缓冲区不在内核中,否则系统调用write也要被打印两次,那么它就只能在用户层。要访问一个文件首先要有这个文件的fd,所以C语言所用的FILE结构体中一定要包含fd,那么今天可以知道FILE结构体中肯定也是有缓冲区的,否则为什么我们调用fflush函数都是传FILE*呢?上面谈论的各种刷新策略也针对的是FILE结构体中的缓冲区。

上述情况的解释:

1.因为显示器是给用户看的外设,所以必须要符合用户按行从左到右的阅读习惯,也就是说向显示器文件中写入时采用的是行刷新,一旦遇到\n就果断刷新,而向文件中刷新数据为了效率采用的是全缓冲,虽然四条输出语句都带了\n,但是仍然不足以将缓冲区写满。

2.fork创建的子进程是对父进程的一种拷贝,它们共享代码和数据(包括FILE中的缓冲区),fork之后马上就退出了,进程一旦退出为了防止进程丢失会刷新一次缓冲区,而刷新缓冲区就是将缓冲区清空,这本质上是一种修改,因为进程具有独立性,为了不然子进程的行为影响父进程就会发生写时拷贝,即子进程复制父进程缓冲区的数据并将其刷新到文件中,随后父进程退出再将数据刷新到文件中。

3.系统调用用的是fd,没有FILE结构体,也就没有FILE所提供的缓冲区。


5.仿写FILE

纸上得来终觉浅,绝知此事要躬行。接下来我们就自己通过使用系统调用接口,来尝试封装一下FILE结构体:

5.1myStdio.h
#pragma once                                                                                        #include<unistd.h>                                                                                  #include<assert.h>                                                                                 #include<sys/types.h>                                                                               #include<sys/stat.h>                                                                                   #include<fcntl.h>                                                                                   
#include<assert.h>                                                                                  #include<stdlib.h>                                                                                    
#include<string.h>                                                                                    #include<stdio.h>
//FILE中有缓冲区,刷新方式,以及fd                                                                           
//定义缓冲区大小                                                                                      
#define SIZE 1024                                                                                      //定义刷新方式                                                                                          
#define SYNC_NOW 1                                                                                 #define SYNC_LINE 2                                                                                   #define SYNC_ALL 3                                                                                      typedef struct FILE_       
{                                                                                           
    int flag;//刷新方式                                                                                       int feilno;//fd                                                                                         int cap;//记录缓冲区容量                                                                                   int size;//记录缓冲区使用
  char buff[];//缓冲区                                                                                  }FILE_;                                                                                        
//实现四个函数:fopen,fflush,fwrite,fclose        
FILE_*fopen_(const char*path,const char*mode);                                                           void fflush_(FILE_*fp);                                                                               void fwrite_(const char*ptr,size_t num,FILE_*fp);                                                       void fclose_(FILE_*fp);                                                                          
5.2myStdio.c
#include"myStdio.h"
//实现四个函数


FILE_ *fopen_(const char*path,const char*mode)
{
  int flags=0;//设置文件打开的方式
  int defaultmode=0666;//设置文件打开的默认权限

  if(strcmp(mode,"r")==0)
  {
    flags|=O_RDWR;
  }
  else if(strcmp(mode,"w")==0)
  {
    flags|=O_WRONLY|O_CREAT|O_TRUNC;
  }
  else if(strcmp(mode,"a")==0)
  {
    flags|=O_WRONLY|O_CREAT|O_APPEND;
  }
  int fd=0;

  if(flags&O_RDWR)
    fd=open(path,flags);
  else
   fd=open(path,flags,defaultmode);

  if(fd<0)//文件打开失败
  {
    perror("open");
    return NULL;
  }

  FILE_*fp=(FILE_*)malloc(sizeof(FILE_));//为FILE_结构体开辟空间
  assert(fp);
  //初始化FILE_
  fp->cap=SIZE;
  fp->feilno=fd;
  fp->flag=SYNC_LINE;//默认设为行刷新
  fp->size=0;

  memset(fp->buff,0,SIZE);
  return fp;

}

void fwrite_(const char*ptr,size_t num,FILE_*fp)
{
  //将字符串拷贝到缓冲区
  memcpy(fp->buff+fp->size,ptr,num);
  //更新缓冲区使用量
  fp->size+=num;

  //按照刷新方式刷新
  if(fp->flag&SYNC_NOW)
  {
    write(fp->feilno,fp->buff,fp->size);
    fp->size=0;
  }
  else if(fp->flag&SYNC_ALL)
  {
    if(fp->size==fp->cap)
    {
      write(fp->feilno,fp->buff,fp->size);
      fp->size=0;
    }
  }
  else if(fp->flag&SYNC_LINE)
  {
    if(fp->buff[fp->size-1]=='\n')//如果最后一个字符是\n
    {

      write(fp->feilno,fp->buff,fp->size);
      fp->size=0;
    }
  }
  
}

void fflush_(FILE_*fp)
{
  //所谓刷新,不过就是将缓冲区中的内容刷新到外设中,有内容才刷新
  if(fp->size>0)
    write(fp->feilno,fp->buff,fp->size);
  
  fsync(fp->feilno);//强制刷新到磁盘
  //刷新完以后缓冲区就没数据了,要将缓冲区置空
  fp->size=0;
}

void fclose_(FILE_*fp)//在关闭文件之前,还要刷新缓冲区
{

  fflush_(fp);
  close(fp->feilno);
}

6.操作系统的缓冲区

不止用户层有缓冲区,内核中也有一个内核缓冲区。当我们使用C语言文件操作函数写入数据时,首先将数据拷贝到FILE结构体的缓冲区中,并按照无缓冲/行缓冲/全缓冲的刷新策略将数据刷新到内核缓冲区中,最后由操作系统自主将内核缓冲去中的数据刷新到磁盘中。

与其将fwrite等函数理解成写入函数,不如将其理解成拷贝函数

理解缓冲区

如果你要强制将内核缓冲区中的数据刷新到外设中,可以使用系统调用fsync文章来源地址https://www.toymoban.com/news/detail-425269.html

到了这里,关于理解缓冲区的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 用Linux的视角来理解缓冲区概念

    缓冲区(buffer)是存储数据的临时存储区域。当我们用C语言向文件中写入数据时,数据并不会直接的写到文件中,中途还经过了缓冲区,而我们需要对缓冲区的数据进行刷新,那么数据才算写到文件当中。而缓冲区通常是一块内存区域,可以是数组、队列、链表等数据结构。

    2024年01月20日
    浏览(53)
  • Linux文件系列: 深入理解缓冲区和C标准库的简单模拟实现

    至此,我们理解了缓冲区的概念和作用,下面我们来简易模拟实现一下C标准库 我们要实现的是: 1.文件结构体的定义 1.首先要有一个文件结构体: 刷新策略分别宏定义为 2.myfopen等等函数的声明 path:文件路径+文件名 mode:打开文件的方式 “r”:只读 “w”:覆盖写 “a”:追加写 strea

    2024年03月11日
    浏览(66)
  • [Linux]理解文件系统!动静态库详细制作使用!(缓冲区、inode、软硬链接、动静态库)

            hello,大家好,这里是bang___bang_,今天来谈谈的文件系统知识,包含有缓冲区、inode、软硬链接、动静态库。本篇旨在分享记录知识,如有需要,希望能有所帮助。 目录 1️⃣缓冲区 🍙缓冲区的意义 🍙常见缓冲区刷新策略 🍙缓冲区位置猜想 🍥现象猜测 🍥现象解

    2024年02月13日
    浏览(48)
  • 【看表情包学Linux】文件描述符 | 重定向 Redirection | dup2 函数 | 缓冲区的理解 (Cache)

       🤣  爆笑 教程  👉 《看表情包学Linux》👈   猛戳订阅     🔥 💭 写在前面: 在上一章中,我们已经把 fd 的基本原理搞清楚了。本章我们将开始探索 fd 的应用特征,探索 文件描述符的分配原则。讲解重定向,上一章是如何使用 fflush 把内容变出来的,介绍 dup2 函数,

    2023年04月25日
    浏览(53)
  • 理解缓冲区

    对于这样的代码,首先可以肯定的是 printf 语句先于 sleep 执行,既然如此那么就应该是先打印语句然后进行休眠,下面看看结果: 但这里却是先休眠以后再打印语句,这是因为存在一个叫缓冲区的东西,当我们要向外设写入数据(让显示器显示就是向显示器写入数据)时会将

    2023年04月25日
    浏览(71)
  • 【Linux】文件缓冲区

    提到文件缓冲区这个概念我们好像并不陌生,但是我们对于这个概念好像又是模糊的存在脑海中,之间我们在介绍c语言文件操作已经简单的提过这个概念,今天我们不妨深入理解什么是文件缓冲区 通过自己实现库中的一些文件操作函数更加深入的理解文件缓冲区 自定义实现

    2024年02月10日
    浏览(56)
  • 【C语言趣味教程】(8) 标准 IO 流:输入和输出 | 标准输入 stdin | 标准输出 stdout | 详解 printf 和 scanf | 探讨 scanf 缓冲区问题和安全性问题

        🔗 《C语言趣味教程》👈 猛戳订阅!!! 0x00 引入:I/O 的概念 计算机中的输入和输出,简称 ,其中:  代表 Input,即输入。

    2024年02月09日
    浏览(49)
  • 【linux】重定向+缓冲区

    自我名言 : 只有努力,才能追逐梦想,只有努力,才不会欺骗自己。 喜欢的点赞,收藏,关注一下把! close(1),为什么没有打印新建文件fd呢? printf(“%dn”,fd); printf会把内容打印到stdout文件中。 但是close(1)关闭标准输出stdout—显示器,int fd=open();新打开的文件fd是1。 st

    2024年02月08日
    浏览(54)
  • 浅谈linux缓冲区的认识!

    今天来为大家分享一波关于缓冲区的知识!那么既然我们要谈缓冲区,那么就得从是什么?为什么?有什么作用这几个方面来谈论一下缓冲区!然后再通过一些代码来更加深刻的理解缓冲区的知识! 从最简单的理解方面来,我们可以将缓冲区理解成一块内存!那么这块内存是

    2024年02月05日
    浏览(54)
  • [Linux打怪升级之路]-缓冲区

    前言 作者 : 小蜗牛向前冲 名言 : 我可以接受失败,但我不能接受放弃    如果觉的博主的文章还不错的话,还请 点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正  本期学习目标:认识什么是缓冲区,缓冲区在哪里,模拟实现一个简单的缓

    2024年02月07日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包