24.原生磁盘的使用
Posted PacosonSWJTU
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了24.原生磁盘的使用相关的知识,希望对你有一定的参考价值。
【README】
1.本文内容总结自 B站 《操作系统-哈工大李治军老师》,内容非常棒,墙裂推荐;
2.磁盘操作抽象
- 第1层抽象:通过盘块号读写磁盘(或逻辑盘块号);
- 第2层抽象:用队列缓存多个进程读写的盘块号;
- 第3层抽象:文件(或通过文件操作磁盘);参见第25讲内容;
3.盘块号调度算法
- 先来先服务- FCFS;
- 最短寻道优先-SSTF;
- 扫描调度-SCAN ;
- 电梯调度-C-SCAN;(推荐,性能最优)
【1】磁盘工作原理
【1.1】 磁盘结构
【图解】
- 1)磁盘由叠起来的盘片组成,1个盘片的2个面都有磁材料,都可以存储信息;
- 2)1个盘面划分为多个圆环,每个圆环叫做磁道;
- 3)每个磁道划分为多个扇区,每个扇区固定512字节;扇区是读写磁盘的基本单位;
- 4)磁头:每当有磁盘读写请求时,在磁盘控制器下,磁头就会移动去寻找磁道,磁道再自身旋转进而定位到扇区;
【1.2】磁盘读写过程
【图解】
- 步骤1:磁盘控制器控制磁头移动,找到正确磁道;
- 步骤2:磁道自身旋转,使得对应扇区旋转到磁头下,方便磁头读写;
- 步骤3:磁头把当前扇区的数据读入到内存缓冲区;(磁信号转为电信号);
- 步骤4:程序把内存缓冲区中的某字节送入cpu,cpu修改字节内容并把数据返还到内存缓冲区;
- 步骤5:把内存缓冲区中数据写出到磁盘(同读磁盘相同,写磁盘也需要磁头移动,磁道自身旋转到对应扇区,最后写操作);(电信号产生磁信号);
小结:
- 磁盘IO过程: 磁盘控制器 -> 磁头寻道 -> 磁道旋转 -> 传输(读写操作)
【1.3】 使用磁盘
1)根据上述内容,我们知道磁盘工作原理,即磁头移动找到对应磁道,磁道自身旋转把对应扇区旋转到磁头下;
补充:
- 不同盘片且具有相同投影的圆环组成1个磁柱,即不同盘片上的多个磁道组成1个磁柱;
- 所以,cpu只需要把 磁柱号,磁头号,扇区号(可以算出磁道号),映射的内存地址,连续读写的扇区数 等多个参数 送入磁盘控制器,然后使用 DMA控制器 盗用总线把磁盘数据读入内存;
2)用out 指令 把上述4个参数送入磁盘控制器;
【图解】
1)符号表示
磁柱号 | Cyl == cylinder |
磁头号 | head |
扇区号 | Sect == sector |
内存地址 |
上述把多个参数直接送入磁盘控制器,编码复杂,太麻烦,所以进行了如下抽象;
【2】磁盘操作抽象
- 第1层抽象:通过盘块号读写磁盘;
- 第2层抽象:用队列缓存多进程读写的盘块号
【2.1】第一层抽象:通过盘块号读写磁盘
(图1)
【图解】
- 1)程序给出盘块号 block ,送入磁盘驱动程序;
- 2)磁盘驱动根据给定的盘块号block和磁盘参数计算出 磁柱 cyl, 磁头号 head,扇区号sec 等参数并送入磁盘控制器;(CHS参数)
- 3)磁盘控制器操作 磁头,盘面等进行工作;
问题:
- 如何把block 计算出 磁柱号,磁头号,扇区号,即一维地址 推导出3维地址;
- 扇区号如何编址? 为什么这样编址 ?
设计思想:
- 考虑到程序执行的局部性原理,给定的block号相邻的盘块可以快速读出;
1)磁盘访问时间
- 磁盘访问时间 = 写入控制器时间 + 寻道时间 + 旋转时间 + 传输时间
时间类型 | 描述 | 耗时 |
写入控制器时间 | 磁盘驱动根据block推导出 磁柱号,磁头号,扇区号等参数,并把上述参数送入控制器; | |
寻道时间 | 磁头移动寻找到磁柱上的对应磁道; (或移动磁臂时间) | 12ms 到 8ms (最耗时,机械运动,直线移动) |
旋转时间 | 磁道自己旋转使得对应扇区在磁头下 | 半周4ms |
传输时间 | 磁头读写时间(磁信号产生电信号或电信号产生磁信号) | 50M/秒,约 0.3ms |
小结:
- 可以看到耗时最长的阶段是寻道时间,所以在访存时,我们应该减少寻道时间(或减少寻道次数);
2)如何做到减少寻道时间(或减少寻道次数)
- 首先,把相邻block的盘块放在同一个磁道上(或相邻扇区上);
- 然后,根据给定block盘块号,把相邻盘块(或相邻扇区)一起读入到内存;
- 补充:block是逻辑盘块号,最终是要映射到物理磁盘扇区上的;
3)扇区编址
(图2)
【图解】综上,根据以上分析,我们对磁盘扇区就知道如何编址了。(相邻扇区号是连续的) ,参见图1和图2。
- 若每个磁道7个扇区,可以看到 0号扇区与7号扇区的投影是重合的;
- 如 0号扇区在盘片1的地址 0~512字节;7号扇区在盘片2的地址0~512字节;
- 因为磁臂上的多个磁头(每个盘片1个磁头)是作为一个整体一起移动的;即 盘片1的磁头移动到 0~512字节的扇区,盘片2的磁头也移动到了 0~512字节的扇区,......;
4)通过 CHS(柱面号C,磁头号H,扇区偏移号S)计算目标扇区号
- 目标扇区号 = C*(heads*sectors) + H *Sectors + S; (公式1)
- 其中 head 表示每个柱面的磁头数量,sector 表示每个磁道的扇区数量;
5)每次读写的扇区数量增多,读写速度会上升(一定程度上);
6)每次读写1K, 每次读写1M, 区别如下表所示。
每次读写数据量 | 扇区数 | 读写速度 | 磁盘碎片 | 磁盘空间利用率 |
1K | 2 | 100K/秒 | 0.5K | 高 |
1M | 2K | 40M/秒 | 0.5M | 低(因为浪费多) |
目前,磁盘容量可以做到很大,完全可以用空间去换时间效率,即把读写单位变大,每次读写1M,而不是1K;
7)对应到读写扇区的高效方案是:
- 每次读写多个相邻连续扇区,这些扇区组成1个逻辑盘块;而不是仅读写一个扇区;
小结:
- 操作系统把磁盘读写单位从扇区 转换为盘块,提高了磁盘读写效率;(一个盘块是连续的几个扇区)
【例】以盘块为单位读写磁盘(重要)
- 步骤1:当操作系统收到 磁盘读写请求时,首先把 盘块号 转为 多个连续扇区的扇区号;
- 步骤2:磁盘驱动根据扇区号算出 CHS(柱面号,磁头号,扇区号偏移),并通过out指令把CHS值送入磁盘控制器;
- 步骤3:磁盘控制器读写目标扇区号数据到内存缓冲区,并返回给上层应用程序;
综上:应用程序可以通过盘块号来读写磁盘。
【图解】
- Req->sector = bh->b_blocknr << 1 ;扇区号等于盘块号左移一位,即乘以2; 所以每个盘块会读写2个扇区数据;
- Do_hd_request方法:根据盘块号算出 公式1的变量,如 CHS-柱面号,磁头号,扇区号偏移,每个磁道的扇区数nsect,每个柱面的磁头数量head等;
- hd_out方法:向磁盘控制器 发出out指令,控制器根据参数读写目标扇区数据;
【2.2】第二层抽象:(用队列缓存多进程读写的盘块号)
操作系统是多进程图像,存在多个进程使用盘块号读写磁盘的情况。
当有多个进程时,需要使用缓冲队列存储不同进程的盘块号。
1)盘块请求队列
【图解】
1)请求队列用于存储不同进程访问磁盘的盘块号;
2)磁盘驱动什么时候从队列中取出盘块号?
- 当磁盘驱动读写完上一个盘块号的数据,就会发出磁盘中断;则磁盘中断处理程序会从请求队列中取出下一个盘块号;
3)问题:多个磁盘访问请求出现在请求队列怎么办?
- 考虑用磁盘调度 。
4)问题:调度的目标是什么? 调度时主要考察什么 ?
- 调度目标:平均访问延迟小;
- 调度时考察:寻道时间是主要矛盾;
解决方法:
- 在磁盘驱动中编写一个算法,选择下一个要读取的盘块号,使得磁盘工作速度快;
- 如 ppt,word都有读写盘块请求,那如何让磁盘工作速度更快;
- 而磁盘读写最耗时的是寻道时间,即如何最小化寻道时间;
补充:
- 生磁盘:根据盘块号使用磁盘;
- 熟磁盘:根据文件使用磁盘;
【3】磁盘调度算法(如何选择盘块)
【3.1】FCFS -磁盘调度算法
1)FCFS: 先来先服务;即盘块调度顺序,与盘块在队列的顺序一致;
【图解】
1)请求队列: 请求队列= [ 98, 183, 37, 122, 14, 124, 65, 67 ] ,队列里存储的是盘块号,也可以理解为磁道号(因为盘块号会被磁盘驱动计算出 磁头号,柱面号,扇区号偏移 )。
2)根据 FCFS 先来先服务的盘块调度算法,盘块调度顺序与队列顺序一致。
磁头移动路径如上图折线所示,总结如下(磁头起始位置在53,可以理解为在第53号磁道):
调度盘块 | 98 | 183 | 37 | 122 | 14 | 124 | 65 | 67 |
移动磁道数量 | 45 | 85 | 146 | 85 | 108 | 110 | 59 | 2 |
小结:磁头总共移动了 640 个磁道。
【3.2】 SSTF-最短寻道优先
1)SSTF: Shortest seek time first ,最短寻道优先;
- 选择与当前磁道距离最短的磁道进行访问;
【图解】
根据最短寻道优先算法,选择与当前磁道距离最短的磁道访问请求,显然盘块调度顺序与队列中的盘块顺序不一致。队列= [ 98, 183, 37, 122, 14, 124, 65, 67 ]
磁头移动路径如上图折线所示,总结如下(磁头起始位置在第53号磁道):
调度盘块 | 65 | 67 | 37 | 14 | 98 | 122 | 124 | 183 |
移动磁道数量 | 12 | 2 | 30 | 23 | 84 | 24 | 2 | 59 |
小结:磁头总共移动了 236个磁道。
2)比较结果:
- 显然 最短寻道优先算法 优于 先来先服务算法。
3)最短寻道优先的问题
- 因为该算法是选择与当前磁道距离最短的磁道进行访问,所以访问路径是在中间移动,即磁头频繁在磁道中间位置,而会造成 边缘磁道 长时间不访问的现象,即 导致边缘磁道的饥饿问题。
所以需要对 最短寻道优先算法进行优化改进。
【3.3】SCAN-扫描调度算法
1)SCAN:扫描调度算法;
- SCAN算法是 SSTF(最短寻道优先)的变体,是由 SSTF + 中途不回折 的思想;
- 即 SCAN算法先是向某一边(如左边)移动直到边界,移动同时访问盘块号;然后在向另一边(右边)移动直到边界。
【图解】
根据SCAN算法,先向某一边移动直到边界,然后向另一边移动直到边界。队列= [ 98, 183, 37, 122, 14, 124, 65, 67 ]。
则磁头移动路径如上图折线所示,总结如下(磁头起始位置在第53号磁道):
调度盘块 | 37 | 14 | 65 | 67 | 98 | 122 | 124 | 183 |
移动磁道数量 | 16 | 23 | 51 | 2 | 31 | 24 | 2 | 59 |
总计:磁头总共移动了 208个磁道。(上图给出的是236,原因在于它访问了 第0号磁道,从第14号磁道到第0号磁道双向需要移动28个磁道)
【3.4】C-SCAN-电梯调度算法
1)C-SCAN:现实生活中的电梯算法;
- SCAN算法是 SSTF的变体,是由 SSTF + 中途不回折 的思想;
2)步骤:
- 步骤1:C-SCAN算法中先是向某一边(如左边)移动直到边界,移动的同时访问盘块号;
- 步骤2:然后立即移动到另一边的边界(如最右边183),仅移动但不做任何盘块访问(这是电梯调度算法与 SCAN算法的区别);
- 步骤3:同步骤1类似,以右边的边界为起点,向左边移动并访问盘块号直到左边边界;
补充:同电梯类似,电梯每次都会移动到到最高楼层接人,并把人送达目的地直到最低楼层;所以称电梯算法。
【图解】
根据SCAN算法,队列= [ 98, 183, 37, 122, 14, 124, 65, 67 ],
则磁头移动路径如上图折线所示,总结如下(磁头起始位置在第53号磁道):
调度盘块 | 37 | 14 | 183 | 124 | 122 | 98 | 67 | 65 |
移动磁道数量 | 16 | 23 | 169 | 59 | 2 | 24 | 31 | 2 |
总计:磁头总共移动了326个磁道。但磁头从第14号移动到第183号磁道是非常快速的(不做任何访问),所以 326 - 169 = 157个磁道。
【小结】磁盘调度算法
盘块队列= [ 98, 183, 37, 122, 14, 124, 65, 67 ];
算法名称 | 移动磁道个数 | 性能 |
FCFS-先来先服务 | 640 | |
SSTF-最短寻道优先 | 236 | |
SCAN-扫描调度 | 208 | |
C-SCAN-电梯调度 | 157 | 最优(推荐) |
【4】多个进程共同使用磁盘
【图解】多个进程共同使用磁盘步骤(重要*)
- 步骤1:多个进程在访问磁盘的时候,多个进程要产生请求放入请求队列;
- 步骤2:然后磁盘中断触发中断处理程序从队列中取出请求盘块号;
- 步骤3:根据盘块号换算出CHS,通过out指令发送给磁盘控制器以进行读写多个扇区;
总结:这就是使用生磁盘的完整故事;
【代码】进程访问磁盘创建请求
// 进程访问磁盘创建请求
static void make_request()
...
// 把盘块号换算为扇区号;(乘以2)
req->sector = bh-> b_blocknr << 1;
// 把盘块请求添加到队列中
add_request(major + blk_dev, req);
// add_request 把请求添加到队列
static void add_request(struct blk_dev struct *dev, struct request *req)
cli(); // 关中断
for(;temp->next; temp = tmep->next)
if (( IN_ORDER(temp, req) || !IN_ORDER(temp, temp->next) )
&& IN_ORDER(req, temp->next)) break;
req->next = temp->next;
temp->next = req;
sti(); // 开中断
// 补充 cli 与 sti 构建起临界区,只允许一个进程进入。
// IN_ORDER 方法 : 判断 s1 小于 s2 (比较扇区号大小)
#define IN_ORDER(s1, s2)
(( s1->dev < s2->dev )|| (s1->dev == s2->dev && s1->sector < s2->sector ))
【代码解说】
- 当 temp 小于req 且 req小于temp->next; 或 temp 大于 temp->next 且 req小于temp->next 时,循环退出;
【4.1】生磁盘的使用整理
【图解】
1)进程得到盘块号, 算出扇区号;
- 如何得到盘块号? 进程或程序是操作文件的,那就是通过文件得到盘块号;
- 并根据盘块号算出扇区号,因为一个盘块对应多个扇区,所以可以看做是起始扇区号;
2)用扇区号 make req(制作磁盘请求),用电梯算法 add_request (选择下一个盘块号)
- 用扇区号 make req,制作一个磁盘扇区访问请求,这个制作请求的代码包含了 内存缓冲区申请与管理的代码(对真实磁盘读写性能提升非常大);
- 用电梯算法把该请求放入请求队列中;
3)进程sleep_on
- 用户进程把 磁盘访问请求 放入队列后就睡眠了;磁盘具体的读写操作由硬件去完成; (即进程间的相互协作)
4)磁盘中断处理
- 磁盘控制器执行完上一个请求后,会发出中断;
- 中断处理程序会从请求队列中获取请求(获取下一个盘块号);
5)do_hd_request 算出 柱面号,磁头号,扇区号;
- 步骤1:磁盘驱动根据 out 命令发出磁盘读写指令;
- 步骤2:读写完成后(或把数据读取到内存缓冲区),会再次发出中断,中断处理程序会调用 read_intr() 方法;
- 步骤3:read_intr()方法会结束请求并唤醒进程;
- 步骤4:进程被唤醒后,就可以在内存缓冲区中读取想要的数据了,进程就可以继续工作了;(从进程发出磁盘读写请求,到被唤醒这段时间,进程是阻塞或睡眠的,显然读写磁盘的程序执行是比较慢的,重要一点是磁盘寻道耗时长)
6)hd_out 调用 outp(...) 完成磁盘端口读写
小结:到这里,生磁盘的使用就完整介绍完了;
- 我们把磁盘的使用抽象为盘块号,然后再在此基础上抽象为 多进程共同使用磁盘的方式;使得磁盘使用更加高效;
补充:如何得到盘块号?
- 上文讲到通过文件来获取盘块号;
- 那通过文件如何来获取盘块号呢。所以引出了 生磁盘到文件 的内容(参见第25章内容);
以上是关于24.原生磁盘的使用的主要内容,如果未能解决你的问题,请参考以下文章