等待队列的原理与源码分析

等待队列(wait queue)用于使进程带等待某一特定的事件发生,而无需频繁的轮询操作,进程在等待时间内睡眠,在等待的事件发生时由内核自动唤醒。
一、等待队列相关数据结构
每一个等待队列都由两部分组成:等待队列头(struct wait_queue_head_t)和等待队列成员(struct wait_queue)。
  1. struct __wait_queue_head {
  2. spinlock_t lock; /*因为等待队列可以在中断时随时修改,因此设置一个自旋锁保证一致性*/
  3. struct list_head task_list;
  4. };
  5. typedef struct __wait_queue_head wait_queue_head_t
  1. struct __wait_queue {
  2. unsigned int flags; /*指明等待的进程是互斥进程还是非互斥进程*/
  3. #define WQ_FLAG_EXCLUSIVE0x01
  4. void *private; /*指向任务的task_struct*/
  5. wait_queue_func_t func;
  6. struct list_head task_list;
  7. };
  8. typedef struct __wait_queue_head wait_queue_head_t
最后形成的结果就是一个等待队列头串起多个等待队列成员,如图1所示:
等待队列的原理与源码分析
等待队列的使用分为以下两部分:
(1)为使当前进程在一个等待队列中睡眠,需要调用wait_event(或某个等价函数),此后,进程进入睡眠,将控制权交给调度器。以块设备为例,当内核向块设备发出请求后,因为数据传输不会立即发生,因此进程睡眠
(2)相对应的,是当数据到达后,必须调用wake_up函数(或某个等价函数)来唤醒等待队列中睡眠的进程
二、等待队列相关的接口函数
1. 声明和初始化相关的接口
(1)静态初始化一个等待队列实例: DEFINE_WAIT(&wq)
  1. #define DEFINE_WAIT_FUNC(name, function)/
  2. wait_queue_t name = {/
  3. .private= current,/
  4. .func= function,/
  5. .task_list= LIST_HEAD_INIT((name).task_list),/
  6. }
  7. #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)
autoremove_wake_function是默认的唤醒函数:
  1. int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)
  2. {
  3. int ret = default_wake_function(wait, mode, sync, key);
  4. if (ret)
  5. list_del_init(&wait->task_list);
  6. return ret;
  7. }
其主要功能是唤醒睡眠进程,并从等待队列链表上将这个等待队列成员删除。
(2)动态声明和初始化一个等待队列
  1. struct wait_queue_t myqueue;
  2. init_waitqueue_entry(&myqueue, tsk);
  3. static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
  4. {
  5. q->flags = 0;
  6. q->private = p;
  7. q->func = default_wake_function;
  8. }
2. 使进程在等待队列上睡眠相关接口
(1)将进程加入某个等待队列链表
  1. void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
  2. {
  3. unsigned long flags;
  4. wait->flags &= ~WQ_FLAG_EXCLUSIVE;
  5. spin_lock_irqsave(&q->lock, flags);
  6. if (list_empty(&wait->task_list)) /*判断当前等待队列是否已经被加入过某个链表,如果没有将wait加入q*/
  7. __add_wait_queue(q, wait);
  8. set_current_state(state); /*加入等待队列后,进程的状态需要按要求改变*/
  9. spin_unlock_irqrestore(&q->lock, flags);
  10. }
另外还有一个添加函数,不过这个函数同常不会直接使用:
  1. void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
  2. {
  3. unsigned long flags;
  4. wait->flags &= ~WQ_FLAG_EXCLUSIVE;
  5. spin_lock_irqsave(&q->lock, flags);
  6. __add_wait_queue(q, wait);
  7. spin_unlock_irqrestore(&q->lock, flags);
  8. }

比较常用的几个宏:

(1)wait_event:将当前进程加入指定的等待队列列表wq,因为在smp的环境下,condition在任何地方都有可能改变,因此,在检查前首先检查 等待条件有没有改变,如果没有改变才会真正加入,如果改变了就可以直接退出。当prepare_to_wait加入等待队列后,再去检查等待状态有没有改变,如果没有改变,那么当前进程就应该放弃CPU了,调用schedule()。

  1. #define wait_event(wq, condition) /
  2. do {/
  3. if (condition) /
  4. break;/
  5. __wait_event(wq, condition);/
  6. } while (0)
  7. #define __wait_event(wq, condition) /
  8. do {/
  9. DEFINE_WAIT(__wait);/
  10. /
  11. for (;;) {/
  12. prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);/
  13. if (condition)/
  14. break;/
  15. schedule();/
  16. }/
  17. finish_wait(&wq, &__wait);/
  18. } while (0)

(2)wait_event_interruptible(wq, conditon):设置进程的状态为TASK_INTERRUPTIBLE,因此,进程除了当喊醒条件变化时被唤醒外,还可以通过接收信号而被唤醒。

(3)wait_event_timeout():等待满足的条件为指定的时间后就可以被唤醒,防止了永久等待

(4)wait_evnt_interruptible_timeout():是(2)和(3)的结合体。

3. 等待队列上进程唤醒相关的接口

有睡眠就必须要唤醒,但是不要求是一种一对一的配对行为,下面列举了比较常用的唤醒相关的宏:

(1)wake_up(x)

  1. #define wake_up(x)__wake_up(x, TASK_NORMAL, 1, NULL)
  2. void __wake_up(wait_queue_head_t *q, unsigned int mode,
  3. int nr_exclusive, void *key) /*mode表示要唤醒的进程的状态*/
  4. {
  5. unsigned long flags;
  6. spin_lock_irqsave(&q->lock, flags);
  7. __wake_up_common(q, mode, nr_exclusive, 0, key);
  8. spin_unlock_irqrestore(&q->lock, flags);
  9. }
  10. static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
  11. int nr_exclusive, int wake_flags, void *key)
  12. {
  13. wait_queue_t *curr, *next;
  14. list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
  15. unsigned flags = curr->flags;
  16. /*执行工作队列处理函数,并选择执行多少个独占的进程,需要注意的是独占进程被添加到了工作队列的尾端,因此,会首先处理普通的进程,最后才去处理独占进程,nr_exclusive就是用来解决“惊群”问题的*/
  17. if (curr->func(curr, mode, wake_flags, key) &&
  18. (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
  19. break;
  20. }
  21. }

(2)其他唤醒宏:

  1. #define wake_up_nr(x, nr)__wake_up(x, TASK_NORMAL, nr, NULL)
  2. #define wake_up_all(x)__wake_up(x, TASK_NORMAL, 0, NULL)
  3. #define wake_up_locked(x)__wake_up_locked((x), TASK_NORMAL)
  4. #define wake_up_interruptible(x)__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
  5. #define wake_up_interruptible_nr(x, nr)__wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
  6. #define wake_up_interruptible_all(x)__wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
  7. #define wake_up_interruptible_sync(x)__wake_up_sync((x), TASK_INTERRUPTIBLE, 1)

你可能感兴趣的:(源码分析)