- 了解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状态。这样后面的进程调度便会调度到该进程,从而唤醒该进程继续执行。