【Linux取经路】文件系统之缓冲区

news2024/9/21 13:23:06

在这里插入图片描述

文章目录

  • 一、先看现象
  • 二、用户缓冲区的引入
  • 三、用户缓冲区的刷新策略
  • 四、为什么要有用户缓冲区
  • 五、现象解释
  • 六、结语

一、先看现象

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main()
{
    const char* fstr = "Hello fwrite\n";
    const char* str = "Hello write\n";

    printf("Hello printf\n");
    fprintf(stdout, "Hello fprintf\n");
    fwrite(fstr, strlen(fstr), 1, stdout); // 返回值是写入成功的快数

    write(1, str, strlen(str)); // 返回值是写入成功的字节数

    // fork();
    return 0;
}

在这里插入图片描述
结构分析:带 fork 的输出重定向最终把有一些内容向 log.txt 文件中写入了多次,并且打印顺序也有所不同。

int main()
{
    const char* fstr = "Hello fwrite";
    const char* str = "Hello write";

    printf("Hello printf");
    fprintf(stdout, "Hello fprintf");
    fwrite(fstr, strlen(fstr), 1, stdout); // 返回值是写入成功的快数

    close(1);

    // write(1, str, strlen(str)); // 返回值是写入成功的字节数

    // fork();
    return 0;
}

在这里插入图片描述
结果分析:代码中只使用了库函数向显示器中进行写入,并且在字符串的结尾没有加 \n,在最后面将标准输出对应的文件描述符进行了关闭,最终显示器上什么也没有。上一段代码在字符串的结尾加上了 \n 最终字符串被成功的打印到了屏幕上。

int main()
{
    const char* str = "Hello write";

    write(1, str, strlen(str)); // 返回值是写入成功的字节数
    close(1);
    
    return 0;
}

在这里插入图片描述
结果分析:字符串的结尾依然不加 \n,但是这一次采用系统调用接口,最后仍然将标准输出对应的文件描述符进行关闭,这一次字符串被成功的打印了出来。

二、用户缓冲区的引入

write 为什么能将不带 \n 的字符串写入到显示器文件中。首先我们需要明确一点进程打开的每一个文件都有一个属于自己的操作系统级别的文件缓冲区,该缓冲区的存在,可以减少对外设的读写操作以提高计算机的效率。举个栗子,在一个进程中向磁盘里的同一个文件进多次行写入,文件缓冲区的存在,可以将每次写入的内容先存储在文件缓冲区中,最后在程序退出或者调用 close 的时候,一次性将文件缓冲区中的所有内容刷新到磁盘。如果没有该文件缓冲区,那在进程里对文件进行 n 次写操做,就要对应 n 次向磁盘的写操作,CPU 和外设之间是存在非常大的速度差的,这样效率会非常低。

write 作为系统调用接口,它就是直接向文件缓冲区中写入,最后在调用 close 接口或者程序退出的时候,会将文件缓冲区的内容刷新到对应的外设中。

printffprintffwrite 底层一定是封装了 write 系统调用接口,那为什么使用 write 系统调用接口就可以将字符串写入到显示器,使用 C 库函数没能把字符串写入到显示器文件?原因在进度条的那篇文章中讲过,我们使用的这些 C 库函数,是把字符串写入到了缓冲区中,这个缓冲区和上面的文件缓冲区有所不同,这里说的缓冲区是 C 语言给我们提供的语言层面的缓冲区,也叫做用户级缓冲区\n 具有刷新用户级缓冲区的作用,因此不加 \n 并且在程序结束前将显示器对应的文件描述符进行了关闭,最终就导致字符串在用户级缓冲区中,没有被刷新到文件缓冲区,所以屏幕上就什么也没有。这里我们可以肯定,在这些 C 库函数中,并不是立即调用 write 接口,而是在遇到 \n 后才去调用 write 接口将用户缓冲区的内容刷新到文件缓冲区中。

在这里插入图片描述

总结:使用 C 系统调用接口向文件中写入,写入的内容先被存储在用户缓冲区中,在合适的时候(遇到 \n)才会进行刷新,这里刷新的本质是调用 write 将数据从用户缓冲区写入内核。

之前说的 exit 会刷新缓冲区,其实就是刷新用户缓冲区,因为 exit 作为 C 库函数,可以看见用户缓冲区,而 _exit 作为系统调用接口,无法看到语言层面的用户缓冲区,因此也就无法刷新用户缓冲区。

三、用户缓冲区的刷新策略

  • 无缓冲:直接刷新,数据不在用户缓冲区中停留。

  • 行缓冲:不刷新,直到碰到 \n

  • 全缓冲:缓冲区满了才刷新。

所谓刷新就是调用 write 接口将数据写入操作系统中的文件缓冲区。显示器文件对应采用的就是行缓冲,向磁盘文件中写入采用的是全缓冲。进程在退出的时候也会刷新用户缓冲区,还可以调用 fflush 进行刷新。

四、为什么要有用户缓冲区

  • 解决效率问题,缓冲区就像菜鸟驿站,不需要我们自己坐火车坐飞机去送东西,而是直接交给菜鸟驿站,然后就可以干自己的事情了,菜鸟驿站可以选择攒上一大批快递然后统一寄送出去。用户缓冲区的存在本质上提高了 C 语言的效率,也就是提高了用户的效率,因为 C 语言是程序员在使用,在使用 C 库函数进行文件写入时,大部分情况只需要把数据交给缓冲区,然后就可以快速的返回,不需要每一次都亲力亲为的去和操作系统打交道。

  • 配合格式化,有些和文件写入相关的 C 库函数是格式化输出函数,在我们看来,它可以写入整形、符点型,但是最终都是以字符串的形式进行写入。格式化就是将类型全都转化成字符串,先写入到用户缓冲区,用户缓冲区中存的一定都是字符串。

用户缓冲区,有进也有出,将数据写入到用户缓冲区中就就叫做进,将用户缓冲区中的数据刷新到内核中的文件缓冲区中,被刷新的数据就可以从用户缓冲区中删掉,这就叫做出。用户缓冲就像就像水流一样源源不断,流的概念就是因此而来。

小TipsFILE 里面就有对应打开文件的缓冲区字段和维护信息。每个被进程打开文件都有自己对应的文件缓冲区。FILE 对象属于用户,用户缓冲区可以看作是在堆上申请的一块空间。

五、现象解释

这下再来解释上面代码中有 fork 然后重定向,写入了多次的原因。首先重定向后,将本来向显示器文件写入的内容,写到了磁盘文件,显示器文件的缓冲区采用行缓冲,即遇到 \n 就会刷新,而磁盘文件采用的是全缓冲,当缓冲区满了才刷新。因此在重定向后,会把三条 C 库函数写入的内容全部保存到缓冲区中,然后调用 fork 创建子进程,此时父子进程代码共享,数据写时拷贝,在程序退出的时候回去刷新用户缓冲区,上面说过,刷新就是将用户缓冲区中的数据写入到内核,然后将用户缓冲区中的内容清空,上面还说过,缓冲区就是在堆上申请的一段空间,可以看作数据部分,因为要删除数据,所以就会进行写时拷贝,此时之前父进程用户缓冲区中的内容就会给子进程拷贝一份,然后父子进程都执行刷新动作,各自刷新自己的缓冲区数据,这就是为什么最终出现多份的原因。没有重定向,只向显示器打印四条消息,是因为显示器采用的是行刷新策略,在调用 fork 前,对应的字符串就已经被刷新出去了。在 fork 的时候,父进程的用户缓冲区中是空的,什么也没有。

磁盘文件全缓冲验证

int main()
{
    const char* fstr = "Hello fwrite\n";
    const char* str = "Hello write\n";

    printf("Hello printf\n");
    sleep(2);
    fprintf(stdout, "Hello fprintf\n");
    sleep(2);
    fwrite(fstr, strlen(fstr), 1, stdout); // 返回值是写入成功的快数
    sleep(2);

    write(1, str, strlen(str)); // 返回值是写入成功的字节数

    sleep(5);

    fork();
    return 0;
}

在这里插入图片描述
分析:最先将 write 内容写入到文件中,因为它是直接写入到文件缓冲区,而剩下的 C 库函数对应的内容是统一一次全部刷新到内核,即使每个字符串后面都有 \n,但最后还是统一全部刷新,这就证明了磁盘文件采用的是全刷新策略。

六、结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!

在这里插入图片描述

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

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

相关文章

谷歌seo推广的费用是多少?

真正的开销取决于个人或企业的具体需求、目标及资源投入水平&#xff0c;如果你选择自己优化&#xff0c;成本主要体现为时间和努力——你需要花时间学习SEO的最佳实践并应用它们来优化你的网站。这种方式就几乎不需要花钱&#xff0c;但需要大量的学习和实践时间 而选择SEO专家…

Unity接入SQLite (一):SQLite介绍

1.简介 SQLite是一个开源的嵌入式关系数据库管理系统。它是一种轻量级的数据库引擎&#xff0c;不需要单独的服务器进程&#xff0c;可以直接嵌入到应用程序中使用。Sqlite使用简单、高效&#xff0c;并且具有对标准SQL的完整支持。它适用于需要在本地存储和访问数据的应用程序…

Shell好用的工具: cut

目标 使用cut可以切割提取指定列\字符\字节的数据 介绍 cut 译为“剪切, 切割” , 是一个强大文本处理工具&#xff0c;它可以将文本按列进行划分的文本处理。cut命令逐行读入文本&#xff0c;然后按列划分字段并进行提取、输出等操作。 语法 cut [options] filename opti…

opencv图像的本质

目的 OpenCV是一个跨平台的库&#xff0c;使用它我们可以开发实时的计算机视觉应用程序。 它主要集中在图像处理&#xff0c;视频采集和分析&#xff0c;包括人脸检测和物体检测等功能。 数字图像在计算机中是以矩阵形式存储的&#xff0c;矩阵中的每一个元素都描述一定的图像…

Ansible安装管理和模块的使用

目录 Ansible是什么 ansible 环境安装部署 管理端安装 ansible ansible 目录结构 配置主机清单 配置密钥对验证 ansible 命令行模块 1&#xff0e;command 模块 在远程主机执行命令&#xff0c;不支持管道&#xff0c;重定向等shell的特性 常用的参数 2&#xff0e;sh…

C# Onnx 使用onnxruntime部署实时视频帧插值

目录 介绍 效果 模型信息 项目 代码 下载 C# Onnx 使用onnxruntime部署实时视频帧插值 介绍 github地址&#xff1a;https://github.com/google-research/frame-interpolation FILM: Frame Interpolation for Large Motion, In ECCV 2022. The official Tensorflow 2…

【Linux】一站式教会:Ubuntu(无UI界面)使用apache-jmeter进行压测

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java对AI的调用开发》 《RabbitMQ》《Spring》《SpringMVC》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 前言一、Java…

基于java+springboot+vue实现的美食信息推荐系统(文末源码+Lw)23-170

1 摘 要 使用旧方法对美食信息推荐系统的信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在美食信息推荐系统的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。这次开发…

运维SRE-19 网站Web中间件服务-http-nginx

Ans自动化流程 1.网站集群核心协议&#xff1a;HTTP 1.1概述 web服务&#xff1a;网站服务&#xff0c;网站协议即可. 协议&#xff1a;http协议,https协议 服务&#xff1a;Nginx服务&#xff0c;Tengine服务....1.2 HTTP协议 http超文本传输协议&#xff0c;负责数据在网站…

【思扬赠书 | 第3期】由面试题“Redis是否为单线程”引发的思考

⛳️ 写在前面参与规则&#xff01;&#xff01;&#xff01; ✅参与方式&#xff1a;关注博主、点赞、收藏、评论&#xff0c;任意评论&#xff08;每人最多评论三次&#xff09; ⛳️本次送书1~4本【取决于阅读量&#xff0c;阅读量越多&#xff0c;送的越多】 很多人都遇到…

Day10-面向对象-抽象类和接口

文章目录 学习目标1. 抽象类1.1 抽象类注意事项1.2 修饰符的使用 2. 接口2.1 定义接口2.2 接口里可以定义的成员2.2 实现接口2.2.1 实现接口语法格式2.2.2 如何调用对应的方法2.2.3 练习 2.3 接口的多实现2.3.1 练习 2.4 冲突问题2.5 接口的多继承(了解)2.6 部分内置接口 学习目…

2024转行要趁早!盘点网络安全的岗位汇总

前段时间&#xff0c;知名机构麦可思研究院发布了《2024年中国本科生就业报告》&#xff0c;其中详细列出近五年的本科绿牌专业&#xff0c;信息安全位列第一。 对于网络安全的发展与就业前景&#xff0c;知了姐说过很多&#xff0c;作为当下应届生收入较高的专业之一&#xf…

【Python笔记-设计模式】对象池模式

一、说明 用于管理对象的生命周期&#xff0c;重用已经创建的对象&#xff0c;从而减少资源消耗和创建对象的开销 (一) 解决问题 主要解决频繁创建和销毁对象所带来的性能开销问题。如数据库连接、线程管理、网络连接等&#xff0c;对象的创建和销毁成本相对较高&#xff0c…

C 语言基本语法及实用案例分享

一、什么是 C 语言&#xff1f; C语言是一种较早的程序设计语言&#xff0c;诞生于1972年的贝尔实验室。1972 年&#xff0c;Dennis Ritchie 设计了C语言&#xff0c;它继承了B语言的许多思想&#xff0c;并加入了数据类型的概念及其他特性。C语言是一门面向过程的计算机编程语…

基于单片机和LabVIEW的多路数据采集系统设计

摘 要:以8位高速、低功耗微控制器STC12C5A60S2为硬件控制核心,以Labview为上位机软件开发平台,设计了一个多路数据采集系统。由下位机单片机对多路模拟信号量进行数据采集,通过串口将采集的模拟量信息上传到上位机,上位机Labview对采集的数据进行存储、显示及处理、分析…

Node.js中如何处理异步编程

在Node.js中&#xff0c;处理异步编程是至关重要的技能。由于Node.js的单线程执行模型&#xff0c;异步编程可以极大地提高程序的性能和响应速度。本文将介绍几种常见的异步编程处理方式&#xff0c;并附上示例代码&#xff0c;帮助您更好地理解和应用异步编程技术。 回调函数…

GitLab代码库提交量统计工具

1.说明 统计公司所有项目的提交情况&#xff0c;可指定分支和时间段&#xff0c;返回每个人的提交新增数、删除数和总数。 2.API 文档地址&#xff1a;http://公司gitlab域名/help/api/README.md 项目列表查询 返回示例&#xff1a; [{"id": 1, //项目ID"http…

软考29-上午题-【数据结构】-排序

一、排序的基本概念 1-1、稳定性 稳定性指的是相同的数据所在的位置经过排序后是否发生变化。若是排序后&#xff0c;次序不变&#xff0c;则是稳定的。 1-2、归位 每一趟排序能确定一个元素的最终位置。 1-3、内部排序 排序记录全部存放在内存中进行排序的过程。 1-4、外部…

TF-A之供应链威胁模型分析

目录 一、简介 二、TF-A 概述 2.1、TF-A 存储库 2.2、外部依赖 2.3、附加二进制文件 2.4、TF-A工具链 2.5、基础设施 三、TF-A数据流 四、攻击树 五、威胁评估与缓解 5.1、影响和可能性评级 5.2、威胁和缓解措施 六、附录 一、简介 软件供应链攻击旨在向软件产品…

《深入浅出 Spring Boot 3.x》预计3月份发版

各位&#xff0c;目前本来新书《深入浅出 Spring Boot 3.x》已经到了最后编辑排版阶段&#xff0c;即将在3月份发布。 目录&#xff1a; 现在把目录截取给大家&#xff1a; 主要内容&#xff1a; 本书内容安排如下。 ● 第 1 章和第 2 章讲解 Spring Boot 和传统 Spri…