linux調度器源碼分析 - 概述(一)


本文為原創,轉載請注明:http://www.cnblogs.com/tolimit/

 

引言

  調度器作為操作系統的核心部件,具有非常重要的意義,其隨着linux內核的更新也不斷進行着更新。本系列文章通過linux-3.18.3源碼進行調度器的學習和分析,一步一步將linux現有的調度器原原本本的展現出來。此篇文章作為開篇,主要介紹調度器的原理及重要數據結構。

調度器介紹

  隨着時代的發展,linux也從其初始版本穩步發展到今天,從2.4的非搶占內核發展到今天的可搶占內核,調度器無論從代碼結構還是設計思想上也都發生了翻天覆地的變化,其普通進程的調度算法也從O(1)到現在的CFS,一個好的調度算法應當考慮以下幾個方面:
  • 公平:保證每個進程得到合理的CPU時間。
  • 高效:使CPU保持忙碌狀態,即總是有進程在CPU上運行。
  • 響應時間:使交互用戶的響應時間盡可能短。
  • 周轉時間:使批處理用戶等待輸出的時間盡可能短。
  • 吞吐量:使單位時間內處理的進程數量盡可能多。
  • 負載均衡:在多核多處理器系統中提供更高的性能
  而整個調度系統至少包含兩種調度算法,是分別針對 實時 進程普通進程,所以在整個linux內核中,實時進程和普通進程是並存的,但它們使用的調度算法並不相同,普通進程使用的是CFS調度算法(紅黑樹調度)。之后會介紹調度器是怎么調度這兩種進程。

進程

  上一節已經說明,在linux中,進程主要分為兩種,一種為實時進程,一種為普通進程
  • 實時進程:對系統的響應時間要求很高,它們需要短的響應時間,並且這個時間的變化非常小,典型的實時進程有音樂播放器,視頻播放器等。
  • 普通進程:包括交互進程和非交互進程,交互進程如文本編輯器,它會不斷的休眠,又不斷地通過鼠標鍵盤進行喚醒,而非交互進程就如后台維護進程,他們對IO,響應時間沒有很高的要求,比如編譯器。
  它們在linux內核運行時是共存的, 實時進程的優先級為0~99,實時進程優先級不會在運行期間改變(靜態優先級),而普通進程的優先級為100~139,普通進程的優先級會在內核運行期間進行相應的改變(動態優先級)。
    

調度策略

  在linux系統中,調度策略分為
  • SCHED_NORMAL:普通進程使用的調度策略,現在此調度策略使用的是CFS調度器。
  • SCHED_FIFO:實時進程使用的調度策略,此調度策略的進程一旦使用CPU則一直運行,直到有比其更高優先級的實時進程進入隊列,或者其自動放棄CPU,適用於時間性要求比較高,但每次運行時間比較短的進程。
  • SCHED_RR:實時進程使用的時間片輪轉法策略,實時進程的時間片用完后,調度器將其放到隊列末尾,這樣每個實時進程都可以執行一段時間。適用於每次運行時間比較長的實時進程。

調度

  首先,我們需要清楚,什么樣的進程會進入調度器進行選擇,就是處於TASK_RUNNING狀態的進程,而其他狀態下的進程都不會進入調度器進行調度。系統發生調度的時機如下
  • 調用cond_resched()時
  • 顯式調用schedule()時
  • 從系統調用或者異常中斷返回用戶空間時
  • 從中斷上下文返回用戶空間時
  當開啟 內核搶占(默認開啟)時,會多出幾個調度時機,如下
  • 在系統調用或者異常中斷上下文中調用preempt_enable()時(多次調用preempt_enable()時,系統只會在最后一次調用時會調度)
  • 在中斷上下文中,從中斷處理函數返回到可搶占的上下文時(這里是中斷下半部,中斷上半部實際上會關中斷,而新的中斷只會被登記,由於上半部處理很快,上半部處理完成后才會執行新的中斷信號,這樣就形成了中斷可重入, 但是即使是中斷下半部, 也是不能夠被調度的)
  而在系統啟動調度器初始化時會初始化一個調度定時器,調度定時器每隔一定時間執行一個中斷,在中斷會對當前運行進程運行時間進行更新,如果進程需要被調度,在調度定時器中斷中會設置一個調度標志位,之后從定時器中斷返回,因為上面已經提到從中斷上下文返回時是可能有調度時機的,如果定時器中斷返回時正好是返回到用戶態空間, 而且調度標志位又置位了, 這時候就會做進程切換. 在內核源碼的匯編代碼中所有中斷返回處理都必須去判斷調度標志位是否設置,如設置則執行schedule()進行調度。而我們知道實時進程和普通進程是共存的,調度器是怎么協調它們之間的調度的呢,其實很簡單,每次調度時,會先在實時進程運行隊列中查看是否有可運行的實時進程,如果沒有,再去普通進程運行隊列找下一個可運行的普通進程,如果也沒有,則調度器會使用idle進程進行運行。之后的章節會放上代碼進行詳細說明。
  系統並不是每時每刻都允許調度的發生,當處於中斷期間的時候(無論是上半部還是下半部),調度是被系統禁止的,之后中斷過后才重新允許調度。而對於異常,系統並不會禁止調度,也就是在異常上下文中,系統是有可能發生調度的。

數據結構

  在這一節中,我們都是以普通進程作為講解對象,因為普通進程使用的調度算法為CFS調度算法,它是以紅黑樹為基礎的調度算法 ,其相比與實時進程的調度算法復雜很多,而實時進程在組織結構上與普通進程沒有太大差別,算法也較為簡單

組成形式

  圖1

  從圖1 中可以看出來,每個CPU對應包含一個運行隊列結構(struct rq),而每個運行隊列又包含有其自己的實時進程運行隊列(struct rt_rq)、普通進程運行隊列(struct cfs_rq)、和一個我自己也不太知道有什么用的dl隊列(struct dl_rq),也就是說每個CPU都有他們自己的實時進程運行隊列及普通進程運行隊列,為了方便,我們在圖中只描述普通進程的組織結構(最復雜的也是普通進程的組織結構), 紅色se則為當前CPU上正在執行的程序,藍色為下個將要執行的程序,其實圖中並不規范,實際上當進程運行時,會從紅黑樹中剝離出來,然后設定下一個調度進程,當進程運行時間結束時,再重新放入紅黑樹中。而為什么CPU0上有兩個藍色將被調度進程,將在組調度中解釋。而為什么紅黑樹中又有一個子紅黑樹,我們將在調度實體中解釋。

組調度(struct task_group)

  我們知道,linux是一個多用戶系統,如果有兩個進程分別屬於兩個用戶,而進程的優先級不同,會導致兩個用戶所占用的CPU時間不同,這樣顯然是不公平的(如果優先級差距很大,低優先級進程所屬用戶使用CPU的時間就很小),所以內核引入組調度。如果基於用戶分組,即使進程優先級不同,這兩個用戶使用的CPU時間都為50%。這就是為什么圖1 中CPU0有兩個藍色將被調度的程序,如果task_group1中的運行時間還沒有使用完,而當前進程運行時間使用完后,會調度task_group1中的下一個被調度進程;相反,如果task_group1的運行時間使用結束,則調用上一層的下一個被調度進程。需要注意的是,一個組調度中可能會有一部分是實時進程,一部分是普通進程,這也導致這種組要能夠滿足即能在實時調度中進行調度,又可以在CFS調度中進行調度。
  linux可以以以下兩種方式進行進程的分組:
  • 用戶ID:按照進程的USER ID進行分組,在對應的/sys/kernel/uid/目錄下會生成一個cpu.share的文件,可以通過配置該文件來配置用戶所占CPU時間比例。
  • cgourp(control group):生成組用於限制其所有進程,比如我生成一個組(生成后此組為空,里面沒有進程),設置其CPU使用率為10%,並把一個進程丟進這個組中,那么這個進程最多只能使用CPU的10%,如果我們將多個進程丟進這個組,這個組的所有進程平分這個10%。
     注意的是,這里的進程組概念和fork調用所產生的 父子進程組概念不一樣,文章所使用的進程組概念全為組調度中進程組的概念。為了管理組調度,內核引進了struct task_group結構,如下:
 1 /* 進程組,用於實現組調度 */
 2 struct task_group {
 3     /* 用於進程找到其所屬進程組結構 */
 4     struct cgroup_subsys_state css;
 5 
 6 #ifdef CONFIG_FAIR_GROUP_SCHED
 7     /* CFS調度器的進程組變量,在 alloc_fair_sched_group() 中進程初始化及分配內存 */
 8     /* 該進程組在每個CPU上都有對應的一個調度實體,因為有可能此進程組同時在兩個CPU上運行(它的A進程在CPU0上運行,B進程在CPU1上運行) */
 9     struct sched_entity **se;
10     /* 進程組在每個CPU上都有一個CFS運行隊列(為什么需要,稍后解釋) */
11     struct cfs_rq **cfs_rq;
12     /* 用於保存優先級默認為NICE 0的優先級 */
13     unsigned long shares;
14 
15 #ifdef    CONFIG_SMP
16     atomic_long_t load_avg;
17     atomic_t runnable_avg;
18 #endif
19 #endif
20 
21 #ifdef CONFIG_RT_GROUP_SCHED
22     /* 實時進程調度器的進程組變量,同 CFS */
23     struct sched_rt_entity **rt_se;
24     struct rt_rq **rt_rq;
25 
26     struct rt_bandwidth rt_bandwidth;
27 #endif
28 
29     struct rcu_head rcu;
30     /* 用於建立進程鏈表(屬於此調度組的進程鏈表) */
31     struct list_head list;
32 
33     /* 指向其上層的進程組,每一層的進程組都是它上一層進程組的運行隊列的一個調度實體,在同一層中,進程組和進程被同等對待 */
34     struct task_group *parent;
35     /* 進程組的兄弟結點鏈表 */
36     struct list_head siblings;
37     /* 進程組的兒子結點鏈表 */
38     struct list_head children;
39 
40 #ifdef CONFIG_SCHED_AUTOGROUP
41     struct autogroup *autogroup;
42 #endif
43 
44     struct cfs_bandwidth cfs_bandwidth;
45 };
  在struct task_group結構中,最重要的成員為 struct sched_entity ** se 和 struct cfs_rq ** cfs_rq。在圖1 中,root_task_group與task_group1都只有一個,它們在初始化時會根據CPU個數為se和cfs_rq分配空間,即在task_group1和root_task_group中會為每個CPU分配一個se和cfs_rq,同理用於實時進程的 struct sched_rt_entity ** rt_se 和 struct rt_rq ** rt_rq也是一樣。為什么這樣呢,原因就是在多核多CPU的情況下,同一進程組的進程有可能在不同CPU上同時運行,所以每個進程組都必須對每個CPU分配它的調度實體(struct sched_entity 和 struct sched_rt_entity)和運行隊列(struct cfs_rq 和 struct rt_rq)。

調度實體(struct sched_entity)

  在組調度中,也涉及到調度實體這個概念,它的結構為struct sched_entity(簡稱se),就是圖1 紅黑樹中的se。其實際上就代表了一個調度對象,可以為一個進程,也可以為一個進程組。對於根的紅黑樹而言,一個進程組就相當於一個調度實體,一個進程也相當於一個調度實體。我們可以先看看其結構,如下:
 1 /* 一個調度實體(紅黑樹的一個結點),其包含一組或一個指定的進程,包含一個自己的運行隊列,一個父親指針,一個指向需要調度的運行隊列指針 */
 2 struct sched_entity {
 3     /* 權重,在數組prio_to_weight[]包含優先級轉權重的數值 */
 4     struct load_weight    load;        /* for load-balancing */
 5     /* 實體在紅黑樹對應的結點信息 */
 6     struct rb_node        run_node;    
 7     /* 實體所在的進程組 */
 8     struct list_head    group_node;
 9     /* 實體是否處於紅黑樹運行隊列中 */
10     unsigned int        on_rq;
11 
12     /* 開始運行時間 */
13     u64            exec_start;
14     /* 總運行時間 */
15     u64            sum_exec_runtime;
16     /* 虛擬運行時間,在時間中斷或者任務狀態發生改變時會更新
17      * 其會不停增長,增長速度與load權重成反比,load越高,增長速度越慢,就越可能處於紅黑樹最左邊被調度
18      * 每次時鍾中斷都會修改其值
19      * 具體見calc_delta_fair()函數
20      */
21     u64            vruntime;
22     /* 進程在切換進CPU時的sum_exec_runtime值 */
23     u64            prev_sum_exec_runtime;
24 
25     /* 此調度實體中進程移到其他CPU組的數量 */
26     u64            nr_migrations;
27 
28 #ifdef CONFIG_SCHEDSTATS
29     /* 用於統計一些數據 */
30     struct sched_statistics statistics;
31 #endif
32 
33 #ifdef CONFIG_FAIR_GROUP_SCHED
34     /* 代表此進程組的深度,每個進程組都比其parent調度組深度大1 */
35     int            depth;
36     /* 父親調度實體指針,如果是進程則指向其運行隊列的調度實體,如果是進程組則指向其上一個進程組的調度實體
37      * 在 set_task_rq 函數中設置
38      */
39     struct sched_entity    *parent;
40     /* 實體所處紅黑樹運行隊列 */
41     struct cfs_rq        *cfs_rq;        
42     /* 實體的紅黑樹運行隊列,如果為NULL表明其是一個進程,若非NULL表明其是調度組 */
43     struct cfs_rq        *my_q;
44 #endif
45 
46 #ifdef CONFIG_SMP
47     /* Per-entity load-tracking */
48     struct sched_avg    avg;
49 #endif
50 };

  實際上,紅黑樹是根據 struct rb_node 建立起關系的,不過 struct rb_node 與 struct sched_entity 是一一對應關系,也可以簡單看為一個紅黑樹結點就是一個調度實體。可以看出,在 struct sched_entity 結構中,包含了一個進程(或進程組)調度的全部數據,其被包含在 struct task_struct 結構中的se中,如下:

 1 struct task_struct {
 2     ........
 3     /* 表示是否在運行隊列 */
 4     int on_rq;
 5 
 6     /* 進程優先級 
 7      * prio: 動態優先級,范圍為100~139,與靜態優先級和補償(bonus)有關
 8      * static_prio: 靜態優先級,static_prio = 100 + nice + 20 (nice值為-20~19,所以static_prio值為100~139)
 9      * normal_prio: 沒有受優先級繼承影響的常規優先級,具體見normal_prio函數,跟屬於什么類型的進程有關
10      */
11     int prio, static_prio, normal_prio;
12     /* 實時進程優先級 */
13     unsigned int rt_priority;
14     /* 調度類,調度處理函數類 */
15     const struct sched_class *sched_class;
16     /* 調度實體(紅黑樹的一個結點) */
17     struct sched_entity se;
18     /* 調度實體(實時調度使用) */
19     struct sched_rt_entity rt;
20 #ifdef CONFIG_CGROUP_SCHED
21     /* 指向其所在進程組 */
22     struct task_group *sched_task_group;
23 #endif
24     ........
25 }

  在 struct sched_entity 結構中,值得我們注意的成員是:

  • load:權重,通過優先級轉換而成,是vruntime計算的關鍵。
  • on_rq:表明是否處於CFS紅黑樹運行隊列中,需要明確一個觀點就是,CFS運行隊列里面包含有一個紅黑樹,但這個紅黑樹並不是CFS運行隊列的全部,因為紅黑樹僅僅是用於選擇出下一個調度程序的算法。很簡單的一個例子,普通程序運行時,其並不在紅黑樹中,但是還是處於CFS運行隊列中,其on_rq為真。只有准備退出、即將睡眠等待和轉為實時進程的進程其CFS運行隊列的on_rq為假。
  • vruntime:虛擬運行時間,調度的關鍵,其計算公式:一次調度間隔的虛擬運行時間 = 實際運行時間 * (NICE_0_LOAD / 權重)。可以看出跟實際運行時間和權重有關,紅黑樹就是以此作為排序的標准,優先級越高的進程在運行時其vruntime增長的越慢,其可運行時間相對就長,而且也越有可能處於紅黑樹的最左結點,調度器每次都選擇最左邊的結點為下一個調度進程。注意其值為單調遞增,在每個調度器的時鍾中斷時當前進程的虛擬運行時間都會累加。單純的說就是進程們都在比誰的vruntime最小,最小的將被調度。
  • cfs_rq:此調度實體所處於的CFS運行隊列。
  • my_q:如果此調度實體代表的是一個進程組,那么此調度實體就包含有一個自己的CFS運行隊列,其CFS運行隊列中存放的是此進程組中的進程,這些進程就不會在其他CFS運行隊列的紅黑樹中被包含(包括頂層紅黑樹也不會包含他們,他們只屬於這個進程組的紅黑樹)。
  對於怎么理解一個進程組有它自己的CFS運行隊列,其實很好理解,比如在根CFS運行隊列的紅黑樹上有一個進程A一個進程組B,各占50%的CPU,對於根的紅黑樹而言,他們就是兩個調度實體。調度器調度的不是進程A就是進程組B,而如果調度到進程組B,進程組B自己選擇一個程序交給CPU運行就可以了,而進程組B怎么選擇一個程序給CPU,就是通過自己的CFS運行隊列的紅黑樹選擇,如果進程組B還有個子進程組C,原理都一樣,就是一個層次結構。

  而在 struct task_struct 結構中,我們注意到有個調度類,里面包含的是調度處理函數,它具體如下:

 1 struct sched_class {
 2     /* 下一優先級的調度類
 3      * 調度類優先級順序: stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
 4      */
 5     const struct sched_class *next;
 6 
 7     /* 將進程加入到運行隊列中,即將調度實體(進程)放入紅黑樹中,並對 nr_running 變量加1 */
 8     void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
 9     /* 從運行隊列中刪除進程,並對 nr_running 變量中減1 */
10     void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
11     /* 放棄CPU,在 compat_yield sysctl 關閉的情況下,該函數實際上執行先出隊后入隊;在這種情況下,它將調度實體放在紅黑樹的最右端 */
12     void (*yield_task) (struct rq *rq);
13     bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt);
14 
15     /* 檢查當前進程是否可被新進程搶占 */
16     void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);
17 
18     /*
19      * It is the responsibility of the pick_next_task() method that will
20      * return the next task to call put_prev_task() on the @prev task or
21      * something equivalent.
22      *
23      * May return RETRY_TASK when it finds a higher prio class has runnable
24      * tasks.
25      */
26     /* 選擇下一個應該要運行的進程運行 */
27     struct task_struct * (*pick_next_task) (struct rq *rq,
28                         struct task_struct *prev);
29     /* 將進程放回運行隊列 */
30     void (*put_prev_task) (struct rq *rq, struct task_struct *p);
31 
32 #ifdef CONFIG_SMP
33     /* 為進程選擇一個合適的CPU */
34     int (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags);
35     /* 遷移任務到另一個CPU */
36     void (*migrate_task_rq)(struct task_struct *p, int next_cpu);
37     /* 用於上下文切換后 */
38     void (*post_schedule) (struct rq *this_rq);
39     /* 用於進程喚醒 */
40     void (*task_waking) (struct task_struct *task);
41     void (*task_woken) (struct rq *this_rq, struct task_struct *task);
42     /* 修改進程的CPU親和力(affinity) */
43     void (*set_cpus_allowed)(struct task_struct *p,
44                  const struct cpumask *newmask);
45     /* 啟動運行隊列 */
46     void (*rq_online)(struct rq *rq);
47     /* 禁止運行隊列 */
48     void (*rq_offline)(struct rq *rq);
49 #endif
50     /* 當進程改變它的調度類或進程組時被調用 */
51     void (*set_curr_task) (struct rq *rq);
52     /* 該函數通常調用自 time tick 函數;它可能引起進程切換。這將驅動運行時(running)搶占 */
53     void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
54     /* 在進程創建時調用,不同調度策略的進程初始化不一樣 */
55     void (*task_fork) (struct task_struct *p);
56     /* 在進程退出時會使用 */
57     void (*task_dead) (struct task_struct *p);
58 
59     /* 用於進程切換 */
60     void (*switched_from) (struct rq *this_rq, struct task_struct *task);
61     void (*switched_to) (struct rq *this_rq, struct task_struct *task);
62     /* 改變優先級 */
63     void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
64              int oldprio);
65 
66     unsigned int (*get_rr_interval) (struct rq *rq,
67                      struct task_struct *task);
68 
69     void (*update_curr) (struct rq *rq);
70 
71 #ifdef CONFIG_FAIR_GROUP_SCHED
72     void (*task_move_group) (struct task_struct *p, int on_rq);
73 #endif
74 };

  這個調度類具體有什么用呢,實際上在內核中不同的調度算法它們的操作都不相同,為了方便修改、替換調度算法,使用了調度類,每個調度算法只需要實現自己的調度類就可以了,CFS算法有它的調度類,SCHED_FIFO也有它自己的調度類,當一個進程創建時,用什么調度算法就將其 task_struct->sched_class 指向其相應的調度類,調度器每次調度處理時,就通過當前進程的調度類函數進程操作,大大提高了可移植性和易修改性。

CFS運行隊列(struct cfs_rq)

  我們現在知道,在系統中至少有一個CFS運行隊列,其就是根CFS運行隊列,而其他的進程組和進程都包含在此運行隊列中,不同的是進程組又有它自己的CFS運行隊列,其運行隊列中包含的是此進程組中的所有進程。當調度器從根CFS運行隊列中選擇了一個進程組進行調度時,進程組會從自己的CFS運行隊列中選擇一個調度實體進行調度(這個調度實體可能為進程,也可能又是一個子進程組),就這樣一直深入,直到最后選出一個進程進行運行為止。
  對於 struct cfs_rq 結構沒有什么好說明的,只要確定其代表着一個CFS運行隊列,並且包含有一個紅黑樹進行選擇調度進程即可。
 1 /* CFS調度的運行隊列,每個CPU的rq會包含一個cfs_rq,而每個組調度的sched_entity也會有自己的一個cfs_rq隊列 */
 2 struct cfs_rq {
 3     /* CFS運行隊列中所有進程的總負載 */
 4     struct load_weight load;
 5     /*
 6      * nr_running: cfs_rq中調度實體數量
 7      * h_nr_running: 只對進程組有效,其下所有進程組中cfs_rq的nr_running之和
 8      */
 9     unsigned int nr_running, h_nr_running;
10 
11     u64 exec_clock;
12     /* 當前CFS隊列上最小運行時間,單調遞增
13      * 兩種情況下更新該值: 
14      * 1、更新當前運行任務的累計運行時間時
15      * 2、當任務從隊列刪除去,如任務睡眠或退出,這時候會查看剩下的任務的vruntime是否大於min_vruntime,如果是則更新該值。
16      */
17     u64 min_vruntime;
18 #ifndef CONFIG_64BIT
19     u64 min_vruntime_copy;
20 #endif
21     /* 該紅黑樹的root */
22     struct rb_root tasks_timeline;
23     /* 下一個調度結點(紅黑樹最左邊結點,最左邊結點就是下個調度實體) */
24     struct rb_node *rb_leftmost;
25 
26     /*
27      * 'curr' points to currently running entity on this cfs_rq.
28      * It is set to NULL otherwise (i.e when none are currently running).
29      */
30     /*
31      * curr: 當前正在運行的sched_entity(對於組雖然它不會在cpu上運行,但是當它的下層有一個task在cpu上運行,那么它所在的cfs_rq就把它當做是該cfs_rq上當前正在運行的sched_entity)
32      * next: 表示有些進程急需運行,即使不遵從CFS調度也必須運行它,調度時會檢查是否next需要調度,有就調度next
33      *
34      * skip: 略過進程(不會選擇skip指定的進程調度)
35      */
36     struct sched_entity *curr, *next, *last, *skip;
37 
38 #ifdef    CONFIG_SCHED_DEBUG
39     unsigned int nr_spread_over;
40 #endif
41 
42 #ifdef CONFIG_SMP
43     /*
44      * CFS Load tracking
45      * Under CFS, load is tracked on a per-entity basis and aggregated up.
46      * This allows for the description of both thread and group usage (in
47      * the FAIR_GROUP_SCHED case).
48      */
49     unsigned long runnable_load_avg, blocked_load_avg;
50     atomic64_t decay_counter;
51     u64 last_decay;
52     atomic_long_t removed_load;
53 
54 #ifdef CONFIG_FAIR_GROUP_SCHED
55     /* Required to track per-cpu representation of a task_group */
56     u32 tg_runnable_contrib;
57     unsigned long tg_load_contrib;
58 
59     /*
60      * h_load = weight * f(tg)
61      *
62      * Where f(tg) is the recursive weight fraction assigned to
63      * this group.
64      */
65     unsigned long h_load;
66     u64 last_h_load_update;
67     struct sched_entity *h_load_next;
68 #endif /* CONFIG_FAIR_GROUP_SCHED */
69 #endif /* CONFIG_SMP */
70 
71 #ifdef CONFIG_FAIR_GROUP_SCHED
72     /* 所屬於的CPU rq */
73     struct rq *rq;    /* cpu runqueue to which this cfs_rq is attached */
74 
75     /*
76      * leaf cfs_rqs are those that hold tasks (lowest schedulable entity in
77      * a hierarchy). Non-leaf lrqs hold other higher schedulable entities
78      * (like users, containers etc.)
79      *
80      * leaf_cfs_rq_list ties together list of leaf cfs_rq's in a cpu. This
81      * list is used during load balance.
82      */
83     int on_list;
84     struct list_head leaf_cfs_rq_list;
85     /* 擁有該CFS運行隊列的進程組 */
86     struct task_group *tg;    /* group that "owns" this runqueue */
87 
88 #ifdef CONFIG_CFS_BANDWIDTH
89     int runtime_enabled;
90     u64 runtime_expires;
91     s64 runtime_remaining;
92 
93     u64 throttled_clock, throttled_clock_task;
94     u64 throttled_clock_task_time;
95     int throttled, throttle_count;
96     struct list_head throttled_list;
97 #endif /* CONFIG_CFS_BANDWIDTH */
98 #endif /* CONFIG_FAIR_GROUP_SCHED */
99 };
  • load:其保存的是進程組中所有進程的權值總和,需要注意子進程計算vruntime時需要用到進程組的load。
 

CPU運行隊列(struct rq)

  每個CPU都有自己的 struct rq 結構,其用於描述在此CPU上所運行的所有進程,其包括一個實時進程隊列和一個根CFS運行隊列,在調度時,調度器首先會先去實時進程隊列找是否有實時進程需要運行,如果沒有才會去CFS運行隊列找是否有進行需要運行,這就是為什么常說的實時進程優先級比普通進程高,不僅僅體現在prio優先級上,還體現在調度器的設計上,至於dl運行隊列,我暫時還不知道有什么用處,其優先級比實時進程還高,但是創建進程時如果創建的是dl進程創建會錯誤(具體見sys_fork)。

  1 /* CPU運行隊列,每個CPU包含一個struct rq */
  2 struct rq {
  3     /* 處於運行隊列中所有就緒進程的load之和 */
  4     raw_spinlock_t lock;
  5 
  6     /*
  7      * nr_running and cpu_load should be in the same cacheline because
  8      * remote CPUs use both these fields when doing load calculation.
  9      */
 10     /* 此CPU上總共就緒的進程數,包括cfs,rt和正在運行的 */
 11     unsigned int nr_running;
 12 #ifdef CONFIG_NUMA_BALANCING
 13     unsigned int nr_numa_running;
 14     unsigned int nr_preferred_running;
 15 #endif
 16     #define CPU_LOAD_IDX_MAX 5
 17     /* 根據CPU歷史情況計算的負載,cpu_load[0]一直等於load.weight,當達到負載平衡時,cpu_load[1]和cpu_load[2]都應該等於load.weight */
 18     unsigned long cpu_load[CPU_LOAD_IDX_MAX];
 19     /* 最后一次更新 cpu_load 的時間 */
 20     unsigned long last_load_update_tick;
 21 #ifdef CONFIG_NO_HZ_COMMON
 22     u64 nohz_stamp;
 23     unsigned long nohz_flags;
 24 #endif
 25 #ifdef CONFIG_NO_HZ_FULL
 26     unsigned long last_sched_tick;
 27 #endif
 28     /* 是否需要更新rq的運行時間 */
 29     int skip_clock_update;
 30 
 31     /* capture load from *all* tasks on this cpu: */
 32     /* CPU負載,該CPU上所有可運行進程的load之和,nr_running更新時這個值也必須更新 */
 33     struct load_weight load;
 34     unsigned long nr_load_updates;
 35     /* 進行上下文切換次數,只有proc會使用這個 */
 36     u64 nr_switches;
 37 
 38     /* cfs調度運行隊列,包含紅黑樹的根 */
 39     struct cfs_rq cfs;
 40     /* 實時調度運行隊列 */
 41     struct rt_rq rt;
 42     struct dl_rq dl;
 43 
 44 #ifdef CONFIG_FAIR_GROUP_SCHED
 45     /* list of leaf cfs_rq on this cpu: */
 46     struct list_head leaf_cfs_rq_list;
 47 
 48     struct sched_avg avg;
 49 #endif /* CONFIG_FAIR_GROUP_SCHED */
 50 
 51     /*
 52      * This is part of a global counter where only the total sum
 53      * over all CPUs matters. A task can increase this counter on
 54      * one CPU and if it got migrated afterwards it may decrease
 55      * it on another CPU. Always updated under the runqueue lock:
 56      */
 57      /* 曾經處於隊列但現在處於TASK_UNINTERRUPTIBLE狀態的進程數量 */
 58     unsigned long nr_uninterruptible;
 59 
 60     /*
 61      * curr: 當前正在此CPU上運行的進程
 62      * idle: 當前CPU上idle進程的指針,idle進程用於當CPU沒事做的時候調用,它什么都不執行
 63      */
 64     struct task_struct *curr, *idle, *stop;
 65     /* 下次進行負載平衡執行時間 */
 66     unsigned long next_balance;
 67     /* 在進程切換時用來存放換出進程的內存描述符地址 */
 68     struct mm_struct *prev_mm;
 69 
 70     /* rq運行時間 */
 71     u64 clock;
 72     u64 clock_task;
 73 
 74     atomic_t nr_iowait;
 75 
 76 #ifdef CONFIG_SMP
 77     struct root_domain *rd;
 78     /* 當前CPU所在基本調度域,每個調度域包含一個或多個CPU組,每個CPU組包含該調度域中一個或多個CPU子集,負載均衡都是在調度域中的組之間完成的,不能跨域進行負載均衡 */
 79     struct sched_domain *sd;
 80 
 81     unsigned long cpu_capacity;
 82 
 83     unsigned char idle_balance;
 84     /* For active balancing */
 85     int post_schedule;
 86     /* 如果需要把進程遷移到其他運行隊列,就需要設置這個位 */
 87     int active_balance;
 88     int push_cpu;
 89     struct cpu_stop_work active_balance_work;
 90 
 91     /* 該運行隊列所屬CPU */
 92     int cpu;
 93     int online;
 94 
 95     struct list_head cfs_tasks;
 96 
 97     u64 rt_avg;
 98     /* 該運行隊列存活時間 */
 99     u64 age_stamp;
100     u64 idle_stamp;
101     u64 avg_idle;
102 
103     /* This is used to determine avg_idle's max value */
104     u64 max_idle_balance_cost;
105 #endif
106 
107 #ifdef CONFIG_IRQ_TIME_ACCOUNTING
108     u64 prev_irq_time;
109 #endif
110 #ifdef CONFIG_PARAVIRT
111     u64 prev_steal_time;
112 #endif
113 #ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING
114     u64 prev_steal_time_rq;
115 #endif
116 
117     /* calc_load related fields */
118     /* 用於負載均衡 */
119     unsigned long calc_load_update;
120     long calc_load_active;
121 
122 #ifdef CONFIG_SCHED_HRTICK
123 #ifdef CONFIG_SMP
124     int hrtick_csd_pending;
125     struct call_single_data hrtick_csd;
126 #endif
127     /* 調度使用的高精度定時器 */
128     struct hrtimer hrtick_timer;
129 #endif
130 
131 #ifdef CONFIG_SCHEDSTATS
132     /* latency stats */
133     struct sched_info rq_sched_info;
134     unsigned long long rq_cpu_time;
135     /* could above be rq->cfs_rq.exec_clock + rq->rt_rq.rt_runtime ? */
136 
137     /* sys_sched_yield() stats */
138     unsigned int yld_count;
139 
140     /* schedule() stats */
141     unsigned int sched_count;
142     unsigned int sched_goidle;
143 
144     /* try_to_wake_up() stats */
145     unsigned int ttwu_count;
146     unsigned int ttwu_local;
147 #endif
148 
149 #ifdef CONFIG_SMP
150     struct llist_head wake_list;
151 #endif
152 
153 #ifdef CONFIG_CPU_IDLE
154     /* Must be inspected within a rcu lock section */
155     struct cpuidle_state *idle_state;
156 #endif
157 };

 

 

 

 

總結

  關鍵的數據結構也介紹得差不多了,之后的章節將會從代碼上為大家詳細講解內核調度器是怎么實現的。
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM