linux0.12中bread函数流程

*************************************************************************

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

linux0.12中bread函数流程_第1张图片

然后该req通过add_request(major+blk_dev,req)插入到请求表中,对应blk_dev[3],

linux0.12中bread函数流程_第2张图片

由于任务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的中断程序中实现),此时的请求表如图:

linux0.12中bread函数流程_第3张图片

然后任务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;
}

 

释放请求项后的请求表如图:

linux0.12中bread函数流程_第4张图片

接着执行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了。

你可能感兴趣的:(c,linux,table,buffer,任务调度,任务)