CFS 調度器數據結構篇

在上一節我們瞭解了CFS的設計原理,包括CFS的引入,CFS是如何實現公平,CFS工作原理的。本小節我們重點在分析CFS調度器中涉及到的一些常見的數據結構,對這些數據結構做一個簡單的概括,梳理各個數據結構之間的關係圖出來。

調度類

CFS調度器是在Linux2.6.23引入的,在當時就提出了調度類概念,調度類就是將調度策略模塊化,有種面向對象的感覺。先來看下調度類的數據結構,調度類是通過struct sched_class數據結構表示

struct sched_class {
	const struct sched_class *next;

	void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
	void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);

	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,
					       struct rq_flags *rf);
	void (*put_prev_task)(struct rq *rq, struct task_struct *p);
	void (*set_curr_task)(struct rq *rq);
	void (*task_tick)(struct rq *rq, struct task_struct *p, int queued);

由於sched_class成員比較多,我們這裏先介紹幾個常用的。可以看到調度類中基本都是一些函數指針,這些函數指針分別代表的不同的含義。

  • next 是用來指向下一級的一個調度類,內核中爲每個調度策略提供了一個調度類,這些調度類是通過next成員鏈接到一起
  • enqueue_task: 用來將一個進程添加到就緒隊列中,同時會增加它的可運行的進程數
  • dequeue_task: 用來將一個進程從就緒隊列移除,同時減少可運行進程的數量
  • check_preempt_curr: 用來檢測當一個進程的狀態設置爲runnable時,檢查當前進程是否可以發生搶佔
  • pick_next_task: 在運行隊列中選擇下一個最合適的進程來運行
  • put_prev_task: 獲得當前進程之前的那個進程
  • set_curr_task: 用來設置當前進程的調度狀態等
  • task_tick: 在每個時鐘tick的時候會調度各個調度類中的tick函數

Linux內核都提供了那些調度類

extern const struct sched_class stop_sched_class;
extern const struct sched_class dl_sched_class;
extern const struct sched_class rt_sched_class;
extern const struct sched_class fair_sched_class;
extern const struct sched_class idle_sched_class;

 Linux內核定義了五種調度類,而且每種調度有對應的調度策略,而每種調度策略有會對應調度哪裏進程。

/*
 * 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

同時也提供了六種調度策略。下表示調度策略和調度類之間的關係

調度類 調度策略 調度對象
stop_sched_class(停機調度類) 停機的進程
dl_sched_class(限期調度類) SCHED_DEADLINE dl進程
rt_sched_class(實時調度類) SCHED_RR 或者 SCHED_FIFO 實時進程
fair_sched_class(公平調度類) SCHED_NORMAL 或者 SCHED_BATCH 普通進程
idle_sched_class(空閒調度類) SCHED_IDLE idle進程

同時這些調度類之間是有優先級關係的。

#ifdef CONFIG_SMP
#define sched_class_highest (&stop_sched_class)
#else
#define sched_class_highest (&dl_sched_class)
#endif
#define for_each_class(class) \
   for (class = sched_class_highest; class; class = class->next)

如果定義了SMP,則最高優先級的是stop調度類。調度類的優先級關係

stop_sched_class > dl_sched_class > rt_sched_class > fair_sched_class > idle_shced_class

調度實體

struct sched_entity {
	/* For load-balancing: */
	struct load_weight		load;
	unsigned long			runnable_weight;
	struct rb_node			run_node;
	struct list_head		group_node;
	unsigned int			on_rq;

	u64				exec_start;
	u64				sum_exec_runtime;
	u64				vruntime;
	u64				prev_sum_exec_runtime;

	u64				nr_migrations;

	struct sched_statistics		statistics;

從Linux2.6.23開始引入了調度實體的概念,調度實體封裝了進程的一些重要的信息。在之前的O(1)算法中調度的單位都是task_struct,而在Linux2.6.23引入調度模塊化後,調度的單位成爲調度實體sched_entity

  • load就是此進程的權重
  • run_node:CFS調度是通過紅黑樹來管理進程的,這個是紅黑樹的節點
  • on_rq: 此值爲1時,代表此進程在運行隊列中
  • exec_start: 記錄這個進程在CPU上開始執行任務的時間
  • sum_exec_runtime: 記錄這個進程總的運行時間
  • vruntime: 代表的是進程的虛擬運行時間
  • prev_sum_exec_runtime: 記錄前面一個進程的總的運行時間
  • nr_migrations: 負載均衡時進程的遷移次數
  • statistics:進程的統計信息

紅黑樹

紅黑樹大家肯定不陌生了,樹左邊節點的值永遠比樹右邊接的值小。

在O(n)和O(1)的調度器中運行隊列都是通過數組鏈表來管理的,而在CFS調度中拋棄了之前的數據結構,採用了以時間爲鍵值的一棵紅黑樹。其中的時間鍵值就是進程的vruntime。

CFS維護了一課以時間爲排序的紅黑樹,所有的紅黑樹節點都是通過進程的se.vruntime來作爲key來進行排序。CFS每次調度的時候總是選擇這棵紅黑樹最左邊的節點,然後來調度它。隨着時間的推移,之前在最左邊的節點隨着運行,進程的vruntime也隨之增大,這些進程慢慢的會添加到紅黑樹的右邊。循環往復這個樹上的所有進程都會被調度到,從而達到的公平。

同時CFS也會維護這棵樹上最小的vruntime的值cfs.min_vruntime,而且這個值是單調遞增的。此值用來跟蹤運行隊列中最小的vruntime的值。

運行隊列

系統中每個CPU上有有一個運行隊列struct rq數據結構,這個struct rq是個PER-CPU的,每個CPU上都要這樣的一個運行隊列,可以可以防止多個CPU去併發訪問一個運行隊列。

/*
 * This is the main, per-CPU runqueue data structure.
 *
 * Locking rule: those places that want to lock multiple runqueues
 * (such as the load balancing or the thread migration code), lock
 * acquire operations must be ordered by ascending &runqueue.
 */
struct rq {

        unsigned int		nr_running;

	/* capture load from *all* tasks on this CPU: */
	struct load_weight	load;
	unsigned long		nr_load_updates;
	u64			nr_switches;

	struct cfs_rq		cfs;
	struct rt_rq		rt;
	struct dl_rq		dl;

可以從註釋看到struct rq是一個per-cpu的變量。

  • nr_running: 代表這個運行隊列上總的運行進程數
  • load: 在這個CPU上所有進程的權重,這個CPU上可能運行的進程有實時進程,普通進程等
  • nr_switches: 進程切換的統計數
  • struct cfs_rq: 就是CFS調度類的一個運行隊列
  • struct rt_rq: 代表的是rt調度類的運行隊列
  • struct dl_rq: 代表的是dl調度類的運行隊列

可以得出的一個結論是,一個struct rq中包括了各種類型的進程,有DL的,有實時的,有普通的。通過將不同進程的掛到不同的運行隊列中管理。

/* CFS-related fields in a runqueue */
struct cfs_rq {
	struct load_weight	load;
	unsigned long		runnable_weight;
	unsigned int		nr_running;
	unsigned int		h_nr_running;

	u64			exec_clock;
	u64			min_vruntime;

從註釋上看struct cfs_rq代表的是CFS調度策略對應的運行隊列

  • load: 是這個CFS_rq的權重,包含着CFS就緒隊列中的所有進程
  • nr_running: 代表的是這個CFS運行隊列中可運行的進程數
  • min_vruntime: 此值代表的是CFS運行隊列中所有進程的最小的vruntime

看下運行隊列的關係圖

每個CPU中都存在一個struct rq運行隊列,struct rq中根據進程調度策略分爲不同的運行隊列,比如普通進程就會掛載到cfs_rq中,在struct cfs_rq中則定義了每一個調度實體,每一個調度實體根據vruntime的值添加到紅黑樹的節點中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章