Linux completion

  • 了解Linux completion

1.Introduction

  在Linux内核中,completion信号量是一个轻量级的机制,它允许一个线程告诉另一个线程某个工作已经做完了。线程(进程)之间的同步大多使用completion,而互斥资源的保护大多使用信号量(互斥锁or自旋锁)。completion在内核中的实现基于等待队列。要使用completion,必须在文件中包含<linux/completion.h>,同时创建一个类型为struct completion的变量。

1.1.struct completion

1 25 struct completion { 2 26 unsigned int done; 3 27 wait_queue_head_t wait; 4 28 }; 
  • unsigned int done;
    指示等待的事件是否完成。初始化时为0。如果为0,则表示等待的事件未完成。大于0表示等待的事件已经完成。

  • wait_queue_head_t wait;
    存放等待该事件完成的进程队列。

1.2.定义及其初始化一个信号量:

1.2.1.静态定义初始化一个信号量:

 44 #define DECLARE_COMPLETION(work) \ 45 struct completion work = COMPLETION_INITIALIZER(work) 

1.2.2.动态定义及初始化一个信号量:

1 static inline void init_completion(struct completion *x) 2 { 3 x->done = 0; 4 init_waitqueue_head(&x->wait); 5 } 

1.3.等待信号量的释放:

void wait_for_completion(struct completion *x); void wait_for_completion_interruptible(struct completion *x); void wait_for_completion_killable(struct completion *x); unsigned long wait_for_completion_timeout(struct completion *x, unsigned long timeout); unsigned long wait_for_completion_interruptible_timeout(struct completion *x, unsigned long timeout); bool try_wait_for_completion(struct completion *x); 

1.4.释放信号量(即唤醒等待的线程):

void complete(struct completion *x); void complete_all(struct completion *x); bool completion_done(struct completion *x); 
  • complete 唤醒阻塞在completion上的首个线程。
  • complete_all 唤醒阻塞在completion上的所有线程。它的实现手法很粗糙,把completion.done的值设为UINT_MAX/2,自然所有等待的线程都醒了。所以如果complete_all 之后还要使用这个completion,就要把它重新初始化。
  • completion_done检查是否有线程阻塞在completion上。但这个API并不准确,它只是检查completion.done是否为0,为0则认为有线程阻塞。这个API并不会去检查实际的等待队列,所以用时要注意。

2.机制分析

  同步函数一般都成对出现,completion也不例外,最基本的两个complete和wait_for_completion函数的实现。

2.1.wait_for_completion

 142 void __sched wait_for_completion(struct completion *x) 143 { 144 wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE); 145 } 103 static inline long __sched 104 __wait_for_common(struct completion *x, 105 long (*action)(long), long timeout, int state) 106 { 107 might_sleep(); 108 109 complete_acquire(x); 110 111 spin_lock_irq(&x->wait.lock); 112 timeout = do_wait_for_common(x, action, timeout, state); 113 spin_unlock_irq(&x->wait.lock); 114 115 complete_release(x); 116 117 return timeout; 118 } 76 static inline long __sched 77 do_wait_for_common(struct completion *x, 78 long (*action)(long), long timeout, int state) 79 { 80 if (!x->done) { 81 DECLARE_WAITQUEUE(wait, current); 82 83 __add_wait_queue_entry_tail_exclusive(&x->wait, &wait); 84 do { 85 if (signal_pending_state(state, current)) { 86 timeout = -ERESTARTSYS; 87 break; 88 } 89 __set_current_state(state); 90 spin_unlock_irq(&x->wait.lock); 91 timeout = action(timeout); 92 spin_lock_irq(&x->wait.lock); 93 } while (!x->done && timeout); 94 __remove_wait_queue(&x->wait, &wait); 95 if (!x->done) 96 return timeout; 97 } 98 if (x->done != UINT_MAX) 99 x->done--; 100 return timeout ?: 1; 101 } 

do_wait_for_common实现操作:

1.调用DECLARE_WAITQUEUE()定义等待队列wait,并将当前进程添加进等待队列wait。

 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) 

2.将wait添加进该完成量的等待队列的末尾,进入循环。

3.设置当前进程为不可中断状态(TASK_UNINTERRUPTIBLE),释放自旋锁并让当前进程进入睡眠状态。

4.一旦进程被调度唤醒,又获得自旋锁并查看等待的事件是否完成。如果完成(大于0),则从完成量的等待队列中删除等待的进程,并自减。

相似函数:

wait_for_completion_timeout:

  与wait_for_completion()最大的区别是它等待超时的情况下返回。也就是说如果经过给定的时间该完成量还没有被唤醒,就直接返回。这样最大的好处是经过一定的时间该进程已经不需要等待某事件,那么就可以直接被唤醒继续执行。

wait_for_completion_interruptible:

  等待完成量的方式是可以被信号打断的。如果当前进程收到 如果收到TIF_SIGPENDING信号,则等待该完成量的进程会被从等待队列中删除,并返回ERESTARTSYS。

wait_for_completion_interruptible_timeout:

  可中断的并且可超时返回的等待完成量。

2.2.completion

  唤醒阻塞在completion上的首个线程。

 31 void complete(struct completion *x) 32 { 33 unsigned long flags; 34 >> 35 spin_lock_irqsave(&x->wait.lock, flags); 36 37 /* 38 * Perform commit of crossrelease here. 39 */ >> 40 complete_release_commit(x); 41 >> 42 if (x->done != UINT_MAX) >> 43 x->done++; >> 44 __wake_up_locked(&x->wait, TASK_NORMAL, 1); >> 45 spin_unlock_irqrestore(&x->wait.lock, flags); 46 } >> 47 EXPORT_SYMBOL(complete); 156 void __wake_up_locked(struct wait_queue_head *wq_head, unsigned int mode, int nr) 157 { 158 __wake_up_common(wq_head, mode, nr, 0, NULL, NULL); 159 } 

  __wake_up_common循环遍历等待队列内的所有元素,分别执行其对应的唤醒函数。这里的唤醒函数即先前定义等待队列项DEFINE_WAIT(__wait)时默认初始化的default_wake_function函数,该函数最终调用try_to_wake_up函数将进程置为TASK_RUNNING状态。这样后面的进程调度便会调度到该进程,从而唤醒该进程继续执行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值