理解缓冲区

news2024/12/26 23:39:22

文章目录

    • 一.缓冲区
      • 1.什么是缓冲区
      • 2.缓冲区的意义
      • 3.缓冲区的刷新策略
      • 4.我们目前谈论的缓冲区在哪里
      • 5.仿写FILE
        • 5.1myStdio.h
        • 5.2myStdio.c
    • 6.操作系统的缓冲区

一.缓冲区

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/461652.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++11 unique_ptr智能指针

#include<iostream> using namespace std;class test { public:test() {cout << "调用构造函数" << endl;}~test() {cout << "调用析构函数" << endl;} };int main(void) {//1.构造函数unique_ptr<test>t1;unique_ptr…

数据结构之KMP算法:彻底搞懂kmp算法

数据结构的学习&#xff0c;kmp匹配算法困扰我许久&#xff0c;此处来一个总结&#xff08;仅供自己复习了解参考使用&#xff09;&#xff0c;如果有不对的地方请多多指点。好了废话不多说我们直接开始好吧。 目录 关于暴力匹配原理的讲解&#xff1a; kmp算法&#xff1a; …

ChatGPT - 如何高效的调教ChatGPT (指令建构模型-LACES问题模型)

文章目录 定义1. Limitation&#xff08;限定条件&#xff09;2. Assignment&#xff08;分配角色&#xff09;3. Context&#xff08;背景或上下文&#xff09;4. Example&#xff08;示例&#xff09;5. Step by Step&#xff08;拆分任务&#xff09; 小Demo 定义 LACES问题…

尚硅谷大数据技术Spark教程-笔记04【SparkCore(核心编程,RDD-行动算子-序列化-依赖关系-持久化-分区器-文件读取与保存)】

视频地址&#xff1a;尚硅谷大数据Spark教程从入门到精通_哔哩哔哩_bilibili 尚硅谷大数据技术Spark教程-笔记01【Spark&#xff08;概述、快速上手、运行环境、运行架构&#xff09;】尚硅谷大数据技术Spark教程-笔记02【SparkCore&#xff08;核心编程&#xff0c;RDD-核心属…

加强人工智能共性技术研发与产业化协同发展

央视网消息&#xff1a;“以5G为代表的新一代信息技术与制造业、交通、旅游等实体经济重要领域深度融合。”4月20日下午&#xff0c;国新办举行一季度工业和信息化发展情况新闻发布会&#xff0c;相关部门负责人在答问时表示&#xff0c;将用好融合应用这把金钥匙&#xff0c;开…

ReactHook学习(第一篇-N)

文章目录 Hook简介概述class组件的不足什么是 Hook?Hook 使用规则 state的研究&#xff08;useState&#xff09;State&#xff1a;组件的记忆&#xff08;响应式数据&#xff09;当普通的变量无法满足时添加一个 state 变量遇见你的第一个 Hook剖析 useState 赋予一个组件多个…

【C++】面向对象

文章目录 3.1 类与对象3.1.1 类成员的访问控制3.1.2 类的成员函数对象的访问方式成员函数的实现内联成员函数 3.1.3 构造函数复制构造函数调用复制构造函数的三种情况深复制与浅复制&#xff1f; 析构函数类的组合 3.1.4 前向引用声明3.1.5 结构体与类对比3.1.6 UML类图属性表示…

IMX6ULL裸机篇之按键消抖实验

一. 按键消抖 在之前的 按键中断实验时&#xff0c;我们讲了如何使用中断的方式驱动按键或GPIO。如果通过中断的方式处理按键的话&#xff0c;按键是需要消抖处理的。 而在之前 按键中断实验中&#xff0c;在中断处理函数中对按键进行消抖&#xff0c;调用了 delay 延时函数。…

剑指 Offer 32 - II. 从上到下打印二叉树 II

目录 题目思路BFS 题目来源 剑指 Offer 32 - II. 从上到下打印二叉树 II 题目思路 I. 按层打印&#xff1a; 题目要求的二叉树的 从上至下 打印&#xff08;即按层打印&#xff09;&#xff0c;又称为二叉树的 广度优先搜索&#xff08;BFS&#xff09;。BFS 通常借助 队列 的…

Midjourney v4 | 如何结合参考图像来生成AI艺术图

网址&#xff1a;midjourney.com 首页展示 首页如下图&#xff1a; 第一步&#xff1a;进入社群 点击首页右下角“Join the Beta”&#xff0c;进入如下页面&#xff1a; 点击“接受邀请”&#xff0c;验证之后进入 可以点击认证账号&#xff0c;进行注册&#xff1a; 应该不…

Redis三种集群模式

一、引言 Redis有三种集群模式&#xff0c;第一个就是主从模式&#xff0c;第二种“哨兵”模式&#xff0c;第三种是Cluster集群模式&#xff0c;第三种的集群模式是在Redis 3.x以后的版本才增加进来的&#xff0c;我们今天就来说一下Redis第一种集群模式&#xff1a;主从集群模…

【英语】100个句子记完7000个托福单词

其实主要的7000词其实是在主题归纳里面&#xff0c;不过过一遍100个句子也挺好的&#xff0c;反正也不多。 文章目录 Sentence 01Sentence 02Sentence 03Sentence 04Sentence 05Sentence 06Sentence 07Sentence 08Sentence 09Sentence 10Sentence 11Sentence 12Sentence 13Sent…

Redis的底层数据结构

Redis的底层数据结构 Redis的底层数据类型&#xff08;对比&#xff09;Redis的底层数据结构Redis数据类型和底层数据结构的对应关系Redis的使用 Redis的底层数据类型&#xff08;对比&#xff09; String&#xff08;字符串&#xff09;List&#xff08;列表&#xff09;Hash…

CRE66365 应用资料

CRE66365是一款高度集成的电流模式PWM控制IC&#xff0c;为高性能、低待机功耗和低成本的隔离型反激转换器。在正常负载条件下&#xff0c;AC输入高电压下工作在QR模式。为了最大限度地减少开关损耗&#xff0c;QR 模式下的最大开关频率被内部限制为 77kHz。当负载较低时&#…

Dcoekr 部署前后端分离项目SpringBoot +Vue

1.docker 部署vue docker 安装 nginx的镜像 niginx 配置文件 nginx.conf #user nobody; worker_processes 1;#error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info;#pid logs/nginx.pid;events {worker_connections…

给大家分享一个比Top更好用的Linux进程管理工具htop

一、前言 相信用过Linux操作系统的同学对Top应该都不陌生&#xff0c;我们通过Top命令可以查看CPU的占用率以及每个进程的详细信息&#xff0c;但是今天我要给大家分享一个比Top更好用的进程管理工具htop&#xff08;High Top&#xff09;。 二、htop功能介绍 htop 是一个高…

Shell编程规范及变量

这里写目录标题 一、Shell脚本编程概述1.1 shell脚本的概念1.2Shell脚本应用场景1.3 shell的作用1.4 linux中有哪些shell 二、 shell脚本的使用2.1shell脚本的构成2.2 运行脚本2.3 重定向和管道操作2.31交互式硬件设备2.32 重定向操作2.33 管道符号 三、shell脚本变量3.1 shell…

【FPGA-DSP】第九期:音频信号处理

从本文开始将记录一些简单的音频信号处理算法在System Generator中的实现方法。本文将介绍如何搭建音频信号的采集与输出模型。 音频信号属于一维信号&#xff0c;一些基本概念如下&#xff1a; 采样频率&#xff1a;根据奈奎斯特采样定理&#xff0c;采样频率Fs应该不低于声…

Vite vue 使用cdn引入element-plus

vite-plugin-cdn-import&#xff1a;cdn的引入插件 npm i vite-plugin-cdn-import or pnpm i vite-plugin-cdn-import vite.config.js import AutoImport from unplugin-auto-import/viteexport default defineConfig({ plugins: [vue({reactivityTransform: true}),importT…

0401概述-最短路径-加权有向图-数据结构和算法(Java)

文章目录 1 最短路径2 最短路径的性质3 加权有向图的数据结构3.1 加权有向边3.2 加权有向图 4 最短路径4.1 最短路径API4.2 最短路径的数据结构4.3 边的松弛4.4 顶点的松弛 结语 1 最短路径 如图1-1所示&#xff0c;一幅加权有向图和其中的一条最短路径&#xff1a; 定义&…