1. 程式人生 > >【原創】(六)Linux程序排程-實時排程器

【原創】(六)Linux程序排程-實時排程器

背景

  • Read the fucking source code! --By 魯迅
  • A picture is worth a thousand words. --By 高爾基

說明:

  1. Kernel版本:4.14
  2. ARM64處理器,Contex-A53,雙核
  3. 使用工具:Source Insight 3.5, Visio

1. 概述

在Linux核心中,實時程序總是比普通程序的優先順序要高,實時程序的排程是由Real Time Scheduler(RT排程器)來管理,而普通程序由CFS排程器來管理。
實時程序支援的排程策略為:SCHED_FIFOSCHED_RR

前邊的系列文章都是針對CFS排程器

來分析的,包括了CPU負載組排程Bandwidth控制等,本文的RT排程器也會從這些角度來分析,如果看過之前的系列文章,那麼這篇文章理解起來就會更容易點了。

前戲不多,直奔主題。

2. 資料結構

有必要把關鍵的結構體羅列一下了:

  • struct rq:執行佇列,每個CPU都對應一個;
  • struct rt_rq:實時執行佇列,用於管理實時任務的排程實體;
  • struct sched_rt_entity:實時排程實體,用於參與排程,功能與struct sched_entity類似;
  • struct task_group:組排程結構體;
  • struct rt_bandwidth:頻寬控制結構體;

老規矩,先上張圖,捋捋這些結構之間的關係吧:

  • 從圖中的結構組織關係看,與CFS排程器基本一致,區別在與CFS排程器是通過紅黑樹來組織排程實體,而RT排程器使用的是優先順序佇列來組織實時排程實體;
  • rt_rq執行佇列,維護了100個優先順序的佇列(連結串列),優先順序0-99,從高到底;
  • 排程器管理的物件是排程實體,任務task_struct和任務組task_group都是通過內嵌排程實體的資料結構,來最終參與排程管理的;
  • task_group任務組排程,自身為每個CPU維護rt_rq,用於存放自己的子任務或者子任務組,子任務組又能往下級聯,因此可以構造成樹;

上述結構體中,struct rqstruct task_group,在前文中都分析過。

下邊針對RT執行佇列相關的關鍵結構體,簡單註釋下吧:

struct sched_rt_entity {
    struct list_head        run_list;   //用於加入到優先順序佇列中
    unsigned long           timeout;    //設定的時間超時
    unsigned long           watchdog_stamp;     //用於記錄jiffies值
    unsigned int            time_slice;     //時間片,100ms,
    unsigned short          on_rq;
    unsigned short          on_list;

    struct sched_rt_entity      *back;  //臨時用於從上往下連線RT排程實體時使用
#ifdef CONFIG_RT_GROUP_SCHED
    struct sched_rt_entity      *parent;    //指向父RT排程實體
    /* rq on which this entity is (to be) queued: */
    struct rt_rq            *rt_rq;     //RT排程實體所屬的實時執行佇列,被排程
    /* rq "owned" by this entity/group: */
    struct rt_rq            *my_q;  //RT排程實體所擁有的實時執行佇列,用於管理子任務或子組任務
#endif
} __randomize_layout;


/* Real-Time classes' related field in a runqueue: */
struct rt_rq {
    struct rt_prio_array active;    //優先順序佇列,100個優先順序的連結串列,並定義了點陣圖,用於快速查詢
    unsigned int rt_nr_running; //在RT執行佇列中所有活動的任務數
    unsigned int rr_nr_running;
#if defined CONFIG_SMP || defined CONFIG_RT_GROUP_SCHED
    struct {
        int curr; /* highest queued rt task prio */     //當前RT任務的最高優先順序
#ifdef CONFIG_SMP
        int next; /* next highest */        //下一個要執行的RT任務的優先順序,如果兩個任務都有最高優先順序,則curr == next
#endif
    } highest_prio;
#endif
#ifdef CONFIG_SMP
    unsigned long rt_nr_migratory;      //任務沒有繫結在某個CPU上時,這個值會增減,用於任務遷移
    unsigned long rt_nr_total;      //用於overload檢查
    int overloaded;     //RT執行佇列過載,則將任務推送到其他CPU
    struct plist_head pushable_tasks;   //優先順序列表,用於推送過載任務
#endif /* CONFIG_SMP */
    int rt_queued;  //表示RT執行佇列已經加入rq佇列

    int rt_throttled;   //用於限流操作
    u64 rt_time;    //累加的執行時,超出了本地rt_runtime時,則進行限制
    u64 rt_runtime;     //分配給本地池的執行時
    /* Nests inside the rq lock: */
    raw_spinlock_t rt_runtime_lock;

#ifdef CONFIG_RT_GROUP_SCHED
    unsigned long rt_nr_boosted;    //用於優先順序翻轉問題解決

    struct rq *rq;      //指向執行佇列
    struct task_group *tg;  //指向任務組
#endif
};

struct rt_bandwidth {
    /* nests inside the rq lock: */
    raw_spinlock_t      rt_runtime_lock;
    ktime_t         rt_period;      //時間週期
    u64         rt_runtime;     //一個時間週期內的執行時間,超過則限流,預設值為0.95ms
    struct hrtimer      rt_period_timer;    //時間週期定時器
    unsigned int        rt_period_active;
};

3. 流程分析

3.1 執行時統計資料

執行時的統計資料更新,是在update_curr_rt函式中完成的:

  • update_curr_rt函式功能,主要包括兩部分:
    1. 執行時間的統計更新處理;
    2. 如果執行時間超出了分配時間,進行時間均衡處理,並且判斷是否需要進行限流,進行了限流則需要將RT隊列出隊,並重新進行排程;

為了更直觀的理解,下邊還是來兩張圖片說明一下:

sched_rt_avg_update更新示意如下:

  • rq->age_stamp:在CPU啟動後執行佇列首次執行時設定起始時間,後續週期性進行更新;
  • rt_avg:累計的RT平均執行時間,每0.5秒減半處理,用於計算CFS負載減去RT在CFS負載平衡中使用的時間百分比;

3.2 組排程

RT排程器CFS排程器的組排程基本類似,CFS排程器的組排程請參考(四)Linux程序排程-組排程及頻寬控制

看一下RT排程器組排程的組織關係圖吧:

  • 系統為每個CPU都分配了RT執行佇列,以及RT排程實體,任務組通過它包含的RT排程實體來參與排程;
  • 任務組task_group的RT佇列,用於存放歸屬於該組的任務或子任務組,從而形成級聯的結構;

看一下實際的組織示意圖:

3.3 頻寬控制

請先參考(四)Linux程序排程-組排程及頻寬控制

RT排程器在頻寬控制中,排程時間週期設定的為1s,執行時間設定為0.95s:

/*
 * period over which we measure -rt task CPU usage in us.
 * default: 1s
 */
unsigned int sysctl_sched_rt_period = 1000000;

/*
 * part of the period that we allow rt tasks to run in us.
 * default: 0.95s
 */
int sysctl_sched_rt_runtime = 950000;

這兩個值可以在使用者態通過/sys/fs/cgroup/cpu/rt_runtime_us/sys/fs/cgroup/cpu/rt_period_us來進行設定。

看看函式呼叫流程:

  • init_rt_bandwidth函式在建立分配RT任務組的時候呼叫,完成的工作是將rt_bandwidth結構體的相關欄位進行初始化:設定好時間週期rt_period和執行時間限制rt_runtime,都設定成預設值;
  • 可以從使用者態通過操作/sys/fs/cgroup/cpu下對應的節點進行設定rt_periodrt_runtime,最終呼叫的函式是tg_set_rt_bandwidth,在該函式中會從下往上的遍歷任務組進行設定時間週期和限制的執行時間;
  • enqueue_rt_entity將RT排程實體入列時,最終觸發start_rt_bandwidth函式執行,當高精度定時器到期時呼叫do_sched_rt_period_timer函式;
  • do_sched_rt_period_timer函式,會去判斷該RT執行佇列的累計執行時間rt_time與設定的限制執行時間rt_runtime之間的大小關係,以確定是否限流的操作。在這個函式中,如果已經進行了限流操作,會呼叫balance_time來在多個CPU之間進行時間均衡處理,簡單點說,就是從其他CPU的rt_rq佇列中勻出一部分時間增加到當前CPU的rt_rq佇列中,也就是將當前rt_rt執行佇列的限制執行時間rt_runtime增加一部分,其他CPU的rt_rq執行佇列限制執行時間減少一部分。

來一張效果示意圖:

3.4 排程器函式分析

來一張前文的圖:

看一下RT排程器例項的程式碼:

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,

#ifdef CONFIG_SMP
    .select_task_rq     = select_task_rq_rt,

    .set_cpus_allowed       = set_cpus_allowed_common,
    .rq_online              = rq_online_rt,
    .rq_offline             = rq_offline_rt,
    .task_woken     = task_woken_rt,
    .switched_from      = switched_from_rt,
#endif

    .set_curr_task          = set_curr_task_rt,
    .task_tick      = task_tick_rt,

    .get_rr_interval    = get_rr_interval_rt,

    .prio_changed       = prio_changed_rt,
    .switched_to        = switched_to_rt,

    .update_curr        = update_curr_rt,
};

3.4.1 pick_next_task_rt

pick_next_task_rt函式是排程器用於選擇下一個執行任務。流程如下:

  • CFS排程器不同,RT排程器會在多個CPU組成的domain中,對任務進行pull/push處理,也就是說,如果當前CPU的執行佇列中任務優先順序都不高,那麼會考慮去其他CPU執行佇列中找一個更高優先順序的任務來執行,以確保按照優先順序處理,此外當前CPU也會把任務推送到其他更低優先順序的CPU執行佇列上。
  • _pick_next_task_rt的處理邏輯比較簡單,如果實時排程實體是task,則直接查詢優先順序佇列的點陣圖中,找到優先順序最高的任務,而如果實時排程實體是task_group,則還需要繼續往下進行遍歷查詢;

關於任務的pull/push,linux提供了struct plist,基於優先順序的雙鏈表,其中任務的組織關係如下圖:

pull_rt_task的大概示意圖如下:

  • 當前CPU上的優先順序任務不高,從另一個CPU的pushable_tasks連結串列中找優先順序更高的任務來執行;

3.4.2 enqueue_task_rt/dequeue_task_rt

當RT任務進行出隊入隊時,通過enqueue_task_rt/dequeue_task_rt兩個介面來完成,呼叫流程如下:

  • enqueue_task_rtdequeue_task_rt都會呼叫dequeue_rt_stack介面,當請求的rt_se對應的是任務組時,會從頂部到請求的rt_se將排程實體出列;
  • 任務新增到rt執行佇列時,如果存在多個任務可以分配給多個CPU,設定overload,用於任務的遷移;

有點累了,收工了。

相關推薦

原創Linux程序排程-實時排程

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,

原創Linux記憶體管理 - zoned page frame allocator - 1

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,

原創Linux程序排程-基礎

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,

原創Linux程序排程-CPU負載

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,

原創Linux程序排程-程序切換

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,

原創Linux程序排程-組排程及頻寬控制

# 背景 - `Read the fucking source code!` --By 魯迅 - `A picture is worth a thousand words.` --By 高爾基 說明: 1. Kernel版本:4.14 2. ARM64處理器,Contex-A53,雙核 3. 使用工具:S

原創Linux程序排程-CFS排程

# 背景 - `Read the fucking source code!` --By 魯迅 - `A picture is worth a thousand words.` --By 高爾基 說明: 1. Kernel版本:4.14 2. ARM64處理器,Contex-A53,雙核 3. 使用工具:S

原創Linux paging_init解析

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,

原創Linux記憶體模型之Sparse Memory Model

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,

原創Linux記憶體管理 - zoned page frame allocator - 2

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,

原創Linux記憶體管理 - zoned page frame allocator - 3

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,

原創Linux記憶體管理 - zoned page frame allocator - 4

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,

原創十三Linux記憶體管理之vma/malloc/mmap

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,

ElasticSearch淺析Scroll

【起因】        正常查某索引下全部資料的dsl舉例如下: POST /fcar_city/city/_search?scroll=10m { "query": { "bool": { "must"

Mybatis動態SQL

在【Mybatis】(一)第一個mybatis例項中已經建立了資料庫和基本的執行環境,接下來將介紹Mybatis動態SQL。 1、定義EmployeeMapperDynamicSQL介面 package com.lhk.mybatis.dao; import c

MybatisMBG程式碼生成器

Mybatis Generator(MBG)通過豐富的配置可以生成不同型別的程式碼,程式碼包含了資料庫表對應的實體類,Mapper介面類,Mapper XML檔案和Example物件等。這些程式碼檔案中

原創Linux記憶體管理之CMA

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,

朝花夕拾Android效能篇之Android程序管理機制

一、Android程序管理的特殊設計        Linux系統對程序的管理方式是一旦程序活動停止,系統就會結束該程序。儘管Android基於Linux Kernel,但在程序管理上,卻採取了另外一種獨特的設計:當程序活動停止時,系統並不會立刻結束它,而是會盡可能地將該程序儲存在記憶體中,在以後的某個時間,

原創十一Linux記憶體管理slub分配器

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,

原創十二Linux記憶體管理之vmap與vmalloc

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,