Linux文件:缓冲区、缓冲区刷新机制 | C库模拟实现

Linux文件:缓冲区、缓冲区刷新机制 | C库模拟实现

  • 一、缓冲区的作用
  • 二、缓冲区的刷新机制
  • 三、测试样例解析
    • 3.1 测试样例和运行结果
    • 3.2 结果分析
      • 1、向显示器文件写入:
      • 2、向磁盘文件进行写入:
  • 四、语言级别的缓冲区究竟在哪?
  • 五、C库函数封装简单模拟
    • 5.1 结构体封装内容
    • 5.2 文件打开接口封装
    • 5.3 文件关闭和文件刷新
    • 5.4 向显示器文件写入

一、缓冲区的作用

 缓冲区本质上就是一部分内存,用于提高效率!!

 对于文件的IO等操作,用户可以直接通过系统调用直接向操作系统进行读操作和写操作。但这样时间开销较大。所以在语言层面一般会维护一段语言级别的缓冲区,用于暂存数据。我们可以快速向缓冲区中写入数据,然后通过一定的刷新方式。将数据从语言级别的缓冲区中拷贝到内核缓冲区。大大提高使用者的效率!!

&mesp;同时由于缓冲区的存在,我们可以积累一定的数据后在统一发生,提高发送的效率!!

二、缓冲区的刷新机制

 缓冲区可以暂存数据,必定存在一定的刷新机制。常见的刷新机制由以下3种:

  1. 无缓冲(立即刷新)
  2. 行缓冲(行刷新)
  3. 全缓冲(缓冲区全部写满后在刷新)

其中显示器文件的刷新机制就是行刷新;对于磁盘上的文件则是全缓冲!!

除此之外,还存在一些特殊的刷新机制:

  • 强制刷新。比如调用flush函数,以及printf在执行时,碰到/n时强制刷新!
  • 进程退出时,一般会刷新缓冲区!

三、测试样例解析

3.1 测试样例和运行结果

 下面我们调用3个常见的C库函数和一个系统调用,都向显示器文件中进行写入。当写入操作完毕后创建子进程,我们来看看分别向显示器文件磁盘文件中进行写入会发生什么?

int main()    
{    fprintf(stdout, "C: hello fprintf\n");    printf("C: hello printf\n");                                          fputs("C: hello fputs\n", stdout);    const char* buf = "system call: hello write\n";    write(1, buf, strlen(buf));    fork();    return 0;    
} 

 我们编译后,直接运行向显示器写入。然后通过输出重定向向log.txt磁盘文件进行写入:

【运行结果】:

在这里插入图片描述

3.2 结果分析

1、向显示器文件写入:

 当我们直接向显示器打印消息时,由于显示器的刷新机制是行刷新;并且我们所打印的字符串都带了‘\n(在printf中,'\n'是一种强制刷新的触发机制)。所以在fork()创建子进程前,数据已经全部被刷新!!

2、向磁盘文件进行写入:

 当我们重定向向磁盘文件写入数据时,缓冲区的刷新机制由行缓冲变成全缓冲。全缓冲也就意味着缓冲区变大,简单的几个字符串不足以将缓冲区写满,无法触发刷新机制

 此时缓冲数据还在缓冲区但当前缓冲区为C语言所提供的缓冲区,和操作系统无关。所以当前缓冲区中的数据依然属于进程。当fork()创建完子进程退出时,一般会刷新缓冲区。

 而刷新缓冲区本质上也是一种清空或者写入操作。所以父进程和子进程在退出前数据共享同一份;当退出时刷新缓冲区,对数据进行修改会发生写时拷贝,父、子进程各自私有一份。所以最后对于C函数调用会存在两份!!

 至于系统调用数据只打印一份的原因在于:系统调用在语言之下,数据不是向语言基本的缓冲区中写入;而是直接向操作系统内核缓冲区中写入。此时数据属于操作系统,不在属于进程!!

四、语言级别的缓冲区究竟在哪?

 下面我们以C为例。

 在此时样例中,我们已经知道对于库函数printf、fprintf自带缓冲区,而系统调用write则没有。(内核提提供的缓冲区此处不考虑)库函数时对系统调用的封装,这也意味着C所提供的缓冲区是二次加上的,由C本身所提供!!

 在C中,所有的IO函数都存在一个FILE指针,或者底层会封装FILE指针。而C的缓冲区的相关信息则保存在FILE结构体中,由FILE结构体来维护!!

FILE结构体内容】:
 在/usr/include/stdio.h路径下,存在这样一段代码typedef struct _IO_FILE FILE。所以可以通过下面操作查找FILE:
在这里插入图片描述

FILE结构体】:

/usr/include/libio.h
struct _IO_FILE {int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags//缓冲区相关/* The following pointers correspond to the C++ streambuf protocol. *//* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */char* _IO_read_ptr; /* Current read pointer */char* _IO_read_end; /* End of get area. */char* _IO_read_base; /* Start of putback+get area. */char* _IO_write_base; /* Start of put area. */char* _IO_write_ptr; /* Current put pointer. */char* _IO_write_end; /* End of put area. */char* _IO_buf_base; /* Start of reserve area. */char* _IO_buf_end; /* End of reserve area. *//* The following fields are used to support backing up and undo. */char *_IO_save_base; /* Pointer to start of non-current get area. */char *_IO_backup_base; /* Pointer to first valid character of backup area */char *_IO_save_end; /* Pointer to end of non-current get area. */struct _IO_marker *_markers;struct _IO_FILE *_chain;int _fileno; //封装的文件描述符#if 0int _blksize;
#elseint _flags2;
#endif_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary *//* 1+column number of pbase(); 0 is unknown. */unsigned short _cur_column;signed char _vtable_offset;char _shortbuf[1];/* char* _save_gptr; char* _save_egptr; */_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

五、C库函数封装简单模拟

 这里博主仅简单封装小部分C库函数,主要用于验证缓冲区。
【待实现接口函数】:

extern myFILE *my_fopen(const char *path, const char *mode); //打开文件
extern int my_fwrite(const char *s, int num, myFILE *stream);             
extern int my_fflush(myFILE *stream);    //刷新文件
extern int my_fclose(myFILE *fp); //关闭文件

5.1 结构体封装内容

 这里我们在结构体中封装文件描述符、缓冲区、刷新策略以及有效数据范围!!

  1. 这里我们实现的刷新策略只有三种:无缓冲、行刷新、全缓冲。
  2. 有效数据范围本应该通过一些指针来维护,这里博主简化为:有效数据从0开始,用有效数据个数来间接代替有效数据范围!
#define SIZE 4096    
#define FLUSH_NONE 1    
#define FLUSH_LINE (1<<1)    
#define FLUSH_ALL  (1<<2)typedef struct _myFILE    
{    char buffer[SIZE];	//缓冲区    int end;	//简化有效空间范围,从0开始    int flag;	//标志位,刷新策略    int fileno;    //文件描述符
}myFILE;

5.2 文件打开接口封装

 文件打开:

  1. 首先我们需要先获取文件的打开方式(这里博主仅实现w、r、a3种)
  2. 获取到打开方式后,我们需要判断文件是否存在。存在,直接通过系统调用正确打开文件;否则需要先创建文件。
  3. 既然文件打开了,最后就是修改结构体myFILE中的内容了!(这里默认创建文件的刷新方式为行刷新 )

【源代码】:

#define FILE_MODE 0666//文件创建默认权限myFILE *my_fopen(const char *path, const char *mode)
{int flag = 0;int fd = 0;//获取文件的打开方式if(strcmp(mode, "r") == 0){flag |= O_RDONLY;}else if(strcmp(mode, "w") == 0){flag |= (O_CREAT | O_WRONLY | O_TRUNC);}else if(strcmp(mode, "a") == 0){flag |= (O_CREAT | O_WRONLY | O_APPEND);}else{//do nothing}  //-------------------------------------------------------------------------        //打开文件if(flag & O_CREAT)//文件需要被创建{fd = open(path, flag, FILE_MODE);                                 }else{fd = open(path, flag);}//文件打开失败if(fd < 0){errno = 2;//没有该文件return NULL;}//------------------------------------------------------------------------- //修改结构体内容,默认创建文件的刷新方式为行刷新 myFILE *fp = (myFILE*)malloc(sizeof(myFILE));if(!fd){errno = 3;return NULL;}fp->flag = FLUSH_LINE;fp->fileno = fd;fp->end = 0;return fp;
}

5.3 文件关闭和文件刷新

  1. 关闭文件后,进程一般会将文件中的内容进行刷新。
  2. 但对于刷新而言,如果文件中由内容需要被刷新,我们直接调用系统调用接口即可完成!!
int my_fflush(myFILE *stream)
{if(stream->end > 0)//存在内容需要刷新{write(stream->fileno, stream->buffer, stream->end);//fscnc(stream->fileno);//强制内核缓冲区刷新stream->end = 0;}return 0;
}int my_fclose(myFILE *stream)
{my_fflush(stream);//刷新文件内容,是否存在内容需要刷新我们由my_fflush函数来判断return close(stream->fileno);
}

5.4 向显示器文件写入

 C库函数中fwrite的原型如下,这里博主简化直接传递数据个数,大小为1byte!!
在这里插入图片描述

  1. 向显示器文件中写入时,我们需要判断是否存在字符\n是会触发`fwrite’刷新机制,刷新缓冲区!
int my_fwrite(const char *s, int num, myFILE *stream)                     
{//写入memcpy(stream->buffer + stream->end, s, num + 1);stream->end += num;//判断是否需要刷新int i = stream->end - 1;if((stream->flag & FLUSH_LINE) && stream->end>0){while(i)//从后往前遍历查找是否存在'\n'{if(stream->buffer[i] == '\n'){my_fflush(stream);break;}i--;}}return i == 0 ? 0 : num;//返回刷新个数
}

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

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

相关文章

网络原理3

运营商路由器&#xff0c;也可以把它当做一个NAT设备它就会对中间经过的数据包&#xff0c;进行网络地址转换当内网设备经过运营商路由器访问外网的时候就会把IP数据包中的源ip&#xff0c;替换成它自己的ip. 我的电脑要发送一个数据给cctalk服务器此时&#xff0c;我的电脑上就…

二叉树求解大小操作详解

目录 一、求所有结点个数 1.1 递归思路 1.2 递归分支图 1.3 递归栈帧图 1.4 C语言实现 二、求叶子结点个数 2.1 递归思路 2.2 递归分支图 2.3 递归栈帧图 2.4 C语言实现 三、求第K层的结点个数 3.1 递归思路 3.2 递归分支图 3.3 递归栈帧图 3.4 C语言实现 四、求…

高性能负载均衡的分类及架构分析

如何选择与部署适合的高性能负载均衡方案&#xff1f; 当单服务器性能无法满足需求&#xff0c;高性能集群便成为提升系统处理能力的关键。其核心在于通过增加服务器数量&#xff0c;强化整体计算能力。而集群设计的挑战在于任务分配&#xff0c;因为无论在哪台服务器上执行&am…

新火种AI|净利润上升628%,英伟达财报说明AI热潮还将持续

作者&#xff1a;一号 编辑&#xff1a;美美 AI大潮仍未放缓&#xff0c;英伟达再次超越预期。 今天凌晨&#xff0c;全球AI算力芯片龙头&#xff0c;被称为“AI时代卖铲人”的英伟达&#xff0c;正式公布了截至2024年4月28日的2025财年第一财季财报&#xff0c;其中第一财季…

java8总结

java8总结 java8新特性总结1. 行为参数化2. lambda表达式2.1 函数式接口2.2 函数描述符 3. Stream API3.1 付诸实践 java8新特性总结 行为参数化lambda表达式Stream Api 1. 行为参数化 定义&#xff1a;行为参数化&#xff0c;就是一个方法接受多个不同的行为作为参数&#x…

C++第三方库【JSON】— jsoncpp

目录 认识JSON jsoncpp库 安装&使用 认识jsoncpp Json::Value jsoncpp序列化 jsoncpp反序列化 认识JSON JSON(JavaScript Object Notation)是一种轻量级的数据交换格式&#xff0c;采用完全独立于编程语言的文本格式来存储和表示数据&#xff0c;常用于在客户端和服…

钉钉网页应用使用JSAPI报错,dd.alert提示errorCode:3.errorMessage:No value for message

问题分析&#xff1a; 起因是我用下图这个页面&#xff08;配置JSAPI鉴权&#xff09;的链接下载了JSAPI&#xff08;客户端API&#xff09;的SDK&#xff0c;但其实如图所示这个版本是2.10.3&#xff1a; 通过查看dingtalk-jsapi的npm版本&#xff0c;可以知道钉钉的JSAPI已…

c++设计模式-->访问者模式

#include <iostream> #include <string> #include <memory> using namespace std;class AbstractMember; // 前向声明// 行为基类 class AbstractAction { public:virtual void maleDoing(AbstractMember* member) 0;virtual void femaleDoing(AbstractMemb…

荣耀MagicBook X 14 Pro锐龙版 2023 集显(FRI-H76)笔记本电脑原装出厂Windows11系统工厂模式安装包下载,带F10智能还原

恢复开箱状态预装OEM系统&#xff0c;适用型号&#xff1a;HONOR荣耀FRI-H76、FRI-H56 链接&#xff1a;https://pan.baidu.com/s/1Lcg45byotu5kDDSBs3FStA?pwdl30r 提取码&#xff1a;l30r 华为荣耀原装WIN11系统工厂安装包&#xff0c;含F10一键恢复功能、系统自带所有驱…

H800基础能力测试

H800基础能力测试 参考链接A100、A800、H100、H800差异H100详细规格H100 TensorCore FP16 理论算力计算公式锁频安装依赖pytorch FP16算力测试cublas FP16算力测试运行cuda-samples 本文记录了H800基础测试步骤及测试结果 参考链接 NVIDIA H100 Tensor Core GPU Architecture…

快速搭建SpringMvc项目

一、什么是springMvc 1、介绍 Spring Web MVC是基于Servlet API构建的原始Web框架&#xff0c;从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称&#xff08; spring-webmvc &#xff09;&#xff0c;但它通常被称为“Spring MVC”。 在控制…

NFT开发框架和工具

NFT&#xff08;非同质化代币&#xff09;开发涉及多个框架和工具&#xff0c;帮助开发者创建、管理和交易NFT。以下是一些常用的NFT开发框架和工具&#xff0c;这些框架和工具覆盖了NFT开发的各个方面&#xff0c;从智能合约编写到前端集成&#xff0c;再到区块链平台和市场&a…

syncthing文件夹同步与版本管理

1 前言 syncthing可以用来同步文件夹里的所有文件&#xff0c;并且有不错的版本管理&#xff0c;基本每次更改文件&#xff0c;20-40秒就被扫描到了&#xff0c;非常丝滑&#xff1b;这次以此来同步obsidian的插件和文件&#xff0c;达到多端同步&#xff1b; 我家里有一台台…

ubuntu设置root开机登录,允许root用户ssh远程登录

ubuntu与centos系统不同&#xff0c;默认root开机不能登录。 1、输入一下命令创建root密码&#xff0c;根据提示输入新密码 sudo passwd root 2、打开gdm-autologin文件&#xff0c;将auth required pam_succeed_if.so user ! root quiet_success这行注释掉&#xff0c;这行就…

leetCode-hot100-数组专题之区间问题

数组专题之区间问题 知识点&#xff1a;解决思路&#xff1a;例题56.合并区间57.插入区间253.会议室 Ⅱ485.无重叠区间 数组区间问题是算法中常见的一类问题&#xff0c;它们通常涉及对数组中的区间进行排序、合并、插入或删除操作。无论是合并区间、插入区间还是删除重复空间&…

使用ScriptGraphicHelper综合图色助手进行找色

使用ScriptGraphicHelper综合图色助手进行找色&#xff0c;然后使用autojs进行点击具体位置。 打开ScriptGraphicHelper软件&#xff0c;载入截图后如上图&#xff0c;比如要点击微信 按住鼠标左键&#xff0c;拖动&#xff0c;选择上图箭头位置,然后点击裁图 可以点击容差范围…

微服务如何做好监控

大家好&#xff0c;我是苍何。 在脉脉上看到这条帖子&#xff0c;说阿里 P8 因为上面 P9 斗争失败走人&#xff0c;以超龄 35 被裁&#xff0c;Boss 上找工作半年&#xff0c;到现在还处于失业中。 看了下沟通记录&#xff0c; 沟通了 1000 多次&#xff0c;但没有一个邀请投递…

基于深度学习的表情识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 随着人工智能技术的快速发展&#xff0c;表情识别成为了人机交互领域的一个研究热点。表情识别技术旨…

【四、性能测试】Linux stress 压力模拟测试工具

在做 CPU 问题解析之前&#xff0c;需要先了解一下压力模拟工具&#xff0c;可以将 CPU、MEM、IO 等进行压力模拟&#xff0c;可以在模拟压力的过程中进行问题解析 一、STRESS 模拟对CPU、Memory、IO、磁盘进行压力测试。可以使用 stress 工具&#xff0c;它是专门针对 linux…

如何将Docker容器打包并在其他服务器上运行

如何将Docker容器打包并在其他服务器上运行 我会幻想很多次我们的相遇&#xff0c;你穿着合身的T恤&#xff0c;一个素色的外套&#xff0c;搭配一条蓝色的牛仔裤&#xff0c;干净的像那天空中的云朵&#xff0c;而我&#xff0c;还是一个的傻傻的少年&#xff0c;我们相识而笑…