引言

本文主要讲解操作系统管理磁盘的原理,概念等。请先回答如下问题,带着问题阅读文章效果更佳。

  1. 操作系统是如何管理磁盘的?
  2. 如何能提高磁盘的访问速度?
  3. 当多个线程同时请求读取操作系统是如何调度?
  4. 文件系统是如何组织的?

以下是笔者自己的博客,持续分享java技术栈相关的知识。博客链接:

https://www.yuque.com/objectn/gx4owr

认识磁盘

读写磁盘

读写磁盘的基本过程,首先移动磁头移动到磁道上,然后转动磁道到对应的扇区上。 可简化为:控制器->寻道->旋转->传输。读写磁盘需要的数据:柱面( C )、磁头( H )、扇区( S )、缓存(内存位置)。把上述这几个值写到磁盘寄存器上。

为了使用户更方便的使用磁盘和提高磁盘读写效率做了以下抽象:盘块号,

通过盘块号读写磁盘

用户直接使用上面的几个参数比较麻烦。对此进行抽象,通过一个盘块号(block)去找到对应的位置。操作系统通过盘块号加工出柱面、磁头、扇区,进而找到对应的位置。

提高读写速度(盘块的抽象)

先来看一下磁盘的访问时间构成:

磁盘访问时间= 写入控制器时间+ 寻道时间+ 旋转时间+ 传输时间。其中最耗时的就是寻道时间。就是磁头到找到对应扇区的时间。因此连续的内容尽量要写在同一个磁道上。

通过一次多读取几个扇区来提高读写效率,连续的几个扇区也叫盘块。但是多读取几个扇区也有空间上的浪费。浪费的原因:最小分配单位是“盘块”,不是“字节”:操作系统为了管理方便,不会按字节给文件分配磁盘空间,而是以“盘块”为单位。一个盘块通常由多个连续扇区组成(例如 4KB = 8 个 512 字节扇区)。即使一个文件只有 1 字节,它也必须占用整整一个盘块。

下面是空间利用率和读写速度的关系图:

磁盘调度算法

由于操作系统使多进程的,为了支持多进程引入了请求队列,这样如何调度就成了问题。就有了磁盘调度算法,核心还是是要减少寻道时间。

简单解释以下磁盘调度的过程

  • 进程通过盘块号,计算出扇区号
  • 通过电梯算法得到放入把盘块号加入请求队列中。
  • 然后根据磁盘驱动计算出s,h,c
  • 磁盘根据s,h,c得到数据,并给线程。

磁盘调度算法:

FCFS磁盘调度算法

  • 原理:严格按照进程请求访问磁盘的先后顺序进行调度,不管磁头当前位置在哪里。
  • 演示
    • 路径:53 → 98 → 183 → 37 → 122 → 14 → 124 → 65 → 67
  • 优点:公平,不会出现“饥饿”现象(即某个请求永远得不到服务)。
  • 缺点:平均寻道时间长,性能较差。如果请求序列跨度大(如从14跳到183再跳回37),磁头会剧烈摆动,效率极低。

SSTF磁盘调度

这是一种贪婪算法,只看眼前利益。

  • 原理:每次选择距离当前磁头位置最近的那个磁道进行访问。
  • 演示
    • 当前在53。
    • 找最近的:65 (距离12) → 67 (距离2) → 37 (距离30) → 14 (距离23) → 98 (距离84) ...
    • 路径:53 → 65 → 67 → 37 → 14 → 98 → 122 → 124 → 183
  • 优点:比FCFS有较好的寻道性能,平均寻道时间较短。
  • 缺点可能导致“饥饿”。如果一直有请求集中在磁头附近(例如一直在60左右),那么远处的请求(如183或14)可能永远得不到服务。

SCAN磁盘调度

模拟了现实生活中的电梯运行逻辑。

  • 原理:磁头沿一个方向(例如向磁道号增加的方向)移动,途中处理所有请求,直到到达磁盘的最边缘(即使边缘没有请求也要走到头),然后改变方向,反向处理请求。
  • 演示(假设向右/大号方向移动):
    • 路径:53 → 65 → 67 → 98 → 122 → 124 → 183 → (碰到边缘199掉头) → 37 → 14
  • 优点:避免了SSTF的饥饿问题,吞吐量较高。
  • 缺点:对刚扫描过的区域(例如刚刚走过的53左边的区域)不公平,这些区域的请求必须等到磁头走个来回才能被响应,导致等待时间方差较大。

C-SCAN磁盘调度(电梯算法)

这是对SCAN算法的改进,旨在提供更均匀的等待时间。

  • 原理:磁头单向移动(例如只从左向右)。当磁头到达最边缘(199)后,立即快速返回到起始端(0),且在返回途中不处理任何请求。回到起点后,再次从头开始向右扫描。这就形成了一个环状结构。
  • 演示
    • 路径:53 → 65 → 67 → 98 → 122 → 124 → 183 → (直接飞回0) → 14 → 37
  • 优点:所有磁道的请求等待时间更加均匀,消除了SCAN算法两端快、中间慢(或反之)的差异。
  • 缺点:由于回程时不服务,理论上单位时间内的服务次数略少于SCAN,但在实际系统中,因为减少了磁头频繁换向的机械损耗,往往表现更好。

总结对比表

表格

算法

核心策略

优点

缺点

适用场景

FCFS

按顺序排队

简单、公平

效率低,磁头乱跑

负载极轻的系统

SSTF

谁近选谁

平均寻道短

远处请求易“饥饿”

对响应时间敏感

SCAN

走到黑再回头

较好性能,无饥饿

两端请求等待久

通用系统

C-SCAN

单向循环,回程不服务

等待时间均匀

逻辑稍复杂

数据库等重负载系统

从生磁盘到文件

盘块号不够直观,使用还是不方便,因此需要在抽象出一个概念文件。那么如何从文件得到盘块号?要建立字符流和盘块的映射关系。操作系统负责维护这个映射。

FCB(文件控制块):这是一个逻辑概念,泛指描述文件属性的所有信息集合。每个文件必然有且仅有一个完整的 FCB。这里记录了上面说的映射关系。

连续结构的映射表

实际上就是数组,访问比较快但是插入较慢。

  • 做法:文件的所有盘块在磁盘上挨着放。文件中的FCB 回记录 文件名,起始块,块数。
  • 缺点:极容易产生外部碎片,且文件很难动态扩展(如果文件后面紧挨着别的文件,就没法变长了)。

链式结构的映射表

是链表,插入快但是访问比较慢。

  • 做法:文件可以分散存放在磁盘的任何地方。每个盘块里除了存数据,还留出一部分空间存“下一个盘块的物理地址”(指针)。文件中的FCB 会记录起始块。
  • 缺点
    • 不支持随机访问:想读文件的第 1000 个盘块,必须从第 1 个开始,顺着链表数 1000 次,极其缓慢。
    • 可靠性差:链表中只要有一个指针坏了,后面的数据就全丢了。

索引结构的映射表

  • 做法:专门拿出一个盘块作为“索引块”,里面密密麻麻写满了数据盘块的物理地址。文件目录项(FCB)里只存这个索引块的地址。
  • 优点:完美支持随机访问,直接查表即可

如果文件较大要使用多级索引。小文件直接映射数据块,中等文件一阶映射,大文件二阶映射。如下图说示:

目录与文件文件系统

引言

多个文件在磁盘中是如何组织?

操作系统开始时把所有的文件都放在同一个目录下,但是造成文件组织混乱,查找不方便。这就引出了目录这个概念,表示一个文件的集合。把不同分类的文件放在不同的目录下。这个目录是以树形结构组织的,方便查找。

下面问题的关键是如何实现目录到盘块的映射的。更具体的就是更具一个路径("my/data/a"),找到对应文件的FCB

目录的实现

下图是一个目录树的示例:

如何把子文件的信息存储到父文件。可以把子文件的FCB存储到父文件中,但是FCB太大了,如果子文件较多,就会占用很多空间。要检索文件名,我们可以存储文件名和对应FCB对应的地址。这样就能节省很多空间。下图是文件树的组织结构。

系统实现自举,磁盘存储信息

首先解释一下自举,就是计算机“自己把自己拉起来”的过程。

实现自举磁盘的结构:

inode位图: 哪些inode空闲,哪些被占用数据区

盘块位图: 哪些盘块是空闲的,硬盘大小不同这个位 图的大小也不同

空闲位图(位向量)… 0011110011101 表示磁盘块2、3、4、5、 8、9、10、12空闲

超级块:记录两个位图有多大等信息。

根目录的节点编号是国定的,通常放在超级块中。

Logo

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

更多推荐