操作系统 (14) 磁盘的使用

206 阅读10分钟

本文引用代码及图片均来自 李治军: 操作系统32讲

CPU与磁盘

磁盘由几块盘片叠放在一起组成,盘片绕着主轴转动,传动手臂移动磁头读取盘片上的磁信息并转换成电信号,写磁盘时则是将电信号转换成磁信息。盘片两面都可以存储信息,每个存储信息的面都有一个磁头

图片来源: 美团技术团队

image.png

盘面划分一圈圈的磁道,编号从外向内,磁道上划分一段段的扇区,为了避免磁性相互影响,不同的磁道以及扇区之间都有一定的间隔扇区是磁盘基本单位,每个扇区大小相等,但是每个磁道上的扇区数不一定相等旧式非分区记录方式相等,新式分区记录方式外圈磁道扇区数大于内圈磁道扇区数,本文以旧式非分区方式为例

图片来源: Google图片

image.png

不同盘面的同一编号磁道形成柱面,很明显磁盘旋转相同的时间外柱面扫过的扇区要多于内柱面,如果文件放在内柱面访问速度会比放在外柱面慢

和其他IO设备一样,CPU也是通过操作控制器来操作磁盘,只要告诉控制器需要操作哪个柱面(cyl)、磁头(head)、扇区(sec)和缓冲区位置,控制器就操作磁盘进行数据的读写,读写完成通过中断告诉CPU,CPU再处理中断就完成一次磁盘读写

image.png

第一层抽象

让程序直接指定柱面和磁头等信息无疑是麻烦且容易出错的,所以操作系统使用block来取代这些信息,称之为第一层抽象

程序读写磁盘时只需指定block,系统会自动从block中计算出柱面、磁头和扇区。假设每条磁道扇区数相等,磁盘扇区编号从外向内,从上到下。那么扇区编号=柱面号柱面扇区数+磁头号磁道扇区数+偏移{扇区编号 = 柱面号 * 柱面扇区数 + 磁头号 * 磁道扇区数 + 偏移},其中柱面扇区数=磁头数磁道扇区数{柱面扇区数 = 磁头数 * 磁道扇区数},即扇区编号=C(headsec)+Hsec+S{扇区编号 = C * (head * sec) + H * sec + S}

image.png

上图中第8个扇区的编号7=0(87)+17+0{7 = 0 * (8 * 7) + 1 * 7 + 0},所以第8个扇区block=7{block = 7}。系统拿到block后根据扇区编号公式得 S=block%sec{S = block \% sec},得出S{S}blockS=(Chead+H)sec{block - S = (C * head + H)* sec},同理可得H=(blocks)/sec%head{H = (block - s) / sec \% head},求出H{H}S{S}后自然可求出C{C}

image.png

要注意一点,系统中并不是一个block对应一个扇区,而是一个block对应几个相邻的扇区,因为磁盘访问主要是寻道费时,一次多读几个扇区可以提高效率,所以实际上进程需要从block推算出扇区号再使用

调度算法

现在进程可以通过block来操作磁盘了,进程把block信息包进磁盘操作请求里然后等着系统执行,那么,在多进程的情景中系统应该先执行哪个进程的磁盘请求呢?解下来又该调度算法登场了

image.png

FCFS

FCFS(First Come First Serve)先来先服务是最朴素的算法,也很直观

image.png

在上图请求序列中磁头共移动640磁道,其实仔细观察可以发现磁头在移动过程中可以把路过的请求一起处理,比如从其实位置5398磁道的过程中处理掉65、67磁道的请求,那么就改进下,先处理距离当前位置最近的请求

SSTF

SSTF(Shortest Seek Time First)最短寻道时间,先处理距离当前位置最新的请求

image.png

可以发现同一个请求序列SSTF磁头移动要比FCFS少很多,但是,如果处理请求过程中不断进来新的请求,这些请求距离当前位置比之前进来的请求更近,就会导致磁头一直在当前位置附件移动而忽视了更早之前进来的请求,造成请求饥饿

SCAN

SCAN也称电梯算法,可以理解成SSTF不折返版,它往某个方向走到时候一直走到头,然后再往反方向走到头,如此循环往复

image.png

该算法对于中间磁道的请求很友好,往返的过程中中间磁道都能得到及时的扫描,所以磁道的等待时间不均匀

C-SCAN

C-SCANSCAN跳跃版,它只扫描一个方向,每次到尽头后就跳跃到另一个方向的尽头重新开始扫描

image.png

C-SCAN可以提供比SCAN均匀的等待时间

第一层抽象总结

磁盘的使用可以总结为下图

image.png

需要注意的是第2步中是进程按照C-SCAN算法的工作形式将请求放入队列中

image.png

进程先根据block算出扇区号,然后关中断(进入临界区),遍历队列中的请求根据扇区号找到自己的位置并插入,开中断(离开临界区)

第二层抽象

一个block的大小毕竟是有限的,如果数据需要占用几个block,那么进程需要记住所有block以及数据不同部分和不同block之间的对应关系,这无疑是麻烦且容易出错的,所以操作系统使用文件来包装这些信息,称为第二层抽象

本文以文本文件为例,文本文件是一种字符流文件(例如txt,c语言 .c 文件),是用户最常使用的文件类型之一。前面提到一个文件对应多个block,那么在本例中文件就是建立字符流block之间的映射

假设用户有一个test.c文件,现在需要将第200-212之间的字符删掉,在编辑器上用户只需要选中字符然后按下删除键,但是对于操作系统,它需要先找到200-212字符对应的block,按照C-SCAN算法发出请求将block内容载入到内存,执行删除操作,最 后再将内容写回block

image.png

存储结构

系统操作文件的关键是确定内容和block之间的映射关系,本例中就是哪些字符在哪个block的关系,其实也就是逻辑block物理block之间的映射关系,映射关系自然是和文件存储结构相关,于是引出以下三种存储结构

顺序存储

就像数组一样,把文件内容存放在一块连续的磁盘空间中

image.png

如上图6、7、8就是test.c占据的block,假设每个block可以存储100个字符,那么200-212就在第3逻辑block里,物理block的起始号是6对应逻辑block 1,那么第3逻辑block就对应8物理block

在顺序结构中只需要知道起始block以及每个block容量就能算出任意内容存储的blockblock的容量是操作系统设定的,而起始block则可以存储在FCB(File Control Block)

image.png

顺序结构的特点和数组一样,随机访问快,但是修改操作慢,比如现在test.c占用678三个block14block被别的文件占用,如果后续文件越来越大一直占用到13号还不够,这时只能把test.c整个搬迁到其他有充足容量的磁盘区域。顺序存储还容易产生磁盘碎片,比如现在4、5block是空闲的但是又装不下别的一整个文件,那么4、5block就被浪费了

链式存储

就像链表一样,把文件内容分散在整个磁盘空间

image.png

链式存储分为隐式链接显示链接

  1. 隐式链接:指针存放在block中,FCB存储起始块,从起始块得到下一个block的指针,依此类推。每次必须读出block的内容才能得到下一个block的指针
  2. 显示链接:为每个磁盘建立一张FAT(File Allocation Table),记录每个block的指针,FCB记录初始块,然后根据FAT查找目标block

图片来源: 千寻瀑༄

image.png

链式结构的特点和链表一样,空间高效利用不会产生磁盘碎片,方便修改。隐式链接不支持随机访问,显示链接FAT常驻内存,查FAT耗时短可看作支持随机访问,但是FAT本身需要占用磁盘空间

索引结构

就像索引表一样,直接记录每一个逻辑block对应的物理block

image.png

专门用一个block记录索引,FCB中存储索引块的位置,索引块中记录每一个逻辑block对应的物理block,如上图第1逻辑block对应9物理block每个文件有自己的专属索引块

索引结构的特点和索引表一样,空间高效利用不会产生磁盘碎片,方便修改,且支持随机访问

一个block的存储是有限的,假设一个block能存储100个索引记录,当文件需要占用多于100block时怎么办?

  1. 多个索引块:索引块顺序或链式存储,相应的特点在前面已经提过
  2. 多级索引:就像页表一样,采用多级结构。比如两级索引结构中,先将一级索引表载入内存,确定逻辑block对应的一级索引项,然后将一级索引项对应的二级索引表载入内存并从中找到对应的物理block

image.png

第二层抽象总结

操作系统(13) IO 中提到我们使用文件的方式以及open函数的作用

FILE *fp = fopen("test.txt", "w");
fprintf(fp, "test");
fclose(fp);

open函数为我们建立了进程--文件--磁盘数据块之间的联系

image.png

还是以删去200-212之间的字符为例,write经过层层调用会进入到file_write里面

image.png

file_write的参数中filp用于确定字符位置(fseek指针)count是读写字符数。file_write先确定读写位置pos,然后根据pos算出物理块号block,再根据block确定扇区号并利用C-SCAN算法提交磁盘读请求把内容读进来,然后就是不断从缓冲区取出内容对读进来的原block的内容进行更改并更新读写位置p

代码来源: Bootlin

image.png

create_block中有些细节,create_block是直接调用了_bmap函数,_bmap根据块号确定索引信息

image.png

具体形式如下,普通文件i_zone里存放block索引,如果是设备文件存放的就是主从设备号之类的信息

图片来源: 庾志辉

image.png

最后文件视图可以总结为下图,根据文件类型的不同,普通文件走磁盘的分支,设备文件走驱动的分支

image.png

参考文献