前言
在Linux中,有些線程需要被公平調(diào)度,保證每個(gè)線程不會(huì)長時(shí)間的調(diào)度不到,這就是我們熟知的CFS調(diào)度類(sched class),但是也有一些關(guān)鍵線程(比如一些顯示刷幀的支撐線程),我們需要保證線程能夠及時(shí)被調(diào)度到,針對(duì)普通負(fù)載較輕的場(chǎng)景,線程的調(diào)度及時(shí)性都能得到保證。但是為了滿足人們的日常使用需求,操作系統(tǒng)后臺(tái)駐留任務(wù)越來越多(這種現(xiàn)象在Android設(shè)備上表現(xiàn)尤為嚴(yán)重),而系統(tǒng)的CPU始終只有一個(gè),即使這個(gè)CPU有8個(gè)核甚至更多,CPU也有可能被塞滿task,為了保證一些重要task的及時(shí)運(yùn)行,這里就有了實(shí)時(shí)進(jìn)程的概念。
Linux中,系統(tǒng)是通過優(yōu)先級(jí)來區(qū)分非實(shí)時(shí)線程和實(shí)時(shí)線程的,Linux將線程優(yōu)先級(jí)分為140個(gè)等級(jí),從0~139,這個(gè)值越小其優(yōu)先級(jí)越高,實(shí)時(shí)線程也叫rt(real time)線程,其優(yōu)先級(jí)范圍為[0,99],非實(shí)時(shí)線程為[100,139]。非實(shí)時(shí)線程也叫cfs線程,他的default優(yōu)先級(jí)為120,通過nice值轉(zhuǎn)化為最終的線程優(yōu)先級(jí)。Nice值的取值范圍為-20~19,可以通過ps命令查看NI列:
本文主要講述rt線程的選核流程。
rt選核流程介紹
每個(gè)task在被wakup喚醒時(shí)候都會(huì)從try_to_wake_up開始執(zhí)行,這里會(huì)為task選擇合適的CPU運(yùn)行,其核心邏輯位于select_task_rq函數(shù)里面,它會(huì)根據(jù)task的sched_class確定調(diào)用的具體函數(shù),而task的sched_class初始化則是在系統(tǒng)更改或者設(shè)置task的priority時(shí),根據(jù)task的priority進(jìn)行設(shè)置:
如果一個(gè)線程為rt,它的sched_class則為rt_sched_class,結(jié)構(gòu)體初始化定義如下:
那么rt線程對(duì)應(yīng)調(diào)度類的選核函數(shù)為select_task_rq_rt,其基本的執(zhí)行邏輯如下圖1:
圖1
rt選核流程比較簡單,其核心邏輯位于find_lowest_rq函數(shù)中,此函數(shù)主要用于尋找符合當(dāng)前rt線程運(yùn)行的cpu,其核心邏輯如下圖2:
圖2
cpupri_find_fitness負(fù)責(zé)從所有系統(tǒng)中所有的符合task運(yùn)行條件的cpu找出來,并更新到lowest_mask里面,然后find_lowest_rq再從最終的lowest_mask里面選擇合適的CPU,選擇邏輯:
如果lowest_mask里面包含task的prev cpu,則直接選擇prev cpu。
選擇lowest_mask在task的sched domain里面的第一個(gè)cpu。
如果前面都沒找到CPU,則判斷l(xiāng)owest_mask里面是否包含了wake cpu,如果包含則直接返回wake cpu
最后如果都沒找到,lowest又不為空,則從lowest_mask里面找一個(gè)隨機(jī)的cpu返回。
下面來看下cpupri_find_fitness如何找到合適的所有適合task運(yùn)行的cpu,主要分為3個(gè)部分:
1.首先對(duì)task優(yōu)先級(jí)進(jìn)行一個(gè)轉(zhuǎn)化,將系統(tǒng)中的task分為0~102個(gè)等級(jí),優(yōu)先級(jí)由低到高,可以分為invalid,idle,normal和rt四類。
圖3
其中invalid優(yōu)先級(jí)為0,idle優(yōu)先級(jí)為1,所有的cfs線程占用一類,優(yōu)先級(jí)為2,rt則每個(gè)優(yōu)先級(jí)占用一個(gè)等級(jí)。
2.因?yàn)閞t線程是可以搶占的,for循環(huán)從最低優(yōu)先級(jí)開始遍歷,這里的優(yōu)先級(jí)為已經(jīng)轉(zhuǎn)化為103個(gè)級(jí)別的優(yōu)先級(jí)狀態(tài),其選核邏輯一般為首先選擇idle的cpu運(yùn)行,其次是搶占有cfs task的cpu運(yùn)行,最后才會(huì)考慮取搶占其他低優(yōu)先級(jí)rt task的cpu,通過__cpupri_find查找各個(gè)優(yōu)先級(jí)在cpu上的運(yùn)行狀態(tài),找到可以使用的cpu。cpupri_vec結(jié)構(gòu)體有兩個(gè)成員,count用來存儲(chǔ)各個(gè)優(yōu)先級(jí)task在哪些CPU上屬于最高優(yōu)先級(jí)task,mask則用來存儲(chǔ)當(dāng)前優(yōu)先級(jí)所在CPU上是最高優(yōu)先級(jí)的的cpumask。例如當(dāng)前系統(tǒng)中cpu0~5上沒有全是cfs task在運(yùn)行,那么normal task的count值為6,mask為0x3f。
__cpupri_find的基本邏輯:
判斷當(dāng)前優(yōu)先級(jí)在哪些CPU上是最高優(yōu)先級(jí),如果沒有的話,說明當(dāng)前系統(tǒng)要么沒有此優(yōu)先級(jí)task,要么是當(dāng)前優(yōu)先級(jí)task并非系統(tǒng)中各個(gè)CPU的最高優(yōu)先級(jí)。
當(dāng)前優(yōu)先級(jí)task在有cpu是處于最高優(yōu)先級(jí),從這些cpu里面去除掉task not allowed cpu,再去除掉被isolation的cpu,最后如果lowest_mask還有CPU,則將lowest_mask作為后面選核的基礎(chǔ)。
經(jīng)過__cpupri_find找到lowest_mask后,再從lowest_mask里面過濾掉capacity無法滿足當(dāng)前task的cpu,最后剩下的就是可以用來運(yùn)行當(dāng)前task的cpumask,如果沒有剩余,則說明當(dāng)前優(yōu)先級(jí)的task所運(yùn)行的cpu沒有滿足條件的,此時(shí)需要進(jìn)入下一個(gè)循環(huán),找到更高優(yōu)先級(jí)的task運(yùn)行的cpu。
3.如果此次循環(huán)沒有找到合適的cpu,會(huì)再進(jìn)行一次循環(huán),嘗試找到合適的CPU。
至此,rt選核的整體流程介紹完畢。
結(jié)語
本文主要從代碼的角度講述了Linux rt選核的主要流程,旨在讓讀者對(duì)于rt線程及其選核邏輯有一個(gè)初步的認(rèn)識(shí),利用rt線程的優(yōu)點(diǎn),可以解決當(dāng)前系統(tǒng)中因?yàn)檎{(diào)度延遲引起的一些性能問題。
rt線程選核相對(duì)cfs線程而言要簡單一些,他不會(huì)考慮energy等因素,但系統(tǒng)中過多的rt task可能會(huì)帶來一定的功耗影響,同時(shí)由于rt主要是為了解決重要task的調(diào)度延遲問題,如果系統(tǒng)中過多的rt線程可能導(dǎo)致一些重負(fù)載場(chǎng)景可能所有CPU都是rt task,引發(fā)rt線程的調(diào)度延遲,從而導(dǎo)致更嚴(yán)重的性能問題,所以也不宜在系統(tǒng)中設(shè)置過多的rt task。
審核編輯:湯梓紅
評(píng)論