缓冲区的奥秘:如何模拟实现C库的FILE结构体和接口

📟作者主页:慢热的陕西人

🌴专栏链接:Linux

📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言

本博客主要内容模拟实现了C库内部的FILE结构体及其对应的接口然后从内核角度再次深入理解了缓冲区的概念

文章目录

    • 1.C库FILE结构体的模拟实现
      • 1.1MY_FILE结构体
      • 1.2myfopen的实现
      • 1.3myfclose的实现
      • 1.4 myfwrite 的实现
      • 1.5myfflush函数的实现
      • 1.6那么我们为什么需要缓冲区?
    • 2.关于缓冲区
      • 2.1fsync接口
      • 2.2printf的执行思路
      • 2.3scanf的执行思路
    • 3.完整代码

1.C库FILE结构体的模拟实现

1.1MY_FILE结构体

因为我们简单实现:包含参数

  • ①文件描述符
  • ②输出缓冲区
  • ③刷新方式的标志位
  • ④缓冲区的当前大小
typedef struct _MY_FILE    
{    int fd;    char outputbuffer[MAX];    int flag; //flash method    int current; // flash size    
} MY_FILE;  

1.2myfopen的实现

主要分四步(也是只进行主题功能的实现):

  • ①文件打开方式的获取
  • ②尝试打开文件(通过操作系统提供的接口)
  • ③构建MY_FILE结构体
  • ④初始化MY_FILE结构体
  • ⑤返回MY_FILE结构体
MY_FILE *myfopen(const char *path, const char *mode){//1.文件的打开方式int flag = 0;if(strcmp(mode,"w") == 0)       flag |= (O_CREAT | O_TRUNC | O_WRONLY);else if(strcmp(mode,"r") == 0)  flag |= O_RDONLY;else if(strcmp(mode, "a") == 0) flag |= (O_CREAT | O_WRONLY| O_APPEND);else {//其余情况//"a+" "w+" 等等}//2.尝试打开文件mode_t m = 0666;int fd = 0;if(flag & O_CREAT) fd = open(path, flag, m);else fd = open(path, flag);if(fd < 0) return NULL;//3.构建MY_FILE结构体MY_FILE* fp = (MY_FILE*)malloc(sizeof(MY_FILE));                                                                                                                                          if(fp == NULL) {close(fd);return NULL;}//4.初始化MY_FILE结构体fp->fd = fd;fp->flag = 0;fp->flag |= BUFF_LINE;memset(fp->outputbuffer, '\0', sizeof(fp->outputbuffer));//返回MY_FILE结构体return fp;}

1.3myfclose的实现

myfclose主要完成以下四个任务:

  • ①清空缓冲区
  • ②关闭文件
  • ③释放堆区的空间
  • ④指针值空
  int myfclose(MY_FILE *fp){//1.清空缓冲区myfflush(fp);//2.关闭文件close(fp->fd);//3.释放堆区的空间free(fp);//4.指针置空fp = NULL;return 0;} 

1.4 myfwrite 的实现

分以下四点:

  • ①判断缓冲区是否已满,满了就直接刷新缓冲区

  • ②根据缓冲区的空间进行数据拷贝

  • ③分两种情况向缓冲区中写入:

    a.缓冲区中的空间大于当前要写入缓冲区的空间大小

    b.缓冲区中的空间小于当前要写入缓冲区的空间大小

  • ④根据刷新方式进行刷新:

    a.全刷新

    b.行刷新

size_t myfwrite(const void *ptr, size_t size, size_t nmemb,MY_FILE *stream)
{//1.如果缓冲区满了就直接刷新缓冲区if(stream->current == NUM) myfflush(stream);//2.根据缓冲区的空间进行数据拷贝size_t user_size = size * nmemb;  //要导入缓冲区的空间大小size_t my_size = NUM - stream->current; //缓冲区中剩余的大小size_t writen = 0;    //写入缓冲区的字节数//如果缓冲区的剩余空间够写入if(my_size >= user_size){memcpy(stream->outputbuffer + stream->current, ptr, user_size);stream->current += user_size;writen = user_size;}else{memcpy(stream->outputbuffer + stream->current, ptr, my_size);                                                                                stream->current += my_size;writen = my_size;}//计划刷新缓冲区//全刷新if(stream->flag & BUFF_ALL){if(stream->current == NUM) myfflush(stream);}else if(stream->flag & BUFF_LINE){if(stream->outputbuffer[stream->current - 1] == '\n') {myfflush(stream);}}else{//..}//返回的是写入成功的字节数return writen;
}

1.5myfflush函数的实现

写入后将当前缓冲区占用空间置零即可!

int myfflush(MY_FILE* fp)
{assert(fp); write(fp->fd, fp->outputbuffer, fp->current);  fp->current = 0;return 0;
}

到这里我们就可以实现一些简答你的IO:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.6那么我们为什么需要缓冲区?

原因是IO在计算机中是非常占用时间的,所以我们只能减少IO的次数,以提高计算机的运行效率,我们将多次的数据存储在一个缓冲区中,然后一次IO多次的数据,达到了提高计算机运行效率的目的。

那么IO的本质就是和外设做交互,和显示器做交互

2.关于缓冲区

我们之前所谈的缓冲区都是:用户级缓冲区。

我们接下来建立一个完整的理解:用户层+内核的理解

实际上我们的工作模式是:

当我们每次打开一个文件的时候,是我们的进程在访问我们的文件,进程需要有文件访问描述符表和打开的文件,文件自带缓冲区。磁盘,我们访问文件的时候一定要有路径。有路径就可以标识磁盘中的某一个位置从而访问到我们的文件。我们使用的C语言,实际上它是一个C标准库中有一个叫libc.a/so的。那么这些库中给我提供了很多方法。我们平时写的代码中会涉及到fwrite/fputs/fprintf...这类的函数。然而这些的函数都是需要一个叫FILE*参数帮我们去做的。而这个FILE*参数是当我们调用open接口的时候,由库给我们创建的。实际上缓冲区是进程帮我们在我们堆空间帮我们申请的。那么我们可以理解为缓冲区在我们的C库里面,也可以认为在我们的进程的堆空间内部是一样的。我们这里就放在库里面去理解。我们这里所说的一系列函数fwrite/fputs/fprintf...实际上是我们的拷贝函数,他们帮我们把对应的数据拷贝到我们的缓冲区里。然后在结合打开的文件的类型,以及我们对于缓冲区的使用情况的刷新策略。然后定期的把我们的数据,从我们的缓冲区里面结合操作系统提供的write()系统调用接口,来帮我们完成将缓冲区中的信息写入到我们的文件内部。所以其实write()的工作其实也是拷贝函数!。那么之前fwrite/fputs/fprintf...这一系列函数是帮我们把信息从用户到语言的库的缓冲区内部。那么write()函数的是从语言的库的缓冲区到我们的操作系统的内核。所以当我们的数据到我们的缓冲区之后:操作系统就会有自己的刷新策略了,并且操作系统的刷新策略一定要更加的复杂。

image-20231117124420926

那么最后总结一下:

我们将一个信息从输入计算机到刷入到我们的硬件上,至少要经过三步:

  • ①在C/C++上先刷新到我们的语言缓冲区内部
  • ②通过系统调用将数据拷贝到我们的内存
  • ③将数据从内存拷贝到我们的外设(磁盘)

如果我想强制刷新我们的内核怎么办?

2.1fsync接口

这个接口的作用是:函数把 fd 对应的文件数据以及文件属性信息(inode等信息)写入到磁盘,并且等待写磁盘操作完成后而返回。意思就是我们强制让操作系统刷新缓冲区,并且把数据写入到磁盘。

我们在myfflush函数的内部加上:

int myfflush(MY_FILE* fp)      
{            assert(fp);       write(fp->fd, fp->outputbuffer, fp->current);        fp->current = 0;      fsync(fp->fd);    return 0;    
} 

当我们给写入文件的信息不加\n的时候:

在这里插入图片描述

我们设定为5秒一次刷新:

动态效果1


**那么我们平时使用的printf实际上不管我们让它输入的是什么格式整数浮点数,其实最终都转换成了字符串的形式!**那么格式转化是谁做的呢?在哪里做的呢?实际上也是printf帮我们做的,它内部有很多的参数,叫格式控制块:%d%s等等!

2.2printf的执行思路

我们只需要完成对应的以下四点即可:

  • ①先获取对应的变量

  • ②定义缓冲区,对变量转成字符串

    a.fwrite(stdout, str);

  • ③将字符串拷贝的stdout->buffer,即可

  • ④结合刷新策略显示即可

2.3scanf的执行思路

实际上scanf是先将我们输入的信息存储到一个叫stdin->buffer。然后对我们的buffer内容进行格式化,然后将格式化的内容写入到我们的变量中。

  • ①调用read(0, stdin->buffer, num)从0号文件读取到我们的stdin->buffer内部。
  • ②紧接着扫描我们输入的字符串:碰到空格就将字符串分割成两个子串,*ap = atoi(str1); *bp = atoi(str2);

3.完整代码

mystdio.c

#include"mystdio.h"    #define NUM 1024    int myfflush(MY_FILE* fp)    
{    assert(fp);    write(fp->fd, fp->outputbuffer, fp->current);    fp->current = 0;    fsync(fp->fd);                                                                      return 0;    
}    MY_FILE *myfopen(const char *path, const char *mode)    
{    //1.文件的打开方式    int flag = 0;    if(strcmp(mode,"w") == 0)       flag |= (O_CREAT | O_TRUNC | O_WRONLY);    else if(strcmp(mode,"r") == 0)  flag |= O_RDONLY;    else if(strcmp(mode, "a") == 0) flag |= (O_CREAT | O_WRONLY| O_APPEND);    else    {    //其余情况    //"a+" "w+" 等等    }    //2.尝试打开文件    mode_t m = 0666;    int fd = 0;    if(flag & O_CREAT) fd = open(path, flag, m);    else fd = open(path, flag);    if(fd < 0) return NULL;    //3.构建FILE结构体MY_FILE* fp = (MY_FILE*)malloc(sizeof(MY_FILE));if(fp == NULL) {close(fd);return NULL;}//4.初始化FILE结构体fp->fd = fd;fp->flag = 0;fp->flag |= BUFF_LINE;memset(fp->outputbuffer, '\0', sizeof(fp->outputbuffer));//返回MY_FILE结构体return fp;
}size_t myfwrite(const void *ptr, size_t size, size_t nmemb,MY_FILE *stream)
{//1.如果缓冲区满了就直接刷新缓冲区if(stream->current == NUM) myfflush(stream);//2.根据缓冲区的空间剩余清空进行数据拷贝size_t user_size = size * nmemb;  //要导入缓冲区的空间大小size_t my_size = NUM - stream->current; //缓冲区中剩余的大小size_t writen = 0;    //写入缓冲区的字节数//如果缓冲区的剩余空间够写入if(my_size >= user_size){memcpy(stream->outputbuffer + stream->current, ptr, user_size);stream->current += user_size;writen = user_size;}else{memcpy(stream->outputbuffer + stream->current, ptr, my_size);stream->current += my_size;writen = my_size;}//计划刷新缓冲区//全刷新if(stream->flag & BUFF_ALL){if(stream->current == NUM) myfflush(stream);}else if(stream->flag & BUFF_LINE){if(stream->outputbuffer[stream->current - 1] == '\n') myfflush(stream);}                                                                                 }else{//..}//返回的是写入成功的字节数return writen;
}int myfclose(MY_FILE *fp)
{//1.清空缓冲区myfflush(fp);//2.关闭文件close(fp->fd);//3.释放堆区的空间free(fp);//4.指针置空fp = NULL;return 0;
}

mystdio.h

#pragma once    #include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<string.h>    
#include<stdlib.h>    
#include<unistd.h>    
#include<assert.h>    #define MAX 1024    
#define BUFF_NONE 0x1    
#define BUFF_LINE 0x2    
#define BUFF_ALL  0x4    typedef struct _MY_FILE    
{    int fd;    char outputbuffer[MAX];                                                             int flag; //flash method    int current; // flash size    
} MY_FILE;    int myfflush(MY_FILE* fp);    
MY_FILE *myfopen(const char *path, const char *mode);    
size_t myfwrite(const void *ptr, size_t size, size_t nmemb,MY_FILE *stream);    
int myfclose(MY_FILE *fp); 

main.c

#include"mystdio.h"    #define MYFILE "log.txt"    int main()    
{    MY_FILE* fp = myfopen(MYFILE, "w");    const char* str = "hello my write";    //操作file    int cnt = 500;                                                                      while(cnt)    {    char buffer[1024];    snprintf(buffer, sizeof(buffer), "%s:%d", str, cnt--);    // printf("%s", buffer);    size_t size = myfwrite(buffer,strlen(buffer), 1, fp);    printf("我写入了%lu个字节\n", size);    if(cnt % 5 == 0) myfflush(fp);    sleep(1);    }    myfclose(fp);    
}

到这本篇博客的内容就到此结束了。
如果觉得本篇博客内容对你有所帮助的话,可以点赞,收藏,顺便关注一下!
如果文章内容有错误,欢迎在评论区指正

在这里插入图片描述

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

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

相关文章

2023感恩节倒计时:跨境卖家如何借势风潮,成功脱颖而出?

感恩节&#xff0c;是西方世界最为重要的家庭聚会时刻之一&#xff0c;也是全球购物狂欢的开端。对于跨境电商卖家而言&#xff0c;这一时刻既是挑战&#xff0c;更是机遇。在感恩节大促倒计时的紧张氛围中&#xff0c;如何让自己的产品在激烈的市场竞争中脱颖而出&#xff0c;…

算法训练 第八周

二、路径总和 1.深度优先搜索 使用递归的方式遍历二叉树&#xff0c;判断当前节点是否为叶子节点&#xff0c;如果是叶子节点&#xff0c;判断路径和是否等于目标和。如果不是叶子节点&#xff0c;则递归遍历左右子树&#xff0c;直到找到叶子节点或者遍历完整个二叉树。具体代…

转录组学习第四弹-数据质控

数据质控 将SRR转为fastq之后&#xff0c;我们需要对fastq进行质量检查&#xff0c;排除质量不好的数据 1.质量检查&#xff0c;生成报告文件 ls *fastq.gz|while read id;do fastqc $id;done并行处理 ls *fastq.gz|xargs fastqc -t 102.生成 html 报告文件和对应的 zip 压缩…

在网页中添加水印的实现方法

在网页设计中&#xff0c;为了保护内容的版权以及增加一些特殊效果&#xff0c;经常需要在页面上添加水印。本文将介绍一种通过Canvas和JavaScript实现在网页上添加水印的方法。 功能&#xff1a; 允许自定义水印内容、字体颜色可以防止用户删除水印元素、修改样式等其他手段…

JNPF开发平台凭什么火?

一、关于低代码 JNPF平台在提供无代码&#xff08;可视化建模&#xff09;和低代码&#xff08;高度可扩展的集成工具以支持跨功能团队协同工作&#xff09;开发工具上是独一无二的。支持简单、快速地构建及不断改进Web端应用程序&#xff0c;可为整个应用程序的生命周期提供全…

Ubuntu18 Opencv3.4.12 viz 3D显示安装、编译、移植

Opencv3.*主模块默认包括两个3D库 calib3d用于相机校准和三维重建 ,viz用于三维图像显示,其中viz是cmake选配。 参考: https://docs.opencv.org/3.4.12/index.html 下载linux版本的源码 sources。 查看cmake apt list --installed | grep cmake 查看vtk apt list --ins…

《白帽子讲web安全》

第十四章 PHP安全 文件包含漏洞是“代码注入”的一种。“代码注入”这种攻击&#xff0c;其原理就是注入一段用户能控制的脚本或代码&#xff0c;并让服务器端执行。“代码注入”的典型代表就是文件包含&#xff08;File Inclusion&#xff09;。文件包含可能会出现在JSP、PHP…

DeepStream--测试TrafficCamNet检测模型

模型地址&#xff1a;https://catalog.ngc.nvidia.com/orgs/nvidia/teams/tao/models/trafficcamnet/version 目前模型是nvidia的加密格式etlt。 nvinfer的配置 [property] gpu-id0 net-scale-factor0.0039215697906911373 tlt-model-keytlt_encode tlt-encoded-modeltraffic…

react等效memo的方法

视频教程 前端技术&#xff5c;Dan博客&#xff5c;在你写memo()之前_哔哩哔哩_bilibili 把与ExpensiveTree的无关的dom做成一个组件 第二种情况&#xff0c;color在ExpensiveTree组件的父级dom 创建一个组件&#xff0c;将state的color和input写上&#xff0c;而ExpensiveTr…

hook io异常注入

文中code https://gitee.com/bbjg001/darcy_common/tree/master/io_hook 需求引入 最近工作需要&#xff0c;需要验证一下我们的服务在硬盘故障下的鲁棒性。 从同事大佬哪里了解到hook技术&#xff0c;可以通过LD_PRELOAD这个环境变量拦截依赖库的调用链&#xff0c;将对标准…

微信小程序记住密码,让登录解放双手

密码是用户最重要的数据&#xff0c;也是系统最需要保护的数据&#xff0c;我们在登录的时候需要用账号密码请求登录接口&#xff0c;如果用户勾选记住密码&#xff0c;那么下一次登录时&#xff0c;我们需要将账号密码回填到输入框&#xff0c;用户可以直接登录系统。我们分别…

OpenCV入门9——目标识别(车辆统计)

文章目录 图像轮廓查找轮廓绘制轮廓轮廓的面积与周长多边形逼近与凸包外接矩形项目总览【车辆统计】视频加载【车辆统计】去背景【车辆统计】形态学处理【车辆统计】逻辑处理【车辆统计】显示信息【车辆统计】 图像轮廓 查找轮廓 # -*- coding: utf-8 -*- import cv2 import n…

Threejs_09 gltf模型的引入(效果初现)

本节使用到的图片、素材、gltf文件&#xff0c;都是老陈打码的原素材 支持&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#x…

SQL DELETE 语句:删除表中记录的语法和示例,以及 SQL SELECT TOP、LIMIT、FETCH FIRST 或 ROWNUM 子句的使用

SQL DELETE 语句 SQL DELETE 语句用于删除表中的现有记录。 DELETE 语法 DELETE FROM 表名 WHERE 条件;注意&#xff1a;在删除表中的记录时要小心&#xff01;请注意DELETE语句中的WHERE子句。WHERE子句指定应删除哪些记录。如果省略WHERE子句&#xff0c;将会删除表中的所…

ANSYS中如何手动为装配体添加接触约束教程

接触的类型&#xff1a; 在接触类型&#xff08;Type&#xff09;选项中&#xff0c;软件共提供了绑定接触&#xff08;Bonded&#xff09;、不分离接触&#xff08;No Separation&#xff09;、无摩擦接触&#xff08;Frictionless&#xff09;、粗糙接触&#xff08;Rough&a…

深入Ansible

1.什么是ansible ansible是新出现的自动化运维工具&#xff0c;基于Python开发&#xff0c;集合了众多运维工具&#xff08;puppet、chef、func、fabric&#xff09;的优点&#xff0c;实现了批量系统配置、批量程序部署、批量运行命令等功能。 ansible是基于 paramiko 开发的…

Linux操作系统使用及C高级编程-D11-D13结构体

由一批数据组合而成的结构型数据。组成结构型数据的每个数据称为结构型数据的“成员”&#xff0c;其描述了一块内存空间的大小及解释意义。 语法&#xff1a; struct 结构体名 { 结构体成员列表 }; 下图是struct的定义和使用方法&#xff0c;其中20行这种赋值方式错误&#xf…

向pycdc项目提的一个pr

向pycdc项目提的一个pr 前言 pycdc这个项目&#xff0c;我之前一直有在关注&#xff0c;之前使用他反编译python3.10项目&#xff0c;之前使用的 uncompyle6无法反编译pyhton3.10生成的pyc文件&#xff0c;但是pycdc可以&#xff0c;但是反编译效果感觉不如uncompyle6。但是版…

BGP笔记

自治系统----AS AS划分的原因&#xff1a;如果网络太大&#xff0c;路由数量进一步增加&#xff0c;路由表规模变得太大&#xff0c;会导致路由收敛速度变慢&#xff0c;设备性能消耗加大&#xff0c;且全网设备对于路由信息的认知不同&#xff0c;造成流量通讯障碍 AS号&…