【Linux笔记】缓冲区的概念到标准库的模拟实现

一、缓冲区

“缓冲区”这个概念相信大家或多或少都听说过,大家其实在C语言阶段就已经接触到“缓冲区”这个东西,但是相信大家在C语言阶段并没有真正弄懂缓冲区到底是个什么东西,也相信大家在C语言阶段也因为缓冲区的问题写出过各种bug。

其实这也不奇怪,因为“缓冲区”这个概念其实已经不是语言层面的东西了,而是系统层面的东西。所以今天我们就要来好好的认识一下这个让我们即熟悉又陌生的“缓冲区”。

1.1、什么是缓冲区?

“缓冲区”我们简单的理解就是一个数据暂存库,当我们要将数据从一个地方传送到另一个地方的时,可以先将数据暂存到这个暂存库中,等时机到了再将数据传送到目标地点。

这就好比我们生活中的快递站,当我们要把一个东西送给另一个人时,就可以先将数据放到快递站,快递站到了时间就会发货,以送往目的地。

而我们的操作系统会为每一个被打开的文件创建一个缓冲区。我们知道,我们创建的文件最终都是要被存储在磁盘中的,而我们要对一个文件进行增删查改操作时,一定要先将文件加载到内存,这时候操作系统为了管理被打开的文件,就要为被打开的文件创建一个struct file结构体对象,这个结构体对象中有很多关于管理这个文件的属性,包括文件的大小、文件的创建时间、文件名、文件的操作方法集、文件的权限、等等……

而其中就有一个缓冲区,当我们要要向文件中写入数据时,就可以先将数据写入到这个缓冲区中,等到了“一定时间”后,操作系统就会将我们写入到缓冲区中的数据统一写入到磁盘中。

而这个struct file是一个内存中的结构体,所以缓冲区的本质其实就是一个内存块。

结构大致如下图所示:

1.2、为什么要有缓冲区

那为什么要有有这么一个缓冲区夹在内存和磁盘中间呢?为什么我们不能直接将数据写入磁盘呢?

这是因为冯诺依曼体系结构:cpu不直接跟外设打交道,cpu是通过内存和外设打交道的,磁盘也属于外设,所以cpu不直接跟磁盘打交道。因为相对于cpu来说磁盘等外设的运行速度实在是太慢了,与cpu的速度相差太多,如果cpu直接跟外设打交道,cpu不然就要等待外设,这就会大大的降低cpu执行的效率。

所以我们我们每次向文件中写入一点东西就直接写入磁盘中,那必定就要求cpu每次都要执行拷贝任务,向磁盘中写入,这样速度就慢了。

所以我们才需要一个缓冲区,先将要写入磁盘的数据暂存起来,等到某一时间后再统一写入到磁盘中,这其实是减少了cpu与磁盘交互的次数从而提高效率。

而将数据从缓冲区中写入到磁盘中称之为缓冲区的刷新,缓冲区的刷新一般有一下5种形式:

1、无缓冲(立即刷新)

2、行缓冲(行刷新)

3、全缓冲(缓冲区满了,再刷新)

4、强制刷新

5、进程退出,自动刷新

无缓冲我这里找不到对应的场景。

行缓冲就是缓冲一行,具体到语法层面就是如果我们在打印的字符串中添加了‘\n’,那就算一行了,那就会将这一行刷新:

比如上面的代码,如果我们加上两个'\n',运行的时候就会全都刷新出来:

而如果我们只在hello后面加上‘\n’,那执行的时候,就会只打印出hello:

那后面的内容呢?答案是:存储在缓冲区之中。

全缓冲就是缓冲区满了,必须刷新了。

强制刷新一般是我们使用一些系统调用来强制刷新缓冲区,例如我们以前是用过的fflush,例如下面这个例子:

上面的代码,如果我们直接运行,一开始是看不到打印的信息的:

只有等时间到了,进程退出的时候自动刷新才能看得见:

如果我们想要让信息立即刷新出来就可以调用fflush:

1.3、语言缓冲区和内核缓冲区

我们上面所谈到的“缓冲区”其实是“内核级缓冲区”,但在我们的语言层面其实还有一个“语言级缓冲区”。

想要讲清楚内核级缓冲区和语言级缓冲区,我们得要从一个奇怪的例子入手:

执行上面的代码,如果我们正常执行,它就是在显示器上按顺序打印出我们所写的内容:

这个没什么问题,但如果我们在执行的时候加上个重定向操作,其结果就有一丢丢奇怪了:

我们会发现代码里面写在最后的write的内容竟然输出到了文件的最开头。

而如果我们再在代码的最后面加上一个fork的话,又会发生什么呢?

如果正常运行的话,那它和不带fork的代码的执行结果是一样的:

但如果我们在执行的时候加上一个重定向,那结果就会变得非常奇怪了:

我们会发现一个,这里出了系统调用的打印会打印到文件的最前面之外,C库函数的打印都打了两次!

想要解释这样的现象,我们就得要慢慢分析了:

1、首先有一个告知的结论是:我们直接向显示器上打印的时候,显示器文件的默认刷新方式是行刷新,所以我们前面的例子中才会出现只要有一个'\n'就会刷新一次的现象。而我们重定向就是向磁盘文件中写入的时候,磁盘文件的刷新方式是是全缓冲,也就是默认等缓冲区写满再刷新,但默认全缓冲不代表一定得等缓冲区写满才能更新,也有可能强制刷新或者是进程退出了自动刷新。

2、我们上面所写的打印信息并不足以将缓冲区写满,所以在fork执行后,数据依旧在缓冲区里面,没有被刷新。

3、而我们在fork创建子进程之后,子进程会进程父进程的文件描述符表和数据:

如上图,所以两个进程的1号文件描述符都指向了log.txt的struct flie,也就是说两个进程都会重定向到log.txt中。

而我们在代码中所写的各种C语言接口的打印,其实并没有直接将数据写入到struct file中的内核文件缓冲区中,而是写入到了C语言给我们提供的一个语言缓冲区中:

所以我们的数据其实是存在两份的,最后当父子进程无论哪个先退出,都会发生“写时拷贝”并刷新缓冲区,将数据写入到内核文件缓冲区中,所以我们代码中C库函数打印的信息,其实是向内核缓冲区中写入了两次的。

而系统调用write的数据只写入了一次,这足以说明,系统调用使用的并非是C语言缓冲区,而是内核缓冲区,它是直接将数据写入到内核缓冲区中的。

二、模拟实现C标准文件操作库

有了上面的这些理论,我们再通过实践来感受一下,缓冲区与C语言标准文件操作库的关系。

我们来模拟实现一个简易的Cstdio库。

我们毕竟不是要实现一个多么完善的stdio文件操作库,只是简单的模拟一下,懂个原理就行了,所以我们就只模拟实现三个接口,分别是fopen、fwrite、fflush、fclose。

首先我们要知道,在C语言中大多的文件操作接口的返回值都是一个FILE*指针,这说明C语言中使用FILE这个结构体来管理被打开的文件的,所以我们还需要在我们自己的stdio头文件中包装一个自己的FILE结构体,而这个结构体中的成员我们今天也不要封装得太复杂,大致像下面这样简单一点就行了:

然后就是声明一下我们自己要实现的一些文件操作接口,模拟库中的即可:

2.1、模拟实现fopen

打开文件的方式第一步一定是先判断文件的打开方式,这里直接用if语句做条件判断即可,判断完了打开方式后,真正打开文件的工作一定是要交给系统调用的:

2.2、模拟实现fflush

然后我们要先来实现fflush,因为无论是close文件或是,向文件中写数据,都需要刷新缓冲区,所以我们要先实现一下fflush。

刷新的本质其实就是将缓冲区内的数据拷贝到内核文件缓冲区中,也就是写入到内核缓冲区中的中,而write正是直接写入到内核缓冲区中的,所以我们需要做的就是调用write将buffer中的数据写入就行了:

 2.3、模拟实现fclose

关闭文件就更简单了,我们只需要在退出前先刷新缓冲区,然后在调用系统调用close关闭文件,最后再讲FILE结构体释放就行了:

2.4、模拟实现fwrite

实现write我们,使用内存拷贝函数,将传过来的数据拷贝到我们的buffer缓冲区中即可,但最后还需要额外判断是否s后面是否包含‘\n’,如果有我们还需要将缓冲区刷新,即行刷新:

测试一下:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

再识C语言 DAY16【进制的转换 】

文章目录 前言进制的转换一、各个进制的组成二、二进制转换其他进制三。其他进制转换为二进制四.小数部分进制转换五.八进制与十进制的相互转换 总如果您发现文章有错误请与我留言,感谢 前言 本文章总结于此视频 进制的转换 一、各个进制的组成 1. 二进制&#x…

【C语言自定义类型详解进阶】结构体(补充结构体的对齐和位段,一口气看完系列,央妈都点赞的博文)

目录 1.结构体 1.1 结构的基础知识 1.2 结构的声明 1.2.1特殊的声明(匿名结构体类型) 1.3结构体变量的定义 1.4关于匿名结构体类型的补充 1.5结构体的自引用 1.6结构体变量的初始化 2.结构体内存对齐(重点) 2.1偏移量补…

报错ValueError: Unknown CUDA arch (8.6) or GPU not supported

文章目录 问题描述解决方案参考文献 问题描述 报错 ValueError: Unknown CUDA arch (8.6) or GPU not supported 本人显卡为 RTX 3060,CUDA 为 10.2,PyTorch 为 1.5 解决方案 修改 C:\Users\Administrator\Envs\test\Lib\site-packages\torch\utils\c…

nvm安装nodejs 报错certificate has expired or is not yet valid

今天在使用nvm安装nodejs时,突然报如下错误: 从报错信息中很容易知道这是因为镜像凭证过期,所以我们只需要换个镜像即可。 打开你nvm的安装目录下的settings.txt文件,将下面两行添加到里面,如果已经有的就覆盖。 nod…

【制作100个unity游戏之24】unity制作一个3D动物AI生态系统游戏3(附项目源码)

最终效果 文章目录 最终效果系列目录前言随着地面法线旋转在地形上随机生成动物不同部位颜色不同最终效果源码完结系列目录 前言 欢迎来到【制作100个Unity游戏】系列!本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第24篇中,我们将探索如何用unity制作一…

一周学会Django5 Python Web开发-Django5创建项目(用命令方式)

锋哥原创的Python Web开发 Django5视频教程: 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计11条视频,包括:2024版 Django5 Python we…

前端滚动组件分享

分享一个前端可视化常用的卡片列表滚动组件&#xff0c;常用于可视化项目左右两侧的卡片列表的滚动。效果如下图所示&#xff1a; 组件描述 当鼠标移入滚动区域时&#xff0c;滚动行为停止当鼠标再次离开时&#xff0c;滚动继续 源码展示 <template><div ref"…

停车场|基于Springboot的停车场管理系统设计与实现(源码+数据库+文档)

停车场管理系统目录 目录 基于Springboot的停车场管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员功能实现 &#xff08;1&#xff09;车位管理 &#xff08;2&#xff09;车位预订管理 &#xff08;3&#xff09;公告管理 &#xff08;4&#…

Zoho Mail企业邮箱商业扩展第2部分:企业运营

在关于Zoho Mail企业邮箱商业扩展应用的这个系列的第一部分中&#xff0c;我们遇到了一位名叫王雪琳的个体企业家。她经营着自己的营销机构&#xff0c;并展示了如何创建和管理自己的企业。为了提升企业的专业形象&#xff0c;王雪琳使用了Zoho Mail来建立个性化的电子邮件域名…

【Linux】线程

线程 我们常常会在linux中或者在操作系统这门课中听到进程和线程的名称&#xff0c;我们之前认识了进程的概念&#xff0c;现在我们来了解一下线程的概念 线程概念&#xff1a; 什么是线程 在一个程序里的一个执行路线就叫做线程&#xff08;thread&#xff09;。更准确的定…

牛客网SQL264:查询每个日期新用户的次日留存率

官网链接&#xff1a; 牛客每个人最近的登录日期(五)_牛客题霸_牛客网牛客每天有很多人登录&#xff0c;请你统计一下牛客每个日期新用户的次日留存率。 有一个登录(login。题目来自【牛客题霸】https://www.nowcoder.com/practice/ea0c56cd700344b590182aad03cc61b8?tpId82 …

第三百一十五回

文章目录 1. 概念介绍2. 基本用法3. 补充用法4. 内容总结 我们在上一章回中介绍了"再谈ListView中的分隔线"&#xff0c;本章回中将介绍showMenu的用法.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在第一百六十三回中介绍了showMenu相关的内容…

C++学习Day04之单例模式

目录 一、程序及输出1.1 饿汉式实例1.2 饿汉式单例1.3 懒汉式单例1.4 线程安全的懒汉式单例 二、分析与总结 一、程序及输出 1.1 饿汉式实例 #include<iostream> using namespace std; #include <string> class Printer { public:static Printer * getInstance()…

《向量数据库指南》——Milvus Cloud「日志」问题定位的指南针

“2.X 集群的日志在哪里导啊”“现在没有对 Milvus Cloud 进行任何读写操作,但是日志还是不断增加,这正常吗?”“请教下 k8s 部署的 Milvus Cloud 日志如果持久化,只能使用共享存储吗?如果只想放在本地盘可以如何配置?” 社区讨论问题的时候基本都离不开日志,因为日志…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Span组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Span组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Span组件 鸿蒙&#xff08;HarmonyOS&#xff09;作为Text组件的子组件&#xff0…

探索Xposed框架:个性定制你的Android体验

探索Xposed框架&#xff1a;个性定制你的Android体验 1. 引言 在当今移动设备市场中&#xff0c;Android系统作为最受欢迎的操作系统之一&#xff0c;其开放性和可定制性备受用户青睐。用户希望能够根据个人喜好和需求对其设备进行定制&#xff0c;以获得更符合自己习惯的使用…

架构整洁之道-软件架构-展示器和谦卑对象、不完全边界、层次与边界、Main组件、服务

6 软件架构 6.9 展示器和谦卑对象 在《架构整洁之道-软件架构-策略与层次、业务逻辑、尖叫的软件架构、整洁架构》有我们提到了展示器&#xff08;presenter&#xff09;&#xff0c;展示器实际上是采用谦卑对象&#xff08;humble object&#xff09;模式的一种形式&#xff…

js手写Promise(下)

目录 resolve与reject的调用时机封装优化 回调返回PromiseisPromise手动调用then 微队列catchresolverejectall传入的序列为空传入的值非Promise race完整的Promise代码 如果没有看过上半部分的铁铁可以看看这篇文章 js手写Promise&#xff08;上&#xff09; resolve与reject…

GEE Colab——如何利用Matplotlib在colab中进行图形制作

在colab中绘制图表 笔记本的一个常见用途是使用图表进行数据可视化。Colaboratory 提供多种图表工具作为 Python 导入,让这一工作变得简单。 Matplotlib Matplotlib 是最常用的图表工具包,详情请查看其文档,并通过示例获得灵感。 线性图 线性图是一种常见的图表类型,用…

ELAdmin 的 CRUD

数据表结构 弄个测试的数据表&#xff0c;不同类型的几个字段&#xff0c;表名位 mp_reply。 生成代码 ELAdmin 可以自动生成代码。 左侧目录系统工具–代码生成&#xff0c;点开以后可以看到上面创建的数据表mp_reply&#xff0c;点击配置。 进入的页面内容有两部分&#…
最新文章