当前位置: 首页 > news >正文

【Linux】文件系统之缓冲区

目录

📖一、先看现象

📖二、用户缓冲区的引入 

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

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

📖五、现象解释 

📖六、普通文件全缓冲验证

📖七、完结 



📖一、先看现象

代码一、 

#include <stdio.h>
#include <string.h>
#include <unistd.h>int main()
{const char* fstr = "Hello fwrite\n";const char* str = "Hello write\n";//C库函数调用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()
{//C库函数调用printf("Hello printf");fprintf(stdout, "Hello fprintf");fwrite(fstr, strlen(fstr), 1, stdout); // 返回值是写入成功的快数close(1);return 0;
}

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


代码三、

int main()
{const char* str = "Hello write";write(1, str, strlen(str)); // 返回值是写入成功的字节数close(1);return 0;
}

 

      结果分析:字符串的结尾依然不加 \n,之前全部都是C库函数调用,但是这一次采用系统调用接口,最后仍然将标准输出对应的文件描述符进行关闭,这一次字符串被成功的打印了出来 


📖二、用户缓冲区的引入 

      首先我们需要明确一点进程打开的每一个文件都有一个属于自己的操作系统级别的文件缓冲区,该缓冲区的存在,可以减少对外设的读写操作以提高计算机的效率。

      举个栗子,在一个进程中向磁盘里的同一个文件进多次行写入,文件缓冲区的存在,可以将每次写入的内容先存储在文件缓冲区中,最后在程序退出或者调用 close 的时候,一次性将文件缓冲区中的所有内容刷新到磁盘如果没有该文件缓冲区,那在进程里对文件进行 n 次写操做,就要对应 n 次向磁盘的写操作,CPU 和外设之间是存在非常大的速度差的,这样效率会非常低。


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

      所以代码三中,直接输出到了显示器上


      printffprintffwrite 底层一定是封装了 write 系统调用接口,那为什么使用 write 系统调用接口就可以将字符串写入到显示器,使用 C 库函数 没能把字符串写入到显示器文件?

      原因在于,C语言的库函数的缓冲区是另一种缓冲区,C 语言给我们提供的是语言层面的缓冲区,也叫做用户缓冲区

      我们日常在显示器上显示,都是由文件缓冲区直接刷新到显示器上的,而用户缓冲区的内容必须经过文件缓冲区,才能刷新到显示器上

      \n 具有刷新用户级缓冲区的作用,因此不加 \n 并且在程序结束前将显示器对应的文件描述符进行了关闭,最终就导致字符串在用户级缓冲区中,没有被刷新到文件缓冲区,所以屏幕上就什么也没有。

      所以我们可以肯定,在这些 C 库函数中,并不是立即调用 write 接口,而是在遇到 \n 后才去调用 write 接口将用户缓冲区的内容刷新到文件缓冲区中。


      这里简单总结一下->:

1. 缓冲区分为两种,一种是用户缓冲区,另一种是文件缓冲区

2. 系统调用接口直接写入文件缓冲区,C库函数写入的是用户缓冲区

3. 想要显示器显示出来内容,必须由文件缓冲区来传入,因此用户缓冲区的内容想要显示出来必须先传入文件缓冲区

4.文件缓冲区在程序结束或者调用close时会把内容自动刷新出去

5.用户缓冲区在遇到 \n 时会调用 write 系统调用,把用户缓冲区刷新到文件缓冲区


      总结>: fopen 封装了 open,fwrite 封装了 write,fread 封装了 read

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


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

用户缓冲区分为三种缓冲:

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

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

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

      我们的显示器文件对应的就是行缓冲,也就是遇到 '\n' 就刷新

      (举个例子,正常程序结束时才刷新缓冲区,但是 '\n' 提前刷新出来)

      普通文件(.txt)对应的是全缓冲,也就是缓冲区写满再刷新

      我们上面说到的程序结束或者调用 close 函数,它不分全缓冲或者行缓冲,因为它一定会将缓冲区刷新

小Tips:

      1. 其实遇到 '\n' 将用户缓冲区刷新到文件缓冲区就相当于已经刷新到显示器/硬盘中了

      2. 全缓冲不会因为 '\n' 的存在把用户缓冲区加载到文件缓冲区


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

      在 C 语言中,当使用 C 库函数进行文件写入等 I/O 操作时,为了提高效率,不会每次都直接与操作系统进行交互。而是先将数据存储在用户空间中的用户缓冲区,库函数把数据交给这个缓冲区后就可以快速返回,让程序继续执行其他任务,不必等待数据真正被写入文件或设备。

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


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


📖五、现象解释 

      我们来回忆一下最开始的代码一中为什么fork的重定向多打印了一些

      最本质的原因就是->:fork生成的子进程会继承用户缓冲区(不会继承文件缓冲区)

      缓冲区就是在堆上申请的一段空间,可以看作数据部分,因为进程结束要删除数据,所以就会进行写时拷贝,为了防止删除内容互相干扰,此时父进程的用户缓冲区中的内容就会给子进程拷贝一份,然后父子进程都执行刷新缓冲区,父子进程各自刷新自己的缓冲区数据,这就是为什么最终出现多份的原因。


      代码一不带fork怎么不多打印

      因为代码一种我们使用了 '\n' ,对于显示器文件采用的是行刷新,也就是遇到 '\n' 就刷新,所以父进程的用户缓冲区是空的,那么子进程 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 内容写入到文件中,因为它是直接写入到文件缓冲区,进而直接刷新到了显示器上

      第二,因为这是重定向到普通文件中,普通文件想要显示到显示器上进行的是全缓冲,也就是用户缓冲区满了再刷新(但是缓冲区很难满,这里其实是程序结束导致的强制刷新缓冲区)

      所以我们最开始只会看到 "Hello write" 

      需要注意的是,我们的每一个C语言库函数都增加了 '\n' ,虽然加了 '\n' ,但是普通文件全缓冲并不会因为 '\n' 的存在把用户缓冲区加载到文件缓冲区

      即使每个字符串后面都有 \n,但最后还是统一全部刷新,这就证明了磁盘文件采用的是全刷新策略。


📖七、完结 

创作不易,留下你的印记!为自己的努力点个赞吧!

http://www.dtcms.com/a/474120.html

相关文章:

  • 【检索:数据库】6、B+树数据库索引全解析:如何为海量磁盘数据构建毫秒级检索系统
  • 创意设计公司网站dede一键更新网站出错
  • 使用Python高效读取ZIP压缩文件中的UTF-8 JSON数据到Pandas和PySpark DataFrame
  • 基于Spring Boot + Vue 3的乡村振兴综合服务平台性能优化与扩展实践
  • 基于单片机的声光控制楼道灯(论文+源码)
  • 网站运营分析云平台网站建设方案
  • 【Linux】进程间同步与互斥(下)
  • 现成的手机网站做APP手机网站开发教程pdf
  • 【栈】5. 验证栈序列(medium)
  • Leetcode之 Hot 100
  • 建立能网上交易的网站多少钱wordpress调取多个分类文章
  • MySQL 索引:原理、分类与操作指南
  • Blender机箱盒体门窗铰链生成器资产预设 Hingegenious
  • 网站托管就业做美食有哪些网站
  • 神经符号AI的深度探索:从原理到实践的全景指南
  • 零食网站建设规划书建行输了三次密码卡锁怎么解
  • Python代码示例
  • 济南市历下区建设局官方网站wordpress高级套餐
  • ALLEGRO X APD版图单独显示某一网络的方法
  • 计算机网络基础篇——如何学习计算机网络?
  • 电子商务网站建设的总体设计wordpress dux主题5.0版本
  • 《jEasyUI 创建简单的菜单》
  • AI【前端浅学】
  • 怎么设置网站名称失眠先生 wordpress
  • 低空物流+韧性供应链:技术架构与企业级落地的开发实践指南
  • Quartus II软件安装步骤(附安装包)Quartus II 18超详细下载安装教程
  • 动规——棋圣匹配
  • 侵入别人的网站怎么做我的家乡网页制作步骤
  • Thonny(Python IDE)下载和安装教程(附安装包)
  • Fastdfs_MinIO_腾讯COS_具体逻辑解析