【Linux】缓冲区理解

需要云服务器等云产品来学习Linux的同学可以移步/–>腾讯云<–/官网,轻量型云服务器低至112元/年,优惠多多。(联系我有折扣哦)

文章目录

  • 1. 一个奇怪的现象
  • 2. 为什么要有缓冲区
  • 3. 缓冲区的刷新策略
  • 4. 缓冲区在哪里
  • 5. 实现一个简易的C语言FILE结构体
  • 6. 缓冲区理解

1. 一个奇怪的现象

#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{printf("hello printf\n");fprintf(stdout, "hello fprintf\n");fputs("hello fputs\n", stdout);const char* msg = "hello write\n";write(1, msg, strlen(msg));fork();return 0;
}

image-20231228102820809

这个运行结果看起来是没有任何问题的。那么现在我们把运行结果进行一次重定向

image-20231228102917709

明显看到打印出来的数据变多了,这是什么原因呢?

这里我们把fork给去掉试试?

image-20231228104824676

所以可以推测出来一定是fork的问题。

仔细观察可以发现,出现重复打印的是C语言的接口,所以应该是C语言的问题

这里面实际上是一个C语言缓冲区的问题,接下来我们来了解一下这个C语言缓冲区的相关内容

2. 为什么要有缓冲区

1. 缓冲区是什么?

缓冲区本质上就是一段内存 在内存中提前预留一段空间,用来存储即将输入或者输出的内容,这一段预留的空间就是缓冲区。

2. 为什么要有缓冲区?

我们知道内存相对于外设来说是一个高速设备,如果每次IO都要从外设读取或者直接写到外设,这种等待的时间太长了(相对来说),所以就干脆预留一段空间,将所有需要写入到外设的数据拷贝到内存的某个空间(缓冲区),然后由操作系统决定什么时候真正写入到外设。这种方式能够很大的提高效率。这个拷贝数据的过程我们并不需要手动实现,而是通过fwrite来实现,那么实际上这个fwrite的本质就是一个拷贝功能的函数,fread同理

image-20231229165745379

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

3. 缓冲区的刷新策略

上文中我们说到,缓冲区的数据什么时候真正写到磁盘中是由操作系统决定的,那么操作系统将缓冲区的内容写到磁盘中遵循什么样的策略呢?

缓冲区的刷新策略:

1. 立即刷新(无缓冲)缓冲区中一出现数据就刷新到外设中——这种很少出现

2. 行刷新(行缓冲)——数据按行刷新,每拷贝一行数据就刷新一次,显示器采用的就是这种刷新策略,因为显示器是给人看了,而按行刷新符合人的阅读习惯,同时刷新效率也不会太低

3. 缓冲区满刷新(全缓冲)——待数据把缓冲区填满后再刷新,这种刷新方式效率最高,一般应用于磁盘文件

两种特殊情况:

  • 用户使用 fflush 等函数强制进行缓冲区刷新;
  • 一般进程推出后缓冲区会被刷新

4. 缓冲区在哪里

把思绪回到文章开头的那个现象中,有了现在的知识储备,我们再来思考一下这个现象:加上fork之后,向显示器上打印的数据有四条,向文件中写入的数据有七条,那么肯定是有三条数据在缓冲区中没有被刷新出来。注意:这三条数据是C语言提供的接口产生的,系统调用的接口并没有出现这种现象,所以这个缓冲区肯定是在语言层的

实际上,C语言封装了一个FILE结构体,在FILE结构体中,除了我们之前说的fd之外,还维护了一个缓冲区

我们来找一找FILE结构体的源码

/usr/include/stdio.h

image-20231229173024363

/usr/include/libio.h

image-20231229173201392

所以现在我们再来解释一下这个奇怪的现象:

  • 一般C库函数写入文件的时候是全缓冲的,但是写入到显示器是行缓冲
  • printf fwrite库函数是自带缓冲区的,当发生了重定向之后,写入显示器的行缓冲就会变成全缓冲
  • 所以我们放入缓冲区的数据不会被立刻刷新,直到fork之后,这个缓冲区也被拷贝了两份
  • 进程退出之后,所有的缓冲区数据会被刷新,写入到文件中,包括子进程的缓冲区,所以就有了两份数据
  • 由于这个缓冲区是语言层的,write系统调用并没有这个缓冲区,所以write的数据不会被拷贝两份

简单总结来说:重定向导致刷新策略发生了改变(由行缓冲变成了全缓冲)。同时发生了写时拷贝,缓冲区的数据变成了两份一样的,父子进程各自刷新,出现重复写入同一份数据。

5. 实现一个简易的C语言FILE结构体

注意,这里实现的只是demo级的FILE,当然会有很多bug,理解其中意思就行。

/* myStdio.h */
#pragma once#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>#define SIZE 1024 // 缓冲区容量// 缓冲区刷新策略
#define SYNC_NOW 1
#define SYNC_LINE 2
#define SYNC_FULL  4typedef struct _FILE //定义的FILE结构体
{int fileno;           //文件描述符int flag;             //缓冲区刷新方式char buffer[SIZE];    //缓冲区int cap;              //缓冲区容量int size;             //缓冲区写入的数据大小
}FILE_;//这里我们自己实现的函数和结构体在后面加了下划线
FILE_ *fopen_(const char* path_name, const char* mode);
void fclose_(FILE_* fp);
void fflush_(FILE_* fp);
void fwrite_(const void *ptr, int num, FILE_* fp);
/* myStdio.c */
#include "myStdio.h"
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{//do nothing}//按照不同方式打开文件并记录文件的fdint fd = 0;if(flags & O_RDONLY) // 只读fd = open(path_name, flags);else //可能要创建fd = open(path_name, flags, defaultMode);if(fd < 0){//文件打开失败char* str = strerror(errno);write(2, str, strlen(str));return NULL;}//文件打开成功,构造FILE*对象并填充内容FILE_* fp = (FILE_*)malloc(sizeof(FILE_));if(fp == NULL){char* str = strerror(errno);write(2, str, strlen(str));return NULL;}fp->fileno = fd;fp->cap = SIZE;fp->size = 0;memset(fp->buffer, 0, SIZE);fp->flag = SYNC_LINE;return fp;
}void fflush_(FILE_* fp)
{//如果缓冲区有内容的话执行写入if(fp->size > 0){write(fp->fileno, fp->buffer, fp->size);fp->size = 0;}
}void fclose_(FILE_* fp)
{//刷新缓冲区fflush_(fp);//关闭文件close(fp->fileno);//释放FILE结构体free(fp);fp = NULL;
}
void fwrite_(const void *ptr, int num, FILE_* fp)
{//写到缓冲区(本质上是拷贝,吧数据从ptr中拷贝到fp->buffer)memcpy(fp->buffer + fp->size, ptr, num);fp->size += num;//执行刷新策略if(fp->flag & SYNC_NOW) //无缓冲{if(fp->size != 0){write(fp->fileno, fp->buffer, fp->size);fp->size = 0;//清空缓冲区}}else if(fp->flag & SYNC_LINE) // 行缓冲{if(fp->buffer[fp->size - 1] == '\n') //这里不考虑 abcd\nefg{write(fp->fileno, fp->buffer, fp->size);fp->size = 0;//清空缓冲区}}else if(fp->flag & SYNC_FULL) // 全缓冲{if(fp->size == fp->cap){write(fp->fileno, fp->buffer, fp->size);fp->size = 0;//清空缓冲区}}else{//do nothing}
}
/* mian.c */
#include "myStdio.h"int main()
{FILE_* fp = fopen_("./log.txt", "w");if(fp == NULL){return 1;}const char* msg = "hello world\n";fwrite_(msg, strlen(msg), fp);fclose_(fp);return 0;
}

image-20231229205922559

6. 缓冲区理解

实际上我们上文中一直在讲的是都是语言层面的缓冲区,但是**操作系统也是有缓冲区的** 。

我们在把一串信息写入到外设(磁盘)中的时候,经历了以下过程

  1. 通过fputs、fprintf、fwrite等函数把massage写入到**FILE结构体中的缓冲区 **;
  2. 通过系统调用write将FILE结构体中的缓冲区内容写入到OS内核缓冲区中;
  3. OS决定什么时候将OS内核的数据真正写入到磁盘中

那么,出现一个问题,如果在第二步结束之后,操作系统宕机了,怎么办?

可能会导致数据丢失。

那么如果在一个对数据丢失0容忍的系统内出现这个问题咋办?

有一个系统调用可以解决这个问题:fsync

image-20231229211135222

这个系统调用的功能就是:强制将内核缓冲区中的数据立刻同步到外设中,而不再采用操作系统的刷新策略

所以,我们上文中实现的fflush_函数事实上得加上这句话

void fflush_(FILE_* fp)
{//如果缓冲区有内容的话执行写入if(fp->size > 0){write(fp->fileno, fp->buffer, fp->size);fp->size = 0;fsync(fp->fileno);}
}

本节完…

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

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

相关文章

面试题:说一下Spring 中的 @Cacheable 缓存注解?

文章目录 1 什么是缓存2 本地缓存和集中式缓存3 本地缓存的优点4 Spring对于缓存的支持4.1 spring支持的CacheManager4.2 GuavaCache4.3 引入依赖4.4 创建配置类4.5 缓存注解4.6 Cacheable的用法 5 Cacheable失效的原因 1 什么是缓存 第一个问题&#xff0c;首先要搞明白什么是…

详解“量子极限下运行的光学神经网络”——相干伊辛机

量子计算和量子启发计算可能成为解答复杂优化问题的新前沿&#xff0c;而经典计算机在历史上是无法解决这些问题的。 当今最快的计算机可能需要数千年才能完成高度复杂的计算&#xff0c;包括涉及许多变量的组合优化问题&#xff1b;研究人员正在努力将解决这些问题所需的时间缩…

VScode的入门手册(IDEA迁移到VScode)

从IDEA迁移到VScode的过程中&#xff0c;会有很多不适应的地方&#xff0c;下面算是一篇VScode的入门手册&#xff0c;也可以说是从IDEA迁移到VScode的手册。 命令面板&#xff08;Command Palette&#xff09; 允许你快速访问和执行命令。 在 Visual Studio Code 中&#x…

最新Jasmine博客模板:简洁美观的自适应Typecho主题

Jasmine是一个专为博客类网站设计的Typecho主题。它以简洁为基础&#xff0c;力求展现出精致而美观的风格。主题采用了响应式设计&#xff0c;即使在移动设备上也能提供良好的使用体验。此外&#xff0c;主题还进行了针对性的优化&#xff0c;包括SEO、夜间模式和代码高亮等方面…

ES的使用(Elasticsearch)

ES的使用&#xff08;Elasticsearch&#xff09; es是什么&#xff1f; es是非关系型数据库&#xff0c;是分布式文档数据库&#xff0c;本质上是一个JSON 文本 为什么要用es? 搜索速度快&#xff0c;近乎是实时的存储、检索数据 怎么使用es? 1.下载es的包&#xff08;环境要…

三台CentOS7.6虚拟机搭建Hadoop完全分布式集群(二)

这个是笔者大学时期的大数据课程使用三台CentOS7.6虚拟机搭建完全分布式集群的案例&#xff0c;已成功搭建完全分布式集群&#xff0c;并测试跑实例。 6.安装JDK 以下操作现在master上操作&#xff0c;然后远程复制到slave01、slave02即可。 6.1 将压缩包发送到master节点机…

通过AWS Endpoints从内网访问S3

AWS S3作为非结构化数据的存储&#xff0c;经常会有内网中的app调用的需求。S3默认是走公网访问的&#xff0c;如果内网app通过公网地址访问S3并获取数据会消耗公网带宽费用。如下图所示&#xff1a; AWS 提供了一种叫做endpoints的资源&#xff0c;这种资源可以后挂S3服务&a…

【Maven】linux部署maven

简介 最近学习hyperledger-fabric超级账本&#xff08;区块链&#xff09;&#xff0c;需要使用到java和maven&#xff0c;所以重新学习了一下如何部署maven&#xff0c;这里附上参考文档。在附上官方网站的下载地址&#xff1a;https://maven.apache.org/download.cgi。首先去…

WPF+Halcon 培训项目实战(7):目标匹配助手

前言 为了更好地去学习WPFHalcon&#xff0c;我决定去报个班学一下。原因无非是想换个工作。相关的教学视频来源于下方的Up主的提供的教程。这里只做笔记分享&#xff0c;想要源码或者教学视频可以和他联系一下。 相关链接 微软系列技术教程 WPF 年度公益课程 Halcon开发 CSD…

Kindle使用USB数据线传书封面无法显示问题

以下内容只针对USB传书&#xff08;非越狱版本&#xff0c;越狱了有相关插件&#xff0c;这里不谈&#xff09;&#xff0c;不包括邮件传书。 恶心图如下&#xff1a; 直接把mobi/azw3/azw &#xff08;epub模式不能直接拷贝&#xff0c;kindle无法读取&#xff09;格式的电子…

STL——stack容器

1.stack基本概念 概念&#xff1a;stack是一种先进后出&#xff08;First In Last Out,FILO&#xff09;的数据结构&#xff0c;它只有一个出口。 栈中只有顶端的元素才可以被外界使用&#xff0c;因此栈不允许有遍历行为。 栈中进入数据称为——入栈&#xff08;push&#x…

在markdown中添加视频的两种方法

查看专栏目录 Network 灰鸽宝典专栏主要关注服务器的配置&#xff0c;前后端开发环境的配置&#xff0c;编辑器的配置&#xff0c;网络服务的配置&#xff0c;网络命令的应用与配置&#xff0c;windows常见问题的解决等。 文章目录 方式一源代码: 方式二结尾语网络的梦想 markd…

提升数据库性能的关键指南-Oracle AWR报告

文章目录 一、了解AWR报告&#xff1a;数据库性能的仪表盘二、生成AWR报告三、解读AWR报告的关键部分1.报告开头的系统基础信息2.ADDM发现3.负载概览(Load Profile)4.参数文件5.顶级前台等待事件6.SQL 统计信息-顶级SQL7.SGA Advisory AND PAG Advisory 一、了解AWR报告&#x…

如何理解Go语言的数组

什么是数组 首先下一个定义&#xff0c;数组是对线性的内存区域的抽象。高维数组和一维数组有着同样的内存布局。&#xff08;大学生考试的时候别借鉴哈&#xff0c;这是自己下的定义&#xff0c;相当于是一篇议论文的论点。&#xff09; 线性的内存区域说白了就是连续的内存…

Mac Pycharm在Debug模式报编码(SyntaxError)错误

1. 错误信息&#xff1a; Traceback (most recent call last):File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/tokenize.py", line 330, in find_cookieline_string line.decode(utf-8) UnicodeDeco…

Vue(二):计算属性与 watch 监听器

03. Vue 指令拓展 3.1 指令修饰符 可以通过 . 来指明一些指令的后缀&#xff0c;不同的后缀中封装了不同的操作&#xff0c;可以帮助我们简化代码&#xff0c;比如之前使用过的监听 enter 键的弹起&#xff0c;我们需要操作事件对象&#xff0c;来检测用户使用了哪个键&#…

无需翻墙|Stable Diffusion WebUI 安装|AI绘画

前言 最近终于有机会从围墙里往外看&#xff0c;了解到外面的世界已经有了天翻地覆的变化&#xff0c;感叹万千&#xff0c;笔者在本地mac&#xff0c;windows&#xff0c;linux&#xff0c;docker部署了不下20遍后&#xff0c;整理出来的linux极简避坑安装方案&#xff0c;供…

4. 云原生之kubesphere基础服务搭建

文章目录 安装kubesphere插件服务暴露NodePort方式LoadBalancer方式安装 OpenELB部署eip资源配置网关启动网关创建路由测试网关路由ingress高级功能在服务中配置LoadBalancer 基础设施部署服务部署建议helm仓库添加helm仓库 运维相关部署gitlab部署nexus3部署harbor 研发相关 安…

回归预测 | MATLAB实ZOA-LSTM基于斑马优化算法优化长短期记忆神经网络的多输入单输出数据回归预测模型 (多指标,多图)

回归预测 | MATLAB实ZOA-LSTM基于斑马优化算法优化长短期记忆神经网络的多输入单输出数据回归预测模型 &#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实ZOA-LSTM基于斑马优化算法优化长短期记忆神经网络的多输入单输出数据回归预测模型 &#xff08;…

ROS仿真R2机器人之安装运行及MoveIt的介绍

R2(Robonaut 2)是NASA美国宇航局与GM通用联合推出的宇航人形机器人&#xff0c;能在国际空间站使用&#xff0c;可想而知其价格是非常昂贵&#xff0c;几百万美刀吧&#xff0c;还好NASA发布了一个R2机器人的Gazebo模型&#xff0c;使用模型就不需要花钱了&#xff0c;由于我们…