humid1ch blogs

本篇文章

手机用户建议
PC模式 或 横屏
阅读


Linux系统 2023 年 3 月 29 日

[Linux] 详析 Linux磁盘文件管理系统、文件inode以及 软硬连接

本篇文章的主要内容就是介绍Linux的文件系统是怎么管理磁盘文件
Linux的文件操作, 都是从内存文件进行操作, 即都是对打开的文件进行操作的.
但是, 我们的操作系统中并不是只有内存文件的, 甚至可以说 内存文件只是操作系统中所有文件的一小部分, 绝大部分的文件都是处于未打开的状态的. 这些文件一般都静静的在磁盘中存储着, 所以也被称为**磁盘文件**
本篇文章的主要内容就是介绍Linux的文件系统是怎么管理磁盘文件的.

磁盘相关信息

在介绍 Linux的文件系统之前, 至少要先了解一下磁盘是什么样的、大概有什么结构?不然怎么进一步理解 文件系统呢?

磁盘的物理结构

这就是一个磁盘, 上面的圆盘叫做磁盘盘片, 悬在盘片上的像针一样的东西叫磁头, 磁盘中间的部分叫主轴, 磁头链接着磁头臂, 磁头臂被一个传动轴连接着:
其中, 主轴下方是轴承和马达, 可以带动盘片旋转, 传动轴则可以让磁头臂左右摆动, 即 磁盘内部的机械结构是类似这样运动的:
磁盘机械运动
磁盘机械运动
并且, 一个磁盘中可能有上下排列有许多的盘片, 并且每个盘片上下都有6一个磁头, 类似这样:

磁盘的存储结构

上面大致的介绍了一下磁盘的物理结构, 知道了磁盘的机械结构可能由多个盘片和多个磁头臂+磁头组成
这两个结构可以说是磁盘最重要的结构之二.
那么数据在磁盘的什么地方存储呢?
其实**数据都在盘片上存储着, 而磁头负责从盘片上读取或向盘片上写入数据**
  1. 盘片是怎么存储数据的?

    其实光滑的盘片上可以看作有无数个同心圆, 图片表示就类似这样:

    不过这些圆在盘片上被称为磁道, 而每个磁道又会被分为许多的的扇区

    ==这只是抽象图, 并不确切表示盘片中磁道和扇区的数量==

    数据就在这些磁道中以二进制的形式存储着

    并且, 每条磁道都有自己的编号, 最外层磁道的编号为0; 每条磁道上的每块扇区也有自己的编号, 每块扇区的大小一般为512字节

    每条磁道的扇区数是相同的, 每块扇区的大小也是相同的, 这也就意味着每条磁道的可存储数据的大小其实也是相同的

    很明显越外层的磁道越长, 长磁道与短磁道可存储数据的大小也是相同的吗?是的, 每条磁道存储数据的密度不相同

  2. 磁盘是通过什么来读取或写入数据的呢?

    既然数据在磁道中存储着, 那么就肯定要从磁道中读取数据 或 向磁道中写入数据

    那么磁盘是通过什么来进行此操作的呢?

    没错, 就是通过磁头

    盘片的旋转结合磁头的左右摆动, 可以使磁头悬浮于盘片的任意位置, 也就是说磁头可以通过一定的操作在盘片的任意位置读取数据或写入数据

    盘片在高速旋转时不能接触到任何物体, 不然盘片就会发生损伤

    所以磁头一般是悬浮在盘片上下的

    磁头通过感应盘片上的磁场变化来读取数据, 通过改变盘片上的磁场来写入数据

    (磁场分为NS, 正好对应二进制的01)

    ==磁盘中可能存在多个水平但上下放置的盘片, 那么这些盘片必定会存在相同半径的磁道同处于同一个圆柱面上, 这个圆柱面我们就称之为磁柱(Cylinder):==

    磁柱是Cylinder, 磁头是Head, 扇区是Sector, 当我们知道这三个结构的编号, 就能在磁盘中定位到一个指定的扇区

    知道磁柱的编号就确定了磁道的编号, 磁头可以确定哪个盘面, 扇区编号可以知道扇区在磁道中的位置, 知道这三个编号, 就能确定到某个盘片上的某条磁道上的某块扇区, 也就知道了当前访问的扇区

    这样的地址被称为**CHS地址**, 用这样的方法, 可以找到磁盘中每个单元的确切位置

  3. 磁盘存储结构的逻辑抽象

    经过上面介绍了磁盘的物理结构 和 存储结构之后, 其实数据就是存储在一个一个圆圈上的. 也可以看作是线性存储的

    毕竟圆圈是可以拉直的

    就像磁带一样, 卷在一起的时候可以看作是数据存储在一个一个圆圈上, 当把磁带拉直也可以看作数据是存储在一条直线上. 不管是卷起来还是拉直, 其实都没有损坏磁带, 更没有损坏磁带上的数据.

    那么其实, 磁盘上的盘片上的磁带也可以抽象成这样的直线的、线性的形式, 作为一个数组被管理起来

    即, 磁盘上的所有盘片的所有磁道都可以抽象成一个线性的数组然后整合起来:

    所有的扇区被整合成一个数组, 每个下标对应着一个扇区, 即可以根据数组的下标来定位到磁盘中的某个扇区, 这里的下标被叫做LBA逻辑块地址

    通过LBA是可以计算出相对应的CHS的, 比如:

    已知磁盘上每个盘片上有100条磁道, 每条磁道20个扇区, 当我们获得一个LBA是9231. 我们就可以根据9231来计算出对应的CHS:

    9231表示第9231个扇区, 每个盘片100个磁道, 每个磁道20个扇区, 即每个盘片2000个扇区:

    9231/2000 -> 第5个面盘上(Head), 9231%2000 = 第1231个扇区, 1231/20 -> 第62条磁道上(C), 1231%20 = 11个扇区(S)

    即, LBA=9231 对应的CHS为 62:5:11

文件系统

我们了解了磁盘的物理存储结构, 并理解了可以将磁盘的存储结构抽象成一个数组来进行管理
对于抽象出来的数组, 每个单元对应着一个扇区, 但是一般来说操作系统与磁盘进行I/O操作的基本单位是4KB. 即, 实际上对操作系统来说, 还可以将抽象出来的一个单元对应一个扇区的数组, 进一步划分一下, 以8个单元为一个大的单元, 即 8*512B = 4KB, 以便于操作系统与磁盘实际进行I/O操作

为什么不将操作系统与磁盘的I/O操作的基本单位 直接设置为一个扇区, 而是固定设置成4KB呢?

  1. 对大多数的磁盘来说, 一个扇区一个扇区的IO太慢了, 设置成4KB是为了提高IO效率

  2. 不让软件设计和硬件具有强相关性, 在此, 就是不让操作系统和磁盘之间在IO方面具有强相关性

因为, 如果操作系统与磁盘的IO单位是按照当前磁盘设计的话, 那么针对不同型号、扇区大小不同的磁盘时, 操作系统就需要做出相应的修改, 这个过程是复杂且繁琐的. 而如果将操作系统与硬件的IO基本单位设置成一个固定的值, 就不需要根据硬件去作出修改. 这也就是软件设计中常说的, 解耦合

不过, 即使将磁盘的存储结构抽象成了数组, 如果操作系统直接对整个数组进行管理, 你会发现也是非常困难的.
就拿一个最普通的512GB的磁盘来说, 每个扇区的大小是512字节, 那么若数组的每个单元表示一个扇区, 那么整个数组需要多大?再以8个扇区为一个单元的数组又有多大?
分别是1,073,741,824134,217,728, 即使后者比前者小了一个数量级, 但依旧是千万级的
让操作系统直接一下子管理着千万级的数组, 会不会太离谱了?即使操作系统可以管理, 但是直接管理千万级的数组, 成本是否太大了?
所以, 操作系统是不会直接对整个磁盘的数组进行管理的, 而是会针对磁盘的不同分区, 将分区分为不同的组, 再针对组进行管理.
对细分出来的组的管理 相比 直接对整个磁盘抽象出来的数组的管理, 一定是简单的不少的!并且, 对于像组, 这种属性基本相同的结构来说, 管理好一个组, 就可以管理好其他所有的组
即:

操作系统会将分区细分为向上面那样的组, 还会分出一个 Boot Block区域.

Boot Block

对分区进行分组之后, 每个组内都会某些相同作用的块或属性:Super Block Group Descriptor Table Block Bitmap inode Bitmap indoe Table. 最后的这个 Data blocks 则表示文件内容
那么这些组的属性是什么意思?有什么作用呢?

组的属性

我们知道 文件 = 属性 + 内容. 那有没有想过一个问题, 文件的属性和内容, 在Linux系统中是存储在一起, 以相同的形式一起管理的吗?
其实不是的, 在Linux操作系统中, 文件的属性和内容是分开存储、以不同的形式分开管理的.
Linux中的文件系统, 会将分区在细分为组, 不同的组内存储一些相同结构的属性, 这些属性描述着组的内容:

Data blocks

Data blocks, 其实就是组内存储文件内容的一个区域, 此区域以 块 为单位, 每块的大小为4KB. 占一个组的绝大部分内容
为什么是单位是4KB? 因为 操作系统与硬件的I/O操作的基本单位就是4KB, 4KB便于操作系统I/O. 其实也就是说明, 文件在操作系统中存储就是以4KB为单位的, 即使文件内容并没有达到4KB的内容, 此文件还是会占用一个4KB的块, 此块独属于此文件, 其他文件不能使用
在文件内容增长的过程中, 文件也会以4KB为单位增大
即:Data blocks 示意图

inode Table

看到inode Table, 其实可以很自然从Table的联想到它应该是一个数组.
但是inode是什么呢?
Linux系统中, 存在一个 inode结构体, 此结构体存储的是磁盘文件的各种属性.
我们使用ll 或者 stat 可以查看多个或单个文件的属性:
我们使用 stat 查看单个文件的详细属性时, 可以看到 一个文件拥有非常多的属性:
File Size Blocks IO Block Device Inode Links Access Uid Gid
这其中有我们很熟悉的file 文件名, 还有Access 权限
但是还有许多不认识的:Blocks IO Block Inode Links
这些属性, 其实都存储在 Linux系统的inode结构体中:
这还只是一部分, 不过我们暂时只需要知道, 在Linux系统中, inode是一个结构体, 存储着文件的所有属性 就可以了
在 inode结构体中, 存储着一个叫 inode的属性, 是一个整型值, 此值表示某文件在操作系统中的唯一的一个编号
就是 stat 命令输出的一个叫 Inode 的属性, 也可以通过 ll -i 查看:
知道了inode具体是什么东西, 那么再看inode Table
其实 inode Table 就是组内用来存储 文件inode结构体的一个表格、数组, inode Table的每个单位存储一个inode结构体

inode结构体的大小为 128字节, 那么 inode Table 的单位大小就是 128字节

即:inode Table结构示意

inode结构体存储着 操作系统层面 文件的所有属性, 但是其中是没有存储文件名的

文件名在Linux操作系统中是属性吗?是的, 但是inode中并没有存储文件名

这其实意味着, 在Linux底层层面, 系统是不认识文件名的, inode结构体中存储的inode编号 是操作系统底层对文件的唯一实际标识

inode Bitmap

看到Bitmap, 其实就能想到是位图
组中 inode Table, 是以数组的形式存储文件的inode的, 并且是每个单元只能存储一个inode结构体
这也就是说, inode Table内部的单元格只存在是、否存储的文件的inode, 两个状态
那么就可以通过一个位图来描述 inode Table 的占用情况, 方便操作系统对此组进行管理
即, inode Bitmap 就是描述 inode Table 占用情况的位图

Block Bitmap

与 inode Bitmap类似, Block Bitmap是用来描述 Data Blocks的占用情况的

Group Descriptor Table

这一部分是描述组中的总览信息的:inode起始编号 inode Table被占用了多少、还剩多少 Data Blocks 中占用了多少、还剩多少 此Group的实际大小是多少 ……
其中有部分信息是可以通过组内的属性计算出来的, 但是总要消耗时间成本, 所以还是直接记录统计存储起来
此部分内容可简称 GDT

Super Block

这以部分内容被称为 Super Block 超级块, 其实并不独属于其所在组
因为此块记录的内容大致是整个分区的相关属性:此分区在磁盘中的实际区域 分区内的分组情况 分区所使用的文件系统格式 ……
此块其实是 操作系统中 文件系统顶层的一个数据结构了

为什么 Super Block 要存储在组内?

既然, Super Block是一个属于此分区内的数据结构, 那么为什么不像Boot Block一样, 单独存储在组外分区内呢?
其实原因只有一个, 那就是为了提供备份
Super Block是一个描述了分区最重要的属性的数据结构, 并且是需要实时维护的.
当操作系统向某个分区的某个组内写入数据时, Super Block也是处于一个维护状态的. 那么如果此时 写入过程和维护过程意外中断了(比如台式机突然断电). 有没有可能正在维护的Super Block会发生损坏?
如果一个分区只有一个Super Block, 并且已经损坏了, 那么此分区还能使用吗?如果此分区刚好是系统分区, 那系统是不是也就完蛋了
所以 Super Block 需要在一个分区内备份许多份, 以防止单独存储出现损坏无法恢复的情况.

就像Windows中, 如果操作系统正在写入数据的时候突然关机断电, 那么在下次开启的时候很有可能会触发一个询问:

上次系统非正常关机, 怎么怎么样, 是否恢复启动?

如果分区中只有一个Super Block这样的结构, 那么是无法恢复的

并不是 所有组内都会存储Super Block, 而是一部分

inode 怎么与 其描述文件的Data Blocks部分 联系起来

Data Blocks 中存储的是文件内容, 是以4KB/块为单位的
inode结构体描述着文件在系统底层的所有属性, 其中一定描述的有指定文件内容在Data Blocks中的存储位置
inode中存储有一个类似block[15]这样的数组结构, 其中:
  1. 一部分存储文件内容在 Data Blocks中的存储位置, 比如 [0, 11] 这些位置直接保存文件内容对应的Data Blocks对应的编号

  2. 如果文件内容很大, 一部分描述不完时, 还会有另一部分内容. [12, 14] 这部分内容会保存某块 Data Block

    但是这块 Data Block中保存的并不是文件内容, 而是像 [0, 11] 那样 保存此文件内容在Data Blocks中对应的其他编号

    这是一种二级映射

创建和删除文件 的执行流程

创建和删除文件时, 操作系统大概会做什么事情呢?
在分析这个问题之前, 还需要再介绍一下目录的相关内容

文件系统中的 目录

我们知道, 在Linux系统中 目录也是文件, 而 文件 = 属性 + 内容
目录的属性我们都知道是什么, 都存储在其inode中, 那么 目录文件的内容是什么呢?
我们访问目录、在目录下创建文件、查看目录下的都有什么文件, 都需要什么权限?
访问目录进入目录, 我们需要的是 x执行权限
在目录下创建文件, 我们需要的是 w写权限
查看目录下的文件, 我们需要的是 r读权限:
文件的w和r权限实际上是对 文件内容的读写权限, 也就是说 目录文件的文件内容 其实就是目录下的文件
准确一点, 应该说 目录文件的文件内容, 其实是 目录下文件的 文件名 和 inode编号 之间的映射
即, 目录文件在 Data Blocks 中, 存储的内容是 其下文件的文件名与inode编号之间的映射关系

同一目录下, 是否可以存在多个相同的文件名?

不可以, 同一目录下的文件名是唯一的, 也就是说, 同一目录下一个文件名只对应一个inode编号

创建文件, 操作系统做了什么?

介绍了这么多 Linux关于inode 的相关信息, 那么创建文件时 操作系统都会做什么, 才能正确的创建文件供用户访问呢?
操作系统大致的操作流程应该是:
  1. 创建文件的inode, 并将inode和内容分别存储至 组的inode Table 和 Data Blocks中, 并修改inode Bitmap 和 Block Bitmap 的相应内容
  2. 找到当前用户所处的目录, 再根据当前目录的inode找到目录文件在 Data Blocks中的数据块
  3. 将 创建的文件的文件名和inode编号, 存储到目录文件在Data Blocks的数据块中, 并修改 相关的 Block Bitmap
这样, 我们就可以在指定的目录下找到指定的文件

*删除文件, 操作系统又做了什么?

Linux操作系统删除文件会怎么做呢?
一般情况下, 我们对删除文件的第一认知就是 将文件的inode和内容 都在inode Table 和 Data Blocks中删除, 然后将 inode Bitmap 和 Block Bitmap 对应位置修改为0, 并将存储在目录文件的内容数据块中的映射关系删除. 做完这些, 就是删除文件
但实际上, 操作系统并不是这样做的.
操作系统删除文件, 并不会真的将文件 存储在Data Blocks的内容 和 inode Table的属性删除, 而是只将 inode Bitmap 和 Block Bitmap 这两个位图中有关删除文件的位置设置为0. 再将目录文件的 Block Bitmap 的相关位置设置为0. 就完成了文件的删除
因为 inode Bitmap 和 Block Bitmap 这两个位图, 描述的是磁盘中实际的存储块的使用情况. 如果这两个位图中相关的位置为0, 那么在操作系统看来, 相应的 inode Table 和 Data Blocks 存储块中就没有存放有效数据, 相应的位置都是可以直接使用的
那么也就是说, 操作系统对文件的删除操作, 从效果来看完成了删除, 因为操作系统可以再次使用那些空间. 但从物理空间上来看, 那些空间内实际上还是存储有相关的文件数据的.
所以, 当删除了一个文件之后, 只要没有再次占用那些空间, 文件是可以被恢复的. 只要知道 文件的inode, 理论上是可以恢复的.
但是, 删除文件之后, 操作系统就可以随意使用那些空间了, 所以实际上也是很有可能无法恢复的

操作系统删除文件, 并不会真的将数据从磁盘中清除.

那么当一个公司的服务器需要更换磁盘时, 一般会对磁盘中的数据进行擦除. (以往可能会使用物理损毁)

软硬连接

我们了解了Linux文件系统的相关信息之后, 我们知道了, 在操作系统的底层, 是不存在文件名这个概念的.
操作系统底层实际上不是通过文件名来找到 文件的属性和内容的, 而是通过inode编号
所以, 其实inode编号才是操作系统底层的标识符.
那么有没有一种可能, 在磁盘中 存在多个文件名映射了同一个inode编号呢?

硬连接

目录中的文件名 和 inode编号, 以一种映射关系存储在指定的数据块中.
操作系统底层只认识inode编号, 不认识文件名. 所以, 无论是访问文件还是读写文件, 最终其实都是通过inode编号来实现的.
也就是说, 只要文件名可以映射到一个inode编号, 那么就可以通过此文件名访问到指定的文件. 即使存在多个文件名映射到同一个inode编号也是可以的.
硬连接, 就可以将不同的文件名, 映射到同一个inode编号上
创建硬连接的命令是:ln 原文件名 新创建的硬链接文件名
可以看到, 不同的文件名映射的inode是相同的. 也就是说, 这两个就是同一个文件, 只是文件名不同, 操作系统没有在磁盘中再创建一个内容相同的文件, 并不会多占用一份磁盘空间. 其实就只是在目录的Data Blocks数据块中, 添加的了一个映射关系, 并没有其他的数据,
在执行了 ln 之后, 此文件inode编号除了被两个不同的文件名映射之外, 还存在另外一个变化:
表示权限一栏的后的数字, 从 1 变为了 2
这是什么意思?
其实, 这个数字是inode结构体记录的此文件的硬连接数, 只要磁盘中存在一个硬连接到此inode的文件, 这个计数就会+1
这样看来, 其实文件在操作系统中只看做是一个 inode结构体 + 文件内容 就可以了, 文件名什么的只是一个供用户查看、记忆的没有什么重要的实际意义的马甲, 而文件名映射的inode编号才是那个最重要的指向一个实际文件的数据. 也就是说, 其实 inode编号 就像是一个 “指针”
那么, 这个硬连接数有什么用呢?
硬连接数, 其实就是一个计数的作用, 只要硬连接数不为0, 就表示此inode所指向的文件 就还在某个地方被映射着甚至使用着呢
相应的, 只要此文件的硬连接数为0了, 那么就表示此文件在操作系统中已经可以不再存在了, 就可以删除了.
也就是说, 只有一个文件的硬连接数为0时, 磁盘中的文件才会被删除. 不为0, 顶多算是解除了某个文件名对其的映射

硬连接有什么用?

要回答这个问题, 先创建一个目录文件和普通文件:
为什么这样呢?
普通文件被创建出来之后, 就只有一个文件名映射到实际文件对应的inode
而目录文件不同:
这就是为什么, 当我们执行当前目录下的可执行文件的时候, 需要使用./作为前缀

软连接

软连接对比硬连接来说, 就基本没有需要注意的地方.
建立软连接的命令也是 ln, 只不过 需要添加一个选项 -s, 可以看做soft , 即 ln -s 表示建立软连接:
与 硬连接不同的是, 软连接是生成了一个新的文件, 因为映射的inode不同与原文件.
不过, 软连接没有什么其他需要注意的点, 可以把Linux中的软连接看作是Windows中的快捷方式
软连接创建的新文件的内容, 其实是所连接的原文件的所在路径

软硬连接都可以用, unlink 命令来取消连接

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)

作者: 哈米d1ch 发表日期:2023 年 3 月 29 日