Chinaunix首页 | 论坛 | 博客
  • 博客访问: 152001
  • 博文数量: 45
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 273
  • 用 户 组: 普通用户
  • 注册时间: 2014-05-28 10:30
文章分类
文章存档

2017年(6)

2016年(3)

2015年(8)

2014年(28)

我的朋友

分类: LINUX

2017-08-02 20:08:45

最近看了linux内核中关于内核线程部分的代码,对kthread_stop和kthread_should_stop这两个API进行了学习,其中有了自己的一些见解,想把这部分的内容记录下来,以便日后查看。但是,kthread_stop这个API用到了completion,所以决定先将这部分内容记录一下,作为分析kthread_stop的准备工作吧。

一、completion的定义与常用API

1 、定义


  1. struct completion{
  2.     unsigned short done;
  3.     wait_queue_head_t wait;
  4. }

在completion的定义中wait是一个等待队列队头,当有内核线程调用wait的时候,如果条件不满足,就会将该该线程添加该等待队列中。而completion的done成员,就是一个标记,用来标记这个条件的,当done为0的时候,条件不满足,非零的情况下,调用线程不需要等待。

2、completion的初始化

completion的初始化,分为静态初始化和动态初始化。静态初始化通过宏定义实现,源码如下:


  1. #define COMPLETION_INITIALIZER(work) \
  2.     { 0, __WAIT_QUEUE_HEAD_INITIALIZER((work).wait) }
  3. #define DECLARE_COMPLETION(work) \
  4.     struct completion work = COMPLETION_INITIALIZER(work)


动态初始化同样比较简单,实现如下:


  1. static inline void init_completion(struct completion *x)
  2. {
  3.     x->done = 0;
  4.     init_waitqueue_head(&x->wait);
  5. }
无论是静态初始化还是动态初始化,都是干了两件事,一是把done初始化0,这样,二是对wait这个等待队列的对头进行初始化。通过将done初始化为0,这样任何一个对刚刚初始化的completion对象调用wait操作,条件都不满足,将都会阻塞。


2、wait_for_completion

对completion对象调用wait操作的函数很多,我先列示如下,由于其操作本质上都是一样的,只不过在条件不满足的时候采取的动作不同罢了,所以,只选择一个函数wait_for_completion进行分析。

  1. void wait_for_completion(struct completion *);
  2. int wait_for_completion_interruptible(struct completion *x);
  3. int wait_for_completion_killable(struct completion *x);
  4. unsigned long wait_for_completion_timeout(struct completion *x,
  5.                          unsigned long timeout);
  6. unsigned long wait_for_completion_interruptible_timeout(
  7.             struct completion *x, unsigned long timeout);
  8. bool try_wait_for_completion(struct completion *x);

wait_for_completion()这个函数实现的也是比较简单的,先列出其代码,然后在一一分析。

  1. void __sched wait_for_completion(struct completion *x)
  2. {
  3.     wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE);
  4. }
  5. static long __sched
  6. wait_for_common(struct completion *x, long timeout, int state)
  7. {
  8.     might_sleep();

  9.     spin_lock_irq(&x->wait.lock);
  10.     timeout = do_wait_for_common(x, timeout, state);
  11.     spin_unlock_irq(&x->wait.lock);
  12.     return timeout;
  13. }
  14. static inline long __sched
  15. do_wait_for_common(struct completion *x, long timeout, int state)
  16. {
  17.     if (!x->done) {
  18.         DECLARE_WAITQUEUE(wait, current);

  19.         wait.flags |= WQ_FLAG_EXCLUSIVE;
  20.         __add_wait_queue_tail(&x->wait, &wait);
  21.         do {
  22.             if (signal_pending_state(state, current)) {
  23.                 timeout = -ERESTARTSYS;
  24.                 break;
  25.             }
  26.             __set_current_state(state);
  27.             spin_unlock_irq(&x->wait.lock);
  28.             timeout = schedule_timeout(timeout);
  29.             spin_lock_irq(&x->wait.lock);
  30.         } while (!x->done && timeout);
  31.         __remove_wait_queue(&x->wait, &wait);
  32.         if (!x->done)
  33.             return timeout;
  34.     }
  35.     x->done--;
  36.     return timeout ?: 1;
  37. }


从上面内核源码可以很容看出,调用路径为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这两个函数中设置,下面是这两个函数的源码。


点击(此处)折叠或打开

  1.  4591 * complete: - signals a single thread waiting on this completion
  2.  4592 * @x: holds the state of this particular completion
  3.  4593 *
  4.  4594 * This will wake up a single thread waiting on this completion. Threads will be
  5.  4595 * awakened in the same order in which they were queued.
  6.  4596 *
  7.  4597 * See also complete_all(), wait_for_completion() and related routines.
  8.  4598 *
  9.  4599 * It may be assumed that this function implies a write memory barrier before
  10.  4600 * changing the task state if and only if any tasks are woken up.
  11.  4601 */
  12.  4602 void complete(struct completion *x)
  13.  4603 {
  14.  4604 unsigned long flags;
  15.  4605
  16.  4606 spin_lock_irqsave(&x->wait.lock, flags);
  17.  4607 x->done++;
  18.  4608 __wake_up_common(&x->wait, TASK_NORMAL, 1, 0, NULL);
  19.  4609 spin_unlock_irqrestore(&x->wait.lock, flags);
  20.  4610 }
  21.  4611 EXPORT_SYMBOL(complete);
  22.  4612
  23.  4613 /**
  24.  4614 * complete_all: - signals all threads waiting on this completion
  25.  4615 * @x: holds the state of this particular completion
  26.  4616 *
  27.  4617 * This will wake up all threads waiting on this particular completion event.
  28.  4618 *
  29.  4619 * It may be assumed that this function implies a write memory barrier before
  30.  4620 * changing the task state if and only if any tasks are woken up.
  31.  4621 */
  32.  4622 void complete_all(struct completion *x)
  33.  4623 {
  34.  4624 unsigned long flags;
  35.  4625
  36.  4626 spin_lock_irqsave(&x->wait.lock, flags);
  37.  4627 x->done += UINT_MAX/2;
  38.  4628 __wake_up_common(&x->wait, TASK_NORMAL, 0, 0, NULL);
  39.  4629 spin_unlock_irqrestore(&x->wait.lock, flags);
  40.  4630 }
  41.  4631 EXPORT_SYMBOL(complete_all);


 这两个函数逻辑很简单,只是修改done的值,然后唤醒等待线程。

阅读(2898) | 评论(3) | 转发(0) |
给主人留下些什么吧!~~

B_C_10242019-11-25 13:33:52

kirito19960111:作者您好:wait_for_completion()的第30行这里传入的参数是 MAX_SCHEDULE_TIMEOUT,据我所知这个值是个负值,那么进入schedule_timeout()后,进入case MAX_SCHEDULE_TIMEOUT:分支,返回值应该是0(MAX_SCHEDULE_TIMEOUT为负值,所以return那里返回0),那么在wait_for_completion()的第32行判断时,timeout直接是0不就直接跳出循环了吗,我不知道中间哪一步分析出现了问题,您能解答一下吗?对于该部分函数的实现,您这是最详细的了。

几年没看linux内核的东西了,已经忘记了。好像schedule_timeout()执行的过程中,进程就会被调度走了,下次调度回来的的时候schedule_timeout()才会继续返回,往后执行吧。

回复 | 举报

B_C_10242019-11-25 13:33:48

kirito19960111:作者您好:wait_for_completion()的第30行这里传入的参数是 MAX_SCHEDULE_TIMEOUT,据我所知这个值是个负值,那么进入schedule_timeout()后,进入case MAX_SCHEDULE_TIMEOUT:分支,返回值应该是0(MAX_SCHEDULE_TIMEOUT为负值,所以return那里返回0),那么在wait_for_completion()的第32行判断时,timeout直接是0不就直接跳出循环了吗,我不知道中间哪一步分析出现了问题,您能解答一下吗?对于该部分函数的实现,您这是最详细的了。

几年没看linux内核的东西了,已经忘记了。好像schedule_timeout()执行的过程中,进程就会被调度走了,下次调度回来的的时候schedule_timeout()才会继续返回,往后执行吧。

回复 | 举报

kirito199601112019-10-17 16:30:30

作者您好:wait_for_completion()的第30行这里传入的参数是 MAX_SCHEDULE_TIMEOUT,据我所知这个值是个负值,那么进入schedule_timeout()后,进入case MAX_SCHEDULE_TIMEOUT:分支,返回值应该是0(MAX_SCHEDULE_TIMEOUT为负值,所以return那里返回0),那么在wait_for_completion()的第32行判断时,timeout直接是0不就直接跳出循环了吗,我不知道中间哪一步分析出现了问题,您能解答一下吗?对于该部分函数的实现,您这是最详细的了。