序言
在前两章的内容中,我们主要是介绍了一个文件被调度时,在内存中的存在形式以及相关的内核结构。在这一章中,我们主要介绍一磁盘是如何管理并存储文件的以及文件在磁盘上的存在形式。
大部分电脑采用硬盘驱动器(HDD
)或固态硬盘(SSD
)来存储文件信息,在这篇文章中我们主要介绍前者的读写文件的结构。
1. 磁盘物理结构
该磁盘采用磁性存储技术,通过磁头在旋转的磁性硬盘表面上读写数据来存储和检索信息。
一个磁盘有多个磁片,一个磁片通常正反两个盘面都具有存储功能,一个盘面通过第三个结构图我们可以看出有很多圆环,一个圆环就叫做磁道,一个磁道又被分成了很多个扇区,而扇区是硬盘数据存储的基本单位
,一个扇区的大小通常是 512字节
。
但是操作系统认为,一次只与磁盘交互一个扇区的数据量太小,这会导致大量的 I/O
操作,所以在 Linux
操作系统中以数据块为单位进行 I/O
操作,一个数据块 Block
的大小通常是 8 个连续的扇区(4KB
)。
1. 如何定位一个扇区
&esmp; 那如何定位一个扇区在磁盘中的位置呢?我们一般采取 CHS
定址法:
- 先找到特定的磁头(
Header
) - 再找到特定的磁道(
Cylinder
) - 最后找到特定的扇区(
Sector
)
这样说来可能不是那么易懂,那我们举个栗子吧:
现在规定一个盘面包含有 1000
个扇区,一个磁道包含有 100
个扇区,现在我需要知道第 5001
号扇区的物理位置。
- 确定盘面(找到特定的磁头):
5001 / 1000 = 5
,第5
个盘面 - 确定在指定盘面上的第几号扇区:
5001 % 1000 = 1
,第1
个扇区 - 确定磁道:
1 / 100 = 0
,第0
个磁道 - 确定扇区:
1 % 100 = 1
,第1
个扇区
结论:该扇区位于第 5
个盘面的 第 0
个 磁道的 第 1
个扇区。
2. 对磁盘的逻辑抽象结构
1. 逻辑结构
我们在上面提到了 Linux
操作系统中以数据块 Block
为单位进行 I/O
操作,所以我们可以抽象地理解为我们的磁盘也被分成了一个一个以 Block
为单位的数据块:
LBA(Logical Block Address,逻辑块寻址)
就是每一个块的起始地址。在每一个块上的读写操作,通过对 LBA
使用 CHS
定址法就可以映射到磁盘上。
现在我们对文件的读写就变成了对该逻辑结构的读写,这对任何磁盘硬件都是通用的,使我们无需关心硬件的具体实现细节,简化操作,抽象层隔离。
2. 空间管理的分治思想
直接管理一块 800GB
大小的硬盘是比较困难的,所以我们需要对我们的磁盘进行分区:
注意:每个区域的大小可以不同,并且可以有不同的管理方式,如:C盘和其他盘的管理。
而一个分区操作系统又分成若干个组:
现在我们关注于对一个组的管理,对该组的管理可以适用于其他任何组。
思考一下,就拿一个小学举例子吧:一个小学的校长很难光凭自己管住所有的学生,但是我们分了年级的呀,一个年级又分成了若干个班级,校长想知道一个学生的信息就问年级主任,年级主任问班主任,这不就有了吗!
3. 一个组的结构
在进入本节内容之前,先问大家一个问题:一个文件 = 文件的内容吗
?如果我创建一个文件却没有向他里面写入任何的内容,那么文件的大小就是 0 吗?
答案肯定不是的,文件 = 文件的内容 + 文件的属性
这个概念很重要!!!
下图就是一个分组的详细结构:
在这里我们将逐个介绍每一块的内容:
1. 数据块 Data blocks
数据块主要是存储文件的内容
,每一个块的大小为 4KB
(八个连续的扇区),并且每一个块都有一个独立的块号。
2. 块位图 Block Bitmap
&esmp;这是一个位图,记录在数据块中哪些块被使用,哪些块是闲置的。大家可能会感到疑惑,是如何实现这个位图的呢?现在假设该块位图只是占了一个块,那一共就有:4 * 1024 * 8 = 32768
位,我们在前面说到了,在数据区中每一个块都有一个独立的块号,如果现在第 100 号块被使用了,那么在快位图中的第 100 位就置 1。
3. i 节点表 inode Table
存放文件的属性,如文件的大小,所拥有者权限,创建时间,修改时间等。每个 inode
通常占用 128字节
,咦?所有文件的都一样吗?那比如一个 1KB
的文件和一个 1GB
的文件没什么差别吗?是的大小都是一样的。因为他们都具有相同的文件属性,只是每个属性对应的值可能不同而已。
该表有两个属性特别重要,我们单拎出来说:
数据块位置
:记录文件数据所在的block
数据块的编号。文件内容存储在block
中,inode
中记录了这些block
的编号,以便快速找到文件内容。inode_number
,系统可以快速定位和访问文件或目录的inode
信息,进而获取文件的元数据和内容。每个分区的inode_number
是独立的,换句话来说,一个分区的inode_number
绝不可能重复,但是不同区之间的inode_number
可能会重复。
这里提出两个疑问:
- 文件的属性怎么不包括
文件名
? - 系统根据
inode_number
定位一个文件的位置,这个东西和文件名
的关系?
4. i 结点位图 inode Bitmap
这个的功能和块位图一致,查询记录在 inode Table
中哪些块被使用,哪些块是闲置的。
5. 块组描述符 Group Descriptor Table
&esmp;这个主要是描述本组的情况,比如,本组一共的大小,少用了多少空间,还剩余多少空间等等。
6. 超级块 Super Block
描述一个分区的情况,记录的信息主要有:bolck 和 inode的总量, 未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的 时间,最近一次检验磁盘的时间等其他文件系统的相关信息。
我们之前说过,不同的分区可以使用不同的管理方式,所以每一个分区的 Super Block
就是描述该分区的文件系统结构,所以一个分区的 Super block
如果受损了,那么该分区就废掉了。为了避免这种情况,会在该分区的分组中间隔的存储 Super Block
,那为什么不每个组都存一个呢?这样又方便又简单。系统也要考虑效率,每个组都存一个太浪费空间了,也不必要。
3. 特殊的文件 — 目录
在大家眼中,目录算得上一个文件吗,如果是文件的话,那目录又存储些什么呢?首先,目录肯定是一个文件,我们前面说过,文件 = 文件的属性 + 文件的内容
。我们从这个角度为切入点,来谈谈目录。
1. 目录的属性
&esmp;在 Linux
中,我们可以通过指令 stat File
来查看一个文件的详细属性,如:
现在我们也对一个目录使用该指令:
可以看到属性是一致的。所以目录也具有文件的属性,换句话说,目录就是文件。那目录存放的数据是什么呢?
2. 目录的内容
&esmp;我们直到,系统提供 inode_number
来寻找该文件在磁盘中的位置,而我们用户使用 文件名
来使用一个文件。所以,目录存储的是 文件名和 inode_number 的链接
!使用指令 ls -i
:
可以看到每一个 文件名
都对应一个 inode_number
。
所以我们获取一个文件内容的步骤是:
- 目录根据文件名,得出该文件的
inode_number
- 系统根据文件所在的分区,查找
inode_number
对应的inode_Table
- 再根据
inode_Table
获取文件内容所在数据块中的位置
现在大家可以解释我们的之前的疑问了吗?
4. 再次理解文件的增删
1. 创建一个文件
创建一个文件:
- 查找该分区中哪个分组有合适的空间
- 查找该组中的
inode Bitmap
,寻找空闲位置 - 将你的属性存入
inode Table
,将你的数据存入数据块 - 将你的
文件名
和inode_number
建立映射关系存入文件
2. 删除一个文件
删除一个文件,原来如此简单:
- 根据
文件名
找到inode_number
- 根据
inode_number
找到对应的inode Table
- 将该
inode Table
对应的位图置0
,再将数据块对应的位图置0
删除一个文件不需要将该文件对应的属性和数据真正的格式化,只需要将占有的标志位从 1
变为 0
即可,代表这块空间是空闲的,可以被使用!
5. 总结
&esmp;这篇文章主要介绍了文件在磁盘中的存储方式,希望大家有所收获😊!