【Linux】理解缓冲区

news2024/12/23 5:39:08

文章目录

    • 一.引入
    • 二.认识缓冲区
      • 1.为什么
      • 2.刷新策略
      • 3.在哪里
    • 三、理解缓冲区

一.引入

image-20221231115202274

我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关!

C接口的函数被打印了两次系统接口前后只是打印了一次:和fork函数有关,fork会创建子进程。在创建子进程的时候,数据会被处理成两份,父子进程发生写时拷贝,我们进行printf调用数据的时候,数据写到显示器外设上,就不属于父进程了,数据没被写到显示器上,依旧属于父进程,而调用printf并不一定把数据刷到显示器上,没有被显示本质就是数据没有从内存到外设,所以这份没有被显示的数据依旧属于这进程,当我们去fork的时候,进程退出要刷新缓冲区,此时刷新的过程就是把数据从内存刷新到外设,刷新到外设的同时,也会把程序内部的缓冲区的数据直接清走,这就是写入,跟写时拷贝有关系

对于这个现象的问题我们可以直接往下看👇


二.认识缓冲区

1.为什么

缓冲区的本质就是一段内存。在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。

数据如果直接从内存到磁盘,在内存中速度快,但是访问外设效率比较低,那太消耗时间了,属于外设IO,所以缓冲区的意义就是节省进程进行数据IO的时间!进程需要把数据拷贝到缓冲区里:我们并不需要拷贝,而是调用fwrite,与其理解fwrite是写入到文件的函数,倒不如理解fwrite是拷贝函数,将数据从进程拷贝到缓冲区或者外设当中

image-20230112164431962

数据可以直接拷贝到缓冲区,高速设备不用在等待低速设备,提高计算机的效率。

2.刷新策略

缓冲区的刷新策略:如果有一块数据,一次写入到外设(效率最高)vs如果有一块数据,多次少量写入到外设,需要多次IO

缓冲区一定结合具体的设备定制自己的刷新策略

1.立即刷新——无缓冲 ,场景较少,比如调用printf直接fflush

2.行刷新——行缓冲——显示器 ,数据的printf带上\n就会立马显示到显示器上。显示器为什么是行缓冲:显示器是外设,进程运行时在内存里的,把数据定期要刷新到外设,显示器设备比较特殊,是给用户来看的,从左到右,所以显示器为了保证刷新效率,并且用户体验良好,所以显示器采用行缓冲,满足用户的阅读体验并且在一定程度上效率不至于太低

3.缓冲区满——全缓冲——磁盘文件,效率最高,只需要一次IO,比如文件读写的时候,直接写到磁盘文件

但是存在特殊情况:a.用户强制刷新 b,进程退出——一般到要进行缓冲区刷新

所以对于全缓冲,缓冲区满了采取刷新,减少IO次数,提高效率。

3.在哪里

缓冲区的位置究竟在哪里:从上面的例子我们直接往显示器上打印结果为4条,往文件打印为7条,这跟缓冲区有关,同时这也说明了缓冲区一定不在内核中,为什么?如果在内核中write也应该打印两次,write是系统接口。我们之前谈论的所有缓冲区都指的是用户级语言层面提供的缓冲区。这个缓冲区,在stdout,stdin,stderr对应的类型---->FILE*,FILE是一个结构体,里面封装了fd,同时还包括了一个缓冲区!

FILE结构体缓冲区,所以我们直接要强制刷新的时候fflush(文件指针),关闭文件fclose(文件指针),这是因为传进去的文件指针对应的缓冲区

从源码出发,我们可以来看一看FILE结构体:

image-20230106144023818

image-20230106144054268

所以我们一般所说的缓冲区是语言级别的缓冲区,C语言提供的在FILE结构体里对应的缓冲区。

现在,我们现在重新来看一看刚开始的现象:

image-20230106150737623

1.如果我们没有进行重定向>,看到了4条消息,stdout默认使用的是行刷新,在进程fork之前,三条C函数已经将数据打印输出到显示器上(外设),你的FILE内部进程内部就不存在对应的数据了。

2.如果我们进行了重定向>,写入文件不在是显示器,而是普通文件,采用的刷新策略是全缓冲,之前的3条C函数虽然带了\n,但是不足以将stdout缓冲区写满,所以数据并没有刷新! 在执行fork的时候,stdout属于父进程,fork创建子进程紧接着就是进程退出,谁先退出就要进行缓冲区刷新,刷新的本质就是修改,修改的时候发生写时拷贝!所以数据最终会显示两份!上面的过程都和write无关,write没有FILE,而用的是fd,就没有C提供的缓冲区!

简单总结来说:重定向导致刷新策略发生了改变(由行缓冲变成了全缓冲)。同时发生了写时拷贝,父子进程各自刷新


三、理解缓冲区

对于缓冲区的理解我们可以自己通过代码来简单实现:

FILE_结构体的设计,这里为了避免与FILE发生冲突,我们命名为FILE_:

#define SIZE 1024
typedef struct _FILE
{
    int flags;//刷新方式
    int fileno;//文件描述符
    int cap;//buffer的总容量
    int size;//buffer当前使用量
    char buffer[SIZE];//缓冲区                                                                              }FILE_;               

主函数:

int main()        
{         
    //打开
    FILE_ *fp = fopen_("./log.txt","w");
    //打开失败
    if(fp==NULL)                        
    {                                   
        return 1;
    }            
    int cnt = 10;
    const char*masg = "hello world ";
    while(1)                         
    {
        //写入
        fwrite_(masg,strlen(masg),fp);
        //刷新
        fflush_(fp);
        //睡眠
        sleep(1);                     
        printf("count:%d\n",cnt);
        cnt--;                   
        if(cnt==0) break;        
    }   
    //关闭
    fclose_(fp);                                                                               
    return 0;                                                                                  
}                                                                                              

对于C语言来说,文件接口一旦打开成功,其余接口要带上FILE*,因为FILE结构体里包含了各种数据:

下面是我们需要自己实现的文件接口:

//打开
FILE_ * fopen_(const char*path_name,const char*mode);
//以下的接口都需要带上FILE_*
void  fwrite_(const void *ptr,int num, FILE_*fp);

void fflush_(FILE_*fp);

void fclose_(FILE_* fp);

fopen_:打开我们需要去判断具体是按什么方式打开:

FILE_ *fopen_(const char *path_name, const char *mode)
{
    int flags = 0;
    int defaultMode=0666; //设置默认权限
    //读方式
    if(strcmp(mode, "r") == 0)
    {
        flags |= O_RDONLY;
    }
    //写方式
    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);
    }
    //其他
    else
    {
        //其他方式这里就不展开了
    }
    int fd = 0;

    if(flags & O_RDONLY) fd = open(path_name, flags);
    else fd = open(path_name, flags, defaultMode);
    //处理打开失败
    if(fd < 0)
    {
        const char *err = strerror(errno);
        write(2, err, strlen(err));
        return NULL; 
    }
    //打开成功
    FILE_ *fp = (FILE_*)malloc(sizeof(FILE_));
    assert(fp);

    fp->flags = SYNC_LINE; //默认设置成为行刷新
    fp->fileno = fd;
    fp->cap = SIZE;
    fp->size = 0;
    memset(fp->buffer, 0 , SIZE);
    return fp; 
}

fwrite_:

void fwrite_(const void *ptr, int num, FILE_ *fp)
{
    // 写入到缓冲区中
    memcpy(fp->buffer+fp->size, ptr, num); //不考虑缓冲区溢出的问题
    fp->size += num;
    // 判断是否刷新
    if(fp->flags & SYNC_NOW)
    {
        write(fp->fileno, fp->buffer, fp->size);
        fp->size = 0; //清空缓冲区
    }
    else if(fp->flags & SYNC_FULL)
    {
        if(fp->size == fp->cap)
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }
    else if(fp->flags & SYNC_LINE)
    {
        if(fp->buffer[fp->size-1] == '\n') // abcd\nefg在这个地方不考虑
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }
    else{
    }
}

fclose:

void fclose_(FILE_ * fp)
{
    fflush_(fp);
    close(fp->fileno);
}

fflush_:这里将数据强制要求操作系统进行外设刷新要用到fsync:

void fflush_(FILE_ *fp)
{
    if( fp->size > 0) write(fp->fileno, fp->buffer, fp->size);
    fsync(fp->fileno); //将数据,强制要求OS进行外设刷新!
    fp->size = 0;
}

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

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

相关文章

C++职工管理系统,eof()函数,选择排序

目录 1.创建管理类 1.1头文件实现&#xff0c;在workerManager.h中设计管理类 1.2源文件实现&#xff0c;在workerManager.cpp中将构造和析构函数的空实现补全 2.菜单功能 2.1在管理类workerManager.h中添加菜单成员函数 2.2在管理类workerManager.cpp中写实现 3.退出功能 …

Pycharm基础安装教程:搭建python环境+安装pycharm

嗨害大家好鸭&#xff01;我是小熊猫~ 之前有的小伙伴总说小熊猫发的内容不够基础… 这次小熊猫就来给大家做一个简单的安装介绍吧~ 这里给大家准备了社区版pycharm2021.2 &#xff08;最新版部分库不稳定&#xff0c;不建议下载&#xff09; 第一次安装的小伙伴实在不会下载…

【JavaSE系列】第十节 —— 带你吃透抽象类

&#xff08;6&#xff09;当一个抽象类 继承一个抽象类的时候&#xff0c;可以不用来重写 当作父类的那个抽象类的抽象方法&#xff1a;提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、抽象类的概念 二、…

二叉苹果树 【树形dp(背包)】

来源&#xff1a;牛客网 题目链接&#xff1a;https://ac.nowcoder.com/acm/contest/25022/1006 题目描述 有一棵二叉苹果树&#xff0c;如果数字有分叉&#xff0c;一定是分两叉&#xff0c;即没有只有一个儿子的节点。这棵树共N个节点&#xff0c;标号1至N&#xff0c;树根编…

rollup.js配置环境变量

场景: 由于项目是通过svelte.js rollup.js框架搭建起来的, 并没有使用到cli脚手架, 没有办法配置不同环境的变量一. 使用cross-env添加环境变量安装cross-env&#xff0c;它可根据不同的系统设置环境变量npm install cross-env --save-dev在 package.json 中&#xff1a;"…

MongoDB索引

介绍 增加查询效率&#xff0c;不必每次都全表扫描单字段索引&#xff1a;在用户单个字段上创建升序/降序索引复合索引&#xff1a;在多个字段上添加索引&#xff1b;如{name:1, age:-1}&#xff0c;关注field顺序其他索引&#xff1a;地理空间索引&#xff0c;文本索引&#x…

浙大MBA复试经验和真题分享——知己知彼胜率更高

前段时间跟朋友吃饭的时候&#xff0c;问我在浙大读MBA是什么体验&#xff1f;其实说实话&#xff0c;当时选择浙大&#xff0c;主要还是为了学历&#xff0c;觉得自己一个普通的二本学历真的平平无奇&#xff0c;公司里面新进的员工虽然年纪轻&#xff0c;但学历却很有来头&am…

PySpark任务提交spark-submit参数设置一文详解

目录 前言 一、PySpark集群运行原理 二、spark-submit参数详解 1.指定运行目录 2.--deploy-mode 3.--master 4.驱动程序和执行器资源 5.--files和--verbose 6.Spark提交配置 三.PySpark程序提交配置选项 1.构建一套虚拟环境 2. 模块依赖问题原因 参阅 前言 之前我们已…

MySQL 笔记

文章目录安装MySQL 语法格式MySQL数据类型命令操作数据库CRUD查询创建表删除表修改表操作数据增改删查基础查询条件查询模糊查询排序查询分组查询分页查询聚合函数约束约束分类安装 软件安装&#xff5c;macOS下超详细的MySQL安装 MySQL 语法格式 每条语句以分号;结尾&#…

LeetCode150.逆波兰表达式

LeetCode刷题记录 文章目录&#x1f4dc;题目描述&#x1f4a1;解题思路⌨C代码&#x1f4dc;题目描述 给你一个字符串数组 tokens &#xff0c;表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。 注意 有效的算符为 、-、* 和/。…

如何mock当前类的私有方法

背景 基础知识 mockito单元测试&#xff1a;它的做法是mock掉当前类的所有外部依赖&#xff0c;保障自己的代码没有问题。举个例子&#xff0c;如果数据库查询的语句出了问题&#xff0c;单元测试不会测试出来。因为它直接mock掉了&#xff0c;不会去真的去查数据库。从这点来…

电压放大器在大功率脉冲电能源研究中的应用

实验名称&#xff1a;大功率脉冲电能源高精度测试技术研究 研究方向&#xff1a;仪器仪表测试 测试目的&#xff1a; 传统的测量精度校准的方法是对传感器单一频率下的刻度因子进行校准&#xff0c;校准方法通常选用同轴分流器串联于放电回路中&#xff0c;通过测量同轴分流器两…

Ubuntu18.04下安装mysql并使用QT成功编译驱动方法

开发环境&#xff1a;Ubuntu18.04QT5.14.2MySQL5.7.240 编译步骤&#xff1a; 1、安装mysql软件和驱动&#xff1a; 打开终端命令&#xff0c;执行安装语句如下&#xff1a; sudo apt-get install mysql-server sudo apt-get install mysql-client sudo apt-get install lib…

MyBatisPlus(MP)学习记录(分页查询的开启+日志打印配置)

MP介绍&#xff08;官网链接&#xff09; MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 前言 本篇文章展示的MP教学中涉及到的版本如下&#xff1a; IDE…

使用Xshell 将Linux文件下载到本地或者将本地的文件上传到Linux

第一步&#xff1a;在Linux中安装上传下载功能的软件 sudo apt install lrzsz第二步&#xff1a;将Linux文件下载到本地&#xff1a;此时我的Linux桌面上有一个叫test.c的文件&#xff0c;我要将它下载到windows桌面上。 在Linux终端输入&#xff1a; sz test.c这里使用的是…

座舱显示的未来到底是什么?宝马供应商给出了答案

“显示器是汽车的核心人机界面&#xff0c;”业内人士表示&#xff0c;与此同时更智能化、体验更好的显示解决方案在驾驶舱的设计中起着核心作用。 屏幕及其增强驾驶体验的巨大潜力正迅速成为未来智能网联汽车DNA的一部分。高分辨率、大尺寸以及曲面屏、OLED屏等等新元素&…

【手写 Vue2.x 源码】第十九篇 - 根据 vnode 创建真实节点

一&#xff0c;前言 上篇&#xff0c;根据 render 函数&#xff0c;生成 vnode&#xff0c;主要涉及以下几点&#xff1a; 封装 vm._render 返回虚拟节点_s&#xff0c;_v&#xff0c;_c的实现 本篇&#xff0c;根据 vnode 虚拟节点渲染真实节点 二&#xff0c;根据 vnode 创…

SpringCloud项目实例3--Nacos整合

然后新建一个Module&#xff0c;命名为nacos-provider-demo&#xff0c;Java代码的包名为ltd.newbee.cloud。在该Module的pom.xml配置文件中增加parent标签&#xff0c;与上层Maven建立好关系。之后&#xff0c;在这个子模块的 pom.xml 文件中加入Nacos的依赖项 spring-cloud-s…

谈一谈暴露偏差

文章由参考文章重新组合而来。 暴露偏差又叫Exposure Bias&#xff0c;是由Teacher Forcing 导致的。 Teacher Forcing Teacher Forcing 是一种用于序列生成任务的训练技巧&#xff0c;与Autoregressive模式相对应&#xff0c;这里阐述下两者的区别&#xff1a; Autoregres…

【Flink系列】部署篇(一):Flink集群部署

主要回答以下问题&#xff1a; Flink集群是由哪些组件组成的&#xff1f;它们彼此之间如何协调工作的&#xff1f;在Flink中job, task, slots,parallelism是什么意思&#xff1f;集群中的资源是如何调度和分配的&#xff1f;如何搭建一个Flink集群&#xff1f;如何配置高可用服…