【Linux】理解缓冲区

这篇具有很好参考价值的文章主要介绍了【Linux】理解缓冲区。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一.引入

linux write 怎么查看fd缓冲区,学好Linux,linux,网络,运维

我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关!

C接口的函数被打印了两次系统接口前后只是打印了一次:和fork函数有关,fork会创建子进程。在创建子进程的时候,数据会被处理成两份,父子进程发生写时拷贝,我们进行printf调用数据的时候,数据写到显示器外设上,就不属于父进程了,数据没被写到显示器上,依旧属于父进程,而调用printf并不一定把数据刷到显示器上,没有被显示本质就是数据没有从内存到外设,所以这份没有被显示的数据依旧属于这进程,当我们去fork的时候,进程退出要刷新缓冲区,此时刷新的过程就是把数据从内存刷新到外设,刷新到外设的同时,也会把程序内部的缓冲区的数据直接清走,这就是写入,跟写时拷贝有关系

对于这个现象的问题我们可以直接往下看👇


二.认识缓冲区

1.为什么

缓冲区的本质就是一段内存。在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。

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

linux write 怎么查看fd缓冲区,学好Linux,linux,网络,运维

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

2.刷新策略

缓冲区的刷新策略:如果有一块数据,一次写入到外设(效率最高)vs如果有一块数据,多次少量写入到外设,需要多次IO

缓冲区一定结合具体的设备定制自己的刷新策略

1.立即刷新——无缓冲 ,场景较少,比如调用printf直接fflush

2.行刷新——行缓冲——显示器 ,数据的printf带上\n就会立马显示到显示器上。显示器为什么是行缓冲:显示器是外设,进程运行时在内存里的,把数据定期要刷新到外设,显示器设备比较特殊,是给用户来看的,从左到右,所以显示器为了保证刷新效率,并且用户体验良好,所以显示器采用行缓冲,满足用户的阅读体验并且在一定程度上效率不至于太低

3.缓冲区满——全缓冲——磁盘文件,效率最高,只需要一次IO,比如文件读写的时候,直接写到磁盘文件

但是存在特殊情况:a.用户强制刷新 b,进程退出——一般到要进行缓冲区刷新

所以对于全缓冲,缓冲区满了采取刷新,减少IO次数,提高效率。

3.在哪里

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

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

从源码出发,我们可以来看一看FILE结构体:

linux write 怎么查看fd缓冲区,学好Linux,linux,网络,运维

linux write 怎么查看fd缓冲区,学好Linux,linux,网络,运维

所以我们一般所说的缓冲区是语言级别的缓冲区,C语言提供的在FILE结构体里对应的缓冲区。

现在,我们现在重新来看一看刚开始的现象:

linux write 怎么查看fd缓冲区,学好Linux,linux,网络,运维

1.如果我们没有进行重定向>,看到了4条消息,stdout默认使用的是行刷新,在进程fork之前,三条C函数已经将数据打印输出到显示器上(外设),你的FILE内部进程内部就不存在对应的数据了。

2.如果我们进行了重定向>,写入文件不在是显示器,而是普通文件,采用的刷新策略是全缓冲,之前的3条C函数虽然带了\n,但是不足以将stdout缓冲区写满,所以数据并没有刷新! 在执行fork的时候,stdout属于父进程,fork创建子进程紧接着就是进程退出,谁先退出就要进行缓冲区刷新,刷新的本质就是修改,修改的时候发生写时拷贝!所以数据最终会显示两份!上面的过程都和write无关,write没有FILE,而用的是fd,就没有C提供的缓冲区!

简单总结来说:重定向导致刷新策略发生了改变(由行缓冲变成了全缓冲)。同时发生了写时拷贝,父子进程各自刷新


三、理解缓冲区

对于缓冲区的理解我们可以自己通过代码来简单实现:

FILE_结构体的设计,这里为了避免与FILE发生冲突,我们命名为FILE_:

#define SIZE 1024
typedef struct _FILE
{
    int flags;//刷新方式
    int fileno;//文件描述符
    int cap;//buffer的总容量
    int size;//buffer当前使用量
    char buffer[SIZE];//缓冲区                                                                              }FILE_;               

主函数:

int main()        
{         
    //打开
    FILE_ *fp = fopen_("./log.txt","w");
    //打开失败
    if(fp==NULL)                        
    {                                   
        return 1;
    }            
    int cnt = 10;
    const char*masg = "hello world ";
    while(1)                         
    {
        //写入
        fwrite_(masg,strlen(masg),fp);
        //刷新
        fflush_(fp);
        //睡眠
        sleep(1);                     
        printf("count:%d\n",cnt);
        cnt--;                   
        if(cnt==0) break;        
    }   
    //关闭
    fclose_(fp);                                                                               
    return 0;                                                                                  
}                                                                                              

对于C语言来说,文件接口一旦打开成功,其余接口要带上FILE*,因为FILE结构体里包含了各种数据:

下面是我们需要自己实现的文件接口:

//打开
FILE_ * fopen_(const char*path_name,const char*mode);
//以下的接口都需要带上FILE_*
void  fwrite_(const void *ptr,int num, FILE_*fp);

void fflush_(FILE_*fp);

void fclose_(FILE_* fp);

fopen_:打开我们需要去判断具体是按什么方式打开:

FILE_ *fopen_(const char *path_name, const char *mode)
{
    int flags = 0;
    int defaultMode=0666; //设置默认权限
    //读方式
    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);
    }
    //其他
    else
    {
        //其他方式这里就不展开了
    }
    int fd = 0;

    if(flags & O_RDONLY) fd = open(path_name, flags);
    else fd = open(path_name, flags, defaultMode);
    //处理打开失败
    if(fd < 0)
    {
        const char *err = strerror(errno);
        write(2, err, strlen(err));
        return NULL; 
    }
    //打开成功
    FILE_ *fp = (FILE_*)malloc(sizeof(FILE_));
    assert(fp);

    fp->flags = SYNC_LINE; //默认设置成为行刷新
    fp->fileno = fd;
    fp->cap = SIZE;
    fp->size = 0;
    memset(fp->buffer, 0 , SIZE);
    return fp; 
}

fwrite_:

void fwrite_(const void *ptr, int num, FILE_ *fp)
{
    // 写入到缓冲区中
    memcpy(fp->buffer+fp->size, ptr, num); //不考虑缓冲区溢出的问题
    fp->size += num;
    // 判断是否刷新
    if(fp->flags & SYNC_NOW)
    {
        write(fp->fileno, fp->buffer, fp->size);
        fp->size = 0; //清空缓冲区
    }
    else if(fp->flags & SYNC_FULL)
    {
        if(fp->size == fp->cap)
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }
    else if(fp->flags & SYNC_LINE)
    {
        if(fp->buffer[fp->size-1] == '\n') // abcd\nefg在这个地方不考虑
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }
    else{
    }
}

fclose:

void fclose_(FILE_ * fp)
{
    fflush_(fp);
    close(fp->fileno);
}

fflush_:这里将数据强制要求操作系统进行外设刷新要用到fsync:文章来源地址https://www.toymoban.com/news/detail-819040.html

void fflush_(FILE_ *fp)
{
    if( fp->size > 0) write(fp->fileno, fp->buffer, fp->size);
    fsync(fp->fileno); //将数据,强制要求OS进行外设刷新!
    fp->size = 0;
}

到了这里,关于【Linux】理解缓冲区的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 用Linux的视角来理解缓冲区概念

    缓冲区(buffer)是存储数据的临时存储区域。当我们用C语言向文件中写入数据时,数据并不会直接的写到文件中,中途还经过了缓冲区,而我们需要对缓冲区的数据进行刷新,那么数据才算写到文件当中。而缓冲区通常是一块内存区域,可以是数组、队列、链表等数据结构。

    2024年01月20日
    浏览(37)
  • Linux文件系列: 深入理解缓冲区和C标准库的简单模拟实现

    至此,我们理解了缓冲区的概念和作用,下面我们来简易模拟实现一下C标准库 我们要实现的是: 1.文件结构体的定义 1.首先要有一个文件结构体: 刷新策略分别宏定义为 2.myfopen等等函数的声明 path:文件路径+文件名 mode:打开文件的方式 “r”:只读 “w”:覆盖写 “a”:追加写 strea

    2024年03月11日
    浏览(56)
  • [Linux]理解文件系统!动静态库详细制作使用!(缓冲区、inode、软硬链接、动静态库)

            hello,大家好,这里是bang___bang_,今天来谈谈的文件系统知识,包含有缓冲区、inode、软硬链接、动静态库。本篇旨在分享记录知识,如有需要,希望能有所帮助。 目录 1️⃣缓冲区 🍙缓冲区的意义 🍙常见缓冲区刷新策略 🍙缓冲区位置猜想 🍥现象猜测 🍥现象解

    2024年02月13日
    浏览(37)
  • 【看表情包学Linux】文件描述符 | 重定向 Redirection | dup2 函数 | 缓冲区的理解 (Cache)

       🤣  爆笑 教程  👉 《看表情包学Linux》👈   猛戳订阅     🔥 💭 写在前面: 在上一章中,我们已经把 fd 的基本原理搞清楚了。本章我们将开始探索 fd 的应用特征,探索 文件描述符的分配原则。讲解重定向,上一章是如何使用 fflush 把内容变出来的,介绍 dup2 函数,

    2023年04月25日
    浏览(40)
  • 【Linux】文件缓冲区

    提到文件缓冲区这个概念我们好像并不陌生,但是我们对于这个概念好像又是模糊的存在脑海中,之间我们在介绍c语言文件操作已经简单的提过这个概念,今天我们不妨深入理解什么是文件缓冲区 通过自己实现库中的一些文件操作函数更加深入的理解文件缓冲区 自定义实现

    2024年02月10日
    浏览(44)
  • 【linux】重定向+缓冲区

    自我名言 : 只有努力,才能追逐梦想,只有努力,才不会欺骗自己。 喜欢的点赞,收藏,关注一下把! close(1),为什么没有打印新建文件fd呢? printf(“%dn”,fd); printf会把内容打印到stdout文件中。 但是close(1)关闭标准输出stdout—显示器,int fd=open();新打开的文件fd是1。 st

    2024年02月08日
    浏览(37)
  • 浅谈linux缓冲区的认识!

    今天来为大家分享一波关于缓冲区的知识!那么既然我们要谈缓冲区,那么就得从是什么?为什么?有什么作用这几个方面来谈论一下缓冲区!然后再通过一些代码来更加深刻的理解缓冲区的知识! 从最简单的理解方面来,我们可以将缓冲区理解成一块内存!那么这块内存是

    2024年02月05日
    浏览(44)
  • 【Linux】缓冲区+磁盘+动静态库

    缓冲区的本质就是一段用作缓存的 内存 。 节省进程进行数据IO的时间。进程使用fwrite等函数把数据拷贝到缓冲区或者外设中。 3.1、 立即刷新(无缓冲)——ffush() 情况很少,比如调用printf后,手动调用fflush刷新缓冲区。 3.2、 行刷新(行缓冲)——显示器 显示器需要满足人

    2024年02月05日
    浏览(31)
  • [Linux打怪升级之路]-缓冲区

    前言 作者 : 小蜗牛向前冲 名言 : 我可以接受失败,但我不能接受放弃    如果觉的博主的文章还不错的话,还请 点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正  本期学习目标:认识什么是缓冲区,缓冲区在哪里,模拟实现一个简单的缓

    2024年02月07日
    浏览(41)
  • 【Linux】基础IO —— 缓冲区深度剖析

    (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是 Scort 目前状态:大三非科班啃C++中 🌍博客主页:张小姐的猫~江湖背景 快上车🚘,握好方向盘跟我有一起打天下嘞! 送给自己的一句鸡汤🤔: 🔥真正的大师永远怀着一颗学徒的心 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏 🎉🎉

    2024年02月02日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包