Linux程序排程之CFS排程器概述--Linux程序的管理與排程(二十四)
1 前景回顧
1.1 程序排程
記憶體中儲存了對每個程序的唯一描述, 並通過若干結構與其他程序連線起來.
排程器面對的情形就是這樣, 其任務是在程式之間共享CPU時間, 創造並行執行的錯覺, 該任務分為兩個不同的部分, 其中一個涉及排程策略, 另外一個涉及上下文切換.
1.2 程序的分類
linux把程序區分為實時程序和非實時程序, 其中非實時程序進一步劃分為互動式程序和批處理程序
根據程序的不同分類Linux採用不同的排程策略.
對於實時程序,採用FIFO, Round Robin或者Earliest Deadline First (EDF)最早截止期限優先排程演算法|的排程策略.
1.3 linux排程器的演變
欄位 | 版本 |
---|---|
O(n)的始排程演算法 | linux-0.11~2.4 |
O(1)排程器 | linux-2.5 |
CFS排程器 | linux-2.6~至今 |
1.4 Linux的排程器組成
2個排程器
可以用兩種方法來啟用排程
一種是直接的, 比如程序打算睡眠或出於其他原因放棄CPU
另一種是通過週期性的機制, 以固定的頻率執行, 不時的檢測是否有必要
因此當前linux的排程程式由兩個排程器組成:主排程器,週期性排程器(兩者又統稱為通用排程器(generic scheduler)
並且每個排程器包括兩個內容:排程框架(其實質就是兩個函式框架)及排程器類
6種排程策略
linux核心目前實現了6中排程策略(即排程演算法), 用於對不同型別的程序進行排程, 或者支援某些特殊的功能
SCHED_NORMAL和SCHED_BATCH排程普通的非實時程序
SCHED_FIFO和SCHED_RR和SCHED_DEADLINE則採用不同的排程策略排程實時程序
SCHED_IDLE則在系統空閒時呼叫idle程序.
5個排程器類
而依據其排程策略的不同實現了5個排程器類, 一個排程器類可以用一種種或者多種排程策略排程某一類程序, 也可以用於特殊情況或者排程特殊功能的程序.
其所屬程序的優先順序順序為
stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
3個排程實體
排程器不限於排程程序, 還可以排程更大的實體, 比如實現組排程.
這種一般性要求排程器不直接操作程序, 而是處理可排程實體, 因此需要一個通用的資料結構描述這個排程實體,即seched_entity結構, 其實際上就代表了一個排程物件,可以為一個程序,也可以為一個程序組.
linux中針對當前可排程的實時和非實時程序, 定義了型別為seched_entity的3個排程實體
sched_dl_entity 採用EDF演算法排程的實時排程實體
sched_rt_entity 採用Roound-Robin或者FIFO演算法排程的實時排程實體 rt_sched_class
sched_entity 採用CFS演算法排程的普通非實時程序的排程實體
2 cfs完全公平排程器
2.1 CFS排程器類fair_sched_class
CFS完全公平排程器的排程器類叫fair_sched_class, 其定義在kernel/sched/fair.c, line 8521, 它是我們熟知的是struct sched_class排程器類型別, 將我們的CFS排程器與一些特定的函式關聯起來
/*
* All the scheduling class methods:
*/
const struct sched_class fair_sched_class = {
.next = &idle_sched_class, /* 下個優先順序的排程類, 所有的排程類通過next連結在一個連結串列中*/
.enqueue_task = enqueue_task_fair,
.dequeue_task = dequeue_task_fair,
.yield_task = yield_task_fair,
.yield_to_task = yield_to_task_fair,
.check_preempt_curr = check_preempt_wakeup,
.pick_next_task = pick_next_task_fair,
.put_prev_task = put_prev_task_fair,
#ifdef CONFIG_SMP
.select_task_rq = select_task_rq_fair,
.migrate_task_rq = migrate_task_rq_fair,
.rq_online = rq_online_fair,
.rq_offline = rq_offline_fair,
.task_waking = task_waking_fair,
.task_dead = task_dead_fair,
.set_cpus_allowed = set_cpus_allowed_common,
#endif
.set_curr_task = set_curr_task_fair,
.task_tick = task_tick_fair,
.task_fork = task_fork_fair,
.prio_changed = prio_changed_fair,
.switched_from = switched_from_fair,
.switched_to = switched_to_fair,
.get_rr_interval = get_rr_interval_fair,
.update_curr = update_curr_fair,
#ifdef CONFIG_FAIR_GROUP_SCHED
.task_move_group = task_move_group_fair,
#endif
};
成員 | 描述 |
---|---|
enqueue_task | 向就緒佇列中新增一個程序, 某個任務進入可執行狀態時,該函式將得到呼叫。它將排程實體(程序)放入紅黑樹中,並對 nr_running 變數加 1 |
dequeue_task | 將一個程序從就就緒佇列中刪除, 當某個任務退出可執行狀態時呼叫該函式,它將從紅黑樹中去掉對應的排程實體,並從 nr_running 變數中減 1 |
yield_task | 在程序想要資源放棄對處理器的控制權的時, 可使用在sched_yield系統呼叫, 會呼叫核心API yield_task完成此工作. compat_yield sysctl 關閉的情況下,該函式實際上執行先出隊後入隊;在這種情況下,它將排程實體放在紅黑樹的最右端 |
check_preempt_curr | 該函式將檢查當前執行的任務是否被搶佔。在實際搶佔正在執行的任務之前,CFS 排程程式模組將執行公平性測試。這將驅動喚醒式(wakeup)搶佔 |
pick_next_task | 該函式選擇接下來要執行的最合適的程序 |
put_prev_task | 用另一個程序代替當前執行的程序 |
set_curr_task | 當任務修改其排程類或修改其任務組時,將呼叫這個函式 |
task_tick | 在每次啟用週期排程器時, 由週期性排程器呼叫, 該函式通常呼叫自 time tick 函式;它可能引起程序切換。這將驅動執行時(running)搶佔 |
task_new | 核心排程程式為排程模組提供了管理新任務啟動的機會, 用於建立fork系統呼叫和排程器之間的關聯, 每次新程序建立後, 則用new_task通知排程器, CFS 排程模組使用它進行組排程,而用於實時任務的排程模組則不會使用這個函式 |
2.2 cfs的就緒佇列
就緒佇列是全域性排程器許多操作的起點, 但是程序並不是由就緒佇列直接管理的, 排程管理是各個排程器的職責, 因此在各個就緒佇列中嵌入了特定排程類的子就緒佇列(cfs的頂級排程就佇列 struct cfs_rq, 實時排程類的就緒佇列struct rt_rq和deadline排程類的就緒佇列struct dl_rq
/* CFS-related fields in a runqueue */
/* CFS排程的執行佇列,每個CPU的rq會包含一個cfs_rq,而每個組排程的sched_entity也會有自己的一個cfs_rq佇列 */
struct cfs_rq {
/* CFS執行佇列中所有程序的總負載 */
struct load_weight load;
/*
* nr_running: cfs_rq中排程實體數量
* h_nr_running: 只對程序組有效,其下所有程序組中cfs_rq的nr_running之和
*/
unsigned int nr_running, h_nr_running;
u64 exec_clock;
/*
* 當前CFS佇列上最小執行時間,單調遞增
* 兩種情況下更新該值:
* 1、更新當前執行任務的累計執行時間時
* 2、當任務從佇列刪除去,如任務睡眠或退出,這時候會檢視剩下的任務的vruntime是否大於min_vruntime,如果是則更新該值。
*/
u64 min_vruntime;
#ifndef CONFIG_64BIT
u64 min_vruntime_copy;
#endif
/* 該紅黑樹的root */
struct rb_root tasks_timeline;
/* 下一個排程結點(紅黑樹最左邊結點,最左邊結點就是下個排程實體) */
struct rb_node *rb_leftmost;
/*
* 'curr' points to currently running entity on this cfs_rq.
* It is set to NULL otherwise (i.e when none are currently running).
* curr: 當前正在執行的sched_entity(對於組雖然它不會在cpu上執行,但是當它的下層有一個task在cpu上執行,那麼它所在的cfs_rq就把它當做是該cfs_rq上當前正在執行的sched_entity)
* next: 表示有些程序急需執行,即使不遵從CFS排程也必須執行它,排程時會檢查是否next需要排程,有就排程next
*
* skip: 略過程序(不會選擇skip指定的程序排程)
*/
struct sched_entity *curr, *next, *last, *skip;
#ifdef CONFIG_SCHED_DEBUG
unsigned int nr_spread_over;
#endif
#ifdef CONFIG_SMP
/*
* CFS load tracking
*/
struct sched_avg avg;
u64 runnable_load_sum;
unsigned long runnable_load_avg;
#ifdef CONFIG_FAIR_GROUP_SCHED
unsigned long tg_load_avg_contrib;
#endif
atomic_long_t removed_load_avg, removed_util_avg;
#ifndef CONFIG_64BIT
u64 load_last_update_time_copy;
#endif
#ifdef CONFIG_FAIR_GROUP_SCHED
/*
* h_load = weight * f(tg)
*
* Where f(tg) is the recursive weight fraction assigned to
* this group.
*/
unsigned long h_load;
u64 last_h_load_update;
struct sched_entity *h_load_next;
#endif /* CONFIG_FAIR_GROUP_SCHED */
#endif /* CONFIG_SMP */
#ifdef CONFIG_FAIR_GROUP_SCHED
/* 所屬於的CPU rq */
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.
*/
int on_list;
struct list_head leaf_cfs_rq_list;
/* 擁有該CFS執行佇列的程序組 */
struct task_group *tg; /* group that "owns" this runqueue */
#ifdef CONFIG_CFS_BANDWIDTH
int runtime_enabled;
u64 runtime_expires;
s64 runtime_remaining;
u64 throttled_clock, throttled_clock_task;
u64 throttled_clock_task_time;
int throttled, throttle_count;
struct list_head throttled_list;
#endif /* CONFIG_CFS_BANDWIDTH */
#endif /* CONFIG_FAIR_GROUP_SCHED */
};
成員 | 描述 |
---|---|
nr_running | 佇列上可執行程序的數目 |
load | 就緒佇列上可執行程序的累計負荷權重 |
min_vruntime | 跟蹤記錄佇列上所有程序的最小虛擬執行時間. 這個值是實現與就緒佇列相關的虛擬時鐘的基礎 |
tasks_timeline | 用於在按時間排序的紅黑樹中管理所有程序 |
rb_leftmost | 總是設定為指向紅黑樹最左邊的節點, 即需要被排程的程序. 該值其實可以可以通過病例紅黑樹獲得, 但是將這個值儲存下來可以減少搜尋紅黑樹花費的平均時間 |
curr | 當前正在執行的sched_entity(對於組雖然它不會在cpu上執行,但是當它的下層有一個task在cpu上執行,那麼它所在的cfs_rq就把它當做是該cfs_rq上當前正在執行的sched_entity |
next | 表示有些程序急需執行,即使不遵從CFS排程也必須執行它,排程時會檢查是否next需要排程,有就排程next |
skip | 略過程序(不會選擇skip指定的程序排程) |