👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍
目录
前言
-
前面我们学习文件相关的知识,如文件操作等,文件都是被进程打开(内存文件),根据冯诺依曼体系结构,被打开的文件一定会被加载到内存,由操作系统管理。(Linux学习目录)
-
但并不是所有的文件都会被进程打开,那么没有打开的文件就只能在磁盘上呆着(磁盘文件)。当然了,没有打开的文件也应当进行管理,我们把做这部分管理工作称之为文件系统。
-
文件系统属于操作系统中的一部分,通常通过磁盘或其他存储介质来管理文件(路径问题,存储问题,获取问题内容+属性,删除修改等效率问题)。因此,在学习文件系统之前,我们首先需要认识磁盘。
一、认识磁盘
1.1 磁盘的物理结构
磁盘可以指代任何一种使用磁性存储介质来存储数据的设备,包括硬盘、软盘、光盘等。在许多情况下,人们使用术语"磁盘"来泛指硬盘。
以下是磁盘的构成图:
-
盘片:磁盘通常由盘片组成,每个盘片都被涂上磁性材料。每个盘片的两面都可以用来存储数据。因此,盘片的个数决定你的磁盘容量。
-
磁头:每面盘片的上下方都会有一个磁头,用于读写数据。这些磁头可以在盘片的表面移动,以便访问不同的磁道和扇区(在后面会详细介绍)。注意:盘片和磁头不接触。磁头与盘片直接接触会导致它们之间的摩擦,从而造成磁头和盘片表面的损坏。这种损坏可能会导致数据丢失。因此磁头与盘片之间是通过一个微小的空气垫来读取和写入数据。
-
其他自行了解 ~
1.2 磁盘的存储结构
上面我们说过,数据存储在盘片上,但具体是如何存储的呢?接着往下看 ~
-
磁道:盘片表面被划分成多个同心圆,每个同心圆称为一个磁道。
-
扇区:每个磁道被划分为多个扇区,扇区是存储数据的最小单位,通常包含
512
字节或4KB
的数据。因此,磁头之所以在盘片表面上移动,其实是根据磁头(Header
)确定哪个盘面,再根据柱面 (Cylinder
)定位哪个磁道,然后再确定扇区(Sector
) 并进行读写数据。我们称定位方式为:CHS
寻址方式 -
柱面:柱面是垂直于盘片表面的一组磁道的集合,这些磁道位于不同盘片上但在相同半径位置上。
1.3 磁盘的逻辑抽象结构
首先我们可以将磁盘想象成一个线性的存储介质,想想磁带:
磁带被卷起来时,就像盘片一样是圆形的,而当我们把磁带拉直后,就是线性的。那么我们可以等同把盘片“拉直”,即想象成线性的结构。那么,我们就可以把盘片当成是数组。
盘片表面被划分成多个同心圆,即磁道。
并且每个磁道被划分为多个扇区,是用来存储数据的
因此,在我们看来,整个磁盘不就可以抽象成是以扇区为单位拼接起来的数组吗?因此对磁盘的管理,就转化成为了对数组空间的管理。
二、EXT2文件系统
2.1 分区
有了上述的知识,此时我们就可以进入文件系统的学习了,文件系统的本质就是操作系统对磁盘上的数据进行管理的方式。那么假设现在有一个磁盘的大小是800GB
,操作系统该如何管理这么大的磁盘空间呢?
操作系统为了更好的管理磁盘,于是对磁盘进行了分区(将大问题分解成小问题,采用分治的思想)。例如在Windows
下的磁盘一样被分为C盘
和D盘
两个区域。
操作系统还是通过先描述再组织的方式对一个一个分区进行管理
struct partion
{
int start;
int end;
}
当磁盘完成分区后,我们还需要对磁盘进行格式化。磁盘格式化就是对磁盘中的分区进行初始化的一种操作,这种操作通常会导致现有的磁盘或分区中所有的文件被清除。简单来说,磁盘格式化就是对分区后的各个区域写入对应的管理信息。
以一个分区为例:
总结:操作系统对磁盘的管理,采用分治思想,把磁盘进行分区,然后进行区内分组,只要把一个组管理好,进而就能管理整个磁盘。
-
启动块(
Boot Block
):在传统的文件系统中,磁盘上的每个分区通常都会以Boot Block
开头。它包含了引导加载程序所需的信息,可以帮助计算机启动并加载操作系统,使得计算机可以正常启动。 -
块组(
Block Group
):文件系统会根据分区的大小划分为数个Block Group
。而每个Block Group
都有着相同的结构组成。如Super Block
等。可以怎么理解:分区就相当于把中国分为数个省份,然后再对每个省份分组成数个市/县。那么如果将每个市/县管理好,不就把一个省管理好了吗? -
超级块(
Super Block
):存储了文件系统的整体信息,如文件系统的大小、块大小、inode
数量、挂载时间等。超级块的信息被破坏,可以说整个文件系统结构就被破坏了。 -
块组描述符(
Group Descriptor Table
):描述了文件系统中每个块组的布局和组织方式。 -
块位图(
Block Bitmap
):记录着Data Block
中哪个数据块已经被占用,哪个数据块没有被占用。使用0/1
状态表示。 -
inode
位图(inode Bitmap
):记录inode
是否空闲可用,使用0/1
状态表示。 -
节点表(
inode Table
):实际上是一个数组,数组的每一个元素都对应一个inode
编号,inode
编号则对应一个inode
结构,inode
结构保存一个文件的所有属性。注意:每个文件都有自己的inode
结构。 -
数据块(
Data block
):用于存储文件内容,以块的形式呈现。常见的文件系统数据块大小4KB
。
2.1 深入理解inode
从上我们可以得出一个结论:在Linux
中,文件的内容和属性是分开存储的!其中保存属性信息的结构称之为inode
。除此之外,因为磁盘中一定会存在大量的文件,注定了存在大量的inode
结构。因此为了区分每个文件的inode
结构,操作系统给每个文件的属性集取一个唯一编号,即inode
编号。总之,inode
是一个文件的属性集合,但为了区分大量的inode
,我们需要为每个inode
设置唯一的inode
编号。注意:inode
编号只在自己的分区内有效。
- inode编号的分配规则
ext2
文件系统会从inode
表中找到一个空闲的inode
,然后标记相应的inode Bitmap
位图,指示该inode
已经被分配。因此,inode
编号是根据inode
表中的位置分配的
struct inode
{
inode编号
文件类型
权限
拥有者
链接数
所属组
ACM时间
指向文件数据块的指针
...
}
注意:在Linux
文件系统中,文件名通常不包含在文件属性集inode
中。文件名只是用户用来标识和访问文件的一种便捷方式。实际上,文件系统通过inode
来管理文件的属性和位置信息,而文件名只是与inode
相关联的一个别名。
在Linux
中,我们可以使用-i
选项来查看一个文件的inode
编号,例如
2.3 如何理解目录
我们用户是拿文件名来对文件操作,可是操作系统只认inode
编号来获取一个文件的属性,那么文件名和inode
编号是如何建立关系的呢?
在Linux
中,我们知道一切皆文件。因此,每个目录也有自己属性,即也有自己的inode
。
而目录里也可以包含数据(目录/文件),因此也有自己的数据块。那么目录的数据块里存的是什么呢?
答:对于目录,数据块中存储的是该目录下文件名和该文件对应的inode
编号的映射关系map<string, int>
。
所以文件名只是一个给用户使用的间接inode
编号,用户只要拿到文件名,然后操作系统再去指定目录的数据块去找文件名和inode
编号的映射关系,就可以进行对文件的读写!
总结:文件名和
inode
编号的关系是由目录来建立和维护的。其数据块存储着文件名和inode
编号的映射关系。
2.4 在磁盘上创建一个文件,操作系统做了什么
- 当在
Linux
中创建一个新文件时,系统会拿着你新建的文件名在指定的目录下的数据块查找是否有已经存在的inode
编号。如果有则创建失败。 - 如果在指定路径下没有同名文件,系统会检查当前分组的
inode Bitmap
是否有空闲的inode
位,如果有并将其标记为已使用。然后根据其标记位为新文件分配一个空闲的inode
编号。 - 找到空闲的
inode
后,系统会将新文件的所有属性写入inode Table
中对应的inode
结点当中。 - 此时,文件还没有分配实际的数据块用于存储内容,这就是所谓的"延迟分配"机制。文件系统会延迟分配数据块,直到用户向文件写入数据时才会触发实际的数据块分配。当用户开始向文件写入数据时,文件系统会根据需要分配适当数量的数据块,并将文件内容写入这些数据块中。这样可以更好地利用磁盘空间,避免不必要的空间浪费。
2.5 在磁盘上删除一个文件,操作系统做了什么
-
当要删除一个文件时,操作系统会拿着指定的文件名到指定目录下的数据块中查找
inode
编号。 -
一旦获得了要删除文件的
inode
编号,系统会在inode Bitmap
中将对应的bit
位由1
置成0
,来表示这个inode
已经被删除。 -
将
inode Bitmap
中对应的bit
位由1
置成0
并不会立即从磁盘中删除对应的inode
和数据块,只是表示这个inode
现在是可用的。实际上,文件系统通常会保留已删除文件的inode
和数据块,以便在将来需要时能够重新分配给新创建的文件使用。这也是为了避免频繁地在磁盘上进行分配和释放操作,从而提高性能和减少磁盘碎片化。(在计算机领域中,删除并不是真的都删了,而是覆盖)
因此,我们就可以理解:为什么拷贝文件的时候很慢,而删除文件的时候很快
因为拷贝文件需要先创建文件,然后再对该文件进行写入操作,该过程需要先在inode Bitmap
中将对应的bit
位置成1
,然后根据其标记位为新文件分配一个空闲的inode
编号并填入文件的属性信息,之后还要申请数据块,最后才能进行文件内容的拷贝,而删除文件仅需将对应的inode Bitmap
置为0
即可。
就像建一座高楼大厦,想要完工的时间是非常长,而要拆掉一座楼,仅仅在墙上写上拆
字即可。
2.6 在磁盘上查找一个文件,操作系统做了什么
-
操作系统会拿着指定的文件名到指定目录下的数据块中查找
inode
编号。 -
一旦拿到
inode
编号后,系统就可以通过该编号找到对应的inode
,其中包含了文件的各种属性 -
系统会检查
inode
中的权限属性,确保用户是有权限来执行对应操作的
2.7 在磁盘上修改一个文件,操作系统做了什么
-
查找指定文件: 首先,操作系统会通过文件路径找到指定文件的
inode
编号,获取文件的属性信息,包括文件的权限设置、所有者信息等。 -
权限验证: 在进行文件修改操作之前,系统会检查当前用户的权限是否足够以及是否具有对文件的写入权限。如果用户具有足够的权限,系统将允许用户对文件进行修改;否则,修改操作将被拒绝。
-
分配数据块: 当需要对文件进行修改时,系统会为该文件分配足够的数据块来存储修改后的内容。如果文件已经存在且需要扩展,系统会分配额外的数据块来容纳新的内容。
-
更新
inode
: 在将内容写入到数据块后,系统会更新文件的属性信息,如文件的大小、修改时间等。这样可以确保文件的属性信息与实际内容保持一致。
三、软硬链接
3.1 软链接
软链接是一种特殊的文件类型。我们可以使用以下命令来创建软连接
ln -s [source_file] [link_name]
# ln - 建立链接
# -s - 建立软链接
# 创建名为link_name的硬链接,指向名为source_file的原始文件。
从上我们可以看出,原文件file
和软连接file_sort
的inode
编号是不一样的(各自具有独立的inode
编号),说明软连接是一个独立的文件。
如何理解软连接?
软连接是独立的文件,有独立的inode
和独立的数据块,而软连接的数据块里面保存的是原文件路径,因此软链接类似于Windows
系统中的快捷方式。它们不包含实际的数据,而只是指向原始文件或目录的路径。因此,如果删除快捷方式,即软连接,不会有任何影响;但如果软链接指向的文件或目录被删除了,软链接就会成为死链接。
3.2 硬链接
硬链接同样也是一种特殊的文件类型。我们可以使用以下命令来创建硬连接
ln <source_file> <link_name>
# 创建名为link_name的硬链接,指向名为source_file的原始文件。
当我们查看文件的属性时,发现硬链接和原文件第二列的数字都变成2
了,这个数字其实是硬链接数;并且它们inode
编号还是一模一样的(不具有独立的inode
编号),因此硬链接不是独立的文件。
如何理解硬链接?
源文件和硬链接的inode
一模一样,即文件属性是一模一样的,而在inode
的结构中,存在一个指针指向对应的文件数据块,那么就注定了数据块也是一模一样,即文件的内容也是一模一样!我们可以来验证验证
向原文件写入内容
向硬链接文件写入内容
那现在假设我要将原文件删除,即将inode
编号删除,那么对应的相同硬链接的inode
编号是否也会被删除呢?
答案是不会的!只是将硬链接数由2
变成了1
。
这是因为 inode
结构中维护了一个称为链接计数link_count
的字段。链接计数表示了指向该inode
的硬链接的数量。每当创建一个新的硬链接时,相应文件的链接计数就会增加。当删除一个硬链接时,链接计数就会减少。只有当链接计数减到0
时,文件的数据块才会被释放。
因此所谓的硬链接就相当于将一个文件进行重命名而已。
3.3 为什么要有软硬链接(实际用途)
- 软连接
创建快捷方式。软链接可以让你在不同的位置引用同一个文件或目录,方便快速访问。(最常见)
比如我创建了一个cpp
文件,并将其生成可执行文件myexe
。内容如下:
那么我们可以为myexe
创建一个快捷方式到/home/wj
下(相当于Windows
桌面),方便快速访问。
- 硬链接
当创建一个普通文件时,操作系统会在文件系统中为该文件分配一个inode
,inode
中包含链接计数link count
的字段,因此这个链接计数会初始化为1
,因为此时文件只有一个文件名(inode
编号)指向struct inode
但是当我们创建一个目录的时候,硬链接数却是2
,这是为什么?
别忘了,一个目录就算是空目录,其还有两个隐藏目录,分别是.
和..
。一个.
表示当前路径,表示的也就是dir
目录,两个..
表示上级目录,也就是当前目录dir
的上一个目录。
此时我们将dir
目录文件的inode
编号和.
的inode
编号打印出来,发现两者的inode
编号是一模一样的。
所以可以得出结论:在Linux
系统中,每个目录下都包含一个名为.
的隐藏文件,它表示当前目录本身。这个.
文件实际上就是指向当前目录的硬链接,它和当前目录共享同一个inode
编号。因此,.
文件导致了目录的硬链接数为2
。(两个..
同理)
因此,现在我们就可以解释为什么我们cd ..
可以返回到上级目录了,原因就是:..
和上级目录的inode编号是相同的,本质就是建立了硬链接关系,又因为它们的inode编号相等,使得共享数据块。因此cd ..
等价于cd 当前路径的上级路径名
需要注意的是:目录文件时不允许我们私自建立硬链接的,我们只允许对普通文件建立硬链接,而上述的隐藏文件可以是因为系统本身规定的,而用户不允许私自对目录建立硬链接原因如下:
比方说创建硬链接目录dir
指向的是根目录/
,假设执行find / -name test.c
,当递归搜索到硬链接目录dir
时,由于dir和/的inode编号一样,由于与根目录/
共享相同的inode
,操作系统会认为它们是同一个目录,然后继续搜索/
目录下的子目录和文件,就会发生无限递归问题。
那有的人就有疑问,.
和..
同样也会发生无限递归问题啊。可是很不好意思,系统默认在做搜索时,不会对.
和..
这两个隐藏目录做搜索 ~