linux0.11源码分析-添加设备请求

525 阅读5分钟

blk_dev

blk_dev先定义了系统可以使用的一些块设备,数量也就是7个。该数组使用主设备号作为索引,在代码中就会看到blk_dev[major]这样找到 blk_dev_struct结构的,实际内容将在各种块设备驱动程序初始化时填入。

kernel/blk_drv/ll_rw_blk.c

struct blk_dev_struct blk_dev[NR_BLK_DEV] = {
	{ NULL, NULL },		/* no_dev */
	{ NULL, NULL },		/* dev mem  虚拟盘 */
	{ NULL, NULL },		/* dev fd 软盘*/
	{ NULL, NULL },		/* dev hd 磁盘*/
	{ NULL, NULL },		/* dev ttyx  */
	{ NULL, NULL },		/* dev tty */
	{ NULL, NULL }		/* dev lp lp打印机设备*/
};

ll_rw_block

在读写磁盘时,先使用open方法,触发中断,中断程序选择使用sys_open方法,往下就会走到ll_rw_block方法。验证设备号后就交给make_request方法。

kernel/blk_drv/ll_rw_blk.c

void ll_rw_block(int rw, struct buffer_head * bh)
{
	unsigned int major;

	//如果主设备号大于等于7或者没有挂载请求的说明是有问题的
	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);
}

include/linux/fs.h

设备号=主设备号*256 +次设备号(也即dev_no = (major<<8) + minor), 其中主设备号:1-内存,2-磁盘,3-硬盘,4-ttyx,5-tty,6-并行口,7-非命名管道。

#define MAJOR(a) (((unsigned)(a))>>8)  //设备号的主版本
#define MINOR(a) ((a)&0xff)

kernel/blk_drv/blk.h

#define NR_BLK_DEV	7 //支持的块设备的数量

struct blk_dev_struct {
	void (*request_fn)(void);  //执行的请求,磁盘就是 do_hd_request,虚拟盘就是do_rd_request,软盘就是do_fd_request
	struct request * current_request;
};

struct request {
	int dev;		/* -1 if no request */
	int cmd;		/* READ or WRITE */
	int errors;
	unsigned long sector;
	unsigned long nr_sectors;
	char * buffer;
	struct task_struct * waiting;
	struct buffer_head * bh;
	struct request * next;
};

make_request

WRITEA/READA 是一种特殊情况 - 它们并并非必要,所以如果缓冲区已经上锁,我们就不管它而退出,否则的话就执行一般的读/写操作。这里'READ'和'WRITE'后面的'A'字符代表英文单词Ahead,表示提前预读/写数据块的意思。对于命令是READA/WRITEA 的情况,当指定的缓冲区正在使用,已被上锁时,就放弃预读/写请求。否则就作为普通的READ/WRITE 命令进行操作。

如果命令是写操作,但是缓冲区中的数据并不“脏”,没有必要往磁盘中写了,直接返回。如果命令是读操作, 但是缓冲区中的数据还是有效的,也就是说缓冲区的数据与进程中的一样,也没必要读,返回。

在读操作优先于写操作,读操作可以沾占满整个队列,但是写操作只能占据整个队列的2/3,写操作不能占满队列,如果前面2/3已经被读操作占据,那么还有后面的1/3给读操作。

kernel/blk_drv/ll_rw_blk.c

static void make_request(int major,int rw, struct buffer_head * bh)
{
	struct request * req;
	int rw_ahead;
    
	if ((rw_ahead = (rw == READA || rw == WRITEA))) {
		if (bh->b_lock) //READA或者WRITEA命令下如果缓冲区已经上锁,就直接退出
			return;
		if (rw == READA)
			rw = READ;
		else
			rw = WRITE;
	}
    
	if (rw!=READ && rw!=WRITE)
		panic("Bad block dev command, must be R/W/RA/WA");
        
      //锁定缓冲区,如果缓冲区已经上锁,则当前任务(进程)就会睡眠,直到被明确地唤醒。   
	lock_buffer(bh);
	if ((rw == WRITE && !bh->b_dirt) || (rw == READ && bh->b_uptodate)) {
		unlock_buffer(bh);
		return;
	}
repeat:
	if (rw == READ)
		req = request+NR_REQUEST;  //先把req指针指向request数组最后一项
	else
		req = request+((NR_REQUEST*2)/3);

	while (--req >= request)  // 从后向前搜索,最后面的可是读操作,所以读优先
		if (req->dev<0)
			break;

	if (req < request) {
		if (rw_ahead) {
			unlock_buffer(bh);
			return;
		}
		sleep_on(&wait_for_request); //没有一个空闲项,睡眠后再次重复
		goto repeat;
	}
    //找到空闲项了,赋值
	req->dev = bh->b_dev;
	req->cmd = rw;
	req->errors=0;
	req->sector = bh->b_blocknr<<1; // 1块等于两个扇区所以乘以2
	req->nr_sectors = 2; // 读取2个扇区即1块 
	req->buffer = bh->b_data;  //缓冲区的数据
	req->waiting = NULL;
	req->bh = bh;
	req->next = NULL;
	add_request(major+blk_dev,req); // major+blk_dev就是blk_dev[major]
}

lock_buffer

static inline void lock_buffer(struct buffer_head * bh)
{
	cli(); //关闭进程自己的中断
	while (bh->b_lock) //如果已经被锁了,就去等待
		sleep_on(&bh->b_wait);
	bh->b_lock=1; //上锁
	sti(); //打开进程自己的中断
}

unlock_buffer

static inline void unlock_buffer(struct buffer_head * bh)
{
	if (!bh->b_lock)
		printk(DEVICE_NAME ": free buffer being unlocked\n");
	bh->b_lock=0;
	wake_up(&bh->b_wait); //唤醒在该资源上等待的进程
}

add_request

如果当前还没有请求,就直接把当前请求给req,执行request_fn,对于硬盘是do_hd_request();如果已经有请求了,就根据电梯算法插入到队列中。

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;    //清除b_dirt标记
        
        // 如果dev 的当前请求(current_request)子段为空,则表示目前该设备没有请求项  
        //本次是第1个请求项,因此可将块设备当前请求指针直接指向该请求项,并立刻执行相应设备的请求函数。
	if (!(tmp = dev->current_request)) {
		dev->current_request = req;
		sti();  //打开中断,进程相应中断
		(dev->request_fn)();  // 执行设备请求函数,对于硬盘是do_hd_request()
		return;
	}
    
        //如果目前该设备已经有请求项在等待,则首先利用电梯算法搜索最佳插入位置,然后将当前请求插入
        //到请求链表中。电梯算法的作用是让磁盘磁头的移动距离最小,从而改善硬盘访问时间
	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();
}

do_hd_request的执行过程看这里

IN_ORDER

#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))))

转换以下就是下面的代码,READ 是0 , WRITE是1,代码的含义是先比较命令,读命令优先于写命令;如果命令相同,则比较设备号,设备号小的设备优先于设备号大的;如果设备号也相同,则比较扇区号,先处理扇区号小的扇区,尽量让磁头层层递进。

bool inorder(request &s1,request &s2)  
{  
    if (s1.cmd<s2.cmd)  
    {  
        return true; //读命令优先
    }  
    else if ( s1.cmd == s2.cmd )  
    {  
        if (s1.dev < s2.dev)  
        {  
            return true;  // 设备号小的设备优先于设备号大的
        }  
        else if ( s1.dev == s2.dev )  
        {  
            if (s1.sector<s2.sector)  
            {  
                return true; // 读写扇区一层层递进,防止磁头来回读写扇区 
            }  
            return false;
        }  
        return false;
    }  
    return false;
}