在Linux驱动程序中,可以使用等待队列(Wait Queue)来实现阻塞进程的唤醒。等待队列很早就作为一个基本的功能单位出现在Linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,可以用来同步对系统资源的访问。
说明
kernel版本:4.14.111
数据结构
struct wait_queue_head {
spinlock_t lock;
struct list_head head;
};
typedef struct wait_queue_head wait_queue_head_t;
其中,自旋锁lock是用来防止并发访问,task_list字段是等待队列中等待的进程链表的头。
/*
* A single wait-queue entry structure:
*/
struct wait_queue_entry {
unsigned int flags;
void *private; (1)
wait_queue_func_t func; (2)
struct list_head entry; (3)
};
1)通常指向当前任务task_struct数据结构
2)回调函数
3)挂入等待队列的链表
等待队列操作
定义并初始化“等待队列头”
1)动态初始化
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue); //会将自旋锁初始化为未锁,等待队列初始化为空的双向循环链表。
2)静态定义
DECLARE_WAIT_QUEUE_HEAD (my_queue);
等待事件
#define wait_event(wq_head, condition) \
do { \
might_sleep(); \
if (condition) \
break; \
__wait_event(wq_head, condition); \
} while (0)
wait_event(queue,condition); //等待以queue为等待队列头等待队列被唤醒,condition必须满足,否则阻塞
wait_event_interruptible(queue,condition); //可被信号打断
wait_event_timeout(queue,condition,timeout); //阻塞等待的超时时间,时间到了,不论condition是否满足,都要返回
wait_event_interruptible_timeout(queue,condition,timeout)
1)首先判断条件是否满足,满足则直接返回
2)条件不满足则执行__wait_event
/* wait_event */
#define __wait_event(wq_head, condition) \
(void)___wait_event(wq_head, condition, TASK_UNINTERRUPTIBLE, 0, 0, \
schedule())
/* wait_event_interruptible */
#define __wait_event_interruptible(wq_head, condition) \
___wait_event(wq_head, condition, TASK_INTERRUPTIBLE, 0, 0, \
schedule())
/* wait_event_timeout */
#define __wait_event_timeout(wq_head, condition, timeout) \
___wait_event(wq_head, ___wait_cond_timeout(condition), \
TASK_UNINTERRUPTIBLE, 0, timeout, \
__ret = schedule_timeout(__ret))
上述分别为wait_event 、wait_event_interruptible 、wait_event_timeout 宏。可以看出,最终都是调用了___wait_event接口。
区别仅仅在于传递的进程状态,以及执行的调度函数。
#define ___wait_event(wq_head, condition, state, exclusive, ret, cmd) \
({ \
__label__ __out; \
struct wait_queue_entry __wq_entry; \ (1)
long __ret = ret; /* explicit shadow */ \
\
init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0); \ (2)
for (;;) { \
long __int = prepare_to_wait_event(&wq_head, &__wq_entry, state);\ (3)
\
if (condition) \ (4)
break; \
\
if (___wait_is_interruptible(state) && __int) { \
__ret = __int; \
goto __out; \
} \
\
cmd; \ (5)
} \
finish_wait(&wq_head, &__wq_entry); \ (6)
__out: __ret; \
})
1)定义一个等待队列
2)初始化该等待队列,代码实现如下:
void init_wait_entry(struct wait_queue_entry *wq_entry, int flags)
{
wq_entry->flags = flags;
wq_entry->private = current; //private指针设置为当前进程task_struct
wq_entry->func = autoremove_wake_function; //设置默认回调函数
INIT_LIST_HEAD(&wq_entry->entry); //初始化等待队列链表
}
3)for循环内调用prepare_to_wait_event
long prepare_to_wait_event(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry, int state)
{
unsigned long flags;
long ret = 0;
spin_lock_irqsave(&wq_head->lock, flags);
if (unlikely(signal_pending_state(state, current))) {
list_del_init(&wq_entry->entry);
ret = -ERESTARTSYS;
} else {
if (list_empty(&wq_entry->entry)) {
if (wq_entry->flags & WQ_FLAG_EXCLUSIVE)
__add_wait_queue_entry_tail(wq_head, wq_entry);
else
__add_wait_queue(wq_head, wq_entry);
}
set_current_state(state);
}
spin_unlock_irqrestore(&wq_head->lock, flags);
return ret;
}
if (list_empty(&wq_entry->entry))判断当前等待队列链表是否初始化,如果未初始化,则将当前等待队列链表加入等待队列头链表中。
set_current_state,设置当前进程状态,wait_event为TASK_UNINTERRUPTIBLE
wait_event_interruptible为TASK_INTERRUPTIBLE
4)调用完prepare_to_wait_event后,再次判断条件是否满足,如果条件满足,则跳出for循环。
5)如果条件不满足,则执行cmd接口,对于wait_event,为schedule,对于wait_event_timeout,为schedule_timeout。即主动执行调度。
6)跳出循环,设置当前进程状态为TSAK_RUNNING。将等待队列从等待队列头链表中删除。对于wait_event,这里是进程被wake_up,且条件满足,才会执行到这里。对于wait_event_timeout,可能是条件满足,也可能是timeout超时,根据返回值判断。
唤醒队列
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL)
#define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)
#define wake_up_locked(x) __wake_up_locked((x), TASK_NORMAL, 1)
#define wake_up_all_locked(x) __wake_up_locked((x), TASK_NORMAL, 0)
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
#define wake_up_interruptible_sync(x) __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)
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);
}
static void __wake_up_common_lock(struct wait_queue_head *wq_head, unsigned int mode,
int nr_exclusive, int wake_flags, void *key)
{
unsigned long flags;
wait_queue_entry_t bookmark;
bookmark.flags = 0;
bookmark.private = NULL;
bookmark.func = NULL;
INIT_LIST_HEAD(&bookmark.entry);
spin_lock_irqsave(&wq_head->lock, flags);
nr_exclusive = __wake_up_common(wq_head, mode, nr_exclusive, wake_flags, key, &bookmark);
spin_unlock_irqrestore(&wq_head->lock, flags);
while (bookmark.flags & WQ_FLAG_BOOKMARK) {
spin_lock_irqsave(&wq_head->lock, flags);
nr_exclusive = __wake_up_common(wq_head, mode, nr_exclusive,
wake_flags, key, &bookmark);
spin_unlock_irqrestore(&wq_head->lock, flags);
}
}
static int __wake_up_common(struct wait_queue_head *wq_head, unsigned int mode,
int nr_exclusive, int wake_flags, void *key,
wait_queue_entry_t *bookmark)
{
wait_queue_entry_t *curr, *next;
int cnt = 0;
if (bookmark && (bookmark->flags & WQ_FLAG_BOOKMARK)) {
curr = list_next_entry(bookmark, entry);
list_del(&bookmark->entry);
bookmark->flags = 0;
} else
curr = list_first_entry(&wq_head->head, wait_queue_entry_t, entry);
if (&curr->entry == &wq_head->head)
return nr_exclusive;
list_for_each_entry_safe_from(curr, next, &wq_head->head, entry) { (1)
unsigned flags = curr->flags;
int ret;
if (flags & WQ_FLAG_BOOKMARK)
continue;
ret = curr->func(curr, mode, wake_flags, key); (2)
if (ret < 0)
break;
if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
if (bookmark && (++cnt > WAITQUEUE_WALK_BREAK_CNT) &&
(&next->entry != &wq_head->head)) {
bookmark->flags = WQ_FLAG_BOOKMARK;
list_add_tail(&bookmark->entry, &next->entry);
break;
}
}
return nr_exclusive;
}
1)遍历当前等待队列头链表中的等待队列。根据传递的唤醒数量,执行循环。
2)执行等待队列定义时,传递的func回调。上文中提到了,默认回调函数为autoremove_wake_function
int autoremove_wake_function(struct wait_queue_entry *wq_entry, unsigned mode, int sync, void *key)
{
int ret = default_wake_function(wq_entry, mode, sync, key);
if (ret)
list_del_init(&wq_entry->entry);
return ret;
}
int default_wake_function(wait_queue_entry_t *curr, unsigned mode, int wake_flags,
void *key)
{
return try_to_wake_up(curr->private, mode, wake_flags);
}
autoremove_wake_function最终会调用try_to_wake_up函数将进程置为TASK_RUNNING状态。这样后面的进程调度便会调度到该进程,从而唤醒该进程继续执行。