内核调度器管理要运行的任务列表,被称为运行队列。睡眠的进程不再被调度,除非被唤醒。进入睡眠等待的进程,可以释放处理器。
等待队列
等待队列实际上用于处理被阻塞的IO,以等待特定条件成立,并感知数据或资源的可用性。
// include/linux/wait.h
struct wait_queue_entry {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head entry;
};
entry
这个字段是将进程加入到等待队列的链表中,每个进程都会进入睡眠状态,直到条件变为真。等待队列可以被看作简单的进程链表和锁。
// 静态声明
DECLARE_WAIT_QUEUE_HEAD(name);
// 动态声明
wait_queue_head_t my_wait_queue;
init_waitqueue_head(&my_wait_queue);
// 阻塞
// 如果条件为false,则阻塞等待队列中的当前任务(进程)。
int wait_event_interruptible(wait_queue_head_t q, CONDITION);
/**
* wait_event_interruptible - sleep until a condition gets true
* @wq_head: the waitqueue to wait on
* @condition: a C expression for the event to wait for
*
* The process is put to sleep (TASK_INTERRUPTIBLE) until the
* @condition evaluates to true or a signal is received.
* The @condition is checked each time the waitqueue @wq_head is woken up.
*
* wake_up() has to be called after changing any variable that could
* change the result of the wait condition.
*
* The function will return -ERESTARTSYS if it was interrupted by a
* signal and 0 if @condition evaluated to true.
*/
// 一直睡眠直到条件变为真。
// 在条件变为true或者收到信号之前,进程一直处于可中断睡眠状态(TASK_INTERRUPTIBLE)。
// 条件在每次唤醒等待队列时被检查。
// wake_up 一般在条件在相关变量变化时被被调用。
// 如果函数被信号中断,则该函数将返回 -ERESTARTSYS,如果@condition计算结果为 true,则返回 0。
#define wait_event_interruptible(wq_head, condition) \
({ \
int __ret = 0; \
might_sleep(); \
if (!(condition)) \
__ret = __wait_event_interruptible(wq_head, condition); \
__ret; \
})
// 解除阻塞
// 如果上述条件为true,则唤醒在等待队列中休眠的进程。
void wake_up_interruptible(wait_queue_head_t *q);
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
/**
* __wake_up - wake up threads blocked on a waitqueue.
* @wq_head: the waitqueue
* @mode: which threads
* @nr_exclusive: how many wake-one or wake-many threads to wake up
* @key: is directly passed to the wakeup function
*
* It may be assumed that this function implies a write memory barrier before
* changing the task state if and only if any tasks are woken up.
*/
void __wake_up(struct wait_queue_head *wq_head, unsigned int mode,
int nr_exclusive, void *key)
{
__wake_up_common_lock(wq_head, mode, nr_exclusive, 0, key);
}
EXPORT_SYMBOL(__wake_up);
wait_event_interruptible
不会持续轮询,而只是在被调用时评估条件。
- 如果条件为假,则进程将进入
TASK_INTERRUPTIBLE
状态并从运行队列中删除,之后每次在等待队列中调用wait_up_interruptible
时,都会重新检查条件。 - 如果
wait_up_interruptible
运行时发现条件为真,则等待队列中的进程将被唤醒。并将进程状态设置为TASK_RUNNING
。进程按照他们进入睡眠的顺序唤醒(先进先出),要唤醒等待队列中等待的所有进程,应该使用wake_up_interruptible_all
。
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
如果调用wake_up
或wake_up_interruptible
,条件为false,则什么都不会发生。如果没有显式调用,进程将永远不会被唤醒。
等待队列的测试程序
// wait_queue.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
static DECLARE_WAIT_QUEUE_HEAD(my_wq);
static int condition = 0;
/* 声明一个工作队列 */
static struct work_struct work;
static void work_handler(struct work_struct *work)
{
printk("Workqueue module handler!\n");
msleep(5000);
printk("Wake up the sleeping module!\n");
condition = 1;
wake_up_interruptible(&my_wq);
}
static int __init test_init(void)
{
printk("Wait queue test module!\n");
INIT_WORK(&work, work_handler);
schedule_work(&work);
printk("Going to sleep %s!\n", __func__);
wait_event_interruptible(my_wq, condition != 0);
printk("Woken up by work job!\n");
return 0;
}
void test_exit(void)
{
printk("Waitqueue example cleanup!\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
[25596.170555] Wait queue test module!
[25596.170560] Going to sleep test_init!
[25596.170565] Workqueue module handler!
[25601.200917] Wake up the sleeping module!
[25601.200931] Woken up by work job!
内核还提供了一个函数,当condtion或者时延时时间到了,等待队列中的进程恢复运行。
/**
* wait_event_interruptible_timeout - sleep until a condition gets true or a timeout elapses
* @wq_head: the waitqueue to wait on
* @condition: a C expression for the event to wait for
* @timeout: timeout, in jiffies
*
* The process is put to sleep (TASK_INTERRUPTIBLE) until the
* @condition evaluates to true or a signal is received.
* The @condition is checked each time the waitqueue @wq_head is woken up.
*
* wake_up() has to be called after changing any variable that could
* change the result of the wait condition.
*
* Returns:
* 0 if the @condition evaluated to %false after the @timeout elapsed,
* 1 if the @condition evaluated to %true after the @timeout elapsed,
* the remaining jiffies (at least 1) if the @condition evaluated
* to %true before the @timeout elapsed, or -%ERESTARTSYS if it was
* interrupted by a signal.
*/
#define wait_event_interruptible_timeout(wq_head, condition, timeout) \
({ \
long __ret = timeout; \
might_sleep(); \
if (!___wait_cond_timeout(condition)) \
__ret = __wait_event_interruptible_timeout(wq_head, \
condition, timeout); \
__ret; \
})
这里的timeout用的单位是jiffies
,所以需要有个函数,将延时时间转换为jiffies
unsigned int jiffies_to_msecs(const unsigned long j);
unsigned int jiffies_to_usecs(const unsigned long j);
unsigned long msecs_to_jiffies(const unsigned int m);
unsigned long usecs_to_jiffies(const unsigned int u);