linux内核中的个性时钟nohz与hres(1)


设计linux内核的那帮家伙想的可真周到啊,前面说过,linux内核的性格就是激情,只要硬件设计的足够灵活,那么设计者就会尽可能的发挥,不放过任 何可自由发挥的点和死角,而且他们从来不管后果,有时还毅然抛弃硬件的建议,最新内核设计linux内核的那帮家伙想的可真周到啊,前面说过,linux内核的性格就是激情,只要硬件设计的足够灵活,那么设计者就会尽可能的发挥,不放过任 何可自由发挥的点和死角,而且他们从来不管后果,有时还毅然抛弃硬件的建议,最新内核的nohz可谓是一项创举。时钟中断是计算机系统必须的,就像人必须 有心跳一样,人的心跳是周期的,计算机系统的“心跳”也是周期的,因此,时钟中断每隔固定的时间就会发生。

真的是这样吗?linux内核的设计者认为如果cpu在空闲态,那么就没有必要心跳了,毕竟计算机不是一个自组织系统,能源全靠外界电源供给,而人是一个 自组织实体,因此人必须要有周期的心跳来自己产生能量,计算机的外界电源只要不断,加上时钟可编程,那么非周期心跳甚至心跳停止就是可能的,linux内 核实现了这一点。在2.6.21内核之前,时钟中断是周期的,在那之后引入了新的时钟封装结构clock_event_device和 clocksource,于是可以更加灵活的实现自己设计的个性时钟,这个个性时钟就是nohz方式和hres方式。当然系统初 启的时候时钟中断还是周期的,当timer_interrupt被调用的时候,就会触发timer软中断,然后在接下来的软中断处理中找机会切到nohz 或者hres,具体代码如下:

  1. void run_local_timers(void)  
  2. {  
  3. hrtimer_run_queues(); //优先处理高精度时钟队列  
  4. raise_softirq(TIMER_SOFTIRQ); //触发软中断,处理函数见下:  
  5. softlockup_tick();  
  6. }  
  7. static void run_timer_softirq(struct softirq_action *h)
    //软中断处理函数  
  8. {  
  9. struct tvec_base *base = __get_cpu_var(tvec_bases);  
  10. hrtimer_run_pending(); //这里有机会切换到nohz或者hres  
  11. if (time_after_eq(jiffies, base->timer_jiffies))  
  12. __run_timers(base);  
  13. }  
  14. void hrtimer_run_pending(void)  
  15. {  
  16. struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);  
  17. if (hrtimer_hres_active()) //如果已经是了,就没有必要切换了,直接返回  
  18. return;  
  19. if (tick_check_oneshot_change(!hrtimer_is_hres_enabled())) 
    //这个if判断就是具体切换到hres或者nohz的代码  
  20. hrtimer_switch_to_hres();  
  21. run_hrtimer_pending(cpu_base);  
  22. }  
  23. int tick_check_oneshot_change(int allow_nohz)  
  24. {  
  25. struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);  
  26. if (!test_and_clear_bit(0, &ts->check_clocks))
     //由此开始的种种判断说明切换所需要到种种条件  
  27. return 0;  
  28. if (ts->nohz_mode != NOHZ_MODE_INACTIVE)  
  29. return 0;  
  30. if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())  
  31. return 0;  
  32. if (!allow_nohz) //如果hres是允许的,那么返回1,这样就会切换到hres高精度模式了  
  33. return 1;  
  34. tick_nohz_switch_to_nohz(); 
    //如果没有机会切换到高精度模式,前面种种验证均通过,这里最起码切换到了nohz模式  
  35. return 0;  

hres 模式和nohz模式的具体切换由hrtimer_switch_to_hres和tick_nohz_switch_to_nohz负责。不能光一味的跟 踪代码,hres和nohz有何关联呢又分别是什么意义呢?hres实际上也不是周期中断的,而是很精确的确定中断,用最近到时的hrtimer的触发时 间来对时钟编程从而在那个时间到来的时候触发中断,而nohz仅仅说明可以用非周期的时间对时钟编程,对精度没有要求。

在hres中,一切事物都由一个 hrtimer负责,比如原来的节拍调度,统计当前进程的时间等操作直接在timer_interrupt进行,而hres模式下,上述操作专门有一个 hrtimer,当clock_event_device的event_handler执行时所有操作都被封装进了 clock_event_device的event_handler,而此event_handler在切换到hres或者nohz的时候被赋值),该函 数遍历所有的hrtimer,所有的hrtimer组织成红黑树,将到期的hrtimer链入一个链表,然后在软中断中执行这个链表的hrtimer的回 调函数,对于别的hrtimer则马上执行:所有hrtimer分为两类,一类不能在软中断中执行,属于比较紧急的,另一个可以在软中断中执行,属于不那 么紧急的。对于纯粹的nohz非hres模式,event_handler中还是传统的处理方式,只不过下次中断的时间可以任意编程。这种方式中,时间测量可以达到钠秒的精度。

每当cpu执行cpu_idle的时候,内核就会找机会停掉系统的心跳,然后在适当时机触发心跳,而不是周期的心跳,这个时机是什么呢?如果一切都由 hrtimer负责了,那么这个时机就是找出的最近到期的timer的到期时刻,虽然停掉了周期的时钟中断,但是别的硬件中断是没有停掉的,而硬件中断可能触发一些事件,比如调度,比如发布一个新的timer,因此,每次硬件中断后都要检查最新的hrtimer的到期情况和重新调度请求,如果有那么马上停 掉关心跳模式切出idle进程。下面的代码体现了这一点,在每次进入硬件中断处理的时候都要调用irq_enter:

  1. void irq_enter(void)  
  2. {  
  3. #ifdef CONFIG_NO_HZ  
  4. int cpu = smp_processor_id();  
  5. if (idle_cpu(cpu) && !in_interrupt())  
  6. tick_nohz_stop_idle(cpu);  
  7. #endif  
  8. __irq_enter();  
  9. #ifdef CONFIG_NO_HZ  
  10. if (idle_cpu(cpu))  
  11. tick_nohz_update_jiffies(); //更新计时,nohz模式由此来作为触发下一
    中断的时机参考。怎么理解呢?看看这个调用条件,只有在cpu处于idle状态时
    才更新时间,因为cpu处于idle时可能已经将周期时钟停掉了,为了不遗失时
    间信息,必须在中断中补上。  
  12. #endif  

nohz 模式下的中断“几乎”是周期的,nohz的字面意义就是非周期,但是它还是基本周期的,因为它没有任何下一个时钟中断的时间点依据;但是hres却是完全 随机时钟中断的,因为它的event_handler中就是操作红黑树上的hrtimer们,因此,它完全可以将下一个到期的hrtimer的到期时刻作为下一个触发时钟中断的时刻,要知道在hres模式里面,所有的时间相关的操作比如计时,节拍调度等都是由hrtimer负责的,如果要选择下一次触发时 钟中断的时机就不能在某一个hrtimer的处理函数里面仲裁了,而必须在全局的处理所有的hrtimer的event_handler函数里面仲裁,这 就是一切。我们看一下cpu_idle:

  1. void cpu_idle(void)  
  2. {  
  3. int cpu = smp_processor_id();  
  4. current_thread_info()->status |= TS_POLLING;  
  5. /* endless idle loop with no priority at all */  
  6. while (1) {  
  7. tick_nohz_stop_sched_tick(1);   
  8. while (!need_resched()) {  
  9. check_pgt_cache();  
  10. rmb();  
  11. if (rcu_pending(cpu))  
  12. rcu_check_callbacks(cpu, 0);  
  13. if (cpu_is_offline(cpu))  
  14. play_dead();  
  15. local_irq_disable();  
  16. __get_cpu_var(irq_stat).idle_timestamp = jiffies;  
  17. /* Don't trace irqs off for idle */  
  18. stop_critical_timings();  
  19. pm_idle();  
  20. start_critical_timings();  
  21. }  
  22. tick_nohz_restart_sched_tick();  
  23. preempt_enable_no_resched();  
  24. schedule();  
  25. preempt_disable();  
  26. }  

其中tick_nohz_stop_sched_tick里面调用了next_jiffies = get_next_timer_interrupt(last_jiffies);这一句,此句的意思就是找出下一个最近的timer或者hrtimer 用来将其到期时间作为下一个时钟中断的时间。在tick_nohz_stop_sched_tick中当然要检查重新调度标志,如果置位那么马上返回不再 nohz了,其实在每个硬件中断后的irq_exit里都要调用tick_nohz_stop_sched_tick函数用来在可能的情况下重新对时钟编 程。


相关内容

    暂无相关文章