本节分析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类似。 ************************************************************************* 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) 之后任务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)
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)
释放请求项后的请求表如图: 接着执行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了。 |
|