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

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

> 作者:დ旧言~

> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:理解缓冲区

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

> 专栏选自: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行,说明上面测试,并不影响系统接口

  1. C的IO接口是打印了2次的
  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 1024
 
struct 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);
   }


🌟结束语 

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


目录
相关文章
|
2月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
98 0
|
2月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
106 1
Linux C/C++之IO多路复用(aio)
|
4月前
|
缓存 安全 Linux
Linux 五种IO模型
Linux 五种IO模型
|
2月前
|
存储 缓存 固态存储
|
2月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
36 0
Linux C/C++之IO多路复用(poll,epoll)
|
4月前
|
小程序 Linux 开发者
Linux之缓冲区与C库IO函数简单模拟
通过上述编程实例,可以对Linux系统中缓冲区和C库IO函数如何提高文件读写效率有了一个基本的了解。开发者需要根据应用程序的具体需求来选择合适的IO策略。
38 0
|
4月前
|
存储 IDE Linux
Linux源码阅读笔记14-IO体系结构与访问设备
Linux源码阅读笔记14-IO体系结构与访问设备
|
5月前
|
Linux 数据处理 C语言
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
84 0
|
4月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
5月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用