Chinaunix首页 | 论坛 | 博客
  • 博客访问: 74974
  • 博文数量: 9
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 42
  • 用 户 组: 普通用户
  • 注册时间: 2013-08-23 12:48
文章分类
文章存档

2015年(3)

2014年(3)

2013年(3)

分类: LINUX

2015-07-14 17:40:30

    等待队列(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_EXCLUSIVE    0x01
  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)
阅读(1628) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~