1. 概述

While writing modules there might be situations where one might have to wait for input some condition to occur before proceeding further. Tasks that need such behavior can make use of the sleep functionality available in the kernel.
In Linux sleeping is handled by a data structure called wait queue, which is nothing but a list of processes waiting for an input or event.

等待队列在内核中有很多用途,尤其在中断处理、进程同步及定时。等待队列实现事件上的条件等待;希望等待特定事件的进程把自己放在合适的等待队列,并放弃控制权。

2. 等待队列

本文代码的内核版本为3.14.69

研究等待队列这个内核非常基础的数据结构,对于加深理解Linux非常有帮忙,等待队列有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t),两者都有一个list_head类型task_list。双向链表通过task_list将 等待队列头和一系列等待队列项串起来,源码如下所示。

2.1 struct wait_queue_head_t

1
2
3
4
5
struct __wait_queue_head {
spinlock_t lock; //用于互斥访问的自旋锁
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

2.2 struct wait_queue_t

1
2
3
4
5
6
7
struct __wait_queue {
unsigned int flags;
void *private; //指向等待队列的进程task_struct
wait_queue_func_t func; //调用唤醒函数,缺省为default_wake_function,调用try_to_wake_up将进程更改为可运行状态并设置调度标志
struct list_head task_list; //链表元素,将wait_queue_t挂到wait_queue_head_t
};
typedef struct __wait_queue wait_queue_t;

2.3 add_wait_queue

1
2
3
4
5
6
7
8
9
10
11
12
13
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue(q, wait); //挂到队列头
spin_unlock_irqrestore(&q->lock, flags);
}

static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
{
list_add(&new->task_list, &head->task_list);
}

该方法的功能是将wait等待队列项 挂到等待队列头q中。

2.4 remove_wait_queue

1
2
3
4
5
6
7
8
9
10
11
12
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
spin_lock_irqsave(&q->lock, flags);
__remove_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}

static inline void __remove_wait_queue(wait_queue_head_t *head, wait_queue_t *old)
{
list_del(&old->task_list);
}

该方法主要功能是将wait等待队列项 从等待队列头q中移除。

3. 等待事件

3.1 wait_event(queue,condition)

The task will keep waiting on the queue as long as the condition does not become true.If put to sleep using this call, the task can not be interrupted.

3.2 wait_event_interruptible(queue,condition)

similar to wait_event, but it can be interrupted by other signals too. It is always preferable to use this interruptible way of sleeping so that the task can be stopped in case the condition never becomes true.

3.3 wait_event_timeout(queue,condition,timeout)

The task will sleep on the queue until the condition becomes true or the timeout mentioned expires, which ever occurs first. The timeout is expressed in jiffies. Task can not be interrupted before the timeout if the condition does not become true.

3.4 wait_event_interruptible_timeout(queue,condition,timeout)

Similar to wait_event_timeout but it can be interrupted.

Once a task has been put to sleep we need to wake it up , which can be done using following :

4. 唤醒队列

4.1 wake_up(queue)

In case the task has been put to non interruptible sleep.

4.2 wake_up_interruptible (queue)

In case the task has been put to an interruptible sleep.


参考资料:

  1. wait queue机制
  2. [linux等待队列wait_queue_head_t和wait_queue_t
  3. Wait queues
  4. 源码解读Linux等待队列