Linux内核设计的艺术-多进程操作文件的综合实例

来源:互联网 发布:量化交易python和r 编辑:程序博客网 时间:2024/05/17 08:23

      进程A是一个写盘进程,目的是往hello1.txt文件中写入str[]中的字符“ABCED”,代码如下:

void FunA();void main(){...FunA();...}void FunA(){char str1[]="ABCDE";int i,j;int fd = open("/mnt/user/user1/user2/hello1.txt",O_RDWR,0644);for(i=0;i<100000;i++){write(fd,str1,strlen(str1));}close(fd);}

        进程B是一个写盘进程,目的是往hello2.txt文件中写入str[]中的字符“ABCED”,代码如下:

void FunB();void main(){...FunB();...}void FunB(){char str1[]="ABCDE";int i,j;int fd = open("/mnt/user/user1/user2/hello2.txt",O_RDWR,0644);for(i=0;i<100000;i++){write(fd,str1,strlen(str1));}close(fd);}


        进程C是一个读盘进程,目的是从hello3.txt文件中读20000字节到buffer中,代码如下:

void FunC();void main(){...FunC();...}void FunC(){char buffer[20000];int i,j;int fd = open("/mnt/user/user1/user2/hello3.txt",O_RDWR,0644);read(fd,buffer,sizeof(buffer));close(fd);}
          

        首先进程A开始执行,刚开始的流程是申请缓冲块,往缓冲块写入数据。后来缓冲区中已经没有空闲且不脏的缓冲块,只有空闲且脏的缓冲块。这就意味着,接下来要强行将缓冲区中的数据同步到硬盘,以便在缓冲区中空出更多的空间。请看getblk函数。

        代码路径:fs/buffer.c

#define BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock)struct buffer_head * getblk(int dev,int block){struct buffer_head * tmp, * bh;repeat:if ((bh = get_hash_table(dev,block)))return bh;tmp = free_list;do {if (tmp->b_count)continue;if (!bh || BADNESS(tmp)<BADNESS(bh)) {bh = tmp;if (!BADNESS(tmp))break;}/* and repeat until we find something good */} while ((tmp = tmp->b_next_free) != free_list);if (!bh) {//bh为空,说明所有的缓冲块b_count都不为0sleep_on(&buffer_wait);//等待goto repeat;//重新选择}wait_on_buffer(bh);//找到了b_count为0的缓冲块,如果b_lock为1,则等待if (bh->b_count)goto repeat;while (bh->b_dirt) {//b_dirt为1,说明目前只有空闲且b_dirt为1的缓冲块了sync_dev(bh->b_dev);//立即同步wait_on_buffer(bh);if (bh->b_count)goto repeat;}        ...}
       代码路径:fs/buffer.c

int sync_dev(int dev){int i;struct buffer_head * bh;bh = start_buffer;for (i=0 ; i<NR_BUFFERS ; i++,bh++) {if (bh->b_dev != dev)continue;wait_on_buffer(bh);if (bh->b_dev == dev && bh->b_dirt)//只要设备号符合是脏的,就同步ll_rw_block(WRITE,bh);}...}
       代码路径:kernel/blk_drv/ll_rw_block.c

void ll_rw_block(int rw, struct buffer_head * bh){unsigned int major;if ((major=MAJOR(bh->b_dev)) >= NR_BLK_DEV ||!(blk_dev[major].request_fn)) {printk("Trying to read nonexistent block-device\n\r");return;}make_request(major,rw,bh);}
       
static void make_request(int major,int rw, struct buffer_head * bh){...lock_buffer(bh);...if (rw == READ)req = request+NR_REQUEST;elsereq = request+((NR_REQUEST*2)/3);while (--req >= request)//找到空闲请求项if (req->dev<0)break;if (req < request) {//没有找到...sleep_on(&wait_for_request);//进程A就成为了等待空闲请求项的进程了goto repeat;}        ...add_request(major+blk_dev,req);}


static void add_request(struct blk_dev_struct * dev, struct request * req){struct request * tmp;req->next = NULL;cli();if (req->bh)req->bh->b_dirt = 0;//b_dirt此时设置为0if (!(tmp = dev->current_request)) {dev->current_request = req;sti();(dev->request_fn)();return;}for ( ; tmp->next ; tmp=tmp->next)if ((IN_ORDER(tmp,req) ||     !IN_ORDER(tmp,tmp->next)) &&    IN_ORDER(req,tmp->next))break;req->next=tmp->next;tmp->next=req;sti();}
        刚开始申请空闲请求项来同步缓冲块(估计同步缓冲块add_request也要形成链表,以后再讨论),后来由于没有空闲的请求项了,调用sleep_on,进程A就成为了等待空闲请求项的进程了。


        然后进程B开始执行,它也是一个写盘进程,同样的步骤,这回getblk申请到了b_count为0,b_lock为1的缓冲块,在getblk中的wait_on_buffer时后(如上面代码所示),进程B将会被挂起。此时系统和硬盘还在不断处理请求项。


        进程C开始执行,它是一个读盘进程,同样的步骤,调用getblk申请的是和B同样的缓冲块,同样调用getblk中的wait_on_buffer时后进程C也被挂起。wait_on_buffer的原理(两个进程同时等待一个缓冲块)已经在上一盘博客中讲过了。http://blog.csdn.net/jltxgcy/article/details/21866169。

        此时,进程A处在等待空闲请求项的等待队列,而进程B和C处在等待同一个缓冲块解锁的队列。

 

        进程0实现系统怠速,系统一段时间后,硬盘完成了请求项交付的同步工作,产生硬盘中断,中断服务程序开始执行,代码如下:

        代码路径:kernel/blk_dev/blk.h

static inline void end_request(int uptodate){DEVICE_OFF(CURRENT->dev);if (CURRENT->bh) {CURRENT->bh->b_uptodate = uptodate;unlock_buffer(CURRENT->bh);}...wake_up(&wait_for_request);//唤醒进程A,即state为就绪态}
static inline void unlock_buffer(struct buffer_head * bh){if (!bh->b_lock)printk(DEVICE_NAME ": free buffer being unlocked\n");bh->b_lock=0;wake_up(&bh->b_wait);//唤醒进程C,即state为就绪态}

       此时进程C和进程A均处于就绪态,由于进程C的时间片多于进程0的时间片,所以由进程0切换到进程C执行。

       进程C是getblk函数中的wait_on_buffer函数切换走的,开始执行的第一件事,是将进程B唤醒,参考上篇博客。

       然后getblk函数返回,继续调用bread函数中ll_rw_block,将缓冲块上锁,最后调用到add_request函数,如下:

       代码路径:kernel/blk_dev/ll_rw_blk.c

static void add_request(struct blk_dev_struct * dev, struct request * req){...for ( ; tmp->next ; tmp=tmp->next)//设备忙,将请求项插入队列if ((IN_ORDER(tmp,req) ||     !IN_ORDER(tmp,tmp->next)) &&    IN_ORDER(req,tmp->next))break;req->next=tmp->next;//next用来组建请求项队列tmp->next=req;sti();}
       请求项设置完毕,并不代表马上就能处理这个请求项。此时硬盘正忙着同步处理其他请求项,现在只能将“读盘”请求项插入请求队列中。

       然后接着执行bread函数中的wait_on_buffer,C进程又一次被挂起。

       进程B从getblk中wait_on_buffer函数继续执行,代码如下:

static inline void wait_on_buffer(struct buffer_head * bh){cli();while (bh->b_lock)//已经上锁了sleep_on(&bh->b_wait);sti();}

void sleep_on(struct task_struct **p){struct task_struct *tmp;if (!p)return;if (current == &(init_task.task))panic("task[0] trying to sleep");tmp = *p;*p = current;current->state = TASK_UNINTERRUPTIBLE;schedule();//切换进程if (tmp)tmp->state=0;}
        sleep_on函数返回后,bh->b_lock仍然为1,所以又一次切换,此时只有进程A为就绪态,所以切换到进程A。
       注解:进程C挂起后,进程B的时间片显然要比进程A要多,所以要求切换到进程B,系统早已经为进程B申请了缓冲块,当时由于这个缓冲块是加锁的,所以进程B要挂起。现在这个缓冲块仍然是加锁的,所以进程B将再次挂起。


        进程A是在make_request函数中切换走的,接着往下执行go repeat,现在系统已经有空闲的请求项了用于写盘了,所以系统将此请求项与即将要同步的缓冲块绑定,并插入请求队列中。之后又没有空闲的请求项了,进程A再次调用sync_dev中ll_rw_block,进程A将再次被挂起。

        接下来,以上步骤将会重复执行。一方面,只要硬盘执行完一次同步操作,就会释放一个请求项,并将其对应的缓冲块解锁,这些都将导致等待空闲请求项或者等待缓冲块解锁的进程被唤醒;另一方面,被唤醒的进程又不断地引发缓冲区与硬盘之间进行数据交互,从而使这些进程不断地被挂起。

0 0
原创粉丝点击