linux內(nèi)核中的個(gè)性時(shí)鐘nohz與hres
設(shè)計(jì)linux內(nèi)核的那幫家伙想的可真周到啊,前面說過,linux內(nèi)核的性格就是激情,只要硬件設(shè)計(jì)的足夠靈活,那么設(shè)計(jì)者就會(huì)盡可能的發(fā)揮,不放過任 何可自由發(fā)揮的點(diǎn)和死角,而且他們從來不管后果,有時(shí)還毅然拋棄硬件的建議,***內(nèi)核設(shè)計(jì)linux內(nèi)核的那幫家伙想的可真周到啊,前面說過,linux內(nèi)核的性格就是激情,只要硬件設(shè)計(jì)的足夠靈活,那么設(shè)計(jì)者就會(huì)盡可能的發(fā)揮,不放過任 何可自由發(fā)揮的點(diǎn)和死角,而且他們從來不管后果,有時(shí)還毅然拋棄硬件的建議,***內(nèi)核的nohz可謂是一項(xiàng)創(chuàng)舉。時(shí)鐘中斷是計(jì)算機(jī)系統(tǒng)必須的,就像人必須 有心跳一樣,人的心跳是周期的,計(jì)算機(jī)系統(tǒng)的“心跳”也是周期的,因此,時(shí)鐘中斷每隔固定的時(shí)間就會(huì)發(fā)生。
真的是這樣嗎?linux內(nèi)核的設(shè)計(jì)者認(rèn)為如果cpu在空閑態(tài),那么就沒有必要心跳了,畢竟計(jì)算機(jī)不是一個(gè)自組織系統(tǒng),能源全靠外界電源供給,而人是一個(gè) 自組織實(shí)體,因此人必須要有周期的心跳來自己產(chǎn)生能量,計(jì)算機(jī)的外界電源只要不斷,加上時(shí)鐘可編程,那么非周期心跳甚至心跳停止就是可能的,linux內(nèi) 核實(shí)現(xiàn)了這一點(diǎn)。在2.6.21內(nèi)核之前,時(shí)鐘中斷是周期的,在那之后引入了新的時(shí)鐘封裝結(jié)構(gòu)clock_event_device和 clocksource,于是可以更加靈活的實(shí)現(xiàn)自己設(shè)計(jì)的個(gè)性時(shí)鐘,這個(gè)個(gè)性時(shí)鐘就是nohz方式和hres方式。當(dāng)然系統(tǒng)初 啟的時(shí)候時(shí)鐘中斷還是周期的,當(dāng)timer_interrupt被調(diào)用的時(shí)候,就會(huì)觸發(fā)timer軟中斷,然后在接下來的軟中斷處理中找機(jī)會(huì)切到nohz 或者h(yuǎn)res,具體代碼如下:
- void run_local_timers(void)
- {
- hrtimer_run_queues(); //優(yōu)先處理高精度時(shí)鐘隊(duì)列
- raise_softirq(TIMER_SOFTIRQ); //觸發(fā)軟中斷,處理函數(shù)見下:
- softlockup_tick();
- }
- static void run_timer_softirq(struct softirq_action *h)
//軟中斷處理函數(shù)- {
- struct tvec_base *base = __get_cpu_var(tvec_bases);
- hrtimer_run_pending(); //這里有機(jī)會(huì)切換到nohz或者h(yuǎn)res
- if (time_after_eq(jiffies, base->timer_jiffies))
- __run_timers(base);
- }
- void hrtimer_run_pending(void)
- {
- struct hrtimer_cpu_base *cpu_base = &__get_cpu_var(hrtimer_bases);
- if (hrtimer_hres_active()) //如果已經(jīng)是了,就沒有必要切換了,直接返回
- return;
- if (tick_check_oneshot_change(!hrtimer_is_hres_enabled()))
//這個(gè)if判斷就是具體切換到hres或者nohz的代碼- hrtimer_switch_to_hres();
- run_hrtimer_pending(cpu_base);
- }
- int tick_check_oneshot_change(int allow_nohz)
- {
- struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
- if (!test_and_clear_bit(0, &ts->check_clocks))
//由此開始的種種判斷說明切換所需要到種種條件- return 0;
- if (ts->nohz_mode != NOHZ_MODE_INACTIVE)
- return 0;
- if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())
- return 0;
- if (!allow_nohz) //如果hres是允許的,那么返回1,這樣就會(huì)切換到hres高精度模式了
- return 1;
- tick_nohz_switch_to_nohz();
//如果沒有機(jī)會(huì)切換到高精度模式,前面種種驗(yàn)證均通過,這里最起碼切換到了nohz模式- return 0;
- }
hres 模式和nohz模式的具體切換由hrtimer_switch_to_hres和tick_nohz_switch_to_nohz負(fù)責(zé)。不能光一味的跟 蹤代碼,hres和nohz有何關(guān)聯(lián)呢又分別是什么意義呢?hres實(shí)際上也不是周期中斷的,而是很精確的確定中斷,用最近到時(shí)的hrtimer的觸發(fā)時(shí) 間來對時(shí)鐘編程從而在那個(gè)時(shí)間到來的時(shí)候觸發(fā)中斷,而nohz僅僅說明可以用非周期的時(shí)間對時(shí)鐘編程,對精度沒有要求。
在hres中,一切事物都由一個(gè) hrtimer負(fù)責(zé),比如原來的節(jié)拍調(diào)度,統(tǒng)計(jì)當(dāng)前進(jìn)程的時(shí)間等操作直接在timer_interrupt進(jìn)行,而hres模式下,上述操作專門有一個(gè) hrtimer,當(dāng)clock_event_device的event_handler執(zhí)行時(shí)(所有操作都被封裝進(jìn)了 clock_event_device的event_handler,而此event_handler在切換到hres或者nohz的時(shí)候被賦值),該函 數(shù)遍歷所有的hrtimer,所有的hrtimer組織成紅黑樹,將到期的hrtimer鏈入一個(gè)鏈表,然后在軟中斷中執(zhí)行這個(gè)鏈表的hrtimer的回 調(diào)函數(shù),對于別的hrtimer則馬上執(zhí)行:所有hrtimer分為兩類,一類不能在軟中斷中執(zhí)行,屬于比較緊急的,另一個(gè)可以在軟中斷中執(zhí)行,屬于不那 么緊急的。對于純粹的nohz非hres模式,event_handler中還是傳統(tǒng)的處理方式,只不過下次中斷的時(shí)間可以任意編程。這種方式中,時(shí)間測量可以達(dá)到鈉秒的精度。
每當(dāng)cpu執(zhí)行cpu_idle的時(shí)候,內(nèi)核就會(huì)找機(jī)會(huì)停掉系統(tǒng)的心跳,然后在適當(dāng)時(shí)機(jī)觸發(fā)心跳,而不是周期的心跳,這個(gè)時(shí)機(jī)是什么呢?如果一切都由 hrtimer負(fù)責(zé)了,那么這個(gè)時(shí)機(jī)就是找出的最近到期的timer的到期時(shí)刻,雖然停掉了周期的時(shí)鐘中斷,但是別的硬件中斷是沒有停掉的,而硬件中斷可能觸發(fā)一些事件,比如調(diào)度,比如發(fā)布一個(gè)新的timer,因此,每次硬件中斷后都要檢查***的hrtimer的到期情況和重新調(diào)度請求,如果有那么馬上停 掉關(guān)心跳模式切出idle進(jìn)程。下面的代碼體現(xiàn)了這一點(diǎn),在每次進(jìn)入硬件中斷處理的時(shí)候都要調(diào)用irq_enter:
- void irq_enter(void)
- {
- #ifdef CONFIG_NO_HZ
- int cpu = smp_processor_id();
- if (idle_cpu(cpu) && !in_interrupt())
- tick_nohz_stop_idle(cpu);
- #endif
- __irq_enter();
- #ifdef CONFIG_NO_HZ
- if (idle_cpu(cpu))
- tick_nohz_update_jiffies(); //更新計(jì)時(shí),nohz模式由此來作為觸發(fā)下一
中斷的時(shí)機(jī)參考。怎么理解呢?看看這個(gè)調(diào)用條件,只有在cpu處于idle狀態(tài)時(shí)
才更新時(shí)間,因?yàn)閏pu處于idle時(shí)可能已經(jīng)將周期時(shí)鐘停掉了,為了不遺失時(shí)
間信息,必須在中斷中補(bǔ)上。- #endif
- }
nohz 模式下的中斷“幾乎”是周期的,nohz的字面意義就是非周期,但是它還是基本周期的,因?yàn)樗鼪]有任何下一個(gè)時(shí)鐘中斷的時(shí)間點(diǎn)依據(jù);但是hres卻是完全 隨機(jī)時(shí)鐘中斷的,因?yàn)樗膃vent_handler中就是操作紅黑樹上的hrtimer們,因此,它完全可以將下一個(gè)到期的hrtimer的到期時(shí)刻作為下一個(gè)觸發(fā)時(shí)鐘中斷的時(shí)刻,要知道在hres模式里面,所有的時(shí)間相關(guān)的操作比如計(jì)時(shí),節(jié)拍調(diào)度等都是由hrtimer負(fù)責(zé)的,如果要選擇下一次觸發(fā)時(shí) 鐘中斷的時(shí)機(jī)就不能在某一個(gè)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);這一句,此句的意思就是找出下一個(gè)最近的timer或者h(yuǎn)rtimer 用來將其到期時(shí)間作為下一個(gè)時(shí)鐘中斷的時(shí)間。在tick_nohz_stop_sched_tick中當(dāng)然要檢查重新調(diào)度標(biāo)志,如果置位那么馬上返回不再 nohz了,其實(shí)在每個(gè)硬件中斷后的irq_exit里都要調(diào)用tick_nohz_stop_sched_tick函數(shù)用來在可能的情況下重新對時(shí)鐘編 程。#p#
看來linux的設(shè)計(jì)者考慮的就是周到,這又是一個(gè)瘋狂的使用并且靈活的發(fā)揮硬件作用的例子,linux本身不區(qū)分中斷優(yōu)先級(jí)在某種意義上縱容了nohz 和hres的出現(xiàn)和發(fā)展,如果有一天linux內(nèi)核變得規(guī)則了,有原則了,像windows一樣了或者說向unix靠齊了,那么linux的時(shí)代也就過去 了,它的性格也就磨平了。
附加:調(diào)度相關(guān)的hrtimer內(nèi)核有兩個(gè)地方調(diào)用了調(diào)度類的task_tick函數(shù),就是在時(shí)鐘中斷(不考慮nohz和hres)和每運(yùn)行隊(duì)列的hrtimer的hrtick處理函數(shù)中:
- void scheduler_tick(void)
- {
- int cpu = smp_processor_id();
- struct rq *rq = cpu_rq(cpu);
- struct task_struct *curr = rq->curr;
- sched_clock_tick();
- spin_lock(&rq->lock);
- update_rq_clock(rq);
- update_cpu_load(rq);
- curr->sched_class->task_tick(rq, curr, 0); //注意參數(shù)
- spin_unlock(&rq->lock);
- #ifdef CONFIG_SMP
- rq->idle_at_tick = idle_cpu(cpu);
- trigger_load_balance(rq, cpu);
- #endif
- }
- static enum hrtimer_restart hrtick(struct hrtimer *timer)
- {
- struct rq *rq = container_of(timer, struct rq, hrtick_timer);
- WARN_ON_ONCE(cpu_of(rq) != smp_processor_id());
- spin_lock(&rq->lock);
- update_rq_clock(rq);
- rq->curr->sched_class->task_tick(rq, rq->curr, 1); //注意參數(shù)
- spin_unlock(&rq->lock);
- return HRTIMER_NORESTART;
- }
- 以fair調(diào)度類為例,其task_tick為task_tick_fair,其中按調(diào)度組向上調(diào)
用了entity_tick:- static void entity_tick(struct cfs_rq *cfs_rq, struct sched_
entity *curr, int queued)- {
- update_curr(cfs_rq);
- #ifdef CONFIG_SCHED_HRTICK
- if (queued) {
- resched_task(rq_of(cfs_rq)->curr); // 在hrtimer相關(guān)的task_tick的
參數(shù)為1正是這里的情況,強(qiáng)行調(diào)度然后返回,這么猛干嘛啊?要理解這里的方式就
要理解每隊(duì)列 hrtimer 的作用,此hrtimer專門負(fù)責(zé)記錄一個(gè)調(diào)度時(shí)機(jī),該時(shí)機(jī)
必須要調(diào)度,為何一定要調(diào)度呢?因?yàn)樵谟?jì)算這個(gè)時(shí)機(jī)并設(shè)置hrtimer的時(shí)候要先
計(jì)算當(dāng)前進(jìn) 程還能運(yùn)行多久,在過了這個(gè)時(shí)間后hrtimer到期,強(qiáng)制調(diào)度,也就
是說只要到了hrtick,那就意味著一次調(diào)度馬上發(fā)生- return;
- }
- if (!sched_feat(DOUBLE_TICK) &&
//如果上述的hrtimer正在計(jì)時(shí),那么就用hrtimer的方式,不再向下進(jìn)行了。- hrtimer_active(&rq_of(cfs_rq)->hrtick_timer))
- return;
- #endif
- if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT))
//否則到此處進(jìn)行常規(guī)的更新,檢查,調(diào)度。- check_preempt_tick(cfs_rq, curr);
- }
為 何附上這么一段呢?因?yàn)槊筷?duì)列的hrtimer要調(diào)用task_tick,而如果event_handler中還是要走到task_tick,兩個(gè)地方做 一件事豈不多余,實(shí)際上只有一個(gè)地方進(jìn)行了真正的task_tick,從上面的代碼就可以看出來,如果是常規(guī)的task_tick進(jìn)入,那么檢查到if (queued) {或者if (!sched_feat(DOUBLE_TICK) &&...的時(shí)候如果有每隊(duì)列hrtimer活動(dòng)的話,就直接返回了,不會(huì)處理下去了,因此可以看出并沒有重復(fù)。看看怎么設(shè)置每隊(duì)列的 hrtimer吧:
- static void hrtick_start_fair(struct rq *rq,
struct task_struct *p)- {
- struct sched_entity *se = &p->se;
- struct cfs_rq *cfs_rq = cfs_rq_of(se);
- WARN_ON(task_rq(p) != rq);
- if (hrtick_enabled(rq) && cfs_rq->nr_running > 1) {
- u64 slice = sched_slice(cfs_rq, se);
//由weight計(jì)算出這個(gè)進(jìn)程應(yīng)該運(yùn)行多久- u64 ran = se->sum_exec_runtime - se->prev_sum_exec_runtime;
//計(jì)算這個(gè)進(jìn)程實(shí)際運(yùn)行了多久- s64 delta = slice - ran; //計(jì)算二者之差
- if (delta < 0) {
- if (rq->curr == p) //若運(yùn)行超時(shí)那么馬上調(diào)度
- resched_task(p);
- return;
- }
- if (rq->curr != p)
- delta = max_t(s64, 10000LL, delta);
- hrtick_start(rq, delta); //否則設(shè)置定時(shí)期hrtimer
- }
- }
【編輯推薦】