最近在做哈工大操作系统实验时对sleep_on函数是如何构造出任务等待队列以及队列中的任务是如何被重新激活并调度的比较感兴趣,查阅了很多资料后总结如下:
sleep_on()函数将进程由运行态转移至睡眠态,即当一个进程调用sleep()函数,该进程会由运行态转移至睡眠态,该函数主要涉及三个指针操作:*p,tmp,和current,*p是等待队列头指针,存放着原等待任务的地址,tmp是函数堆栈上建立的临时指针,当一个进程调用sleep_on()函数时,*p会指向该进程,即该进程加入了等待队列,而tmp会指向原等待队列头指针,以下图举例,当task1调用sleep_on(),因为原等待队列为空,所以tmp为空指针,*p指向task1,即task1加入等待队列,,当task2调用sleep_on(),tmp指向task1(即原等待队列头部),*p指向task2,即task2加入等待队列,当task3调用sleep_on(),tmp指向task2(即原等待队列头部),*p指向task3,即task3加入等待队列。
void sleep_on(struct task_struct **p)
{
struct task_struct *tmp;
// ……
tmp = *p;
// 仔细阅读,实际上是将 current 插入“等待队列”头部,tmp 是原来的头部
*p = current;
// 切换到睡眠态
current->state = TASK_UNINTERRUPTIBLE;
// 让出 CPU去执行别的进程
schedule();
// 唤醒队列中的上一个(tmp)睡眠进程。0 换作 TASK_RUNNING 更好
// 在记录进程被唤醒时一定要考虑到这种情况,实验者一定要注意!!!
if (tmp)
tmp->state=0;
}
wake_up函数常与sleep_on函数配合使用,作用就是将任务队列指针指向的第一个任务状态转为就绪态,并将任务队列指针置为空,以上图sleep_on函数构成的隐式队列为例,当调用wake_up(&buffer_wait),当前任务task3的状态会被置为就绪态,并将buffer_wait队列指针置为空,此时等待队列已经不存在了,但是队列中的任务并非一次性全部被激活,而是按照后进先出的顺序随着任务被重新调度依次被前一个任务激活,因此当task3被重新调度时,会执行schedule()语句的下一句,即将tmp指针指向的任务状态置为就绪态,即激活task3的上一个任务task2,当task2被重新调度时,也会激活它的上一个任务task1,但当任务被激活时并不能保证它一定会被调度,如果任务不能被调度就只能被执行sleep_on函数继续休眠了,更详细的例子可以看下面的参考文章:linux0.11进程睡眠sleep_on函数和唤醒wake_up函数分析。
void wake_up(struct task_struct **p)
{
if (p && *p) {
(**p).state=0;
*p=NULL;
}
}
参考文档 学习linux0.11-schedule函数
参考文档 linux0.11进程调度(运行态\就绪态\睡眠态之间的转换)
参考文档 操作系统实验3:进程运行轨迹的跟踪与统计
参考文档 linux0.11进程睡眠sleep_on函数和唤醒wake_up函数分析
[参考文档] linux内核完全注释