截止期任務排程

0. 警告

修改這些設定可能會導致系統行為不可預測甚至不穩定。對於 -rt(組)排程,假設根使用者知道他們在做什麼。

1. 概述

sched_dl 排程類中包含的 SCHED_DEADLINE 策略基本上是 Earliest Deadline First (EDF) 排程演算法的實現,並透過一種機制(稱為 Constant Bandwidth Server, CBS)增強,使得任務之間行為的隔離成為可能。

2. 排程演算法

2.1 主要演算法

SCHED_DEADLINE [18] 使用三個引數,分別名為“執行時 (runtime)”、“週期 (period)”和“截止期 (deadline)”,來排程任務。一個 SCHED_DEADLINE 任務應在每個“週期 (period)”微秒內獲得“執行時 (runtime)”微秒的執行時間,並且這些“執行時 (runtime)”微秒在週期開始後的“截止期 (deadline)”微秒內可用。為了實現這一行為,每當任務喚醒時,排程器都會計算一個與保證一致的“排程截止期 (scheduling deadline)”(使用 CBS[2,3] 演算法)。然後使用 EDF[1] 基於這些排程截止期來排程任務(選擇排程截止期最早的任務執行)。請注意,如果使用適當的“准入控制”策略(參見“4. 頻寬管理”一節),任務實際上會在“截止期 (deadline)”內獲得“執行時 (runtime)”單位的時間(顯然,如果系統過載,此保證無法得到遵守)。

總而言之,CBS[2,3] 演算法為任務分配排程截止期,使得每個任務在每個週期內最多執行其執行時,避免不同任務之間的任何干擾(頻寬隔離),而 EDF[1] 演算法選擇排程截止期最早的任務作為下一個執行的任務。由於此功能,不嚴格遵守“傳統”即時任務模型(參見第 3 節)的任務可以有效地使用新策略。

更詳細地說,CBS 演算法透過以下方式為任務分配排程截止期:

  • 每個 SCHED_DEADLINE 任務都由“執行時 (runtime)”、“截止期 (deadline)”和“週期 (period)”引數來表徵;

  • 任務的狀態由“排程截止期 (scheduling deadline)”和“剩餘執行時 (remaining runtime)”描述。這兩個引數最初都設定為 0;

  • 當 SCHED_DEADLINE 任務喚醒(準備好執行)時,排程器會檢查是否

             remaining runtime                  runtime
    ----------------------------------    >    ---------
    scheduling deadline - current time           period
    

    然後,如果排程截止期小於當前時間,或者此條件得到驗證,則排程截止期和剩餘執行時將重新初始化為

    排程截止期 = 當前時間 + 截止期 剩餘執行時 = 執行時

    否則,排程截止期和剩餘執行時保持不變;

  • 當 SCHED_DEADLINE 任務執行時間 t 時,其剩餘執行時減少為

    remaining runtime = remaining runtime - t
    

    (技術上,執行時在每個時鐘週期或任務被取消排程/搶佔時減少);

  • 當剩餘執行時小於或等於 0 時,任務被稱為“受限 (throttled)”(在即時文獻中也稱為“耗盡 (depleted)”),並且在其排程截止期之前無法被排程。此任務的“補充時間 (replenishment time)”(參見下一項)設定為等於排程截止期的當前值;

  • 噹噹前時間等於受限任務的補充時間時,排程截止期和剩餘執行時將更新為

    scheduling deadline = scheduling deadline + period
    remaining runtime = remaining runtime + runtime
    

sched_attr 的 sched_flags 欄位中的 SCHED_FLAG_DL_OVERRUN 標誌允許任務透過傳送 SIGXCPU 訊號來了解執行時超限。

2.2 頻寬回收

截止期任務的頻寬回收基於 GRUB (Greedy Reclamation of Unused Bandwidth) 演算法 [15, 16, 17],並在設定 SCHED_FLAG_RECLAIM 標誌時啟用。

下圖說明了 GRUB 處理的任務的狀態名稱

                    ------------
        (d)        |   Active   |
     ------------->|            |
     |             | Contending |
     |              ------------
     |                A      |
 ----------           |      |
|          |          |      |
| Inactive |          |(b)   | (a)
|          |          |      |
 ----------           |      |
     A                |      V
     |              ------------
     |             |   Active   |
     --------------|     Non    |
        (c)        | Contending |
                    ------------

一個任務可以處於以下狀態之一:

  • ActiveContending:如果它已準備好執行(或正在執行);

  • ActiveNonContending:如果它剛剛阻塞且尚未超過 0-lag 時間;

  • Inactive:如果它被阻塞且已超過 0-lag 時間。

狀態轉換

  1. 當一個任務阻塞時,它不會立即變為非活動狀態,因為其頻寬無法立即回收而不會破壞即時保證。因此,它進入一個稱為 ActiveNonContending 的過渡狀態。排程器啟動“非活動定時器”,使其在 0-lag 時間觸發,此時可以在不破壞即時保證的情況下回收任務的頻寬。

    進入 ActiveNonContending 狀態的任務的 0-lag 時間計算為

               (runtime * dl_period)
    deadline - ---------------------
                    dl_runtime
    

    其中 runtime 是剩餘執行時,而 dl_runtime 和 dl_period 是保留引數。

  2. 如果任務在非活動定時器觸發之前喚醒,則任務重新進入 ActiveContending 狀態,並且“非活動定時器”被取消。此外,如果任務在不同的執行佇列上喚醒,則任務的利用率必須從之前的執行佇列的活動利用率中移除,並必須新增到新的執行佇列的活動利用率中。為了避免任務在某個執行佇列上喚醒而“非活動定時器”在不同 CPU 上執行時出現競爭,使用“dl_non_contending”標誌來指示任務不在執行佇列上但處於活動狀態(因此,當任務阻塞時設定該標誌,當“非活動定時器”觸發或任務喚醒時清除該標誌)。

  3. 當“非活動定時器”觸發時,任務進入 Inactive 狀態,其利用率將從執行佇列的活動利用率中移除。

  4. 當非活動任務喚醒時,它進入 ActiveContending 狀態,其利用率將新增到它所入隊的執行佇列的活動利用率中。

對於每個執行佇列,GRUB 演算法跟蹤兩種不同的頻寬:

  • 活動頻寬 (running_bw):這是所有處於活動狀態(即 ActiveContending 或 ActiveNonContending)任務的頻寬總和;

  • 總頻寬 (this_bw):這是所有“屬於”該執行佇列的任務的總和,包括處於 Inactive 狀態的任務。

  • 最大可用頻寬 (max_bw):這是截止期任務可用的最大頻寬,目前設定為 RT 容量。

該演算法回收處於 Inactive 狀態的任務的頻寬。它透過以等於以下速度遞減執行任務 Ti 的執行時來做到這一點:

dq = -(max{ Ui, (Umax - Uinact - Uextra) } / Umax) dt

其中

  • Ui 是任務 Ti 的頻寬;

  • Umax 是最大可回收利用率(受 RT 節流限制);

  • Uinact 是(每個執行佇列的)非活動利用率,計算為 (this_bq - running_bw);

  • Uextra 是(每個執行佇列的)額外可回收利用率(受 RT 節流限制)。

現在來看一個簡單的例子,兩個截止期任務的執行時為 4,週期為 8(即頻寬為 0.5)

       A            Task T1
       |
       |                               |
       |                               |
       |--------                       |----
       |       |                       V
       |---|---|---|---|---|---|---|---|--------->t
       0   1   2   3   4   5   6   7   8


       A            Task T2
       |
       |                               |
       |                               |
       |       ------------------------|
       |       |                       V
       |---|---|---|---|---|---|---|---|--------->t
       0   1   2   3   4   5   6   7   8


       A            running_bw
       |
     1 -----------------               ------
       |               |               |
    0.5-               -----------------
       |                               |
       |---|---|---|---|---|---|---|---|--------->t
       0   1   2   3   4   5   6   7   8


- Time t = 0:

  Both tasks are ready for execution and therefore in ActiveContending state.
  Suppose Task T1 is the first task to start execution.
  Since there are no inactive tasks, its runtime is decreased as dq = -1 dt.

- Time t = 2:

  Suppose that task T1 blocks
  Task T1 therefore enters the ActiveNonContending state. Since its remaining
  runtime is equal to 2, its 0-lag time is equal to t = 4.
  Task T2 start execution, with runtime still decreased as dq = -1 dt since
  there are no inactive tasks.

- Time t = 4:

  This is the 0-lag time for Task T1. Since it didn't woken up in the
  meantime, it enters the Inactive state. Its bandwidth is removed from
  running_bw.
  Task T2 continues its execution. However, its runtime is now decreased as
  dq = - 0.5 dt because Uinact = 0.5.
  Task T2 therefore reclaims the bandwidth unused by Task T1.

- Time t = 8:

  Task T1 wakes up. It enters the ActiveContending state again, and the
  running_bw is incremented.

2.3 節能排程

當選擇 cpufreq 的 schedutil 調速器時,SCHED_DEADLINE 實現 GRUB-PA [19] 演算法,將 CPU 執行頻率降低到仍能滿足截止期的最小值。此行為目前僅針對 ARM 架構實現。

如果改變頻率所需的時間與保留週期處於同一數量級,則必須特別小心。在這種情況下,設定固定的 CPU 頻率會導致更少的截止期錯失。

3. 即時任務排程

警告

本節包含關於經典截止期排程理論的(不詳盡的)摘要,以及它如何應用於 SCHED_DEADLINE。如果讀者只對瞭解如何使用排程策略感興趣,可以“安全地”跳到第 4 節。無論如何,我們強烈建議您回到這裡繼續閱讀(一旦測試的衝動得到滿足 :P),以確保完全理解所有技術細節。

對哪種任務可以利用這種新的排程機制沒有限制,儘管必須說它特別適合需要其時間行為保證的週期性或偶發性即時任務,例如多媒體、流媒體、控制應用程式等。

3.1 定義

典型的即時任務由計算階段(任務例項或作業)的重複組成,這些階段以週期性或偶發性方式啟用。每個作業 J_j(其中 J_j 是任務的第 j 個作業)的特徵是到達時間 r_j(作業開始的時間)、完成作業所需的計算時間 c_j,以及作業的絕對截止期 d_j,這是作業應完成的時間。最大執行時間 max{c_j} 稱為任務的“最壞情況執行時間”(WCET)。即時任務可以是週期性的,如果 r_{j+1} = r_j + P,則週期為 P;或者是偶發性的,如果 r_{j+1} >= r_j + P,則最小到達間隔時間為 P。最後,d_j = r_j + D,其中 D 是任務的相對截止期。總而言之,即時任務可以描述為

任務 = (WCET, D, P)

即時任務的利用率定義為其 WCET 與其週期(或最小到達間隔時間)之間的比率,表示執行任務所需的 CPU 時間的比例。

如果總利用率 U=sum(WCET_i/P_i) 大於 M(M 等於 CPU 數量),則排程器無法滿足所有截止期。請注意,總利用率定義為系統中所有即時任務的利用率 WCET_i/P_i 之和。當考慮多個即時任務時,第 i 個任務的引數用“_i”字尾表示。此外,如果總利用率大於 M,則我們面臨即時任務餓死非即時任務的風險。相反,如果總利用率小於 M,則非即時任務將不會被餓死,並且系統可能能夠滿足所有截止期。事實上,在這種情況下,可以為遲滯提供一個上限(定義為 0 和作業完成時間與其絕對截止期之間差值的最大值)。更精確地說,可以證明,使用全域性 EDF 排程器,每個任務的最大遲滯小於或等於

((M − 1) · WCET_max − WCET_min)/(M − (M − 2) · U_max) + WCET_max

其中 WCET_max = max{WCET_i} 是最大 WCET,WCET_min=min{WCET_i} 是最小 WCET,U_max = max{WCET_i/P_i} 是最大利用率[12]。

3.2 單處理器系統可排程性分析

如果 M=1(單處理器系統),或在分割槽排程的情況下(每個即時任務靜態分配給一個且只有一個 CPU),則可以形式化檢查是否所有截止期都得到遵守。如果所有任務的 D_i = P_i,則當且僅當在 CPU 上執行的任務的總利用率小於或等於 1 時,EDF 才能遵守在 CPU 上執行的所有任務的所有截止期。如果某些任務的 D_i != P_i,則可以將任務的密度定義為 WCET_i/min{D_i,P_i},並且如果 CPU 上執行的任務的密度之和小於或等於 1,則 EDF 能夠遵守在 CPU 上執行的所有任務的所有截止期

sum(WCET_i / min{D_i, P_i}) <= 1

需要注意的是,這個條件只是充分條件,而不是必要條件:存在可排程的任務集,但不符合該條件。例如,考慮由 Task_1=(50ms,50ms,100ms) 和 Task_2=(10ms,100ms,100ms) 組成的任務集 {Task_1,Task_2}。EDF 顯然能夠排程這兩個任務而不會錯過任何截止期(Task_1 一旦釋放就立即排程,並及時完成以滿足其截止期;Task_2 在 Task_1 之後立即排程,因此其響應時間不會大於 50ms + 10ms = 60ms),即使

50 / min{50,100} + 10 / min{100, 100} = 50 / 50 + 10 / 100 = 1.1

當然,可以測試 D_i != P_i 任務的精確可排程性(檢查一個既充分又必要的條件),但這不能透過將總利用率或密度與一個常數進行比較來完成。相反,可以使用所謂的“處理器需求”方法,計算所有任務在大小為 t 的時間間隔內滿足所有截止期所需的 CPU 總時間 h(t),並將此時間與間隔大小 t 進行比較。如果 h(t) 對於 t 的所有可能值都小於 t(即任務在大小為 t 的時間間隔內所需的時間小於該間隔的大小),則 EDF 能夠排程任務並滿足所有截止期。由於對 t 的所有可能值執行此檢查是不可能的,因此已證明[4,5,6]僅需對 0 到最大值 L 之間的 t 值執行測試。引用的論文包含所有數學細節,並解釋瞭如何計算 h(t) 和 L。無論如何,這種分析過於複雜且耗時,無法線上執行。因此,如第 4 節所述,Linux 使用基於任務利用率的准入測試。

3.3 多處理器系統可排程性分析

在具有全域性 EDF 排程(非分割槽系統)的多處理器系統上,可排程性的充分測試不能基於利用率或密度:可以證明,即使 D_i = P_i 任務集,其利用率略大於 1,無論 CPU 數量如何,都可能錯過截止期。

考慮一個在 M 個 CPU 系統上的 M+1 個任務的集合 {Task_1,...Task_{M+1}},其中第一個任務 Task_1=(P,P,P) 的週期、相對截止期和 WCET 等於 P。其餘 M 個任務 Task_i=(e,P-1,P-1) 具有任意小的最壞情況執行時間(此處表示為“e”)和小於第一個任務的週期。因此,如果所有任務同時啟用,全域性 EDF 會首先排程這 M 個任務(因為它們的絕對截止期等於 t + P - 1,因此它們小於 Task_1 的絕對截止期,即 t + P)。結果是,Task_1 只能在時間 t + e 排程,並將在其絕對截止期之後的時間 t + e + P 完成。任務集的總利用率為 U = M · e / (P - 1) + P / P = M · e / (P - 1) + 1,對於較小的 e 值,這可能非常接近 1。這被稱為“Dhall 效應”[7]。注意:Dhall 原始論文中的例子在此處略有簡化(例如,Dhall 更正確地計算了 lim_{e->0}U)。

即時文獻[8,9]中已經開發了更復雜的全域性 EDF 可排程性測試,但它們不是基於總利用率(或密度)與固定常量的簡單比較。如果所有任務都有 D_i = P_i,則可以簡單地表達一個充分的可排程性條件:

sum(WCET_i / P_i) <= M - (M - 1) · U_max

其中 U_max = max{WCET_i / P_i}[10]。請注意,對於 U_max = 1,M - (M - 1) · U_max 變為 M - M + 1 = 1,此可排程性條件正好證實了 Dhall 效應。關於多處理器即時排程可排程性測試文獻的更完整調查可在 [11] 中找到。

如前所述,強制總利用率小於 M 並不能保證全域性 EDF 在不錯過任何截止期的情況下排程任務(換句話說,全域性 EDF 不是最優排程演算法)。然而,總利用率小於 M 足以保證非即時任務不會被餓死,並且即時任務的遲滯具有上限[12](如前所述)。各種論文[13,14]中已經開發了關於即時任務最大遲滯的不同界限,但對於 SCHED_DEADLINE 而言,重要的理論結果是,如果總利用率小於或等於 M,則任務的響應時間是有限的。

3.4 與 SCHED_DEADLINE 引數的關係

最後,理解第 2 節中描述的 SCHED_DEADLINE 排程引數(執行時、截止期和週期)與本節中描述的即時任務引數(WCET、D、P)之間的關係非常重要。請注意,任務的時間約束由上述絕對截止期 d_j = r_j + D 表示,而 SCHED_DEADLINE 根據排程截止期(參見第 2 節)排程任務。如果使用准入測試來保證排程截止期得到遵守,那麼 SCHED_DEADLINE 可用於排程即時任務,從而保證任務的所有作業截止期都得到遵守。為此,必須透過設定以下引數來排程任務:

  • 執行時 >= WCET

  • 截止期 = D

  • 週期 <= P

換句話說,如果 runtime >= WCET 並且 period <= P,那麼排程截止期和絕對截止期 (d_j) 重合,因此適當的准入控制允許此任務的作業絕對截止期得到遵守(這被稱為“硬可排程性屬性”,並且是 [2] 中引理 1 的擴充套件)。請注意,如果 runtime > deadline,准入控制肯定會拒絕此任務,因為它無法滿足其時間約束。

參考文獻

1 - C. L. Liu 和 J. W. Layland. 多道程式設計的排程演算法 -

在硬即時環境中。計算機械協會雜誌,20(1),1973。

2 - L. Abeni, G. Buttazzo. 在硬即時系統中整合多媒體應用程式

即時系統。第 19 屆 IEEE 即時系統研討會論文集,1998 年。http://retis.sssup.it/~giorgio/paps/1998/rtss98-cbs.pdf

3 - L. Abeni. 多媒體應用程式的服務機制。ReTiS 實驗室

技術報告。http://disi.unitn.it/~abeni/tr-98-01.pdf

4 - J. Y. Leung 和 M.L. Merril. 關於週期性即時任務搶佔式排程的一個註釋。

資訊處理快報,第 11 卷,第 3 期,第 115-118 頁,1980 年。

5 - S. K. Baruah, A. K. Mok 和 L. E. Rosier. 在單個處理器上搶佔式排程

硬即時偶發任務。第 11 屆 IEEE 即時系統研討會論文集,1990 年。

6 - S. K. Baruah, L. E. Rosier 和 R. R. Howell. 關於在單個處理器上週期性即時任務搶佔式排程的演算法和複雜性。

即時系統雜誌,第 4 卷,第 2 期,第 301-324 頁,1990 年。

7 - S. J. Dhall 和 C. L. Liu. 關於即時排程問題。操作

研究,第 26 卷,第 1 期,第 127-140 頁,1978 年。

8 - T. Baker. 多處理器 EDF 和截止期單調可排程性

分析。第 24 屆 IEEE 即時系統研討會論文集,2003 年。

9 - T. Baker. 多處理器上 EDF 可排程性分析。

IEEE 並行與分散式系統學報,第 16 卷,第 8 期,第 760-768 頁,2005 年。

10 - J. Goossens, S. Funk 和 S. Baruah, 多處理器上週期任務系統優先順序驅動排程。

即時系統雜誌,第 25 卷,第 2–3 期,第 187–205 頁,2003 年。

11 - R. Davis 和 A. Burns. 多處理器系統硬即時排程綜述。

ACM 計算調查,第 43 卷,第 4 期,2011 年。http://www-users.cs.york.ac.uk/~robdavis/papers/MPSurveyv5.0.pdf

12 - U. C. Devi 和 J. H. Anderson. 全域性 EDF 排程下多處理器的遲滯界限。

即時系統雜誌,第 32 卷,第 2 期,第 133-189 頁,2008 年。

13 - P. Valente 和 G. Lipari. EDF 在多處理器上排程軟即時任務遲滯的上限。

第 26 屆 IEEE 即時系統研討會論文集,2005 年。

14 - J. Erickson, U. Devi 和 S. Baruah. 全域性 EDF 改進的遲滯界限。

第 22 屆 Euromicro 即時系統會議論文集,2010 年。

15 - G. Lipari, S. Baruah, 常量頻寬伺服器中未使用頻寬的貪婪回收,

第 12 屆 IEEE Euromicro 即時系統會議,2000 年。

16 - L. Abeni, J. Lelli, C. Scordino, L. Palopoli, SCHED DEADLINE 的貪婪 CPU 回收。

在德國杜塞爾多夫舉行的即時 Linux 研討會 (RTLWS) 論文集,2014 年。

17 - L. Abeni, G. Lipari, A. Parri, Y. Sun, 多核 CPU 回收:並行還是序列?

在 2016 年第 31 屆 ACM 應用計算年度研討會論文集。

18 - J. Lelli, C. Scordino, L. Abeni, D. Faggioli, Linux 核心中的截止期排程,

軟體:實踐與經驗,46(6): 821-839,2016 年 6 月。

19 - C. Scordino, L. Abeni, J. Lelli, Linux 核心中節能即時排程,

第 33 屆 ACM/SIGAPP 應用計算研討會 (SAC 2018),法國波城,2018 年 4 月。

4. 頻寬管理

如前所述,為了使 -deadline 排程有效和有用(即能夠在“截止期”內提供“執行時”時間單位),擁有某種方法來控制 CPU 時間的可用部分分配給各種任務非常重要。這通常稱為“准入控制”,如果未執行此操作,則無法保證 -deadline 任務的實際排程。

如第 3 節所述,正確排程一組即時任務所需的一個必要條件是總利用率小於 M。當談到 -deadline 任務時,這意味著所有任務的執行時與週期之比的總和必須小於 M。請注意,執行時/週期之比等同於“傳統”即時任務的利用率,也常被稱為“頻寬”。用於控制可分配給 -deadline 任務的 CPU 頻寬的介面類似於已用於 -rt 任務的即時組排程(又稱 RT-throttling - 參見 即時組排程)的介面,並且基於位於 procfs 中的可讀/寫控制檔案(用於系統範圍設定)。請注意,由於需要進一步討論如何管理任務組級別的 SCHED_DEADLINE 頻寬,因此尚未為 -deadline 任務定義每組設定(透過 cgroupfs 控制)。

截止期頻寬管理與 RT 節流之間的主要區別在於,-deadline 任務擁有自己的頻寬(而 -rt 任務沒有!),因此我們不需要更高級別的節流機制來強制執行所需的頻寬。換句話說,這意味著介面引數僅在准入控制時使用(即,當用戶呼叫 sched_setattr() 時)。然後根據實際任務引數執行排程,以便將 CPU 頻寬分配給 SCHED_DEADLINE 任務,以滿足它們在粒度方面的需求。因此,使用這個簡單的介面,我們可以限制 -deadline 任務的總利用率(即,Sum (runtime_i / period_i) < global_dl_utilization_cap)。

4.1 系統範圍設定

系統範圍設定在 /proc 虛擬檔案系統下配置。

目前,-rt 旋鈕用於 -deadline 准入控制,並且在 CONFIG_RT_GROUP_SCHED 啟用時,-deadline 執行時會計入(根)-rt 執行時。當 !CONFIG_RT_GROUP_SCHED 時,該旋鈕僅用於 -dl 准入控制。我們意識到這並非完全理想;但是,目前最好有一個小介面,以後可以輕鬆更改。理想情況(參見第 5 節)是從 -deadline 伺服器執行 -rt 任務;在這種情況下,-rt 頻寬是 dl_bw 的直接子集。

這意味著,對於包含 M 個 CPU 的 root_domain,可以建立 -deadline 任務,只要其頻寬總和保持在以下值以下:

M * (sched_rt_runtime_us / sched_rt_period_us)

也可以停用此頻寬管理邏輯,從而可以自由地將系統超額訂閱到任意級別。這可以透過在 /proc/sys/kernel/sched_rt_runtime_us 中寫入 -1 來完成。

4.2 任務介面

指定一個週期性/偶發性任務,該任務在每個例項中執行給定量的執行時,並根據其自身時間約束的緊急程度進行排程,通常需要一種宣告以下內容的方式:

  • 一個(最大/典型)例項執行時間,

  • 連續例項之間的最小間隔,

  • 每個例項必須完成的時間約束。

因此

  • 提供了一個新的 struct sched_attr,其中包含所有必需的欄位;

  • 實現了操縱它的新排程相關係統呼叫,即 sched_setattr() 和 sched_getattr()。

出於除錯目的,可以透過 /proc/<pid>/sched(dl.runtime 和 dl.deadline 條目,兩個值均以納秒為單位)檢索 SCHED_DEADLINE 任務的剩餘執行時和絕對截止期。目前正在討論從生產程式碼中檢索這些值的程式設計方式。

4.3 預設行為

SCHED_DEADLINE 頻寬的預設值為 rt_runtime 等於 950000。在 rt_period 等於 1000000 的情況下,預設意味著 -deadline 任務在每個 root_domain 中最多可以使用 95% 的 CPU 時間,乘以構成該 root_domain 的 CPU 數量。這意味著非 -deadline 任務將獲得至少 5% 的 CPU 時間,並且 -deadline 任務將以相對於“截止期”引數的保證最壞情況延遲獲得其執行時。如果“截止期”=“週期”並且使用 cpuset 機制實現分割槽排程(參見第 5 節),那麼這種簡單的頻寬管理設定能夠確定性地保證 -deadline 任務將在一個週期內獲得其執行時。

最後,請注意,為了不危及准入控制,-deadline 任務不能 fork。

4.4 sched_yield() 的行為

當 SCHED_DEADLINE 任務呼叫 sched_yield() 時,它會放棄其剩餘的執行時並立即受到節流,直到下一個週期,此時其執行時將被補充(設定了一個特殊的標誌 dl_yielded,用於在呼叫 sched_yield() 後正確處理節流和執行時補充)。

sched_yield() 的這種行為允許任務恰好在下一個週期開始時喚醒。此外,這在未來與頻寬回收機制結合使用時可能會很有用,因為 sched_yield() 會將剩餘執行時提供給其他 SCHED_DEADLINE 任務進行回收。

5. 任務 CPU 親和性

-deadline 任務不能擁有小於其建立所在的整個 root_domain 的親和性掩碼。但是,可以透過 cpuset 設施 (CPUSETS) 指定親和性。

5.1 SCHED_DEADLINE 和 cpusets HOWTO

以下是一個簡單配置的示例(將一個 -deadline 任務固定到 CPU0)(rt-app 用於建立 -deadline 任務)

mkdir /dev/cpuset
mount -t cgroup -o cpuset cpuset /dev/cpuset
cd /dev/cpuset
mkdir cpu0
echo 0 > cpu0/cpuset.cpus
echo 0 > cpu0/cpuset.mems
echo 1 > cpuset.cpu_exclusive
echo 0 > cpuset.sched_load_balance
echo 1 > cpu0/cpuset.cpu_exclusive
echo 1 > cpu0/cpuset.mem_exclusive
echo $$ > cpu0/tasks
rt-app -t 100000:10000:d:0 -D5 # it is now actually superfluous to specify
                               # task affinity

6. 未來計劃

仍缺少

  • 以程式設計方式檢索當前執行時和絕對截止期的方法

  • 截止期繼承的改進,特別是關於在不相互作用的任務之間保持頻寬隔離的可能性。這正在從理論和實踐兩方面進行研究,希望能儘快提供一些演示程式碼;

  • 基於 (c)group 的頻寬管理,以及可能還有排程;

  • 非根使用者的訪問控制(以及需要解決的相關安全問題),即允許非特權使用者使用這些機制的最佳方式是什麼,以及如何防止非根使用者“欺騙”系統?

如前所述,我們還計劃將這項工作與 EDF 節流補丁 [https://lore.kernel.org/r/cover.1266931410.git.fabio@helm.retis] 合併,但我們仍處於合併的初步階段,我們非常希望獲得有助於我們決定方向的反饋。

附錄 A. 測試套件

SCHED_DEADLINE 策略可以使用兩個應用程式輕鬆測試,它們是更廣泛的 Linux 排程器驗證套件的一部分。該套件可在 GitHub 倉庫中找到:https://github.com/scheduler-tools

第一個測試應用程式名為 rt-app,可用於啟動具有特定引數的多個執行緒。rt-app 支援 SCHED_{OTHER,FIFO,RR,DEADLINE} 排程策略及其相關引數(例如,niceness、priority、runtime/deadline/period)。rt-app 是一個有價值的工具,因為它可用於合成地重現某些工作負載(可能模仿實際用例),並評估排程器在此類工作負載下的行為。透過這種方式,結果易於重現。rt-app 可在以下地址獲取:https://github.com/scheduler-tools/rt-app

執行緒引數可以從命令列指定,例如:

# rt-app -t 100000:10000:d -t 150000:20000:f:10 -D5

上述命令建立 2 個執行緒。第一個執行緒由 SCHED_DEADLINE 排程,每 100ms 執行 10ms。第二個執行緒以 SCHED_FIFO 優先順序 10 排程,每 150ms 執行 20ms。測試將總共執行 5 秒。

更有趣的是,配置可以透過 json 檔案描述,並作為輸入傳遞給 rt-app,例如:

# rt-app my_config.json

用第二種方法可以指定的引數是命令列選項的超集。有關更多詳細資訊,請參閱 rt-app 文件(<rt-app-sources>/doc/*.json)。

第二個測試應用程式使用 chrt 完成,它支援 SCHED_DEADLINE。

用法很簡單:

# chrt -d -T 10000000 -D 100000000 0 ./my_cpuhog_app

這樣,my_cpuhog_app 將在 SCHED_DEADLINE 預留中執行,每 100ms 執行 10ms(請注意,引數以納秒錶示)。您還可以使用 chrt 為已執行的應用程式建立預留,前提是您知道其 pid

# chrt -d -T 10000000 -D 100000000 -p 0 my_app_pid

附錄 B. 最小 main() 函式

下面提供了一個簡單(簡陋)的獨立程式碼片段,展示了即時應用程式開發人員如何建立 SCHED_DEADLINE 預留:

#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <linux/unistd.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <sys/syscall.h>
#include <pthread.h>

#define gettid() syscall(__NR_gettid)

#define SCHED_DEADLINE       6

/* XXX use the proper syscall numbers */
#ifdef __x86_64__
#define __NR_sched_setattr           314
#define __NR_sched_getattr           315
#endif

#ifdef __i386__
#define __NR_sched_setattr           351
#define __NR_sched_getattr           352
#endif

#ifdef __arm__
#define __NR_sched_setattr           380
#define __NR_sched_getattr           381
#endif

static volatile int done;

struct sched_attr {
     __u32 size;

     __u32 sched_policy;
     __u64 sched_flags;

     /* SCHED_NORMAL, SCHED_BATCH */
     __s32 sched_nice;

     /* SCHED_FIFO, SCHED_RR */
     __u32 sched_priority;

     /* SCHED_DEADLINE (nsec) */
     __u64 sched_runtime;
     __u64 sched_deadline;
     __u64 sched_period;
};

int sched_setattr(pid_t pid,
               const struct sched_attr *attr,
               unsigned int flags)
{
     return syscall(__NR_sched_setattr, pid, attr, flags);
}

int sched_getattr(pid_t pid,
               struct sched_attr *attr,
               unsigned int size,
               unsigned int flags)
{
     return syscall(__NR_sched_getattr, pid, attr, size, flags);
}

void *run_deadline(void *data)
{
     struct sched_attr attr;
     int x = 0;
     int ret;
     unsigned int flags = 0;

     printf("deadline thread started [%ld]\n", gettid());

     attr.size = sizeof(attr);
     attr.sched_flags = 0;
     attr.sched_nice = 0;
     attr.sched_priority = 0;

     /* This creates a 10ms/30ms reservation */
     attr.sched_policy = SCHED_DEADLINE;
     attr.sched_runtime = 10 * 1000 * 1000;
     attr.sched_period = attr.sched_deadline = 30 * 1000 * 1000;

     ret = sched_setattr(0, &attr, flags);
     if (ret < 0) {
             done = 0;
             perror("sched_setattr");
             exit(-1);
     }

     while (!done) {
             x++;
     }

     printf("deadline thread dies [%ld]\n", gettid());
     return NULL;
}

int main (int argc, char **argv)
{
     pthread_t thread;

     printf("main thread [%ld]\n", gettid());

     pthread_create(&thread, NULL, run_deadline, NULL);

     sleep(10);

     done = 1;
     pthread_join(thread, NULL);

     printf("main dies [%ld]\n", gettid());
     return 0;
}