03.排程器分析
1.排程器
核心中用來安排程序執行的模組稱為排程器(scheduler),它可以切換程序狀態.
排程器是CPU中央處理器的管理員,主要負責完成做兩件事情:
- 選擇某些就緒進 程來執行
- 打斷某些執行的程序讓它們變為就緒狀態
排程器分配CPU時間的基本依據就是程序的優先順序。上下文 切換(context switch):將程序在CPU中切換執行的過程,核心承擔此任務,負責重建和儲存被切換掉之前的CPU狀態
2.排程類分析
sched_class結構體表示排程類,定義在kernel/sched/sched.h
成員分析
enqueue_task:向就緒佇列新增一個程序,當某個任務進入執行狀態,該函式將會被呼叫,將排程的實體放入到紅黑樹中
dequeue_task:將一個程序從就緒佇列中刪除,當某個任務退出可執行狀態時,呼叫該函式,從紅黑樹中移除對應的排程實體
yield_task:在程序想要資源放棄對處理器的控制權的時候,使sched_yiled系統呼叫 ,會呼叫核心的API處理操作
check_preempt_curr:檢查當前執行的任務是否被搶佔
pick_next_task:選擇要執行的最合適的程序
put_prev_task:用另一個程序代替當前的執行的程序
set_curr_task:當任務修改所呼叫的類,或修改它的任務組時,將呼叫這個函式
task_tick:每次啟用週期排程器時,由週期性排程器使用
struct sched_class { //系統中有多個排程類,按照排程的優先順序存放在連結串列中 const struct sched_class *next; //將程序加入到執行隊列當中,即將排程實體(程序)存放到紅黑樹中,並對nr_running變數自增1 void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags); //從執行的隊列當中刪除程序,並對nr_running變數自減1 void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags); //放棄CPU的執行權,該函式執行後進先出,直接將排程實體放在紅黑樹的最右端 void (*yield_task) (struct rq *rq); bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt); //用於檢查當前程序是否可被新程序搶佔 void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags); /* * It is the responsibility of the pick_next_task() method that will * return the next task to call put_prev_task() on the @prev task or * something equivalent. * * May return RETRY_TASK when it finds a higher prio class has runnable * tasks. */ //選擇下一個應用要執行的程序 struct task_struct * (*pick_next_task) (struct rq *rq, struct task_struct *prev); //將程序放回到執行隊列當中 void (*put_prev_task) (struct rq *rq, struct task_struct *p); #ifdef CONFIG_SMP //為程序選擇一個合適的CPU int (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags); //遷移任務到另一個CPU void (*migrate_task_rq)(struct task_struct *p); //用於喚醒程序 void (*task_waking) (struct task_struct *task); void (*task_woken) (struct rq *this_rq, struct task_struct *task); //修改CPU的親和力 void (*set_cpus_allowed)(struct task_struct *p, const struct cpumask *newmask); //啟動執行佇列 void (*rq_online)(struct rq *rq); //禁止執行佇列 void (*rq_offline)(struct rq *rq); #endif //當程序改變它的排程類或程序組時被呼叫 void (*set_curr_task) (struct rq *rq); //呼叫time tick函式,可能引起程序切換,驅動執行時(running)被搶佔 void (*task_tick) (struct rq *rq, struct task_struct *p, int queued); //當程序建立的時候呼叫,不同的排程策略的程序初始化也不同 void (*task_fork) (struct task_struct *p); //程序退出時呼叫 void (*task_dead) (struct task_struct *p); /* * The switched_from() call is allowed to drop rq->lock, therefore we * cannot assume the switched_from/switched_to pair is serliazed by * rq->lock. They are however serialized by p->pi_lock. */ //用於程序切換操作 void (*switched_from) (struct rq *this_rq, struct task_struct *task); void (*switched_to) (struct rq *this_rq, struct task_struct *task); //改變程序的優先順序 void (*prio_changed) (struct rq *this_rq, struct task_struct *task, int oldprio); unsigned int (*get_rr_interval) (struct rq *rq, struct task_struct *task); void (*update_curr) (struct rq *rq); #ifdef CONFIG_FAIR_GROUP_SCHED void (*task_move_group) (struct task_struct *p); #endif };
3.排程類
Linux排程類:dl_sched_class、rt_sched_class、fair_sched_class及idle_sched_class等。每一個程序都對應一種排程策略,每一種排程策略又對應一種排程類,每一個排程類可以對應多種排程策略.
SCHED_FIFO排程策略的實時程序永遠比SCHED_NORMAL排程策略的普通程序優先順序高
排程類的優先順序順序:
stop_sched_class > dl_sched_class > rt_sched_class > fair_sched_class > idle_sched_class
//linux核心排程策略原始碼:/include/uapi/linux/sched.h extern const struct sched_class stop_sched_class; extern const struct sched_class dl_sched_class; //實時排程器,排程策略:SCHED_FIFO,SCHED_RR extern const struct sched_class rt_sched_class; //完全公平排程器,排程策略:SCHED_NORMAL,SCHED_BATCH等 extern const struct sched_class fair_sched_class; extern const struct sched_class idle_sched_class; //優先順序最高,會中斷所有的其他程序,並且不會被其他的任務打斷 const struct sched_class stop_sched_class = { .next = &dl_sched_class, .enqueue_task = enqueue_task_stop, .dequeue_task = dequeue_task_stop, .yield_task = yield_task_stop, .check_preempt_curr = check_preempt_curr_stop, .pick_next_task = pick_next_task_stop, .put_prev_task = put_prev_task_stop, const struct sched_class dl_sched_class = { .next = &rt_sched_class, .enqueue_task = enqueue_task_dl, .dequeue_task = dequeue_task_dl, .yield_task = yield_task_dl, .check_preempt_curr = check_preempt_curr_dl, .pick_next_task = pick_next_task_dl, .put_prev_task = put_prev_task_dl, //作用於實時程序 const struct sched_class rt_sched_class = { .next = &fair_sched_class, .enqueue_task = enqueue_task_rt, .dequeue_task = dequeue_task_rt, .yield_task = yield_task_rt, .check_preempt_curr = check_preempt_curr_rt, .pick_next_task = pick_next_task_rt, .put_prev_task = put_prev_task_rt, //每個CPU的第一個PID=0執行緒,swapper是一個靜態執行緒,排程類屬於idel_sched_class,一般執行在開機過程和CPU異常的時候會做dump const struct sched_class idle_sched_class = { /* .next is NULL */ /* no enqueue/yield_task for idle tasks */ /* dequeue is not valid, we print a debug message there: */ .dequeue_task = dequeue_task_idle, .check_preempt_curr = check_preempt_curr_idle, .pick_next_task = pick_next_task_idle, .put_prev_task = put_prev_task_idle, // 原始碼目錄:kernel/sched/fair.c //公平排程器CFS,一般常用於執行緒 const struct sched_class fair_sched_class = { .next = &idle_sched_class, .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,
SCHED_NORMAL,SCHED_BATCH,SCHED_IDLE直接對映到fair_sched_class
SCHED_RR,SCHED_FIFO與rt_schedule_class進行關聯
linux排程核心選擇下一個合適的task執行時,會按照優先順序順序遍歷排程類的pick_next_task函式.
4.優先順序與排程策略
//原始碼:/include/linux/sched/prio.h
#define MAX_USER_RT_PRIO 100
#define MAX_RT_PRIO MAX_USER_RT_PRIO
#define MAX_PRIO (MAX_RT_PRIO + NICE_WIDTH)
#define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2)
程序分類:
- 實時程序(Real-Time Process):優先順序高,需要立即執行的程序
- 普通程序(Normal Process):優先順序低,更長執行時間的程序
程序的優先順序是一個0-139的數字,數字越小,優先順序越高,0-99為實時程序,100-139為普通程序
排程策略
unsigned int policy:儲存程序的排程策略
SHCED_NORMAL:用於普通程序,通過CFS排程器來實現
SHCED_BATCH:相當於SCHED_NORMAL分化的版本,採用分時策略,根據動態優先順序,分配CPU執行所需要資源
SHCED_IDLE:優先順序最低,在系統空空閒時才執行這類程序
SHCED_RR:輪流排程演算法(實時排程策略)
SHCED_DEADLINE:新支援的實時程序排程策略,針對突發性的計算
SCHED_BATCH用於非互動處理器消耗性程序,SHCED_IDLE是在系統負載很低時使用CFS
/*
* Scheduling policies
*/
#define SCHED_NORMAL 0
#define SCHED_FIFO 1
#define SCHED_RR 2
#define SCHED_BATCH 3
/* SCHED_ISO: reserved but not implemented yet */
#define SCHED_IDLE 5
#define SCHED_DEADLINE 6
5.排程器結構分析
程序排程任務:合理分配CPU時間給執行的程序
排程器目標:有效的分配CPU時間片
主排程器:通過schedule()函式來完成程序的選擇和切換
週期排程器:根據頻率自動呼叫
scheduler_tick函式:根據程序執行時間觸發排程
上下文切換:用於切換地址空間,切換暫存器,棧空間
原始碼目錄:kernel/sched/sched.h
/* 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:只對程序有效
unsigned int nr_running, h_nr_running;
u64 exec_clock;
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).
*/
struct sched_entity *curr, *next, *last, *skip;