[校招面試]CPU調(diào)度系列二

完全公平調(diào)度CFS

CFS(Completely Fair Scheduler)試圖按照對(duì) CPU 時(shí)間的 “最大需求(gravest need)” 運(yùn)行任務(wù);這有助于確保每個(gè)進(jìn)程可以獲得對(duì) CPU 的公平共享。

CFS初探

CFS 調(diào)度程序使用安撫(appeasement)策略確保公平性。當(dāng)某個(gè)任務(wù)進(jìn)入運(yùn)行隊(duì)列后,將記錄當(dāng)前時(shí)間,當(dāng)某個(gè)進(jìn)程等待 CPU 時(shí),將對(duì)這個(gè)進(jìn)程的?wait_runtime?值加一個(gè)數(shù),這個(gè)數(shù)取決于運(yùn)行隊(duì)列當(dāng)前的進(jìn)程數(shù)。當(dāng)執(zhí)行這些計(jì)算時(shí),也將考慮不同任務(wù)的優(yōu)先級(jí)值。 將這個(gè)任務(wù)調(diào)度到 CPU 后,它的?wait_runtime?值開(kāi)始遞減,當(dāng)這個(gè)值遞減到其他任務(wù)成為紅黑樹(shù)的最左側(cè)任務(wù)時(shí),當(dāng)前任務(wù)將被搶占。通過(guò)這種方式,CFS 努力實(shí)現(xiàn)一種理想?狀態(tài),即?wait_runtime?值為 0!

CFS 維護(hù)任務(wù)運(yùn)行時(shí)(相對(duì)于運(yùn)行隊(duì)列級(jí)時(shí)鐘,稱為?fair_clock(cfs_rq->fair_clock)),它在某個(gè)實(shí)際時(shí)間的片段內(nèi)運(yùn)行,因此,對(duì)于單個(gè)任務(wù)可以按照理想的速度運(yùn)行。

例如,如果具有 4 個(gè)可運(yùn)行的任務(wù),那么?fair_clock?將按照實(shí)際時(shí)間速度的四分之一增加。每個(gè)任務(wù)將設(shè)法跟上這個(gè)速度。這是由分時(shí)多任務(wù)處理的量子化特性決定的。也就是說(shuō),在任何一個(gè)時(shí)間段內(nèi)只有一個(gè)任務(wù)可以運(yùn)行;因此, 其他進(jìn)程在時(shí)間上的拖欠將增大(wait_runtime)。因此,一旦某個(gè)任務(wù)進(jìn)入調(diào)度,它將努力趕上它所欠下的時(shí)間(并且要比所欠時(shí)間多一點(diǎn),因?yàn)樵谧汾s時(shí)間期間,fair_clock?不會(huì)停止計(jì)時(shí))。

粒度和延遲如何關(guān)聯(lián)?

關(guān)聯(lián)粒度和延遲的簡(jiǎn)單等式為:?

gran = (lat/nr) - (lat/nr/nr)

其中 gran = 粒度,

lat = 延遲,而?

nr = 運(yùn)行中的任務(wù)數(shù)。

加權(quán)任務(wù)引入了優(yōu)先級(jí)。假設(shè)我們有兩個(gè)任務(wù):其中一個(gè)任務(wù)占用 CPU 的時(shí)間量是另一個(gè)任務(wù)的兩倍,比例為 2:1。執(zhí)行數(shù)學(xué)轉(zhuǎn)換后,對(duì)于權(quán)重為 0.5 的任務(wù),時(shí)間流逝的速度是以前的兩倍。我們根據(jù)?fair_clock?對(duì)樹(shù)進(jìn)行排隊(duì)。

請(qǐng)注意,CFS 沒(méi)有使用時(shí)間片(time slices),至少,沒(méi)有優(yōu)先使用。CFS 中的時(shí)間片具有可變的長(zhǎng)度并且動(dòng)態(tài)確定。

運(yùn)行時(shí)調(diào)優(yōu)選項(xiàng)

引入了重要的?sysctls?來(lái)在運(yùn)行時(shí)對(duì)調(diào)度程序進(jìn)行調(diào)優(yōu)(以?ns?結(jié)尾的名稱以納秒為單位):

sched_latency_ns:針對(duì) CPU 密集型任務(wù)進(jìn)行目標(biāo)搶占延遲(Targeted preemption latency)。

sched_batch_wakeup_granularity_ns:針對(duì)?SCHED_BATCH?的喚醒(Wake-up)粒度。

sched_wakeup_granularity_ns:針對(duì)?SCHED_OTHER?的喚醒粒度。

sched_compat_yield:由于 CFS 進(jìn)行了改動(dòng),嚴(yán)重依賴?sched_yield()?的行為的應(yīng)用程序可以要求不同的性能,因此推薦啟用?sysctls。

sched_child_runs_first:child 在?fork?之后進(jìn)行調(diào)度;此為默認(rèn)設(shè)置。如果設(shè)置為 0,那么先調(diào)度 parent。

sched_min_granularity_ns:針對(duì) CPU 密集型任務(wù)執(zhí)行最低級(jí)別搶占粒度。

sched_features:包含各種與調(diào)試相關(guān)的特性的信息。

sched_stat_granularity_ns:收集調(diào)度程序統(tǒng)計(jì)信息的粒度。

系統(tǒng)中運(yùn)行時(shí)參數(shù)的典型值

新的調(diào)度程序調(diào)試接口

新調(diào)度程序附帶了一個(gè)非常棒的調(diào)試接口,還提供了運(yùn)行時(shí)統(tǒng)計(jì)信息,分別在 kernel/sched_debug.c 和 kernel/sched_stats.h 中實(shí)現(xiàn)。要提供調(diào)度程序的運(yùn)行時(shí)信息和調(diào)試信息,需要將一些文件添加到 proc pseudo 文件系統(tǒng):

/proc/sched_debug:顯示運(yùn)行時(shí)調(diào)度程序可調(diào)優(yōu)選項(xiàng)的當(dāng)前值、CFS 統(tǒng)計(jì)信息和所有可用 CPU 的運(yùn)行隊(duì)列信息。當(dāng)讀取這個(gè) proc 文件時(shí),將調(diào)用?sched_debug_show()?函數(shù)并在 sched_debug.c 中定義。

/proc/schedstat:為所有相關(guān)的 CPU 顯示特定于運(yùn)行隊(duì)列的統(tǒng)計(jì)信息以及 SMP 系統(tǒng)中特定于域的統(tǒng)計(jì)信息。kernel/sched_stats.h 中定義的?show_schedstat()?函數(shù)將處理 proc 條目中的讀操作。

/proc/[PID]/sched:顯示與相關(guān)調(diào)度實(shí)體有關(guān)的信息。在讀取這個(gè)文件時(shí),將調(diào)用 kernel/sched_debug.c 中定義的?proc_sched_show_task()?函數(shù)

CFS內(nèi)部原理

Linux 內(nèi)的所有任務(wù)都由稱為 task_struct 的任務(wù)結(jié)構(gòu)表示。該結(jié)構(gòu)(以及其他相關(guān)內(nèi)容)完整地描述了任務(wù)并包括了任務(wù)的當(dāng)前狀態(tài)、其堆棧、進(jìn)程標(biāo)識(shí)、優(yōu)先級(jí)(靜態(tài)和動(dòng)態(tài))等等。可以在 ./linux/include/linux/sched.h 中找到這些內(nèi)容以及相關(guān)結(jié)構(gòu)。 但是因?yàn)椴皇撬腥蝿?wù)都是可運(yùn)行的,在 task_struct 中不會(huì)發(fā)現(xiàn)任何與 CFS 相關(guān)的字段。 相反,會(huì)創(chuàng)建一個(gè)名為 sched_entity 的新結(jié)構(gòu)來(lái)跟蹤調(diào)度信息。

任務(wù)和紅黑樹(shù)的結(jié)構(gòu)層次關(guān)系

樹(shù)的根通過(guò) rb_root 元素通過(guò) cfs_rq 結(jié)構(gòu)(在 ./kernel/sched.c 中)引用。紅黑樹(shù)的葉子不包含信息,但是內(nèi)部節(jié)點(diǎn)代表一個(gè)或多個(gè)可運(yùn)行的任務(wù)。紅黑樹(shù)的每個(gè)節(jié)點(diǎn)都由 rb_node 表示,它只包含子引用和父對(duì)象的顏色。 rb_node 包含在 sched_entity 結(jié)構(gòu)中,該結(jié)構(gòu)包含rb_node引用、負(fù)載權(quán)重以及各種統(tǒng)計(jì)數(shù)據(jù)。最重要的是,sched_entity 包含 vruntime(64 位字段),它表示任務(wù)運(yùn)行的時(shí)間量,并作為紅黑樹(shù)的索引。 最后,task_struct 位于頂端,它完整地描述任務(wù)并包含 sched_entity 結(jié)構(gòu)。

就 CFS 部分而言,調(diào)度函數(shù)非常簡(jiǎn)單。 在 ./kernel/sched.c 中,通用 schedule() 函數(shù),它會(huì)先搶占當(dāng)前運(yùn)行任務(wù)(除非它通過(guò) yield() 代碼先搶占自己)。注意 CFS 沒(méi)有真正的時(shí)間切片概念用于搶占,因?yàn)閾屨紩r(shí)間是可變的。當(dāng)前運(yùn)行任務(wù)(現(xiàn)在被搶占的任務(wù))通過(guò)對(duì) put_prev_task 調(diào)用(通過(guò)調(diào)度類)返回到紅黑樹(shù)。當(dāng)schedule函數(shù)開(kāi)始確定下一個(gè)要調(diào)度的任務(wù)時(shí),它會(huì)調(diào)用 pick_next_task函數(shù)。此函數(shù)也是通用的(在./kernel/sched.c 中),但它會(huì)通過(guò)調(diào)度器類調(diào)用 CFS 調(diào)度器。

CFS調(diào)度算法的核心是選擇具有最小vruntine的任務(wù)。運(yùn)行隊(duì)列采用紅黑樹(shù)方式存放,其中節(jié)點(diǎn)的鍵值便是可運(yùn)行進(jìn)程的虛擬運(yùn)行時(shí)間。CFS調(diào)度器選取待運(yùn)行的下一個(gè)進(jìn)程,是所有進(jìn)程中vruntime最小的那個(gè),他對(duì)應(yīng)的便是在樹(shù)中最左側(cè)的葉子節(jié)點(diǎn)。實(shí)現(xiàn)選擇的函數(shù)為pick_next_task_fair,CFS 中的 pick_next_task 函數(shù)可以在 ./kernel/sched_fair.c(稱為pick_next_task_fair())中找到。 此函數(shù)只是從紅黑樹(shù)中獲取最左端的任務(wù)并返回相關(guān)sched_entity。通過(guò)此引用,一個(gè)簡(jiǎn)單的 task_of()調(diào)用確定返回的task_struct 引用。通用調(diào)度器最后為此任務(wù)提供處理器。

static struct task_struct *pick_next_task_fair(struct rq *rq)

{

struct task_struct *p;

struct cfs_rq *cfs_rq = &rq->cfs;

struct sched_entity *se;

if (unlikely(!cfs_rq->nr_running))

return NULL;

do {/*此循環(huán)為了考慮組調(diào)度*/

se = pick_next_entity(cfs_rq);

set_next_entity(cfs_rq, se);/*設(shè)置為當(dāng)前運(yùn)行進(jìn)程*/

cfs_rq = group_cfs_rq(se);

} while (cfs_rq);

p = task_of(se);

hrtick_start_fair(rq, p);

return p;

}

實(shí)質(zhì)工作調(diào)用__pick_next_entity完成。

/*函數(shù)本身并不會(huì)遍歷數(shù)找到最左葉子節(jié)點(diǎn)(即就是所有進(jìn)程中vruntime最小的那個(gè)),因?yàn)樵撝狄呀?jīng)緩存在rb_leftmost字段中*/

static struct sched_entity *__pick_next_entity(struct cfs_rq *cfs_rq)

{

/*rb_leftmost為保存的紅黑樹(shù)的最左邊的節(jié)點(diǎn)*/

struct rb_node *left = cfs_rq->rb_leftmost;

if (!left)

return NULL;

return rb_entry(left, struct sched_entity, run_node);

}

重要的 CFS 數(shù)據(jù)結(jié)構(gòu)

對(duì)于每個(gè) CPU,CFS 使用按時(shí)間排序的紅黑(red-black)樹(shù)。

紅黑樹(shù)是一種自平衡二叉搜索樹(shù),這種數(shù)據(jù)結(jié)構(gòu)可用于實(shí)現(xiàn)關(guān)聯(lián)數(shù)組。對(duì)于每個(gè)運(yùn)行中的進(jìn)程,在紅黑樹(shù)上都有一個(gè)節(jié)點(diǎn)。紅黑樹(shù)上位于最左側(cè)的進(jìn)程表示將進(jìn)行下一次調(diào)度的進(jìn)程。紅黑樹(shù)比較復(fù)雜,但它的操作具有良好的最差情況(worst-case)運(yùn)行時(shí),并且在實(shí)際操作中非常高效:它可以在?O(log n)?時(shí)間內(nèi)搜索、插入和刪除 ,其中?n?表示樹(shù)元素的數(shù)量。葉節(jié)點(diǎn)意義不大并且不包含數(shù)據(jù)。為節(jié)省內(nèi)存,有時(shí)使用單個(gè)哨兵(sentinel)節(jié)點(diǎn)執(zhí)行所有葉節(jié)點(diǎn)的角色。內(nèi)部節(jié)點(diǎn)到葉節(jié)點(diǎn)的所有引用都指向哨兵節(jié)點(diǎn)。

該樹(shù)方法能夠良好運(yùn)行的原因在于:

1.紅黑樹(shù)可以始終保持平衡。

2.由于紅黑樹(shù)是二叉樹(shù),查找操作的時(shí)間復(fù)雜度為對(duì)數(shù)。但是,除了最左側(cè)查找以外,很難執(zhí)行其他查找,并且最左側(cè)的節(jié)點(diǎn)指針始終被緩存。

3.對(duì)于大多數(shù)操作,紅黑樹(shù)的執(zhí)行時(shí)間為?O(log n),而以前的調(diào)度程序通過(guò)具有固定優(yōu)先級(jí)的優(yōu)先級(jí)數(shù)組使用?O(1)O(log n)?行為具有可測(cè)量的延遲,但是對(duì)于較大的任務(wù)數(shù)無(wú)關(guān)緊要。Molnar 在嘗試這種樹(shù)方法時(shí),首先對(duì)這一點(diǎn)進(jìn)行了測(cè)試。

4.紅黑樹(shù)可通過(guò)內(nèi)部存儲(chǔ)實(shí)現(xiàn) — 即不需要使用外部分配即可對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行維護(hù)。

讓我們了解一下實(shí)現(xiàn)這種新調(diào)度程序的一些關(guān)鍵數(shù)據(jù)結(jié)構(gòu)。

struct task_struct 的變化

CFS 去掉了?struct prio_array,并引入調(diào)度實(shí)體(scheduling entity)和調(diào)度類 (scheduling classes),分別由?struct sched_entity?和?struct sched_class?定義。因此,task_struct?包含關(guān)于?sched_entity?和?sched_class?這兩種結(jié)構(gòu)的信息:

struct task_struct {

/* Defined in 2.6.23:/usr/include/linux/sched.h */....

-? struct prio_array *array;

+? struct sched_entity se;

+? struct sched_class *sched_class;??

?....???....

};

struct sched_entity

運(yùn)行實(shí)體結(jié)構(gòu)為sched_entity,該結(jié)構(gòu)包含了完整的信息,用于實(shí)現(xiàn)對(duì)單個(gè)任務(wù)或任務(wù)組的調(diào)度。它可用于實(shí)現(xiàn)組調(diào)度。調(diào)度實(shí)體可能與進(jìn)程沒(méi)有關(guān)聯(lián)。所有的調(diào)度器都必須對(duì)進(jìn)程運(yùn)行時(shí)間做記賬。CFS不再有時(shí)間片的概念,但是他也必須維護(hù)每個(gè)進(jìn)程運(yùn)行的時(shí)間記賬,因?yàn)樗枰_保每個(gè)進(jìn)程只在公平分配給他的處理器時(shí)間內(nèi)運(yùn)行。CFS使用調(diào)度器實(shí)體結(jié)構(gòu)來(lái)最終運(yùn)行記賬。

sched_entity 結(jié)構(gòu)體簡(jiǎn)介

實(shí)現(xiàn)記賬功能,由系統(tǒng)定時(shí)器周期調(diào)用

static void update_curr(struct cfs_rq *cfs_rq)

{

struct sched_entity *curr = cfs_rq->curr;

u64 now = rq_of(cfs_rq)->clock;/*now計(jì)時(shí)器*/

unsigned long delta_exec;

if (unlikely(!curr))

return;

/*

* Get the amount of time the current task was running

* since the last time we changed load (this cannot

* overflow on 32 bits):

*/

/*獲得從最后一次修改負(fù)載后當(dāng)前任務(wù)所占用的運(yùn)行總時(shí)間*/

/*即計(jì)算當(dāng)前進(jìn)程的執(zhí)行時(shí)間*/

delta_exec = (unsigned long)(now - curr->exec_start);

if (!delta_exec)/*如果本次沒(méi)有執(zhí)行過(guò),不用重新更新了*/

return;

/*根據(jù)當(dāng)前可運(yùn)行進(jìn)程總數(shù)對(duì)運(yùn)行時(shí)間進(jìn)行加權(quán)計(jì)算*/

__update_curr(cfs_rq, curr, delta_exec);

curr->exec_start = now;/*將exec_start屬性置為now*/

if (entity_is_task(curr)) {/*下面為關(guān)于組調(diào)度的,暫時(shí)不分析了*/

struct task_struct *curtask = task_of(curr);

trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime);

cpuacct_charge(curtask, delta_exec);

account_group_exec_runtime(curtask, delta_exec);

}

}

struct sched_class

該調(diào)度類類似于一個(gè)模塊鏈,協(xié)助內(nèi)核調(diào)度程序工作。每個(gè)調(diào)度程序模塊需要實(shí)現(xiàn)?struct sched_class建議的一組函數(shù)。

sched_class 結(jié)構(gòu)體簡(jiǎn)介

函數(shù)功能說(shuō)明:

enqueue_task:當(dāng)某個(gè)任務(wù)進(jìn)入可運(yùn)行狀態(tài)時(shí),該函數(shù)將得到調(diào)用。它將調(diào)度實(shí)體(進(jìn)程)放入紅黑樹(shù)中,并對(duì)?nr_running?變量加 1。

dequeue_task:當(dāng)某個(gè)任務(wù)退出可運(yùn)行狀態(tài)時(shí)調(diào)用該函數(shù),它將從紅黑樹(shù)中去掉對(duì)應(yīng)的調(diào)度實(shí)體,并從?nr_running?變量中減 1。

yield_task:在?compat_yield sysctl?關(guān)閉的情況下,該函數(shù)實(shí)際上執(zhí)行先出隊(duì)后入隊(duì);在這種情況下,它將調(diào)度實(shí)體放在紅黑樹(shù)的最右端。

check_preempt_curr:該函數(shù)將檢查當(dāng)前運(yùn)行的任務(wù)是否被搶占。在實(shí)際搶占正在運(yùn)行的任務(wù)之前,CFS 調(diào)度程序模塊將執(zhí)行公平性測(cè)試。這將驅(qū)動(dòng)喚醒式(wakeup)搶占。

pick_next_task:該函數(shù)選擇接下來(lái)要運(yùn)行的最合適的進(jìn)程。

load_balance:每個(gè)調(diào)度程序模塊實(shí)現(xiàn)兩個(gè)函數(shù),load_balance_start()?和load_balance_next(),使用這兩個(gè)函數(shù)實(shí)現(xiàn)一個(gè)迭代器,在模塊的?load_balance?例程中調(diào)用。內(nèi)核調(diào)度程序使用這種方法實(shí)現(xiàn)由調(diào)度模塊管理的進(jìn)程的負(fù)載平衡。

set_curr_task:當(dāng)任務(wù)修改其調(diào)度類或修改其任務(wù)組時(shí),將調(diào)用這個(gè)函數(shù)。

task_tick:該函數(shù)通常調(diào)用自 time tick 函數(shù);它可能引起進(jìn)程切換。這將驅(qū)動(dòng)運(yùn)行時(shí)(running)搶占。

task_new:內(nèi)核調(diào)度程序?yàn)檎{(diào)度模塊提供了管理新任務(wù)啟動(dòng)的機(jī)會(huì)。CFS 調(diào)度模塊使用它進(jìn)行組調(diào)度,而用于實(shí)時(shí)任務(wù)的調(diào)度模塊則不會(huì)使用這個(gè)函數(shù)。

運(yùn)行隊(duì)列中CFS 有關(guān)的字段

對(duì)于每個(gè)運(yùn)行隊(duì)列,都提供了一種結(jié)構(gòu)來(lái)保存相關(guān)紅黑樹(shù)的信息。

struct cfs_rq {

struct load_weight load;/*運(yùn)行負(fù)載*/

unsigned long nr_running;/*運(yùn)行進(jìn)程個(gè)數(shù)*/

u64 exec_clock;

u64 min_vruntime;/*保存的最小運(yùn)行時(shí)間*/

struct rb_root tasks_timeline;/*運(yùn)行隊(duì)列樹(shù)根*/

struct rb_node *rb_leftmost;/*保存的紅黑樹(shù)最左邊的

節(jié)點(diǎn),這個(gè)為最小運(yùn)行時(shí)間的節(jié)點(diǎn),當(dāng)進(jìn)程

選擇下一個(gè)來(lái)運(yùn)行時(shí),直接選擇這個(gè)*/

struct list_head tasks;

struct list_head *balance_iterator;

/*

* 'curr' points to currently running entity on this cfs_rq.

* It is set to NULL otherwise (i.e when none are currently running).

*/

struct sched_entity *curr, *next, *last;

unsigned int nr_spread_over;

#ifdef CONFIG_FAIR_GROUP_SCHED

struct rq *rq; /* cpu runqueue to which this cfs_rq is attached */

/*

* leaf cfs_rqs are those that hold tasks (lowest schedulable entity in

* a hierarchy). Non-leaf lrqs hold other higher schedulable entities

* (like users, containers etc.)

*

* leaf_cfs_rq_list ties together list of leaf cfs_rq's in a cpu. This

* list is used during load balance.

*/

struct list_head leaf_cfs_rq_list;

struct task_group *tg; /* group that "owns" this runqueue */

#ifdef CONFIG_SMP

/*

* the part of load.weight contributed by tasks

*/

unsigned long task_weight;

/*

*? h_load = weight * f(tg)

*

* Where f(tg) is the recursive weight fraction assigned to

* this group.

*/

unsigned long h_load;

/*

* this cpu's part of tg->shares

*/

unsigned long shares;

/*

* load.weight at the time we set shares

*/

unsigned long rq_weight;

#endif

#endif

};


內(nèi)核 2.6.24 中的變化

新版本中不再追趕全局時(shí)鐘(fair_clock),任務(wù)之間將彼此追趕。將引入每個(gè)任務(wù)(調(diào)度實(shí)體)的時(shí)鐘?vruntime(wall_time/task_weight),并且將使用近似的平均時(shí)間初始化新任務(wù)的時(shí)鐘。其他重要改動(dòng)將影響關(guān)鍵數(shù)據(jù)結(jié)構(gòu)。下面展示了?struct sched_entity?中的預(yù)期變動(dòng):

2.6.24 版本中 sched_entity 結(jié)構(gòu)的預(yù)期變動(dòng)
2.6.24 版本中 cfs_rq 結(jié)構(gòu)的預(yù)期變動(dòng)
2.6.24版本中新添加的 task_group 結(jié)構(gòu)

每個(gè)任務(wù)都跟蹤它的運(yùn)行時(shí),并根據(jù)該值對(duì)任務(wù)進(jìn)行排隊(duì)。這意味著運(yùn)行最少的任務(wù)將位于樹(shù)的最左側(cè)。同樣,通過(guò)對(duì)時(shí)間加權(quán)劃分優(yōu)先級(jí)。每個(gè)任務(wù)在下面的時(shí)間段內(nèi)力求獲得精確調(diào)度:

sched_period = (nr_running > sched_nr_latency) ? sysctl_sched_latency : ((nr_running * sysctl_sched_latency) / sched_nr_latency)

其中?sched_nr_latency?=?(sysctl_sched_latency / sysctl_sched_min_granularity)。這表示,當(dāng)可運(yùn)行任務(wù)數(shù)大于?latency_nr?時(shí),將線性延長(zhǎng)調(diào)度周期。sched_fair.c 中定義的?sched_slice()?是進(jìn)行這些計(jì)算的位置。因此,如果每個(gè)可運(yùn)行任務(wù)運(yùn)行與?sched_slice()?等價(jià)的時(shí)間,那么將花費(fèi)的時(shí)間為?sched_period,每個(gè)任務(wù)將運(yùn)行與其權(quán)重成比例的時(shí)間量。此外,在任何時(shí)刻,CFS 都承諾超前運(yùn)行?sched_period,因?yàn)樽詈髨?zhí)行調(diào)度的任務(wù)將在這個(gè)時(shí)限內(nèi)再次運(yùn)行。

因此,當(dāng)一個(gè)新任務(wù)變?yōu)榭蛇\(yùn)行狀態(tài)時(shí),對(duì)其位置有嚴(yán)格的要求。在所有其他任務(wù)運(yùn)行之前,此任務(wù)不能運(yùn)行;否則,將破壞對(duì)這些任務(wù)作出的承諾。然而,由于該任務(wù)確實(shí)進(jìn)行了排隊(duì),對(duì)運(yùn)行隊(duì)列的額外權(quán)重將縮短其他所有任務(wù)的時(shí)間片,在?sched_priod?的末尾釋放一點(diǎn)位置,剛好滿足新任務(wù)的需求。這個(gè)新的任務(wù)就被放在這個(gè)位置。

優(yōu)先級(jí)和CFS

CFS 不直接使用優(yōu)先級(jí)而是將其用作允許任務(wù)執(zhí)行的時(shí)間的衰減系數(shù)。 低優(yōu)先級(jí)任務(wù)具有更高的衰減系數(shù),而高優(yōu)先級(jí)任務(wù)具有較低的衰減系數(shù)。 這意味著與高優(yōu)先級(jí)任務(wù)相比,低優(yōu)先級(jí)任務(wù)允許任務(wù)執(zhí)行的時(shí)間消耗得更快。 這是一個(gè)絕妙的解決方案,可以避免維護(hù)按優(yōu)先級(jí)調(diào)度的運(yùn)行隊(duì)列。

CFS 組調(diào)度

考慮一個(gè)兩用戶示例,用戶 A 和用戶 B 在一臺(tái)機(jī)器上運(yùn)行作業(yè)。用戶 A 只有兩個(gè)作業(yè)正在運(yùn)行,而用戶 B 正在運(yùn)行 48 個(gè)作業(yè)。組調(diào)度使 CFS 能夠?qū)τ脩?A 和用戶 B 進(jìn)行公平調(diào)度,而不是對(duì)系統(tǒng)中運(yùn)行的 50 個(gè)作業(yè)進(jìn)行公平調(diào)度。每個(gè)用戶各擁有 50% 的 CPU 使用。用戶 B 使用自己 50% 的 CPU 分配運(yùn)行他的 48 個(gè)作業(yè),而不會(huì)占用屬于用戶 A 的另外 50% 的 CPU 分配。

CFS 調(diào)度模塊(在 kernel/sched_fair.c 中實(shí)現(xiàn))用于以下調(diào)度策略:SCHED_NORMAL、SCHED_BATCH?和?SCHED_IDLE。對(duì)于?SCHED_RR?和?SCHED_FIFO?策略,將使用實(shí)時(shí)調(diào)度模塊(該模塊在 kernel/sched_rt.c 中實(shí)現(xiàn))。

CFS 另一個(gè)有趣的地方是組調(diào)度 概念(在 2.6.24 內(nèi)核中引入)。組調(diào)度是另一種為調(diào)度帶來(lái)公平性的方式,尤其是在處理產(chǎn)生很多其他任務(wù)的任務(wù)時(shí)。 假設(shè)一個(gè)產(chǎn)生了很多任務(wù)的服務(wù)器要并行化進(jìn)入的連接(HTTP服務(wù)器的典型架構(gòu))。不是所有任務(wù)都會(huì)被統(tǒng)一公平對(duì)待,CFS 引入了組來(lái)處理這種行為。產(chǎn)生任務(wù)的服務(wù)器進(jìn)程在整個(gè)組中(在一個(gè)層次結(jié)構(gòu)中)共享它們的虛擬運(yùn)行時(shí),而單個(gè)任務(wù)維持其自己獨(dú)立的虛擬運(yùn)行時(shí)。這樣單個(gè)任務(wù)會(huì)收到與組大致相同的調(diào)度時(shí)間。我們會(huì)發(fā)現(xiàn) /proc 接口用于管理進(jìn)程層次結(jié)構(gòu),讓我們對(duì)組的形成方式有完全的控制。使用此配置,我們可以跨用戶、跨進(jìn)程或其變體分配公平性。

調(diào)度類和域

與 CFS 一起引入的是調(diào)度類概念。每個(gè)任務(wù)都屬于一個(gè)調(diào)度類,這決定了任務(wù)將如何調(diào)度。 調(diào)度類定義一個(gè)通用函數(shù)集(通過(guò)sched_class),函數(shù)集定義調(diào)度器的行為。例如,每個(gè)調(diào)度器提供一種方式, 添加要調(diào)度的任務(wù)、調(diào)出要運(yùn)行的下一個(gè)任務(wù)、提供給調(diào)度器等等。每個(gè)調(diào)度器類都在一對(duì)一連接的列表中彼此相連,使類可以迭代(例如,要啟用給定處理器的禁用)。一般結(jié)構(gòu)如下圖所示。注意,將任務(wù)函數(shù)加入隊(duì)列或脫離隊(duì)列只需從特定調(diào)度結(jié)構(gòu)中加入或移除任務(wù)。 函數(shù) pick_next_task 選擇要執(zhí)行的下一個(gè)任務(wù)(取決于調(diào)度類的具體策略)。

調(diào)度類視圖

但是不要忘了調(diào)度類是任務(wù)結(jié)構(gòu)本身的一部分,這一點(diǎn)簡(jiǎn)化了任務(wù)的操作,無(wú)論其調(diào)度類具體如何實(shí)現(xiàn)。例如, 以下函數(shù)用 ./kernel/sched.c 中的新任務(wù)搶占當(dāng)前運(yùn)行任務(wù)(其中 curr 定義了當(dāng)前運(yùn)行任務(wù), rq 代表 CFS 紅黑樹(shù)而 p 是下一個(gè)要調(diào)度的任務(wù)):

static inline void check_preempt( struct rq *rq, struct task_struct *p ){?

?rq->curr->sched_class->check_preempt_curr( rq, p );

}

如果此任務(wù)正使用公平調(diào)度類,則 check_preempt_curr() 將解析為check_preempt_wakeup()。 我們可以在 ./kernel/sched_rt.c,/kernel/sched_fair.c 和 ./kernel/sched_idle.c 中查看這些關(guān)系。

調(diào)度類是調(diào)度發(fā)生變化的另一個(gè)有趣的地方,但是隨著調(diào)度域的增加,功能也在增加。 這些域允許您出于負(fù)載平衡和隔離的目的將一個(gè)或多個(gè)處理器按層次關(guān)系分組。 一個(gè)或多個(gè)處理器能夠共享調(diào)度策略(并在其之間保持負(fù)載平衡)或?qū)崿F(xiàn)獨(dú)立的調(diào)度策略從而故意隔離任務(wù)。

2.6.24 中的組調(diào)度有哪些改變

在 2.6.24 中,我們將能夠?qū)φ{(diào)度程序進(jìn)行調(diào)優(yōu),從而實(shí)現(xiàn)對(duì)用戶或組的公平性,而不是任務(wù)公平性。可以將任務(wù)進(jìn)行分組,形成多個(gè)實(shí)體,調(diào)度程序?qū)⑵降葘?duì)待這些實(shí)體,繼而公平對(duì)待實(shí)體中的任務(wù)。要啟用這個(gè)特性,在編譯內(nèi)核時(shí)需要選擇?CONFIG_FAIR_GROUP_SCHED。目前,只有?SCHED_NORMAL?和SCHED_BATCH?任務(wù)可以進(jìn)行分組。

可以使用兩個(gè)獨(dú)立的方法對(duì)任務(wù)進(jìn)行分組,它們分別基于:

1.用戶 ID。

2.cgroup pseudo 文件系統(tǒng):這個(gè)選項(xiàng)使管理員可以根據(jù)需要?jiǎng)?chuàng)建組。有關(guān)更多細(xì)節(jié),閱讀內(nèi)核源文檔目錄中的 cgroups.txt 文件。

3.內(nèi)核配置參數(shù)?CONFIG_FAIR_USER_SCHED?和?CONFIG_FAIR_CGROUP_SCHED?可幫助您進(jìn)行選擇。

通過(guò)引入調(diào)度類并通過(guò)增強(qiáng)調(diào)度統(tǒng)計(jì)信息來(lái)簡(jiǎn)化調(diào)試,這個(gè)新的調(diào)度程序進(jìn)一步擴(kuò)展了調(diào)度功能。

其他調(diào)度器

繼續(xù)研究調(diào)度,您將發(fā)現(xiàn)正在開(kāi)發(fā)中的調(diào)度器將會(huì)突破性能和擴(kuò)展性的界限。Con Kolivas 沒(méi)有被他的 Linux 經(jīng)驗(yàn)羈絆,他開(kāi)發(fā)出了另一個(gè) Linux 調(diào)度器,其縮寫為:BFS。該調(diào)度器據(jù)說(shuō)在 NUMA 系統(tǒng)以及移動(dòng)設(shè)備上具有更好的性能, 并且被引入了 Android 操作系統(tǒng)的一款衍生產(chǎn)品中。

展望

對(duì)于 Linux 技術(shù)而言,惟一不變的就是永恒的變化。今天,CFS 是 2.6 Linux 調(diào)度器; 明天可能就會(huì)是另一個(gè)新的調(diào)度器或一套可以被靜態(tài)或動(dòng)態(tài)調(diào)用的調(diào)度器。 CFS、RSDL 以及內(nèi)核背后的進(jìn)程中還有很多秘密等待我們?nèi)パ芯俊?/p>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容