本文出現(xiàn)的內(nèi)核代碼來(lái)自Linux5.4.28,為了減少篇幅,我們盡量不引用代碼,如果有興趣,讀者可以配合代碼閱讀本文。
一、有幾種負(fù)載均衡的方式?
整個(gè)Linux的負(fù)載均衡器有下面的幾個(gè)類型:
實(shí)際上內(nèi)核的負(fù)載均衡器(本文都是特指CFS任務(wù)的)有兩種,一種是為繁忙CPU們準(zhǔn)備的periodic balancer,用于CFS任務(wù)在busy cpu上的均衡。還有一種是為idle cpu們準(zhǔn)備的idle balancer,用于把繁忙CPU上的任務(wù)均衡到idle cpu上來(lái)。idle balancer有兩種,一種是nohz idle balancer,另外一種是new idle balancer。
周期性負(fù)載均衡(periodic load balance或者tick load balance)是指在tick中,周期性的檢測(cè)系統(tǒng)的負(fù)載均衡狀況,找到系統(tǒng)中負(fù)載最重的domain、group和CPU,將其上的runnable任務(wù)拉到本CPU以便讓系統(tǒng)的負(fù)載處于均衡的狀態(tài)。周期性負(fù)載均衡只能在busy cpu之間均衡,要想讓系統(tǒng)中的idle cpu“燥起來(lái)”就需要借助idle load balance。
NOHZ load balance是指其他的cpu已經(jīng)進(jìn)入idle,本CPU任務(wù)太重,需要通過(guò)ipi將其他idle的CPUs喚醒來(lái)進(jìn)行負(fù)載均衡。為什么叫NOHZ load balance呢?那是因?yàn)檫@個(gè)balancer只有在內(nèi)核配置了NOHZ(即tickless mode)下才會(huì)生效。如果CPU進(jìn)入idle之后仍然有周期性的tick,那么通過(guò)tick load balance就能完成負(fù)載均衡了,不需要IPI來(lái)喚醒idle的cpu。和周期性均衡一樣,NOHZ idle load balance也是通過(guò)busy cpu上tick驅(qū)動(dòng)的,如果需要kick idle load balancer,那么就會(huì)通過(guò)GIC發(fā)送一個(gè)ipi中斷給選中的idle cpu,讓它代表系統(tǒng)所有的idle cpu們進(jìn)行負(fù)載均衡。
New idle load balance比較好理解,就是在CPU上沒(méi)有任務(wù)執(zhí)行,馬上要進(jìn)入idle狀態(tài)的時(shí)候,看看其他CPU是否需要幫忙,從來(lái)從busy cpu上拉任務(wù),讓整個(gè)系統(tǒng)的負(fù)載處于均衡狀態(tài)。NOHZ load balance涉及系統(tǒng)中所有的idle cpu,但New idle load balance只是和即將進(jìn)入idle的本CPU相關(guān)。
?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-680082.html
二、周期性負(fù)載均衡的大概過(guò)程為何?
當(dāng)tick到來(lái)的時(shí)候,在scheduler_tick函數(shù)中會(huì)調(diào)用trigger_load_balance來(lái)觸發(fā)周期性負(fù)載均衡,相關(guān)的代碼如下:
整個(gè)代碼非常的簡(jiǎn)單,主要的邏輯就是調(diào)用raise_softirq觸發(fā)SCHED_SOFTIRQ,當(dāng)然要滿足均衡間隔時(shí)間的要求(后面會(huì)詳述)。nohz_balancer_kick用來(lái)觸發(fā)nohz idle balance的,這是后面兩個(gè)章節(jié)要仔細(xì)描述的內(nèi)容。上面的圖片,我特地保留了函數(shù)的注釋,這里看起似乎注釋不對(duì),因?yàn)檫@個(gè)函數(shù)不但觸發(fā)的周期性均衡,也觸發(fā)了nohz idle balance。然而,其實(shí)nohz idle balance本質(zhì)上也是另外一種意義上的周期性負(fù)載均衡,只是因?yàn)镃PU進(jìn)入idle,無(wú)法產(chǎn)生tick,因此讓能產(chǎn)生tick的busy CPU來(lái)幫忙觸發(fā)tick balance。而實(shí)際上tick balance和nohz idle balance都是通過(guò)SCHED_SOFTIRQ的軟中斷來(lái)處理,最后都是執(zhí)行run_rebalance_domains這個(gè)函數(shù)。
三、整個(gè)nohz idle balance的過(guò)程是怎樣的?
這個(gè)問(wèn)題可以拆解成兩個(gè)問(wèn)題:
1)系統(tǒng)中有多個(gè)idle的cpu,如何選擇執(zhí)行nohz idle balance的那個(gè)cpu?
2)怎么通知到idle的CPU,喚醒的CPU如何進(jìn)行均衡?
如果不考慮功耗,那么從所有的idle cpu中選擇一個(gè)就OK了,然而,在異構(gòu)系統(tǒng)中(例如手機(jī)環(huán)境),我們要考慮更多。例如:如果大核CPU和小核CPU都處于idle狀態(tài),那么選擇喚醒大核CPU還是小核CPU?大核CPU雖然算力強(qiáng),但是功耗高。如果選擇小核,雖然能省功耗,但是提供的算力是否足夠。此外,發(fā)起idle balance請(qǐng)求的CPU在那個(gè)cluster?是否首選同一個(gè)cluster的cpu來(lái)執(zhí)行nohz idle balance?還有cpu idle的深度如何?很多思考點(diǎn),不過(guò)本文就不詳述了,畢竟標(biāo)準(zhǔn)內(nèi)核選擇的最簡(jiǎn)單的算法:隨便選擇一個(gè)idle cpu(也就是idle cpu mask中的第一個(gè))。
我們定義發(fā)起nohz idle balance的CPU叫做kicker;接收請(qǐng)求來(lái)執(zhí)行均衡操作的CPU叫做kickee。Kicker和kickee之間的交互是這樣的:
1)Kicker通知kickee已經(jīng)被選中執(zhí)行nohz idle balance,具體是通過(guò)設(shè)定kickee cpu runqueue的nohz_flags成員來(lái)完成的。
2)Send ipi把kickee喚醒
3)Kickee被中斷喚醒,執(zhí)行scheduler_ipi來(lái)處理這個(gè)ipi中斷。當(dāng)發(fā)現(xiàn)其runqueue的nohz_flags成員被設(shè)定了,那么知道自己被選中,后續(xù)的流程其實(shí)和周期性均衡一樣的,都是觸發(fā)一次SCHED_SOFTIRQ類型的軟中斷
我們?cè)購(gòu)?qiáng)調(diào)一下:被kick的那個(gè)idle cpu并不是負(fù)責(zé)拉其他繁忙cpu上的任務(wù)到本CPU上就完事了,kickee是為了重新均衡所有idle cpu(tick被停掉)的負(fù)載,也就是說(shuō)被選中的idle cpu僅僅是一個(gè)系統(tǒng)所有idle cpu的代表,它被喚醒是要把系統(tǒng)中繁忙CPU的任務(wù)均衡到系統(tǒng)中所有的idle cpu們。此外,在上面的步驟1中,有可能有多個(gè)kicker同時(shí)選中一個(gè)kickee,因此這里需要檢測(cè)pending的請(qǐng)求,避免重復(fù)操作。具體的代碼可以參考nohz_balancer_kick函數(shù)。
SCHED_SOFTIRQ軟中斷的處理函數(shù)如下:
nohz idle balance和periodic load balance都是通過(guò)SCHED_SOFTIRQ類型的軟中斷來(lái)完成,也就是說(shuō)它們兩個(gè)都是通過(guò)SCHED_SOFTIRQ注冊(cè)的handler函數(shù)run_rebalance_domains來(lái)完成其功能的,那么如果一個(gè)CPU被選中做nohz idle balance,于此同時(shí)tick也到了,那么怎么處理?這個(gè)時(shí)候調(diào)度器優(yōu)先處理nohz idle balance,畢竟nohz idle balance是一個(gè)全局的事情(代表系統(tǒng)所有idle cpu做均衡),而periodic load balance只是均衡自己的各階sched domain。
四、什么條件下才需要喚醒idle CPU來(lái)執(zhí)行NOHZ idle load balance?
在一個(gè)active的CPU上,tick會(huì)周期性到來(lái),我們?cè)谠揅PU的tick中檢測(cè)是否需要觸發(fā)NOHZ load balance。顯然一個(gè)輕載的CPU可以“自力更生”,不需要其他idle的CPU來(lái)協(xié)助,那么如何界定一個(gè)CPU上的任務(wù)的輕和重?以至于需要冒險(xiǎn)(功耗損失)要將其他idle的CPU喚醒?主要考慮下面幾點(diǎn):
- 本CPU不能處于idle狀態(tài),且其runqueue中的任務(wù)數(shù)大于等于2(load balance主要是遷移runnable的任務(wù),>=2保證了至少有一個(gè)可以被遷移的任務(wù))
- 系統(tǒng)中有其他的CPU處于tickless mode的idle狀態(tài)
- NOHZ load balance不宜觸發(fā)的過(guò)于頻繁。下一章會(huì)詳細(xì)描述。
- 本CPU runqueue有至少1個(gè)CFS任務(wù),并且CPU的算力被大量消耗在RT task或者IRQ處理上,可以用于執(zhí)行cfs的算力大大降低了,這時(shí)候也需要其他idle cpu來(lái)幫忙。
- 在異構(gòu)計(jì)算系統(tǒng)中,如果當(dāng)前CPU上有misfit task,并且系統(tǒng)中有更高算力的idle cpu,那么也會(huì)發(fā)起balance,讓算力更高的處理器來(lái)承接該misfit task。和提高cache命中率相比,調(diào)度器更期待任務(wù)可以獲得更適合算力的CPU。
具體的代碼可以參考nohz_balancer_kick函數(shù)。
五、如何控制觸發(fā)nohz idle balance的頻次?
雖然nohz idle balance本質(zhì)上是tick balance,但是它會(huì)發(fā)IPI,會(huì)喚醒idle的cpu,帶來(lái)額外的開銷,所以還是要控制觸發(fā)觸發(fā)nohz idle balance的頻次。為了方便控制觸發(fā)nohz idle balance,調(diào)度器定義了一個(gè)nohz的全局變量,其數(shù)據(jù)結(jié)構(gòu)如下:
nr_cpus和idle_cpus_mask這兩個(gè)成員可以讓調(diào)度器了解當(dāng)前系統(tǒng)idle CPU的情況,從而選擇合適的CPU來(lái)執(zhí)行nohz idle balance。一個(gè)idle的cpu被kick并不總是完成負(fù)載均衡,有時(shí)候也可能是因?yàn)橐耣locked load,讓系統(tǒng)中的CPU負(fù)載符合當(dāng)前的狀態(tài)。這部分不是本文的內(nèi)容,不再詳述。next_balance是用來(lái)控制觸發(fā)nohz idle balance的時(shí)間點(diǎn),這個(gè)時(shí)間點(diǎn)應(yīng)該是和系統(tǒng)中所有idle cpu的rq->next_balance相關(guān)的,也就是說(shuō),如果系統(tǒng)中所有idle cpu都還不需要均衡,那么根本也就沒(méi)有必要觸發(fā)nohz idle balance,因此,在執(zhí)行nohz idle balance的時(shí)候,調(diào)度器實(shí)際上會(huì)遍歷idle cpu找到rq->next_balance最小的(即最近需要均衡的)賦值給nohz.next_balance。
具體執(zhí)行nohz idle balance非常簡(jiǎn)單,遍歷系統(tǒng)所有的idle cpu,調(diào)用rebalance_domains來(lái)完成該cpu上的各個(gè)level的sched domain的負(fù)載均衡。具體的代碼可以參考nohz_idle_balance函數(shù)。
?資料直通車:Linux內(nèi)核源碼技術(shù)學(xué)習(xí)路線+視頻教程內(nèi)核源碼
學(xué)習(xí)直通車:Linux內(nèi)核源碼內(nèi)存調(diào)優(yōu)文件系統(tǒng)進(jìn)程管理設(shè)備驅(qū)動(dòng)/網(wǎng)絡(luò)協(xié)議棧
六、做new idle load balance需要考慮哪些因素?
目前調(diào)度器做new idle load balance主要考慮兩個(gè)因素:當(dāng)前cpu的cache狀態(tài)和當(dāng)前的整機(jī)負(fù)載情況。如果該CPU平均idle時(shí)間非常短,那么當(dāng)CPU重新回來(lái)執(zhí)行的任務(wù)的時(shí)候,CPU cache還是熱的,如果從其他CPU上拉取任務(wù),那么這些新的任務(wù)會(huì)破壞其他任務(wù)的cache,從而影響過(guò)去任務(wù)的性能,同時(shí)也有功耗的增加。整機(jī)負(fù)載的影響記錄在root domain中的overload成員中,所謂overload就是指滿足下面的條件:
- 大于1個(gè)runnable task,即該CPU上有等待執(zhí)行的任務(wù)
- 只有一個(gè)正在運(yùn)行的任務(wù),但是是misfit task
滿足上面的條件我們稱這個(gè)CPU是overload狀態(tài)的,如果系統(tǒng)中至少有一個(gè)CPU是overload狀態(tài),那么我們認(rèn)為系統(tǒng)是overload狀態(tài)的。如果系統(tǒng)沒(méi)有overload,那么也就沒(méi)有必要做new idle load balance了。
上面是從CPU視角做的決定,降低了new idlebalance的次數(shù),此外,調(diào)度器也從sched domain的角度進(jìn)行檢查,進(jìn)一步避免了無(wú)效new idlebalance發(fā)生的次數(shù)。首先我們要明確一點(diǎn):做new idle load balance是有開銷的,我們辛辛苦苦找到了繁忙的CPU,從它的runqueue中拉了任務(wù)來(lái),然而如果自己其實(shí)也沒(méi)有那么閑,可能很快就有任務(wù)放置到自己的runqueue上來(lái),這樣,那些用于均衡的CPU時(shí)間其實(shí)都白白浪費(fèi)了。怎么避免這個(gè)尷尬狀況?我們需要兩個(gè)數(shù)據(jù):一個(gè)是當(dāng)前CPU的平均idle時(shí)間,另外一個(gè)是在new idle load balance引入的開銷(max_newidle_lb_cost成員)。如果CPU的平均idle時(shí)間小于max_newidle_lb_cost+本次均衡的開銷,那么就不啟動(dòng)均衡。
為了控制cpu無(wú)效進(jìn)入new idle load balance,runqueue數(shù)據(jù)結(jié)構(gòu)中有下面的成員:
計(jì)算avg_idle的算法非常簡(jiǎn)單,如下:
和nohz idle balance一樣,new idle balance不僅僅要處理負(fù)載均衡,同時(shí)也要負(fù)責(zé)處理blocked load的更新。如果條件不滿足,該cpu不需要進(jìn)行均衡,那么在進(jìn)入idle狀態(tài)之前,還需要看看系統(tǒng)中的那些idle cpu們的blocked load是否需要更新了,如果需要,那么該CPU就會(huì)執(zhí)行blocked load的負(fù)載更新。其背后的邏輯是:與其在nohz idle balance過(guò)程中遍歷選擇一個(gè)idle CPU來(lái)做負(fù)載更新,還不如就讓這個(gè)即將進(jìn)入idle的cpu來(lái)處理。具體的代碼可以參考newidle_balance函數(shù)。
七、對(duì)于一個(gè)sched domain而言,多久做一次負(fù)載均衡比較適合?
負(fù)載均衡執(zhí)行的頻次其實(shí)是在延遲和開銷之間進(jìn)行平衡。不同level的sched domain上負(fù)載均衡帶來(lái)的開銷是不一樣的。在手機(jī)平臺(tái)上,MC domain在inter-cluster之內(nèi)進(jìn)行均衡,對(duì)性能的影響小一點(diǎn)。但是DIE domain上的均衡需要在cluster之間遷移任務(wù),對(duì)性能和功耗的影響都比較大一些(例如cache命中率,或者一個(gè)任務(wù)遷移到原來(lái)深度睡眠的大核CPU)。因此執(zhí)行均衡的時(shí)間間隔應(yīng)該是和domain的層級(jí)相關(guān)的。此外,負(fù)載狀況也會(huì)影響均衡的時(shí)間間隔,在各個(gè)CPU負(fù)載比較重的時(shí)候,均衡的時(shí)間間隔可以拉大,畢竟大家都忙,讓子彈先飛一會(huì),等塵埃落定之后在執(zhí)行均衡也不遲。
struct sched_domain和均衡相關(guān)的數(shù)據(jù)成員包括:
對(duì)于一個(gè)4+4的手機(jī)平臺(tái),在MC domain上,小核和大核cluster的min_interval都是4ms,而max_interval等于8ms。而在DIE domain層級(jí)上,由于CPU個(gè)數(shù)是8,其min_interval是8ms,而max_interval等于16ms。真正的均衡間隔是定義在balance_interval中,是一個(gè)不斷跟隨sched domain的不均衡程度而變化的值。初值一般從min_interval開始,隨著不均衡的狀況在變好,balance_interval會(huì)逐漸變大,從而讓均衡的間隔變大,直到max_interval。
八、結(jié)束語(yǔ)
周期性均衡和nohz idle balance都是SCHED類型的軟中斷觸發(fā),最后都調(diào)用了rebalance_domains來(lái)執(zhí)行該CPU上各個(gè)level的sched domain的均衡,具體在某個(gè)sched domain執(zhí)行均衡的函數(shù)是load_balance函數(shù)。對(duì)于new idle load balance,也是遍歷該CPU上各個(gè)level的sched domain執(zhí)行均衡動(dòng)作,調(diào)用的函數(shù)仍然是load_balance。因此,無(wú)論哪一種均衡,最后都萬(wàn)法歸宗來(lái)到load_balance。由于篇幅原因,本文不再詳細(xì)分析load_balance的邏輯,想要了解細(xì)節(jié)且聽下回分解吧。
原文作者:內(nèi)核工匠
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-680082.html
?
到了這里,關(guān)于深入分析負(fù)載均衡情景的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!