当前位置:首页 » 《我的小黑屋》 » 正文

【Linux】基础IO----理解缓冲区

19 人参与  2024年04月16日 08:31  分类 : 《我的小黑屋》  评论

点击全文阅读


> 作者:დ旧言~
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:理解缓冲区

> 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安!

> 专栏选自:Linux初阶

> 望小伙伴们点赞?收藏✨加关注哟??

?前言

缓冲区大家其实不陌生,像我们使用的 VS2019 编译器这里就有缓冲区,那它到底在哪呢,比如我们打印时的窗口需要我们输入,这里就有缓冲区。其实在输入我们也好奇为什么编译器会等待我们输入,这里就不得不谈我们缓冲区的相关知识,那具体是什么呢?今天我们来解开这层面纱。

⭐主体

学习【Linux】基础IO----理解缓冲区咱们按照下面的图解:

? 认识缓冲区

为什么有缓冲区

概念:

缓冲区 (buffer),它是内存空间的一部分。 也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区,显然缓冲区是具有一定大小的。

理解:

数据如果直接从内存到磁盘,在内存中速度快,但是访问外设效率比较低,那太消耗时间了,属于外设IO,所以缓冲区的意义就是节省进程进行数据IO的时间!进程需要把数据拷贝到缓冲区里:我们并不需要拷贝,而是调用fwrite,与其理解fwrite是写入到文件的函数,倒不如理解fwrite是拷贝函数,将数据从进程拷贝到缓冲区或者外设当中。

图解:

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

缓冲区如何刷新

概念:

缓冲区的刷新策略:如果有一块数据,一次写入到外设(效率最高)vs如果有一块数据,多次少量写入到外设,需要多次IO。缓冲区一定结合具体的设备定制自己的刷新策略

方法:

立即刷新——无缓冲 ,场景较少,比如调用printf直接fflush行刷新——行缓冲——显示器 ,数据的printf带上\n就会立马显示到显示器上。显示器为什么是行缓冲:显示器是外设,进程运行时在内存里的,把数据定期要刷新到外设,显示器设备比较特殊,是给用户来看的,从左到右,所以显示器为了保证刷新效率,并且用户体验良好,所以显示器采用行缓冲,满足用户的阅读体验并且在一定程度上效率不至于太低缓冲区满——全缓冲——磁盘文件,效率最高,只需要一次IO,比如文件读写的时候,直接写到磁盘文件

总结:

但是存在特殊情况:a.用户强制刷新 b,进程退出——一般到要进行缓冲区刷新,所以对于全缓冲,缓冲区满了采取刷新,减少IO次数,提高效率。

缓冲区在哪里呢

缓冲区的位置究竟在哪里???

从上面的例子我们直接往显示器上打印结果为4条,往文件打印为7条,这跟缓冲区有关,同时这也说明了缓冲区一定不在内核中,为什么?如果在内核中write也应该打印两次,write是系统接口。我们之前谈论的所有缓冲区都指的是用户级语言层面提供的缓冲区。这个缓冲区,在stdout,stdin,stderr对应的类型---->FILE*,FILE是一个结构体,里面封装了fd,同时还包括了一个缓冲区!

理解FILE结构体缓冲区:

FILE结构体缓冲区,所以我们直接要强制刷新的时候fflush(文件指针),关闭文件fclose(文件指针),这是因为传进去的文件指针对应的缓冲区。

查看源码来解释FILE结构体:

  

分析:

总结:

所以我们一般所说的缓冲区是语言级别的缓冲区,C语言提供的在FILE结构体里对应的缓冲区。重定向导致刷新策略发生了改变(由行缓冲变成了全缓冲)。同时发生了写时拷贝,父子进程各自刷新

? 引入缓冲器

概念分析:

高速设备与低速设备的不匹配(cpu运算是纳秒,内存是微秒,磁盘是毫秒甚至是秒相差1000倍),势必会让高速设备花时间等待低速设备,我们可以在这两者之间设立一个缓冲区。

缓冲区优点:

可以解除两者的制约关系,数据可以直接送往缓冲区,高速设备不用再等待低速设备,提高了计算机的效率可以减少数据的读写次数,如果每次数据只传输一点数据,就需要传送很多次,这样会浪费很多时间,因为开始读写与终止读写所需要的时间很长,如果将数据送往缓冲区,待缓冲区满后再进行传送会大大减少读写次数,这样就可以节省很多时间。例如:我们想将数据写入到磁盘中,不是立马将数据写到磁盘中,而是先输入缓冲区中,当缓冲区满了以后,再将数据写入到磁盘中,这样就可以减少磁盘的读写次数,不然磁盘很容易坏掉

? 缓冲区答疑

问题一:代码分析

问题抛出:

分析结果:

同样的一个程序,向显示器打印输出4行文本,向普通文件(磁盘上)打印的时候,变成了7行,说明上面测试,并不影响系统接口

C的IO接口是打印了2次的系统接口,只打印了一次

我们最后调用fork,上面的函数已经被执行完了,但不代表数据已经被刷新了。

问题二:缓冲区是谁提供

曾经“我们所谈的缓冲区”,绝对不是由OS提供的,如果是OS同一提供,那么我们上面的代码,表现应该是一样的,而不是C的IO接口打印两次,所以是C标准库提供并且维护的用户级缓冲区

fputs把不是直接把数据直接放进操作系统,而是加载进C标准库的缓冲区中,加载完后自己可以直接返回;如果直接调用的是write接口,则是直接写给OS,不经过缓冲区

C语言提供的接口都是向显示器打印的,刷新策略都是行刷新,那么最后执行fork的时候 —— 一定是函数执行完了 && 数据已经被刷新了(因为都带\n),所以fork执行无意义如你对应的程序进行了重定向 ——> 要向磁盘文件打印 ——> 隐形的刷新策略变成了全缓冲!—— > \n便没有意义了 ——> 函数一定执行完了,数据还没有刷新!! 在当前进程对应的C标准库中的缓冲区中!!

? 设计用户缓冲区

代码如下:

#include <stdio.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <assert.h>#include <stdlib.h>#define NUM 1024struct MyFILE_{    int fd;             //文件描述符    char buffer[1024];  // 缓冲区    int end;            //当前缓冲区的结尾};typedef struct MyFILE_ MyFILE;//类型重命名MyFILE *fopen_(const char *pathname, const char *mode){    assert(pathname);    assert(mode);    MyFILE *fp = NULL;//什么也没做,最后返回NULL    if(strcmp(mode, "r") == 0)    {    }    else if(strcmp(mode, "r+") == 0)    {    }    else if(strcmp(mode, "w") == 0)    {        int fd = open(pathname, O_WRONLY | O_TRUNC | O_CREAT, 0666);        if(fd >= 0)        {            fp = (MyFILE*)malloc(sizeof(MyFILE));            memset(fp, 0, sizeof(MyFILE));            fp->fd = fd;        }    }    else if(strcmp(mode, "w+") == 0)    {    }    else if(strcmp(mode, "a") == 0)    {    }    else if(strcmp(mode, "a+") == 0)    {    }    else{        //什么都不做    }    return fp;}//是不是应该是C标准库中的实现!void fputs_(const char *message, MyFILE *fp){    assert(message);    assert(fp);    strcpy(fp->buffer+fp->end, message); //abcde\0    fp->end += strlen(message);    //for debug    printf("%s\n", fp->buffer);    //暂时没有刷新, 刷新策略是谁来执行的呢?用户通过执行C标准库中的代码逻辑,来完成刷新动作    //这里效率提高,体现在哪里呢??因为C提供了缓冲区,那么我们就通过策略,减少了IO的执行次数(不是数据量)    if(fp->fd == 0)    {        //标准输入    }    else if(fp->fd == 1)    {        //标准输出        if(fp->buffer[fp->end-1] =='\n' )        {            //fprintf(stderr, "fflush: %s", fp->buffer); //2            write(fp->fd, fp->buffer, fp->end);            fp->end = 0;        }    }    else if(fp->fd == 2)    {        //标准错误    }    else    {        //其他文件    }}void fflush_(MyFILE *fp){    assert(fp);    if(fp->end != 0)    {        //暂且认为刷新了--其实是把数据写到了内核        write(fp->fd, fp->buffer, fp->end);        syncfs(fp->fd); //将数据写入到磁盘        fp->end = 0;    }}void fclose_(MyFILE *fp){    assert(fp);    fflush_(fp);    close(fp->fd);    free(fp);}int main() {     close(1);                                                                                     MyFILE *fp = fopen_("./log.txt", "w");     if(fp == NULL)     {       printf("open file error");       return 1;     }        fputs_("one:hello world error", fp);     fputs_("two:hello world error", fp);     fputs_("three:hello world error", fp);     fputs_("four:hello world error", fp);     fclose(fp);   }

?结束语 

       今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力???,回见。


点击全文阅读


本文链接:http://zhangshiyu.com/post/95838.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1