C-SCAN磁盘调度(电梯算法)
上节我们已经知道了通过传递block这个参数就可以在磁盘中指定的一些扇区进行读写操作。现在我们考虑一种情况,一个操作系统中会有多个进程使用到磁盘,如何保证这些进程使用磁盘既准确又快速?那么就要引出我们的磁盘调度算法了,这里我们介绍操作系统中正在使用的调度算法,就是C-SCAN磁盘调度算法,也被称作电梯算法。
我们都知道电梯在运行的时候有上升和下降2种逻辑情况。当电梯上升的时候,本次上升的终点就是最高的请求楼层。当电梯下降的时候,本次下降的终点就是最低的请求楼层。
C-SCAN磁盘调度算法也是这样的原理,下面我们来分析有关这个算法的代码:
// linux-0.11/kernel/blk_drv/ll_rw_blk.c
static void add_request(struct blk_dev_struct * dev, struct request * req)
{
struct request * tmp;
req->next = NULL;
cli();
if (req->bh)
req->bh->b_dirt = 0;
if (!(tmp = dev->current_request)) {
dev->current_request = req;
sti();
(dev->request_fn)();
return;
}
// 调度算法的核心代码,分析下面代码,我们先要知道
// IN_ORDER是什么意思?可以看下文IN_ORDER的介绍
// 就可以知道是比较tmp与req中柱面号大小的
//下面从这段代码可以看出,当符合这两种情况时就跳出循环
// 并将req插入tmp和next之间。一种情况是当tmp的柱面号
// 小于req的柱面号,且req小于next的柱面号,这种其实
// 就相当于电梯上升时候的情况;还有一种情况是当tmp的柱面号
// 小于next的柱面号,且req小于next的柱面号,这种其实
// 就相当于电梯下降时候的情况。不管这两种任何一种情况,
// 下一步磁盘读写都会进入req这个对象上,否则就按照原有的
// 队列进行磁盘读写。这样的话就能更高效的使用磁盘
for ( ; tmp->next ; tmp=tmp->next)
if ((IN_ORDER(tmp,req) ||
!IN_ORDER(tmp,tmp->next)) &&
IN_ORDER(req,tmp->next))
break;
req->next=tmp->next;
tmp->next=req;
sti();
}
IN_ORDER的代码如下:
// 核心思想是比较s1与s2中的sector大小
// 其实就是比较s1与s2中的柱面号的大小
// 因为柱面的寻找是耗时最长的,所以要保证
// 寻找柱面也即寻道的时间不能太长,就要在
// 寻道上面做优化处理
#define IN_ORDER(s1,s2) \
((s1)->cmd<(s2)->cmd || ((s1)->cmd==(s2)->cmd && \
((s1)->dev < (s2)->dev || ((s1)->dev == (s2)->dev && \
(s1)->sector < (s2)->sector))))
生磁盘的使用分析
得到盘块号
首先进程要得到一个盘块号,然后才能对磁盘进行读写。怎样得到盘块号呢,举例分析下面的代码
int do_execve(unsigned long * eip,long tmp,char * filename,
char ** argv, char ** envp)
{
...
// 这里的关键代码是bread这个读磁盘的接口,我们发现函数
// bread的第二个参数是某个文件的设备号
if (!(bh = bread(inode->i_dev,inode->i_zone[0]))) {
retval = -EACCES;
goto exec_error2;
}
...
}
struct buffer_head * bread(int dev,int block)
{
struct buffer_head * bh;
// 这句代码就是获取携带磁盘块的指针,也就是获取bh
if (!(bh=getblk(dev,block)))
panic("bread: getblk returned NULL\n");
if (bh->b_uptodate)
return bh;
// 读写磁盘
ll_rw_block(READ,bh);
// 唤醒下一个磁盘读写请求队列,如果有的话。
wait_on_buffer(bh);
if (bh->b_uptodate)
return bh;
brelse(bh);
return NULL;
}
从上面看出进程获取block是通过文件系统来获取的。下面当获取到block后,我们会进入ll_rw_block这个函数,我们来分析一下它。
分析ll_rw_block
void ll_rw_block(int rw, struct buffer_head * bh)
{
unsigned int major;
if ((major=MAJOR(bh->b_dev)) >= NR_BLK_DEV ||
!(blk_dev[major].request_fn)) {
printk("Trying to read nonexistent block-device\n\r");
return;
}
// 这里会进入这句函数,来请求磁盘的读写
make_request(major,rw,bh);
}
分析make_request
static void make_request(int major,int rw, struct buffer_head * bh)
{
/* fill up the request-info, and add it to the queue */
req->dev = bh->b_dev;
req->cmd = rw;
req->errors=0;
req->sector = bh->b_blocknr<<1;
req->nr_sectors = 2;
req->buffer = bh->b_data;
req->waiting = NULL;
req->bh = bh;
req->next = NULL;
// 这个时候将解析好的req加入到磁盘读写请求队列中
add_request(major+blk_dev,req);
}
从上面可以看出我们加完req后,就唤醒另一个磁盘请求队列进程,然后睡眠自己。那么磁盘控制器是如何访问这些队列来读写磁盘呢?当然是通过磁盘中断。
hd_interrupt:
pushl %eax
pushl %ecx
pushl %edx
push %ds
push %es
push %fs
movl $0x10,%eax // 进入内核空间
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax // 重置fs
mov %ax,%fs
movb $0x20,%al
outb %al,$0xA0 # EOI to interrupt controller #1
jmp 1f # give port chance to breathe
1: jmp 1f
1: xorl %edx,%edx
xchgl do_hd,%edx
testl %edx,%edx
jne 1f
movl $unexpected_hd_interrupt,%edx
1: outb %al,$0x20
// 中断处理函数do_hd入口
call *%edx # "interesting" way of handling intr.
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret
从上面可以看出执行do_hd这个函数指针,这个函数指针又会指向谁呢?从下面代码可以看出可以执行到read_intr。其实吧,这个do_hd可以指向磁盘读写的中断处理函数,下面我们来分析一下读的处理函数。
分析read_intr
static void read_intr(void)
{
// 获取上次读磁盘的状态,看看有没有错误之类的
if (win_result()) {
bad_rw_intr();
do_hd_request();
return;
}
// 这个就是读的核心代码,向外设的HD_DATA数据端口读取数据
// 每次读取一个扇区512个字节的信息
port_read(HD_DATA,CURRENT->buffer,256);
CURRENT->errors = 0;
CURRENT->buffer += 512;
CURRENT->sector++;
// 如果没读完,那就继续读,知道读完为止
if (--CURRENT->nr_sectors) {
do_hd = &read_intr;
return;
}
// 读完后,唤醒另一个等待进程啦
end_request(1);
// 另一个等待进程继续做磁盘读写请求处理
do_hd_request();
}
到这里我们发现一个进程的磁盘请求读写已经完成,后面的进程请求以此类推。
总结
(1) 进程“得到盘块号”,算出扇区号(sector)
(2) 用扇区号make req,用电梯算法add_request
(3) 进程sleep_on static void read_intr(void)
(4) 磁盘中断处理
(5) do_hd_request算出cyl,head,sector
(6) hd_out调用outp(…)完成端口写