加锁和解锁的基本思想是,当某个进程进入临界区,它将持有一个某种类型的锁(UNIX里一般来说是semaphore,Linux里一般是信号量和原子量或者spinlock)。
void wakeup(地址(事件))
{
禁用所有的中断;
根据地址(事件)查找睡眠进程队列;
for(每个在该事件上睡眠的进程)
{
将该进程从哈希队列中移出;
设置状态为就绪;
将该进程放入调度链表中;
清除进程表中的睡眠地址(事件);
if(进程不在内存中)
{
唤醒swapper进程;
}
else if(唤醒的进程更适合运行)
{
设置调度标志;
}
}
恢复中断;
} |
在wakeup调用之后,被唤醒的进程并不是马上投入运行,而是让其适合运行,等到下次调度该进程才有机会运行(也可能不会运行)。
代码示例:
由于没能找到UNIX的源码,因此用Linux的源代码替代。上述伪代码已经是比较简单的处理。Linux 0.01则更简单。
//Linux 0.01的实现:
//不可中断睡眠
void sleep_on(struct task_struct **p)
{
struct task_struct *tmp;
if (!p)
return;
if (current == &(init_task.task)) //current宏用来获取当前运行进程的task_struct
panic("task[0] trying to sleep");
tmp = *p; //将已经在睡眠的进程p2保存到tmp
*p = current; //将当前进程p1保存到睡眠进程队列中(实际上只有一个)
current->state = TASK_UNINTERRUPTIBLE; //p1状态设置为不可中断的睡眠
schedule(); //上下文切换,执行其他进程
//p1被唤醒后回到此处
if (tmp)
tmp->state=0; //将p2的状态设置为运行,等待调度
}
//可中断睡眠
void interruptible_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; //正在等待的进程p2保存到tmp
*p=current; //将当前进程p1保存到睡眠进程队列中
repeat: current->state = TASK_INTERRUPTIBLE; //将p1状态设置为可中断睡眠
schedule(); //上下文切换
if (*p && *p != current) { //p2睡眠被中断
(**p).state=0;//p1设置为运行态
goto repeat; //回到repeat,继续让p2睡眠
}
*p=NULL;
if (tmp)
tmp->state=0; //将p2的状态设置为运行态,等待调度
} |
这两个函数比较难以理解,主要是在最后两条语句。在schedule()之前,切换的是当前进程的上下文,但是,切换回来之后,却是将原先正在睡眠的进程置为就绪态。在执行schedule()之前,各指针如下图所示(不好意思,不会粘贴图片):
---
| p |
---
||
\/
---- Step 3 ---------
| *p |--------->| current |
---- ---------
|
X Step 1
|
\/
---------------- Step 2 -----
| Wait Process |<--------| tmp |
---------------- ----- |