linux0.11源码分析-sleep_on与wake_up

1,207 阅读3分钟

sleep_on

如果一个资源没有加锁,就表示还没有被使用,一旦加上锁后,在没有解锁前,其他进程想要使用该资源就要释放cpu等待了。注意执行到schedule()方法时,下面的代码就不会执行了,需要等待调度才能运行,CPU会将该进程的状态保存到进程的TSS中,包括当前进程执行到的位置信息,等待下一次调度后,该进程还有从该位置继续执行,也就执行schedule()方法后面的代码。理解这句话是很重要的,要不然,后面讲解是如何唤醒的过程,就不那么容易理解。

kernel/sched.c

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;
}
struct task_struct *current = &(init_task.task);    // 当前任务指针(初始化指向任务0)

current表示当前CPU正在运行的进程,初始化的时候指向进程0。

现在假设有3个进程T1、T2、T3、T4来抢夺资源,T1先抢到,T1将该资源上锁(lock)。

T1还没有使用完该资源,T2也抢到该资源,但是资源已经被上锁,T2进入sleep_on。变量wait存的是一个地址,使用该地址可以找到进程。变量p存的也是一个地址,使用该地址就可以找到变量wait,那么*p就表示变量wait中的地址。

T2进入sleep_on,把在当前资源上等待的进程给tmp,此时资源上还没有等待进程,因为T1是第一个抢到的,抢到后就锁住。T2抢到后发现被锁,T2进入等待,那么此时这个tmp里面保存着NULL地址,,current就表示的是当前进程T2,接下来*p = current就是把变量wait中的值(地址)修改成current,即T2的地址。注意此时T2只能执行到schedule()方法,需要等待唤醒调度后才能继续执行。

T3进入sleep_on,把在当前资源上等待的进程给tmp,此时的wait是有值的,即T2的地址,然后wait又被重新赋值为T3的地址,与T2一样,schedule()执行完后,就等着了。

T4进入sleep_on,把在当前资源上等待的进程给tmp,此时的wait就是T3的地址,然后wait又被重新赋值为T4的地址,与T3一样,schedule()执行完后,也等着了。

wake_up

T1使用完资源后,开始解锁,唤醒等待在该资源上的进程,T4是最后抢到的,资源上的wait就是T4的地址,*p就是最后一个sleep_on的进程,即T4,接着把T4进程的状态修改为可运行状态,然后等待调度。

接下来就是一股骚操作了~😅,现在T4被调度上CPU,开始运行,那么就会继续沿着sleep_onschedule()方法的位置运行,把tmp的状态修改为可运行状态,即T3准备运行。进程会不断循环检资源有没有被锁住。假如T4被唤醒后,又要执行调度把T3唤醒,接着又调度把T2唤醒,现在T1使用完了资源,T2、T3、T4都被唤醒,3个进程又开始公平的抢资源了。

while (bh->b_lock)
	sleep_on(&bh->b_wait);
bh->b_lock=1;

kernel/sched.c

void wake_up(struct task_struct **p)
{
	if (p && *p) {
		(**p).state=0;          // 置为就绪(可运行)状态TASK_RUNNING.
		*p=NULL;
	}
}