Linux——文件缓冲区与模拟实现stdio.h

news2024/12/28 21:52:18

前言

我们学习了系统层面上的文件操作,也明白了重定向的基本原理,在重定向中,我们使用fflush(stdout)刷新了缓冲区,当时我们仅仅知道重定向需要刷新缓冲区,但是不知道其所以然,今天我们来见识一下。

一、缓冲区

缓冲区的本质就是内存的一部分,C标准库提供了他的缓冲区,操作系统也有自己的缓冲区,我们绝大部分使用的缓冲区是C/C++的缓冲区

大家看如下代码,理解一下缓冲区的作用

明明代码中hello linux在前面,休眠在后面,按道理应该是先打印内容,再进行休眠才对,这里等到睡眠结束才打印出来结果。这就是缓冲区在从中起作用。

举个例子

我在重庆,我的朋友在四川,当我想要给他一箱娃哈哈AD钙奶的时候, 如果我开车屁颠屁颠的送过去,确保再他手上之后再返回,效率就会十分低下,如果我将AD钙奶给菜鸟驿站,让菜鸟驿站帮我送过去,虽然时间没有变快,但是效率肯定变高了(驿站会存很多快递一起往四川寄过去)

其中,菜鸟驿站就是缓冲区,缓冲区的作用就是提高使用者的效率,同时因为有缓冲区的存在,我们积累了一部分再统一发送,这也变相的提高了发送的效率

二、缓冲区的刷新方式

缓冲区能够暂存数据,那么他肯定有相应的刷新策略

一般策略

        1.无缓冲(立即刷新)

        2.行缓冲(行刷新)

        3.全缓冲(缓冲区满了,再刷新)

特殊情况

        1.用户输入fflush(),强制刷新。

        2.进程退出的时候,也要进行刷新缓冲区

对于一般显示器文件,采用行缓冲的策略

对于磁盘上的文件,采用全缓冲的策略

三、缓冲区的样例

执行,打印出结果,我们选择重定向到文件中,发现打印顺序不一样。 

 我们在最后面添加上fork,按道理打印应该跟之前一样

这里打印到显示器是正常现象,重定向到文件中竟然超级加倍了 

明明fork在最后,fork之后没有代码需要执行的,为什么C提供的打印会打印双份,而系统调用依然是只有一个。 

我们一步步分析

1.显示器文件的刷新策略是行刷新,我们在显示器上打印都是带了‘\n’,fork之前,数据已经全部被刷新,包括系统调用接口。

2.重定向到log.txt,本质是向磁盘进行写入,刷新方式由行刷新变成了全缓冲。

3.全缓冲意味着缓冲区变大,写入的数据不足以将缓冲区填满,fork执行后,数据依然在缓冲区中,没有刷新出去

4.这里谈到的缓冲区,是C语言提供的缓冲区,跟操作系统没关系(因为系统调用打印并没有加倍,C加倍了,一定跟C脱不了关系)

5.C/C++提供的缓冲区,里面一定保存了用户的数据,这属于当前进程自己的数据,如果缓冲区刷新,将数据交给了操作系统,就由操作系统来帮我们处理了,不属于当前进程了。

6.当进程退出时,一般都要强制刷新缓冲区。

根据上述内容,我们小总结一下

fork之后,数据仍在C语言的缓冲区中,属于当前进程自己的数据,那么fork之后,父子进程代码和数据共享,同时父子进程陆续退出,退出时会刷新缓冲区(将数据给操作系统——也算写入),就会发生写时拷贝,父给操作系统一份,子也会给操作系统一份,因此会打印两份内容!!!

而write系统调用,没有使用C语言的缓冲区,直接写入到操作系统中,并不属于该进程,因此不会写时拷贝。


那么你兜兜转转说了这么多,还没说什么叫刷新?为什么重定向到文件中,write先行打印?

从C语言缓冲区写入到操作系统内部称为刷新,而write函数,根本没有经过C语言缓冲区,直接在操作系统内部,因此重定向时 write 先打印!!!

C语言搞缓冲区出来就是为了提高效率,C语言追求的就是效率,姿势可以不帅,但速度一定要快,写入到C语言缓冲区速度要比写入到操作系统快多了,写入完成直接返回运行后续代码了,直到写满了缓冲区或者进程退出时,一起将缓冲区的内容再写到操作系统中。

而后续操作系统中给每一个文件也设置了文件缓冲区,操作系统再根据他自己的刷新策略进行刷新,写入到磁盘中,和用户已经没关系了。

四、C语言缓冲区在哪

说了这么多,我也知道缓冲区确实存在,那他到底在哪里呢?

答案是在FILE结构体里,我们输入输出都要有一个FILE,像printf函数,虽然传参不需要FILE,但他内部一定封装了

比如,当我们进行scanf输入的时候,我们输入的字符都会被暂存到stdin的缓冲区中,再根据输入的%d或者其他格式,帮我们转化。

五、简易的stdio.h

我们写简单一点,目的是帮助我们更好理解缓冲区和重定向,只需要四个函数,fopen,fclose,fprintf,fflush。

1.mystdio.h

那么mystdio.h头文件如下,MyFile结构体中,有fileno,flag,buffer,end。

2.mystdio.c

my_fopen函数,首先先判断方式为“r”、“w”、“a”,还是其他,如果flag中有O_CREAT,则open打开文件需要添加权限掩码。malloc开辟MyFile空间,同时初始化一下。

 my_fflush函数刷新,使用write写到操作系统中

my_fwrite函数,先使用memcpy从s里面把数据拷贝到buffer(不用strcpy是不拷贝"\0"),判断刷新方式与end大小,看看是否需要刷新,循环遍历‘\n’,防止出现str = "abcd\nefg"的情况。发现 '\n' 就先处理end大小,再退出循环开始刷新,后面再将str中\n后面的数据拷贝回buffer的起始位置,再处理end。

 my_fclose函数简单,退出前先刷新一下就可以了,再调用close。

3.main.c

结果如下,每次遇到 '\n' 都会进行缓冲区的刷新。 因此总是在hello后面进程刷新

通过模拟的方式能帮我们更好理解缓冲区的概念。

最后附上总代码

mystdio.h

#pragma once

#define SIZE 4096
#define FLUSH_NONE 1
#define FLUSH_LINE 2
#define FLUSH_ALL 4
typedef struct _MyFile
{
    int fileno;         //文件描述符
    int flag;           //刷新方式
    char buffer[SIZE];  //缓冲区
    int end;            //缓冲区大小                   
}MyFile;

MyFile *my_fopen(const char* path,const char* mode);

int my_fwrite(const char* s, int num,MyFile *stream);

int my_fflush(MyFile* stream);

int my_fclose(MyFile* stream);

mystdio.c 

 

#include "mystdio.h"    
#include<string.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<unistd.h>    
#include<errno.h>    
#include<stdlib.h>    
MyFile *my_fopen(const char* path,const char* mode)    
{    
    int fd = 0;    
    int flag = 0;    
    if(strcmp(mode,"r")== 0)    
    {    
        flag |= O_RDONLY;    
    }    
    else if(strcmp(mode,"w")==0)    
    {    
        flag |= O_WRONLY|O_CREAT|O_WRONLY;    
    }    
    else if(strcmp(mode,"a")==0)    
    {    
        flag |= O_WRONLY|O_CREAT|O_APPEND;    
    }    
    else{    
        //其他模式      
    }    
    if(flag & O_CREAT)    
    {    
        fd = open(path,flag,0666);    
    }    
    else    
    {    
        fd = open(path,flag);    
    }    
    if(fd < 0)    
    {    
        errno = 2;    
        return NULL;    
    }    
    MyFile* fp = (MyFile*)malloc(sizeof(MyFile));    
    if(!fp)    
    {    
        errno = 3;    
        return NULL;    
    }    
    fp->flag = FLUSH_LINE;    
    fp->end = 0;                                                 
    fp->fileno = fd;    
    return fp;    
}    
int my_fwrite(const char* s,int num,MyFile *stream)    
{
    memcpy(stream->buffer+stream->end,s,num);
    stream->end += num;
    //判断是否需要刷新
    if((stream->flag & FLUSH_LINE)&&stream->end>0)
    {
        int i = 0;
        int len = 0;//记录\n后还有几个字符
        for(;i<stream->end;i++)
        {
            if(stream->buffer[i]=='\n')
            {
                len = stream->end - i - 1;
                stream->end = i+1;
                break;
            }
        }
        my_fflush(stream);
        memcpy(stream->buffer,stream->buffer+i+1,len);
        stream->end = len;
    }
    return num; 
}
int my_fflush(MyFile* stream)
{
    if(stream->end > 0)
    {
        //写到操作系统中
        write(stream->fileno, stream->buffer,stream->end);
        
        //内核级别的文件缓冲区刷新 
        fsync(stream->fileno); 
    }
    return 0;
}
int my_fclose(MyFile* stream)
{
    my_fflush(stream);
    return close(stream->fileno);
}

main.c

#include"mystdio.h"    
#include<stdio.h>    
#include<string.h>    
#include<unistd.h>    
int main()    
{    
    MyFile* fp = my_fopen("log.txt","w");    
    if(fp==NULL)    
    {    
        perror("my_fopen");    
        return 1;    
    }    
    int cnt = 10;    
    const char *msg = "hello\nlinux";    
    while(cnt--)    
    {    
        my_fwrite(msg,strlen(msg),fp);    
        sleep(1);    
    }    
    
    my_fclose(fp);    
    return 0;                                                    
}

谢谢大家观看!!!! 

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

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

相关文章

vue自定义主题皮肤方案

方案一&#xff1a;CSS变量换肤&#xff08;推荐&#xff09; 利用css定义变量的方法&#xff0c;用var在全局定义颜色变量&#xff08;需将变量提升到全局即伪类选择器 :root&#xff09;然后利用js操作css变量&#xff0c;document.getElementsByTagName(‘body’)[0].style…

nodejs版本过高导致vue-cli项目无法正常运行解决方案

95% emitting CompressionPlugin ERROR Error: error:0308010C:digital envelope routines::unsupported 方法一&#xff1a;在使用 npm run dev之前使用 set NODE_OPTIONS--openssl-legacy-provider Error: error:0308010C:digital envelope routines::unsupported 解决方法…

UI自动化、性能、API测试一体平台:RunnerGo

UI自动化测试已经成为现代软件开发过程中不可或缺的一部分。它能够提供诸多优势&#xff0c;包括提高测试效率、减少人力成本、提升软件质量等。同时&#xff0c;可视化工具为UI自动化测试带来了更多便利和灵活性。RunnerGo近期上线脚本录制器&#xff0c;根据你的测试操作直接…

【基础计算机网络2】物理层——通信基础

【前言回顾】 【考纲内容】 一、物理层的基本概念 1.1 物理层的主要任务 物理层解决如何在连接各种计算机的传输媒体上传输数据比特流&#xff0c;而不是指具体的传输媒介。物理层的主要任务&#xff1a;确定与传输媒体接口有关的一些特性。 1.2 物理层的一些特性 机械特性…

前端报错404,nginx正常、gateway没有转发请求

问题描述&#xff1a;前端报错 404 Not Found 原因&#xff1a;nacos中对应服务没有上线&#xff0c;下线后&#xff0c;可以启动本地服务&#xff0c;然后在测试上调试代码。&#xff01;&#xff01; 记住重启对应服务&#xff0c;也不会自动上线。

STM32CubeMX 配置 STM32F103 工程:通过DAC输出正弦波

说明&#xff1a;STM32CubeMX 配置 STM32F103 工程&#xff0c;通过DAC输出正弦波&#xff0c;参考代码可自动计算频率&#xff0c;自动计算正弦数据。 先参考这篇文章配置时钟、工程输出的设置&#xff1a; STM32CubeMX 配置 STM32F103 工程&#xff1a;通过DAC生成三角波、…

基于PLC的城市智能交通灯三路口控制系统设计26.06

摘要 可编程控制器(Programmable Logic Controller)简称PLC。是一种综合了通讯、自动控制和计算机技术发展而来的主要用于工业生产的自动控制系统。PLC运用数字运算操作系统&#xff0c;采用可编程序存储器&#xff0c;来于储存内部程序。执行逻辑运算、定时控制、顺序控制、计…

Elastic Stack--08--SpringData框架

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 SpringData[官网&#xff1a; https://spring.io/projects/spring-data](https://spring.io/projects/spring-data) Spring Data Elasticsearch 介绍 1.SpringData-…

Python速度大比拼:与主流编程语言的速度对决

在评估用于具体业务的编程语言时&#xff0c;经常考虑的一个关键指标之一是执行速度。Python以其简单性和可读性而闻名&#xff0c;但有时却因其性能而受到质疑。在这个领域&#xff0c;我们需要深入比较分析Python在执行速度方面与其他流行的编程语言相比的情况。 程语言执行速…

lucky-canvas实现老虎机、九宫格和大转盘抽奖

lucky-canvas是一款开源免费的基于 jscanvas 的前端插件&#xff0c;UI精美&#xff0c;功能强大&#xff0c;使用起来比较方便。 lucky-canvas官网https://100px.net/ 一、使用 注意&#xff1a;下例是vue中的应用&#xff0c;具体还有js和uniapp中的应用&#xff0c;详细查…

pandas plot函数:数据可视化的快捷通道

一般来说&#xff0c;我们先用pandas分析数据&#xff0c;然后用matplotlib之类的可视化库来显示分析结果。而pandas库中有一个强大的工具--plot函数&#xff0c;可以使数据可视化变得简单而高效。 1. plot 函数简介 plot函数是pandas中用于数据可视化的一个重要工具&#xff0…

2.ElasticSearch 高级查询语法Query DSL实战

1. ES高级查询Query DSL ES中提供了一种强大的检索数据方式,这种检索方式称之为Query DSL&#xff08;Domain Specified Language 领域专用语言&#xff09; , Query DSL是利用Rest API传递JSON格式的请求体(RequestBody)数据与ES进行交互&#xff0c;这种方式的丰富查询语法让…

药业“钉”上云端:与钉钉共舞数字化新时代

在信息化、智能化的时代背景下&#xff0c;药业行业的经营管理面临着巨大的机遇与挑战。一家药业公司经营范围广泛&#xff0c;拥有各种传统药物配方&#xff0c;同时现代化的新物流线和ERP系统支持公司能够更好的运营。该药业公司与无雀科技商谈后&#xff0c;决定与钉钉平台合…

<Linux> 初识线程

目录 前言&#xff1a; 一、什么是线程 &#xff08;一&#xff09;基本概念 &#xff08;二&#xff09;线程理解 &#xff08;三&#xff09;线程与进程的关系 &#xff08;四&#xff09;简单实用线程 &#xff08;五&#xff09;重谈虚拟地址空间 1. 页表的大小 2…

信息安全、网络安全以及数据安全三者之间的区别

随着信息技术的飞速发展&#xff0c;网络安全、信息安全、数据安全等词汇在平时出现的频率越来越高&#xff0c;尤其是数据安全&#xff0c;是大家都关心的一个重要话题。事实上&#xff0c;有很多人对网络安全、信息安全、数据安全的概念是区分不清的&#xff0c;下面由我帮大…

Yolov8-pose关键点检测:特征融合涨点篇 | 广义高效层聚合网络(GELAN) | YOLOv9

💡💡💡本文独家改进:即结合用梯度路径规划(CSPNet)和(ELAN)设计了一种广义的高效层聚合网络(GELAN),高效结合YOLOv8-pose,实现涨点。 将GELAN添加在backbone和head处,提供多个yaml改进方法 Yolov8-Pose关键点检测专栏介绍:https://blog.csdn.net/m0_6377421…

二维数组的传递和返回

指针和二维数组 指针存储的是内存单元的地址&#xff0c;当使用引用运算符 *&#xff0c;或者变址运算符 [ ] 时才能将指针所指向的内存单元中的值取出。 指针有两个关键属性&#xff1a; 1.它存储的是内存地址 2.它存储的是什么类型变量的内存地址&#xff0c;这一点非常…

盛元广通粮油质量检测实验室管理系统

近年来对于食品安全问题层出不穷&#xff0c;为提高粮食检测中心管理水平&#xff0c;关系到千千万万的消费者的健康饮食问题&#xff0c;粮油作为老百姓日常生活饮食必需品、消耗品&#xff0c;需从源头上对粮食在本省&#xff08;区、市、县&#xff09;不同粮食品种检测检测…

vulhub靶场-matrix-breakout-2-morpheus

下载&部署 从官网中下载 https://www.vulnhub.com/entry/matrix-breakout-2-morpheus,757/ 下载完成后&#xff0c;在vmware中打开&#xff0c;选择刚刚下载的ova文件 vmware打开文件后需要将刚导入的机器重新启动 再检查下网卡是否是和kali在同一张网卡下就可以开始打靶了…

使用Amazon Bedrock托管的Claude3 学习中国历史

最近被Amazon Bedrock托管的Claude3 刷屏了&#xff0c;那么先简单介绍下什么是Claude 3。 Claude 3是Anthropic 推出了下一代 Claude模型&#xff0c;针对不同用例进行优化的三种先进模型&#xff1a;Claude 3 Haiku、Claude 3 Sonnet 和 Claude 3 Opus&#xff0c;使用户能够…