操作系统生磁盘的使用---18

x33g5p2x  于2022-08-17 转载在 其他  
字(3.0k)|赞(0)|评价(0)|浏览(681)

仍然从硬件开始…

使用磁盘从认识磁盘开始

  • 画一个示意图:

  • 看看俯视图:

  • 磁盘的访问单位是扇区
  • 扇区大小:512字节
  • 扇区的大小是传输时间和 碎片浪费的折衷

磁盘的I/O过程

如果要读磁盘,那么基本步骤如下:

  • 首先将磁头移动到指定磁道处
  • 旋转磁道到指定扇区处,通过磁生电,读取一个字节的输出到内存缓冲区中

如果要写磁盘,步骤和上面类似:

  • 首先将磁头移动到指定磁道处
  • 旋转磁道到指定扇区处,通过电生磁,向扇区中某处位置写入一个字节数据

最直接的使用磁盘

看了上面的基本过程后,相信大家可以理解了,如果我们要操作磁盘读写的话,就是告诉磁盘控制器关于柱面、磁头、扇区、缓 存位置,然后是读还是写,剩下的由磁盘控制器完成。

  • 磁臂移动到对应的柱面位置
  • 通过磁头,决定到底读取的是哪一个盘面上的磁道,这样磁盘控制器就可以给对应的磁头上电,就可以利用对应的磁头去读取对应盘面磁道上的数据了
  • 读取当前磁道上的扇区,还需要知道需要连续读取几个扇区
  • 通过告诉磁盘控制器内存缓存的位置,下面就可以利用总线调用技术DMA,将扇区上的数据读取到内存缓冲区上,或者将内存缓存中的数据写入到磁盘中
void do_hd_request(void){ 
...hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,...);
port_write(HD_DATA,CURRENT->buffer,256);}
void hd_out(drive, nsect, sec, head, cyl, cmd...){
port = HD_DATA; //数据寄存器端口(0x1f0)
//需要连续读取几个扇区
outb_p(nsect,++port);
//哪个扇区
outb_p(sect,++port);
//哪个柱面
outb_p(cyl,++port); 
outb_port(cyl>>8,++port);
//哪个磁头
outb_p(0xA0|(drive<<4)|head, ++port);
outb_p(cmd, ++port); }

实际代码书写就是找到磁盘控制器对应的端口位置,然后将要操作的柱面,磁头,扇区位置,通过out指令发送到磁盘控制器的端口中,还有缓存的位置,剩下的事情就由磁盘控制器完成。

通过盘块号读写磁盘(一层抽象)

上面直接通过磁盘具体参数来操作磁盘过于繁琐,面向用户使用显然不合适,因此需要再来一层抽象。

为了简化操作,操作系统引入了block盘块号,磁盘驱动负责从block计算出cyl,head,sec(CHS)。
block盘块号的引入,相当于增加了一层一维编址到三维编址的转换过程。

问题:如何编址?为什么这样编址?

磁臂的移动速度,即寻道耗费的时间相对较长,因此要提高磁盘访问时间,就需要尽可能提高寻道时间,或者减少寻道次数,尽量让一次数据的读写,都在一个磁道上完成。

因此相邻的盘块号应该尽量处在一个磁道上,即block相邻的盘块可以快速读出。

当然,盘块号block最近要映射到扇区上去,因此这要求扇区尽量是在同一个扇区上连续排列的。

从CHS到扇区号,从扇区到盘块

可以看到下图,第一个柱面的某个磁道上,分步了0~6号扇区,当需要放置第7个扇区的时候,为了避免磁臂的移动,需要将第7个扇区放在同一个磁道,但是不同柱面上。

每次磁盘访问的主要时间都花费在了磁臂寻道和磁头旋转上,而数据传输的耗时,确可以忽略不计。

因此,我们是否可以在一次磁盘读取过程中,连续读取多个扇区的数据,而不是每次都只是一个扇区,这样就提供磁盘的读写速度,并且由于文件通常会采用连续存放,因此,对于大文件来说,这样做可以更加明显感觉到读写速度的提升。

因此,操作会将连续的几个扇区看做是一个盘块,上层应用发出一个盘块号后,操作系统经过计算就能知道需要连续读取多少个扇区了。

相当于操作系统读取磁盘的最小单位为一个盘块,而一个盘块可能由多个扇区组成,类比内存的分页机制,就可以知道,这样做会造成磁盘空间的浪费,

例如下面的test.c文件,分配存储到盘块1中,而一个盘块对应三个连续的扇区,但是test.c文件的大小只占据两个扇区,因此扇区三的空间实际是被浪费掉的。

因此,将一次性从磁盘读取的数据量需要卡在一个均衡值,利用磁盘空间的浪费来换取磁盘读取速度的提升。

再接着使用磁盘:程序输出block

  • 上层应用发出读磁盘请求
static void make_request(){ 
struct requset *req;
req=request+NR_REQUEST;
//根据盘块号计算出扇区号---从b_blocknr<<1可以知道Linux 0.11中盘块号和扇区的映射关系
//0---->0
//1----->2
//2----->4
//可以看到在linux 0.11中一个盘块对应两个连续的扇区
req->sector=bh->b_blocknr<<1; 
//加入请求队列
add_request(major+blk_dev,req); }
void do_hd_request(void){ 
//拿到盘块号
unsigned int block=CURRENT->sector; 
//经过一系列除法和取余数计算,得到对应的CHS
__asm__(“divl %4”:”=a”(block),”=d”(sec):”0”(block),
“1”(0),”r”(hd_info[dev].sect));
__asm__(“divl %4”:”=a”(cyl),”=d”(head):”0”(block),
“1”(0),”r”(hd_info[dev].head));
//通过out指令发送到磁盘对应的端口
hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,...);
... }

多个进程通过队列使用磁盘(第二层抽象)

如果存在多个进程都需要访问磁盘时,此时就需要一个请求队列,将多个访问进程的磁盘访问请求,都加入请求队列中进行阻塞等待。

当磁盘驱动处理完上一次磁盘读写,发出中断时,会再去请求队列中获取对应的磁盘读取请求继续处理,处理完后,再发出中断通知操作系统,然后继续从请求队列中获取相关磁盘读写请求…

FCFS磁盘调度算法

如果是按照先进先出的方式对磁盘读取请求进行处理,那么从上图看来可以知道,该方法实现最大的问题在于磁头的来回移动,而上面已经讲过了,磁盘读写最主要的时间是花在了寻道上,因此我们应该减少磁头的来回移动,尽量在一次移动过程中,顺带将移动范围内的磁盘读取请求处理掉。

SSTF磁盘调度

短寻道优先的策略在于,先将位于当前磁头位置最近的磁盘读取请求处理掉,但是这样会导致部分请求的饥饿问题。

SCAN磁盘调度

SCAN磁盘调度结合了短寻道优先策略和移动过程中顺带处理磁盘读取请求的特点,可以说已经比较完美了,但是该调度策略哈斯存在一些问题:

  • 因为是来回扫描,因此位于中间的请求被处理的优先级还是较高,因此不算完全公平

如果和做电梯进行类比的话,相当于十楼的用户先按了电梯,但是电梯再上升过程中就把其他楼层的用户也都载上了。

并且,如果电梯会先往最近的楼层移动,符合最短寻道原则。

但是,可以看出,中间楼层用户乘坐电梯的机会要更大一些。

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

为什么称该方法为电梯算法呢,看下图:

电梯上升过重中,会直接上升到10楼,因为十楼用户先按的电梯,而电梯下降的时候,会顺便把低楼层的用户都载上。

多个进程共同使用磁盘

生磁盘(raw disk)的使用整理

相关文章