在SMP(對稱多處理器)環(huán)境下,每個CPU對應(yīng)一個run_queue(可執(zhí)行隊(duì)列)。如果一個進(jìn)程處于TASK_RUNNING狀態(tài)(可執(zhí)行狀態(tài)),則它會被加入到其中一個run_queue(且同一時刻僅會被加入到一個run_queue),以便讓調(diào)度程序安排它在這個run_queue對應(yīng)的CPU上面運(yùn)行。
一個CPU對應(yīng)一個run_queue這樣的設(shè)計(jì),其好處是:
1、一個持續(xù)處于TASK_RUNNING狀態(tài)的進(jìn)程總是趨于在同一個CPU上面運(yùn)行(其間,這個進(jìn)程可能被搶占、然后又被調(diào)度),這有利于進(jìn)程的數(shù)據(jù)被CPU所緩存,提高運(yùn)行效率;
2、各個CPU上的調(diào)度程序只訪問自己的run_queue,避免了競爭;
然而,這樣的設(shè)計(jì)也可能使得各個run_queue里面的進(jìn)程不均衡,造成“一些CPU閑著、一些CPU忙不過來”混亂局面。為了解決這個問題,load_balance(負(fù)載均衡)就登場了。
load_balance所需要做的事情就是,在一定的時機(jī),通過將進(jìn)程從一個run_queue遷移到另一個run_queue,來保持CPU之間的負(fù)載均衡。
這里的“均衡”二字如何定義?load_balance又具體要做哪些事情呢?對于不同調(diào)度策略(實(shí)時進(jìn)程 OR 普通進(jìn)程),有著不同的邏輯,需要分開來看。
實(shí)時進(jìn)程的負(fù)載均衡
實(shí)時進(jìn)程的調(diào)度是嚴(yán)格按照優(yōu)先級來進(jìn)行的。在單CPU環(huán)境下,CPU上運(yùn)行著的總是優(yōu)先級最高的進(jìn)程,直到這個進(jìn)程離開TASK_RUNNING狀態(tài),新任的“優(yōu)先級最高的進(jìn)程”才開始得到運(yùn)行。直到所有實(shí)時進(jìn)程都離開TASK_RUNNING狀態(tài),其他普通進(jìn)程才有機(jī)會得到運(yùn)行。(暫時忽略sched_rt_runtime_us和sched_rt_period_us的影響。)
推廣到SMP環(huán)境,假設(shè)有N個CPU,N個CPU上分別運(yùn)行著的也必須是優(yōu)先級最高的top-N個進(jìn)程。如果實(shí)時進(jìn)程不足N個,那么剩下的CPU才分給普通進(jìn)程去使用。對于實(shí)時進(jìn)程來說,這就是所謂的“均衡”。
實(shí)時進(jìn)程的優(yōu)先級關(guān)系是很嚴(yán)格的,當(dāng)優(yōu)先級最高的top-N個進(jìn)程發(fā)生變化時,內(nèi)核必須馬上響應(yīng):
1、如果這top-N個進(jìn)程當(dāng)中,有一個離開TASK_RUNNING狀態(tài)、或因?yàn)閮?yōu)先級被調(diào)低而退出top-N集團(tuán),則原先處于(N+1)位的那個進(jìn)程將進(jìn)入top-N。內(nèi)核需要遍歷所有的run_queue,把這個新任的top-N進(jìn)程找出來,然后立馬讓它開始運(yùn)行;
2、反之,如果一個top-N之外的實(shí)時進(jìn)程的優(yōu)先級被調(diào)高,以至于擠占了原先處于第N位的進(jìn)程,則內(nèi)核需要遍歷所有的run_queue,把這個被擠出top-N的進(jìn)程找出來,將它正在占用的CPU讓給新進(jìn)top-N的那個進(jìn)程去運(yùn)行;
在這幾種情況下,新進(jìn)入top-N的進(jìn)程和退出top-N的進(jìn)程可能原本并不在同一個CPU上,那么在它得到運(yùn)行之前,內(nèi)核會先將其遷移到退出top-N的進(jìn)程所在的CPU上。
具體來說,內(nèi)核通過pull_rt_task和push_rt_task兩個函數(shù)來完成實(shí)時進(jìn)程的遷移:
pull_rt_task – 把其他CPU的run_queue中的實(shí)時進(jìn)程pull過來,放到當(dāng)前CPU的run_queue中。被pull過來的實(shí)時進(jìn)程要滿足以下條件:
1、進(jìn)程是其所在的run_queue中優(yōu)先級第二高的(優(yōu)先級最高的進(jìn)程必定正在運(yùn)行,不需要移動);
2、進(jìn)程的優(yōu)先級比當(dāng)前run_queue中最高優(yōu)先級的進(jìn)程還要高;
3、進(jìn)程允許在當(dāng)前CPU上運(yùn)行(沒有親和性限制);
該函數(shù)會在以下時間點(diǎn)被調(diào)用:
1、發(fā)生調(diào)度之前,如果prev進(jìn)程(將要被替換下去的進(jìn)程)是實(shí)時進(jìn)程,且優(yōu)先級高于當(dāng)前run_queue中優(yōu)先級最高的實(shí)時進(jìn)程(這說明prev進(jìn)程已經(jīng)離開TASK_RUNNING狀態(tài)了,否則它不會讓位于比它優(yōu)先級低的進(jìn)程);
2、正在運(yùn)行的實(shí)時進(jìn)程優(yōu)先級被調(diào)低時(比如通過sched_setparam系統(tǒng)調(diào)用);
3、正在運(yùn)行的實(shí)時進(jìn)程變成普通進(jìn)程時(比如通過sched_setscheduler系統(tǒng)調(diào)用);
push_rt_task – 把當(dāng)前run_queue中多余的實(shí)時進(jìn)程推給其他run_queue。需要滿足以下條件:
1、每次push一個進(jìn)程,這個進(jìn)程的優(yōu)先級在當(dāng)前run_queue中是第二高的(優(yōu)先級最高的進(jìn)程必定正在運(yùn)行,不需要移動);
2、目標(biāo)run_queue上正在運(yùn)行的不是實(shí)時進(jìn)程(是普通進(jìn)程),或者是top-N中優(yōu)先級最低的實(shí)時進(jìn)程,且優(yōu)先級低于被push的進(jìn)程;
3、被push的進(jìn)程允許在目標(biāo)CPU上運(yùn)行(沒有親和性限制);
4、滿足條件的目標(biāo)run_queue可能存在多個(可能多個CPU上都沒有實(shí)時進(jìn)程在運(yùn)行),應(yīng)該選擇與當(dāng)前CPU最具親緣性的一組CPU中的第一個CPU所對應(yīng)的run_queue作為push的目標(biāo)(順著sched_domain–調(diào)度域–逐步往上,找到第一個包含目標(biāo)CPU的sched_domain。見后面關(guān)于sched_domain的描述);
該函數(shù)會在以下時間點(diǎn)被調(diào)用:
1、非正在運(yùn)行的普通進(jìn)程變成實(shí)時進(jìn)程時(比如通過sched_setscheduler系統(tǒng)調(diào)用);
2、發(fā)生調(diào)度之后(這時候可能有一個實(shí)時進(jìn)程被更高優(yōu)先級的實(shí)時進(jìn)程搶占了);
3、實(shí)時進(jìn)程被喚醒之后,如果不能馬上在當(dāng)前CPU上運(yùn)行(它不是當(dāng)前CPU上優(yōu)先級最高的進(jìn)程);
看起來,實(shí)時進(jìn)程的負(fù)載均衡對于每個CPU一個run_queue這種模式似乎有些別扭,每次需要選擇一個實(shí)時進(jìn)程,總是需要遍歷所有run_queue,在尚未能得到運(yùn)行的實(shí)時進(jìn)程之中找到優(yōu)先級最高的那一個。其實(shí),如果所有CPU共用同一個run_queue,就沒有這么多的煩惱了。為什么不這樣做呢?
1、在CPU對run_queue的競爭方面,“每個CPU去競爭每一個run_queue”比“每個CPU去競爭一個總的run_queue”略微好一些,因?yàn)楦偁幍牧6雀×耍?br /> 2、在進(jìn)程的移動方面,每個CPU一個run_queue這種模式其實(shí)也不能很好的把進(jìn)程留在同一個CPU上,因?yàn)閲?yán)格的優(yōu)先級關(guān)系使得進(jìn)程必須在出現(xiàn)不均衡時立刻被移動。不過,一些特殊情況下進(jìn)程的遷移還是有一定選擇面的。比如優(yōu)先級相同的時候就可以盡量不做遷移、push_rt_task的時候可以選擇跟當(dāng)前CPU最為親近的CPU去遷移。
普通進(jìn)程的負(fù)載均衡
可以看出,實(shí)時進(jìn)程的負(fù)載均衡性能是不會太好的。為了滿足嚴(yán)格的優(yōu)先級關(guān)系,絲毫的不均衡都是不能容忍的。所以一旦top-N的平衡關(guān)系發(fā)生變化,內(nèi)核就必須即時完成負(fù)載均衡,形成新的top-N的平衡關(guān)系。這可能會使得每個CPU頻繁去競爭run_queue、進(jìn)程頻繁被遷移。
而普通進(jìn)程則并不要求嚴(yán)格的優(yōu)先級關(guān)系,可以容忍一定程度的不均衡。所以普通進(jìn)程的負(fù)載均衡可以不必在進(jìn)程發(fā)生變化時即時完成,而采用一些異步調(diào)整的策略。
普通進(jìn)程的負(fù)載均衡在以下情況下會被觸發(fā):
1、當(dāng)前進(jìn)程離開TASK_RUNNING狀態(tài)(進(jìn)入睡眠或退出),而對應(yīng)的run_queue中已無進(jìn)程可用時。這時觸發(fā)負(fù)載均衡,試圖從別的run_queue中pull一個進(jìn)程過來運(yùn)行;
2、每隔一定的時間,啟動負(fù)載均衡過程,試圖發(fā)現(xiàn)并解決系統(tǒng)中不均衡;
另外,對于調(diào)用exec的進(jìn)程,它的地址空間已經(jīng)完全重建了,當(dāng)前CPU上已經(jīng)不會再緩存對它有用的信息。這時內(nèi)核也會考慮負(fù)載均衡,為它們找一個合適的CPU。
那么,對于普通進(jìn)程來說,“均衡”到底意味著什么呢?
在單CPU環(huán)境下,處于TASK_RUNNING狀態(tài)的進(jìn)程會以其優(yōu)先級為權(quán)重,瓜分CPU時間。優(yōu)先級越高的進(jìn)程,權(quán)重越高,分得的CPU時間也就越多。在CFS調(diào)度(完全公平調(diào)度,針對普通進(jìn)程的調(diào)度程序)中,這里的權(quán)重被稱作load。假設(shè)某個進(jìn)程的load為m,所有處于TASK_RUNNING狀態(tài)的進(jìn)程的load之和為M,那么這個進(jìn)程所能分到的CPU時間是m/M。比如系統(tǒng)中有兩個TASK_RUNNING狀態(tài)的進(jìn)程,一個load為1、一個load為2,總的load是1+2=3。則它們分到的CPU時間分別是1/3和2/3。
推廣到SMP環(huán)境,假設(shè)有N個CPU,那么一個load為m的進(jìn)程所能分到的CPU時間應(yīng)該是N*m/M(如果不是,則要么這個進(jìn)程擠占了別的進(jìn)程的CPU時間、要么是被別的進(jìn)程擠占)。對于普通進(jìn)程來說,這就是所謂的“均衡”。
那么,如何讓進(jìn)程能夠分到N*m/M的CPU時間呢?其實(shí),只需要把所有進(jìn)程的load平分到每一個run_queue上,使得每個run_queue的load(它上面的進(jìn)程的load之和)都等于M/N,這樣就好了。于是,每個run_queue的load就成了是否“均衡”的判斷依據(jù)。
下面看看load_balance里面做些什么。注意,不管load_balance是怎樣被觸發(fā)的,它總是在某個CPU上被執(zhí)行。而load_balance過程被實(shí)現(xiàn)得非常簡單,只需要從最繁忙(load最高)的run_queue中pull幾個進(jìn)程到當(dāng)前run_queue中(只pull,不push),使得當(dāng)前run_queue與最繁忙的run_queue得到均衡(使它們的load接近于所有run_queue的平均load),僅此而已。load_balance并不需要考慮所有run_queue全局的均衡,但是當(dāng)load_balance在各個CPU上分別得到運(yùn)行之后,全局的均衡也就實(shí)現(xiàn)了。這樣的實(shí)現(xiàn)極大程度減小了負(fù)載均衡的開銷。
load_balance的過程大致如下:
1、找出最繁忙的一個run_queue;
2、如果找到的run_queue比本地run_queue繁忙,且本地run_queue的繁忙程度低于平均水平,那么遷移幾個進(jìn)程過來,使兩個run_queue的load接近平均水平。反之則什么都不做;
在比較兩個run_queue繁忙程度的問題上,其實(shí)是很有講究的。這個地方很容易想當(dāng)然地理解為:把run_queue中所有進(jìn)程的load加起來,比較一下就OK了。而實(shí)際上,需要比較的往往并不是實(shí)時的load。
這就好比我們用top命令查看CPU占用率一樣,top命令默認(rèn)1秒刷新一次,每次刷新你將看到這1秒內(nèi)所有進(jìn)程各自對CPU的占用情況。這里的占用率是個統(tǒng)計(jì)值,假設(shè)有一個進(jìn)程在這1秒內(nèi)持續(xù)運(yùn)行了100毫秒,那么我們認(rèn)為它占用了10%的CPU。如果把1秒刷新一次改成1毫秒刷新一次呢?那么我們將有90%的機(jī)率看到這個進(jìn)程占用0%的CPU、10%的機(jī)率占用100%的CPU。而無論是0%、還是100%,都不是這個進(jìn)程真實(shí)的CPU占用率的體現(xiàn)。必須把一段時間以內(nèi)的CPU占用率綜合起來看,才能得到我們需要的那個值。
run_queue的load值也是這樣。有些進(jìn)程可能頻繁地在TASK_RUNNING和非TASK_RUNNING狀態(tài)之間變換,導(dǎo)致run_queue的load值不斷抖動。光看某一時刻的load值,我們是體會不到run_queue的負(fù)載情況的,必須將一段時間內(nèi)的load值綜合起來看才行。于是,run_queue結(jié)構(gòu)中維護(hù)了一個保存load值的數(shù)組:
unsigned long cpu_load[CPU_LOAD_IDX_MAX] (目前CPU_LOAD_IDX_MAX值為5)
每個CPU上,每個tick的時鐘中斷會調(diào)用到update_cpu_load函數(shù),來更新該CPU所對應(yīng)的run_queue的cpu_load值。這個函數(shù)值得羅列一下:
/* this_load就是run_queue實(shí)時的load值 */
unsigned long this_load = this_rq->load.weight;
for (i = 0, scale = 1; i < CPU_LOAD_IDX_MAX; i++, scale += scale) {
unsigned long old_load = this_rq->cpu_load[i];
unsigned long new_load = this_load;
/* 因?yàn)樽罱K結(jié)果是要除以scale的,這里相當(dāng)于上取整 */
if (new_load > old_load)
new_load += scale-1;
/* cpu_load[i] = old_load + (new_load - old_load) / 2^i */
this_rq->cpu_load[i] = (old_load*(scale-1) + new_load) >> i;
}
cpu_load[i] = old_load + (new_load – old_load) / 2^i。i值越大,cpu_load[i]受load的實(shí)時值的影響越小,代表著越長時間內(nèi)的平均負(fù)載情況。而cpu_load[0]就是實(shí)時的load。
盡管我們需要的是一段時間內(nèi)的綜合的負(fù)載情況,但是,為什么不是保存一個最合適的統(tǒng)計(jì)值,而要保存這么多的值呢?這是為了便于在不同場景下選擇不同的load。如果希望進(jìn)行進(jìn)程遷移,那么應(yīng)該選擇較小的i值,因?yàn)榇藭r的cpu_load[i]抖動比較大,容易發(fā)現(xiàn)不均衡;反之,如果希望保持穩(wěn)定,那么應(yīng)該選擇較大的i值。
那么,什么時候傾向于進(jìn)行遷移、什么時候又傾向于保持穩(wěn)定呢?這要從兩個維度來看:
第一個維度,是當(dāng)前CPU的狀態(tài)。這里會考慮三種CPU狀態(tài):
1、CPU剛進(jìn)入IDLE(比如說CPU上唯一的TASK_RUNNING狀態(tài)的進(jìn)程睡眠去了),這時候是很渴望馬上弄一個進(jìn)程過來運(yùn)行的,應(yīng)該選擇較小的i值;
2、CPU處于IDLE,這時候還是很渴望弄一個進(jìn)程過來運(yùn)行的,但是可能已經(jīng)嘗試過幾次都無果了,故選擇略大一點(diǎn)的i值;
3、CPU非IDLE,有進(jìn)程正在運(yùn)行,這時候就不太希望進(jìn)程遷移了,會選擇較大的i值;
第二個維度,是CPU的親緣性。離得越近的CPU,進(jìn)程遷移所造成的緩存失效的影響越小,應(yīng)該選擇較小的i值。比如兩個CPU是同一物理CPU的同一核心通過SMT(超線程技術(shù))虛擬出來的,那么它們的緩存大部分是共享的。進(jìn)程在它們之間遷移代價較小。反之則應(yīng)該選擇較大的i值。(后面將會看到linux通過調(diào)度域來管理CPU的親緣性。)
至于具體的i的取值,就是具體策略的問題了,應(yīng)該是根據(jù)經(jīng)驗(yàn)或?qū)嶒?yàn)結(jié)果得出來的,這里就不贅述了。
調(diào)度域
前面已經(jīng)多次提到了調(diào)度域(sched_domain)。在復(fù)雜的SMP系統(tǒng)中,為了描述CPU與CPU之間的親緣關(guān)系,引入了調(diào)度域。
兩個CPU之間的親緣關(guān)系主要有以下幾種:
1、超線程。超線程CPU是一個可以“同時”執(zhí)行幾個線程的CPU。就像操作系統(tǒng)通過進(jìn)程調(diào)度能夠讓多個進(jìn)程“同時”在一個CPU上運(yùn)行一樣,超線程CPU也是通過這樣的分時復(fù)用技術(shù)來實(shí)現(xiàn)幾個線程的“同時”執(zhí)行的。這樣做之所以能夠提高執(zhí)行效率,是因?yàn)镃PU的速度比內(nèi)存速度快很多(一個數(shù)量級以上)。如果cache不能命中,CPU在等待內(nèi)存的時間內(nèi)將無事可做,可以切換到其他線程去執(zhí)行。這樣的多個線程對于操作系統(tǒng)來說就相當(dāng)于多個CPU,它們共享著大部分的cache,非常之親近;
2、同一物理CPU上的不同核心?,F(xiàn)在的多核CPU大多屬于這種情況,每個CPU核心都有獨(dú)立執(zhí)行程序的能力,而它們之間也會共享著一些cache;
3、同一NUMA結(jié)點(diǎn)上的CPU;
4、不同NUMA結(jié)點(diǎn)上的CPU;
在NUMA(非一致性內(nèi)存體系)中,CPU和RAM以“結(jié)點(diǎn)”為單位分組。當(dāng)CPU訪問與它同在一個結(jié)點(diǎn)的“本地”RAM芯片時,幾乎不會有競爭,訪問速度通常很快。相反的,CPU訪問它所屬結(jié)點(diǎn)之外的“遠(yuǎn)程”RAM芯片就會非常慢。
(調(diào)度域可以支持非常復(fù)雜的硬件系統(tǒng),但是我們通常遇到的SMP一般是:一個物理CPU包含N個核心。這種情況下,所有CPU之間的親緣性都是相同的,引入調(diào)度域的意義其實(shí)并不大。)
進(jìn)程在兩個很親近的CPU之間遷移,代價較小,因?yàn)檫€有一部分cache可以繼續(xù)使用;在屬于同一NUMA結(jié)點(diǎn)上的兩個CPU之間遷移,雖然cache會全部丟失,但是好歹內(nèi)存訪問的速度是相同的;如果進(jìn)程在屬于不同NUMA結(jié)點(diǎn)的兩個CPU之間遷移,那么這個進(jìn)程將在新NUMA結(jié)點(diǎn)的CPU上被執(zhí)行,卻還是要訪問舊NUMA結(jié)點(diǎn)的內(nèi)存(進(jìn)程可以遷移,內(nèi)存卻沒法遷移),速度就要慢很多了。
通過調(diào)度域的描述,內(nèi)核就可以知道CPU與CPU的親緣關(guān)系。對于關(guān)系遠(yuǎn)的CPU,盡量少在它們之間遷移進(jìn)程;而對于關(guān)系近的CPU,則可以容忍較多一些的進(jìn)程遷移。
對于實(shí)時進(jìn)程的負(fù)載均衡,調(diào)度域的作用比較小,主要是在push_rt_task將當(dāng)前run_queue中的實(shí)時進(jìn)程推到其他run_queue時,如果有多個run_queue可以接收實(shí)時進(jìn)程,則按照調(diào)度域的描述,選擇親緣性最高的那個CPU對應(yīng)的run_queue(如果這樣的CPU有多個,那么約定選擇編號最小那一個)。所以,下面著重討論普通進(jìn)程的負(fù)載均衡。
首先,調(diào)度域具體是如何描述CPU之間的親緣關(guān)系的呢?假設(shè)系統(tǒng)中有兩個物理CPU、每個物理CPU有兩個核心、每個核心又通過超線程技術(shù)虛擬出兩個CPU,則調(diào)度域的結(jié)構(gòu)如下:
1、一個調(diào)度域是若干CPU的集合,這些CPU都是滿足一定的親緣關(guān)系的(比如至少是屬于同一NUMA結(jié)點(diǎn)的);
2、調(diào)度域之間存在層次關(guān)系,一個調(diào)度域可能包括多個子調(diào)度域,每個子調(diào)度域包含了父調(diào)度域的一個CPU子集,并且子調(diào)度域中的CPU滿足比父調(diào)度域更嚴(yán)格的親緣關(guān)系(比如父調(diào)度域中的CPU至少是屬于同一NUMA結(jié)點(diǎn)的,子調(diào)度域中的CPU至少是屬于同一物理CPU的);
3、每個CPU分別具有其對應(yīng)的一組sched_domain結(jié)構(gòu),這些調(diào)度域處于不同層次,但是都包含了這個CPU;
4、每個調(diào)度域被依次劃分成多個組,每個組代表調(diào)度域的一個CPU子集;
5、最低層次的調(diào)度域包含了親緣性最近的幾個CPU、而最低層次的調(diào)度組則只包含一個CPU;
對于普通進(jìn)程的負(fù)載均衡來說,在一個CPU上,每次觸發(fā)load_balance總是在某個sched_domain上進(jìn)行的。低層次的sched_domain包含的CPU有著較高的親緣性,將以較高的頻率被觸發(fā)load_balance;而高層次的sched_domain包含的CPU有著較低的親緣性,將以較低的頻率被觸發(fā)load_balance。為了實(shí)現(xiàn)這個,sched_domain里面記錄著每次load_balance的時間間隔,以及下次觸發(fā)load_balance的時間。
前面討論過,普通進(jìn)程的load_balance第一步是需要找出一個最繁忙的CPU,實(shí)際上這是通過兩個步驟來實(shí)現(xiàn)的:
1、找出sched_domain下最繁忙的一個sched_group(組內(nèi)的CPU對應(yīng)的run_queue的load之和最高);
2、從該sched_group下找出最繁忙的CPU;
可見,load_balance實(shí)際上是實(shí)現(xiàn)了對應(yīng)sched_domain下的sched_group之間的平衡。較高層次的sched_domain包含了很多CPU,但是在這個sched_domain上的load_balance并不直接解決這些CPU之間的負(fù)載均衡,而只是解決sched_group之間的平衡(這又是load_balance的一大簡化)。而最底層的sched_group是跟CPU一一對應(yīng)的,所以最終還是實(shí)現(xiàn)了CPU之間的平衡。
其他問題
CPU親和力
linux下的進(jìn)程可以通過sched_setaffinity系統(tǒng)調(diào)用設(shè)置進(jìn)程親和力,限定進(jìn)程只能在某些特定的CPU上運(yùn)行。負(fù)載均衡必須考慮遵守這個限制(前面也多次提到)。
遷移線程
前面說到,在普通進(jìn)程的load_balance過程中,如果負(fù)載不均衡,當(dāng)前CPU會試圖從最繁忙的run_queue中pull幾個進(jìn)程到自己的run_queue來。
但是如果進(jìn)程遷移失敗呢?當(dāng)失敗達(dá)到一定次數(shù)的時候,內(nèi)核會試圖讓目標(biāo)CPU主動push幾個進(jìn)程過來,這個過程叫做active_load_balance。這里的“一定次數(shù)”也是跟調(diào)度域的層次有關(guān)的,越低層次,則“一定次數(shù)”的值越小,越容易觸發(fā)active_load_balance。
這里需要先解釋一下,為什么load_balance的過程中遷移進(jìn)程會失敗呢?最繁忙run_queue中的進(jìn)程,如果符合以下限制,則不能遷移:
1、進(jìn)程的CPU親和力限制了它不能在當(dāng)前CPU上運(yùn)行;
2、進(jìn)程正在目標(biāo)CPU上運(yùn)行(正在運(yùn)行的進(jìn)程顯然是不能直接遷移的);
(此外,如果進(jìn)程在目標(biāo)CPU上前一次運(yùn)行的時間距離當(dāng)前時間很小,那么該進(jìn)程被cache的數(shù)據(jù)可能還有很多未被淘汰,則稱該進(jìn)程的cache還是熱的。對于cache熱的進(jìn)程,也盡量不要遷移它們。但是在滿足觸發(fā)active_load_balance的條件之前,還是會先試圖遷移它們。)
對于CPU親和力有限制的進(jìn)程(限制1),即使active_load_balance被觸發(fā),目標(biāo)CPU也不能把它push過來。所以,實(shí)際上,觸發(fā)active_load_balance的目的是要嘗試把當(dāng)時正在目標(biāo)CPU上運(yùn)行的那個進(jìn)程弄過來(針對限制2)。
在每個CPU上都會運(yùn)行一個遷移線程,active_load_balance要做的事情就是喚醒目標(biāo)CPU上的遷移線程,讓它執(zhí)行active_load_balance的回調(diào)函數(shù)。在這個回調(diào)函數(shù)中嘗試把原先因?yàn)檎谶\(yùn)行而未能遷移的那個進(jìn)程push過來。為什么load_balance的時候不能遷移,active_load_balance的回調(diào)函數(shù)中就可以了呢?因?yàn)檫@個回調(diào)函數(shù)是運(yùn)行在目標(biāo)CPU的遷移線程上的。一個CPU在同一時刻只能運(yùn)行一個進(jìn)程,既然這個遷移線程正在運(yùn)行,那么期望被遷移的那個進(jìn)程肯定不是正在被執(zhí)行的,限制2被打破。
當(dāng)然,在active_load_balance被觸發(fā),到回調(diào)函數(shù)在目標(biāo)CPU上被執(zhí)行之間,目標(biāo)CPU上的TASK_RUNNING狀態(tài)的進(jìn)程可能發(fā)生一些變化,所以回調(diào)函數(shù)發(fā)起遷移的進(jìn)程未必就只有之前因?yàn)橄拗?而未能被遷移的那一個,可能更多,也可能一個沒有。
評論