本文引用代码及图片均来自 李治军: 操作系统32讲
CPU与磁盘
磁盘由几块盘片叠放在一起组成,盘片绕着主轴转动,传动手臂移动磁头读取盘片上的磁信息并转换
成电信号,写磁盘时则是将电信号转换成磁信息。盘片两面都可以存储信息,每个存储信息的面都有一个磁头
盘面划分一圈圈的磁道
,编号从外向内,磁道上划分一段段的扇区
,为了避免磁性相互影响,不同的磁道以及扇区之间都有一定的间隔
。扇区
是磁盘基本单位,每个扇区大小相等
,但是每个磁道上的扇区数不一定相等
,旧式
非分区记录方式相等,新式
分区记录方式外圈磁道扇区数大于内圈磁道扇区数,本文以旧式
非分区方式为例
不同盘面的同一编号
磁道形成柱面,很明显磁盘旋转相同的时间外柱面扫过的扇区要多于内柱面,如果文件放在内柱面访问速度会比放在外柱面慢
和其他IO设备一样,CPU也是通过操作控制器来操作磁盘,只要告诉控制器需要操作哪个柱面(cyl)、磁头(head)、扇区(sec)和缓冲区位置
,控制器就操作磁盘进行数据的读写,读写完成通过中断
告诉CPU,CPU再处理中断就完成一次磁盘读写
第一层抽象
让程序直接指定柱面和磁头等信息无疑是麻烦且容易出错的,所以操作系统使用block
来取代这些信息,称之为第一层抽象
程序读写磁盘时只需指定block
,系统会自动从block
中计算出柱面、磁头和扇区。假设每条磁道扇区数相等,磁盘扇区编号从外向内,从上到下。那么,其中,即
上图中第8
个扇区的编号,所以第8
个扇区。系统拿到block
后根据扇区编号公式得 ,得出后,同理可得,求出和后自然可求出
要注意一点,系统中并不是
一个block
对应一个扇区,而是一个block
对应几个相邻的扇区,因为磁盘访问主要是寻道
费时,一次多读几个扇区可以提高效率,所以实际上进程需要从block
推算出扇区号再使用
调度算法
现在进程可以通过block
来操作磁盘了,进程把block
信息包进磁盘操作请求里然后等着系统执行,那么,在多进程的情景中系统应该先执行哪个进程的磁盘请求呢?解下来又该调度算法登场了
FCFS
FCFS(First Come First Serve)
先来先服务是最朴素的算法,也很直观
在上图请求序列中磁头共移动640
磁道,其实仔细观察可以发现磁头在移动过程中可以把路过
的请求一起处理,比如从其实位置53
到98
磁道的过程中处理掉65、67
磁道的请求,那么就改进下,先处理距离当前位置最近的请求
SSTF
SSTF(Shortest Seek Time First)
最短寻道时间,先处理距离当前位置最新的请求
可以发现同一个请求序列SSTF
磁头移动要比FCFS
少很多,但是,如果处理请求过程中不断进来新的请求,这些请求距离当前位置比之前进来的请求更近,就会导致磁头一直在当前位置附件移动而忽视了更早之前进来的请求,造成请求饥饿
SCAN
SCAN
也称电梯算法,可以理解成SSTF
的不折返
版,它往某个方向走到时候一直走到头,然后再往反方向走到头,如此循环往复
该算法对于中间磁道
的请求很友好,往返的过程中中间磁道都能得到及时的扫描,所以磁道的等待时间
不均匀
C-SCAN
C-SCAN
是SCAN
的跳跃版
,它只扫描一个方向,每次到尽头后就跳跃到另一个方向的尽头重新开始扫描
C-SCAN
可以提供比SCAN
更均匀的等待时间
第一层抽象总结
磁盘的使用可以总结为下图
需要注意的是第2步中是进程
按照C-SCAN
算法的工作形式将请求放入
队列中
进程先根据block
算出扇区号,然后关中断(进入临界区),遍历队列中的请求根据扇区号找到自己的位置并插入,开中断(离开临界区)
第二层抽象
一个block
的大小毕竟是有限的,如果数据需要占用几个block
,那么进程需要记住所有block
以及数据不同部分和不同block
之间的对应关系
,这无疑是麻烦且容易出错的,所以操作系统使用文件
来包装这些信息,称为第二层抽象
本文以文本文件为例,文本文件是一种字符流文件(例如txt,c语言 .c 文件),是用户最常使用的文件类型之一。前面提到一个文件对应多个block
,那么在本例中文件就是建立字符流
和block
之间的映射
假设用户有一个test.c
文件,现在需要将第200-212
之间的字符删掉,在编辑器上用户只需要选中字符然后按下删除键,但是对于操作系统,它需要先找到200-212
字符对应的block
,按照C-SCAN
算法发出请求将block
内容载入到内存,执行删除操作,最 后再将内容写回block
存储结构
系统操作文件的关键是确定内容和block之间的映射关系,本例中就是哪些字符在哪个block
的关系,其实也就是逻辑block
和物理block
之间的映射关系,映射关系自然是和文件存储结构
相关,于是引出以下三种存储结构
顺序存储
就像数组一样,把文件内容存放在一块连续的磁盘空间中
如上图6、7、8就是test.c
占据的block
,假设每个block
可以存储100个
字符,那么200-212
就在第3
个逻辑block
里,物理block
的起始号是6
对应逻辑block 1
,那么第3
个逻辑block
就对应8
号物理block
在顺序结构中只需要知道起始block
以及每个block
的容量
就能算出任意内容存储的block
,block
的容量是操作系统设定的,而起始block
则可以存储在FCB(File Control Block)
中
顺序结构的特点和数组一样,随机访问
快,但是修改
操作慢,比如现在test.c
占用678
三个block
,14
号block
被别的文件占用,如果后续文件越来越大一直占用到13
号还不够,这时只能把test.c
整个搬迁
到其他有充足容量的磁盘区域。顺序存储还容易产生磁盘碎片
,比如现在4、5
号block
是空闲的但是又装不下别的一整个文件,那么4、5
号block
就被浪费了
链式存储
就像链表一样,把文件内容分散在整个磁盘空间
链式存储分为隐式链接
和显示链接
- 隐式链接:指针存放在
block
中,FCB
存储起始块,从起始块得到下一个block
的指针,依此类推。每次必须
读出block
的内容才能得到下一个block
的指针 - 显示链接:为每个磁盘建立一张
FAT(File Allocation Table)
,记录每个block
的指针,FCB
记录初始块,然后根据FAT
查找目标block
链式结构的特点和链表一样,空间高效利用
不会产生磁盘碎片,方便
修改。隐式链接不支持
随机访问,显示链接
中FAT常驻内存
,查FAT
耗时短可看作
支持随机访问,但是FAT
本身需要占用磁盘空间
索引结构
就像索引表一样,直接记录每一个逻辑block
对应的物理block
专门用一个block
记录索引,FCB
中存储索引块
的位置,索引块中记录每一个逻辑block
对应的物理block
,如上图第1
个逻辑block
对应9
号物理block
,每个
文件有自己的专属索引块
索引结构的特点和索引表一样,空间高效利用
不会产生磁盘碎片,方便
修改,且支持随机访问
一个block
的存储是有限的,假设一个block
能存储100
个索引记录,当文件需要占用多于100
个block
时怎么办?
多个索引块
:索引块顺序或链式存储,相应的特点在前面已经提过多级索引
:就像页表
一样,采用多级结构。比如两级索引结构中,先将一级索引表
载入内存,确定逻辑block
对应的一级
索引项,然后将一级索引项对应的二级索引表
载入内存并从中找到对应的物理block
第二层抽象总结
在 操作系统(13) IO 中提到我们使用文件的方式以及open
函数的作用
FILE *fp = fopen("test.txt", "w");
fprintf(fp, "test");
fclose(fp);
open
函数为我们建立了进程--文件--磁盘数据块
之间的联系
还是以删去200-212
之间的字符为例,write
经过层层调用会进入到file_write
里面
file_write
的参数中filp
用于确定字符位置(fseek指针)
,count
是读写字符数。file_write
先确定读写位置pos
,然后根据pos
算出物理块号block
,再根据block
确定扇区号
并利用C-SCAN
算法提交磁盘读请求把内容读进来
,然后就是不断从缓冲区取出内容对读进来的原block的内容
进行更改并更新读写位置p
create_block
中有些细节,create_block
是直接调用了_bmap
函数,_bmap
根据块号
确定索引信息
具体形式如下,普通
文件i_zone
里存放block索引
,如果是设备
文件存放的就是主从设备号之类
的信息
最后文件视图可以总结为下图,根据文件类型的不同,普通文件走磁盘的分支,设备文件走驱动的分支