如果要读磁盘,那么基本步骤如下:
如果要写磁盘,步骤和上面类似:
看了上面的基本过程后,相信大家可以理解了,如果我们要操作磁盘读写的话,就是告诉磁盘控制器关于柱面、磁头、扇区、缓 存位置,然后是读还是写,剩下的由磁盘控制器完成。
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最近要映射到扇区上去,因此这要求扇区尽量是在同一个扇区上连续排列的。
可以看到下图,第一个柱面的某个磁道上,分步了0~6号扇区,当需要放置第7个扇区的时候,为了避免磁臂的移动,需要将第7个扇区放在同一个磁道,但是不同柱面上。
每次磁盘访问的主要时间都花费在了磁臂寻道和磁头旋转上,而数据传输的耗时,确可以忽略不计。
因此,我们是否可以在一次磁盘读取过程中,连续读取多个扇区的数据,而不是每次都只是一个扇区,这样就提供磁盘的读写速度,并且由于文件通常会采用连续存放,因此,对于大文件来说,这样做可以更加明显感觉到读写速度的提升。
因此,操作会将连续的几个扇区看做是一个盘块,上层应用发出一个盘块号后,操作系统经过计算就能知道需要连续读取多少个扇区了。
相当于操作系统读取磁盘的最小单位为一个盘块,而一个盘块可能由多个扇区组成,类比内存的分页机制,就可以知道,这样做会造成磁盘空间的浪费,
例如下面的test.c文件,分配存储到盘块1中,而一个盘块对应三个连续的扇区,但是test.c文件的大小只占据两个扇区,因此扇区三的空间实际是被浪费掉的。
因此,将一次性从磁盘读取的数据量需要卡在一个均衡值,利用磁盘空间的浪费来换取磁盘读取速度的提升。
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,...);
... }
如果存在多个进程都需要访问磁盘时,此时就需要一个请求队列,将多个访问进程的磁盘访问请求,都加入请求队列中进行阻塞等待。
当磁盘驱动处理完上一次磁盘读写,发出中断时,会再去请求队列中获取对应的磁盘读取请求继续处理,处理完后,再发出中断通知操作系统,然后继续从请求队列中获取相关磁盘读写请求…
如果是按照先进先出的方式对磁盘读取请求进行处理,那么从上图看来可以知道,该方法实现最大的问题在于磁头的来回移动,而上面已经讲过了,磁盘读写最主要的时间是花在了寻道上,因此我们应该减少磁头的来回移动,尽量在一次移动过程中,顺带将移动范围内的磁盘读取请求处理掉。
短寻道优先的策略在于,先将位于当前磁头位置最近的磁盘读取请求处理掉,但是这样会导致部分请求的饥饿问题。
SCAN磁盘调度结合了短寻道优先策略和移动过程中顺带处理磁盘读取请求的特点,可以说已经比较完美了,但是该调度策略哈斯存在一些问题:
如果和做电梯进行类比的话,相当于十楼的用户先按了电梯,但是电梯再上升过程中就把其他楼层的用户也都载上了。
并且,如果电梯会先往最近的楼层移动,符合最短寻道原则。
但是,可以看出,中间楼层用户乘坐电梯的机会要更大一些。
为什么称该方法为电梯算法呢,看下图:
电梯上升过重中,会直接上升到10楼,因为十楼用户先按的电梯,而电梯下降的时候,会顺便把低楼层的用户都载上。