最近看了linux内核中关于内核线程部分的代码,对kthread_stop和kthread_should_stop这两个API进行了学习,其中有了自己的一些见解,想把这部分的内容记录下来,以便日后查看。但是,kthread_stop这个API用到了completion,所以决定先将这部分内容记录一下,作为分析kthread_stop的准备工作吧。
一、completion的定义与常用API
1 、定义
-
struct completion{
-
unsigned short done;
-
wait_queue_head_t wait;
- }
在completion的定义中wait是一个等待队列队头,当有内核线程调用wait的时候,如果条件不满足,就会将该该线程添加该等待队列中。而completion的done成员,就是一个标记,用来标记这个条件的,当done为0的时候,条件不满足,非零的情况下,调用线程不需要等待。
2、completion的初始化
completion的初始化,分为静态初始化和动态初始化。静态初始化通过宏定义实现,源码如下:
-
#define COMPLETION_INITIALIZER(work) \
-
{ 0, __WAIT_QUEUE_HEAD_INITIALIZER((work).wait) }
-
#define DECLARE_COMPLETION(work) \
- struct completion work = COMPLETION_INITIALIZER(work)
动态初始化同样比较简单,实现如下:
-
static inline void init_completion(struct completion *x)
-
{
-
x->done = 0;
-
init_waitqueue_head(&x->wait);
- }
2、wait_for_completion
对completion对象调用wait操作的函数很多,我先列示如下,由于其操作本质上都是一样的,只不过在条件不满足的时候采取的动作不同罢了,所以,只选择一个函数wait_for_completion进行分析。
-
void wait_for_completion(struct completion *);
-
int wait_for_completion_interruptible(struct completion *x);
-
int 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);
wait_for_completion()这个函数实现的也是比较简单的,先列出其代码,然后在一一分析。
-
void __sched wait_for_completion(struct completion *x)
-
{
-
wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE);
-
}
-
static long __sched
-
wait_for_common(struct completion *x, long timeout, int state)
-
{
-
might_sleep();
-
-
spin_lock_irq(&x->wait.lock);
-
timeout = do_wait_for_common(x, timeout, state);
-
spin_unlock_irq(&x->wait.lock);
-
return timeout;
-
}
-
static inline long __sched
-
do_wait_for_common(struct completion *x, long timeout, int state)
-
{
-
if (!x->done) {
-
DECLARE_WAITQUEUE(wait, current);
-
-
wait.flags |= WQ_FLAG_EXCLUSIVE;
-
__add_wait_queue_tail(&x->wait, &wait);
-
do {
-
if (signal_pending_state(state, current)) {
-
timeout = -ERESTARTSYS;
-
break;
-
}
-
__set_current_state(state);
-
spin_unlock_irq(&x->wait.lock);
-
timeout = schedule_timeout(timeout);
-
spin_lock_irq(&x->wait.lock);
-
} while (!x->done && timeout);
-
__remove_wait_queue(&x->wait, &wait);
-
if (!x->done)
-
return timeout;
-
}
-
x->done--;
-
return timeout ?: 1;
- }
从上面内核源码可以很容看出,调用路径为wait_for_completion——>wait_for_common——>do_wait_for_common,这里wait_for_completion只是简单的讲了两个参数,一个timeout,其值为MAX_SCHEDULE_TIMEOUT,被定义为LONG_MAX,对理解wait_for_completion没什么影响。在do_wait_for_common函数中,可以看到,实际上是把当前进程加加入到了该complete变量x的等待队列中,这是在21行和22行中完成额。注意在21行设置了标记WQ_FLAG_EXCLUSIVE,这个标记实际上是避免“惊群现象”。默认情况下,当当等待时间满足时,会唤醒所有等待队列里面的任务,但只有其中一个任务会继续执行下去,其他的任务在被唤醒后又会再次等待。在设置了WQ_FLAG_EXCLUSIVE标记后,就不会发生这样的现象。具体怎么实现的可以查一查。在24行的signal_pending_state()函数中,实际上是为了检测进程是否可以休眠,如果有信号在pending状态,而且state状态还是UNINTERRUPTIBLE状态的话,是不能休眠等待的。果线程满足休眠的条件,就会到28行,更改线程的状态,调用schedule_timeout()函数,将当前线程休眠。在休眠之前释放自旋锁。
在32行,检测线程等待的条件是否满足。也就是done着这个标记是否被更改为非0值,或者是否等待时间到了,如果二者有一个条件满足,线程就不在等待,否则线程会再次等待。在34-35行,检测是否因为等待超时而被唤醒。如果执行37行,那么等待的条件以及满足了,当然这个条件是complete()设置了,这里实际行说已经与获得了自旋锁的状态了,所以37行才可以更改done。
4.complete() 和complete_all()
done这个标记的设置,实际上是在complete和complete_all这两个函数中设置,下面是这两个函数的源码。
点击(此处)折叠或打开
-
4591 * complete: - signals a single thread waiting on this completion
-
4592 * @x: holds the state of this particular completion
-
4593 *
-
4594 * This will wake up a single thread waiting on this completion. Threads will be
-
4595 * awakened in the same order in which they were queued.
-
4596 *
-
4597 * See also complete_all(), wait_for_completion() and related routines.
-
4598 *
-
4599 * It may be assumed that this function implies a write memory barrier before
-
4600 * changing the task state if and only if any tasks are woken up.
-
4601 */
-
4602 void complete(struct completion *x)
-
4603 {
-
4604 unsigned long flags;
-
4605
-
4606 spin_lock_irqsave(&x->wait.lock, flags);
-
4607 x->done++;
-
4608 __wake_up_common(&x->wait, TASK_NORMAL, 1, 0, NULL);
-
4609 spin_unlock_irqrestore(&x->wait.lock, flags);
-
4610 }
-
4611 EXPORT_SYMBOL(complete);
-
4612
-
4613 /**
-
4614 * complete_all: - signals all threads waiting on this completion
-
4615 * @x: holds the state of this particular completion
-
4616 *
-
4617 * This will wake up all threads waiting on this particular completion event.
-
4618 *
-
4619 * It may be assumed that this function implies a write memory barrier before
-
4620 * changing the task state if and only if any tasks are woken up.
-
4621 */
-
4622 void complete_all(struct completion *x)
-
4623 {
-
4624 unsigned long flags;
-
4625
-
4626 spin_lock_irqsave(&x->wait.lock, flags);
-
4627 x->done += UINT_MAX/2;
-
4628 __wake_up_common(&x->wait, TASK_NORMAL, 0, 0, NULL);
-
4629 spin_unlock_irqrestore(&x->wait.lock, flags);
-
4630 }
- 4631 EXPORT_SYMBOL(complete_all);
这两个函数逻辑很简单,只是修改done的值,然后唤醒等待线程。