*************************************************************************
2011/3/8 总结了Linux0.12中的bread函数大致流程,还有些细节,待以后解决
*************************************************************************
假设有四个任务,任务A,任务B,任务C,任务D(不包括任务0),任务A,B,C,D都将在内核态执行bread函数,但是任务A最先执行,任务B其次,接着任务C,而任务D是在任务A执行完bread后才执行bread,并且,任务A和D的dev和block相同,和其余两个任务的block都不相同。
任务A:dev=3,block=7
任务B:dev=3,block=6
任务C:dev=3,block=18
任务D:dev=3,block=7
并且,A任务是第一个插入请求队列的请求
好,现在开始分析。
首先,任务A通过getblk()得到了一个未上锁(如果是get_hash_table直接得到,可能uptodate=1或dirt=1,可以看我上一篇画的getblk流程图) 的缓冲头bh,我们假设,任务A是从free_list中得到的bh,因此,lock和uptodate都为0,接着,任务A进入ll_rw_block(READ,bh)函数,经历make_request(major,READ,bh),在make_request()后,创建了一个request结构:
PS:在make_request()中对bh上了锁,表明要对缓冲块进行操作
lock_buffer(bh);
然后该req通过add_request(major+blk_dev,req)插入到请求表中,对应blk_dev[3],
:
由于任务A是第一个请求,因此,在插入队列后,直接执行(dev->current_request_fn)(),也就是do_hd_request()。
注意,由于假设除ABCD外没有其他任务操作缓冲块,因此,除了时钟中断外没有设备驱动中断!
do_hd_request()会先计算出要从硬盘中读取的位置,磁头号、柱面号和扇区号,然后调用hd_out(...,WIN_READ,&read_init)
在hd_out()中,会向硬盘发送指令,...,outb(cmd,++port);发送完后,一直return(后面的操作交给磁盘驱动器,程序不管了),return到ll_rw_block()下面一句wait_on_buffer(bh)。
static inline void wait_on_buffer(struct buffer_head * bh)
{
cli();
while (bh->b_lock)
sleep_on(&bh->b_wait);
sti();
}
之后任务A会在bh->b_wait队列上睡眠,主动调度到其他任务,直到被唤醒,并且b_lock=0(等待硬盘中断)!
好,下面该任务B出场了,假设此时任务A等待的硬盘中断还没有来,任务B也在经历任务A之前经历的事情,lock_buffer(bh)...当执行到add_request()时,发现,请求队列中已经有了一个请求(任务A的),于是执行Plan B,将自己的请求插入请求列表后return(这个请求会在任务A的中断程序中实现),此时的请求表如图:
然后任务B返回ll_rw_block()下一条语句,wait_on_buffer(bh),或者这个时候任务A的硬盘中断回来了,通过sys_call系统调用进入read_intr()
static void read_intr(void)
{
if (win_result()) { #如果硬盘控制器的状态寄存器表示操作出错
bad_rw_intr(); #增加error++
do_hd_request(); #重新请求硬盘做相应处理,ie.reset
return;
}
port_read(HD_DATA,CURRENT->buffer,256); #最重要的一步!!!从硬盘控制器的HD_DATA端口读一个扇区 #的数据到b_data中
CURRENT->errors = 0;
CURRENT->buffer += 512; #buffer指针加一个扇区
CURRENT->sector++; #增加起始扇区
if (--CURRENT->nr_sectors) { #减去读扇区数,未到0的话说明还有扇区没读,一般是一块设备块,有2个扇区
SET_INTR(&read_intr); #设置中断函数后,return到中断前其他任务,等待下一个中断
return;
}
end_request(1);
do_hd_request();
}
read_intr() return后等待下一个中断,此时任务调度到任务C,恰巧,任务C也进行bread操作,getblk()...执行到make_request()处,发现空闲请求项已经满了(杯具),于是执行sleep_on(&wait_for_request),在wait_for_request对列中睡眠,等待有任务用完后请求项后释放并唤醒他。
等到任务A的下一个中断来了后,再执行read_intr(),这次两个扇区都读完了,最后,执行end_request(1)
extern inline void end_request(int uptodate)
{
DEVICE_OFF(CURRENT->dev);
if (CURRENT->bh) {
CURRENT->bh->b_uptodate = uptodate; #这里设置了uptodate=1,表示缓冲块已经更新
unlock_buffer(CURRENT->bh); #解锁bh(任务A在wait_on_buffer处等待),并且唤醒了任务A!!!
}
if (!uptodate) { #错误处理
printk(DEVICE_NAME " I/O error/n/r");
printk("dev %04x, block %d/n/r",CURRENT->dev,
CURRENT->bh->b_blocknr);
}
wake_up(&CURRENT->waiting); #唤醒req->waiting,这里没有用到
wake_up(&wait_for_request); #唤醒空闲等待请求项的任务(任务C)
CURRENT->dev = -1; #释放请求项(之后调度到任务C后,他就可以用了)
CURRENT = CURRENT->next;
}
释放请求项后的请求表如图:
接着执行do_hd_request(),
do_hd_request()就会去处理之前的任务B请求项,同样会等待两次中断,如果在等待期间任务C也插入了请求项,那么同样,在任务B请求项完成后,唤醒任务B,然后继续处理任务C的请求项(任务ABC全都睡眠在bh->b_wait),如果,此时任务D还没有执行bread,那么当任务C的请求项完成后,end_request(1)将请求表中的请求项清空。
任务A,B,C先后被唤醒,之后执行
if (bh->b_uptodate)
return bh;
此时,bh块处于unlock状态
如果此时任务D执行,其在get_hash_table()阶段就找到了任务A用过的bh,只要判断下bh->uptodate是否为1,就可以直接返回bh了。