进程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); }
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都不为0 sleep_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;
else
req = 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此时设置为0 if (!(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将再次被挂起。
接下来,以上步骤将会重复执行。一方面,只要硬盘执行完一次同步操作,就会释放一个请求项,并将其对应的缓冲块解锁,这些都将导致等待空闲请求项或者等待缓冲块解锁的进程被唤醒;另一方面,被唤醒的进程又不断地引发缓冲区与硬盘之间进行数据交互,从而使这些进程不断地被挂起。