当你必须一个复杂的系统,协调系统的方方面面,灵活地支持各种机制和策略,即使很简单的问题也会变得很复杂。linux绝对就是这样一个复杂的系统。所以我们要理解它,尽量从原理的角度去理解事务的处理流程,尽量避免各种细枝末节的干扰,尽量规避那些足以压垮自己的庞然大物。(尽管细致末节和庞然大物很可能就是linux闪光的地方,但我们还是小心为上。)
原理
现在我们来考虑linux中线程的阻塞。它的原理很简单。我们有一个要阻塞的线程A和要唤醒它的线程B(当然也可以是中断处理例程ISR),有一个两者共知的等待队列Q(也许这个等待队列属于一个信号量什么的)。首先是线程A阻塞,要加入等待队列Q,需要先申请一个队列节点N,节点N中包含指向线程A的线程控制块(TCB)的指针,然后A就可以将自己的线程状态设为阻塞,并调用schedule()将自己踢出CPU的就绪队列。过了一定时间,线程B想要唤醒等待队列Q中的线程,它只需要获得线程A的TCB指针,将线程A状态设为就绪即可。等线程A恢复运行,将节点N退出等待队列Q,完成整个从阻塞到恢复的流程。
原理讲起来总是没意思的,下面我们还是看代码吧。我们规避复杂的任务状态转换和调度的内容,即使对等待队列的分析,也是按照从基础到扩展的顺序。代码出现在三个地方:include/linux/wait.h , kernel/wait.c, kernel/sched.c。不用说wait.h是头文件,wait.c是实现的地方,而sched.c则体现了waitqueue的一种应用(实现completion)。为了更好地分析completion,我们还需要include/linux/completion.h。
waitqueue实现
我们仍然先看数据结构。
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;
};
typedef struct __wait_queue wait_queue_t;
其中,wait_queue_head_t 就是等待队列头,wait_queue_t 就是队列节点。
wait_queue_head_t 包括一个自旋锁lock,还有一个双向循环队列task_list,这在我们的预料之内。
wait_queue_t 则包括较多,我们先来剧透一下。
flags变量只可能是0或者WQ_FLAG_EXCLUSIVE。flags标志只影响等待队列唤醒线程时的操作,置为WQ_FLAG_EXCLUSIVE则每次只允许唤醒一个线程,为0则无限制。
private指针,其实就是指向TCB的指针。
func是一个函数指针,指向用于唤醒队列中线程的函数。虽然提供了默认的唤醒函数default_wake_function,但也允许灵活的设置队列的唤醒函数。
task_list是一个双向循环链表节点,用于链入等待队列的链表。
依照旧例,waitqueue在数据结构之后为我们提供了丰富的初始化函数。因为太多了,我们只好分段列出。
#define __WAITQUEUE_INITIALIZER(name, tsk) { \
.private = tsk, \
.func = default_wake_function, \
.task_list = { NULL, NULL } }
#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 } }
#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);
}
#define init_waitqueue_head(q) \
do {