天天看點

2.6.28核心的程序load_balance

程序負載均衡已經提到過不止一次了,這個特性很重要,因為有多個cpu,我們不能讓一個cpu過于空閑,當然也不能讓它過于繁忙,這就需要負載均衡來完成,前面寫過一篇文章簡單說明了一下負載均衡的政策,主要就是不能太頻繁做這件事,而且原則就是能不做盡量不做,在做負載均衡的時候,有個cpu_load數組很重要,那篇文章很細化,本文将從大架構上了解新核心的負載均衡的思想。

衆所周知,2.6.22核心以後就陸續引入了nohz這種節能的節拍方式,大體來說就是如果沒有任務的話,就不再頻繁的時鐘中斷了,而是将cpu徹底停掉,呵呵,當然不能掉電,而是執行halt,這個halt隻有中斷可以喚醒,nohz重要的就是将時鐘中斷停掉,那麼何時打開呢?新核心實作了hrtimer,這種時鐘非常精确,而且時鐘硬體也在進化,通過軟硬結合,我們可以将硬體程式設計,然後掃描系統中該cpu所屬的所有排隊的hrtimer,請求這個cpu的控制時鐘在這個hrtimer隊列中最早到期的hrtimer到期時來一次中斷,這樣就可以了,不過這種方式要考慮的東西不僅僅是這些,所有的觸發機制都要用hrtimer實作,從代碼中可以看到,原來在時鐘中斷中的處理現在都搬到了一個hrtimer中了,就連程序的睡眠喚醒也搬到了hrtimer裡面,cpu隻要開始執行idle之前就會判斷是否要停掉cpu的時鐘,如果最近的觸發時間離現在很遠,那麼停掉時鐘會省下不少電,另外,在每次中斷完成,也要檢查是否有程序需要運作,如果需要,那麼不再halt,而是排程程序,并且排入一個時鐘,什麼時鐘呢?就是這個程序運作時間夠了時要中斷它一下,否則它就要一直運作了,以前都是靠周期性的時鐘來完成的,現在是nohz了,是以就必須手工對硬體編一次程式,其實就是排入一個hrtimer,那麼這個程序運作多久怎麼計算呢?看看cfs的排程,裡面有,如果是普通的排程算法,比如O(1),那麼就是時間片了。

另外對于nohz,還有一個重要的東西,就是idle load balance,簡稱ilb,這個過程就是在多個cpu中,不能全部都進入nohz,因為這樣的話,可能時間長了,各個cpu負載會極端的不均衡,是以必須有一個cpu看家,這樣别的cpu才能安心睡眠,否則所有的cpu都隻能等待下次的喚醒事件了,其實别的cpu都進入nohz而長眠了,那麼那些cpu上就已經沒有什麼可以運作的程序了,是以load_balance的意義不是很大。這裡的意義在于,雖然别的cpu進入nohz了,那僅僅意味着其時鐘不再是周期的了,隻要有程序運作,那麼就有必要進行load_balance。如果系統所有的cpu都無事可做了,那麼這個ilb也就該去睡覺了。

再解釋一下上面的這一段的後半部分,為何ilb要幫别的cpu做load_balance呢?因為别的cpu都長睡了,進入nohz了,那麼cpu都進入長睡了,那還需要負載均衡幹甚,其實需要負載均衡的不是這些進入nohz的cpu,而是和它們一個domain的cpu們,也就是一個domain中還沒有長睡的cpu們需要負載均衡,本來這些事情都是需要這個長睡的cpu做的,這下倒好,叫起它沒有必要,這就違背了nohz的初衷了,于是必然有一個程序來承擔這一切阿,于是這就是ilb程序,其實就是一個cpu上的idle程序不進入nohz,而是做這個負載均衡。

static inline void trigger_load_balance(struct rq *rq, int cpu)

{

#ifdef CONFIG_NO_HZ //如果我們進入tick的時候有事可做,那麼就不再作為ilb了,ilb隻有idle可以

if (rq->in_nohz_recently && !rq->idle_at_tick) {

rq->in_nohz_recently = 0;

if (atomic_read(&nohz.load_balancer) == cpu) {

cpu_clear(cpu, nohz.cpu_mask);

atomic_set(&nohz.load_balancer, -1);

}

if (atomic_read(&nohz.load_balancer) == -1) {

int ilb = first_cpu(nohz.cpu_mask); //尋找一個ilb

if (ilb < nr_cpu_ids)

resched_cpu(ilb); //這個函數會發送ipi,進而喚醒那個選出來的程序,然後執行idle load balance

} //如果系統所有的cpu都進入nohz,那麼就不做load_balance了

if (rq->idle_at_tick && atomic_read(&nohz.load_balancer) == cpu && cpus_weight(nohz.cpu_mask) == num_online_cpus()) {

resched_cpu(cpu);

return;

} //如果這個cpu是idle,而且它不是ilb,那麼它就不必進行load_balance了

if (rq->idle_at_tick && atomic_read(&nohz.load_balancer) != cpu && cpu_isset(cpu, nohz.cpu_mask))

#endif

if (time_after_eq(jiffies, rq->next_balance))

raise_softirq(SCHED_SOFTIRQ);

可以看出ilb設計的精妙,idle再也不idle了,它也有了自己的任務,就是負載均衡。主要上面的倒數第二個和第三個if判斷,如果該cpu不是idle狀态,那麼也可以做laod_balance,這幾代核心将laod_balance作為softirq來執行了,效率自然有些不錯了,以往都是在時鐘中斷或者schedule排程器中執行的,很延遲的,那麼系統中就會有idle balance和non-idle balance兩種負載均衡,後者是傳統的負載均衡,而前者是應對nohz的負載均衡。有人提出一個更新檔,說是将timer遷移到ilb所在的cpu上,這樣就可以通過将timer的到期和ilb的周期喚醒的時間重合來減少在别的cpu上timer的喚醒,進而可以最小限度的影響nohz的狀态,達到節能,這個想法是不錯的,大動timer的代碼不現實,于是就專門寫了一個遷移timer的函數來執行就可以了,這樣本來需要在5秒後由于timer到期而被喚醒的nohz狀态的cpu的這個timer可以被遷移到這個ilb所在的cpu上,進而那個cpu不用被喚醒了,由于timer的精度小,這個cpu恰好周期執行的時候它到期,非常不錯,不過按照上面的那個函數,如果這個ilb需要執行timer了,那麼是需要再選出一個cpu作為ilb的執行者呢,還是讓這個cpu執行完timer之後繼續作為ilb的cpu,測試見分曉。

最後想一下,既然有non-idle balance,那麼就可以說明這些執行non-idle balance的cpu,也就是沒有進入nohz睡眠的cpu自己就可以進行負載均衡,那麼還何必用這些進入nohz的cpu參與呢?這裡有兩個意義,第一個就是load_balance是負載均衡的“拉”操作,也就是主動地将别的比較忙的cpu上的程序拉到這個進行load_balance的cpu上,第二個意義就是如果和進入nohz的cpu同domain的cpu們有一個或者多個都很忙的話,那麼是繼續為了省電而讓别的cpu置身于繁忙的工作呢還是喚醒這個處于idle-nohz的cpu分擔一些工作呢?答案當然是後者,因為如果就是為了省電,那麼你完全可以生活在男耕女織的時代,既然系統裡面有那麼多的cpu,那麼就要有效利用,那麼這種情況下,很抱歉,長睡的程序必須醒來來分擔一些工作了。拉方式的負載均衡就是嘗試在同排程域選擇一個最繁忙的cpu組,然後在這個組選擇一個最繁忙的cpu,最後将這個忙cpu上的任務分給這個執行拉平衡的cpu,在ilb的方式下,是ilb owner來代替所有的處于長睡的cpu執行load balance的,那麼執行拉平衡的cpu是ilb所在的cpu可是為忙cpu分擔任務的cpu卻是這個被代理的處于長睡的cpu,一旦遷移成功,那麼馬上就會用ipi喚醒這個處于nohz的長睡程序,于是這個程序就不再睡了,馬上投入工作

 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1273497

繼續閱讀