CFS排程器(一)

2021-09-11 06:14:00 字數 3789 閱讀 2180

關於排程類和優先順序的概念,前面的文章《排程器概述》中已經做了介紹了,本文不在重述。本文主要關注的就是cfs排程器,或者叫做fair_sched_class排程類。這種排程器是被sched_normal、sched_batch這兩種排程策略使用的。(本文基於linux4.0)

提到排程器涉及到兩個問題:

1.怎麼根據參量快速選出下乙個要排程執行的程序?

2.怎麼定義和更新不同程序的相關參量?

帶著這兩個問題,我們慢慢去講解cfs排程器。

程序執行時間 = 程序權重 * cpu總時間 / 所有程序總權重
核心中每個排程實體都用乙個結構體成員來表示權重:

struct sched_entity
表示權重的結構體為:

struct load_weight
其中weight代表的是權重,inv_weight是為了方便計算出來的乙個中間值。核心根據優先順序nice來定義權重,約定nice值為0的程序權重值為1024,其他nice值的程序權重可以根據查表獲取:

/*

* nice levels are multiplicative, with a gentle 10% change for every

* nice level changed. i.e. when a cpu-bound task goes from nice 0 to

* nice 1, it will get ~10% less cpu time than another cpu-bound task

* that remained on nice 0.

* * the "10% effect" is relative and cumulative: from _any_ nice level,

* if you go up 1 level, it's -10% cpu usage, if you go down 1 level

* it's +10% cpu usage. (to achieve that we use a multiplier of 1.25.

* if a task goes up by ~10% and another task goes down by ~10% then

* the relative distance between them is ~25%.)

*/static const int prio_to_weight[40] = ;

其他nice值的權重值都是基於1024計算出來的,高一優先順序的程序權重總是低一優先順序程序的1.25倍。除了這個表,核心為了計算方便還定義了另外乙個表:

/*

* inverse (2^32/x) values of the prio_to_weight array, precalculated.

* * in cases where the weight does not change often, we can use the

* precalculated inverse to speed up arithmetics by turning divisions

* into multiplications:

*/static const u32 prio_to_wmult[40] = ;

核心設定程序權重的函式:

static void set_load_weight(struct task_struct *p)

load->weight = scale_load(prio_to_weight[prio]);

load->inv_weight = prio_to_wmult[prio];

}

cfs排程器的目標是保證每個程序的完全公平排程,嘗試對程序一視同仁,把cpu時間平均分配給每個程序去執行,程序執行的同時會記錄每個程序的執行時間,按照這種公平演算法,cpu每次排程都會優先選擇執行時間最少的那個程序,以保證程序的公平。

假如按照上述思想去設計排程器,那麼會有乙個問題,所有程序都是完全公平,那麼如何體現優先順序的不同呢?實際上核心並不是使用實際執行時間作為pick_next程序的參考量,而採用的是乙個虛擬時間,這個虛擬時間加入了不同優先順序權重因素的。虛擬時間的計算方法如下:

vruntime = delta_exec * nice0_weight / weight;
vruntime是虛擬執行時間,delta_exec是實際執行時間,nice0_weight是nice值為0的權重,也就是1024,weight程序的權重。這樣由上面的表可知,優先順序越高的程序算出來的vruntime越小,從而被cfs排程器選出來的概率就越大。runqueue中的所有程序都要維護乙個vruntime變數,並且把這些程序按照紅黑樹來管理,vrunquque總是處於紅黑樹的最左側,這樣在演算法選擇時具有更高的效率。

回到前面的問題,cfs排程器就是通過vruntime參量來決定當前要選擇執行的程序,為了選擇演算法的高效和快速,採用紅黑樹來管理這個參量,這就是cfs排程器的核心思想了。

下面是核心中的**定義:

struct sched_entity ;
對於實際執行時間轉換為虛擬時間,通過如下函式來實現calc_delta_fair:

static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)

有了這個虛擬時間,我們可以用來作為選擇pick next程序的參考量,那麼此參考量何時需要更新呢?

1.程序建立,加入執行佇列時要更新

2.週期排程時要更新

排程延遲表示是保證系統中的每個程序都能執行一遍所需要的時間。它不是乙個固定值,計算方式如下:

unsigned int sysctl_sched_latency = 6000000ull;         //6ms

static unsigned int sched_nr_latency = 8;

unsigned int sysctl_sched_min_granularity = 750000ull;

static u64 __sched_period(unsigned long nr_running)

return period;

}

當系統處於就runnable的程序少於乙個定值(預設值8)的時候,排程延遲也是固定乙個值不變(預設值6ms)。當系統就緒態程序個數超過這個值時,我們保證每個程序至少執行一定的時間,那麼排程延遲總時間就是nr_running * sysctl_sched_min_granularity。

當獲取到乙個排程延遲後,還需要計算乙個程序分配的時間片,cfs排程器和以往的o(1)排程器不同,不在有固定的時間片,而是通過權重計算出的時間片。按照前面權重的介紹,通過

時間片=排程延遲總時間*程序權重/總權重
來計算乙個程序的時間片。

static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)

slice = __calc_delta(slice, se->load.weight, load);

}return slice;

}

__calc_delta傳入的引數分別是總時間,當前排程實體的權重,整個runqueue的總權重。因此每個程序的時間片是不固定的,而是根據權重和系統中程序的數量發生著變化。

linux排程器(四) 主排程器與CFS

當核心從系統呼叫返回,或者從中斷處理程式返回,核心都會檢查當前程序是否設定了tif need resched標誌 或者程序主動放棄cpu時 sched yield,sleep或者收到sigstop,sigttop訊號 都會進入主排程器。同樣的我們先看一下主排程的框架部分,該部分就是sched.c s...

inux核心 CFS程序排程器

現代作業系統設計的目的在於管理底層硬體資源並使整個計算機系統執行效能達到最優。我們可以發現實現這一目的的方法可以歸結為兩點 cpu虛擬化和記憶體虛擬化。現代作業系統一般都能提供多工執行環境,每個程序都可以擁有自己的虛擬cpu,程序在執行過程中感覺自己是獨佔cpu。類似地,記憶體虛擬化是通過讓每個程序...

CFS排程器的思想的新理解

本文通過詳細分析老的排程器來說明cfs的優勢。總是新理解新理解的,我怎麼這麼沒完沒了啊,其實每隔一段時間我都會在工作之餘再讀一讀linux核心源 的關鍵部分,每次讀都有新的理解,然後就第一時間將心得記錄下來,今天又讀了cfs排程器,越來越發現其美妙了。這次配合了sched nice design.t...