解析Linux内核的同步与互斥机制(六)

源出处: http://www.startos.com/linux/tips/2011011921499_6.html


4.2 wake_up 的实现细节

  \kernel \sched.c

  /*

  * The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just

  * wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve

  * number) then we wake all the non-exclusive tasks and one exclusive task.

  *

  * There are circumstances in which we can try to wake a task which has already

  * started to run but is not in state TASK_RUNNING. try_to_wake_up() returns

  * zero in this (rare) case, and we handle it by continuing to scan the queue.

  */

  static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,

  int nr_exclusive, int sync, void *key)

  {

  struct list_head *tmp, *next;

  list_for_each_safe(tmp, next, &q->task_list) {

  wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);

  unsigned flags = curr->flags;

  if (curr->func(curr, mode, sync, key) &&

  (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)

  break;

  }

  }

  // 循环结束的条件包括list_for_each_safe本身、排他性(flags & WQ_FLAG_EXCLUSIVE)以及唤醒非0个进程,!--nr_exclusive非常巧妙,当传入0时,!--nr_exclusive的结果总是0,if条件不可能成立,就无法break,即表示唤醒所有的进程。对于非WQ_FLAG_EXCLUSIVE进程,由于(flags & WQ_FLAG_EXCLUSIVE)为0后,就不计算!--nr_exclusive,因此这个过程可以唤醒所有的非 WQ_FLAG_EXCLUSIVE进程。但遇到WQ_FLAG_EXCLUSEVE之后的任意进程无法唤醒。

  最终哪个进程运行是由 schedule决定的。

  /**

  * __wake_up - wake up threads blocked on a waitqueue.

  * @q: the waitqueue

  * @mode: which threads

  * @nr_exclusive: how many wake-one or wake-many threads to wake up

  * @key: is directly passed to the wakeup function

  */

  void fastcall __wake_up(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key)

  {

  unsigned long flags;

  spin_lock_irqsave(&q->lock, flags);

  __wake_up_common(q, mode, nr_exclusive, 0, key);

  //通用的wakeup,不可重入的,需要__wake_up对之进行封装

  spin_unlock_irqrestore(&q->lock, flags);

  }

  EXPORT_SYMBOL(__wake_up);

  根据int nr_exclusive值唤醒对应的进程,只是更改了进程的状态,具体何时运行由schedule决定。

  并没有将唤醒的进程从等待队列中删除,只有当schedule获得cpu时才从等待队列中删除。

  \include\linux\wait.h

  void FASTCALL(__wake_up(wait_queue_head_t *q, unsigned int mode, int nr, void *key));

  extern void FASTCALL(__wake_up_locked(wait_queue_head_t *q, unsigned int mode));

  extern void FASTCALL(__wake_up_sync(wait_queue_head_t *q, unsigned int mode, int nr));

  #define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)

  #define wake_up_nr(x, nr) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, nr, NULL)

  #define wake_up_all(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 0, NULL)

  #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

  #define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)

  #define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)

  各种wakeup通过宏定义的形式本质上就是一个函数__wake_up,但对外的接口不一样,这样对外的意义明确,相当于采用了默认参数,而不是在各个wakeup内部调用函数,省掉了函数调用的开销。在实现代码复用的同时保证了对外的明确接口,值得借鉴。

  #define wake_up_locked(x) __wake_up_locked((x), TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE)

  #define wake_up_interruptible_sync(x) __wake_up_sync((x),TASK_INTERRUPTIBLE, 1)

  5 独占等待和高级休眠

  5.1 独占等待

  当一个进程调用 wake_up 在等待队列上,所有的在这个队列上等待的进程被置为可运行的。 这在许多情况下是正确的做法。但有时,可能只有一个被唤醒的进程将成功获得需要的资源,而其余的将再次休眠。这时如果等待队列中的进程数目大,这可能严重降低系统性能。为此,内核开发者增加了一个“独占等待”选项。它与一个正常的睡眠有 2 个重要的不同:

  2 当等待队列入口设置了 WQ_FLAG_EXCLUSEVE 标志,它被添加到等待队列的尾部;否则,添加到头部。因为唤醒一个WQ_FLAG_EXCLUSEVE标志的进程后就不再唤醒其他任意类型的进程。添加在尾部可以保证FIFO。

  2 当 wake_up 被在一个等待队列上调用, 它在唤醒第一个有 WQ_FLAG_EXCLUSIVE 标志的进程后停止唤醒。但内核仍然会唤醒所有的非独占等待进程,因为所有的非WQ_FLAG_EXCLUSIVE进程在队头。

  采用独占等待要满足以下条件:

  2 希望对资源进行有效竞争;

  2 当资源可用时,唤醒一个进程就足够来完全消耗资源;

  2 所有使用该资源的进程都应采用统一的独占等待规则。

  使一个进程进入独占等待,可调用:

  void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state);

  注意:无法使用通用的wait_event 和它的变体宏函数来进行独占等待。

  此时需要使用休眠的高级特性,利用等待队列的prepare_to_wait_exclusive和finish_wait接口函数手动编写相关代码,

  5.2 高级休眠的基本步骤:

  (1)分配和初始化一个 wait_queue_t 结构, 随后将其添加到正确的等待队列。

  (2)设置进程状态,标记为休眠。TASK_RUNNING 意思是进程能够运行。有 2 个状态指示一个进程是在睡眠: TASK_INTERRUPTIBLE 和 TASK_UNTINTERRUPTIBLE。2.6 内核的驱动代码通常不需要直接操作进程状态。但如果需要这样做使用的代码是:

  void set_current_state(int new_state);

  在老的代码中, 你常常见到如此的东西:current->state = TASK_INTERRUPTIBLE; 但是象这样直接改变 current 是不推荐的,当数据结构改变时这样的代码将会失效。通过改变 current 状态,只改变了调度器对待进程的方式,但进程还未让出处理器。

  (3)最后一步是放弃处理器。 但必须先检查进入休眠的条件。如果不做检查会引入竞态: 如果决定休眠后,在做上述准备工作到真正调用schedule时,若等待的条件变为真,不对条件重新进行判断,则你可能错过唤醒且长时间休眠。因此典型的代码下:

  if (!condition) schedule();

  在调用schedule前,应对条件再次进行检查。

  (4)更改进程状态并将进程从等待队列中删除。

  如果代码是从 schedule 返回,则进程肯定处于TASK_RUNNING 状态。 如果不需睡眠而跳过对 schedule 的调用,必须将任务状态重置为 TASK_RUNNING。无论是否调用过schedule,都需要从等待队列中去除这个进程,否则它可能被多次唤醒。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值