現在網際網路公司使用的都是多cpu(多核)的伺服器了,linux作業系統會自動把任務分配到不同的處理器上,並盡可能的保持負載均衡。那linux核心是怎麼做到讓各個cpu的壓力均勻的呢?
做乙個負載均衡機制,重點在於:
1. 何時檢查並調整負載情況?
2. 如何調整負載?
先看第乙個問題。
如果讓我這樣的庸俗程式設計師來設計,我第乙個想到的就是每隔一段時間檢查一次負載是否均衡,不均則調整之,這肯定不是最高效的辦法,但肯定是實現上最簡單的。實際上,2.6.20版linux kernel的確使用軟中斷來定時調整多cpu上的壓力(呼叫函式run_rebalance_domains),每秒1次。
但每秒一次還是不能滿足要求,對很多應用來說,1秒太長了,一秒鐘內如果發生負載失衡對很多web應用都是不能接受的,何況其他實時應用。最好kernel能夠緊跟程序的變化來調整。
那麼,好,我們在程序建立和程序exit的時候檢查並調整負載呢?可以,但是不完整,乙個程序建立以後如果頻繁的睡眠、醒來、睡眠、醒來,它這樣折騰對cpu的負載是有影響的,你就不管它了嗎?說到底,我們其實關注的是程序是否在使用cpu,而不是它是否誕生了。所以,我們應該在程序睡眠和醒來這兩個時間點檢查cpu們的負載。
再看第二個問題,怎麼調整負載呢?從最繁忙的那個cpu上挪乙個程序到最閒的那個cpu上,如果負載還不均衡,就再挪乙個程序,如果還不均衡,繼續挪....這也是個最笨的方法,但它卻真的是linux cpu負載均衡的核心,不過實際的演算法在此基礎上有很多細化。對於intel的cpu,壓縮在同乙個chip上的多核是共享同乙個l2的(如下圖,裡面的乙個processor其實就是乙個chip),如果任務能盡可能的分配在同乙個chip上,l2 cache就可以繼續使用,這對執行速度是有幫助的。所以除非「很不均衡」,否則盡量不要把乙個chip上的任務挪到其他chip上。
於是,為了應對這種cpu core之間的異質性——在不同的core之間遷移任務,代價不同——linux kernel引入了sched_domain和sched_group的概念。sched_domain和sched_group的具體原理,可參考劉勃的文章
和英文資料
。smp負載均衡檢查或調整在兩個核心函式裡發生:
1. schedule()。當程序呼叫了sleep、usleep、poll、epoll、pause時,也就是呼叫了可能睡去的操作時都會轉為核心**裡對schedule()函式的呼叫。
2. try_to_wake_up() 。說白了就是程序剛才睡了,現在要醒來,那醒來以後跑在哪個cpu上呢?這個選擇cpu的過程,也就是負載均衡的過程。
我們先看schedule()的**,我們忽略函式前面那些和負載均衡無關的**(本文**以核心2.6.20版為準):
[kernel/sched.c --> schedule() ]
3489 cpu = smp_processor_id();
3490 if (unlikely(!rq->nr_running))
3498 }
每個cpu都有乙個執行佇列即這裡的
rq,執行佇列裡放著該cpu要執行的程序,如果執行佇列裡沒有程序了,就說明當前cpu沒有可排程的任務了,那就要呼叫idle_balance從其它cpu上「平衡」一些(就是挪一些)程序到當前rq裡。
再看 idle_balance()的實現:
[kernel/sched.c --> idle_balance()]
2806 /*
2807 * idle_balance is called by schedule() if this_cpu is about to become
2808 * idle. attempts to pull tasks from other cpus.
2809 */
2810 static void idle_balance(int this_cpu, struct rq *this_rq)
2811
2833 if (!pulled_task)
2834 /*
2835 * we are going idle. next_balance may be set based on
2836 * a busy processor. so reset next_balance.
2837 */
2838 this_rq->next_balance = next_balance;
2839 }
從子 sched_domain到父sched_domain遍歷該cpu對應的domain(2816行),並呼叫load_balance_newidle,我們繼續:
[kernel/sched.c --> load_balance_newidle()]
2730 static int
2731 load_balance_newidle(int this_cpu, struct rq *this_rq, struct sched_domain *sd)
2732
2758
2759 busiest = find_busiest_queue(group, newly_idle, imbalance,
2760 &cpus);
2761 if (!busiest)
2765
2766 bug_on(busiest == this_rq);
2767
2768 schedstat_add(sd, lb_imbalance[newly_idle], imbalance);
2769
2770 nr_moved = 0;
2771 if (busiest->nr_running > 1)
變數this_cpu和變數cpu有什麼區別?變數this_cpu是實際執行這個函式的處理器(「目標處理器」),而變數cpu是程序p在睡眠之前執行的處理器??為了方便我們暫且稱之為「源處理器」。當然,這兩個處理器也可能是同乙個,比如程序p在處理器a上執行,然後睡眠,而執行try_to_wake_up的也是處理器a,其實這樣就最好了,程序p在處理器a裡cache的資料都不用動,直接讓a執行p就行了??這就是1428行的邏輯。
如果this_cpu和cpu不是同乙個處理器,那麼**繼續:
1447 if (this_sd)
1483 }
計算出「目標處理器」和「源處理器」各自的負載(
1453行和1454行),再計算「目標處理器」上的每任務平均負載 tl_per_task,最後進行判斷:如果「目標處理器」的負載小於「源處理器」的負載且兩處理器負載相加都比 tl_per_task小的話,喚醒的程序轉為「目標處理器」執行。還有一種情況就是1474行的判斷,如果「目標處理器」的負載加上被喚醒的程序的負載後,還比「源處理器」的負載(乘以imbalance後)的小的話,也要把喚醒的程序轉為「目標處理器」執行。如果兩個因素都不滿足,那還是由p程序原來呆的那個cpu(即」源處理器「)繼續來處理吧。
有點兒繞,是吧?其實**雖繞,用意是簡單的:
1472行-1473行其實是這樣乙個用意:如果「目標處理器」的負載很小,小得即使把壓力全給到「源處理器」上去也不會超過「源處理器」上的平均任務負載,那麼這「目標處理器」的負載是真的很小,值得把p程序挪過來。
1474行的用意則是:如果我們真的把p程序挪到「目標處理器」以後,「目標處理器」的壓力也不比「源處理器」大多少,所以,還是值得一挪。
說來說去,還是那個笨原則:把任務從最忙的cpu那兒轉到很閒的cpu這兒。
我們已經看過了睡眠和醒來時的核心函式,那麼軟中斷裡的
run_rebalance_domains又幹了些什麼呢?其實也很簡單,它呼叫了load_balance函式,而這個函式和load_balance_newidle實現上基本一樣,就不累述了。
這裡沒有**程序優先順序和程序負載的計算方法,因為太複雜我也不太理解,以後看**如果有心得,再與大家分享。
linux在多核處理器上的負載均衡原理
現在網際網路公司使用的都是多cpu 多核 的伺服器了,linux作業系統會自動把任務分配到不同的處理器上,並盡可能的保持負載均衡。那linux核心是怎麼做到讓各個cpu的壓力均勻的呢?做乙個負載均衡機制,重點在於 1.何時檢查並調整負載情況?2.如何調整負載?先看第乙個問題。如果讓我這樣的庸俗程式設...
linux在多核處理器上的負載均衡原理 2
假如現在是 core 3 在執行idle balance,則先在domain 1裡找最忙的group,找到第二忙的group是core 0 core 4不在domain 1裡,所以不會找到它 再從core 0裡找最忙的runqueue 執行佇列 core 0就乙個執行佇列,所以直接就是它對應的run...
linux多核處理器上的負載均衡原理
看到一篇文章,大概給總結了下,就不貼了。現在很多伺服器都是多核伺服器了,linux作業系統會自動把任務分配到不同的處理器上,並盡可能的保持負載均衡。linux多核cpu上負載均衡兩個問題 1.何時檢查並調整負載情況?2.如何調整負載?第乙個問題,可以定時調整負載,這種方法雖然簡單,但不高效。實際上,...