前言:前面主要从进程的角度理解文件操作,这篇接着往下看,认识磁盘怎样保存和读写数据。
1.磁盘怎样读写数据
文件可以看作内容加属性。内容是我们写进去的文字、代码等数据,属性则包括文件大小、权限、所属用户等信息,它们都需要保存。
磁盘提供存放这些数据的空间,文件系统负责把它们组织起来,让我们能通过文件名和路径找到需要的文件。这里先从机械硬盘的结构看起。
1.1盘片、磁头、磁道与扇区
先看机械硬盘的实物结构:

中间的圆盘是盘片,主轴带动盘片旋转,旁边的磁头负责读写数据,磁头臂则带着磁头移动到不同位置。数据通过盘面上的磁性状态保存下来。
一块硬盘可以有多个盘片,盘片也可以使用上下两个盘面存储数据,每个使用中的盘面有对应的磁头。
为了描述数据的位置,还需要认识磁道和扇区:

- 磁道:盘面上一圈圈同心圆形的轨迹。
- 扇区:磁道进一步划分出的一段段存储区域。
绿绿的那个标记的是一个扇区。可以先找到数据在哪一圈,再找到它在这一圈的哪一段。
当然随着技术的发展肯定是会发生变换的,但是这里后面按一个扇区 512 字节来举例。
再把多个盘面放到一起看:

不同盘面上,半径相同的磁道合在一起,就构成了一个柱面。右图中上下对应的这些圆圈,看起来像一个圆柱的侧面,因此叫柱面。
1.2读写时磁盘怎样移动
比如想要读写一个扇区,磁头需要先到达相应的位置:

这个过程可以分成几个动作来理解:
- 找到磁道。 磁头臂移动,让磁头到达目标磁道,这个过程叫寻道;同时选择要访问的盘面所对应的磁头。
- 等待扇区。 盘片继续旋转,等待目标扇区转到磁头下方,这段等待就是旋转延迟。
- 读写数据。 位置合适后,磁头读取数据,或者把新数据写入相应区域。
图中的多个磁头由同组传动臂带着一起移动,也就是"共进退"。移动到某个位置后,各个磁头便对应同一柱面中的不同盘面。
机械硬盘的访问时间中,就包含了这些移动和等待。连续读写相邻的数据,通常可以减少反复寻道,所以比来回访问分散的位置更有利,这点的原因就像是我们之前的缓冲区存在的意义一样。
2.从 CHS 到 LBA
现在已经认识了柱面、磁头和扇区,只要给出这三个位置,就可以在传统磁盘模型中找到一个扇区。这种表示方法叫作 CHS。
| 字母 | 含义 | 解决的问题 |
|---|---|---|
| C | Cylinder,柱面 | 位于哪一个柱面 |
| H | Head,磁头 | 访问哪个盘面上的磁道 |
| S | Sector,扇区 | 位于这条磁道的哪一个扇区 |
也就是:确定柱面,再确定盘面,最后确定扇区。
2.1把扇区排成一条线
每次描述位置都用三个数字,理解起来比较直观。不过,对于使用磁盘的系统来说,还可以把这些扇区统一编号,用一个数字表示位置。
可以先借助磁带来理解:

磁带卷起来时是一圈圈的,拉开后就成了一条长带。磁盘的盘面虽然不会真的被拉直,但我们可以在理解时,把磁道里的扇区按顺序排开。
先看一个柱面:把不同盘面上的磁道分别展开,一行就是一条磁道,行里面的每一格就是一个扇区。

条磁道包含相同数量的扇区。一个柱面展开后,像一张二维表;整个磁盘又有多个柱面,就像有多张这样的表。
把这些表的每一行依次接起来,就可以得到一条连续的扇区序列:

从 0 开始给它们编号,每个扇区就有了自己的线性地址,这就是 LBA(Logical Block Address,逻辑块地址)。
可以把它理解成一个数组:数组元素是一个个逻辑扇区,下标就是 LBA。 图中标红的扇区,既可以用"哪个柱面、哪个盘面、哪个扇区"来描述,也可以直接用它在线性序列中的编号来描述。
2.2CHS 与 LBA 怎样换算
换算的思路和二维数组、三维数组计算元素位置很像:先数前面有多少完整的柱面,再数当前柱面中前面的磁道,最后数磁道内的位置,计算方式和位图的方式一样,但是因为哲理是三位所以会更复杂一些。
先约定几个字母:
| 符号 | 含义 |
|---|---|
H |
总磁头数,也就是每个柱面中对应的磁道数量 |
T |
每条磁道的扇区数量 |
c |
当前柱面编号,从 0 开始 |
h |
当前磁头编号,从 0 开始 |
s |
当前磁道内的扇区编号,从 1 开始 |
L |
LBA 编号,从 0 开始 |
一个柱面包含 H × T 个扇区,所以公式是:
L = c × H × T + h × T + (s - 1)
其中三部分分别表示:
c × H × T:前面所有柱面占了多少个扇区。h × T:当前柱面中,前面的磁道占了多少个扇区。s - 1:当前磁道内,目标扇区前面还有多少个扇区。
最后减 1,是因为这里的 CHS 扇区编号从 1 开始,而 LBA 从 0 开始。
例如,按上面的线性图取 4 个磁头、每条磁道 4 个扇区。要找柱面 1、磁头 1、扇区 2,对应的 LBA 就是:
L = 1 × 4 × 4 + 1 × 4 + (2 - 1) = 21
反过来,知道 LBA,也可以通过除法和取余找回 CHS:
| 要求的编号 | 计算方式 |
|---|---|
柱面 c |
L ÷ (H × T),取整数部分 |
磁头 h |
先求 L % (H × T),再除以 T,取整数部分 |
扇区 s |
L % T + 1 |
这样做就可以实现硬件与软件之间的解耦。
3.文件系统管理方式
LBA 让我们能找到某个扇区,但文件可能很大,也有自己的属性和名字。要把这些数据安排好,还需要继续认识块、分区和 inode。
3.1从扇区到块
文件系统组织和分配空间时,通常以**块(block)**为单位。一个文件系统块可以包含多个连续的扇区。
用前面一个扇区 512 字节的例子,如果块大小取 4KB,也就是 4096 字节,那么一个块就包含:
4096 ÷ 512 = 8 个扇区

先把扇区连续编号,再每 8 个分成一组,这一组就是一个块。这样,组织文件空间时,就可以按块来安排。
如果把这段空间的起点看作 0,那么:
- 扇区编号除以 8,取整数部分,就能知道它属于哪个块。
- 块号乘以 8,就是这个块的起始扇区编号。
- 再加上块内的偏移 0~7,就能找到块中的某个扇区。
例如,块 3 对应这一段空间中的 24~31 号扇区。换成整块磁盘上的地址时,还要加上这段空间本身的起始扇区号。
3.2把磁盘划分成分区
一块磁盘的空间可以划分为多个分区,每个分区占据其中的一段逻辑范围。
例如,把一块磁盘的容量按 300GB、300GB、200GB 分成三部分,就是一种分区安排。磁盘还是同一块,只是这些空间被分别管理了。

只要记录各自从哪里开始、占多大范围,就能确定这些分区在磁盘上的位置。
分区和格式化还需要区分一下:
| 操作 | 做什么 |
|---|---|
| 分区 | 在磁盘上划出不同的空间范围 |
| 格式化 | 在选定的空间中建立文件系统所需的管理结构 |
可以理解成先划出几块场地,再在每块场地里安排东西怎样存放、怎样登记。划出范围后,还需要文件系统继续组织其中的文件。
3.3用 inode 保存文件信息
文件内容可以放进数据块,但权限、大小、所属用户等属性,又放在哪里?

我们平时使用 ls -l,看到的是文件的权限、所有者、大小、修改时间等信息。程序要获得这些信息,并不需要先把文件正文全部读出来。
它可以记录这个文件的相关属性:
| 信息 | 作用 |
|---|---|
| 文件类型和权限 | 说明它是什么类型,允许怎样访问 |
| 所有者和所属组 | 说明文件属于谁 |
| 文件大小和相关时间 | 记录内容大小、访问与修改等信息 |
| 数据位置信息 | 用来找到保存文件内容的数据块 |
文件有自己的 inode,inode 也有对应的编号,叫作 inode 号。在同一个文件系统中,可以通过这个编号区分不同的 inode。
我机器上的例子:

ls -li 比平时的详细列表多了最左边这一列,框出的数字就是 inode 号。
文件名没有保存在 inode 中。 关于这点我放在下一篇文章里再讨论吧,我也不想把一篇文章搞得太长。
其实可以在内核中看到 inode的定义结构体:

磁盘提供存储空间,文件系统把这些空间组织成可以保存、查找和使用的文件。 关于系统如何组织文件系统这点下一篇文章里再做讨论先这样吧。
完