分享

从源码学习Linux linux0.12中bread函数流程

 linuxhk 2012-04-27

  本节分析linux0.11的文件操作。主要包括文件的打开,读,写,关闭。Linux0.11支持4种类型的文件:字符设备文件,块设备文件,常规文件,管道。它们用统一的接口sys_open,sys_read,sys_write,sys_close来操作。

    首先来分析sys_open(fs/open.c),它首先在当前进程的打开文件表struct file *current->flip[32]中找到一个空项fd,并且在内核文件表struct file file_table[64]中找到一个空项struct file f,让current->flip[fd]=&f。然后调用open_namei(fs/namei.c)根据文件名找到该文件的inode(如果flag中有CREATE属性,则表明当找不到该文件,会创建一个新的inode和一个新的目录项),然后填充f的内容。

struct file {

unsigned short f_mode;                                          //文件类型及RWX属性等,与inode->i_mode相同

unsigned short f_flags;                                           //文件打开和控制的标志,R /W/A等

unsigned short f_count;                                          //文件引用计数

struct m_inode * f_inode;                                        //文件的inode

off_t f_pos;                                                             //文件当前读写位置

};

    文件关闭sys_close时,要同时清空current->flip[fd]和current->close_on_exec位图,以及file_table[]的项,并释放文件的inode。

    文件读sys_read(fs/read_write.c)时,它根据文件的不同类型,执行不同的操作。

    对于管道文件,它首先检验f->f_mode是否是读管道模式,如果是,则执行read_pipe(fs/pipe.c)。对管道文件的inode来说,inode->i_zone[0]存放的是管道缓存的当前头指针,node->i_zone[1]存放的是管道缓存的当前尾指针,inode->i_size存放的是管道缓存的地址。管道从头读,从尾写。

   对于字符设备文件,它调用rw_char(fs/char_dev.c)进行处理。

   对于块设备文件,它调用block_read(fs/block_dev.c),其主要调用breada函数。

   对于常规文件,它调用file_read(fs/file_dev.c)。

   对于sys_write,其处理与sys_read类似。

linux0.12中bread函数流程

 86人阅读 评论(1) 收藏 举报

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

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

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多