溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

linux內(nèi)核中如何切換到nohz與hres

發(fā)布時間:2021-11-02 10:29:14 來源:億速云 閱讀:187 作者:柒染 欄目:系統(tǒng)運維

本篇文章為大家展示了linux內(nèi)核中如何切換到nohz與hres,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

設(shè)計linux內(nèi)核的那幫家伙想的可真周到啊,前面說過,linux內(nèi)核的性格就是激情,只要硬件設(shè)計的足夠靈活,那么設(shè)計者就會盡可能的發(fā)揮,不放過任 何可自由發(fā)揮的點和死角,而且他們從來不管后果,有時還毅然拋棄硬件的建議,***內(nèi)核設(shè)計linux內(nèi)核的那幫家伙想的可真周到啊,前面說過,linux內(nèi)核的性格就是激情,只要硬件設(shè)計的足夠靈活,那么設(shè)計者就會盡可能的發(fā)揮,不放過任 何可自由發(fā)揮的點和死角,而且他們從來不管后果,有時還毅然拋棄硬件的建議,***內(nèi)核的nohz可謂是一項創(chuàng)舉。時鐘中斷是計算機系統(tǒng)必須的,就像人必須 有心跳一樣,人的心跳是周期的,計算機系統(tǒng)的“心跳”也是周期的,因此,時鐘中斷每隔固定的時間就會發(fā)生。

真的是這樣嗎?linux內(nèi)核的設(shè)計者認為如果cpu在空閑態(tài),那么就沒有必要心跳了,畢竟計算機不是一個自組織系統(tǒng),能源全靠外界電源供給,而人是一個 自組織實體,因此人必須要有周期的心跳來自己產(chǎn)生能量,計算機的外界電源只要不斷,加上時鐘可編程,那么非周期心跳甚至心跳停止就是可能的,linux內(nèi) 核實現(xiàn)了這一點。在2.6.21內(nèi)核之前,時鐘中斷是周期的,在那之后引入了新的時鐘封裝結(jié)構(gòu)clock_event_device和 clocksource,于是可以更加靈活的實現(xiàn)自己設(shè)計的個性時鐘,這個個性時鐘就是nohz方式和hres方式。當然系統(tǒng)初 啟的時候時鐘中斷還是周期的,當timer_interrupt被調(diào)用的時候,就會觸發(fā)timer軟中斷,然后在接下來的軟中斷處理中找機會切到nohz 或者hres,具體代碼如下:

  1. void run_local_timers(void)  

  2. {  

  3. hrtimer_run_queues(); //優(yōu)先處理高精度時鐘隊列  

  4. raise_softirq(TIMER_SOFTIRQ); //觸發(fā)軟中斷,處理函數(shù)見下:  

  5. softlockup_tick();  

  6. }  

  7. static void run_timer_softirq(struct softirq_action *h)
    //軟中斷處理函數(shù)  

  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()) //如果已經(jīng)是了,就沒有必要切換了,直接返回  

  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負責(zé)。不能光一味的跟 蹤代碼,hres和nohz有何關(guān)聯(lián)呢又分別是什么意義呢?hres實際上也不是周期中斷的,而是很精確的確定中斷,用最近到時的hrtimer的觸發(fā)時 間來對時鐘編程從而在那個時間到來的時候觸發(fā)中斷,而nohz僅僅說明可以用非周期的時間對時鐘編程,對精度沒有要求。

在hres中,一切事物都由一個 hrtimer負責(zé),比如原來的節(jié)拍調(diào)度,統(tǒng)計當前進程的時間等操作直接在timer_interrupt進行,而hres模式下,上述操作專門有一個 hrtimer,當clock_event_device的event_handler執(zhí)行時(所有操作都被封裝進了 clock_event_device的event_handler,而此event_handler在切換到hres或者nohz的時候被賦值),該函 數(shù)遍歷所有的hrtimer,所有的hrtimer組織成紅黑樹,將到期的hrtimer鏈入一個鏈表,然后在軟中斷中執(zhí)行這個鏈表的hrtimer的回 調(diào)函數(shù),對于別的hrtimer則馬上執(zhí)行:所有hrtimer分為兩類,一類不能在軟中斷中執(zhí)行,屬于比較緊急的,另一個可以在軟中斷中執(zhí)行,屬于不那 么緊急的。對于純粹的nohz非hres模式,event_handler中還是傳統(tǒng)的處理方式,只不過下次中斷的時間可以任意編程。這種方式中,時間測量可以達到鈉秒的精度。

每當cpu執(zhí)行cpu_idle的時候,內(nèi)核就會找機會停掉系統(tǒng)的心跳,然后在適當時機觸發(fā)心跳,而不是周期的心跳,這個時機是什么呢?如果一切都由 hrtimer負責(zé)了,那么這個時機就是找出的最近到期的timer的到期時刻,雖然停掉了周期的時鐘中斷,但是別的硬件中斷是沒有停掉的,而硬件中斷可能觸發(fā)一些事件,比如調(diào)度,比如發(fā)布一個新的timer,因此,每次硬件中斷后都要檢查***的hrtimer的到期情況和重新調(diào)度請求,如果有那么馬上停 掉關(guān)心跳模式切出idle進程。下面的代碼體現(xiàn)了這一點,在每次進入硬件中斷處理的時候都要調(diào)用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模式由此來作為觸發(fā)下一
    中斷的時機參考。怎么理解呢?看看這個調(diào)用條件,只有在cpu處于idle狀態(tài)時
    才更新時間,因為cpu處于idle時可能已經(jīng)將周期時鐘停掉了,為了不遺失時
    間信息,必須在中斷中補上。  

  12. #endif  

nohz 模式下的中斷“幾乎”是周期的,nohz的字面意義就是非周期,但是它還是基本周期的,因為它沒有任何下一個時鐘中斷的時間點依據(jù);但是hres卻是完全 隨機時鐘中斷的,因為它的event_handler中就是操作紅黑樹上的hrtimer們,因此,它完全可以將下一個到期的hrtimer的到期時刻作為下一個觸發(fā)時鐘中斷的時刻,要知道在hres模式里面,所有的時間相關(guān)的操作比如計時,節(jié)拍調(diào)度等都是由hrtimer負責(zé)的,如果要選擇下一次觸發(fā)時 鐘中斷的時機就不能在某一個hrtimer的處理函數(shù)里面仲裁了,而必須在全局的處理所有的hrtimer的event_handler函數(shù)里面仲裁,這 就是一切。我們看一下cpu_idle:

void cpu_idle(void)  {  int cpu = smp_processor_id();  current_thread_info()->status |= TS_POLLING;  /* endless idle loop with no priority at all */  while (1) {  tick_nohz_stop_sched_tick(1);   while (!need_resched()) {  check_pgt_cache();  rmb();  if (rcu_pending(cpu))  rcu_check_callbacks(cpu, 0);  if (cpu_is_offline(cpu))  play_dead();  local_irq_disable();  __get_cpu_var(irq_stat).idle_timestamp = jiffies;  /* Don't trace irqs off for idle */  stop_critical_timings();  pm_idle();  start_critical_timings();  }  tick_nohz_restart_sched_tick();  preempt_enable_no_resched();  schedule();  preempt_disable();  }  }

其中tick_nohz_stop_sched_tick里面調(diào)用了next_jiffies = get_next_timer_interrupt(last_jiffies);這一句,此句的意思就是找出下一個最近的timer或者hrtimer 用來將其到期時間作為下一個時鐘中斷的時間。在tick_nohz_stop_sched_tick中當然要檢查重新調(diào)度標志,如果置位那么馬上返回不再 nohz了,其實在每個硬件中斷后的irq_exit里都要調(diào)用tick_nohz_stop_sched_tick函數(shù)用來在可能的情況下重新對時鐘編 程。

看來linux的設(shè)計者考慮的就是周到,這又是一個瘋狂的使用并且靈活的發(fā)揮硬件作用的例子,linux本身不區(qū)分中斷優(yōu)先級在某種意義上縱容了nohz 和hres的出現(xiàn)和發(fā)展,如果有一天linux內(nèi)核變得規(guī)則了,有原則了,像windows一樣了或者說向unix靠齊了,那么linux的時代也就過去 了,它的性格也就磨平了。

附加:調(diào)度相關(guān)的hrtimer內(nèi)核有兩個地方調(diào)用了調(diào)度類的task_tick函數(shù),就是在時鐘中斷(不考慮nohz和hres)和每運行隊列的hrtimer的hrtick處理函數(shù)中:

  1. void scheduler_tick(void)  

  2. {  

  3. int cpu = smp_processor_id();  

  4. struct rq *rq = cpu_rq(cpu);  

  5. struct task_struct *curr = rq->curr;  

  6. sched_clock_tick();  

  7. spin_lock(&rq->lock);  

  8. update_rq_clock(rq);  

  9. update_cpu_load(rq);  

  10. curr->sched_class->task_tick(rq, curr, 0); //注意參數(shù)  

  11. spin_unlock(&rq->lock);  

  12. #ifdef CONFIG_SMP  

  13. rq->idle_at_tick = idle_cpu(cpu);  

  14. trigger_load_balance(rq, cpu);  

  15. #endif  

  16. }  

  17. static enum hrtimer_restart hrtick(struct hrtimer *timer)  

  18. {  

  19. struct rq *rq = container_of(timer, struct rq, hrtick_timer);  

  20. WARN_ON_ONCE(cpu_of(rq) != smp_processor_id());  

  21. spin_lock(&rq->lock);  

  22. update_rq_clock(rq);  

  23. rq->curr->sched_class->task_tick(rq, rq->curr, 1); //注意參數(shù)  

  24. spin_unlock(&rq->lock);  

  25. return HRTIMER_NORESTART;  

  26. }  

  27. 以fair調(diào)度類為例,其task_tick為task_tick_fair,其中按調(diào)度組向上調(diào)
    用了entity_tick:  

  28. static void entity_tick(struct cfs_rq *cfs_rq, struct sched_
    entity *curr, int queued)  

  29. {  

  30. update_curr(cfs_rq);  

  31. #ifdef CONFIG_SCHED_HRTICK  

  32. if (queued) {  

  33. resched_task(rq_of(cfs_rq)->curr); // 在hrtimer相關(guān)的task_tick的
    參數(shù)為1正是這里的情況,強行調(diào)度然后返回,這么猛干嘛啊?要理解這里的方式就
    要理解每隊列 hrtimer 的作用,此hrtimer專門負責(zé)記錄一個調(diào)度時機,該時機
    必須要調(diào)度,為何一定要調(diào)度呢?因為在計算這個時機并設(shè)置hrtimer的時候要先
    計算當前進 程還能運行多久,在過了這個時間后hrtimer到期,強制調(diào)度,也就
    是說只要到了hrtick,那就意味著一次調(diào)度馬上發(fā)生  

  34. return;  

  35. }  

  36. if (!sched_feat(DOUBLE_TICK) && 
    //如果上述的hrtimer正在計時,那么就用hrtimer的方式,不再向下進行了。  

  37. hrtimer_active(&rq_of(cfs_rq)->hrtick_timer))  

  38. return;  

  39. #endif  

  40. if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT)) 
    //否則到此處進行常規(guī)的更新,檢查,調(diào)度。  

  41. check_preempt_tick(cfs_rq, curr);  

為 何附上這么一段呢?因為每隊列的hrtimer要調(diào)用task_tick,而如果event_handler中還是要走到task_tick,兩個地方做 一件事豈不多余,實際上只有一個地方進行了真正的task_tick,從上面的代碼就可以看出來,如果是常規(guī)的task_tick進入,那么檢查到if (queued) {或者if (!sched_feat(DOUBLE_TICK) &&...的時候如果有每隊列hrtimer活動的話,就直接返回了,不會處理下去了,因此可以看出并沒有重復(fù)??纯丛趺丛O(shè)置每隊列的 hrtimer吧:

  1. static void hrtick_start_fair(struct rq *rq, 
    struct task_struct *p)  

  2. {  

  3. struct sched_entity *se = &p->se;  

  4. struct cfs_rq *cfs_rq = cfs_rq_of(se);  

  5. WARN_ON(task_rq(p) != rq);  

  6. if (hrtick_enabled(rq) && cfs_rq->nr_running > 1) {  

  7. u64 slice = sched_slice(cfs_rq, se); 
    //由weight計算出這個進程應(yīng)該運行多久  

  8. u64 ran = se->sum_exec_runtime - se->prev_sum_exec_runtime; 
    //計算這個進程實際運行了多久  

  9. s64 delta = slice - ran; //計算二者之差  

  10. if (delta < 0) {  

  11. if (rq->curr == p) //若運行超時那么馬上調(diào)度  

  12. resched_task(p);  

  13. return;  

  14. }  

  15. if (rq->curr != p)  

  16. delta = max_t(s64, 10000LL, delta);  

  17. hrtick_start(rq, delta); //否則設(shè)置定時期hrtimer  

  18. }  

上述內(nèi)容就是linux內(nèi)核中如何切換到nohz與hres,你們學(xué)到知識或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識儲備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI