linux0.11进程睡眠sleep_on函数和唤醒wake_up函数分析

内核中的这两个函数主要用于访问资源时的同步操作。高速缓冲区的访问就是其中的一个例子:如果两个进程都要访问同一个缓冲块,那么其中的一个进程就必然睡眠等待,直到该缓冲块被释放才可访问。赵炯博士所著的linux0.11内核完全注释一书中也是对该问题进行详细的讨论,但是我在阅读这部分内容的时候存在一些疑问,在此发表下自己的见解。

先贴出这两个函数

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;
}

void wake_up(struct task_struct **p)
{
	if (p && *p) {
		(**p).state=0;
		*p=NULL;
	}
}
我们以块设备请求项队列的同步操作作为例子来分析。

首先我们已经知道进程在访问块设备的时候是通过一个中间层去调用相应的驱动程序完成块设备的读写操作,这个中间层也即块设备请求项队列request,linux0.11一共为这个队列分配了32项。但是如果系统中有大量块设备访问请求的时候,32项也是不够用的,也就是说再有进程访问块设备的话,该进程就必须睡眠等待,直到请求项队列中出现空项。因此,linux在内核中定义了一个等待请求项的任务结构指针wait_for_request,该指针指向因request资源不足而睡眠等待的任务。

为了更加清晰的说明问题,这里假定request队列已满,并且此时还有2个进程也要请求块设备操作。首先是进程A在请求过程中,发现request队列已满,则该进程就会进入睡眠状态,具体会调用sleep_on(&wait_for_request)函数。在sleep函数中首先断言p值和current的有效性,然后语句tmp=*p会将局部指针tmp指向wait_for_request(首次调用为NULL),语句*p=current会将wait_for_request指向当前进程,最后将当前进行状态置为不可中断状态并schedule,到此进程A将被挂起。注意进程A挂起时的代码位置,下次恢复执行后将从schedule语句的下一条语句继续执行,也即tmp判断语句处。到这里,sleep_on函数使得进程A被挂起之外,还改变两个任务结构指针变量的值,一个是全局的等待任务指针wait_for_request,它现在指向任务A的进程结构体,另一个是进程A的局部指针tmp,它指向为NULL。

分析到这里可能还是不能看出更多的端倪,好继续往下分析。任务A一直因申请不到资源处于等待睡眠状态,而这时又有一个进程B执行块设备请求,由于请求项队列处于满状态,因此进程B同样需要调用sleep_on函数进行睡眠等待,由于任务A已经修改了wait_for_request指针的值(指向进程A),所以进程B在执行到schedul语句的时候,其tmp指针将指向进程A,而wait_for_request指针此时将指向进程B。

到这里很明显可以推出:如果某个进程因无法请求到request资源而进入进程睡眠状态时,全局指针wait_for_request始终指向最后一个进入睡眠的进程,而每个进程中的局部指针tmp将指向上一个睡眠的进程,其中第一个进程的tmp指向NULL。

另外一个非常值得注意的地方是:在sleep_on函数中,进程在调用schedule函数切换到其它进程后,而该函数并未返回,也就是说其局部指针变量tmp仍然保留在该进程的堆栈上,当该进程恢复的时候将从schedule后面一条语句继续执行。

到此进程A和进程B因无法申请到request资源而被挂起,而进程C这时完成了块设备的访问操作,它将调用end_request函数释放其占用的request资源,同时将唤醒因无法申请到request资源的进程,具体将调用wake_up(&wait_for_request)。由于进程B比进程A后进入睡眠,所以wait_for_request指针指向进程B,因此很明显wake_up函数的(**p).state=0语句会将进程B唤醒,除此之外还将wait_for_request置为NULL(初始状态)。我们知道进程B恢复之后将继续执行未完的sleep_on函数,也即判断tmp指针项是否为NULL,前面已分析进程B的tmp指向进程A,因此tmp->state=0语句将会唤醒进程A。也就是说进程B帮助进程A获得了运行的资格,另外由于进程A的tmp指针指向NULL,所以进程A直接从sleep_on函数返回。

到这里也很明显的推出:如果某一个进程调用wake_up(&wait_for_request)函数,那么所有因请求request资源而进入睡眠的进程将反向依次恢复运行(最后一个睡眠的进程先恢复),最后一个睡眠的进程由wait_for_request指针进行恢复,后面睡眠进程由前一个睡眠进程的tmp指针进行恢复,直到恢复第一个睡眠的进程为止。这个过程就像多米诺骨牌一样,一个进程恢复所有进程都恢复,而这个引线就是局部指针tmp。至于恢复后的进程是否能够再次请求到request资源就不再管了,如果还是请求不到,那继续sleep_on就行了。

而赵博士认为每次调用wake_up仅恢复wait_for_request指针指向的最后睡眠的那个任务(在tmp判断前应加上*p=tmp),所以认为linus对于sleep_on函数的实现存在bug,而经过以上的分析,可以看出linus的这种实现是可行的。



你可能感兴趣的:(Linux/kernel)