在Linux驱动程序中,阻塞进程可以使用等待队列(Wait Queue)来实现。由于等待队列很有用,在Linux 2.0的时代,就已经引入了等待队列机制。等待队列的基本数据结构是一个双向链表,这个链表存储睡眠的进程。等待队列也与进程调度机制紧密结合,能够用于实现内核中异步事件通知机制。等待队列可以用来同步对系统资源的访问。例如,当完成一项工作之后,才允许完成另一项工作。

在内核中,等待队列是有很多用处的,尤其是在中断处理、进程同步、定时等场合。可以使用等待队列实现阻塞进程的唤醒。它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制,同步对系统资源的访问等。

完成量就是以等待队列为基础

1 结构

等待队列头:

struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

lock:

用来对tasklist链表起保护作用。当要向task list链表中加入或者删除元素时,内核内部就会锁定lock锁,当修改完成后,会释放lock锁。也就是说, lock自旋锁在对task_list与操作的过程中,实现了对等待队列的互斥访问。

task_list:所有等待的队列都挂在这上面

等待队列:

typedef struct __wait_queue wait_queue_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;
};

 

操作步骤:

定义和初始化等待队列头

#define DECLARE_WAIT_QUEUE_HEAD(name) \  //定义
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)


#define init_waitqueue_head(q) \ //初始化
do { \
static struct lock_class_key __key; \
\
__init_waitqueue_head((q), &__key); \
} while (0)

定义等待队列成员:

#define DECLARE_WAITQUEUE(name, tsk)          \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

添加和移除等待队列:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

设置等待事件:

#define wait_event(wq, condition)   
#define wait_event_timeout(wq, condition, timeout)
#define wait_event_interruptible(wq, condition)
#define wait_event_interruptible_timeout(wq, condition, timeout)

#define __wait_event(wq, condition) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
if (condition) \
break; \
schedule();//进程调度 \
} \
finish_wait(&wq, &__wait); \
} while (0)

wait event宏的功能是,在等待队列中睡眠直到condition为真。在等待的期间,进程会被置为TASK _UNINTERRUPTIBLE进入睡眠,直到condition变量变为真。每次进程被唤醒的时候都会检查condition的值。

wait event timeont宏与wait event宏类似,不过如果所给的睡眠时间为负数则立即返回。如果在睡眠期间被唤醒,且condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回0。

wait event interruptible宏与wait event宏的区别是,调用该宏在等待的过程中当前进程会被设置为TASK INTERRUPTIBLE状态。在每次被唤醒的时候,首先检查condition是否为真,如果为真则返回;否则检查如果进程是被信号唤醒,会返回-ERESTARTSYS错误码。如果是condition为真,则返回0.

wait event interruptible timeout宏与wait event timeout宏类似,不过如果在睡眠期间被信号打断则返回ERESTARTSYS错误码。

 

唤醒等待队列:

唤醒时很有意思,比如你调用 wake_up 去唤醒一个使用 wait_event 等,进入休眠的进程,唤醒之后,它会判断 condition 是否为真,如果还是假的继续睡眠。至于为什么这个样子,后面分析代码就会明白。

#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)

wake_up宏唤醒等待队列,可唤醒处于TASK_INTERRUPTIBLE和TASK UNINTERUPTIBLE状态的进程,这个宏和wait event/wait event timeout成对使用。

wake up interruptible宏和wake up0)唯一的区别是,它只能唤醒TASK INTERRUPTIBLE状态的进程。这个宏可以唤醒使用wait event interruptible.wait event interruptible timeout宏睡眠的进程。

等待队列_进程调度

总结:

  1. 创建等待队列成员、等待队列头
  2. 将等待队列成员加入到等待队列中去
  3. 设置当前进程的进程状态
  4. 进程调度~
  5. 唤醒