leevis.com icon indicating copy to clipboard operation
leevis.com copied to clipboard

Linux-0.11内核睡眠和唤醒操作以及多个隐式的等待队列

Open vislee opened this issue 3 years ago • 0 comments

概述

内核代码申请某个资源而被其他任务占用时,就会把自己设置为不能被打断的睡眠状态(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

vislee avatar Dec 17 '21 16:12 vislee