1. 程式人生 > >linux排程器原始碼研究

linux排程器原始碼研究

    在組排程中,也涉及到排程實體這個概念,它的結構為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.     u64            exec_start;
  13.     /* 總執行時間 */
  14.     u64            sum_exec_runtime;
  15.     /* 虛擬執行時間,在時間中斷或者任務狀態發生改變時會更新
  16.      * 其會不停增長,增長速度與load權重成反比,load越高,增長速度越慢,就越可能處於紅黑樹最左邊被排程
  17.      * 每次時鐘中斷都會修改其值
  18.      * 具體見calc_delta_fair()函式
  19.      */
  20.     u64            vruntime;
  21.     /* 程序在切換進CPU時的sum_exec_runtime值 */
  22.     u64            prev_sum_exec_runtime;
  23.     /* 此排程實體中程序移到其他CPU組的數量 */
  24.     u64            nr_migrations;
  25. #ifdef CONFIG_SCHEDSTATS
  26.     /* 用於統計一些資料 */
  27.     struct sched_statistics statistics;

  28. #endif
  29. #ifdef CONFIG_FAIR_GROUP_SCHED
  30.     /* 代表此程序組的深度,每個程序組都比其parent排程組深度大1 */
  31.     int            depth;
  32.     /* 父親排程實體指標,如果是程序則指向其執行佇列的排程實體,如果是程序組則指向其上一個程序組的排程實體
  33.      * 在 set_task_rq 函式中設定
  34.      */
  35.     struct sched_entity    *parent;
  36.     /* 實體所處紅黑樹執行佇列 */
  37.     struct cfs_rq        *cfs_rq;        
  38.     /* 實體的紅黑樹執行佇列,如果為NULL表明其是一個程序,若非NULL表明其是排程組 */
  39.     struct cfs_rq        *my_q;
  40. #endif
  41. #ifdef CONFIG_SMP
  42.     /* Per-entity load-tracking */
  43.     struct sched_avg    avg;
  44. #endif
  45. };
    實際上,紅黑樹是根據 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.      * prio: 動態優先順序,範圍為100~139,與靜態優先順序和補償(bonus)有關
  7.      * static_prio: 靜態優先順序,static_prio = 100 + nice + 20 (nice值為-20~19,所以static_prio值為100~139)
  8.      * normal_prio: 沒有受優先順序繼承影響的常規優先順序,具體見normal_prio函式,跟屬於什麼型別的程序有關
  9.      */
  10.     int prio, static_prio, normal_prio;
  11.     /* 實時程序優先順序 */
  12.     unsigned int rt_priority;
  13.     /* 排程類,排程處理函式類 */
  14.     const struct sched_class *sched_class;
  15.     /* 排程實體(紅黑樹的一個結點) */
  16.     struct sched_entity se;
  17.     /* 排程實體(實時排程使用) */
  18.     struct sched_rt_entity rt;
  19. #ifdef CONFIG_CGROUP_SCHED
  20.     /* 指向其所在程序組 */
  21.     struct task_group *sched_task_group;
  22. #endif
  23.     ........
  24. }
    在 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.     /* 將程序加入到執行佇列中,即將排程實體(程序)放入紅黑樹中,並對 nr_running 變數加1 */
  7.     void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
  8.     /* 從執行佇列中刪除程序,並對 nr_running 變數中減1 */
  9.     void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
  10.     /* 放棄CPU,在 compat_yield sysctl 關閉的情況下,該函式實際上執行先出隊後入隊;在這種情況下,它將排程實體放在紅黑樹的最右端 */
  11.     void (*yield_task) (struct rq *rq);
  12.     bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt);
  13.     /* 檢查當前程序是否可被新程序搶佔 */
  14.     void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);
  15.     /*
  16.      * It is the responsibility of the pick_next_task() method that will
  17.      * return the next task to call put_prev_task() on the @prev task or
  18.      * something equivalent.
  19.      *
  20.      * May return RETRY_TASK when it finds a higher prio class has runnable
  21.      * tasks.
  22.      */
  23.     /* 選擇下一個應該要執行的程序執行 */
  24.     struct task_struct * (*pick_next_task) (struct rq *rq,
  25.                         struct task_struct *prev);
  26.     /* 將程序放回執行佇列 */
  27.     void (*put_prev_task) (struct rq *rq, struct task_struct *p);
  28. #ifdef CONFIG_SMP
  29.     /* 為程序選擇一個合適的CPU */
  30.     int (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags);
  31.     /* 遷移任務到另一個CPU */
  32.     void (*migrate_task_rq)(struct task_struct *p, int next_cpu);
  33.     /* 用於上下文切換後 */
  34.     void (*post_schedule) (struct rq *this_rq);
  35.     /* 用於程序喚醒 */
  36.     void (*task_waking) (struct task_struct *task);
  37.     void (*task_woken) (struct rq *this_rq, struct task_struct *task);
  38.     /* 修改程序的CPU親和力(affinity) */
  39.     void (*set_cpus_allowed)(struct task_struct *p,
  40.                  const struct cpumask *newmask);
  41.     /* 啟動執行佇列 */
  42.     void (*rq_online)(struct rq *rq);
  43.     /* 禁止執行佇列 */
  44.     void (*rq_offline)(struct rq *rq);
  45. #endif
  46.     /* 當程序改變它的排程類或程序組時被呼叫 */
  47.     void (*set_curr_task) (struct rq *rq);
  48.     /* 該函式通常呼叫自 time tick 函式;它可能引起程序切換。這將驅動執行時(running)搶佔 */
  49.     void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
  50.     /* 在程序建立時呼叫,不同調度策略的程序初始化不一樣 */
  51.     void (*task_fork) (struct task_struct *p);
  52.     /* 在程序退出時會使用 */
  53.     void (*task_dead) (struct task_struct *p);
  54.     /* 用於程序切換 */
  55.     void (*switched_from) (struct rq *this_rq, struct task_struct *task);
  56.     void (*switched_to) (struct rq *this_rq, struct task_struct *task);
  57.     /* 改變優先順序 */
  58.     void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
  59.              int oldprio);
  60.     unsigned int (*get_rr_interval) (struct rq *rq,
  61.                      struct task_struct *task);
  62.     void (*update_curr) (struct rq *rq);
  63. #ifdef CONFIG_FAIR_GROUP_SCHED
  64.     void (*task_move_group) (struct task_struct *p, int on_rq);
  65. #endif
  66. };
    這個排程類具體有什麼用呢,實際上在核心中不同的排程演算法它們的操作都不相同,為了方便修改、替換排程演算法,使用了排程類,每個排程演算法只需要實現自己的排程類就可以了,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.     u64 exec_clock;
  11.     /* 當前CFS佇列上最小執行時間,單調遞增
  12.      * 兩種情況下更新該值: 
  13.      * 1、更新當前執行任務的累計執行時間時
  14.      * 2、當任務從佇列刪除去,如任務睡眠或退出,這時候會檢視剩下的任務的vruntime是否大於min_vruntime,如果是則更新該值。
  15.      */
  16.     u64 min_vruntime;
  17. #ifndef CONFIG_64BIT
  18.     u64 min_vruntime_copy;
  19. #endif
  20.     /* 該紅黑樹的root */
  21.     struct rb_root tasks_timeline;
  22.     /* 下一個排程結點(紅黑樹最左邊結點,最左邊結點就是下個排程實體) */
  23.     struct rb_node *rb_leftmost;
  24.     /*
  25.      * 'curr' points to currently running entity on this cfs_rq.
  26.      * It is set to NULL otherwise (i.e when none are currently running).
  27.      */
  28.     /*
  29.      * curr: 當前正在執行的sched_entity(對於組雖然它不會在cpu上執行,但是當它的下層有一個task在cpu上執行,那麼它所在的cfs_rq就把它當做是該cfs_rq上當前正在執行的sched_entity)
  30.      * next: 表示有些程序急需執行,即使不遵從CFS排程也必須執行它,排程時會檢查是否next需要排程,有就排程next
  31.      *
  32.      * skip: 略過程序(不會選擇skip指定的程序排程)
  33.      */
  34.     struct sched_entity *curr, *next, *last, *skip;
  35. #ifdef    CONFIG_SCHED_DEBUG
  36.     unsigned int nr_spread_over;
  37. #endif
  38. #ifdef CONFIG_SMP
  39.     /*
  40.      * CFS Load tracking
  41.      * Under CFS, load is tracked on a per-entity basis and aggregated up.
  42.      * This allows for the description of both thread and group usage (in
  43.      * the FAIR_GROUP_SCHED case).
  44.      */
  45.     unsigned long runnable_load_avg, blocked_load_avg;
  46.     atomic64_t decay_counter;
  47.     u64 last_decay;
  48.     atomic_long_t removed_load;
  49. #ifdef CONFIG_FAIR_GROUP_SCHED
  50.     /* Required to track per-cpu representation of a task_group */
  51.     u32 tg_runnable_contrib;
  52.     unsigned long tg_load_contrib;
  53.     /*
  54.      * h_load = weight * f(tg)
  55.      *
  56.      * Where f(tg) is the recursive weight fraction assigned to
  57.      * this group.
  58.      */
  59.     unsigned long h_load;
  60.     u64 last_h_load_update;
  61.     struct sched_entity *h_load_next;
  62. #endif /* CONFIG_FAIR_GROUP_SCHED */
  63. #endif /* CONFIG_SMP */
  64. #ifdef CONFIG_FAIR_GROUP_SCHED
  65.     /* 所屬於的CPU rq */
  66.     struct rq *rq;    /* cpu runqueue to which this cfs_rq is attached */
  67.     /*
  68.      * leaf cfs_rqs are those that hold tasks (lowest schedulable entity in
  69.      * a hierarchy). Non-leaf lrqs hold other higher schedulable entities
  70.      * (like users, containers etc.)
  71.      *
  72.      * leaf_cfs_rq_list ties together list of leaf cfs_rq'in a cpu. This
  73.      * list is used during load balance.
  74.      */
  75.     int on_list;
  76.     struct list_head leaf_cfs_rq_list;
  77.     /* 擁有該CFS執行佇列的程序組 */
  78.     struct task_group *tg;    /* group that "owns" this runqueue */
  79. #ifdef CONFIG_CFS_BANDWIDTH
  80.     int runtime_enabled;
  81.     u64 runtime_expires;
  82.     s64 runtime_remaining;
  83.     u64 throttled_clock, throttled_clock_task;
  84.     u64 throttled_clock_task_time;
  85.     int throttled, throttle_count;
  86.     struct list_head throttled_list;
  87. #endif /* CONFIG_CFS_BANDWIDTH */
  88. #endif /* CONFIG_FAIR_GROUP_SCHED */
  89. };
  • load:其儲存的是程序組中所有程序的權值總和,需要注意子程序計算vruntime時需要用到程序組的load。

CPU執行佇列(struct rq)

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

相關推薦

linux排程原始碼研究

    在組排程中,也涉及到排程實體這個概念,它的結構為struct sched_entity(簡稱se),就是圖1 紅黑樹中的se。其實際上就代表了一個排程物件,可以為一個程序,也可以為一個程序組。對於根的紅黑樹而言,一個程序組就相當於一個排程實體,一個程序也相當於一個排程實體。我們可以先看看其結構,如下

linux排程原始碼分析

引言   排程器作為作業系統的核心部件,具有非常重要的意義,其隨著linux核心的更新也不斷進行著更新。本系列文章通過linux-3.18.3原始碼進行排程器的學習和分析,一步一步將linux現有的排程器原原本本的展現出來。此篇文章作為開篇,主要介紹排程器的原理及重要資料結構。 排程器介紹   

linux排程/proc資訊解讀

轉載自 https://blog.csdn.net/wudongxu/article/details/8574755   注下面的時間或時刻都是從rq->clock中獲得的,而這個值是由update_rq_clock底層cpu來更新的。並且很多資訊是需要核心配置CO

linux排程_第三代cfs(3)_分解程式碼vruntime的詳解

前面講了排隊的關鍵值,是靠這兩個引數確定的se->vruntime - cfs_rq->min_vruntime,後一個引數是為了建立一個標準,像一本線二本線那樣的概念,所以今天我們來重點討論第一個引數的情況。 vruntime的詳解: 看到虛擬執行時間,肯定會

linux排程(六)——應用層理解CFS及組排程

         上面我們介紹了CFS及組排程相關的主要內容,但可能很多人還跟我一樣有點雲裡霧裡的,下面我們直接從應用層面上也檢視CFS及組排程的效果。首先對於非組排程,決定它們執行時間的唯一因素就是weight,也就是我們知道的nice,我們可以通過renice來重新調整

MapReduce多使用者任務排程——容量排程(Capacity Scheduler)原理和原始碼研究

前言:為了研究需要,將Capacity Scheduler和Fair Scheduler的原理和程式碼進行學習,用兩篇文章作為記錄。如有理解錯誤之處,歡迎批評指正。 容量排程器(Capacity Scheduler)是Yahoo公司開發的多使用者排程器。多使用者排程器的使用

Linux高效能網路:協程系列08-協程實現之排程

目錄 Linux高效能網路:協程系列01-前言 Linux高效能網路:協程系列02-協程的起源 Linux高效能網路:協程系列03-協程的案例 Linux高效能網路:協程系列04-協程實現之工作原理 Linux高效能網路:協程系列05-協程實現之原語操作 Linux高效能網路:協程

Linux CFS排程之負荷權重load_weight--Linux程序的管理與排程(二十五)

1. 負荷權重 1.1 負荷權重結構struct load_weight 負荷權重用struct load_weight資料結構來表示, 儲存著程序權重值weight。其定義在/include/linux/sched.h, v=4.6, L1195, 如下所示 struct load_weight {

Java定時任務Timer排程【一】 原始碼分析(圖文詳解版)

就以鬧鐘的例子開頭吧(後續小節皆以鬧鐘為例,所有原始碼只列關鍵部分)。 public class ScheduleDemo { public static void main(String[] args) throws InterruptedException {

Java定時任務Timer排程【二】 多執行緒原始碼分析(圖文版)

  上一節通過一個小例子分析了Timer執行過程,牽涉的執行執行緒雖然只有兩個,但實際場景會比上面複雜一些。 首先通過一張簡單類圖(只列出簡單的依賴關係)看一下Timer暴露的介面。   為了演示Timer所暴露的介面,下面舉一個極端的例子(每一個介面方法面

排程簡介,以及Linux排程策略

程序是作業系統虛擬出來的概念,用來組織計算機中的任務。但隨著程序被賦予越來越多的任務,程序好像有了真實的生命,它從誕生就隨著CPU時間執行,直到最終消失。不過,程序的生命都得到了作業系統核心的關照。就好像疲於照顧幾個孩子的母親核心必須做出決定,如何在程序間分配有限的計算資源,最終讓使用者獲得最佳的使用體驗。核

jQuery原始碼研究:選擇模組所用方法(1)

Sizzle模組第二篇。 順序看下去,看模組所用的方法。 2、createCache()方法,建立一個受限的鍵值對型別快取。返回的是一個函式, function createCache(){ var keys = []; function cache( key

jQuery原始碼研究:選擇

jQuery的css選擇器,是一大亮點,其實現原始碼也可單獨拎出來作為模組使用。 先看個整體,在jQuery原始碼中在行229-2752區域。 var Sizzle = (function(window){ // 具體實現暫略... })(window) css選擇

系統技術非業餘研究 » Erlang R14B 新增加引數 +swt 控制排程的喚醒閥值

來自github上的otp原始碼提交訊息: Erlang R14B 新增加引數 +swt very_low|low|medium|high|very_high Set scheduler wakeup threshold. Default is medium The threshold dete

Linux程序排程的設計--Linux程序的管理與排程(十七)

1 前景回顧 1.1 程序排程 記憶體中儲存了對每個程序的唯一描述, 並通過若干結構與其他程序連線起來. 排程器面對的情形就是這樣, 其任務是在程式之間共享CPU時間, 創造並行執行的錯覺, 該任務分為兩個不同的部分, 其中一個

小米開原始檔管理MiCodeFileExplorer-原始碼研究(9)-入口分析

AndroidManifest.xml是Android應用程式最重要的配置檔案。入口檔案和intent-filter<application android:icon="@drawable/icon" android:label="@st

分散式任務排程平臺XXL-JOB--原始碼解析六:executor執行原始碼解析之啟動jetty服務接受請求並執行排程

啟動jetty服務並執行排程 1.1 啟動一個jetty server服務     首先載入ServerConnector, 載入HandlerCollection並設定handlers, 其中connector負責接受客戶端http請求, 而handlers負責處理客戶

Dubbo原始碼解析(一)請求排程 Dispatcher

排程器 Dispatcher 排程策略 all 所有訊息都派發到執行緒池,包括請求,響應,連線事件,斷開事件,心跳等。 direct 所有訊息都不派發到執行緒池,全部在 IO 執行緒上直接執行。 message 只有請求響應訊息派發到執行緒池,其它連線斷開

linux rt排程

RT(RealTime scheduler)實時排程器,對應使用者設定的排程策略為 SCHED_FIFO/SCHED_RR。 SCHED_FIFO 先進先出佇列排程,高優先順序任務一直執行,直到任務阻塞,或者主動退出,或者被更高優先順序 任務搶佔。 SCHED_RR 時間片

Linux CFS排程之虛擬時鐘vruntime與排程延遲--Linux程序的管理與排程(二十六)

CFS負責處理普通非實時程序, 這類程序是我們linux中最普遍的程序, 今天我們把注意力轉向CFS的虛擬時鐘 1 前景回顧 1.1 CFS排程器類 Linux核心使用CFS是來排程我們最常見的普通程序, 其所屬排程器類為fai