一. 学习wait queue之前的背景知识
1. 在linux内核里面,我们将进程分为以下几种状态:
可运行状态(TASK_RUNNING)
处于这种状态的进程,要么正在运行,要么正准备被CPU调度运行。正在运行的进程就是当前进程(由current所指向的进程),而准备运行的进程只要得到CPU就可以立即投入运行,CPU是这些进程唯一等待的系统资源。系统中有一个运行队列(run_queue),用来容纳所有处于可运行状态的进程,调度程序执行时,从中选择一个进程投入运行。在后面我们讨论进程调度的时候,可以看到运行队列的作用。当前运行进程一直处于该队列中,也就是说,current总是指向运行队列中的某个元素,只是具体指向谁由调度程序(schedule)决定。
等待状态(TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE)
处于该状态的进程正在等待某个事件(event)或某个资源,它肯定位于系统中的某个等待队列(wait_queue)中。Linux中处于等待状态的进程分为两种:可中断的等待状态(TASK_INTERRUPTIBLE)和不可中断的等待状态(TASK_UNINTERRUPTIBLE))。处于可中断等待态的进程可以被信号唤醒,如果收到信号,该进程就从等待状态进入可运行状态,并且加入到运行队列中,等待被调度;而处于不可中断等待态的进程是因为硬件环境不能满足而等待,例如等待特定的系统资源,它任何情况下都不能被打断,只能用特定的方式来唤醒它,例如唤醒函数wake_up()等。
暂停状态
此时的进程暂时停止运行来接受某种特殊处理。通常当进程接收到SIGSTOP、SIGTSTP、SIGTTIN或 SIGTTOU信号后就处于这种状态。例如,正接受调试的进程就处于这种状态。
僵死状态
进程虽然已经终止,但由于某种原因,父进程还没有执行wait()系统调用,终止进程的信息也还没有回收。顾名思义,处于该状态的进程就是死进程,这种进程实际上是系统中的垃圾,必须进行相应处理以释放其占用的资源。
注意,上述TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE中的INTERRUPT并不是硬件中断,而是Linux中的信号(signal)。
2. 在linux内核里面,signal/event到底是什么?
signal的本质是软件层次上对中断的一种模拟。它是一种异步通信的处理机制,事实上,进程并不知道信号何时到来。
event本质上是一种同步通信的机制。事实上,往往进程/线程会阻塞,直到等到event,然后继续往下执行。
二. Linux wait queue机制
Linux 中的等待队列机制 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/97107297
三.wait queue代码分析和使用案例
1.waitqueue实现涉及以下文件:
(1)include/linux/wait.h
(2)kernel/wait.c
2.这里主要分析wait.h中定义的数据结构和API:
/*
* 1.wait queue head以及wait queue node定义说明:
* (1)首先,wait_queue_head_t 就是等待队列头,wait_queue_t 就是队列节点。
* (2)wait_queue_head_t包括一个自旋锁lock,还有一个双向循环队列task_list(即通过链表实现队列)
* (3)wait_queue_t 则包括较多属性:
* a.flags变量只可能是0或者WQ_FLAG_EXCLUSIVE。flags标志只影响等待队列唤醒线程时的操作,
* 置为WQ_FLAG_EXCLUSIVE则每次只允许唤醒一个线程,为0则无限制。
* b.private指针,其实就是指向PCB的指针。
* c.func是一个函数指针,指向用于唤醒队列中线程的函数。
* 虽然提供了默认的唤醒函数default_wake_function,但也允许灵活的设置队列的唤醒函数。
* d.task_list是一个双向循环链表节点,用于链入等待队列的链表。
*/
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);
int default_wake_function(wait_queue_t *wait, unsigned mode, int flags, void *key);
struct __wait_queue {
unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
/*
* 说明:新的内核中已经使用以下结构替换掉struct __wait_queue:
* struct wait_queue_entry {
* unsigned int flags;
* void *private;
* wait_queue_func_t func;
* struct list_head entry;
};
*/
typedef struct __wait_queue wait_queue_t;
/*
* 2.waitqueue在数据结构之后为我们提供了丰富的初始化函数和宏定义
*/
#define __WAITQUEUE_INITIALIZER(name, tsk) { \
.private = tsk, \
.func = default_wake_function, \
.task_list = { NULL, NULL } }
//(1)声明并初始化wait_queue_t
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
.task_list = { &(name).task_list, &(name).task_list } }
//(2)声明并初始化wait_queue_head_t
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
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);
}
//(3)初始化wait_queue_head_t
#define init_waitqueue_head(q) \
do { \
static struct lock_class_key __key; \
\
__init_waitqueue_head((q), &__key); \
} while (0)
//(4)另外定义了宏DECLARE_WAIT_QUEUE_HEAD_ONSTACK。根据配置是否使用CONFIG_LOCKDEP,
// 决定其实现。
// spinlock很复杂,配置了CONFIG_LOCKDEP就会定义一个局部静态变量__key对spinlock使
// 用的正确性进行检查(通过检查锁的嵌套调用深度)。因为使用了局部静态变量,所以只能检
// 查定义在栈上的变量,所以是DECLARE_WAIT_QUEUE_HEAD_ONSTACK。很多使用spinlock的
// 地方都可以看到这种检查。
#ifdef CONFIG_LOCKDEP
# define __WAIT_QUEUE_HEAD_INIT_ONSTACK(name) \
({ init_waitqueue_head(&name); name; })
# define DECLARE_WAIT_QUEUE_HEAD_ONSTACK(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INIT_ONSTACK(name)
#else
# define DECLARE_WAIT_QUEUE_HEAD_ONSTACK(name) DECLARE_WAIT_QUEUE_HEAD(name)
#endif
//(5)init_waitqueue_entry()和init_waitqueue_func_entry()用于初始化wait_queue_entry_t
static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
{
q->flags = 0;
q->private = p;
q->func = default_wake_function;
}
static inline void init_waitqueue_func_entry(wait_queue_t *q,
wait_queue_func_t func)
{
q->flags = 0;
q->private = NULL;
q->func = func;
}
//(6)waitqueue_active()查看队列中是否有等待线程
static inline int waitqueue_active(wait_queue_head_t *q)
{
return !list_empty(&q->task_list);
}
//(7)__add_wait_queue()将节点加入等待队列头部
// __add_wait_queue_tail()将节点加入等待队列尾部
// __remove_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);
}
/*
* Used for wake-one threads:
*/
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);
}
static inline void __remove_wait_queue(wait_queue_head_t *head,
wait_queue_t *old)
{
list_del(&old->task_list);
}
//(8)add_wait_queue()将节点加入等待队列头部
// add_wait_queue_exclusive()将节点加入等待队列尾部
// remove_wait_queue()将节点从等待队列中删除
// 这里三个函数和前面三个函数最大的区别就是这里加了禁止中断的自旋锁
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);
}
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);
}
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);
}
3.wait_queue_t使用案例之——使用默认的内核唤醒函数
(1) 使用内核默认的唤醒函数,用于内核线程间同步(___wait_event()向wait_queue_head中插入默认的wait_queue_t<wait_queue_entry>节点):
(2) wait_event的核心函数
(3) wake_up的核心函数
4.wait_queue_t使用案例之——使用自定义的内核唤醒函数
以kernel中的l2cap_sock.c为例进行说明:
实际他的唤醒函数最终还是调用内核的唤醒函数实现。
5.wait_queue的使用总结
(1) 主要有两种使用方式
a. 使用内核默认的唤醒方式
b. 使用驱动自定义的唤醒方式——实际上就是在默认唤醒方式上增加了struct wait_queue_entry->flags的控制,驱动程序利用这个flags可以实现更多的唤醒行为(completion的实现也是在这个flags上做文章)。
c. 以上两种方式由不同的API组合实现,具体参考wait.h以及上面的两个使用案例展示。
四.wait queue应用之completion机制的实现
1.completion实现涉及以下文件:
kernel/sched/completion.c
include/linux/completion.h
使用逻辑基本和第三小结中展示的wait queue案例类似,细节上有差异。
2.wait queue和completion之间有什么差异
但是,话又说回来,那wait queue和completion到底又有什么不同?因为,不可能无缘无故linux kernel在已经拥有wait queue的情况下,又去做一个功能重叠的completion出来。答案看这里:
参考: