Linux缓冲区续集——手撕fopen、fwrite、fflush、fclose等C库函数

这篇具有很好参考价值的文章主要介绍了Linux缓冲区续集——手撕fopen、fwrite、fflush、fclose等C库函数。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

头文件:

接下来就是设计这四个函数:Mystdio.c

重点讲一讲_fflush函数的底层实现原理:

所以数据内容的经过如下:

总结:       

执行——测试写好的这4个函数:

运行结果:

修改测试代码:

运行结果:


       回顾上文,我讲述了关于Linux文件系统中关于缓冲区的含义和理解,用一个特殊案例表明了我们所了解到的缓冲区是C语言库函数中特有的,而系统调用函数没有。 此外就是C库缓冲区的刷新策略,共有三种:立即刷新、行缓冲、全缓冲.....

Linux文件系统的缓冲区问题

        那么接下来我就根据学到的缓冲区,通过在VIM编译器中自己简单手撕一下四个函数:fopen、fclose、fwrite、fflush函数的底层实现,因为这4个函数都是C库函数,都有缓冲区,那么我们就可以去深入了解一下这些函数到底是怎么做到将内容写入到指定文件中去的。

头文件:

   #include<stdio.h>
   #include<stdlib.h>
   #include<assert.h>
   #include<unistd.h>
   #include<sys/types.h>
   #include<sys/stat.h>
   #include<fcntl.h>
   #include<errno.h>
   
    //定义缓冲区刷新策略
  #define cash_now 1        //立即刷新
  #define cash_line 2       //行缓冲
  #define cash_all 4        //全缓冲
  
  #define SIZE 1024
  
    //重命名结构体
  typedef struct _FILE{
      int _fileno;    //文件描述符
      int _flag;      //缓冲区刷新策略
      int _cap;       //缓冲区最大容量
       int _size;     //当前缓冲区的存在的字节数                                                                                                                                        
      char _buf[SIZE]; //缓冲区——数组
  }_FILE;
  
  //函数声明
  _FILE* _fopen(const char* path,const char* mode);
  void _fwrite(void* ptr,int num,_FILE* fp);
  void _fclose(_FILE* fp);
  void _fflush(_FILE* fp);

        因为缓冲区的位置在FILE结构体的内部,我们手撕的话,需要自己创建一个类似FILE的结构体,在该结构体中上面5个是必要的成员变量,_fileno是用于使用系统调用函数open的返回值,_flag可以自己设置缓冲区的刷新策略:cash_now、cash_line、cash_all;_cap和_size就是用于缓冲区的容量和当前值,而最重要的就是_buf缓冲区了。我们写的内容全都要靠它进行传递。

然后我来介绍一下我们要手撕的C库函数:       

1.fopen函数用于打开文件,对其进行读/写。

2.fwrite函数用于将写好的内容送入缓冲区中。

3.fflush函数用于强制刷新缓冲区的内容到指定流中。

4.fclose函数用于关闭文件,最终清理缓冲区残留内容。

接下来就是设计这四个函数:Mystdio.c

  #include"Mystdio.h"
  #include<string.h>
   
   _FILE* _fopen(const char* path,const char* mode){
       int flags=0;
   
       if(strcmp(mode,"r")==0){
           flags=flags |O_RDONLY;
       }
      else if(strcmp(mode,"w")==0){
          flags=flags |O_WRONLY |O_CREAT |O_TRUNC;
      }
  
      else if(strcmp(mode,"a")==0){
          flags=flags | O_WRONLY |O_CREAT |O_APPEND;
      }
  
      else{
          //剩余的r+,w+,....
      }
  
      int power=0666;    //设置文件权限

      int fd=0;          //文件描述符


      if(flags& O_RDONLY){           //读文件的文件描述符                                                                                                                            
          fd=open(path,flags);
      }
      else{                            //写文件的文件描述符
          fd=open(path,flags,power);
      }
  
       //判断文件是否被打开


        //情况1:打开文件失败
      if(fd<0){
          const char* str=strerror(errno);
          write(2,str,strlen(str));        //将失败原因写到缓冲区中
          return NULL;    //返回空
      }
  
        //情况2:打开文件成功
      else{
          _FILE* fp=(_FILE*)malloc(sizeof(_FILE));
          assert(fp);    //断言fp一定申请结构体对象成功

          //为结构体对象赋初值
          fp->_cap=SIZE;
          fp->_size=0;
          fp->_fileno=fd;
          fp->_flag=cash_line;
     
          memset(fp->_buf,0,SIZE);      //初始化数组——缓冲区                                                                                                                     
          return fp;                    //函数返回值
          }
      }
  

    //强制刷新缓冲区函数  
  void _fflush(_FILE* fp){

      if(fp->_size>0){    //表明当前缓冲区中还有内容
            //将缓冲区的内容写入文件中
          write(fp->_fileno,fp->_buf,fp->_size);
          fp->_size=0;
      }

    //重点:
     syncfs(fp->_fileno);
  
  }

    //写入文件函数
  void _fwrite(void* ptr,int num,_FILE* fp){
      memcpy(fp->_buf+fp->_size,ptr,num);
      fp->_size+=num;
  
        //判断缓冲区的刷新策略
      if(fp->_flag==cash_now){    //立即刷新—— //将缓冲区的内容写入文件中
           write(fp->_fileno,fp->_buf,fp->_size); 
           fp->_size=0;
      }
  
      else if(fp->_flag==cash_line){    //行缓冲——需要遇到'\n'才会刷新
          if(fp->_buf[fp->_size-1]=='\n'){
            
             //将缓冲区的内容写入文件中
              write(fp->_fileno,fp->_buf,fp->_size);
              fp->_size=0;
          }
      }
 
      else if(fp->_flag==cash_all){    //全缓冲——需要写满缓冲区才会刷新
            if(fp->_size==fp->_cap){

             //将缓冲区的内容写入文件中
              write(fp->_fileno,fp->_buf,fp->_size);
              fp->_size=0;
          }
      }
  
      else{
          //...
      }
  }
  
  //关闭文件函数
  void _fclose(_FILE* fp){
      _fflush(fp);
      close(fp->_fileno);
  }

        由上面各个函数的底层实现可知:fopen、fclose、fwrite函数的底层实现是都由open、close、wirte等系统调用函数封装和优化才形成的。 

重点讲一讲_fflush函数的底层实现原理:

        我们说的缓冲区是C语言库中给我们提供的缓冲区,也就是说我们使用fprintf、fput、fwrite函数写的内容是会先写到C语言中的缓冲区,然后根据fopen函数中的FILE*类型指针的文件描述符经过OS操作系统将缓冲区内容刷新并且送往磁盘文件中。

        而在送往磁盘文件的过程中,C语言的缓冲区被刷新并不是直接送到文件中,它会经过内核缓冲区,没听错!OS中也有自己的缓冲区,只不过这个缓冲区我们根本不会用到,内核缓冲区的作用就是将各个进程对文件进行读写的内容暂时集中起来放在这里,相当于一个大型仓库,也是用于数据的传递。内核缓冲区的刷新策略与之前说的C语言给我们提供的缓冲区刷新策略不同,不能用C缓冲区的刷新方式去看待内核缓冲区。

Linux缓冲区续集——手撕fopen、fwrite、fflush、fclose等C库函数

        所以我们在使用fpinrtf、fputs、fwrite函数将想要写入的内容传入文件时,它们会先被放到C缓冲区中,然后执行fflush(stdout);语句:

fflush:标准IO函数(如fread,fwrite等)会在内存中建立缓冲,该函数刷新内存缓冲,将内容写入内核缓冲,要想将其真正写入磁盘,还需要调用fsync。(即需要先调用fflush,然后再调用fsync,否则不会起作用)。

        强制刷新C缓冲区内容,也只是刷新到了内核缓冲区中。等达到它的刷新要求或者采用fsync()函数后,这些数据才会被真正写入磁盘文件中。

所以数据内容的经过如下:

       Linux缓冲区续集——手撕fopen、fwrite、fflush、fclose等C库函数


总结:

fflush:强制刷新C库缓冲区内容到系统的内核缓冲区。

fsync:强制刷新内核缓冲区的数据到指定的文件流中。

        Linux对10文件的操作分为:

        1.不带缓存:open read,write等。posix系统标准,在用户空间没有缓冲,在内核空间还是进行了缓存的。数据----->内核缓存区---->磁盘.

        假设内核缓存区长度为100字节且需要写满后才会刷新。当我们调用 ssize t write (int fd,const void * buf,size tcount);写操作时,设每次写入count=10字节,那么我们要调用10次这个函数才能把这个缓存区写满,没写满时数据还是在内核缓冲区中,并没有写入到磁盘中,内核缓存区满了之后或者执行了fsync (强制写入硬盘) 之后,才进行实际的I/O操作,把数据写入文件上。

        2.带缓存区: fopen fwrite fputs、fread等,是c标准库中定义的。数据----->C库缓存区----->内核缓存区---->磁盘。

        假设C库缓存区长度为50字节,内核缓存区100字节,我们用标准C库函数fwrite(); 将数据写入到这个C缓存区中,每次写10字节,需要写5次到C缓冲区才会满或者直接调用 fflush( ) ),才将数据写到内核存区, 直到内核缓存区满了之后或者执行了fsync();之后,才进行实际的I/O操作,将内核缓冲区的数据写入磁盘上。


执行——测试写好的这4个函数:

     #include<stdio.h>
     #include"Mystdio.h"
     #include<string.h>
    
     int main(){
         _FILE* fp= _fopen("log.txt","w");
         if(fp==NULL){
                 perror("open file");
                 return -1;
         }
    
            char* str="一骑红尘妃子笑\n";
        int cnt=10;
        while(1){
            _fwrite(str,strlen(str),fp);
    
            sleep(1);                                                                        
            printf("cnt:%d\n",cnt--);
    
            if(cnt%3==0){
               _fflush(fp);
            }
    
            if(cnt==0){
                break;
            }
        }
    
        _fclose(fp);
        return 0;
       }

在测试案例中,我通过循环的方式,使用自己写的_fwrite函数将字符串写入文件中,因为使用了\n字符,所以该情况为行缓冲刷新策略,每写入缓冲区一行字符串,就立即刷新到文件中去。

运行结果:

Linux缓冲区续集——手撕fopen、fwrite、fflush、fclose等C库函数

修改测试代码:

Linux缓冲区续集——手撕fopen、fwrite、fflush、fclose等C库函数

 将字符的\n字符去掉,再次汇编链接生成可执行文件.

运行结果:

Linux缓冲区续集——手撕fopen、fwrite、fflush、fclose等C库函数

        该结果为全缓冲的刷新策略,去掉了\n字符后,字符串会被一直留在缓冲区中,除非是调用_fclose函数或者缓冲为满,才会被刷新到文件中去。 文章来源地址https://www.toymoban.com/news/detail-499871.html

到了这里,关于Linux缓冲区续集——手撕fopen、fwrite、fflush、fclose等C库函数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • Linux之缓冲区的理解

    目录 一、问题引入 二、缓冲区 1、什么是缓冲区 2、刷新策略 3、缓冲区由谁提供 4、重看问题 三、缓冲区的简单实现 我们先来看看下面的代码:我们使用了C语言接口和系统调用接口来进行文件操作。在代码的最后,我们还使用fork函数创建了一个子进程。  代码运行结果如

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

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

    2024年02月05日
    浏览(54)
  • 【Linux】深入理解文件缓冲区

    问题引入 首先看一段代码: 运行代码,结果如下: 如果此时将输出结果重定向一下: 会发现 printf 、 fwrite 都打印了两次。 究其原因,就要谈到缓冲区和缓冲区刷新策略的概念了。 如何理解缓冲区 假设你在青岛,你要从网上买一件商品,商家所在地是北京。你不会跑去北

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

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

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

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

    2024年02月05日
    浏览(39)
  • 【Linux】基础IO----理解缓冲区

    作者:დ旧言~ 座右铭:松树千年终是朽,槿花一日自为荣。 目标:理解缓冲区 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安! 专栏选自:Linux初阶 望小伙伴们点赞👍收藏✨加关注哟💕💕 缓冲区大家其实不陌生,像我们使用的 VS2019 编译器这里就有缓冲区,那它

    2024年04月13日
    浏览(44)
  • 【Linux】基础IO —— 缓冲区深度剖析

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

    2024年02月02日
    浏览(53)
  • Linux操作系统——重定向与缓冲区

    上一篇文章(文件详解)我们一直在谈,一个文件要被访问就必须要先被打开,打开之前就必须要先把文件加载到内存,同时呢我们的操作系统为了管理文件也会为我们的文件创建相对应的struct file对象,那么这个struct file对象里面应该有什么? 其实struct file里面最核心的两个

    2024年01月16日
    浏览(47)
  • 用Linux的视角来理解缓冲区概念

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

    2024年01月20日
    浏览(53)
  • (9)Linux Git的介绍以及缓冲区

    💭 前言 本章我们先对缓冲区的概念进行一个详细的探究,之后会带着大家一步步去编写一个简陋的 \\\"进度条\\\" 小程序。最后我们来介绍一下 Git,着重讲解一下 Git 三板斧,一般只要掌握三板斧就基本够用了。 先说一下 unistd.h 库中的 sleep 函数,它可以按照秒去休眠 我们先创

    2024年02月03日
    浏览(49)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包