【Linux】缓冲区理解

news/2024/10/18 6:14:01/

​🌠 作者:@阿亮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 callconst 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.ppmy.cn/news/11208.html

相关文章

二、数据仓库模型设计

数据仓库模型设计一、数据模型二、关系模型三、维度模型1、事实表&#xff08;1&#xff09;事务事实表&#xff08;2&#xff09;周期快照事实表&#xff08;3&#xff09;累计快照事实表&#xff08;4&#xff09;无事实的事实表2、维度表3、维度模型类型&#xff08;1&#…

GAN Step By Step -- Step7 WGAN

GAN Step By Step 心血来潮 GSBS&#xff0c;顾名思义&#xff0c;我希望我自己能够一步一步的学习GAN。GAN 又名 生成对抗网络&#xff0c;是最近几年很热门的一种无监督算法&#xff0c;他能生成出非常逼真的照片&#xff0c;图像甚至视频。GAN是一个图像的全新的领域&#…

基于javaweb(springboot+mybatis)生活美食分享平台管理系统设计和实现以及文档报告

基于javaweb(springbootmybatis)生活美食分享平台管理系统设计和实现以及文档报告 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎…

RK3399平台开发系列讲解(CPU篇)CPUFreq 中央处理器频率调节技术

🚀返回专栏总目录 文章目录 一、CPUFreq组成二、设备树配置沉淀、分享、成长,让自己和他人都能有所收获!😄 📢中央处理器频率调节(Central Processing Unit frequency,CPUFreq)技术可以降低ARM芯片的功耗,例如在系统对任务压力较小时,通过调整处理器工作频率与输入…

蓝桥杯C51

#include "reg52.h"sfr AUXR 0x8e; //定义辅助寄存器sbit S5 P3^2; //定义按键S5引脚 sbit S4 P3^3; //定义按键S4引脚unsigned char count 0; //定义中断计数器 unsigned char t_h 0; //定义运行时间的变量 unsigned char t_m 0; …

C 语言中的一些谜题(一)

关联博文&#xff1a;《C 语言中的谜题(二)》 1. puzzle 1 #include<stdio.h>#define TOTAL_ELEMENTS (sizeof(array) / sizeof(array[0]))int array[] {23,34,12,17,204,99,16};int main(){int d;for(d-1;d < (TOTAL_ELEMENTS-2);d)printf("%d\n",array[d…

Databend 开源周报 第 75 期

Databend 是一款强大的云数仓。专为弹性和高效设计。自由且开源。即刻体验云服务&#xff1a;https://app.databend.com 。 What’s New 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 Features & Improvements ✨ Format 实现 JSON 输出格式 …

Conan:C/C++的依赖管理

对于新一代的编程语言Java、Python&#xff0c;当编译、运行时需要第三方的库&#xff0c;可以用语言自带的工具&#xff0c;下载这些依赖库&#xff0c;级联依赖的库也会被下载&#xff0c;部署时也会去下载或者用之前下载的。 而C/C编译、运行时&#xff0c;可以链接操作系统…