【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’,如果有我们还需要将缓冲区刷新,即行刷新:
测试一下:
相关文章:

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

【前端收藏】前端小作文-前端八股文知识总结(超万字超详细)持续更新
有了这个八股文不仅对你基础知识的巩固,不管你是几年老前端程序员,还是要去面试的,文章覆盖了前端常用及不常用的方方面面,都是前端日后能用上的,对你的前端知识有总结意义,看完后,懂的不懂的都…...

GNSS模块的惯导技术:引领定位科技的前沿
全球导航卫星系统(GNSS)模块的惯导技术是一项颇具前瞻性的科技,它结合了全球定位系统和惯性导航技术,为各个领域的定位需求提供了更为精准和可靠的解决方案。本文将深入探讨GNSS模块的惯导技术,以及它如何在多个领域中…...

Flutter 和 Android原生(Activity、Fragment)相互跳转、传参
前言 本文主要讲解 Flutter 和 Android原生之间,页面相互跳转、传参, 但其中用到了两端相互通信的知识,非常建议先看完这篇 讲解通信的文章: Flutter 与 Android原生 相互通信:BasicMessageChannel、MethodChannel、…...

Kubernetes基础(十一)-CNI网络插件用法和对比
1 CNI概述 1.1 什么是CNI? Kubernetes 本身并没有实现自己的容器网络,而是借助 CNI 标准,通过插件化的方式来集成各种网络插件,实现集群内部网络相互通信。 CNI(Container Network Interface,容器网络的…...

yo!这里是单例模式相关介绍
目录 前言 特殊类设计 只能在堆上创建对象的类 1.方法一(构造函数下手) 2.方法二(析构函数下手) 只能在栈上创建对象的类 单例模式 饿汉模式实现 懒汉模式实现 后记 前言 在面向找工作学习c的过程中,除了基本…...

2023年上-未来几年我要做什么
1月份,离职。 2月份,春节休假回来,中旬去参加了一个月的瑜伽培训,学会了倒立、鹤蝉。。。。 3月份,瑜伽培训结束,开始收拾房子,并调研各类项目。 4月份,参与了朋友的区块链项目 …...

智能汽车竞赛摄像头处理(3)——动态阈值二值化(大津法)
前言 (1)在上一节中,我们学习了对图像的固定二值化处理,可以将原始图像处理成二值化的黑白图像,这里面的本质就是将原来的二维数组进行了处理,处理后的二维数组里的元素都是0和255两个值。 (2…...

BGP协议
1.BGP相关概念 1.1 BGP的起源 不同自治系统(路由域)间路由交换与管理的需求推动了EGP的发展,但是EGP的算法简单,无法选路,从而被BGP取代。 自治系统:(AS) IGP:自治系统…...

一个完整工作流管理系统的组成部分
一个完整工作流管理系统的组成部分 一个完整的工作流管理系统通常由工作流引擎、工作流设计器、流程操作、工作流客户端程序、流程监控、表单设计器、与表单的集成以及与应用程序的集成八个部分组成。 一、工作流组成 1. 工作流引擎 工作流引擎作为工作流管理系统的核心部分&…...

鱼和熊掌如何兼得?一文解析RDS数据库存储架构升级
在2023年云栖大会上,阿里云数据库产品事业部负责人李飞飞在主题演讲中提到,瑶池数据库推出“DB存储”一体化能力,结合人工智能、机器学习、存储等方法和创新能力,实现Buffer Pool Extension能力和智能冷温热数据分层能力。在大会的…...

中科大计网学习记录笔记(五):协议层次和服务模型
前言: 学习视频:中科大郑烇、杨坚全套《计算机网络(自顶向下方法 第7版,James F.Kurose,Keith W.Ross)》课程 该视频是B站非常著名的计网学习视频,但相信很多朋友和我一样在听完前面的部分发现信…...
同构异机迁移方案2_目标服务器仅安装数据库软件scp物理文件
源端和目标端的数据库版本需要保持一致,补丁版本可以不一致,目标端磁盘空间不能小于源端空间,目标端只需要安装 Oracle 软件即可。 特别说明:本文档案例Oracle的安装路径不同,数据目录一致,采用scp的方式实…...

华为机考入门python3--(6)牛客6-质数因子
分类:质数、素数 知识点: 取余符号% 5%3 2 取整符号// 5//3 1 list中int元素转str map(str, list) 题目来自【牛客】 def prime_factors(n): """ 输入一个正整数n,输出它的所有质因子(重复的也…...

11月最新版付费进群源码自动定位+开源
Nginx 1.22.1 php5.6 mysql5.6 数据库配置:/config/database.php 配置后台域名:config/extra/ip.php 设置伪静态thinkphp 后台账号88886666 密码12345 代码结构 关键代码剖析 <?php // ----------------------------------------------------…...

Python算法题集_旋转图像
Python算法题集_旋转图像 题目48:旋转图像1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【矩阵复本】2) 改进版一【矩阵转置矩阵反转】3) 改进版二【四值旋转】 4. 最优算法 题目48:旋转图像 本文为Python算法题集之一…...

[ChatGPT们】ChatGPT 如何辅助编程初探
主页:元存储的博客 全文 9000 字, 原创请勿转载。 我没有写过诗,但有人说我的代码像诗一样优雅 -- 雷军 图片来源:https://www.bilibili.com/video/BV1zL411X7oS/ 1. 引言 作为一个程序员,我们不仅要熟悉各种编程语…...

深入Spring MVC的工作流程
深入Spring MVC的工作流程 在Spring MVC的面试问题中,常常被询问到的一个问题。Spring MVC的程序中,HTTP请求是如何从开始到结束被处理的。为了研究这个问题,我们将需要深入学习一下Spring MVC框架的核心过程和工作流程。 1. 启动请求生命周…...
我的数据结构c(给自己用的)
目录 顺序表: 链表: 栈: 队列: 我想在之后的大学数据结构课上需要自己写来做题,但每次都自己写,那太麻烦了,所以我就将这个博客来把所有的C语言的数据结构弄上去, 问我为什么不…...

使用Arcgis对欧洲雷达高分辨率降水数据重投影
当前需要使用欧洲高分辨雷达降水数据,但是这个数据的投影问题非常头疼。实际的投影应该长这样(https://gist.github.com/kmuehlbauer/645e42a53b30752230c08c20a9c964f9?permalink_comment_id2954366https://gist.github.com/kmuehlbauer/645e42a53b307…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...

AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...

【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...