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

      进程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都不为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将再次被挂起。

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

Linux内核设计的艺术-多进程操作文件的综合实例,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。