Linux入门---缓冲区模拟实现

news2024/11/28 22:02:26

前言

有了前面的基础我们知道c语言的缓冲区本质上就是FILE结构体的一部分,我们每次使用stdout将数据打印到屏幕上时,本质上是先将数据拷贝到FILE结构体的缓冲区中,然后再拷贝到内核缓冲区中也就是file结构体里面的缓冲区,最后再刷新到屏幕上面,在前面的学习中我们是使用fopen函数打开文件,fwrite函数将数据输出到文件里面,fflush函数刷新FILE结构体里面的缓冲区,fclose函数关闭打开的文件,这些函数都是c语言提供的函数,而我们知道c语言是用户级语言,这些c语言函数在底层是调用系统的接口来实现的功能,比如说open函数,write函数,close函数等等,那么本篇文章我将带着大家通过这些系统调用接口来实现一个属于自己的缓冲区

准备工作

首先创建三个文件:mystdio.h(这个文件里面存放各种函数和结构体的声明) mystdio.c(这个文件里面存放.h文件里面的函数实现) main.c(这个文件则是用来测试main.c文件里面的函数实现是否是正确),那么这里的操作就如下:
在这里插入图片描述

在linux系统通过fd也就是一个数组的下标来往指定的文件里面写入内容,所以使用write函数时会返回一个正整数,使用c语言的fopen函数打开一个文件时会返回一个FILE结构体,这个结构体里面就会装对应文件在linux系统中的fd和一个缓冲区以及缓冲区对应的各种属性比如说:缓冲区的大小,缓冲区的占用率,缓冲区的刷新方式等等,既然我们要自己模拟实现缓冲区的话是不是也得自己创建一个结构体来存放文件的fd和对应的缓冲区以及对应的属性啊,那么这里的代码如下:

typedef struct FILE_  
{  
    int file_fd;//文件的fd  
    int capacity;//缓冲区的容量  
    int size;//缓冲区已经被使用的  
    int flags;//缓冲区刷新的方式  
    char buffer[SIZE];//缓冲区  
}FILE_;  

file_fd中记录了文件的fd,capacity记录缓冲区的容量,size记录缓冲区中已经装了多少个字符,因为c语言中有三种缓冲区刷新方式:立即缓冲,行缓冲和全缓冲,那么这里就得在结构体中创建一个flags来记录当前缓冲区中数据的刷新方式,字符数组buffer就是缓冲区,使用写入函数就是将数据写入到这个数组里面,既然这里有刷新方式的话,那么我们就可以定义三个标记位来分别代表立即刷新,行刷新和满刷新,那这里的代码如下:

#pragma once   
#define SIZE 1024  
#define SYNC_NOW  1  
#define SYNC_LINE 2  
#define SYNC_FULL 4
#define size 1024	  
typedef struct FILE_  
{  
    int file_fd;//文件的fd  
    int capacity;//缓冲区的容量  
    int size;//缓冲区已经被使用的  
    int flags;//缓冲区刷新的方式  
    char buffer[SIZE];//缓冲区  
}FILE_;                                                                                                                                 

结构体创建完之后,我们还得创建一些函数来对结构体做一些操作,类似于c语言的fwrite函数,fclose函数,fflush函数,比如说下面的代码:

FILE_ *fopen_(const char* path_name,const char* mode);
void fwrite_(const void*ptr,int num,FILE_*fp);
void fclose_(FILE_*fp);
void fflush_(FILE_*fp);   

这些函数参数都是根据c语言文件函数模仿写的的,那么接下来我们就要模拟实现这些函数。那么mystdio.h文件里面的代码就如下:

#pragma once   
#define SIZE 1024  
#define SYNC_NOW  1  
#define SYNC_LINE 2  
#define SYNC_FULL 4
#define size 1024
typedef struct FILE_
{
    int file_fd;//文件的fd
    int capacity;//缓冲区的容量
    int size;//缓冲区已经被使用的
    int flags;//缓冲区刷新的方式
    char buffer[SIZE];//缓冲区
}FILE_;
FILE_ *fopen_(const char* path_name,const char* mode);
void fwrite_(const void*ptr,int num,FILE_*fp);
void fclose_(FILE_*fp);
void fflush_(FILE_*fp);    

当然这里有些后面的用到的函数头文件没有包含并不是完整的代码,大家注意一下。

fopen_函数的实现

c语言的fopen函数在底层调用操作系统的open函数来打开文件,那么我们这里也采用同样的方式来实现打开文件的功能,首先来看看open函数的介绍
在这里插入图片描述
第一个参数是文件的名字也就是你要打开哪个文件,第二个参数就是以什么样的形式来打开一个文件,c语言有很多种打开文件的方式,但是我们这里实现的open函数就采用平时使用的最多的几个打开方法:w(写的方式打开),r(读的方式打开),a(追加的方式打开),fopen_函数底层是通过调用操作系统的open函数来实现,但是open函数有标志位参数不同的标志位有着不同的功能,所以在fopen_函数的开头就得创建一个flags来表示打开文件的方式,然后通过else if语句来赋予flags不同的标记位,这里赋予标记位的方法就是直接将flags与等标记位即可,比如说下面的代码:

  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_CREAT|O_TRUNC|O_WRONLY;                                                                                  
      }                                                                                                                     
      else if(strcmp(mode,"a")==0)                                                                                             
      {                                                                                                                     
          flags|=O_CREAT|O_TRUNC|O_APPEND;                                                                                  
      }                                                                                                
      else                                                                      
      {                                                                                                                       
          printf("打开文件的方式错误\n"); 
          return NULL}                                                                                                                               
  }

有了标记位之后就可以使用open函数打开文件,因为用写和追加的方式打开文件的话会存在创建文件的情况,创建文件得给这个文件添加对应的起始权限,而以读的方式打开文件的话却不存在创建文件的情况,所以我们这里得再添加一个if else语句用来区分open打开文件时是否需要创建文件,并且open调用完成之后会返回该文件的文件描述符,所以我们这里还得创建一个整型变量来接收open函数的返回值,如果返回值大于0的话表明打开文件成功了,如果返回值小于0的话表明打开文件失败了,那么这时我们得在屏幕上告诉使用者文件打开失败了,那这里告诉使用者的方法就是调用write函数向文件描述符为2的文件里面输出对应的错误信息,那这一步的功能就如下:

 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_结构体来描述新打开的文件,将文件的fd以及文件的缓冲区的大小等信息进行填入,因为这个结构体在出了函数之后还要被使用,所以我们就使用malloc在堆上动态开辟该结构体,将结构体的信息填充完毕之后就返回结构体的地址,那么完整的代码就如下:

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_CREAT|O_TRUNC|O_WRONLY;
      }
      else if(strcmp(mode,"a")==0)
      {
          flags|=O_CREAT|O_TRUNC|O_APPEND;                                                                         
      }                                                                         
      else                                                                         
      {                                                                         
          printf("打开文件的方式错误\n");                                                                         
          return NULL;                                                                         
      }                                                                         
      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->capacity=SIZE;
      fp->size=0;
      fp->file_fd=fd;
      memset(fp->buffer,0,SIZE);
      return fp;
  }

fflush_函数模拟实现

fflush_函数的作用就是将文件缓冲区中的内容输出到文件里面,那这里我们就可以使用write函数进行输出,然后将文件结构体里面的size置为0即可,那这个函数实现的代码就如下:

  void fflush_(FILE_*fp)
  {
      if(fp->size>0)
      {
          write(fp->file_fd,fp->buffer,fp->size);
          fp->size=0
      }                                                                                                                                 
  }   

fclose_函数模拟实现

fclose_函数就是关闭我们打开的文件,在关闭文件之前首先得将文件缓冲区里面的内容进行刷新,然后使用操作系统的close函数关闭打开的文件,那么这里的代码就如下:

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

fwrite_函数模拟实现

最后就是fwrite_函数,这个函数就是往文件里面写入内容,我们先来看看这个函数的参数声明:

void fwrite_(const void*ptr,int num,FILE_*fp);

第一个参数ptr表明要往文件里面写入的字符串,第二个参数表明要写入文件的字符个数,第三个参数则是往哪个文件里面进行写入,将内容写入到文件之前得先将内容写入到文件的缓冲区里面,然后再根据刷新的策略来判断该如何将数据刷新到文件里面,那这里就先使用memcpy函数将字符串拷贝进缓冲区里面由于我们这里是简易的实现缓冲区,所以不考虑缓冲区溢出的情况,将数据拷贝进缓冲区之后我们就要判断缓冲区刷新的策略,那这里就得使用else if语句来分情况进行处理,那这里的代码就如下:

void fwrite_(const void* ptr,int num ,FILE_ *fp)  
{                                             
    memcpy(fp->buffer+fp->size,ptr,num);               
    fp->size+=num;                            
    if(fp->flags&SYNC_NOW)  
    {  
  
    }  
    else if(fp->flags&SYNC_FULL)   
    {  
  
    }
    else if(fp->flags&SYNC_LINE)
    {

    }
    else 
    {

    }
  
} 

对于满缓冲这里就直接调用write函数将buffer中的数据输出到文件里面然后将fp中的size置为0即可,那这里的代码就如下:

    if(fp->flags&SYNC_NOW)  
    {  
        write(fp->file_fd,fp->buffer,fp->size);
        fp->size=0;
    }

对于满刷新策略就只用加个判断语句如果缓冲区的size等于capacity的话就调用write函数清空一下缓冲区,那么这咯的代码就如下:

    else if(fp->flags&SYNC_FULL) 
    {
        if(fp->size==fp->capacity)
        {
            write(fp->file_fd,fp->buffer,fp->size);
            fp->size=0;                                                                                                                 
        }                                                
    }   

由于我们这里只是简单的实现我们就不考虑特殊的情况,我们这里假设传递过来的字符串只有结尾有出现\n的可能,这样的话对于行缓冲我们只用判断一下缓冲区的最后一个字符是否是\n即可,那这里完整的代码就如下:

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->file_fd,fp->buffer,fp->size);
        fp->size=0;
    }      
    else if(fp->flags&SYNC_FULL) 
    {      
        if(fp->size==fp->capacity)
        {  
            write(fp->file_fd,fp->buffer,fp->size);
            fp->size=0;                                  
        }                                                                                                                               
    }                                                    
    else if(fp->flags&SYNC_LINE)                         
    {
        if(fp->buffer[fp->size-1]=='\n')                   
        {                                                

到这里我们的缓冲区模拟实现就完成,接下来我们就测试一下上面写的代码是否符合我们的预期。

测试代码

这里我们就测试一下行刷新,测试的代码如下:

   #include"mystdio.h"
   int main()
   {
       FILE_* fp=fopen_("tmp","w");
       fwrite_("abcdefghijklmn",15,fp);                                                                                                 
       fclose_(fp);
       return 0;
   }

这段代码的运行结果如下:
在这里插入图片描述
可以看到这里是正常运行的,因为字符串的末尾会自动添加\n,所以我们将测试的代码进行一下修改:

 #include"mystdio.h"
 int main()
 {
     const char* ptr="abcd";
     FILE_* fp=fopen_("tmp","w");
     fwrite_(ptr,4,fp);
     sleep(10);                                                                                                                      
     fwrite_(ptr,5,fp);
     fclose_(fp);
     return 0;
}

这段代码的运行结果如下:
在这里插入图片描述
一开始tmp文件里面没有任何数据,因为采用行缓冲的策略,等过了10秒再往文件里面输入内容,由于内容的末尾存在\n,所以文件里面就会出现内容:
在这里插入图片描述
那么这就是基本缓冲区的模拟实现,当然这些代码有很多的不足,但是本篇文章的目的是通过这些代码来让大家理解缓冲区的工作原理,并不是真正的实现。

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

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

相关文章

TSN网络流量记录器:一种经济高效的解决方案,用于验证汽车网络中的以太网融合。

在未来几年&#xff0c;汽车线束将从不同协议的异构网络转变为分层的同构以太网网络。在这种新情况下&#xff0c;模拟真实车辆网络的实验室测试台将需要分析工具以支持它们在车内通信验证过程中进行验证。 汽车向以太网融合原因 随着汽车内部技术变得越来越复杂&#xff0c;相…

信息调查的观念

每次做一件事前都要把这件事调查清楚&#xff0c;比如考一门科目我们要把和这门科目有关的资源都收集起来&#xff0c;然后把再从中筛选出有用的信息&#xff0c;如数值计算方法我们在考试前就可以把b站有关的学习资源网课或者前人总结的考试经验做个收集总结&#xff0c;做出对…

正则表达式 - 量词

目录 一、贪心、懒惰和占有 二、用 *、 和 ? 进行匹配 三、匹配特定次数 四、懒惰量词 五、占有量词 六、示例——括号字符串计数 {m,n} 是通用形式的量词&#xff0c;正则表达式还有三个常用量词&#xff0c;分别是 、?、*。它们的形态虽然不同于 {m,n}&#xff0c;功…

8年测试总结,项目/团队如何做自动化测试?效率价值?吐血整理...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Python自动化测试&…

Linux:centos:用户基础设置》》添加,修改属性,删除,修改密码

useradd &#xff08;属性&#xff09; 用户 新建用户 usermod &#xff08;属性&#xff09; 用户 调整用户属性 userdel &#xff08;属性&#xff09; 用户 删除用户 passwd &#xff08;属性&#xff09; 用户 修改用户密…

科技云报道:国内AI大模型鏖战,上演科技罗生门

科技云报道原创。 ChatGPT的狂热从年初持续至今&#xff0c;这份狂热不仅仅来源于用户层&#xff0c;从业者、投资人以及企业可以说有过之无不及。 于是&#xff0c;这些投资人、从业者以及企业将狂热转化&#xff0c;宣布入局大模型赛道并推出相关产品。一时间&#xff0c;大…

百万年薪架构师甩出的SpringBoot趣味实战手册,GitHub标星81.6K

前言 本书内容很全面&#xff0c;囊括了Spring生态的常用技术&#xff0c;如MVC、持久化、 Redis、定时任务、消息队列、搜索引擎。本书知识讲解由浅到深&#xff0c;循序渐进&#xff0c;从Hello World讲到Spring核心原理&#xff1b;技术讲解深入浅出&#xff0c;总能以“接…

无线传感网络的节点部署覆盖及能源消耗问题研究(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 随着微电子技术的不断发展,被称作传感器节点的微小智能嵌入式设备不断的被开发出来,推动了无线传感器网络的发展。一个典型的无…

Matlab Python 如何在figure上画出表格【优化】

之前写过一次博客 Matlab&Python 如何在figure上画出表格 这次是对该博客的优化 图的结果见下相对之前有很大的进步&#xff1a; coding 在这里包含数据的绘制&#xff0c;表格的添加&#xff0c;设置表格的大小、位置等 clc clear close all; path(path,E:\new_matlab_Too…

如何获取不同分区模板的基因表达矩阵,abagen: Allen 大脑图谱遗传数据工具箱的使用笔记

abagen: Allen 大脑图谱遗传数据工具箱的使用笔记 介绍使用abagen工具箱进行标准化处理和报告代码实例——获取Schaefer2018_400Parcels_7Networks的基因表达数据基于surf空间的模板基于volume空间的模板参考文献介绍 基因表达从根本上塑造了人类大脑的结构和功能结构。像Allen…

【Linux】shell编程—数组

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、shell数组1,数组的概念2.数组的定义 二、Shell数组操作1. 获取数组的所有元素的列表2. 获取数组的所有元素下标3.取数组的元素个数4. 获取数组的某个元素的值5.…

Jmeter 压测 QPS

文章目录 1、准备工作1.1 Jmeter的基本概念1.2 Jmeter的作用1.3.Windows下Jmeter下载安装1.4 Jmeter的目录结构1.5 启动1.6 设置中文1.6.1 设置调整1.6.2 配置文件调整&#xff08;一劳永逸&#xff09; 2、Jmeter线程组基本操作2.1 线程组是什么2.2 线程组2.2.1 创建线程组2.2…

原型part学习NeurIPS2019

当我们面临具有挑战性的图像分类任务时&#xff0c;我们希望通过分解part来解释推理。每一类别的更多原型证据有助于做出最终分类决策。作者提出一种深度网络架构&#xff1a;Prototypical Part网络即ProtoPNet。网络通过寻找原型part来解释图像&#xff0c;并基于原型part进行…

同步 Swagger URL问题, 用这个插件就可解决

这个开源的 API 管理工具叫 Postcat, 支持从 Swagger URL 增量同步 API 数据到 Postcat。 使用 进入 API 模块&#xff0c;鼠标移动到主按钮加号&#xff0c;下拉看到从 Swagger 同步 URL 的选项。 填写完配置点击立即同步即可同步 API 数据。 同步规则 新的数据覆盖旧的数据…

PHP语言调用api接口,电商平台商品详情接口(封装可高并发)

PHP是为Web而生的语言&#xff0c;它提供了一些强大的内置函数来处理HTTP请求和响应。PHP为开发人员提供了一些Web开发工具&#xff0c;包括HTML、CSS、JavaScript以及各种数据库的连接和互动。与其他Web开发工具相比&#xff0c;PHP可以更加高效地运转与发挥作用。 PHP表现出…

Matlab 非线性迭代法(3)阻尼牛顿法 L-M

高斯牛顿法详解_我只是一只自动小青蛙的博客-CSDN博客 一、思想 先看一下牛顿高斯迭代法的缺点&#xff1a; 1、在计算的过程中可能会出现奇异矩阵&#xff08;不满秩&#xff09;&#xff0c;比如&#xff1a;J(k)​)TJ(k) 为病态矩阵的时候就不能得到正确的解&#xff0c;或…

如何提升性能测试效能

上周六应邀在天津devops峰会的质量内建专场做了一次分享&#xff0c;主题是《稳定性保障利器&#xff1a;全链路压测》。 其中关于全链路压测对质量内建的意义&#xff0c;我做了一个总结&#xff0c;如下图所示。本文基于下图做了展开描述&#xff0c;仅供参考。 如何理解性能…

从零开始Vue3+Element Plus后台管理系统(八)——模仿禅道做一个Vue3版本的高级查询组件

暗黑模式 使用 Vue3element Plus 简单模仿了禅道系统的高级搜索组件&#xff0c;说简单也有点复杂&#xff0c;还没有完全开发完&#xff0c;但是大体架子有了&#xff0c;剩下一些功能点继续coding。边开发边记录吧&#xff0c;因为这个相比之前的内容确实复杂一些&#xff0c…

Java的基操,基操(一)

&#x1f525;二进制&#x1f525;二进制和十进制的转化&#x1f525;注释&#x1f525;标识符&#x1f525;关键字/保留字&#x1f525;变量(variable) &#x1f525;二进制 二进制&#xff0c;是计算技术中广泛采用的一种数制&#xff0c;由德国数理哲学大师莱布尼茨于 1679 …

Helm chart 常用命令以及原理和生产实践

问: 到哪里去搜索helm package? 答: artifacthub.io Helm 的实质就是搞一些模版&#xff0c;最终依据这些模版生成k8s的系列yaml文件(deployemnt,service,secret,map等等),从而在k8s上能够简单部署出完整应用。可以用helm template查看最终生成的k8s部署文件。 helm version…