Linux 等待队列

  • 了解linux 等待队列

1.概述

  While writing modules there might be situations where one might have to wait for input some condition to occur before proceeding further. Tasks that need such behavior can make use of the sleep functionality available in the kernel.

  In Linux sleeping is handled by a data structure called wait queue, which is nothing but a list of processes waiting for an input or event.

  等待队列在内核中有很多用途,尤其在中断处理、进程同步及定时。等待队列实现事件上的条件等待;希望等待特定事件的进程把自己放在合适的等待队列,并放弃控制权。

  在Linux驱动程序中,可使用等待队列(wait queue)来实现阻塞进程的唤醒,以队列为基础数据结构,与进程调度机制紧密结合,用于实现内核的异步事件通知机制,也可用于同步对系统资源的访问。(信号量在内核中也依赖等待队列来实现)

2.等待队列

  等待队列有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t),两者都有一个list_head类型task_list。双向链表通过task_list将 等待队列头和一系列等待队列项串起来,源码如下所示。

2.1. struct wait_queue_head_t

  include/linux/wait.h:
  34 struct wait_queue_head {
  35     spinlock_t      lock;  /* 保护等待队列的原子锁 (自旋锁),在对head与操作的过程中,使用该锁实现对等待队列的互斥访问*/
  36     struct list_head    head;   /* 等待队列,双向循环链表,存放等待的进程 */
  37 };
  38 typedef struct wait_queue_head wait_queue_head_t; 

2.2. struct wait_queue_entry

/*该结构是对一个等待任务的抽象。每个等待任务都会抽象成一个wait_queue,并且挂载到wait_queue_head上。该结构定义如下:*/
  27 struct wait_queue_entry {
  28     unsigned int        flags;
  29     void            *private;  //指向等待队列的进程task_struct
  30     wait_queue_func_t   func; //调用唤醒函数,缺省为default_wake_function,调用try_to_wake_up将进程更改为可运行状态并设置调度标志
  31     struct list_head    entry; //链表元素,将wait_queue_t挂到wait_queue_head_t
  32 };

在这里插入图片描述

2.3.等待队列头初始化

2.3.1.静态初始化

  通过宏DECLARE_WAIT_QUEUE_HEAD(name)来创建类型为wait_queue_head_t的等待队列头name,代码如下:

 include/linux/wait.h:
  54 #define __WAIT_QUEUE_HEAD_INITIALIZER(name) {                   \                                       
  55     .lock       = __SPIN_LOCK_UNLOCKED(name.lock),          \
  56     .head       = { &(name).head, &(name).head } }
  57 
  58 #define DECLARE_WAIT_QUEUE_HEAD(name) \
  59     struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

2.3.2.动态初始化

  include/linux/wait.h:
  63 #define init_waitqueue_head(wq_head)                        \                                           
  64     do {                                    \
  65         static struct lock_class_key __key;             \
  66                                         \
  67         __init_waitqueue_head((wq_head), #wq_head, &__key);     \
  68     } while (0)
  69     

  9 void __init_waitqueue_head(struct wait_queue_head *wq_head, const char *name, struct lock_class_key *key)
 10 {
 11     spin_lock_init(&wq_head->lock);
 12     lockdep_set_class_and_name(&wq_head->lock, key, name);
 13     INIT_LIST_HEAD(&wq_head->head);
 14 }              

  所以如果使用 init_waitqueue_head()的用法为:

wait_queue_head_t name;
init_waitqueue_head(&name);

Note:DECLARE_WAIT_QUEUE_HEAD() 与 init_waitqueue_head()的区别:

DECLARE_WAIT_QUEUE_HEAD完全时用宏定义实现的,因此程序在编译的时候就完成了变量的定义与初始化。
而 init_waitqueue_head()本质上来说是对 __init_waitqueue_head()的一次封装。虽然使用了宏定义,其仍然是一个函数,它对变量的初始化是在程序运行的时候进行的;

2.4.等待队列中的等待项初始化

2.4.1.静态初始化

第一种方法:DECLARE_WAITQUEUE

  通过宏DECLARE_WAITQUEUE(name, tsk) 来创建类型为wait_queue_t的等待队列项name,并将tsk赋值给成员变量private, default_wake_function赋值给成员变量func,代码如下:

  46 #define __WAITQUEUE_INITIALIZER(name, tsk) {                    \
  47     .private    = tsk,                          \
  48     .func       = default_wake_function,                \
  49     .entry      = { NULL, NULL } }                                                                      
  50 
  51 #define DECLARE_WAITQUEUE(name, tsk)                        \
  52     struct wait_queue_entry name = __WAITQUEUE_INITIALIZER(name, tsk)

第二种方法:DEFINE_WAIT

1118 #define DEFINE_WAIT_FUNC(name, function)                    \
1119     struct wait_queue_entry name = {                    \
1120         .private    = current,                  \
1121         .func       = function,                 \
1122         .entry      = LIST_HEAD_INIT((name).entry),         \                                           
1123     }                                            
1124                                                  
1125 #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

  使用DEFINE_WAIT声明一个wait_queue_t类型的新变量,并用CPU上运行的当前进程的描述符和唤醒函数autoremove_wake_function的地址初始化这个新变量。

2.4.2.动态初始化

  79 static inline void init_waitqueue_entry(struct wait_queue_entry *wq_entry, struct task_struct *p)       
  80 {
  81     wq_entry->flags     = 0;
  82     wq_entry->private   = p;
  83     wq_entry->func      = default_wake_function;
  84 }

用法:

wait_queue_t wait;
struct task_struct task;
init_waitqueue_entry(&wait, &task);

3.APIs

3.1.add_wait_queue

  该方法的功能是将wq_entry等待队列项 挂到等待队列头wq_head中。

 154 static inline void __add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry) 
 155 {  
 156     list_add(&wq_entry->entry, &wq_head->head);
 157 }
 158 
 
 18 void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
 19 {
 20     unsigned long flags;
 21 
 22     wq_entry->flags &= ~WQ_FLAG_EXCLUSIVE;
 23     spin_lock_irqsave(&wq_head->lock, flags);
 24     __add_wait_queue(wq_head, wq_entry);                                                                 
 25     spin_unlock_irqrestore(&wq_head->lock, flags);
 26 }    
 27 EXPORT_SYMBOL(add_wait_queue);

3.2. add_wait_queue_exclusive:向等待队列中添加一个每次只能唤醒一个数据项

 169 static inline void __add_wait_queue_entry_tail(struct wait_queue_head *wq_head, struct wait_queue_entry 
 170 {   
 171     list_add_tail(&wq_entry->entry, &wq_head->head);
 172 }
 
 29 void add_wait_queue_exclusive(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)        
 30 {
 31     unsigned long flags;
 32 
 33     wq_entry->flags |= WQ_FLAG_EXCLUSIVE;
 34     spin_lock_irqsave(&wq_head->lock, flags);
 35     __add_wait_queue_entry_tail(wq_head, wq_entry);
 36     spin_unlock_irqrestore(&wq_head->lock, flags);
 37 }    
 38 EXPORT_SYMBOL(add_wait_queue_exclusive);

两者区别:

  • add_wait_queue(wait_queue_head_t *head, wait_queue_t *wait)
    每次向等待队列的头后面添加一个非互斥唤醒的等待项;
  • add_wait_queue_exclusive(wait_queue_head_t *head, wait_queue_t *wait)
    每次向等待队列的尾部添加一个互斥唤醒的等待项;

3.3.remove_wait_queue

  该方法主要功能是将wq_entry等待队列项 从等待队列头wq_head中移除。

 181 static inline void
 182 __remove_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)                 
 183 {
 184     list_del(&wq_entry->entry);
 185 }
 
 40 void remove_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
 41 {
 42     unsigned long flags;
 43 
 44     spin_lock_irqsave(&wq_head->lock, flags);
 45     __remove_wait_queue(wq_head, wq_entry);
 46     spin_unlock_irqrestore(&wq_head->lock, flags);                                                       
 47 }
 48 EXPORT_SYMBOL(remove_wait_queue);

4.等待事件

  • wait_event(queue,condition)
    The task will keep waiting on the queue as long as the condition does not become true.If put to sleep using this call, the task can not be interrupted.

  • wait_event_interruptible(queue,condition)
    similar to wait_event, but it can be interrupted by other signals too. It is always preferable to use this interruptible way of sleeping so that the task can be stopped in case the condition never becomes true.

  • wait_event_timeout(queue,condition,timeout)
    The task will sleep on the queue until the condition becomes true or the timeout mentioned expires, which ever occurs first. The timeout is expressed in jiffies. Task can not be interrupted before the timeout if the condition does not become true.

  • wait_event_interruptible_timeout(queue,condition,timeout)
    Similar to wait_event_timeout but it can be interrupted.

  Once a task has been put to sleep we need to wake it up , which can be done using following :

  • wake_up(queue)
    In case the task has been put to non interruptible sleep.

  • wake_up_interruptible (queue)
    In case the task has been put to an interruptible sleep.

4.1.wait_event :用于使当前线程进入休眠等待状态。

#define wait_event(wq, condition)                    \
do {                                    \
    if (condition)                            \  //判断条件是否满足,如果满足则退出等待     
        break;                            \
    __wait_event(wq, condition);                    \  //如果不满足,则进入__wait_event宏
} while (0)

#define __wait_event(wq, condition)                    \
    (void)___wait_event(wq, condition, TASK_UNINTERRUPTIBLE, 0, 0, schedule())

将__wait_event()宏展开如下所示:

 246 #define ___wait_event(wq_head, condition, state, exclusive, ret, cmd)       \
 247 ({                                      \
 248     __label__ __out;                            \
 249     struct wait_queue_entry __wq_entry;                 \
 250     long __ret = ret;   /* explicit shadow */               \
 
 251     /*定义并且初始化等待队列项,后面会将这个等待队列项加入等待队列当中,
          同时在初始化的过程中,会定义func函数的调用函数autoremove_wake_function, 
          该函数会调用default_wake_function函数*/                    
 252     init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0);    \
 253     for (;;) {    
 			/*调用prepare_to_wait_event函数,将等待项加入等待队列当中,
 			并将进程状态置为不可中断TASK_UNINTERRUPTIBLE;*/                          \
 254         long __int = prepare_to_wait_event(&wq_head, &__wq_entry, state);\
 255                                         \
 256         if (condition)                          \  //继续判断条件是否满足          
 257             break;                          \
 258                                         \
 259         if (___wait_is_interruptible(state) && __int) {         \
 260             __ret = __int;                      \
 261             goto __out;                     \
 262         }                               \
 263                                         \
 264         cmd;         \  //schedule() ,如果不满足,则交出CPU的控制权,使当前进程进入休眠状态                                                              
 265     }                                   \
 
 		/**如果condition满足,即没有进入休眠状态,跳出上面的for循环,便会将该等待队列进程设置为可运行状态,并从其所在的等待队列头中删除    */    
 266     finish_wait(&wq_head, &__wq_entry);                   \  
 267 __out:  __ret;                                  \
 268 })

4.2.唤醒机制

  与wait_event函数对应的是wake_up函数,wake_up函数用于唤醒处于该等待队列的进程。wake_up()将会执行__wake_queue->func()唤醒函数,默认是default_wake_function(),default_wake_function()调用try_to_wake_up()将进程更改为可运行状态并设置待调度标记。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值