我们知道内核有个全局变量叫着jiffies,同时有个跟它有关的宏定义叫着HZ。Jiffies依赖于CPU的定时器,每一个时钟tick,jiffies数值加1.一般HZ是一个整数,HZ个jiffies为1s钟。一般我们设置HZ为100,时钟中断的间隔为10ms,也就意味着jiffies的最小精确度为10ms。Schedulertick也是在这个时钟中断中完成的。
Timertick带来了好处,同时也有一些不如人意的地方。比如如果没有task可运行,CPU进入idle进程,并最终进入powercollapse状态。时钟中断会定期的将CPU从power collapse状态唤醒,然后又重新进入powercollapse状态。这是很没有必要的。
所以在linux kernel里面引入了NOHZ feature,通过CONFIG_NO_HZ_COMMON来控制。Nohz就是关闭idle CPU上的时钟中断。这样不会存在定期的时钟中断。在必要的时候通过IPI_RESCHEDULE让idle的cpu退出powercollapse。
在trigger_load_balance函数中。
#ifdefCONFIG_NO_HZ_COMMON
if (nohz_kick_needed(rq))
nohz_balancer_kick();
#endif
nohz_kick_needed用于检查是否需要kick nohz load balance,就是检查当前CPU是否负载过高等等情况,是否需要唤醒一个idle CPU让其pull 一些进程过去,分担一下压力。
- static void nohz_balancer_kick(void)
- {
- int ilb_cpu;
- nohz.next_balance++;
- ilb_cpu = find_new_ilb();
-
- if (ilb_cpu >= nr_cpu_ids)
- return;
- if (test_and_set_bit(NOHZ_BALANCE_KICK, nohz_flags(ilb_cpu)))
- return;
- /*
- * Use smp_send_reschedule() instead of resched_cpu().
- * This way we generate a sched IPI on the target cpu which
- * is idle. And the softirq performing nohz idle load balance
- * will be run before returning from the IPI.
- */
- smp_send_reschedule(ilb_cpu);
- return;
- }
- void handle_IPI(int ipinr, struct pt_regs *regs)
- {
- unsigned int cpu = smp_processor_id();
- struct pt_regs *old_regs = set_irq_regs(regs);
- if ((unsigned)ipinr < NR_IPI) {
- trace_ipi_entry_rcuidle(ipi_types[ipinr]);
- __inc_irq_stat(cpu, ipi_irqs[ipinr]);
- }
- switch (ipinr) {
- case IPI_RESCHEDULE:
- scheduler_ipi();
- break;
voidscheduler_ipi(void)
{
…
irq_enter();
sched_ttwu_pending();
/*
* Check if someone kicked us for doingthe nohz idle load balance.
*/
if (unlikely(got_nohz_idle_kick())) {
this_rq()->idle_balance = 1;
raise_softirq_irqoff(SCHED_SOFTIRQ);
}
irq_exit();
首先调用sched_ttwu_pending();将pending的一些在当前rq的wake_list的tasks激活。
然后调用raise_softirq_irqoff(SCHED_SOFTIRQ);发起一个软中断。关于SCHED_SOFTIRQ在前面已经讲到了。
open_softirq(SCHED_SOFTIRQ,run_rebalance_domains)
再次回到run_rebalance_domains函数。
staticvoid run_rebalance_domains(struct softirq_action *h)
{
struct rq *this_rq = this_rq();
enum cpu_idle_type idle =this_rq->idle_balance ?
CPU_IDLE : CPU_NOT_IDLE;
/*
* If this cpu has a pendingnohz_balance_kick, then do the
* balancing on behalf of the otheridle cpus whose ticks are
* stopped. Do nohz_idle_balance*before* rebalance_domains to
* give the idle cpus a chance to loadbalance. Else we may
* load balance only within the localsched_domain hierarchy
* and abort nohz_idle_balancealtogether if we pull some load.
*/
nohz_idle_balance(this_rq, idle);
rebalance_domains(this_rq, idle);
}
这一次主要看第一个函数nohz_idle_balance。其中rebalance_domains为当前cpu做idle load balance。nohz_idle_balance检查this rq之外的idle cpu是否需要做idle load balance。
关于rebalance_domains(rq,CPU_IDLE)在上一篇loadbalance中已经详细讲解了。
这边讲一下如何wake up一个nohz 的idle cpu。
在load_balance里面attach_tasks用于将需要migrate的tasks插入到remote cpu相应的rq里面。
staticvoid attach_task(struct rq *rq, struct task_struct *p)
{
lockdep_assert_held(&rq->lock);
BUG_ON(task_rq(p) != rq);
p->on_rq = TASK_ON_RQ_QUEUED;
activate_task(rq, p, 0);
check_preempt_curr(rq, p, 0);
}
check_preempt_curr用于检查是否需要抢占rq上的curr task。
- void check_preempt_curr(struct rq *rq, struct task_struct *p, int flags)
- {
- const struct sched_class *class;
-
- if (p->sched_class == rq->curr->sched_class) {
- rq->curr->sched_class->check_preempt_curr(rq, p, flags);
- } else {
- for_each_class(class) {
- if (class == rq->curr->sched_class)
- break;
- if (class == p->sched_class) {
- resched_curr(rq);
- break;
- }
- }
- }
- void resched_curr(struct rq *rq)
- {
- struct task_struct *curr = rq->curr;
- int cpu;
-
- lockdep_assert_held(&rq->lock);
-
- if (test_tsk_need_resched(curr))
- return;
- cpu = cpu_of(rq);
- if (cpu == smp_processor_id()) {
- set_tsk_need_resched(curr);
- set_preempt_need_resched();
- return;
- }
- if (set_nr_and_not_polling(curr))
- smp_send_reschedule(cpu);
- else
- trace_sched_wake_idle_without_ipi(cpu);
- }