leevis.com
leevis.com copied to clipboard
Linux-0.11内核睡眠和唤醒操作以及多个隐式的等待队列
概述
内核代码申请某个资源而被其他任务占用时,就会把自己设置为不能被打断的睡眠状态(TASK_UNINTERRUPTIBLE),直到占有该资源的任务主动释放,释放后需要唤醒因为等待该资源而睡眠的任务。 而资源少任务又多,可能存在多个任务等待资源而睡眠,因此就需要一个等待队列保存这些任务。
代码分析
我们用高速缓冲区 buffer.c 中的代码举例。 高速缓冲区是在内存中开辟了一块内存,作为块设备也就是磁盘 和 内核程序的一个桥梁,也就是说 内核读磁盘一定要经过高速缓冲,而高速缓冲作为一个缓冲一定比磁盘小,所以就有可能存在所有缓冲块都被占用而运行的任务分不到的情况。此时,就需要调用内核函数sleep_on
把自己设置为不可中断的睡眠状态让出cpu,让其他任务执行。
buffer.c 文件
// 定义了一个全局静态的buffer等待队列头指针,注意只是一个头指针,指向的是任务结构体。
// 全局变量本来就是在代码段,所以static修饰的主要作用就是本文件可见。
static struct task_struct * buffer_wait = NULL;
// 获取一个缓冲块
#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 {
......
/* and repeat until we find something good */
} while ((tmp = tmp->b_next_free) != free_list);
if (!bh) {
// 缓冲区中的缓冲块都在使用,需要睡眠本程序让出cpu。
// buffer_wait 就是一个指针,指向最近被加入等待队列的进程。
sleep_on(&buffer_wait);
goto repeat;
}
......
return bh;
}
// 释放一个缓冲块
void brelse(struct buffer_head * buf)
{
if (!buf)
return;
......
// 唤醒等待队列上的任务
wake_up(&buffer_wait);
}
sched.c文件
// 睡眠本任务,让出cpu。
void sleep_on(struct task_struct **p)
{
// 局部变量定义在内核栈上,多个栈的tmp变量形成一个隐形的队列。
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;
// 让出cpu挂起该任务,调度执行其他任务
schedule();
// 被唤醒后再次被调度后从此处开始执行。
// 任务队列不为空,就是说还有不可中断的睡眠任务,则继续唤醒。
if (tmp)
tmp->state=0;
}
// 唤醒等待队列上的任务
void wake_up(struct task_struct **p)
{
// 等待队列不为空,则唤醒对列头指向的任务。
if (p && *p) {
// 只是改为可运行状态,等待被调度才能执行。
(**p).state=0;
// 等待队列被设置为空。
// 如果还有任务因为等待相同的资源,例如上述缓冲块资源时,会建立新的等待队列。所以会有多个隐式的等待队列。
// 只要唤醒对列头的任务,就可以通过对头任务继续唤醒本次等待队列的任务了。
*p=NULL;
}
}
总结
比较难理解的有2点,
1.就是调用了 schedule
函数后,本任务就被挂起,执行其他任务了。
2.通过栈中的tmp变量形成的队列。 实际上nginx的filter模块的函数也是通过类似方式,把filter模块的过滤函数串成一个单链表。
参考下图可以更好的理解: 2个隐式队列。队列1 task3已经被唤醒。 task3.sp0.tmp -> task7.sp0.tmp -> NULL buffer_wait -> task5.sp0.tmp -> task4.sp0.tmp -> task2.sp0.tmp -> NULL