CFS線程調(diào)度機(jī)制分析
一、前言
操作系統(tǒng)上運(yùn)作著各種應(yīng)用、服務(wù)來滿足用戶需求,這些應(yīng)用、服務(wù)實(shí)現(xiàn)的功能,通常都會依托一個個具體的線程來完成。在2022年的今天,無論是手機(jī)用戶還是平臺廠商,都不會容忍一臺手機(jī)的功能僅限于單一的通信功能。手機(jī)設(shè)備不比服務(wù)器,其多核架構(gòu)體系上只存在8個cpu core,而執(zhí)行線程的數(shù)量卻輕而易舉超過8個。
每個cpu上該執(zhí)行什么線程,是由linux線程調(diào)度器來進(jìn)行決策的,關(guān)于這一部分,網(wǎng)上已經(jīng)有相當(dāng)多的文章進(jìn)行描述,因此本文在講解一些概念的基礎(chǔ)上,會分析為何當(dāng)前的調(diào)度器仍滿足不了android系統(tǒng)的業(yè)務(wù)需求,我們?yōu)楹芜€需要做調(diào)度優(yōu)化。先拋出一張圖(圖1),大家感受下高負(fù)載下的cpu,是不是像極了正在工作的你,忙的不可開交!

擁有android開發(fā)經(jīng)驗(yàn)的伙伴,都清楚上面是通過google perfetto工具抓取的系統(tǒng)運(yùn)行狀態(tài)圖示,8個核心全部跑滿,要達(dá)到這樣的現(xiàn)象并不困難,多啟動幾個應(yīng)用,抓取下啟動時(shí)的狀態(tài)即可。那么問題來了,高負(fù)載的系統(tǒng)中,每個核心都不止運(yùn)行一個線程,這必然導(dǎo)致線程執(zhí)行存在先后順序,每次執(zhí)行的時(shí)間也存在差異,那么,影響用戶體驗(yàn)的線程能否及時(shí)執(zhí)行起來,能否得到足夠長的執(zhí)行時(shí)間,就顯得尤為重要,這也是調(diào)度團(tuán)隊(duì)需要解決的問題。
二、線程調(diào)度器如何運(yùn)作
實(shí)際的開發(fā)過程中,我們發(fā)現(xiàn)CFS調(diào)度類線程更容易出現(xiàn)調(diào)度問題,因此目前調(diào)度的優(yōu)化圍繞該調(diào)度類展開。下文的主要分析對象為CFS調(diào)度類,我們先從幾個基礎(chǔ)概念入手。
2.1 基礎(chǔ)調(diào)度概念
調(diào)度類(sched class):linux內(nèi)核支持的調(diào)度類如圖2所示,每個線程歸屬于其中一種調(diào)度類,并遵循一種調(diào)度策略。android系統(tǒng)中,覆蓋面最廣的是CFS調(diào)度類fair_sched_class,其次是實(shí)時(shí)調(diào)度類中的rt_sched_class,我們喜歡將它們的線程分別成為cfs線程和rt線程。調(diào)度類存在優(yōu)先級,圖2從上到下羅列的調(diào)度類,優(yōu)先級從高至底。調(diào)度器總是從最高優(yōu)先級的調(diào)度類中開始檢索需要執(zhí)行的線程,同個調(diào)度類存在多個線程,則按具體的調(diào)度策略進(jìn)行檢索。

調(diào)度類提供的實(shí)現(xiàn)接口用于定制具體的調(diào)度策略,開發(fā)人員也可按照相應(yīng)的規(guī)則實(shí)現(xiàn)自己的調(diào)度類和調(diào)度策略。
運(yùn)行隊(duì)列(runqueue):線程需要執(zhí)行時(shí),會選取某個核,并放置到該核對應(yīng)的運(yùn)行隊(duì)列上,這個過程我們稱為入隊(duì);當(dāng)線程由于某種原因不再需要執(zhí)行時(shí),將從運(yùn)行隊(duì)列上摘除,對應(yīng)的過程即出隊(duì)。不同調(diào)度策略,運(yùn)行隊(duì)列的維護(hù)方式不同,如cfs線程的隊(duì)列維護(hù)是通過紅黑樹完成,而rt線程的隊(duì)列維護(hù)則是通過優(yōu)先級鏈表完成。
注:圖3為CFS調(diào)度類的實(shí)現(xiàn),對應(yīng)的接口作用如表1所示。


調(diào)度延遲(sched latency):線程在運(yùn)行隊(duì)列中的等待時(shí)間,這主要來自三個方面:從idle cpu喚醒需要等待器件做好準(zhǔn)備;等待更高優(yōu)先級調(diào)度類線程執(zhí)行完成;等待同調(diào)度類的線程時(shí)間片耗盡。
時(shí)間片(sched slice):線程單次執(zhí)行的時(shí)長。CFS調(diào)度策略存在調(diào)度周期,理想情況下,調(diào)度周期內(nèi)運(yùn)行隊(duì)列的每個線程都將執(zhí)行一次,當(dāng)運(yùn)行隊(duì)列中只有一個線程時(shí),將總是由該線程分得全部時(shí)間片。

2.2 權(quán)重的作用
CFS調(diào)度策略就是青天大老爺,它來到linux內(nèi)核,只做三件事:公平!公平!還是公平!其公平規(guī)則緊緊圍繞權(quán)重展開,理解了權(quán)重的作用,也就掌握了該調(diào)度策略的核心。
我們知道,每個線程都存在優(yōu)先級priority,記錄在其抽象結(jié)構(gòu)體struct task_struct中,并提供相應(yīng)的系統(tǒng)調(diào)用供用戶修改。實(shí)際上,android通過這些系統(tǒng)調(diào)用指定的是nice值,如表2所示。
文件路徑:system/core/libsystem/include/system/thread_defs.h

nice值與priority之間存在一個轉(zhuǎn)換關(guān)系,如下:
nice值在一定程度上定義了該線程的重要程度,它是一個存在很久的概念。在CFS調(diào)度策略誕生之前,普通線程指定nice值后,調(diào)度器會賦予固定大小的時(shí)間片。nice值越小,優(yōu)先級越高,時(shí)間片越大。調(diào)度器總是讓高優(yōu)先級的線程優(yōu)先執(zhí)行完分配的時(shí)間片。這樣的做法存在兩個問題:
(1)低優(yōu)先級的調(diào)度延遲無法得到有效控制。由于總是讓高優(yōu)先級先執(zhí)行,那么調(diào)度延遲很大程度上取決于高優(yōu)先級線程的數(shù)量。
(2)固定時(shí)間片會導(dǎo)致低優(yōu)先級線程的執(zhí)行變得異常艱難。低優(yōu)先級線程本身時(shí)間片是非常小的,如果長時(shí)間的等待換來的是幾個ms的執(zhí)行,那它的任務(wù)將很難執(zhí)行完。
因此,內(nèi)核引入CFS調(diào)度策略,保證公平調(diào)度。為此,調(diào)度器特地給nice值建立一一對應(yīng)的權(quán)重值,如圖5所示,注意到,一共有40個權(quán)重等級,代表CFS調(diào)度類線程的nice范圍[-20, 19]:

權(quán)重與nice值的計(jì)算關(guān)系如下:
weight = 1024 / (1.25 ^ nice)
權(quán)重的作用主要體現(xiàn)在兩個方面:
(1)時(shí)間片分配大小
時(shí)間片是按線程權(quán)重占比進(jìn)行分配。如果兩個權(quán)重為1024(nice值為0)的線程放在同個核上,在沒有其它線程干擾的情況下,這兩個線程將平分整個調(diào)度周期。我們將掛在運(yùn)行隊(duì)列的線程看做一個調(diào)度實(shí)體se(sched entity),則該se的時(shí)間片為:
slice = se->load.weight / cfs_rq->load
其中,se->load.weight代表該se的權(quán)重值,即圖5中nice值對應(yīng)的數(shù)值(如果該se代表一個具體線程)。cfs_rq->load則代表當(dāng)前運(yùn)行隊(duì)列上所有調(diào)度實(shí)體的權(quán)重總和。注意,以上描述僅適用于未開啟組調(diào)度的情況,當(dāng)開啟組調(diào)度后,情況會有些微不同,另有章節(jié)涉及。
(2)虛擬時(shí)間增長
調(diào)度實(shí)體的執(zhí)行時(shí)間,將按照權(quán)重反饋到虛擬時(shí)間的增長上。我們之前說過,CFS調(diào)度策略是通過一棵紅黑樹來進(jìn)行維護(hù)的,虛擬時(shí)間越小的線程,將掛在紅黑樹的越左端,而最左端是普遍情況下調(diào)度器選取的下一個執(zhí)行對象。
對于一個權(quán)重為1024的線程,執(zhí)行2ms時(shí),會原封不動地將這2個ms累加到虛擬時(shí)間上,但是,當(dāng)它的權(quán)重增加到9548時(shí)(對應(yīng)nice值為-10),虛擬時(shí)間只增加0.2ms。它們之間存在以下關(guān)系:
vruntime = exectime * (1024 / se->load.weight)
虛擬時(shí)間除了會影響調(diào)度實(shí)體在紅黑樹中的位置外,還影響喚醒搶占能否成功。如下:
傳遞的參數(shù)中,*curr代表當(dāng)前正在執(zhí)行的調(diào)度實(shí)體,*se代表準(zhǔn)備發(fā)起搶占的調(diào)度實(shí)體。此次搶占能否成功,取決于*curr的虛擬時(shí)間是否大于*se,當(dāng)差值超過gran值時(shí),則成功發(fā)起一次搶占。這樣設(shè)計(jì)的邏輯是,*curr通常是上一次調(diào)度器選擇時(shí),紅黑樹中最左端的調(diào)度實(shí)體(即虛擬時(shí)間最?。?,在周期tick到來之前,它理應(yīng)獲得的時(shí)間片可能還沒耗盡,此時(shí)如果直接允許喚醒的*se發(fā)起搶占,那對*curr是不公平的,那么,如果不發(fā)生搶占是否可行呢?這里運(yùn)行隊(duì)列已經(jīng)發(fā)生變化(一個新的task掛入隊(duì)列),需要盡快重新核算調(diào)度周期和時(shí)間片,如果等到周期tick命中當(dāng)前任務(wù)再核算可能黃花菜都涼了,一個簡單的例子(圖6):

調(diào)度周期是10ms,任務(wù)A和B由于權(quán)重相同各分5ms,圖中第二個tick到來后, A時(shí)間片耗盡切換到B。1ms后,任務(wù)C喚醒。如果發(fā)生搶占,那么在喚醒之前的調(diào)度周期中,任務(wù)B就太可憐了,沒有跟A一樣耗盡時(shí)間片。如果不發(fā)生搶占,那么喚醒的C需要等待下一次tick到來,基于新的系統(tǒng)狀況來核算任務(wù)B的時(shí)間片(由于新增高優(yōu)先級任務(wù),大概率會耗盡其時(shí)間片),對于C這個高優(yōu)先級任務(wù)而言,調(diào)度延遲又長了點(diǎn)。
總之,交給虛擬時(shí)間吧!值得注意的是,gran值依然是受權(quán)重值影響,*se的權(quán)重越大,gran值越小,其潛在含義是,如果喚醒線程優(yōu)先級高,那么gran值就小,從而更容易發(fā)生喚醒搶占)。如下:
gran = sysctl_sched_wakeup_granularity * (1024 / se->load.weight)
其中,sysctl_sched_wakeup_granularity是內(nèi)核提供給用戶空間的一個可設(shè)置的數(shù)值。
三、如何進(jìn)行優(yōu)化
3.1 存在的問題
在了解調(diào)度的基礎(chǔ)概念以及權(quán)重的作用后,我們應(yīng)該明白調(diào)度器是如何運(yùn)作的。CFS調(diào)度策略最讓人驚嘆的就是其基于虛擬時(shí)間的公平調(diào)度。假定調(diào)度周期為10ms,并且只有兩個線程位于同個運(yùn)行隊(duì)列上,則:

對于假設(shè)1,兩個線程的權(quán)重相同,平分10ms的調(diào)度周期,時(shí)間片均為5ms,對應(yīng)的虛擬時(shí)間也相同;對于假設(shè)2,兩個線程的權(quán)重存在差異,時(shí)間片也產(chǎn)生很大的差別,但是將時(shí)間片轉(zhuǎn)換為虛擬時(shí)間,依然基本一致。注意,以上計(jì)算僅存在于理論中,實(shí)際上,由于存在調(diào)度時(shí)機(jī),線程并不會在0.002ms后立刻發(fā)生切換。
這樣的“公平”設(shè)計(jì)很巧妙,但是依然不滿足手機(jī)這種強(qiáng)交互設(shè)備對及時(shí)性的需求。首先,執(zhí)行時(shí)間會不斷累積到虛擬時(shí)間上,這意味著,一個長時(shí)間執(zhí)行的線程,即便權(quán)重很高,它依然逃脫不了虛擬時(shí)間的不停增加,也就存在被其它長時(shí)間睡眠的喚醒線程搶占的可能性。其次,實(shí)際運(yùn)行情況下,即便一個線程的權(quán)重很高,它始終不能分到一個周期內(nèi)全部的時(shí)間片,并且分到的時(shí)間片也是有限的,當(dāng)時(shí)鐘周期tick命中后,cpu使用權(quán)交給另一個線程,往往需要到下一個周期tick的調(diào)度點(diǎn)才能交換回來。
3.2 調(diào)度優(yōu)化點(diǎn)
從2.2節(jié)表2可以發(fā)現(xiàn),原生android對系統(tǒng)中一些關(guān)鍵線程會進(jìn)行優(yōu)化,比如將負(fù)責(zé)圖形顯示合成的surfaceflinger線程、hw-composer線程更改為實(shí)時(shí)調(diào)度類,能有效降低調(diào)度延遲,確保任務(wù)執(zhí)行完再調(diào)度出去。但系統(tǒng)中如果存在較多的實(shí)時(shí)線程是不合適的,實(shí)時(shí)線程并不講究“公平”,很可能會導(dǎo)致關(guān)鍵線程執(zhí)行互相受到影響,或者影響其它普通線程。因此,android主要還是通過提高CFS調(diào)度類線程的優(yōu)先級來進(jìn)行改善,如音頻普通線程使用的nice值為-16,前臺交互應(yīng)用的UI、Render線程使用的nice值為-10,system_server下一些關(guān)鍵持鎖線程nice值為-2或者-4。
從3.1節(jié)我們也可以知道,這樣的優(yōu)化在高負(fù)載下仍然會存在問題,現(xiàn)在系統(tǒng)中CFS調(diào)度類的線程對時(shí)延要求越來越高,比如120刷新率的界面,一幀合成時(shí)間就要求在8ms以內(nèi),也就兩個周期tick的時(shí)間長度(250HZ)。因此,我們可以從更底層的角度去對這些關(guān)鍵的CFS調(diào)度類線程做改善,比如做以下嘗試:
(1)喚醒搶占維度:關(guān)鍵線程喚醒時(shí),我們是不是可以不考慮虛擬時(shí)間的評估,直接對當(dāng)前非關(guān)鍵線程發(fā)起搶占呢;反之,非關(guān)鍵線程在喚醒時(shí),則不允許對關(guān)鍵線程進(jìn)行搶占。
(2)避開高優(yōu)先級調(diào)度類:關(guān)鍵線程在選核時(shí),如果能避開更高優(yōu)先級調(diào)度類所在的核心,那么這一部分的調(diào)度延遲就可以避免。
(3)虛擬時(shí)間補(bǔ)償:原生內(nèi)核其實(shí)已經(jīng)存在此類做法,當(dāng)線程長時(shí)間休眠時(shí),并不會累加運(yùn)行時(shí)間到其虛擬時(shí)間,線程喚醒后,將遠(yuǎn)小于當(dāng)前運(yùn)行隊(duì)列的虛擬時(shí)間最小值。試想一下,如果我們不做任何改動,那該線程將長期霸占紅黑樹最左端的位置,這顯然是不合理的。因此在喚醒時(shí),內(nèi)核對其進(jìn)行修正,使虛擬時(shí)間對齊到最小虛擬時(shí)間的基礎(chǔ)上,僅減少半個或1個調(diào)度周期時(shí)長作為補(bǔ)償。對于短時(shí)間休眠呢?短休眠的線程有可能其虛擬時(shí)間大于補(bǔ)償后的時(shí)間,那就仍維持現(xiàn)狀掛入。
對于關(guān)鍵線程,我們依然可以采用這個思路,喚醒時(shí)削減一定的虛擬時(shí)間作為補(bǔ)償,使其在接下來的幾次調(diào)度中占據(jù)優(yōu)勢。

四、組調(diào)度的引入
4.1 認(rèn)識調(diào)度組
谷歌這兩年在推行通用內(nèi)核鏡像(GKI),使用android系統(tǒng)的手機(jī)廠商,都必須確保設(shè)備能直接使用GKI鏡像,因此,廠商在內(nèi)核修改這一塊存在許多限制。在kernel-5.4內(nèi)核引入GKI1.0之后,我們發(fā)現(xiàn)谷歌使能組調(diào)度功能, CONFIG_FAIR_GROUP_SCHED配置為true,分組的情況如圖8所示,關(guān)鍵組別如下:
dev/cpuctl/tasks ---- root組
dev/cpuctl/top-app/tasks ---- top-app組(用戶交互組)
dev/cpuctl/foreground/tasks ---- 前臺組
dev/cpuctl/background/tasks ---- 后臺組
...

組調(diào)度對CFS調(diào)度策略的影響是顯著的,原生內(nèi)核提供這套機(jī)制,是為了將各類線程按group的形式進(jìn)行資源分配,也就是將同一屬性的線程歸納到同一個group下,而android系統(tǒng)也順勢將各類應(yīng)用進(jìn)程、服務(wù)進(jìn)程放入相應(yīng)的組中,內(nèi)核線程則默認(rèn)放在root組(這一點(diǎn)很重要)。
4.2 公平分配規(guī)則的變動
我們在2.2節(jié)提到過調(diào)度實(shí)體的概念,當(dāng)時(shí)是將每個線程看做一個調(diào)度實(shí)體,而調(diào)度實(shí)體是掛在運(yùn)行隊(duì)列上的,引入調(diào)度組之后,調(diào)度實(shí)體就不一定是代表一個待執(zhí)行線程了,組調(diào)度為其建立起層級結(jié)構(gòu),如圖9所示。

在頂層的cfs_rq上,掛載兩個調(diào)度實(shí)體,其中一個為task級別的se,屬于root group,你可以把它當(dāng)成某個內(nèi)核線程,它的pid號記錄在“dev/cpuctl/tasks”中,另一個為group級別的se,代表某個調(diào)度組在當(dāng)前cpu上的調(diào)度實(shí)體,比如前臺組“foreground”。聰明的你已經(jīng)發(fā)現(xiàn),group的se擁有自己的運(yùn)行隊(duì)列,屬于第二層的cfs_rq,其上掛載該組在當(dāng)前cpu上的線程,記錄在“dev/cpuctl/foreground/tasks”中。
層級結(jié)構(gòu)引入后,時(shí)間片的分配規(guī)則也隨之產(chǎn)生變化。我們之前提到,一個調(diào)度周期會按當(dāng)前運(yùn)行隊(duì)列各個調(diào)度實(shí)體的權(quán)重占比進(jìn)行分配,但層級結(jié)構(gòu)需要注意的分配規(guī)則變動有兩個:
(1)調(diào)度周期的分配自上而下。假如我們給定調(diào)度周期為10ms,那么,這10個ms將從頂層的cfs_rq開始分配,分配依然是按se的權(quán)重占比劃分。我們假定頂層cfs_rq底下的兩個se(task級別和group級別)各自分得5ms,那么group se的5個ms,將繼續(xù)往下一層分配給其維護(hù)的第二層cfs_rq的task se們。
(2)group級別se的權(quán)重不再與nice值掛鉤。我們之前提到,task級別的se,即具體某一個線程,可以通過調(diào)節(jié)nice值更改其權(quán)重,進(jìn)而影響時(shí)間片的分配、虛擬時(shí)間的累加。然而,group se下面維護(hù)的cfs_rq,可能存在多個task se,難道是將這些task se的權(quán)重累加嗎?顯然不是這樣的,linux的設(shè)計(jì)總是統(tǒng)一而優(yōu)雅,對于一個group se,也有其與權(quán)重掛鉤的“nice”值。
我們注意到,圖8中存在一個參數(shù)“cpu.shares”。share值即代表當(dāng)前group級別se的權(quán)重,它并不受其子task se的影響,這個值在android平臺默認(rèn)是1024。你也可以在其他group目錄如“dev/cpuctl/foreground/”中發(fā)現(xiàn)它的存在。
4.3 帶來的問題
受到4.2節(jié)(1)的影響,我們對某個task se進(jìn)行虛擬時(shí)間上的補(bǔ)償優(yōu)化,意義已經(jīng)不大,因?yàn)槿绻愕膖ask se是掛在某個group的cfs_rq下,那么這個做法只會讓task在group所屬的cfs_rq中存在優(yōu)勢,而要讓它優(yōu)先得到執(zhí)行,調(diào)度器必須得先在頂層的cfs_rq中選中這個group se才行。那對group se做虛擬時(shí)間補(bǔ)償如何?這樣也不行,相當(dāng)于group底下的所有子task se都會受益,并且你還不能確保目標(biāo)task能優(yōu)先得到執(zhí)行。
受4.2節(jié)(2)的影響,nice值的作用在不同層級的cfs_rq中表現(xiàn)就不一樣了。我們之前說到group se的權(quán)重即share值,這樣的說法也不太準(zhǔn)確,實(shí)際上,同個組的線程們極有可能運(yùn)行在不同的cpu,所以內(nèi)核同樣采取占比的方式來計(jì)算對應(yīng)cpu上的group se的權(quán)重,公式如下:
ge->load.weight = tg->weight * grq->load.weight / SUM(grq->load.weight)
其中,ge->load.weight是當(dāng)前cpu掛載的group se的權(quán)重;tg->weight就是我們提到的share值;grq->load.weight就是當(dāng)前cpu掛載的group se,其維護(hù)的cfs_rq的權(quán)重,這個值是其上掛載的所有task se的權(quán)重總和;SUM(grq->load.weight)自然是group se在所有cpu的cfs_rq的權(quán)重總和。
感興趣的伙伴可以看下內(nèi)核函數(shù)calc_group_shares(struct cfs_rq *cfs_rq),上面擁有非常詳細(xì)的注釋,這個函數(shù)用來更新group se的權(quán)重,它在線程入隊(duì)、出隊(duì)、周期tick命中以及用戶修改share值時(shí),都會調(diào)用更新。
下面通過2個實(shí)驗(yàn)來看下組調(diào)度帶來的影響。我們首先創(chuàng)建4個循環(huán)執(zhí)行的線程,命名為“root_0”、“root_1”、“top_0”、“top_1”。其中,“root_0”、“root_1”維持默認(rèn)分組,也就是在root group中,它們?nèi)腙?duì)時(shí),會直接掛在頂層的cfs_rq。“top_0”、“top_1”則放入新創(chuàng)建的tmp group中,避免收到其它線程的干擾,它們位于所在cpu的group se維護(hù)的cfs_rq。之后,我們將“root_0”、“top_0”綁定在cpu4,“root_1”、“top_1”綁定在cpu5。則調(diào)度實(shí)體的分布如圖10所示。

實(shí)驗(yàn)一:4個線程nice值均為默認(rèn)值0(對應(yīng)權(quán)重為1024),root group和tmp group的share值均為默認(rèn)值1024,調(diào)度周期target latency為10ms?!皌op_0”和“top_1”雖然處于不同的cpu,但它們都屬于tmp group。cpu4上“top_0”的時(shí)間片分配規(guī)則為:
(1)計(jì)算其所在group即tmp_grp_se的權(quán)重
tmp_grp_se.weight = tmp_grp.share * top_0_se.weight / (top_0_se.weight + top_1_se.weight) = 512
(2)頂層cfs_rq目前掛載兩個se,它們的時(shí)間片分別為:
root_0_se_slice = target_latency * root_0_se.weight / (root_0_se.weight + tmp_grp_se.weight) = 6.7 ms
tmp_grp_se_slice = target_latency * tmp_grp_se.weight / (root_0_se.weight + tmp_grp_se.weight) = 3.3 ms
(3)tmp_grp再將其配額分給其擁有的task se。由于其所屬的cfs_rq上只掛著“top_0”,因此top_0_se_slice為3.3 ms。
可以看到,雖然所有線程的nice值都是0,但是處于root group下的“root_0”卻拿到多出一倍的時(shí)間配額。

實(shí)驗(yàn)二:在實(shí)驗(yàn)一的基礎(chǔ)上,我們將“top_0”的nice值調(diào)整為-10,讓它成為高優(yōu)先級線程,其余條件不變。按照之前的處理步驟,在cpu4上,“root_0”拿到的時(shí)間片為5.3 ms,“top_0”拿到的時(shí)間片為4.7 ms,一個高優(yōu)先級的線程居然跟低優(yōu)先級的線程旗鼓相當(dāng)!

我們也可以通過調(diào)整share值來影響結(jié)果,在此不再演示?,F(xiàn)在我們再回過來看下,CFS調(diào)度策略還公平嗎?其實(shí)從調(diào)度組的維度來看,還是公平的。這里我們難受的一點(diǎn)在于,root group的線程各自獨(dú)立,在計(jì)算權(quán)重時(shí)擁有很大優(yōu)勢,但實(shí)際上這些線程很少直接參與用戶圖形繪制、音頻輸入等業(yè)務(wù)。具體的業(yè)務(wù)線程又被android推入諸如top-app、foreground組,組內(nèi)還包含其它不影響用戶體驗(yàn)的線程,同個組的線程分散在不同的核上,引起組權(quán)重的重新分配,也就是說,一些關(guān)鍵業(yè)務(wù)線程的時(shí)間片分配會受到同組內(nèi)其他不相關(guān)線程的影響;另一個點(diǎn),android的業(yè)務(wù)設(shè)計(jì)上,不同組之間的線程可能會建立聯(lián)系,如app層對框架層的依賴,內(nèi)核層的公共資源爭奪等,也就是說,一些關(guān)鍵業(yè)務(wù)線程也會在某些特定場景下依賴其他組的線程,這些線程理論上在這個時(shí)刻更應(yīng)該看做是同個組??傊?,目前調(diào)度組這樣的使用方式會帶來問題,僅從share值和nice值去調(diào)整,是很難達(dá)到目的。
五、結(jié)語
我們今天了解到權(quán)重在CFS調(diào)度策略中的重要地位,也了解到CFS調(diào)度策略存在的一些問題,包括引入組調(diào)度后產(chǎn)生的弊端。基于權(quán)重的“公平”,看起來并不能滿足手機(jī)場景對于關(guān)鍵線程的調(diào)度需求。如何保證關(guān)鍵線程的及時(shí)調(diào)度,同時(shí)又不餓死其它線程,或許還需要一種新的機(jī)制引入,至少目前來看,權(quán)重對這一塊的調(diào)節(jié)很有限。
參考文獻(xiàn):
1、https://github.com/oppo-source/android_kernel_5.10_oppo_mt6983/
2、http://www.wowotech.net/sort/process_management