【Linux】如何理解缓冲区

news2024/11/25 0:30:16

文章目录

  • 📕 看现象
  • 📕 理解本质
  • 📕 模拟文件接口
    • mystdio.h
    • mystdio.c

📕 看现象

如下代码,运行结果如图。

请添加图片描述

  1 #include<sys/types.h>  
  2 #include<sys/stat.h>  
  3 #include<fcntl.h>  
  4 #include<stdio.h>  
  5 #include<unistd.h>  
  6 #define LOG "log.txt"  
  7   
  8 int main()  
  9 {  
 10   fprintf(stdout,"hello fprintf\n");  
 11   const char* msg="hello write\n";  
 12   write(1,msg,sizeof msg);                                                                                                   
 13   return 0;  
 14 } 

在 return 之前,调用 fork() 创建一个子进程,再尝试同样的操作,结果如下。

  1 #include<sys/types.h>
  2 #include<sys/stat.h>
  3 #include<fcntl.h>
  4 #include<stdio.h>
  5 #include<unistd.h>
  6 #include<string.h>
  7 #define LOG "log.txt"
  8 
  9 int main()
 10 {
 11   fprintf(stdout,"hello fprintf\n");
 12   const char* msg="hello write\n";
 13   write(1,msg,strlen(msg));
 14 
 15   fork();                                                                                                                    
 16   return 0;
 17 }

从打印的次数来看,直接执行的都一样。但是重定向的里面, hello fprintf 很明显是打印了两次。

请添加图片描述

📕 理解本质

如下,对于 C 语言的函数库,调用 fprintf() 函数,要依赖于 FILE 对象,而 FILE 对象是由 struct FILE 结构体构建的,这个结构体里面必然要有对应文件的文件描述符
与此同时,struct FILE 里面也有自己的缓冲区,当我们调用 fprintf() 向某个 FILE 对象写入数据的时候,实际上是将数据复制到 FILE 对象自己的缓冲区。

数据放到 FILE 对象的缓冲区之后,C库会结合一定的刷新策略,把缓冲区里面的数据写入操作系统(可以通过 FILE 对象里面的 fd 找到),由操作系统交给文件的缓冲区。刷新策略包括:

  • 无缓冲:不提供缓冲,例如调用 fprintf 的时候,直接就把数据写给操作系统,不写给 C库自己的缓冲区。
  • 行缓冲:数据写到C库自己的缓冲区,只要遇到 ‘\n’ ,就把 ‘\n’ 以及之前的数据,全部写到操作系统里面。
  • 全缓冲:只有把 FILE 对象的缓冲区写满了,才会刷新到操作系统里面。

请添加图片描述
一般而言,显示器的刷新策略是行缓冲。普通文件的刷新策略是全缓冲

在这里,我们要区分两个概念,struct FILE 的缓冲区 和 文件的缓冲区,前者是我们使用 C 语言的 fwrite、fread 函数会用到的,后者是使用系统调用 write、read 的时候用到的。

所以,这就能理解上面的现象了,首先对于write() 而言,它是系统调用,所以没有C库的缓冲区刷新策略。
而 fprintf 是 C 语言封装的库函数,有上面提到的三种刷新策略。

  1 #include<sys/types.h>
  2 #include<sys/stat.h>
  3 #include<fcntl.h>
  4 #include<stdio.h>
  5 #include<unistd.h>
  6 #include<string.h>
  7 #define LOG "log.txt"
  8 
  9 int main()
 10 {
 11   fprintf(stdout,"hello fprintf\n");
 12   const char* msg="hello write\n";
 13   write(1,msg,strlen(msg));
 14 
 15   fork();                                                                                                                    
 16   return 0;
 17 }

对于 ./test ,这是打印到显示器,fprintf 的刷新策略是行缓冲,所以 fork 之前,FILE 对象里缓冲区的数据就已经刷新到文件的缓冲区,然后刷新到到显示器里面了。而 write 是系统调用,直接刷新到文件缓冲区,再刷新到显示器,所以在 fork 之前,文件缓冲区里也没有数据。那么,就会只打印一份

但是,./test > log.txt 是重定向到文件,fprintf 的刷新策略就变成了全缓冲,可是字符串无法把缓冲区写满,那么 fork 之前,数据被写到 FILE 对象的缓冲区里面,还存在那里,没有被刷新到文件里面。fork 之后,子进程继承了父进程的数据,两个进程都要刷新,谁先刷新就发生写时拷贝,另一个进程继续刷新,这样子就刷新了两次!! write 依然是直接把数据写到文件的缓冲区, 然后写入文件。
请添加图片描述

📕 模拟文件接口

mystdio.h

#include<stdio.h>

#define NUM 1024

// 定义缓冲方式
#define BUF_NONE 0x1
#define BUF_LINE 0x2
#define BUF_ALL  0x4



typedef struct _MY_FILE
{
  int fd;
  char BUFFER[NUM]; // 缓冲区
  int current; // 写到BUFFER的哪个位置了
  int flag;   // 什么方式缓冲
}MY_FILE;


int my_fclose(MY_FILE *fp);
int my_fflush(MY_FILE *stream);
MY_FILE *my_fopen(const char *path, const char *mode);
size_t my_fwrite(const void *ptr, size_t size, size_t nmemb,MY_FILE *stream);

mystdio.c

如下,我们调用 write 方法,实际上就是把 struct FILE 缓冲区里面的内容,拷贝到文件的缓冲区,至于数据什么时候从文件中的缓冲区,刷新到磁盘,是由操作系统调度的。
而C语言中的 fwrite 等等,实际上就是对操作系统接口的封装,本质上是将数据写入自己 struct FILE 中的缓冲区,然后按照缓冲区的策略来刷新到文件的缓冲区。

请添加图片描述

#include"mystdio.h"
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<malloc.h>
#include<assert.h>
#include<stdlib.h>


int my_fclose(MY_FILE *fp)
{
  assert(fp);
  // 冲刷缓冲区,这是语言层面上的
  if(fp->current > 0) my_fflush(fp);
  // 关闭文件,close
  close(fp->fd); 
  // free 开辟的 fp
  free(fp);
  fp=NULL;

  return 0;
}

int my_fflush(MY_FILE *stream)
{
  // 把缓冲区的数据,冲刷给操作系统,让操作系统决定刷新策略
  write(stream->fd,stream->BUFFER,stream->current-1);
  stream->current=0;

  return 0;
}

MY_FILE *my_fopen(const char *path, const char *mode)
{
  // 1.识别标志位
  int flag=0; // 打开方式,位图存储
  if(strcmp(mode,"r") == 0) flag|= O_RDONLY;
  else if(strcmp(mode,"w") == 0) flag |= O_CREAT | O_WRONLY | O_TRUNC;
  else if(strcmp(mode,"x") == 0) flag |= O_WRONLY | O_CREAT | O_APPEND;
  else{ // r+  w+ x+ ……

  }

  // 2.打开文件
  mode_t m=0666;  // 默认权限
  int fd=0;
  if(flag & O_CREAT) fd=open(path,flag,m);
  else fd=open(path,flag);

  if(fd < 0) return NULL; // 打开失败

  // 3.构建给用户返回的 MY_FILE 对象,并初始化
  MY_FILE *mf=(MY_FILE*)malloc(sizeof(MY_FILE));
  if(mf == NULL){
    return NULL; // 也是打开文件失败
  }
  mf->fd=fd;
  mf->current=0;
  mf->flag=BUF_LINE; // 行缓冲
  memset(mf->BUFFER,'\0',sizeof(mf->BUFFER));

  // 4.返回
  return mf;
}


// 返回写了多少个字节
size_t my_fwrite(const void *ptr, size_t size, size_t nmemb,MY_FILE *stream)
{
  assert(ptr);
  // 缓冲区满了,直接刷新
  if(stream->current == NUM) my_fflush(stream);

  // 缓冲区没有满,根据缓冲区情况拷贝即可
  size_t freesize=NUM-stream->current; // 剩余字节数
  size_t writesize=size*nmemb;         // 写入字节数
  
  size_t ret=0;

  if(freesize >= writesize) {
    memcpy(stream->BUFFER+stream->current,ptr,writesize);
    stream->current+=writesize;
    ret+=writesize;
  }  
  else{ //写入能写的
    memcpy(stream->BUFFER+stream->current,ptr,freesize);
    stream->current=NUM;
    ret+=freesize;

  }

  // 计划刷新
  if(stream->flag & BUF_ALL){
    if(stream->current == NUM) my_fflush(stream);
  }
  else if(stream->flag & BUF_LINE){
    if(stream->BUFFER[stream->current -1 ] == '\0') my_fflush(stream);
  }
  else{

  }

  return ret;
}

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

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

相关文章

算法强化--分解因数

大家好,今天为大家带来一道题目 链接&#xff1a;https://www.nowcoder.com/questionTerminal/0f6976af36324f8bab1ea61e9e826ef5 来源&#xff1a;牛客网 [编程题]分解因数 热度指数&#xff1a;8605时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒空间限制&#xff1a;…

臻图信息:数字技术推动智慧楼宇开启新模式

近年来&#xff0c;在数字技术的迅速发展下&#xff0c;正在深刻影响着各行各业的发展趋势。现代建筑行业已经随着通信技术、AI 智能技术、计算机技术的发展&#xff0c;向着新的发展模式开始转变。 借助数字孪生技术构建数字化、流程化的物联网平台&#xff0c;新的智能楼宇建…

串口通讯详解

目录 一、串口通讯简介&#xff1a; 二、串口通信基本原理&#xff1a; 三、通信方式 四、串口通信特点 一、串口通讯简介&#xff1a; &#xff08;1&#xff09;串口通讯是指通过串口进行数据传输的一种通讯方式&#xff0c;通过数据信号线、地线等&#xff0c;按位进行传输数…

【Python】实战:生成无关联单选问卷 csv《社会参与评估表》

目录 一、适用场景 二、业务需求 三、Python 文件 &#xff08;1&#xff09;创建文件 &#xff08;2&#xff09;代码示例 四、csv 文件 一、适用场景 实战场景&#xff1a; 问卷全部为单选题问卷问题全部为必填问题之间无关联关系每个问题的答案分数不同根据问卷全…

论文阅读:DLME = Deep Local-flatness Manifold Embedding

Author: Zelin Zang, Siyuan Li, Di Wu and Stan Z Li. 1-4: Westlake University 摘要 流形学习&#xff08;ML, Manifold learning&#xff09;旨在从高维数据中识别低维结构和嵌入&#xff0c;然而我们发现现有工作在采样不足的现实数据集上效果不佳。一般的ML方法对数据结…

C++学习记录——이십 map和set

文章目录 1、setmultiset 2、map3、map::operator[] 1、set vector/list/deque等是序列式容器&#xff0c;map&#xff0c;set是关联式容器。序列式容器的特点就是数据线性存放&#xff0c;而关联式容器的数据并不是线性&#xff0c;数据之间有很强的关系。 它们的底层是平衡…

P1038 [NOIP2003 提高组] 神经网络

题目背景 人工神经网络&#xff08;Artificial Neural Network&#xff09;是一种新兴的具有自我学习能力的计算系统&#xff0c;在模式识别、函数逼近及贷款风险评估等诸多领域有广泛的应用。对神经网络的研究一直是当今的热门方向&#xff0c;兰兰同学在自学了一本神经网络的…

用PyCharm和Anaconda搭建强化学习环境

一些碎语&#xff1a;因为我之前没学习过python&#xff0c;所以搭建这个环境的周期差不多一周&#xff0c;最终搭好了这个又爱又恨的环境&#xff08;这个成语用的多少有点文化沙漠了&#xff09;&#xff0c;这里简单梳理一下搭建环境的完整步骤。 首先下载Anaconda 下载地址…

Java线程间通信方式(3)

前文了解了线程通信方式中的CountDownLatch&#xff0c; Condition&#xff0c;ReentrantLock以及CyclicBarrier&#xff0c;接下来我们继续了解其他的线程间通信方式。 Phaser Phaser是JDK1.7中引入的一种功能上和CycliBarrier和CountDownLatch相似的同步工具&#xff0c;相…

mapbox-gl 移动端(H5)位置共享交互

文章目录 前言逆地理编码&#xff1a;获取周边地点地理编码&#xff1a;查询位置大头针选位位置卡片 前言 分享最近写的一个小demo&#xff0c;功能类似微信小程序端的大头针位置共享功能&#xff0c;需要实现的主要功能包括位置查询、周边地点检索、位置定位等&#xff0c;数…

BUUCTF jarvisoj_level0

小白垃圾做题笔记而已&#xff0c;不建议阅读。。。 这道题感觉主要就是64位程序ebp8 题目中给出了shellcode 我们直接将返回地址覆盖就好。 在main函数中调用了vulnerable_function()函数。 vulnerable函数是一个漏洞函数&#xff1a;(存在缓溢出)&#xff0c;我们只需要将…

html-audio标签样式重写思路

搭配slider 组件 ,利用原生audio的属性和方法重写样式 写个样式.监听url变化 初始化绑定播放, 拖动进度条,拖动音量, 静音按钮等事件 const audioRef ref(null) // 绑定audio标签 const playProcess ref(0) // 进度条绑定的值 const volume ref(1) // 音量绑定的值 const …

C++ STL之vector容器

目录 一、vector容器的介绍 二、vector容器的使用 1.vector的构造函数 2.vector的赋值操作 3.vector的容量与大小 4.vector的插入和删除 5.vector的数据存取 6.vector的互换容器 7.算法模块在vector的应用 ①find算法(std) ②sort算法(std) 一、vector容器的介绍 引…

07 - 进程创建大盘点

---- 整理自狄泰软件唐佐林老师课程 查看所有文章链接&#xff1a;&#xff08;更新中&#xff09;Linux系统编程训练营 - 目录 文章目录 1. 进程创建回顾2. 再论进程创建2.1 思考2.2 vfork()深度分析2.3 vfork()要点分析2.4 fork()的现代优化2.5 编程实验&#xff1a;fork() &…

【安卓源码】Binder机制2 -- addService 流程

0、binder 进程间通信原理 一次完整的 Binder IPC 通信过程通常是这样&#xff1a; 首先 Binder 驱动在内核空间创建一个数据接收缓存区&#xff1b; 接着在内核空间开辟一块内核缓存区&#xff0c;建立内核缓存区和内核中数据接收缓存区之间的映射关系&#xff0c;以及内核中…

PHP入门【1】环境搭建

目录 一&#xff0c;安装appserv组合包 二&#xff0c;运行第一个php程序 一&#xff0c;安装appserv组合包 组合包&#xff1a;将apache&#xff0c;mysql&#xff0c;php等服务器软件和工具安装配置完成后打包处理 组合包大大提高了我们的效率&#xff0c;不需要为配置环境…

Linux服务器出现503 服务不可用错误怎么办?

​  HTTP 503 服务不可用错误代码表示网站暂时不可用。无论您是网站访问者还是管理员&#xff0c;503 页面都很麻烦。尽管该错误表明存在服务器端问题&#xff0c;但对于访问者和网络管理员来说&#xff0c;有一些可能的解决方案。本文将解释Linux服务器出现503 服务不可用错…

PowerShell Studio 2023 Crack

PowerShell Studio 2023 Crack SAPIEN Script Packager为MSI Builder添加了ARM64平台支持。 增加了对Microsoft PowerShell 7.2.11和7.3.4的支持。 WiX工具集已更新到3.14。 PowerShell Studio 2023 Crack是可用的功能最强大、功能最完整的PowerShell集成脚本环境(ISE)之一。更…

通达信VCP形态选股公式,憋了好几天才写出来

VCP形态的英文”Volatility Contraction Pattern”的缩写&#xff0c;意思是“波动收缩形态”。VCP形态是全美交易冠军马克米勒维尼的核心交易模式之一&#xff0c;在其著作《股票魔法师》中有详细介绍。 马克米勒维尼把VCP形态比喻为湿毛巾&#xff0c;拧过一次后仍含水&…

动态链接库的链接和运行

本文对动态链接库的链接和运行进行一个总结&#xff0c;为什么要分开说呢&#xff1f;因为链接通过生成可执行文件并不代表运行时能找到依赖的动态库。这与静态库是不一样的&#xff0c;因为静态库在编译完成后会库会编译到可执行程序中&#xff0c;但是动态链接库则不然&#…