目录

一、认识硬件 - 磁盘

1. 磁盘的物理构成

2. 磁盘的存储构成

3. 磁盘的逻辑结构

二、文件系统

1. Linux ext2文件系统

2. 磁盘格式化介绍

3. 理解文件操作

三、理解软硬链接

1. 软链接

2. 硬链接

3. 总结

四、文件系统的底层逻辑补充

磁盘和内存交互的基本单位

操作系统如何管理内存?

内核级缓冲区:文件页缓冲区


一、认识硬件 - 磁盘

磁盘是一种永久性存储介质,它是我们电脑中唯一一个机械设备,也是一个外设(输出/输入)。同时,在我们电脑中还有一个叫内存的东西,它则是掉电易失性存储介质。

1. 磁盘的物理构成

磁盘张什么样,如图所示为磁盘的正面和背面样子:其中在正面,我们对其中结构的称呼如下:需要注意:

  • 磁头和盘面是不接触的;
  • 盘面有多个,以主轴为中心重叠放置,一个盘面上只有一个磁头;
  • 盘片是会以主轴为中心高速旋转的。

2. 磁盘的存储构成

磁道与扇区

在上述图中,每一个盘片表面都被划分为许多同心圆轨迹,每一个同心圆叫做磁道。每一个磁道又被划分为若干个弧形区域,这样的区域叫做扇区

扇区是磁盘上进行数据读写的最基本单元,传统标准为512字节,现代高级格式化硬盘通常为4KB。所以我们可以将磁盘看做是有无数个扇区构成的存储介质。

要将对磁盘进行数据读写,首先需确定数据的位置,可以分为以下三个步骤:

  1. 确定是哪一个盘面(定位使用的是哪一个磁头)
  2. 然后确定是哪一个磁道
  3. 最后确定是哪一个扇区

这种寻址的方法叫做CHS寻址方式

然后向该位置进行读写,读写的操作就是:操作系统向磁盘读写数据时,实际上是控制磁头在高速旋转的盘片上方,通过电路控制改变或感应盘片表面的磁极性来实现二进制数据的写入与读取,并且所有的读写操作都是严格按照扇区为单位进行的。

3. 磁盘的逻辑结构

我们可以将磁盘想象成一个线性的存储结构,就像磁带一样,当卷起来的时候就是圆的,但是当将磁带拉直就是线性的。而磁盘是有一个个盘面组成,而每一个盘面有是有很多磁道组成,二磁道又是由许多扇区组成。所以磁盘就是有很多个扇区组成。

所以,对于一整个磁盘,我们可以将其想象成一条直线,整个有由多个盘面组成,每个盘面又由很多磁道组成,每个磁道由由很多扇区组成。如图所示:

如果我们将这个线性结构看做一个数组,则每一个扇区都有一个下标,而这种下标,我们逻辑扇区地址(也叫LBA地址)。通过 LBA,我们可以计算出数据在磁盘物理结构上的具体位置,即物理地址(由磁头号/盘面号柱面号/磁道号扇区号组成,简称 CHS)。。示例:

假如每个盘面有2000个扇区,每个磁道有50个扇区,则下标为2888的扇区就位于第2个磁头(盘面)(因为2888 / 2000 = 1, 2888 % 2000 = 888),第18个磁道(因为888 / 50 = 17, 888 % 50 = 38),第38号扇区。

由此,我们就完成了从逻辑地址(LBA)到物理地址(CHS)的转化。


二、文件系统

1. Linux ext2文件系统

在Linux ext2文件系统中,为了更好的管理磁盘,会先对磁盘进行分区,管理每个分区,就可以管理好整个磁盘(比如我们电脑上的C盘和D盘其实就是对磁盘的分区)。在此基础上,通过格式化操作,ext2文件系统会进一步对分区进行分组,每个分区划分为若干个“块组(Block Group)”。具体的如图所示:

其中的 Boot Block 是启动块,位于磁盘的最开始(通常是前 1024 字节)。它不属于 Ext2 文件系统本身,而是用于存放操作系统的引导加载程序。当电脑开机时,就会先读取这里来启动系统。

随后的Block Group 0 ... Block Group n(块组 0 到 n):表示的就是一个个的分组,即分区分成的很多个大小相等的块组。而对于每一个组块,又有以下细分,如图所示:我们知道文件=文件内容+文件属性,但其实在磁盘中内容和属性是分开存储的。为什么怎么说呢?在理解了这个分组中的各个区域代表的意思就知道了。

  • Data Blocks (数据块):存文件内容的区域,以块的形式呈现。每个块的常见大小为 4KB 相当于8个扇区(如果每个扇区是512字节的话),并且这个块表示的也是文件系统的块。
  • Inode Table (Inode 表) :这是存放文件属性的地方,但不包含文件名。一个 inode 表由许多固定大小的 inode 结构体组成,每个文件都对应表中的一个 inode,每一个inode都有唯一的一个编号(inode编号)。所以在Linux系统中标识一个文件使用的就是inode编号。
    通过 ls 的 -i 选项就可以查看到文件对应的 inode 编号:

所以说内容和属性是分开存储的。那 inode 和 文件内容 又是怎么关联的呢?

Data Blocks(数据块)是由许许多多个块(例如 4KB 大小)组成的线性空间,每个块都有自己的编号,你可以把它想象成数组的下标。而 Inode Table 中的每个 inode 结构体里,不仅存储的一个inode的编号,还存储了以下关键信息:

  • 文件大小。
  • 文件所有者.
  • 文件权限(rwx)。
  • 时间戳(创建、修改、访问时间)。
  • 数据块索引数组
  • ......

数据块索引数组,这个数组中存储的是在 Data Blocks 中存储文件内容的块编号。通常这个数组中里有 12-15 个数据个数,这其中也分为了直接和间接的索引。

  • 如果文件很小,则前 12 个数组内容存储的编号直接指向 Data Block 中存放内容块的编号。系统直接去读这些块即可;
  • 如果文件较大,超出了直接指针的范围,系统会使用第 13 个索引。这个指针指向的一个数据块不再存储文件内容,而是存储了一个“块编号列表”。通过这个列表,再去找存内容的数据块。
  • 如果文件更大,则还会使用到二级(第14个下标)或三级索引(第15个下标),它们执行的数据块中存储的还是块编号列表,

具体内核的实现如下图所示:

这样我们就可以通过inode结构体中的这个数据块索引数组找到Data Blocks中存储内容的块,从而实现一个inode对应一个文件内容的关联。

注意:

这里的inode是文件系统中的inode,不是Linux操作系统源代码(VFS(虚拟文件系统)层)中的inode 。

  • Inode Bitmap (Inode 位图):表示当前块组中 Inode 表的占用情况。位图中的每一位对应一个 inode 编号,如果该位为 1,表示对应的 inode 已经被使用;如果为 0,表示该 inode 空闲,可供分配。
  • Block Bitmap (数据块位图):表示当前块组中数据块的占用情况。位图中的每一位对应一个数据块编号,如果该位为 1,表示对应的数据块已经被使用;如果为 0,表示该数据块空闲,可以用于存储新数据。
  • Group Descriptor Table (块组描述符表):表示当前这个分组的使用情况。它是一个由多个“块组描述符”组成的数组,每个描述符对应一个块组,记录了每个块组内部的使用情况信息(如inode位图的使用情况等)。
  • Super Block (超级块):表示文件系统整个分区的基本信息,例如:总块数、总 inode 数、块组总数、每个块组的大小、每个块组的 inode 数和块数、文件系统状态等。它是文件系统挂载时首先读取的关键结构

注意:

  • Super Block (超级块) 并不是每个分组都有的。虽然标准的 Super Block 只存在于第 0 号块组(开头),但为了防止损坏,文件系统通常会在后续的特定块组(如第 1, 3, 5, 7... 号块组)中保留超级块的备份。当主超级块损坏时,可以使用这些备份进行修复。
    识别文件类型就需要用到一个"魔术数"的概念,它是超级块中的一个固定字段,文件系统工具通过读取超级块的魔术数,识别文件系统类型
  • inode 总数整个文件系统(分区)的属性,属于分区级别的(所有块组的 inode 数之和等于超级块中的总inode数),分配是是以分区为单位的。

2. 磁盘格式化介绍

以上是我们ext文件系统的整个结构,在进行分区之后,但是这些复杂的结构并不会自动出现在新硬盘上,每一个分区在被使用之前,都必须提前将部分文件系统的属性信息设置进对应的分区中——这个过程,就是我们常说的‘格式化’。

具体来说格式化就是要将超级块、块组描述符表、Inode位图、数据块位图按照特定的规则提前“写入”到磁盘分区的物理空间中。即:

  • 超级块要提前写入,因为它记录了文件系统的类型(如ext2,ext4)、总块数、空闲块数、Inode总数等最核心的全局信息。没有它,系统甚至无法识别这是一个什么类型的文件系统。
  • 块组描述符表需要提前写入,因为它可以告诉系统每个块组的Inode表在哪里、数据块在哪里、还有多少剩余空间等。
  • 初始化位图:创建或重置 blocks bitmap 和inode bitmap,也就是将它们全部初始化为“未使用”状态
  • 而Inode表和数据块,虽然不需要像超级块那样写入具体的配置参数,但必须在磁盘上预留出连续的空间位置。
    • 对于Inode表:格式化程序会根据分区大小计算出需要多少个Inode,并在磁盘上划出一块区域专门存放它们。
    • 对于数据块:这是真正用来存储文件内容的地方。格式化会将剩余的绝大部分空间切割成一个个固定大小(如4KB)的块。虽然此刻里面没有数据,但必须通过前面的“位图”将它们标记为全0(即全部空闲),以便后续随时可以被分配使用。

总之,格式化就是对分区写入管理信息,然后每个分区从一个“存储容器”变成了一个可被管理的“文件系统”。

3. 理解文件操作

那么基于上述逻辑,我们可以深入理解 Linux 下文件操作的本质。当我们执行新建、删除、查找或修改文件时,系统底层究竟在做什么?

Linux系统中,一个文件,一个inode。每一个inode都有自己的inode编号(inode的设置,是以分区为单位的,不能跨分区),inode 中包含了文件的所有属性既然 Inode 里不存文件名,那系统怎么知道一个文件名对应哪个 Inode 呢?答案就在目录里。

为什么需要“目录”?

首先来看看目录,目录页是文件,所以目录页有属性和内容,也有自己的inode,也有存放数据的 Data Block。那么目录里面存的是什么呢?

普通文件的 Data Block 存的是文本或二进制数据,而目录的 Data Block 存的是:该目录下所有文件的 文件名 与其对应的 Inode 编号 的映射关系

结论:目录的作用,就是维护一份“文件名 -> Inode”的映射关系列表。

拓展:目录的inode又是怎么知道的呢?

目录是文件,所以目录的inode是在它的上级目录的 Data Block 中存着,那么上级目录又是怎么找到的呢?这有要看上上级目录了,如此重复,直到根目录,而根目录没有父目录,它依赖于文件系统的预定义规则,所以根目录的 Inode 编号是固定不变的,一般inode是2

所以任意找一个文件,都必须带路径,通过路径,因为Linux系统是一颗树,所以就要从根目录(/)出发向下找到当前目录的inode,从而找到目录内容,从而找到要找的文件的 inode,再重复这个过程,直到找目标文件的inode。这就是Linux系统查找文件的过程,是一个单向层层遍历的过程。

然而,如果每次访问文件都要进行如此繁琐的磁盘 I/O 操作,系统性能将极其低下。为了解决这个问题,Linux操作系统会将最近访问过的目录项(即“文件名”到“Inode”的映射关系)保存在内存中,这种机制叫做 Dentry 缓存

新建一个文件,系统要做什么?

  1. 系统先检查当前目录的 Data Block,确认没有重名文件(因为同一目录下不能有重复的文件名映射)。
  2. 然后通过目录,定位到当前文件所属的分区,并准备在该分区内分配 inode。。
  3. 再在这个分区中查Group Descriptor Table,找到有空闲的分组。
  4. 最后在Inode Bitmap中找空闲的inode,进行分配并写入文件属性。

如果要对文件进行写入,则就是在找到inode的基础上通过数据库数组关联到内容,然后对Data Blocks进行写入。

删除一个文件,系统要做什么?

  1. 系统先通过路径找到目标文件的 Inode。
  2. 将Inode Bitmap中对应的inode编号置0
  3. 将inode结构体中数据块数组中的编号在Block Bitmap中置0。

因此,删除文件时只是将Inode Bitmap和Block Bitmap中的数据置为无效而已。但是像拷贝文件这类写入操作,不仅要修改Inode Bitmap和Block Bitmap,还要修改Data Blocks区域。这也是为什么删除文件一般都比写入文件(如拷贝等操作)快的原因。

与之对应恢复文件也就是将这些位图(Bitmap)中的数据重新置为有效(置1),这也是为什么我们在误删文件后,第一原则是立即停止对该磁盘的任何写入操作,防止原始数据被新数据覆盖,导致永久无法恢复。

查找一个文件,系统要做什么?

  1. 通过路径找到目标文件的 Inode。
  2. 判断Inode Bitmap中对应的inode编号置0是否有效。
  3. 有效则查找成功;否则则失败。
  4. 成功之后,通过inode中的数据块数组找到对应的内容块,再将它们加载到内存就拿到了文件内容了。

修改一个文件,系统要做什么?

  1. 先找到inode,就拿到了对应文件的所有属性以及数据块数组(关联了文件内容)
  2. 修改内容,则就是在数据库数组中进行增、删操作...
  3. 修改属性,就是对inode属性进行写入,删除操作...

三、理解软硬链接

1. 软链接

建立软链接

对一个文件建立软链接的指令:

ln -s 原文件名 软链接的文件名

如果要对文件 file.txt 建立软链接,则具体过程如图所示:

并且从图中,我们可以看出:

  • 软链接文件和原文件的inode是不一样的;
  • 软链接文件的大小也比原文件要小。

以上便是软链接的方法和所看到的现象。

理解软链接

软链接,也被称为符号链接软链接是一个完全独立的文件。它拥有自己独立的 Inode 编号,它拥有自己独立的数据块(Data Block),它的数据块里只存了一行文本字符串,也就是指向目标文件的路径(例如 /home/user/original.txt )

可以把软链接理解为 Windows 系统中的“快捷方式”。当你访问软链接时,操作系统读取它的内容,然后跳转到那个路径去找真正的文件。如图所示:

但是如果源文件被删除了,软链接就变成了“死链接”,因为它指向的路径不再有效。如图:

要删除软链接,有两种方法:

  1. rm + 软链接文件名
  2. unlink + 软链接文件名

注意:不要使用 rm -rf <软链接文件名>,如果软链接指向目录,-r 参数会递归删除目录内容,导致误删源文件!

应用场景

正是因为软链接可以当做快捷方式的作用,而在系统 /lib64/ 目录下存在大量软链接,这主要是为了实现共享库的版本管理与向后兼容。 它允许程序通过一个通用的名称(如 libssl.so)来调用库,而实际上指向的是具体的版本文件(如 libssl.so.1.1)。这样既保证了旧程序能找到依赖,又方便系统在升级库文件时,只需更新链接而无需重新编译所有软件。如下就是Linux中/lib64/目录下的文件:其中,比如 ld-linux-x86-64.so.2 就是一个软链接,指向的是 ld-2.17.so 。

2. 硬链接

建立硬链接

对一个文件建立硬链接的指令:

ln 原文件名 硬链接文件名

这里和软链接不一样,因为硬链接不用加 -s 选项。使用示例:对文件 test2.txt 建立一个硬链接,过程如图所示:从图中我们可以得到以下现象:

  • 软链接文件和原文件的inode是一样的
  • 软链接文件和原文件的硬链接数从1变成了2。

文件属性中的硬链接数

在一个存在文件的目录中,通过ls -l 可以看到以下信息:如图所示其中蓝色的框框中的数字就是硬链接数,它本质是一个计数器,是在inode结构体中的一个属性,表示有多少个文件名指向同一个inode编号。因为在当前目录中保存了文件名和文件的inode编号的映射关系,虽然 inode 编号唯一,但是也会存在多个不同的文件名对应同一个inode的编号的情况,也就是多个文件名映射一个inode编号,所以这些文件本质上都是同一个文件,只是文件名不同而已。

所以当我们执行 rm 文件名 删除一个文件时,实际上只是将其 inode 中对应的硬链接数减一,直到这个数减到 0 时,才会正真执行删除操作(其实也就是对inode位图和block位图置零)。

结论:

硬链接不是一个独立的文件,因为它没有独立的 Inode。所谓的“建立硬链接”,本质上并不是创建了一个新文件,而是在特定目录的数据块中,新增了一条“文件名 : Inode 编号”的映射关系。那么当删除一个文件时,硬链接的文件都不会受影响,只是这里的映射关系少了一条而已。

新创建的普通文件硬链接数为什么是1?新创建的目录为什么硬链接数为2?

新创建一个普通文件,因为此时该文件只有当前这一个文件名,所以引用计数就是1,所以硬链接数就是 1。但是对于新创建的目录,我们会看到它的硬链接数为2,如图所示:

但是我们如果通过ls -lai 查看这个目录的inode编号,以及这个目录中的一个隐藏文件 . 的编号,就会发现:新创建的 dir 目录和它里面的一个隐藏文件 . 的inode是一样的。而IG木箱下面的这个两个隐藏文件( . / .. 也是硬链接)也就是说对应图中1188347这个inode编号,是有两个文件名指向它的,所以这里目录的硬链接数为2。

补充:如果我们要求一个目录下相邻子目录的个数,则可以使用当前目录的硬链接数减2,得出结果。因为每个子目录中有一个 .. 的隐藏文件对应的就是当前目录。

但是,Linux系统不允许手动对目录建立硬链接, . .. 只是系统在设定时建立的,属于特例

在Linux中不允许手动对目录建立硬链接,如图所示:因为如果建立了的话,会引发系统的一个环路问题

在 Linux 中,文件系统是一棵以根目录 / 为起点的倒置树。当我们访问一个文件(例如 /home/user/test.txt)时,内核的查找逻辑是单向且分层的:首先会从根目录出发,找到根目录 / 的 inode(通常是 2 号),然后遍历根目录文件的内容,找到第二个目录 home,再在这个目录文件的内容中遍历去找 user 这个目录,最后再在 user 这个目录中去找这个文件。

所以如果我们在某一个目录下建立了一个硬链接(比如说是指向 根目录 的硬链接),那么当遍历当这个硬链接所在的目录时,找到这个硬链接时,则就又会回到根目录,由此出现不停的寻找的情况,也就是死循环,即出现环路问题

虽然用户不能创建,但系统内核在创建目录时会自动生成两个特殊的硬链接文件

  • . (当前目录):指向自己。
  • .. (父目录):指向上一级目录。

这两个文件由内核严格控制维护,目的是为了确保目录树结构的完整性,而不会引发上述的死循环问题。

3. 总结

  • 软链接,也被称为符号链接软链接是一个完全独立的文件。它拥有自己独立的 Inode 编号,它拥有自己独立的数据块(Data Block),它的数据块里只存了一行文本字符串,也就是指向目标文件的路径(例如 /home/user/original.txt )。相当于快捷方式。
  • 硬链接不是一个独立的文件,因为它没有独立的 Inode。所谓的“建立硬链接”,本质上并不是创建了一个新文件,而是在特定目录的数据块中,新增了一条“文件名 : Inode 编号”的映射关系

四、文件系统的底层逻辑补充

磁盘和内存交互的基本单位

在计算机操作系统中,内存和磁盘是两种速度差异巨大的存储介质。为了协调这两者之间的工作,操作系统引入了一种核心的数据交换机制。这种交互并非以单个字节或任意大小的数据块进行,而是基于一个固定的基本单位,大小为一般为4KB。

物理内存会被操作系统划分成一个个大小相等、连续的块,每一个块的大小为4KB。根据上面关于文件系统的认识,我们也知道磁盘上的文件的内容是按照一个个数据块存储的,大小也是为4KB。

当磁盘和内存进行文件级交互时,是以4KB为单位进行交互的,物理内存中的4KB叫做页框,而磁盘上用于与内存进行交换的数据单位叫做页帧,大小也为4KB。如图所示:注意:

  • 因为是以4KB的块位单位来交互数据的,所以即使要访问的是1KB,也需要将这4Kb全部加载;又比如如果修改了磁盘中一个文件的某1KB的数据,则它所在的整个4KB的空间都要重新写入。
  • 如果不想要以4KB的大小来交互数据,则就需要去修改操作系统源代码,并重新编译。但是最常用的还是4KB。
  • 为什么一定是4KB整块交互呢?这其实是在计算机发展史中经过验证的。
    对于太小的情况,原因有两点:在硬件IO上,如果每次只读取几个字节,频繁启动磁头寻道或发送指令,效率极低。通过一次性读取或写入一个较大的固定块(4KB),可以显著减少访问外设(磁盘)的次数;基于计算机中的局部性原理(即程序运行时,往往倾向于访问最近访问过的数据或附近地址的数据),所以操作系统在读取数据时,不会只读取请求的那一点点,而是会将包含该数据的整个 4KB 页框都加载到内存中,这就也是程序的预加载机制。
    对于太大时,比如 1MB,哪怕程序只用了 1KB 数据,也要占用整个块,造成严重的内部碎片。

具体如何交互,是有操作系统管理的,我们不关心。

操作系统如何管理内存?

之前虚拟地址空间时我们知道,它是操作系统提供的,我们在应用层看到的是虚拟地址,就是因为操作系统在虚拟地址空间做了很多事,所以滁州学院一定是要看到物理地址的。

现在我们已经知道物理内存就是一个个连续的4KB大小的块组成的,但是这样的块有很多,,那么,操作系统是如何知道哪些被使用了,哪些没有使用,哪些内存被使用了多少,哪些内存是被锁定的,.......,这样的一系列问题,所以操作系统是怎么管理内存的?答案就是:先描述,再组织

所以操作系统首先需要定义一个数据结构来描述每一个4KB页框的属性。在 Linux 内核等系统中,这个结构体通常被称为 struct page ,即:

struct page {
    // page页必要的属性信息
    // 例如:该页是否被占用?属于哪个进程?是否在磁盘上有备份?引用计数是多少?
};

在源码中如图:其中flags表示了当前页框当前的使用状态(有没有被使用等),而_count则是一个引用计数表示当前有多少个内核组件正在使用它,这也关于发生写时拷贝原理中的关键点。

有了描述单个页框的结构体后,操作系统采用了一种非常直接且高效的方式来组织所有物理页框:数组。

如果假设系统有 4GB 的物理内存,每页大小为 4KB( 2^{12}字节)。 总页数 = 4GB/4KB=2^{32} / 2^{12} = 2^{20}=1048576页。那么操作系统会定义一个巨大的全局数组:

struct page mem_array[1,048,576]; // 假如数组名为 mem_array

而数组的下标就对应了物理内存的第几个4KB的页框,所以每一个页框就有了对应页号的概念,这里的下标就是对应页框的页号

因为每一个页狂的大小是固定的(4KB,即 2^{12} ),所以物理地址的低 12 位代表页内偏移,而高位部分则直接代表页号(Page Number),也就是数组的下标。所以我们如果访问一个物理地址如 0x11 22 33 44,只需要将它通过位运算比如 右移 12 位 或者是 & 0xFF FF F0 00 就可以找到对应的页框了。

所以对内存的管理就变成了对数组的管理。

因此,当我们需要操作某一块物理内存时,只需在数组中找到对应的 page 结构体,就能定位到它在物理内存中的具体位置(物理页框),进而读写其中的数据。所以,所有针对物理内存的生命周期管理操作(如申请和释放),本质上都是在操作这个内存 page 数组。

内核级缓冲区:文件页缓冲区

当我们计算机开机时,操作系统会预先把文件系统相关的信息(比如Super Block,Group Descriptor Table ,Block Bitmap,InodeBitmap等)加载到内存了。

当我们通过一个进程打开一个文件对其进行读写时。首先打开了这个文件,在操作系统中就有了一个 struct file 结构体,进程task_struct可以通过文件描述符fd找到它。此时文件的属性和内容呢怎么找到呢?其实在struct file中只保存了少量的属性的数据(比如文件描述符这个的属性都没有在这个结构体中),正真的文件属性是在操作系统创建的一个struct inode 结构体的。所以当我们打开一个文件时,操作系统会根据这个文件名,在目录中找到这个文件的inode编号,然后确认磁盘中的inode bitmap 中中文件是否存在,如果存在,则就将磁盘中indoe table中这个文件 inode 对应的属性加载到操作系统内核中struct inode 结构体中。最后通过struct file 我们就能够找到这个 struct inode,从而找到文件属性。比如在Linux2.6的一个源代码(不同版本的代码可能不一样)中的定义如图所示:

那么文件内容呢?在Linux操作系统的源代码中的 struct file 中有一个 struct address_space *f_mapping; ,这个结构体中有一个: struct radix_tree_root page_tree; ,这个page_tree本质上是一个基树/字典树,这棵树的叶子节点存着的是一个个struct page 数组。如图所示:

当通过一个进程对一个文件进行读写时,在内核中保存文件内容的就是保存在这一个个的struct page对象中的。这就是内核中的文件的页缓冲区,也就是操作系统内核级缓冲区,即文件缓冲区。最后这些page对象中数据都会写入到内存中,再与磁盘进行交互,最后这一步是操作系统自己完成的。

总结一下文件访问的全过程

当打开一个文件时,会预先将这个文件在磁盘中的inode属性加载到内存,然后当在写数据到文件中时,首先会将数据写入磁盘的文件中时,首先会将内容加载到用户级缓冲区(C语言自己提供的,FILE结构体中维护的缓冲区),让后再通过系统调用(比如write),就会将数据从用户级缓冲区写入到内核级缓冲区(也就是这里的页缓冲区,即这里叶子节点中的一个个struct page对象中),最后才会将数据通过4KB的数据交互从物理内存写入到磁盘中。总结一下就是例如图这样的结构:

小知识:当数据写入磁盘会经历3次拷贝:用户缓冲区拷贝到内核缓冲区,内核缓冲区拷贝到物理内存,物理内存拷贝到磁盘。


感谢各位观看!希望能多多支持!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐