等待队列在linux内核中有着举足轻重的作用,很多linux驱动都或多或少涉及到了等待队列。因此,对于linux内核及驱动开发者来说,掌握等待队列是必须课之一。
Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。它有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t,typedef struct __wait_queue wait_queue_t;)。等待队列头和等待队列项中都包含一个list_head类型的域作为”连接件”。它通过一个双链表和把等待task的头,和等待的进程列表链接起来。下面具体介绍。
一.数据结构定义
列表头
文件路径:/include/linux/wait.h
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;//列表头
列表项
struct __wait_queue {
unsigned int flags;
void* private;
wait_queue_func_t func;
struct list_head task_list;
};
双向链表结构
struct list_head {
struct list_head* next;
struct list_head* prev;
};
二.作用
在内核里面,等待队列是有很多用处的,尤其是在中断处理、进程同步、定时等场合。可以使用等待队列在实现阻塞进程的唤醒。它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制,同步对系统资源的访问等。
三.字段详解
1、spinlock_t lock;
在对task_list与操作的过程中,使用该锁实现对等待队列的互斥访问。
2、srtuct list_head_t task_list;
双向循环链表,存放等待的进程。
操作
1.定义并初始化列表头
直接定义
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
init_waitqueue_head()函数会将自旋锁初始化为未锁,等待队列初始化为空的双向循环链表。
/include/linux/wait.h
#define init_waitqueue_head(q)
do {
static struct lock_class_key (__key);
__init_waitqueue_head((q), (&__key));
} while (0)
/kernel/wait.c
void __init_waitqueue_head(wait_queue_head_t *q, struct lock_class_key *key)
{
spin_lock_init(&q->lock);
lockdep_set_class(&q->lock, key);
INIT_LIST_HEAD(&q->task_list);
}
/include/linux/list.h
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
使用宏
DECLARE_WAIT_QUEUE_HEAD(my_queue);
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) {
.lock = __SPIN_LOCK_UNLOCKED(name.lock),
.task_list = { &(name).task_list, &(name).task_list } }
#define DECLARE_WAIT_QUEUE_HEAD(name)
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
定义并初始化LIST,相当于(1)
定义等待队列
DECLARE_WAITQUEUE(name,tsk);
#define DECLARE_WAITQUEUE(name, tsk)
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
#define __WAITQUEUE_INITIALIZER(name, tsk) {
.private = tsk,
.func = default_wake_function,
.task_list = { NULL, NULL } }
注意此处是定义一个wait_queue_t类型的变量name,并将其private设置为tsk,wait_queue_t 结构:
struct __wait_queue {
unsigned int flags;//指明该等待的进程是互斥进程还是非互斥进程,0是非互斥进程
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
其中flags域指明该等待的进程是互斥进程还是非互斥进程。其中0是非互斥进程,WQ_FLAG_EXCLUSIVE(0x01)是互斥进程。
等待队列(wait_queue_t)和等待对列头(wait_queue_head_t)的区别是等待队列是等待队列头的成员。
也就是说等待队列头的task_list域链接的成员就是等待队列类型的(wait_queue_t)。
(从等待队列头中)添加/移出等待队列
添加
设置等待的进程为非互斥进程,并将其添加进等待队列头(q)的队头中(list.h)
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);
}
EXPORT_SYMBOL(add_wait_queue);
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
{
list_add(&new->task_list, &head->task_list);
}
/include/linux/list.h
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
/include/linux/list.h
static inline void __list_add(struct list_head *new,struct list_head *prev,struct list_head *next)
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
该函数也和add_wait_queue()函数功能基本一样,只不过它是将等待的进程(wait)设置为互斥进程,且添加到末尾
void add_wait_queue_exclusive(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_tail(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(add_wait_queue_exclusive);
static inline void __add_wait_queue_tail(wait_queue_head_t *head,
wait_queue_t *new)
{
list_add_tail(&new->task_list, &head->task_list);
}
移除
在等待的资源或事件满足时,进程被唤醒,使用该函数被从等待头中删除
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);
}
EXPORT_SYMBOL(remove_wait_queue);
static inline void __remove_wait_queue(wait_queue_head_t *head,wait_queue_t *old)
{
list_del(&old->task_list);
}
等待事件
wait_event()宏:
/**
* wait_event - sleep until a condition gets true
* @wq: the waitqueue to wait on
* @condition: a C expression for the event to wait for
*
* The process is put to sleep (TASK_UNINTERRUPTIBLE) until the
* @condition evaluates to true. The @condition is checked each time
* the waitqueue @wq is woken up.
*
* wake_up() has to be called after changing any variable that could
* change the result of the wait condition.
*/
#define wait_event(wq, condition) \
do { \
if (condition) \
break; \
__wait_event(wq, condition); \
} while (0)
#define __wait_event(wq, condition) \
(void)___wait_event(wq, condition, TASK_UNINTERRUPTIBLE, 0, 0, schedule())
/*
* The below macro ___wait_event() has an explicit shadow of the __ret
* variable when used from the wait_event_*() macros.
*
* This is so that both can use the ___wait_cond_timeout() construct
* to wrap the condition.
*
* The type inconsistency of the wait_event_*() __ret variable is also
* on purpose; we use long where we can return timeout values and int
* otherwise.
*/
#define ___wait_event(wq, condition, state, exclusive, ret, cmd) \
({ \
__label__ __out; \
wait_queue_t __wait;//列表项 \
long __ret = ret; /* explicit shadow */ \
\
INIT_LIST_HEAD(&__wait.task_list); \
if (exclusive) \
__wait.flags = WQ_FLAG_EXCLUSIVE; \
else \
__wait.flags = 0; \
\
for (;;) { \
long __int = prepare_to_wait_event(&wq, &__wait, state);\
\
if (condition) \
break; \
\
if (___wait_is_interruptible(state) && __int) { \
__ret = __int; \
if (exclusive) { \
abort_exclusive_wait(&wq, &__wait, \
state, NULL); \
goto __out; \
} \
break; \
} \
\
cmd; \
} \
finish_wait(&wq, &__wait); \
__out: __ret; \
})
在等待会列中睡眠直到condition为真。在等待的期间,进程会被置为TASK_UNINTERRUPTIBLE进入睡眠,直到condition变量变为真。每次进程被唤醒的时候都会检查
condition的值.