【Linux】缓冲区理解

news2024/12/1 0:33:48

​🌠 作者:@阿亮joy.
🎆专栏:《学会Linux》
🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
在这里插入图片描述

目录

    • 👉缓冲区👈
      • 缓冲区引起的差异
      • 缓冲区的刷新策略
      • 缓冲区在哪里
      • 简单模拟实现缓冲区
    • 👉总结👈

👉缓冲区👈

缓冲区引起的差异

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    // C接口
    printf("hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    fputs("hello fputs\n", stdout);

    // system call
    const char* msg = "hello write\n";
    write(1, msg, strlen(msg));

    fork();

    return 0;
}

在这里插入图片描述

将 fork 函数注释掉,再重复以上的过程。

在这里插入图片描述

在这里插入图片描述
对比这两份份代码,产生这种差异应该是与 fork 创建子进程有关,也应该与写实拷贝有关。想要理解这种想象,我们必须要理解缓冲区。

缓冲区本质就是一段内存,其意义是节省进程进行数据 IO 的时间。缓冲区就想现实生活中的快递行业能够节省发送者的时间。

在这里插入图片描述

进程要向文件中写入数据,首先进程会将数据拷贝到缓冲区,再将数据刷新写入到文件中。而我们之前用的 fwrite 等函数本质上就是拷贝函数。

缓冲区的刷新策略

如果有一堆数据,一次将这堆数据写入到外设的效率是要比将这堆数据分批次写入到外设的效率要高的。因为 IO 的大多数时间都要等外设就绪的,分批次写入需要等待更长的时间。那么,缓冲区一定会结合具体的设备定制自己的刷新策略:

  • 立即刷新 — 无缓冲
  • 行刷新 — 行缓冲(显示器)
  • 缓冲区满 — 全缓冲(磁盘文件)

为什么显示器采取的是行缓冲呢?因为显示器是给用户看的,如果采取全缓冲的刷新策略,用户体验会比较差。为了用户体验不会太差且 IO 效率也不至于太低,所以显示器就采取了行缓冲的刷新策略。

两种特殊情况:一是用户强制刷新缓冲区;二是进程退出时,一般都要进行缓冲区刷新。

缓冲区在哪里

在这里插入图片描述

为了解释上图出现的现象,我们必须知道缓存区在哪里。因为这种现象一定和缓冲区有关!通过上图的现象,我们可以知道:该缓冲区一定不在内核中!!! 如果在内核中,那么 write 也应该被打印两次。其实我们之前谈论的所有的缓冲区都是用户级语言层面给我们提供的缓冲区,这个缓冲区就是在 FILE 结构体里,该结构体中包含了文件描述符 fd 和 用户级缓冲区。所以我们调用 fflush 函数强制刷新缓冲区,要传文件指针FILE*

在这里插入图片描述

因为 IO 相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上访问文件都是通过文件描述符 fd 访问的。fprintf 等函数向外设写入数据,首先会写入到 FILE 结构体内部的缓冲区中,然后在合适的时候刷新到外设中。

那么接下来,本人就给大家解释一下上面 fork 引起的差异。

  • 如果没有进行输出重定向,看到了四条数据。stdout 默认使用的是行刷新。在 fork 创建子进程之前,三条 C 语言函数已经将数据打印输出到显示器(外设)上了,FILE 结构体内部和进程内部不存在对应的数据了。
  • 如果进行了输出重定向,写入文件不再是显示器,而是磁盘文件。其采取的缓冲区刷新策略是全缓冲,之前的三条 C 语言函数要打印的数据不足以将 stdout 的缓冲区写满,数据并没有被刷新到磁盘文件中。stdout 是属于父进程的。fork 创建子进程后,紧接着就是进程退出。谁先退出,一定要进行缓冲区刷新,其本质就是修改。一旦数据发生修改,那么就会有写时拷贝!最终 C 语言接口的数据会显示两份。
  • write 的数据为什么被没有显示两次呢?因为上面的过程都和 write 无关,write 没有使用 FILE*,而用的是文件描述符 放大,没有 C 语言提供的缓冲区。

简单模拟实现缓冲区

// myStdio.h
#pragma once

#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define SIZE 1024   // 缓冲区大小
#define SYNC_NOW   1    // 无缓冲
#define SYNC_LINE  2    // 行缓冲
#define SYNC_FULL  4    // 全缓冲

typedef struct FILE_
{
    int flags;  // 缓冲区刷新策略
    int fileno; // 文件描述符
    int size;   // buffer当前的使用量
    int capacity;   // buffer的总容量
    char buffer[SIZE];  //缓冲区
}FILE_;


FILE_* fopen_(const char* pathname, const char* mode);
void fwrite_(const void* ptr, int num, FILE_* fp);
void fflush_(FILE_* fp);
void fclose_(FILE_* fp);

// myStdio.c
#include "myStdio.h"

FILE_* fopen_(const char* pathname, 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
    {
        // TODO:r+, w+...
    }

    int fd = 0;
    if(flags & O_RDONLY)  fd = open(pathname, flags);
    else  fd = open(pathname, flags, defaultMode);

    if(fd < 0)
    {
        const char* err = strerror(errno);
        write(2, err, strlen(err));
        return NULL;    // 打开文件失败返回NULL的原因
    }

    FILE_* fp = (FILE_*)malloc(sizeof(FILE_));
    assert(fp != NULL);

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

    return fp;  // 打开文件成功返回FILE*的原因
}

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_LINE)
    {
        // 不考虑abcd\nef的情况
        if(fp->buffer[fp->size - 1] == '\n')
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0; // 清空缓冲区
        }
    }
    else if(fp->flags & SYNC_FULL)
    {
        if(fp->size == fp->capacity)
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0; // 清空缓冲区
        }
    }
    else
    {
        return;
    }
}

void fflush_(FILE_* fp)
{
    if(fp->size > 0)  write(fp->fileno, fp->buffer, fp->size);
    fsync(fp->fileno);	// 强制刷新内核缓冲区,将数据刷新到外设中
    fp->size = 0;	// 清空缓冲区
}

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

// main.c
#include "myStdio.h"
#include <stdio.h>

int main()
{
    FILE_* fp = fopen_("log.txt", "w");
    if(fp == NULL)
    {
        return 1;
    }

    int cnt = 10;
    const char* msg = "hello world\n";
    while(1)
    {
        --cnt;
        fwrite_(msg, strlen(msg), fp);
        sleep(1);
        printf("count:%d\n", cnt);
        if(cnt == 0)   break;
    }
    fclose_(fp);

    return 0;
}

在这里插入图片描述

监控脚本
while :; do cat log.txt ; sleep 1; echo "------------------"; done

因为默认的是行缓冲,所以一秒就会向文件写入一次数据。当然也可以调用fflush_函数强制刷新缓冲区。

其实调用 write 接口也不是直接将数据直接就写入外设中,而是内核缓冲区中,至于什么时候刷新内核缓冲区由操作系统自主决定!但是有些信息是非常重要的,需要马上刷新内核缓冲区写入到磁盘文件中。那么此时就需要借助fsync接口了,该接口可以直接刷新内核缓冲区并将数据写入外设中。

在这里插入图片描述

在这里插入图片描述

👉总结👈

本篇博客主要讲解了什么是缓冲区、缓冲区的刷新策略、缓冲区在哪里以及简单模拟实现缓冲区。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️

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

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

相关文章

深入理解ECAPA-TDNN——兼谈Res2Net、ASP统计池化、SENet、Batch Normalization

概述 ECAPA-TDNN是说话人识别中基于TDNN的神经网络&#xff0c;是目前最好的单体模型之一关于TDNN&#xff0c;可以参考深入理解TDNN&#xff08;Time Delay Neural Network&#xff09;——兼谈x-vector网络结构 ECAPA-TDNN TDNN本质上是1维卷积&#xff0c;而且常常是1维膨…

【Django项目开发】角色管理模块的开发(八)

文章目录一、序列化器设计1、嵌套的序列化器设计2、普通的序列化类支持&#xff1a;新增、修改角色名、删除、查询3、用于给某一个角色批量授权的序列化4、用于给某一个角色单一授权&#xff0c;包括取消单一授权二、视图类设计1、包含的接口有哪些2、set_permission_to_role方…

听说……国产的领航辅助驾驶系统都很卷?

什么是城市NOA功能&#xff1f; 基于国产芯片的城市NOA功能有看点吗&#xff1f; 国产芯片发展到了什么阶段&#xff1f; 车上配的激光雷达是不是越多越好&#xff1f; 车企常吹的“数据驱动”、“中央计算” 究竟是真是假&#xff1f; ……最近&#xff0c;两家中国公司组CP 推…

基于ros将文件夹中的图像转换为bag包(c++版本)

一、前期工作创建工作空间 二、创建工作包 创建完成后&#xff0c;文件夹的格式为&#xff1a; 三、准备编译文件和代码 3.1 更换编译文件中的内容 将上图中的&#xff0c;CMakeLists.txt文件中的内容&#xff0c;替换为下面的内容 cmake_minimum_required(VERSION 3.0.2) p…

【漏洞复现】Yapi接口管理平台RCE漏洞汇总

文章目录前言YApi接口管理平台远程代码执行漏洞一、漏洞描述二、影响版本三、FOFA语句四、漏洞复现五、修复建议YApi NoSQL注入导致远程命令执行漏洞一、YApi介绍二、漏洞描述三、漏洞分析四、漏洞详情五、影响版本六、漏洞复现七、修复建议前言 本篇文章主要归纳总结YApi各版…

南邮数据结构

md文档网址&#xff1a;https://gitee.com/infiniteStars/wang-dao-408-notes/blob/master/408/数据结构.md 1 绪论 1.1算法的基本概念 程序与算法的区别和联系 联系&#xff1a;程序是计算机指令的有序集合&#xff0c;是算法用某种程序设计语言的表述&#xff0c;是算法在…

2023年如何搭建最小可行性的产品文档/产品手册?

在推出并击败竞争对手进入市场的竞赛中&#xff0c;很容易将“不必要的”任务&#xff08;如文档&#xff09;放在次要位置。但根据 Write the Docs 纪录片社区的说法&#xff0c;文档应该既是先导性的&#xff0c;也是参与性的。这意味着您应该在开始开发之前开始记录&#xf…

吊打高斯模糊的stackBlur加入OpenCV

stackBlur介绍 stackBlur 最近才加入到OpenCV中&#xff0c;将在下一个Relase版本&#xff08;4.7&#xff09;中出现。C用户可以尝试从源码编译OpenCV体验一下。Python 用户可以尝试用pip安装rolling版本的OpenCV&#xff1a; pip install opencv-python-rolling4.6.0.202210…

新手教程 | 常见的爬虫类型有哪些?

程序猿圈流传着一个神话级别的事&#xff1a;全公司仅靠1个人&#xff0c;每年就能转上1400多万美元。听起来天方夜谭一样&#xff0c;那他是如何做到的呢&#xff1f;看报道就会发现&#xff0c;他利用的是爬虫技术。 随着互联网的发展&#xff0c;从海量的互联网数据中&…

不可错过,Java程序员必备珍藏书单

不要因为迷茫&#xff0c;而停止了脚下的路。给大家推荐一份Java程序员必看的书单&#xff0c;豆瓣评分都挺不错的&#xff0c;往下看&#xff01; 一、Java 基础篇书单 《Java编程思想》&#xff1a;从Java的基础语法到最高级特性&#xff08;深入的面向对象概念、多线程、自…

煤矿智能化相关50项团体标准征求意见

智能化煤矿总体架构 原文地址&#xff1a;https://chinacs.scimall.org.cn/a3651.html 由煤矿智能化创新联盟等单位提出&#xff0c;中国煤炭学会归口&#xff0c;中煤科工集团常州研究院有限公司等单位起草的《煤矿通信接口与协议通用技术要求》50项团体标准已完成征求意见稿的…

用 Python 脚本实现电脑唤醒后自动拍照 截屏并发邮件通知

背景 背景是这样的, 我的家里台式机常年 休眠, 并配置了 Wake On Lan (WOL) 方便远程唤醒并使用. 但是我发现, 偶尔台式机会被其他情况唤醒, 这时候我并不知道, 结果白白运行了好几天, 浪费了很多电. 所以我的需求是这样的: &#x1f914; 电脑唤醒后(可能是开机, 有可能是…

3款电脑必装软件,功能强大且免费,打死也舍不得卸载

闲话不多说&#xff0c;直接上狠货。 1、FlowUs息流 FlowUs息流是一款知识管理与协作平台&#xff0c;以云端笔记为载体&#xff0c;配合在线文档、知识库、文件夹等多形态功能&#xff0c;支持免费使用&#xff0c;极大提高个人与团队工作效率。支持多端同步使用&#xff0c;无…

STL空间配置器框架分析

目录 一、空间配置器概念 二、空间配置器的作用 三、内存池技术 四、空间配置器的实现原理 3.1 流程概述 3.2 一级空间配置器 3.3 二级空间配置器 3.3.1 二级空间配置器设计 3.3.2 内存碎片问题 一、空间配置器概念 即为各个容器高效的管理空间(空间的申请与回收)的。…

聊一聊双亲委派模式

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 说起双亲委派模型&#xff0c;不得不说一下类加载器。 类加载器是什么&#xff1f; 当我们编译Java类时&#xff0c;JVM会创建与平台和…

Allegro174版本新功能介绍之移动画布不闪屏设置

Allegro174版本新功能介绍之移动画布不闪屏设置 Allegro在升级到174版本后,在移动画布的时候,视图数据量比较大的情况,会出现闪屏现象 Allegro在切换到Open GL模式下,这个现象会有所缓解,具体操作如下 选择Setup选择User-preferences

【Ansible】ansible 基础知识

ansible 文章目录ansible一、ansible Ad-Hoc 命令1.命令格式2.模块类型3.联机帮助4.常用模块4.1 command & shell 模块4.2 script 模块4.3 copy 模块4.4 yum_repository4.5 yum 模块4.6 systemd 模块4.7 group 模块4.8 user 模块4.9 file 模块4.10 cron 模块4.11 template …

SIE高级副总裁:关于PS VR2定价、设计、内容的思考

2023年2月22日&#xff0c;索尼将正式推出次世代头显PS VR2&#xff0c;首发VR游戏将超过30款&#xff0c;其中包括热门游戏《GT赛车7 VR》。此外&#xff0c;PS5全球销量也已突破3000万。实际上距离索尼推出上一代PS VR&#xff0c;已经过去了6年时间&#xff0c;相比于Quest等…

M12269 支持PD3.1等快充协议、140W升降压3-8节多串锂电充放电移动电源管理IC

引言 在快充技术持续迭代升级的过程中&#xff0c;充电从小功率向中大功率的转变是最为明显的。支持的快充功率从最初的7.5W&#xff0c;已经向最高240W迈进。PD3.1协议的推出&#xff0c;进一步助力快充加速走向中大功率。新增三种固定电压档&#xff1a;28V&#xff08;100-1…

VS1053 MP3模块介绍

VS1053MP3模块简介ATK-VS1053 MP3 MODULE是ALIENTEK推出的一款高性能音频编解码模块&#xff0c;该模块采用VS1053B作为主芯片&#xff0c;支持&#xff1a;MP3/WMA/OGG/WAV/FLAC/MIDI/AAC等音频格式的解码&#xff0c;并支持&#xff1a;OGG/WAV音频格式的录音&#xff0c;支持…