linux0.12中bread函数流程

    技术2022-05-19  20

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

    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 x, 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了。


    最新回复(0)