Linux文件IO——缓冲区&&磁盘上的文件管理

发布于:2025-03-16 ⋅ 阅读:(16) ⋅ 点赞:(0)

前言

什么是缓冲区?

  • 缓冲区是内存空间上的一小段内存,我们平常在写程序的时候,其实是很难感知到缓冲区的存在的,接下来看一段代码,可以很好地体现缓冲区的存在。
#include<stdio.h>
#include<unistd.h>
int main()
{
    printf("这是我关于缓冲区的一次小测试");
    sleep(2);
    return 0;
}

运行后

789721af98364a54a4693dbab0234c5d.png

2秒后

0f855b0f451f468faf696a7b9d3b03a8.png

按照我们对于平常代码执行顺序的理解,我们应该先打印printf中的文本信息,在执行sleep函数休眠2秒,但实际并不是这样,事实恰恰相反。

对上述代码做一次改动,再次运行

#include<stdio.h>
#include<unistd.h>
int main()
{
    printf("这是我关于缓冲区的一次小测试\n");
    sleep(2);
    return 0;
}

我们添加在文本信息后添加了换行符"\n",

结果

8957a6a36be34eef91a0659c914b2fa0.png

  这一次,符合我们心中的预期,先打印文本信息,在进行休眠。

  这其实就是缓冲区在发挥作用,有人说,缓冲区让我对代码执行产生误解,我摸不清代码怎么执行了,为什么要存在它?

  为什么会有缓冲区,缓冲区的作用是什么?这就是今天我们所探究的。

缓冲区

缓冲区是什么?

  • 缓冲区是内存空间上的一段内存,大小由操作系统分配。
  • 一个暂时存储数据的区域 

缓冲区存在于哪里?

  • 缓冲区存在于内存空间

1.为什么要有缓冲区呢?

  缓冲区是一个暂时存储数据的地方,当数据足够,就会向目标地点进行输送,我们现实生活中也存在这样的地方,就是我们日常的快递站。

  当你需要将礼物送给你的朋友,而你们相距甚远的时候,比如一个在江西,一个在黑龙江,你会亲自将礼物送到他手上吗?

b7d0c19fd42f4da2a672f42128627b9d.jpeg

 

  答案显然是否定的,那样成本不仅高,还会耗费你大量的时间,这时候,我们都会选择——寄快递,让快递员替我们将礼物送出,当你将快递交给快递站,他们也不会立刻将你的快递发出,送一个快递,对他们来说和让你自己送没有区别,不过耗费的不是你的时间和成本,快递站也有他们自己的寄送规则。

快递站寄送规则

  • 立即寄送
  • 等到一定数量寄送
  • 存满快递寄送

以上三种规则,第一种时间快,但成本太大,收益几乎没有,甚至倒贴;

第二种,不仅能有收益,还能保障效率;

第三种,牺牲时间换取收益,收益提高了,但用户等待的时间很长。

快递站的这三种寄快递规则,也就相当于我们缓冲区的推送(刷新)数据规则

  1. 实时刷新
  2. 行刷新
  3. 存满刷新

通过上述,我们理解的缓冲区的作用

  • 提高效率,暂时存储数据,待到数据数量达到一定时,刷新推送数据到磁盘文件当中,避免了多次读写带来的时间损耗,降低效率                         

缓冲区的运作过程

   当一个文件被进程打开的时候,它就不存在于磁盘之上,而是存在于内存之上,这是因为操作系统会将其属性和内容均加载到内存上,属性我们知道,操作系统内核会形成一个struct file类型的结构体来保存属性。

那文件内容呢?

  •   被操作系统拷贝到缓冲区当中,内核文件管理不仅会创建文件结构体,还会为文件内容分配一段内存空间,也就是我们的缓冲区,将文件内容放入其中,并为文件结构体添加相关指针,用来管理这一段内存空间。526bfb9dec18496eb0379b538c8d4134.jpeg
  •   当进程对文件内容进行操作的时候,实际并不是对磁盘上的文件进行操作,而是对内存上的文件进行操作,修改的,增加的,或者删除的内容,都是在对内存上缓冲区里面的内容操作,当操作结束,关闭文件,结束进程,会自动刷新缓冲区,将缓冲区内的内容拷贝到磁盘当中。cd0f3563968d4daab67e2164acfc1d93.jpeg

  所以,缓冲区的运作过程

  1. 文件被打开,文件内容从磁盘上拷贝到缓冲区当中
  2. 用户进程对文件内容操作,实际是对内存中缓冲区中的内容操作
  3. 关闭文件,用户进程结束,缓冲区刷新,其中的数据被重新拷贝到磁盘文件中。

总结:对文件内容的操作,本质就是文件内容数据的来回拷贝。

缓冲区的刷新规则

通过上面的学习

我们知道缓冲区的刷新的三个规则

  • 即时刷新
  • 行刷新
  • 全满刷新

在我们开头的代码中,为什么加换行符之前不刷新,加了换行符后立刻刷新呢?

除了等待进程退出,自动刷新缓冲区

我们还有两种方式刷新缓冲区

  • 使用换行符号强制刷新
  • 使用flush函数强制刷新

什么时候行刷新,什么时候全满刷新呢?

  • 一般对于显示器文件,我们采用行刷新的策略
  • 对于磁盘文件,我们一般全满刷新。

来看一个样例

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
    fprintf(stdout,"C: hello fprintf\n");
    printf("C: hello printf\n");
    fputs("C: hello fputs\n",stdout);
    const char*str="system call:hello write";
    write(1,str,strlen(str));
    fork();
    return 0;
}

当我们向显示器当中打印的时候

结果:

0b754e69ef2f47d5bb5aa348cd1f431b.png

当我们向文件当中打印的时候

结果:

0c4dff89d9a844478c4b29211f8848bd.png

这是为何?

  1. 当我们向显示器中打印信息的时候,为了照顾用户体验,显示器的刷新方式默认为行刷新,并且每个打印语句都有\n,当执行到fork的时候,这时候的缓冲区已经被刷新完了,缓冲区当中已经没有数据了。
  2. 当我们重定向到文件中的时候,刷新方式变成了全缓冲,即等到缓冲区数据到达一定体量再刷新数据,切刷新方式为全缓冲的时候,缓冲区也会变大,这时候代码执行到fork语句的时候,缓冲区内还有数据。
  3. 子进程和父进程共享代码和数据,如果执行到fork函数的时候,父进程缓冲区中还有数据,子进程也会一并将其继承下来。


  所以,当父子进程都退出的时候,父进程缓冲区的数据被刷新,子进程缓冲区的数据被刷新,子进程缓冲区的数据是继承父进程的,所以会重复打印文本信息。

C式缓冲区和内核缓冲区

 当我们重定向,将信息输出到指定文件中的时候

acbcc618e79e4666adec2fcc0a18b759.png

  我们可以发现,C语言函数接口的语句,每个都打印了两次,而系统接口函数的文本信息仅仅只打印了一次,这是为什么?

  •      利用子进程会继承父进程数据的特点,我们也可以让子进程继承缓冲区中的数据,实现两次打印的效果,但这都是针对C语言函数接口而言的,对于系统函数接口write,它写入的文本信息其实并不在父进程的缓冲区当中,而是在内核缓冲区,直接和操作系统打交道的缓冲区,而父进程和子进程的缓冲区,则是C语言自己维护的缓冲区。
  •     也就是说,此时共有三个缓冲区,父进程一个C语言缓冲区,子进程一个C语言缓冲区,一个内核缓冲区,父子进程的缓冲区中的数据是一样的,重复的,内核缓冲区的数据则是系统调用接口write写入的,独一份的,当进程退出的时候,父子进程缓冲区数据被刷入内核缓冲区,再和内核缓冲区的数据一同被刷新拷贝到磁盘文件当中。44368a9d0965483e916b0eedb0569866.jpeg

  缓冲区的作用就是提高效率,解决各个设备读写速度不匹配带来的低速拖累高速的情况,C式缓冲区存在的目的也是为了减少用户进程与内核缓冲区的交互,希望数据积攒到一定体量再写入内核缓冲区,就像内核缓冲区和磁盘文件的存储关系一样。

  如果想直接写入内核缓冲区,就使用操作系统提供的系统调用接口,它们会直接对内核缓冲区读写,如read和write;如果想写入C式缓冲区,则使用C语言提供的调用接口,如printf和scanf。


总结

  • 无论是C式缓冲区还是内核缓冲区,它们的存在目的都是为了避免频繁读写,从而提高整体效率
  • 使用C调用接口,读写经过C式缓冲区;使用系统调用接口,读写跳过C式缓冲区,直接进入内核缓冲区。

磁盘上的文件管理

文件分为被打开、未被打开两种状态,被打开的文件加载到内存空间当中,由内核操作系统的文件系统同一管理,那未被打开的文件呢?

  • 未被打开的文件存在于磁盘之上,由磁盘的文件系统管理 

接下来,进入磁盘的文件管理学习!

 磁盘物理结构

首先来认识一下,磁盘

e8d27ce60f004b2f90f492817fccb039.png

磁盘由许多盘片组成,每个盘片由有正反两个面,每个面分配一个磁头,也就是说,有多少个盘面,就有多少个磁头。

俯瞰盘片

  • 盘片分成许多空心圆,每个空心圆的边缘都是一圈磁道。
  • 磁道平均分,每一段磁道就是扇区,扇区是磁盘存储数据的基本单位。
  • 越内的扇区越小,但存储的数据量不变,因此,它的数据密度更大。

注:扇区是磁盘存储数据的基本单位,大小一般为512个字节。

 CHS方法

  1. 定位磁道
  2. 定位盘片
  3. 定位扇区

磁盘逻辑结构

如图,将卷成圆形的磁带抽出来,就是一条长带。

同样,我们也可以将磁盘看成卷起来的磁带,将其扯出来,也是一条长带。

这是一种线性结构,因此,我们可以将磁盘看成一个数组,一个元素就是一个扇区,对文件的查找,就可以转换成对数组元素的查找!


磁盘的大小很大,而一个扇区的大小仅仅只有512字节,如果真正按照以扇区作为一个数组元素的方式划分,那么数组元素的数量将达到恐怖的数字,这个数量也是我们不愿意看到的。

借用现实生活中,我们分省分市的划分方法,为了更好地管理文件,磁盘管理系统对文件采用分区分组的管理方式。

先将磁盘分成几个大区,再在一个大区中分组,最后组中存储文件。

组中又分为几个块,这些块就是磁盘文件管理的核心,我们认识一下

  • Data blocks:存储文件内容的块区,该块区中有许多数据块,数据块用来存储对应文件的内容,每个数据块大小为4KB(8个扇区)
  • inode Table:存储的是struct inode结构体,每个结构体大小为128字节,该结构体中保存的是文件的inode编号,属性,数据块指针(指向Data blocks中的数据块,找到文件内容)
  • inode Bitmap:文件位图,来判断一个文件是不是存在,如果该文件存在,对应比特位为1,否则为0。
  • Block Bitmap:用来判断对应的数据块是否被占用,如果被占用,对应比特位为1,否则为0.
  • Group Descriptor Table:存储的是当前组的信息,如属性,数据块使用量,文件量,组大小。
  • Super Block:存放文件系统本身的结构信息。记录的信息主要有:大区中block和inode的总量,未使用的block和inode的量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息,Super Block的信息被破坏,该大区的文件系统结构就被破坏了
  • 一般一个分区中,有好几个组都有Super Block,防止分区文件系统结构被破坏后无法被修复。

在组中,我们不止一次见到了inode。那么inode是什么?

  • inode是文件编号,一个inode代表一个文件,它是文件的唯一标识。

inode虽然是文件的唯一标识,但我们在平常Linux文件操作中,却从未见过,一般都是靠文件名来查找,操作文件,这是为什么?

  • inode是文件的唯一标识符,这是对内核而言的,内核通过inode对文件进行定位。
  • 当文件被创建的时候,内核会为其分配inode,并为其文件名和inode建立映射关系,这个映射关系会被存储到该文件的目录文件当中。
  • 当用户层使文件名操作文件,内核操作系统会先在该目录下找到对应目录关系,用inode替换文件名,再进行后续操作。

Linux操作系统怎么在磁盘中查找文件?

  • 依靠文件路径查找文件,对于一个文件而言,文件路径是唯一的。

原理:

  • 文件系统管理分区首先要进行挂载才能被使用,挂载点一般是一个目录,后序将该目录当成入口访问某个分区上的文件。
  • 查找文件,依靠的是文件路径,通过目录进入分区,在通过文件名和inode的映射关系找到文件。

删除或者修改一个文件的本质是什么?

  • 删除一个文件的本质,就是将其inode Bitmap中的比特位置为0,将其Block Bitmap中的比特位置0,这样在逻辑上,该文件就被删除了,当该inode被重新使用,数据块内容被覆盖,才是真正被删除。这也为恢复文件提供机会,前提是保存了inode。
  • 修改一个文件的本质是通过struct inode结构体中的数据块指针找到对应的数据块,修改数据块中的内容。

创建一个文件的过程是怎么样的?

  • 权限检查:
    • 在创建文件之前,内核会检查当前用户是否有在目标目录中创建文件的权限。
    • 这通常涉及读取目录的权限位和当前用户的权限。
  • 分配inode和数据块:
    • 如果权限检查通过,文件系统会为新文件分配一个inode,用于存储文件的属性(如文件大小、权限、时间戳等)。
    • 同时,文件系统还会为新文件分配必要的数据块,用于存储文件内容。
  • 更新目录结构
    • 文件系统会在目标目录中为新文件创建一个目录项,将文件名与inode关联起来。
    • 这通常涉及更新目录的数据块,以包含新文件的条目。

寄语

   本次文章介绍了缓冲区和磁盘上的文件管理,希望其中的一些观点能够帮助大家学习,缓冲区我个人认为难度较大,难以理解,其中有问题的地方希望大家能够指出来,在评论区进行讨论。