1. 程式人生 > 其它 >03.排程器分析

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_FIFOrt_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;