1. 程式人生 > >Linux程序排程之CFS排程器概述--Linux程序的管理與排程(二十四)

Linux程序排程之CFS排程器概述--Linux程序的管理與排程(二十四)

1 前景回顧

1.1 程序排程

記憶體中儲存了對每個程序的唯一描述, 並通過若干結構與其他程序連線起來.

排程器面對的情形就是這樣, 其任務是在程式之間共享CPU時間, 創造並行執行的錯覺, 該任務分為兩個不同的部分, 其中一個涉及排程策略, 另外一個涉及上下文切換.

1.2 程序的分類

linux把程序區分為實時程序非實時程序, 其中非實時程序進一步劃分為互動式程序和批處理程序

根據程序的不同分類Linux採用不同的排程策略.

對於實時程序,採用FIFO, Round Robin或者Earliest Deadline First (EDF)最早截止期限優先排程演算法|的排程策略.

1.3 linux排程器的演變

欄位 版本
O(n)的始排程演算法 linux-0.11~2.4
O(1)排程器 linux-2.5
CFS排程器 linux-2.6~至今

1.4 Linux的排程器組成

2個排程器

可以用兩種方法來啟用排程

  • 一種是直接的, 比如程序打算睡眠或出於其他原因放棄CPU

  • 另一種是通過週期性的機制, 以固定的頻率執行, 不時的檢測是否有必要

因此當前linux的排程程式由兩個排程器組成:主排程器週期性排程器(兩者又統稱為通用排程器(generic scheduler)

核心排程器(core scheduler))

並且每個排程器包括兩個內容:排程框架(其實質就是兩個函式框架)及排程器類

6種排程策略

linux核心目前實現了6中排程策略(即排程演算法), 用於對不同型別的程序進行排程, 或者支援某些特殊的功能

  • SCHED_NORMAL和SCHED_BATCH排程普通的非實時程序

  • SCHED_FIFO和SCHED_RR和SCHED_DEADLINE則採用不同的排程策略排程實時程序

  • SCHED_IDLE則在系統空閒時呼叫idle程序.

5個排程器類

而依據其排程策略的不同實現了5個排程器類, 一個排程器類可以用一種種或者多種排程策略排程某一類程序, 也可以用於特殊情況或者排程特殊功能的程序.

其所屬程序的優先順序順序為

stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class

3個排程實體

排程器不限於排程程序, 還可以排程更大的實體, 比如實現組排程.

這種一般性要求排程器不直接操作程序, 而是處理可排程實體, 因此需要一個通用的資料結構描述這個排程實體,即seched_entity結構, 其實際上就代表了一個排程物件,可以為一個程序,也可以為一個程序組.

linux中針對當前可排程的實時和非實時程序, 定義了型別為seched_entity的3個排程實體

  • sched_dl_entity 採用EDF演算法排程的實時排程實體

  • sched_rt_entity 採用Roound-Robin或者FIFO演算法排程的實時排程實體 rt_sched_class

  • sched_entity 採用CFS演算法排程的普通非實時程序的排程實體

2 cfs完全公平排程器

2.1 CFS排程器類fair_sched_class

CFS完全公平排程器的排程器類叫fair_sched_class, 其定義在kernel/sched/fair.c, line 8521, 它是我們熟知的是struct sched_class排程器類型別, 將我們的CFS排程器與一些特定的函式關聯起來

/*
 * All the scheduling class methods:
 */
const struct sched_class fair_sched_class = {
        .next                   = &idle_sched_class,  /*  下個優先順序的排程類, 所有的排程類通過next連結在一個連結串列中*/
        .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,

#ifdef CONFIG_SMP
        .select_task_rq         = select_task_rq_fair,
        .migrate_task_rq        = migrate_task_rq_fair,

        .rq_online              = rq_online_fair,
        .rq_offline             = rq_offline_fair,

        .task_waking            = task_waking_fair,
        .task_dead              = task_dead_fair,
        .set_cpus_allowed       = set_cpus_allowed_common,
#endif

        .set_curr_task          = set_curr_task_fair,
        .task_tick              = task_tick_fair,
        .task_fork              = task_fork_fair,

        .prio_changed           = prio_changed_fair,
        .switched_from          = switched_from_fair,
        .switched_to            = switched_to_fair,

        .get_rr_interval        = get_rr_interval_fair,

        .update_curr            = update_curr_fair,

#ifdef CONFIG_FAIR_GROUP_SCHED
        .task_move_group        = task_move_group_fair,
#endif
};
成員 描述
enqueue_task 向就緒佇列中新增一個程序, 某個任務進入可執行狀態時,該函式將得到呼叫。它將排程實體(程序)放入紅黑樹中,並對 nr_running 變數加 1
dequeue_task 將一個程序從就就緒佇列中刪除, 當某個任務退出可執行狀態時呼叫該函式,它將從紅黑樹中去掉對應的排程實體,並從 nr_running 變數中減 1
yield_task 在程序想要資源放棄對處理器的控制權的時, 可使用在sched_yield系統呼叫, 會呼叫核心API yield_task完成此工作. compat_yield sysctl 關閉的情況下,該函式實際上執行先出隊後入隊;在這種情況下,它將排程實體放在紅黑樹的最右端
check_preempt_curr 該函式將檢查當前執行的任務是否被搶佔。在實際搶佔正在執行的任務之前,CFS 排程程式模組將執行公平性測試。這將驅動喚醒式(wakeup)搶佔
pick_next_task 該函式選擇接下來要執行的最合適的程序
put_prev_task 用另一個程序代替當前執行的程序
set_curr_task 當任務修改其排程類或修改其任務組時,將呼叫這個函式
task_tick 在每次啟用週期排程器時, 由週期性排程器呼叫, 該函式通常呼叫自 time tick 函式;它可能引起程序切換。這將驅動執行時(running)搶佔
task_new 核心排程程式為排程模組提供了管理新任務啟動的機會, 用於建立fork系統呼叫和排程器之間的關聯, 每次新程序建立後, 則用new_task通知排程器, CFS 排程模組使用它進行組排程,而用於實時任務的排程模組則不會使用這個函式

2.2 cfs的就緒佇列

就緒佇列是全域性排程器許多操作的起點, 但是程序並不是由就緒佇列直接管理的, 排程管理是各個排程器的職責, 因此在各個就緒佇列中嵌入了特定排程類的子就緒佇列(cfs的頂級排程就佇列 struct cfs_rq, 實時排程類的就緒佇列struct rt_rq和deadline排程類的就緒佇列struct dl_rq

/* 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: 只對程序組有效,其下所有程序組中cfs_rq的nr_running之和
    */
    unsigned int nr_running, h_nr_running;

    u64 exec_clock;

    /*
     * 當前CFS佇列上最小執行時間,單調遞增
     * 兩種情況下更新該值: 
     * 1、更新當前執行任務的累計執行時間時
     * 2、當任務從佇列刪除去,如任務睡眠或退出,這時候會檢視剩下的任務的vruntime是否大於min_vruntime,如果是則更新該值。
     */

    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).
     * curr: 當前正在執行的sched_entity(對於組雖然它不會在cpu上執行,但是當它的下層有一個task在cpu上執行,那麼它所在的cfs_rq就把它當做是該cfs_rq上當前正在執行的sched_entity)
     * next: 表示有些程序急需執行,即使不遵從CFS排程也必須執行它,排程時會檢查是否next需要排程,有就排程next
     *
     * skip: 略過程序(不會選擇skip指定的程序排程)
     */
    struct sched_entity *curr, *next, *last, *skip;

#ifdef  CONFIG_SCHED_DEBUG
    unsigned int nr_spread_over;
#endif

#ifdef CONFIG_SMP
    /*
     * CFS load tracking
     */
    struct sched_avg avg;
    u64 runnable_load_sum;
    unsigned long runnable_load_avg;
#ifdef CONFIG_FAIR_GROUP_SCHED
    unsigned long tg_load_avg_contrib;
#endif
    atomic_long_t removed_load_avg, removed_util_avg;
#ifndef CONFIG_64BIT
    u64 load_last_update_time_copy;
#endif

#ifdef CONFIG_FAIR_GROUP_SCHED
    /*
     *   h_load = weight * f(tg)
     *
     * Where f(tg) is the recursive weight fraction assigned to
     * this group.
     */
    unsigned long h_load;
    u64 last_h_load_update;
    struct sched_entity *h_load_next;
#endif /* CONFIG_FAIR_GROUP_SCHED */
#endif /* CONFIG_SMP */

#ifdef CONFIG_FAIR_GROUP_SCHED
    /* 所屬於的CPU rq */
    struct rq *rq;  /* cpu runqueue to which this cfs_rq is attached */

    /*
     * leaf cfs_rqs are those that hold tasks (lowest schedulable entity in
     * a hierarchy). Non-leaf lrqs hold other higher schedulable entities
     * (like users, containers etc.)
     *
     * leaf_cfs_rq_list ties together list of leaf cfs_rq's in a cpu. This
     * list is used during load balance.
     */
    int on_list;
    struct list_head leaf_cfs_rq_list;
    /* 擁有該CFS執行佇列的程序組 */
    struct task_group *tg;  /* group that "owns" this runqueue */

#ifdef CONFIG_CFS_BANDWIDTH
    int runtime_enabled;
    u64 runtime_expires;
    s64 runtime_remaining;

    u64 throttled_clock, throttled_clock_task;
    u64 throttled_clock_task_time;
    int throttled, throttle_count;
    struct list_head throttled_list;
#endif /* CONFIG_CFS_BANDWIDTH */
#endif /* CONFIG_FAIR_GROUP_SCHED */
};
成員 描述
nr_running 佇列上可執行程序的數目
load 就緒佇列上可執行程序的累計負荷權重
min_vruntime 跟蹤記錄佇列上所有程序的最小虛擬執行時間. 這個值是實現與就緒佇列相關的虛擬時鐘的基礎
tasks_timeline 用於在按時間排序的紅黑樹中管理所有程序
rb_leftmost 總是設定為指向紅黑樹最左邊的節點, 即需要被排程的程序. 該值其實可以可以通過病例紅黑樹獲得, 但是將這個值儲存下來可以減少搜尋紅黑樹花費的平均時間
curr 當前正在執行的sched_entity(對於組雖然它不會在cpu上執行,但是當它的下層有一個task在cpu上執行,那麼它所在的cfs_rq就把它當做是該cfs_rq上當前正在執行的sched_entity
next 表示有些程序急需執行,即使不遵從CFS排程也必須執行它,排程時會檢查是否next需要排程,有就排程next
skip 略過程序(不會選擇skip指定的程序排程)

3 參考