• 【Linux】C文件系统详解(三)——如何理解缓冲区以及自主封装一个文件接口


    如何理解缓冲区

    以前写过一个进度条, 有一个输出缓冲区->这个缓冲区在哪里,为什么要存在
    struct file [缓冲区]中的缓冲区与上面这个缓冲区有关系吗

    1.先看现象->提出问题
    2.提出文件缓冲区
    3.解释问题

    现象

    int main()
    {
    	//C库
    	fprintf(stdout,"hello fprintf\n");
    	//系统调用
    	const char* msg = "hello write\n";
    	write(1,msg,strlen(msg));//这里不用加上\0
    	
    	fork();
    	return 0
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这样可以打印出来

    hello fprintf
    hello write
    
    • 1
    • 2

    因为此时都是向显示器打印,是采用行缓冲,所以直接就刷新出来的(见下图中的解释)
    但是如果我们重定向:./myfile > log.txt
    结果不一样了:

    hello write
    hello fprintf
    hello fprintf
    
    • 1
    • 2
    • 3

    但是如果不加fork();就不会产生这样的结果.
    因为此时是普通文件,采用的刷新策略是全缓冲
    所以真正的调用顺序应该是:在fork之前,write就直接打印进文件了,但是fwrite只是写在缓冲区中.在fork之后,fwrite的缓冲区中的文件变成了两份(写时拷贝),由此,会出现打印两次的现象.(下图中有解释)

    概念:文件缓冲区

    ![[文件系统 2023-11-16 16.12.27.excalidraw|800]]

    为什么要有缓冲区

    可以节省调用者的时间:系统调用也是要花费大量时间的
    进程可以继续做自己的事情,最后统一刷新

    缓冲区在哪里

    在你进行fopen打开文件的时候,会得到一个FILE结构体,缓冲区就在该结构体中
    而调用write时,是系统调用,没有缓冲区,会直接刷新出来

    自己封装一个简单的文件接口

    自主封装

    目标

    用最简单的方式,呈现出对FILE的理解
    特点:实现的是一个demo版本,重在呈现原理

    代码

    makefile

    myfile:main.c myfile.c
    	gcc -o $@ $^ 
    .PHONY:clean
    clean:
    	rm -f myfile
    
    • 1
    • 2
    • 3
    • 4
    • 5

    myfile.h

    #pragma once
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define NUM 1024
    #define BUFF_NONE 0x1
    #define BUFF_LINE 0x2
    #define BUFF_ALL 0x4
    
    typedef struct MY_FILE
    {
        int fd;
        char outputbuffer[NUM];
        int flags; // 刷新方式
        int current;
    } MY_FILE;
    
    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);
    int my_fclose(MY_FILE *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

    myfile.c

    #include "myfile.h"
    
    MY_FILE *my_fopen(const char *path, const char *mode)
    {
        int flags = 0;
        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);
        mode_t m = 0666;
        int fd = 0;
        if (flags & O_CREAT)
            fd = open(path, flags, m);
        else
            fd = open(path, flags);
        if (fd < 0)
        {
            perror("open error");
            return NULL;
        }
    
        MY_FILE *mf = (MY_FILE *)malloc(sizeof(MY_FILE));
        if (mf == NULL)
        {
            close(fd);
            return NULL;
        }
        mf->fd = fd;
        mf->flags = 0;
        mf->current = 0;
        mf->flags |= BUFF_LINE;
        // mf->outputbuffer[0] = 0;//初始化缓冲区
        memset(mf->outputbuffer, '\0', sizeof(mf->outputbuffer));
        return mf;
    }
    
    int my_fflush(MY_FILE *fp)
    {
        assert(fp);
        // 将用户缓冲区中的数据,通过系统调用接口,冲刷给OS
        write(fp->fd, fp->outputbuffer, fp->current);
        fp->current = 0;
    
        fsync(fp->fd);
        return 0;
    }
    
    size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MY_FILE *stream)
    {
        // 1. 缓冲区如果已经满了,就直接写入
        assert(stream);
        if (stream->current == NUM)
            my_fflush(stream);
    
        // 2. 根据缓冲区剩余情况,进行数据拷贝即可
        size_t user_size = size * nmemb;
        size_t my_size = NUM - stream->current;
        size_t writen = 0;
        if (my_size >= user_size)
        {
            memcpy(stream->outputbuffer + stream->current, ptr, user_size);
            //3. 更新计数器字段
            stream->current += user_size;
            writen = user_size;
        }
        else
        {
            memcpy(stream->outputbuffer + stream->current, ptr, my_size);
            //3. 更新计数器字段
            stream->current += my_size;
            writen = my_size;
        }
    
        // 4. 开始计划刷新, 他们高效体现在哪里 -- TODO
        // 不发生刷新的本质,不进行写入,就是不进行IO,不进行调用系统调用,所以my_fwrite函数调用会非常快,数据会暂时保存在缓冲区中
        // 可以在缓冲区中积压多份数据,统一进行刷新写入,本质:就是一次IO可以IO更多的数据,提高IO效率
        if (stream->flags & BUFF_ALL)
        {
            if (stream->current == NUM)
                my_fflush(stream);
        }
        else if (stream->flags & BUFF_LINE)
        {
            if (stream->outputbuffer[stream->current - 1] == '\n')
                my_fflush(stream);
        }
        return writen;
    }
    
    int my_fclose(MY_FILE *fp)
    {
        assert(fp);
        // 1.关闭文件的时候,C要帮助我们进行冲刷缓冲区
        if (fp->current > 0)
        {
            my_fflush(fp);
        }
        // 2.关闭文件
        close(fp->fd);
        // 3.释放堆空间
        free(fp);
        // 4.指针置为NULL
        fp = NULL;
        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
    • 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
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107

    关于缓冲区

    1.历史上我们所谈的缓冲区指的是:用户级缓冲区,语言提供
    2.用户层+内核->强制刷新内核

    ![[文件系统 2023-11-17 10.15.39.excalidraw|900]]

    强制刷新内核

    fsync(fp->fd);
    
    • 1

    关于字符串格式化函数

    printf和scanf函数

    int my_printf(const char* format,...)
    {
    	//1.先获取对应的变量a
    	//2.定义缓冲区,对a转成字符串
    	//2.1 fwrite(stdout,str);
    	//3.将字串拷贝的stdout->buffer即可
    	//4.结合刷新策略显示即可
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    ![[Pasted image 20230325111337.png]]
    ![[Pasted image 20230325111513.png]]
    完结.

  • 相关阅读:
    高效的 Json 解析框架 kotlinx.serialization
    基于Canal实现MySQL 8.0 数据库数据同步
    为什么?Mybatis的一级和二级缓存都不建议使用?
    栈(Stack)和队列(Queue)
    HTML,CSS实现鼠标划过头像,头像突出变大(附源码)
    油管125万粉顶级交易员_教你使用macd
    130. 如何使 SAP UI5 SmartField 在运行时渲染成超链接的形式并支持跳转
    ASP.NET WebForm--事件
    Java 21 新功能展示(含示例)
    视频剪辑大师:批量快进慢放,让你的视频瞬间生动起来!
  • 原文地址:https://blog.csdn.net/weixin_62712120/article/details/134466968