前言:

前面我们已经对于文件的操作有了深刻的理解,我们使用open系统调用将文件打开,那么此时我们的文件是内存级文件,那么我们没有打开的文件我们也提到过,此时我们的文件就在磁盘中,此时文件为磁盘级文件。

那么我们要理解磁盘级文件,就要先对我们的磁盘存储方式,存储结构有一定的了解。

然后我们的操作系统是计算机软硬件的管理者,那么我们的操作系统也要对其进行管理,线描术后组织,所以也要学习操作系统是如何进行管理的。

一、磁盘

1、认识磁盘

机械磁盘是计算机中唯一一个机械设备。

其也是一个外设,然后其价格相比内存,比较低,而且容量很大,但是其运算速度比较慢。目前我们主流的是使用SSD。对于磁盘,现在大部分都是企业服务器进行使用的多。

其大致样子如下:

可以看到,我们的磁盘,和我们小时候看到的光盘很像,其表面是非常光滑的。

上面光滑的那面,我们称为盘面,然后那个在盘片上面的那个针,我们称为磁头。

这个磁头,未来会在对数据读取,写入的时候,其转动,在盘面上进行读取写入。

2、磁盘的物理结构

下面我们先来看一张图:

上面就是我们磁盘中的物理结构了,其中间有一个马达,是带动我们的磁盘进行转动的,然后我们的磁盘,就是那个光滑的那面,就会在上面进行转动,其就好比我们的纸张,然后我们的磁头,就会从磁盘上进行写读操作。

3、磁盘的存储结构

如上图,是一个磁盘的一面,我们肉眼看我们的磁盘,其是很光滑的,但是实际上,我们要是使用显微镜来看的话,我们可以看到,在我们的磁盘上,会有很多粗糙的道,这些我们称为磁道。

然后这些磁道中,又会被分为一个一个扇区:

可以看到,我们的磁道并不是完整连续的,其又被分为了一个一个的扇区。

未来,我们的磁盘的存储单位就是一个扇区。

我们还发现,越远离我们的轴心,那么我们的磁道是越长的,那么是否越远离轴心,我们的扇区越多,那么去存储的数据量更大呢?

其实不然,在一个盘面上,我们每个磁道的扇区数是一样的,那么也就是说,我们的盘面上,所有的磁道的存储数量都是一样的。

当然,对于这个磁盘中的扇区的数量,有的厂家的设计不一样,可能也会不一样,有的为了提高存储效率,那么就会将其疏密程度都弄的很高,那么外层的数量就更多,那么整个磁盘的存储数量也就更大,但是也会增大我们存储算法的复杂性。

后续,我们对于磁盘的扇区,我们就认为是一样的来学习。

我们的磁盘中,也不止一个盘面,我们上面看到的是一个俯视图,只能看到一个盘面,我们将视角移动到侧面,我们再看:

可以看到,我们一个磁盘系统中,是由多个盘面组成的,我们每个盘片其是有两面的,其每一面都会有一个磁头:

然后我们的磁头都是使用的一个转动臂的,那么也就是说,我们的磁头未来转动的速度,转动的磁道的半径都是一样的,所以,我们的磁头所在的磁道,我们将其组合起来,那么不就是一个圆柱体的一面么。

所以我们又将所有磁头的磁道组合成的称为一个柱面。

上面我们还提到了每个磁道又是分为很多个扇区的,一个扇区的存储容量为512字节。其是我们磁盘对于数据存取的基本单位,我们又称为块设备。后续我们也会进行讲解。

下面我们可以思考一下,我们要对磁盘中写入一个4KB大小的数据,那么我们如何找到具体的扇区进行写入呢?

那么首先,我们得找到我们要写入的盘面,找到盘面后,我们再找到对应的磁道,然后在磁道中,我们最后就找到了对应的扇区。

磁头(header),磁道(cylinder),扇区(sector)

所以上面的方式我们又称为CHS地址定位。

这个寻址方式是站在磁盘的物理结构上构思出来的。下面我们再来看看在操作系统中是如何的。

4、磁盘的逻辑结构

我们可以将磁盘中的磁道,拉直,那么其就是一条直线,那么我们再来,将扇区带入,那么不是一个一维数组吗。那么磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象成为卷在⼀起的磁带,那么磁盘的逻 辑存储结构我们也可以类似于:

然后我们几个磁头指向的磁道,组合起来的柱面,那么不就是一个二维数组么?

然后我们的整个磁盘系统,又可以由多个柱面组成,那么就是一个三维数组了。

然后,我们还有一个操作,就是我们又可以将三维数组,组合成一个一维数组

那么我们对于扇区的定位,不就是一个数组的下标了嘛,这种方式我们称为LBA寻址。

那么上面的理解,我们知道,我们对于一个扇区的寻址,实际上是一个三维数组,那么我们上面所提到的,寻址操作是有问题的,是基于我们开始理解接触磁盘的物理结构所提出的。这是我们重新理解了其物理结构得到的。这个地址我们称为CHS地址。

其实际情况是:先确定那个柱面,就和我们的三维数组一样先确定第一个下标,那么就是确定柱面,然后我们再确定盘片,也就是确定在二维数组中的那一行,然后最后就是确定扇区了。

在我们的操作系统中,我们是将这个三维数组抽象成一个一维数组的,抽象成一维数组的这个地址我们称为LBA地址。

上面,物理结构的CHS地址,逻辑结构的LBA地址,我们之间也可以通过方法进转换,不过这个事情,磁盘会自己进行。

5、CHS地址&&LBA地址

CHS转成LBA:

• 磁头数*每磁道扇区数=单个柱⾯的扇区总数

• LBA=柱⾯号C*单个柱⾯的扇区总数+磁头号H*每磁道扇区数+扇区号S-1

• 即:LBA=柱⾯号C*(磁头数*每磁道扇区数)+磁头号H*每磁道扇区数+扇区号S-1

• 扇区号通常是从1开始的,⽽在LBA中,地址是从0开始的

• 柱⾯和磁道都是从0开始编号的

• 总柱⾯,磁道个数,扇区总数等信息,在磁盘内部会⾃动维护,上层开机的时候,会获取到这些    参数。

LBA转成CHS:

• 柱⾯号C=LBA//(磁头数*每磁道扇区数)【就是单个柱⾯的扇区总数】

• 磁头号H=(LBA%(磁头数*每磁道扇区数))//每磁道扇区数

• 扇区号S=(LBA%每磁道扇区数)+1

• "//":表⽰除取整

所以:从此往后,在磁盘使⽤者看来,根本就不关⼼CHS地址,⽽是直接使⽤LBA地址,磁盘内部⾃⼰ 转换。所以: 从现在开始,磁盘就是⼀个元素为扇区的⼀维数组,数组的下标就是每⼀个扇区的LBA地址。OS使⽤ 磁盘,就可以⽤⼀个数字访问磁盘扇区了。

二、文件系统

通过上面对于磁盘硬件的理解,我们对于磁盘的结构,都有一定的认识。

那么在操作系统中是如何操作的呢?

1、块概念

前面我们知道,我们的磁盘中的最小单位就是扇区,一个扇区是512字节,那么我们的操作系统对于文件的存取操作是否是一个扇区一个扇区这样呢?

其实并不是,而是连续几个扇区这样读取的,在我们的操作系统中,其将磁盘中的八个扇区当成一个块区,然后其读取就是按块为单位进行的。所以我们的磁盘文件系统在操作系统中,会将八个扇区又合为块,那么其大小就是4KB,那么这个就是我们操作系统中对于文件读取的基本单位。

我们的磁盘系统在操作系统中的组成就可以为下图所示了:

如上图所示,每八个扇区为一个块,然后对于块,其也有对应的编号,注意的是其块号的下标是从0开始的。

LBA地址和块号之间的转换:

我们每个扇区都有其对应的LBA地址,然后我们八个扇区为一个块。

已知道LBA地址:

块号=LBA地址/8

知道块号:

LBA地址=块号*8+[1~7]

块号*8=这个块号的起始LBA地址

操作系统为啥不直接使用扇区为单位进行读取呢?

首先就是扇区的大小为512字节,这样读取的效率太低了,而我们为块为单位,那么就快很多了。

还有就是我们的磁盘厂商也很多,那么如果是按照扇区为单位读取,那么就和硬件高耦合了,所以其规定一个块区为单位,也是和我们的硬件进行解耦操作。

2、分区

上面我们提到,操作系统进行存取操作的基本单位是块,那么整个磁盘下,就是块了么?

其实并不是,在整个磁盘下,其还会做分区管理,就比如我们的学校,我们一个专业,虽然大家都是学的一个专业的,课程是一样的,但是我们还是会分为好几个班。这是为了更好的管理。

在我们的操作系统中其也使用这个方式,对我们的磁盘进行分治的方法进行管理。

就比如我们的Windows系统中,我们的硬盘分为CDEF盘这样。

那么其是如何分区的呢?

在我们Linux下,其是按照柱面为单位进行分区的,而且我们分区,也不需要将我们所属的分区都记录下来,只需要将我们的这个区域的开始和结束位置记录一下即可,这是因为在逻辑结构上,其是线性的,是一个一维数组。所以我们只需要得到这两个位置,就可以确定这个分区的范围了。

3、indoe

我们开始接触到到Linux的时候,就提到文件=内容+属性。

 那么我们对于文件的存储,是要将文件的属性和内容都存储的,那么其是存储在一起的吗?

其实在Linux下,文件的内容和属性是分开存放的。

然后呢,在我们的分区中,还会进行分组操作。 

然后这个组内,是由非常多个块区组成的。

在我们一个分区中,大概会存储如上这些信息。

下面我们来对其进行详细讲解。

首先,我们最先看看Data Blocks。

这个区域就是存储我们文件的内容的,那么其中是由非常多的块区组成的

如上图,就是我们的Data Blocks的示例图,对于每个Data Blocks 其都有唯一的编号,前面我们也提到。然后呢,上面的inode Table ,其就是存放我们文件的属性的,不过文件名字不存在在这边,然后inode Table其就是一个结构体,,其中就包括了文件的几个属性,其中就有我们这个文件使用了那个块区的编号,在这个结构体中,有一个i_block映射表,就是一个数组,其就会记录我们的文件使用了那些数据块。

所以我们又说:

文件=inode+Data Blocks

那当我们要访问一个文件时,怎么找到文件的内容和属性信息一起找到呢?

在操作系统中,inode结构体里,有一个i_block映射表,这张表记录的是一个文件使用的是data blocks里面的哪个数据块。通过映射表就能将属性和内容联系起来。

所以查找一个文件的过程如下:在一个分组中,我们要找一个文件,要先找到这个文件对应的inode编号,再通过inode编号找到这个文件对应的inode结构体,找到文件的属性,然后通过i_block映射表找到对应文件的内容信息。

三、EXT2文件系统


1、Block Group(块组)


block group由上文提到的管理文件的信息和文件的内容及属性构成

其中下面四个部分存储的是文件的管理信息,后面两个存放文件的属性和内容信息

3.2块组内部构成介绍
3.2.1Super Block(超级块)


超级块存放文件系统本身的结构信息,描述整个分区的文件系统信息。记录的信息主要有:block 和 inode 的总量,未使用的 block 和 inode 的数量,一个 block 和 inode 的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block 的信息被破坏,可以说整个文件系统结构就被破坏了。

超级块在每个块组的开头都有一份拷贝(第一个块组必须有,后面的块组可以没有)。为了保证文件系统在磁盘部分扇区出现物理问题的情况下还能正常工作,就必须保证文件系统的 super block 信息在这种情况下也能正常访问。所以一个文件系统的 super block 会在多个 block group 中进行备份,这些 super block 区域的数据保持一致。

内核中的代码如下图:

那每个组都有一个super block吗?

其实并不是每个组都有super block。2-3个块组中就会存在一个super block。

既然超级块存放文件系统本身的结构信息,描述整个分区的文件系统信息,为什么不只在开头的块组中,而需要每2-3个块组中就存放一个呢?

原因是这唯一的 Super Block 所在的扇区 / 磁道物理损坏,或者被意外覆盖,整个文件系统就会彻底无法识别,所有数据都变得不可访问。但是如果有备份时,即使主super block损坏,系统还能加载备份副本,让文件系统恢复正常。所以2-3个块组中就会存在一个super block的副本。

2、GDT(块组描述符表)


GDT 全称是 Group Descriptor Table,它是 ext2文件系统里,用来管理每个块组元数据的核心结构。每个块组对应一个 Group Descriptor(块组描述符),里面主要记录:该块组的 block bitmap 所在块号、该块组的 inode bitmap 所在块号、该块组的 inode table 起始块号、该块组的空闲块数、空闲 inode 数、已分配目录数等统计信息。
系统要读写某个块组时,先查 GDT,就能精准定位到这个组的 bitmap 和 inode 表位置,不用全磁盘扫描。

3、Block Bitmap&&inode Bitmap


这两个都是位图,一个用于管理数据块,一个用于管理inode。

block bitmap的比特位的位置代表的是块编号,而比特位为0为1代表的是对应的块是否被占用。

inode bitmap的比特位的位置代表的是inode编号,而比特位为0为1代表的是对应的inode是否被占用、是否有效。

这两个位图是这样使用的:

block:当把对应块的位从 1 → 0,标记为空闲可复用,告诉系统这块可以被新的数据覆盖了,相当于删除操作。新建文件、写数据时,系统不用遍历整个磁盘,只需要扫block bitmap,将一个位置的0 → 1,来分配数据块。

inode:把对应 inode 的位从 1 → 0,标记为空闲可复用,告诉系统这个位置可以被新的文件属性覆盖了,相当于删除操作。新建文件时,系统必须先拿到一个新 inode。扫 inode bitmap ,将一个位置的0 →  1 ,来分配 inode。

但这两个删除操作都不会真正去清空磁盘上的原始数据,只是在位图表上划掉了这个条目,告诉系统:这块地方可以被新数据覆盖了。

这样设计的好处在于:

1.速度极快:只需要修改 bitmap 里的一两个 bit,比真的去擦除整块数据快成千上万倍。
2.方便恢复:只要新数据还没写入,被删除的文件数据和 inode 信息还在磁盘上,就有机会通过数据恢复工具(如 extundelete、testdisk)找回来。
3.延迟写入:只有当新文件需要占用这些「空闲」块时,旧数据才会被真正覆盖掉。

4、文件创建,删除,修改,查找的过程

上面我们已经对整个磁盘系统有了完整的理解,也知道了在磁盘系统中,对于文件的管理是如何进行的。

下面我们来看看我们对于文件进行操作,具体是干了些啥。

首先是文件的创建:

创建文件,那么此时是没有写入内容的,那么就是一个空文件,所以呢是先对文件的属性进行写入,那么就先找表示存放文件属性的那边,那么我们得找一个空位置,所以就去这个分组的inode bitmap中去找,那么其实际上是一个二进制数,那么就通过位运算,找到一个为0的,然后将其置为1,然后通过其是第几个bit位,然后找到对应的inode table位置,然后将文件的属性写入。

文件删除:

我们知道,在分组中,存在两个二进制表,inode bitmap和block bitmap,删除这个文件,先找到其对应的inode,然后找到inode bitmap,将其对应位置的bit位从1改为0,然后将inode table中的引用技术归0,然后将对应的block bitmap从1修改为0,那么在操作系统看来,那些对应的位置的块区就可以被使用了。所以对于文件的删除,并没有去删除文件属性和内容所使用的块区的内容,而只是去对其inode bitmap和block bitmap进行修改,所以我们会发现,我们删除文件的时候会非常快。

文件的修改:

文件修改分为内容修改和属性修改两类。内容修改时,系统先通过 inode 编号加载 inode,根据文件偏移量找到对应的数据块:若修改未超出原块范围则直接覆盖数据,若需要新增内容则从 block bitmap 中分配空闲块,标记为已分配后写入新数据,并将新块号添加到 inode 指针中,同时更新 inode 的文件大小和修改时间戳。属性修改则直接定位到 inode table 中的对应条目,修改权限、所有者、时间戳等元数据,无需操作数据块,修改完成后同步更新磁盘上的 inode 信息。

文件的查看:

就是通过该文件的inode,找到其对应的inode table,这就是可以看到我们文件的属性,然后也是一样通过inode找到其在Data block数据块中的使用的块区,就可以查看到了。

5、目录文件

对于目录,我们先确定一个点,我们的目录也是文件,Linux下一切皆文件,所以目录也是文件,目录文件的内容就是存放的其下级目录和文件的属性,所以在文件系统中,对于目录的创建,修改,删除,查找就和上面所说到的一样,和普通文件一样。

然后,对于目录权限,就比如我们的读权限被限制了,那么我们就无法通过inode中的映射数组,查看到其使用的块区,那么就无法读取到这个文件。

写权限,就是我们要将数据写入这个目录文件的数据块区,但是其不允许我们去建立inode中和数据块区之间的映射关系。这个目录中,还会保存这个文件名字和文件inode的映射关系,所以这也是为啥我们不让一个目录文件下有两个名的文件。

所以也更加说明了,Linux下,一切皆文件。

6、重谈inode

inode编号,其是可以跨组的,但是不能跨分区,也就是说其不仅是在自己这个组内有效,还在整个分区有效。

前面我们知道,一个分区的大小,其有多少块区,多少个组,其实是知道的,都是提前设计好的,固定好的。

所以我们知道我们每个分组的块号,那么我们拿到一个块号,是可以计算出块所在的组区的。

组区=块号/一组的块号数量

我们可以求出其在那个Group。

我们在对于一个非常大的文件进行存储的时候,如果一个分组的块号不够用,那么其实是可以跨组进行存储的,我们只需要将其使用的inode编号进行记录即可。

7、路径解析和目录问题

上面我们对于目录提出了,目录也是一个文件的概念。目录存储的内容就是其下级文件和目录的属性还有inode等。

所以我们要找到一个文件,那么就要从其绝对路径层层往下找。因为其inode的信息是存储在其上一级目录的。所以我们前面提到,我们对于文件的操作,要传递文件的路径。

那么我们的操作系统是从那里开始找呢?

入口在那里?

我们对于文件的查找,都是从根目录开始层层递归查找的。根目录的inode是固定的。

所以我们每次对一个文件进行查找,都需要从开始位置开始往下查找,但是这样感觉效率又比较慢。

所以在我们的Linux中,会对我们用户访问过的路径进行一个缓存,这叫路径缓存。

那么我们用户对很大文件进行访问,那么就会导致对于路径的缓存也是很多的,那么也是需要进行管理的,所以,也有一个结构体进行管理:

struct dengtry

{}

其是一个多叉树,

然后呢,其会为其分配inode。

那么就如上图所示,其实这个就是前面我们学习Linux系统刚刚开始的时候,我们见到一个目录树,其就是这个目录树的子树。

然后并不是只有目录有这个结构体,其实所有文件都会有,对于普通文件,还有空目录,那么就是叶子节点。

然后我们对于路径搜索,在第一次的时候速度都会慢一点,第二次以后速度就会快一点了,这是因为这个路径被我们操作系统缓存起来了。

上面我们提到,对于文件的访问是一定要通过路径的,那么我们传入的是相对路径,并不是完整的路径,那么这又是如何操作的呢?

平时我们操作文件,不管是终端输入命令、使用软件工具,还是代码中打开文件,底层都是进程发起文件访问操作。每个进程都维护独立的当前工作目录 CWD,进程会以该目录为基准,解析你输入的绝对路径或相对路径。也就是说,路径由进程负责解析处理,我们输入的路径字符串,相当于指引进程定位文件的地址指令。Linux 整体路径体系,由系统自带的标准根目录层级,以及用户后续新建的文件、文件夹共同组成。

8、对大文件如何存储问题

如果我们要存入一个几十GB的文件,那么我们一个分组区域肯定是不够的,而且我们inode中的映射表i_block[];也要非常多的映射关系了。

但是实际上,我们的inode 中的i_block数组其大小也是有限制的:

可以看到,这个数组的长度是15,那么如果按照我们的理解,其一个位置映射一个块区,那么其最多只能映射4KB*15=60KB的数据,而且我们知道的是一个文件的inode中只有一个映射表,那么这个量级肯定是远远不行的,所以其肯定不是一个位置映射一个块区的,实际上这个数组的映射关系入下图所示:

可以看到,这个映射表中,前十二个位置是一个位置就映射一个块区,那么这里就是48KB的大小,当我们的文件大小超过了48KB,那么就会使用第十三位置的,那么其也是指向的一个块区,但是这个块区不是直接存储文件的内容的,其里面是存储的文件所使用到的,除开前面十二个位置所使用到的块区的其余块区,那么一个块区是4KB,然后我们一个地址是4字节大小,所以其可以存储1024个地址,那么也就是说这个块区可以存储1024个块区的地址,那么其最大也可以指向4MB大小的块区了。

如果此时还是不行,那么就会使用第十四个位置,其是一个二级间接索引表指针,其也是先指向一个块区,然后这个块区,其也B不是存储块区的地址,其又再指向一个块区的地址,这个我们称为二级块区,那么到这个二级块区,其才直接进行指向我们文件使用的块区,那么一个二级指针,可以指向1024个块区,然后我们一个一级索引指针块区,又可以指向1024个二级索引块区,那么总的来说就是4KB*1024*1024=4GB

那么如果我们文件又大于4GB4MB48KB,那么还有三级间接块区索引表指针。那么其逻辑和上面一样,不过这个是嵌套了三层这样,那么其总的存储大小就为1024*1024*1024*4KB=4TB。

所以最后的三级块区索引表理论上可以指向块区的总容量,已经超过很多磁盘的大小了。

9、挂载分区

上面我们提到,在我们操作系统中,对于文件是会进行分区操作的,那么我们咋知道我们的文件在那个分区呢?

前面我们刚开始引入文件系统分区的时候,提到,会有一个格式化工作,那么其格式化的本质就是对磁盘分区中写入管理信息。

但是此时还是无法使用的,因为这样我们的分区是没有入口的,所以我们需要再将其挂载到指定的目录下,那么这样其就会也有一个文件路径了,那么也就有了入口,也就有方法找到了。

 四、文件打开的整个流程

下面是我们在Linux下,打开一个文化,从进程创建,到磁盘系统中的一张完整流程图:

1、进程层:从文件描述符开始

每个进程(进程 A、进程 B)都有自己的文件管理体系:

  1. task_struct —— 进程描述符,是内核中代表一个进程的核心结构
  2. struct files_struct —— 进程的文件表,管理该进程打开的所有文件
  3. struct file *fd_array[] —— 文件描述符数组,每个下标(0、1、2...)就是我们常说的 "文件描述符 fd"

💡 关键点:每个进程都有独立的文件描述符表,所以进程 A 和进程 B 即使打开同一个文件,它们的 fd 编号也可能不同。


2、打开文件对象层:struct file

文件描述符数组中的每一项都指向一个 struct file(打开的文件对象):

  • f_path:文件路径,包含两个重要成员 ——
    • mntstruct vfsmount):所挂载的文件系统
    • dentrystruct dentry):目录项对象
  • f_op:文件操作函数集(file_operations),如 read、write、open 等
  • f_mapping:地址空间映射,用于页缓存

💡 关键点:多个进程可以共享同一个 struct file(比如父子进程通过 fork 共享),它们的引用计数 f_count 会增加。


3、目录项层:struct dentry

dentry(目录项)是路径与 inode 之间的桥梁:

  • d_parent:父目录的 dentry
  • d_child:子目录 / 文件链表
  • d_inode:指向对应的 inode
  • d_op:目录项操作函数集(dentry_operations

💡 形象理解:你可以把 dentry 想象成 "文件名",把 inode 想象成 "文件实体内容"。dentry 负责把路径名翻译成具体的 inode。


4、索引节点层:struct inode

inode 是文件的真正本体,存储了文件的所有元数据:

  • i_mode:文件权限(rwx)和类型(普通文件 / 目录 / 设备等)
  • i_uid / i_gid:所有者用户 ID 和组 ID
  • i_size:文件大小
  • i_blocks:占用的磁盘块数
  • i_atime / i_mtime / i_ctime:访问时间、修改时间、状态改变时间
  • i_op:inode 操作函数集(inode_operations),如 create、mkdir、link 等
  • i_fop:文件操作函数集(file_operations
  • i_sb:指向超级块 super_block

💡 关键点:inode 不存储文件名!文件名存在 dentry 和目录文件中。这就是为什么硬链接可以有多个文件名指向同一个 inode。


5、超级块层:struct super_block

超级块代表整个文件系统的全局信息:

  • s_type:文件系统类型(如 ext4、xfs)
  • s_op:超级块操作函数集(super_operations
  • s_root:文件系统根目录的 dentry
  • s_inodes:该文件系统所有 inode 的链表

6、文件系统类型层

最右侧展示了 VFS 如何抽象不同的具体文件系统:

  • struct file_system_type:描述一种文件系统类型(如 ext4)
    • mount:挂载函数
    • kill_sb:卸载超级块函数
  • struct file_operations:具体文件系统实现的文件操作(read、write 等)
  • struct inode_operations:具体文件系统实现的 inode 操作(create、mkdir 等)
  • struct super_operations:具体文件系统实现的超级块操作

其大致会经历下面的几个流程:

五、软硬链接

1、硬链接

我们先来看现象:

我们可以使用ln 原文件   硬链接名

那么就可以创建一个文件的硬链接了,那么我们看到上面,我们对log.txt进行硬链接log.exe,然后我们发现,我们使用ll -i就可以将文件的inode号也显示出来,我们发现,此时这两个文件的inode号是一样的,而且其内容也是一样的,前面我们讲过,每个文件都有其对应的inode号,就算是拷贝的文件,其也是如此,那么也就是说,上面两个实际上指向的还是同一个空间,其就类似于我们的引用,就是给我们的文件取了个别名。

然后我们还发现,在硬链接后,其属性有个标志位变化了,从1变成了2,那么这个就是标识我们这个文件有多少个硬链接数。

那么其用处是啥呢?

其就是用来对我们的文件进行备份的。

当我们对一个文件进行删除操作的时候,其会先看我们的硬链接数,如果不是1,是一个大于1的数,那么其就是删除掉我们这个名字和这个文件的inode的映射,在存储中,并没有进行删除,所以还是实际存在的。

只有当这个标识位为1,那么就会进行删除。

在我们的目录文件也如此,而且,当我们的目录创建,其硬链接数就是2。

可以看到我们创建一个目录,其都会带有.和..两个隐藏目录,其分别就是当前目录和上级目录,那么其本质就是硬链接。

然后当我们在一个目录下,创建一个目录,那么这个子目录中都会创建一个上级目录的硬链接,那么我们可以通过这个标识位来算出其子目录。

子目录个数=标识位数-2。

然后,我们用户是不能对目录创建硬链接的。这样在find的时候,会造成环形回路,形成死循环。

对于.和..目录硬链接,系统在find的时候是做了特殊处理的,但是如果是我们用户进行硬链接,其识别不出来,无法做出反应,就造成死循环了。

2、软链接

软链接是一个独立文件,文件内部只存储目标文件 / 目录的路径字符串,相当于 Windows 的快捷方式。

有自己独立的 inode;

和源文件是两个完全不同的文件;

读写时系统会自动解析里面的路径,跳转到目标。

其使用ln -s进行创建,如下:

其有如下特点:

1. inode 独立

源文件 inode ≠ 软链接 inode

ls -i 可查看两者 inode 完全不同

2. 可跨分区、跨文件系统

硬链接只能同分区,软链接无限制,可以:

不同磁盘分区之间创建链接

指向网络挂载目录、U 盘文件

3. 可指向目录

系统不限制目录软链接,日常大量使用(如 /bin、/lib 都是软链接)。

4. 可指向不存在文件(悬空链接)

如果源文件删除 / 改名 / 移动,软链接变成失效软链接(红标)

5. 删除规则

删除软链接本身:rm link_test不影响源文件

删除源文件:软链接失效,源数据直接丢失

6. 大小特殊

软链接文件大小 = 内部存储的路径字符串长度 例:指向 /abc,文件大小就是 4 字节

然后在软链接中,其也会存在环形回路的情况:不过操作系统对其会进行检测,然后检测到环形回路就会终止。

Logo

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

更多推荐