操作系统之生磁盘的使用二

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(…)完成端口写

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值