• 【Linux】语言层面缓冲区的刷新问题以及简易模拟实现



    前言

    我们接下来要谈论的是我们语言层面的缓冲区(C,C++之类的),不是我们操作系统内核里面自带的缓冲区,我们每次在打开一个文件的时候,以C语言为例子,C语言会为我们所打开的这个文件分配一块缓冲区,用来缓存我们读写的数据`,这个缓冲区会被放在我们创建的FILE的结构体里面,里面存放着缓冲区的字段和维护信息

    一、缓冲区刷新方法分类

    a.无缓冲–直接刷新

    b.行缓冲–不刷新,直到碰到\n才刷新

    显示器写入一般采用的是行缓冲

    c.全缓冲–缓冲区满了才刷新

    文件写入一般采用的是全缓冲,缓冲区满了或者程序结束的时候刷新

    二、 缓冲区的常见刷新问题

    1.问题

    在这里插入图片描述
    我们将可执行文件内容重定向到log1里面
    在这里插入图片描述
    最后我们发现与C有关的接口被打印了两次,这是什么原因呢?

    之前我们说过,我们朝文件里面写入是全缓冲,也就是等缓冲区满了或者程序结束的时候去刷新,打印两次的都是属于C语言的接口, 其会建立一个语言层面的缓冲区, 我们在fork之前,printf,fprintf,fwrite写入的数据都存放在语言层面的缓冲区,fork之后创建子进程,子进程对父进程的数据内容进行拷贝,因为此时缓冲区为刷新,子进程会连同父进程语言层面缓冲区内容一起拷贝
    所以之后,父子进程语言层面的缓冲区中都存放着相同的数据,在程序结束的时候会对语言层面的缓冲区进行刷新,将其刷新到系统里面的缓冲区,

    若子进程先刷新,因为对父进程数据进行更改了(即清空语言缓冲区),这个时候会发生写实拷贝,之后子进程缓冲区的数据就被刷新到系统缓冲区了。
    父进程同理,也会进行一遍缓冲区的刷新,父子进程都对数据进行了刷新写入系统缓冲区,所以文件里面就会写入两次。

    wirite属于系统接口,调用以后会直接写入到内核缓冲区里面,之后写入硬盘文件中,没有语言层面缓冲区概念,所以只写入文件一次

    2.刷新本质

    用户刷新的本质是通关重定向到文件描述符为1的文件(stdout)+write写入内核缓冲区,FILE对象属于用户不是操作系统,FILE里面的缓冲区属于语言层面的缓冲区(用户级缓冲区),目前我们认为,只要数据刷新到了内核中,数据就可以写入硬件了

    在这里插入图片描述
    这些C接口最后写入内核缓冲区,本质都是调用write的系统接口

    三、模拟实现

    1.Mystdio.h

    #include 
    
    #define SIZE 1024
    
    #define FLUSH_NOW 1//无缓冲
    #define FLUSH_LINE 2//行缓冲
    #define FLUSH_ALL 4//全缓冲
    
    typedef struct IO_FILE{
        int fileno;//文件描述符
        int flag; //刷新方式
        
        char outbuffer[SIZE]; // 简单模拟语言层缓冲区
        int out_pos;//缓冲区当前大小
    }_FILE;
    
    _FILE * _fopen(const char*filename, const char *flag);
    int _fwrite(_FILE *fp, const char *s, int len);
    void _fclose(_FILE *fp);
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2.Mystdio.c

    #include "Mystdio.h"
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define FILE_MODE 0666//文件默认权限
    
     
    _FILE * _fopen(const char*filename, const char *flag)
    {
        assert(filename);
        assert(flag);
    
        int f = 0;//文件的写入方式
        int fd = -1;//文件描述符
        if(strcmp(flag, "w") == 0) {
            f = (O_CREAT|O_WRONLY|O_TRUNC);
            fd = open(filename, f, FILE_MODE);
            //获取文件描述符
        }
        else if(strcmp(flag, "a") == 0) {
            f = (O_CREAT|O_WRONLY|O_APPEND);
            fd = open(filename, f, FILE_MODE);
        }
        else if(strcmp(flag, "r") == 0) {
            f = O_RDONLY;
            fd = open(filename, f);
        }
        else 
            return NULL;
    
        if(fd == -1) return NULL;
    
        _FILE *fp = (_FILE*)malloc(sizeof(_FILE));
        //创建文件指针结构体
        if(fp == NULL) return NULL;
    
        fp->fileno = fd;
        //fp->flag = FLUSH_LINE;
        fp->flag = FLUSH_ALL;
        fp->out_pos = 0;
    
        return fp;
    }
    
     
    int _fwrite(_FILE *fp, const char *s, int len)
    {
        // "abcd\n"
        memcpy(&fp->outbuffer[fp->out_pos], s, len); // 没有做异常处理, 也不考虑局部问题
        fp->out_pos += len;
    
        if(fp->flag&FLUSH_NOW)//无缓冲
        {
            write(fp->fileno, fp->outbuffer, fp->out_pos);
            fp->out_pos = 0;
        }
        else if(fp->flag&FLUSH_LINE)//行缓冲
        {
            if(fp->outbuffer[fp->out_pos-1] == '\n'){ // 不考虑其他情况
                write(fp->fileno, fp->outbuffer, fp->out_pos);
                fp->out_pos = 0;
            }
        }
        else if(fp->flag & FLUSH_ALL)//全缓冲
        {
            if(fp->out_pos == SIZE){
                write(fp->fileno, fp->outbuffer, fp->out_pos);
                fp->out_pos = 0;
            }
        }
    
        return len;
    }
    
    void _fflush(_FILE *fp)//手动刷新缓冲区
    {
        if(fp->out_pos > 0){
            write(fp->fileno, fp->outbuffer, fp->out_pos);
            fp->out_pos = 0;
        }
    }
    
    void _fclose(_FILE *fp)
    {
        if(fp == NULL) return;
        _fflush(fp);
        close(fp->fileno);
        free(fp);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    3.main.c

    #include "Mystdio.h"
    #include 
    
    #define myfile "test.txt"
    
    int main()
    {
        _FILE *fp = _fopen(myfile, "a");
        if(fp == NULL) return 1;
    
        const char *msg = "hello world\n";
        int cnt = 10;
        while(cnt){
            _fwrite(fp, msg, strlen(msg));
            // fflush(fp);
            sleep(1);
            cnt--;
        }
    
        _fclose(fp);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
  • 相关阅读:
    工程师新红利,我国预计到2025年培养20万名这类人才
    Linux环境下C++使用CMakeLists编译运行gRPC最小化独立入门项目
    Java代码审计——WebGoat XSS
    牛客网字节面试算法刷题记录
    java毕业设计慢性病管理mybatis+源码+调试部署+系统+数据库+lw
    程序设计与算法(三)C++面向对象程序设计笔记 第五周 继承
    认识异常【超详细】
    Python 关键字 yield
    【6.26更新】Win11 23H2 22631.3810镜像:免费下载!
    低成本打造头部效果!20w粉也能在B站增长700w播放!
  • 原文地址:https://blog.csdn.net/m0_74774759/article/details/134351406