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

news2024/12/25 13:15:17

目录

头文件:

接下来就是设计这四个函数: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缓冲区的刷新方式去看待内核缓冲区。

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

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

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

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

       


总结:

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字符,所以该情况为行缓冲刷新策略,每写入缓冲区一行字符串,就立即刷新到文件中去。

运行结果:

修改测试代码:

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

运行结果:

        该结果为全缓冲的刷新策略,去掉了\n字符后,字符串会被一直留在缓冲区中,除非是调用_fclose函数或者缓冲为满,才会被刷新到文件中去。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/682940.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ASEMI快恢复二极管MUR80100PT功能和应用实用指南

编辑-Z MUR80100PT是一种高性能、超快恢复二极管&#xff0c;设计用于各种应用&#xff0c;包括电源、逆变器和电机控制系统。本文将提供一个全面的指南&#xff0c;以了解MUR80100PT的特点和应用&#xff0c;以及它在提高电子设备的效率和可靠性方面的重要性。 MUR80100PT的特…

使用vite创建vue3、react项目

一、使用vite需要的环境 node: 14.18 , 16 vite官网&#xff1a;https://cn.vitejs.dev/guide/ 如上图、官网上明确给出了提醒&#xff0c;要使用vite搭建项目&#xff0c;需要node版本在14.18 二、使用vite创建vue3项目 1. 使用命令启动vite创建项目 使用 NPM: $ npm cr…

开始使用Dotnetty高性能网络库进行网络通讯

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;新的征程&#xff0c;我们面对的不是…

深度:激光和光纤诞生记

光子盒研究院出品 导读&#xff1a;20世纪50年代以来&#xff0c;科技领域掀起了一场光学革命&#xff0c;激光和光纤的诞生&#xff0c;带来了革命性突破。事实上&#xff0c;激光和光纤的诞生也是第一次量子革命的范畴&#xff0c;因为这些技术的发展是基于对量子规律的观测和…

Java集合之LinkedList详解

Java集合之LinkedList 一、LinkedList类的继承关系1. 基类功能说明1.1. Iterator&#xff1a;提供了一种方便、安全、高效的遍历方式。1.2. Collection&#xff1a;为了使ArrayList具有集合的基本特性和操作。1.3. Queue: LinkedList是一种队列&#xff08;Queue&#xff09;数…

I/O error on POST request for “...“ PKIX path building failed的解决办法

异常&#xff1a; 项目中需要用RestTemplate调三方接口&#xff0c;url是https开头加密的。postman可以调通&#xff0c;代码提示没有证书&#xff0c;具体如下&#xff1a; [ERROR][2023-06-25 10:41:16,574][com.peraglobal.restInterface.controller.PLMController]I/O err…

MySQL如何在Centos7环境安装:简易指南

目录 前言 一、卸载不要的环境 1.检查本地MySQL是否正在运行 2.停止正在运行的MySQL 二、检查系统安装包 三、卸载这些默认安装包 1.手动一个一个卸载 2.自动卸载全部 四、获取mysql官方yum源 五、安装mysql yum源&#xff0c;对比前后yum源 1.安装前 2.安装中 3.…

MySQL进阶SQL语句之函数运用

目录 1.select&#xff08;显示表格中一个或数个字段的所有数据记录&#xff09; 2.distinct&#xff08;不显示重复的数据记录&#xff09; 3.where&#xff08;有条件查询&#xff09; 4.and 、or&#xff08;且、或&#xff09; 5. in&#xff08;显示已知的值的数据记…

浅谈单线程和多线程的异同

前两天有个面试&#xff0c;面试官问了我一个单线程和多线程的问题&#xff0c;情境如下&#xff1a; 面试官&#xff1a;你对单线程和多线程有什么看法&#xff1f; 我&#xff1a; 面试官&#xff1a; 我&#xff1a; 面试官&#xff1a; 我 现在先让我们来了解一下进程…

WS协议—介绍及原理

举例来说&#xff0c;我们想了解今天的天气&#xff0c;只能是客户端向服务器发出请求&#xff0c;服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。 WebSocket 协议在2008年诞生&#xff0c;2011年成为国际标准。所有浏览器都已经支持了。它的最大特点就是&…

ms17_010(永恒之蓝)漏洞复现详细教程

如题&#xff0c;这是个漏洞复现的详细教程&#xff0c;本教程针对的系统是Windows7操作系统&#xff0c;其他系统请自行测试。 备注&#xff1a;教程会很详细&#xff0c;讲解会很明白&#xff0c;一文可以解决你的常见困难。 测试环境 kalilinux 192.168.1.109 &#xff08;…

Java设计模式之结构型-装饰器模式(UML类图+案例分析)

目录 一、基本概念 二、角色设计 三、代码实现 案例一 案例二 四、总结 一、基本概念 装饰器模式是指不必在改变原有的类和不使用继承的情况下&#xff0c;动态扩展一个对象的功能。 二、角色设计 角色描述抽象构件是一个接口或者抽象类&#xff0c;定义我们最核心的…

科技云报道:边缘计算步入“黄金年代”

科技云报道原创。 当前时点&#xff0c;AI大模型已经站在了从“玩具”向“工具”快速演化的关键迭代期。如何让大模型渗透进入各类垂直场景&#xff0c;如何更低成本的使用大模型&#xff0c;如何让更多场景与用户接触AI&#xff0c;成为了发展的下一个重点。 在AI向实际场景…

大数据应用——总结与反思

1.谈谈你对大数据行业的认识&#xff0c;目前对应的大数据岗位有哪些&#xff1f;每种岗位需要掌握哪些技能水平&#xff1f;目前自己的差距在哪里&#xff1f; &#xff08;1&#xff09;概述 对于大数据行业的认识&#xff0c;我的理解是&#xff0c;大数据是指海量数据&…

8种常见的SQL错误用法

前言&#xff1a;MySQL在2016年仍然保持强劲的数据库流行度增长趋势。越来越多的客户将自己的应用建立在MySQL数据库之上&#xff0c;甚至是从Oracle迁移到MySQL上来。但也存在部分客户在使用MySQL数据库的过程中遇到一些比如响应时间慢&#xff0c;CPU打满等情况。现将《Apsar…

Dumuz同步微信通讯录及常见问题

在Dumuz工具中&#xff0c;【微信通讯录同步】主要功能是从当前登录的微信上下载通讯录相关成员数据。 第1步&#xff1a; 打开应用【微信-消息批量发送】&#xff0c;在工具栏中点击【微信通讯录】如下图所示&#xff1a; 第2步&#xff1a; 进入【微信通讯录】 对话框&#…

【AUTOSAR】AUTOSAR开发工具链(九)----基于BTC的MIL/SIL测试操作说明(1)

一、BTC使用注意事项 1、安装成功后&#xff0c;在Edit->Preference->General->Compiler可以找到编辑器MSVC140 启动BTC&#xff1a;插入电子狗、选择与电子狗相匹配的License、选择相应的工具包 B2B就是MIL V SIL 适用于MBD开发的测试&#xff0c;单独SIL适用于手写…

【Jmeter教程】__将提取的参数并设置成全局变量(常用于提取token)

目录 一、提取参数 1、使用正则表达式提取器提取token 2、使用json提取器提取token 二、将提取参数设置成全局变量 三、常见问题 一、提取参数 1、使用正则表达式提取器提取token 查看登录响应参数找出token。图中token为 "ticketString": "ccf26b17-a96f…

深入理解MySQL主从配置原理

目录 1. MySQL主从复制原理工作原理 2. 主从配置步骤1: 配置主节点2: 备份主节点数据3: 配置从节点4: 启动主从复制 3.常见问题4. 需要考虑的一些因素 MySQL主从复制是一种数据库复制技术&#xff0c;通过将一个MySQL服务器&#xff08;主节点&#xff09;上的数据同步到其他My…

IP地点定位为什么有误差?

随着互联网的不断普及&#xff0c;人们对IP地点定位需求越来越多。然而&#xff0c;即便是在现代技术的支持下IP地点定位仍然存在误差。那么&#xff0c;IP地点定位为什么会出现误差呢&#xff1f; IP&#xff08;Internet Protocol&#xff09;地址是指互联网协议&#xff08;…