Linux程式優先順序的處理--Linux程式的管理與排程(二十二)

JeanCheng發表於2016-06-20
日期 核心版本 架構 作者 GitHub CSDN
2016-06-14 Linux-4.6 X86 & arm gatieme LinuxDeviceDrivers Linux程式管理與排程

1 前景回顧


1.1 程式排程


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

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

核心必須提供一種方法, 在各個程式之間儘可能公平地共享CPU時間, 而同時又要考慮不同的任務優先順序.

排程器的一個重要目標是有效地分配 CPU 時間片,同時提供很好的使用者體驗。排程器還需要面對一些互相沖突的目標,例如既要為關鍵實時任務最小化響應時間, 又要最大限度地提高 CPU 的總體利用率.

排程器的一般原理是, 按所需分配的計算能力, 向系統中每個程式提供最大的公正性, 或者從另外一個角度上說, 他試圖確保沒有程式被虧待.

1.2 程式的分類


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

型別 描述 示例
互動式程式(interactive process) 此類程式經常與使用者進行互動, 因此需要花費很多時間等待鍵盤和滑鼠操作. 當接受了使用者的輸入後, 程式必須很快被喚醒, 否則使用者會感覺系統反應遲鈍 shell, 文字編輯程式和圖形應用程式
批處理程式(batch process) 此類程式不必與使用者互動, 因此經常在後臺執行. 因為這樣的程式不必很快相應, 因此常受到排程程式的怠慢 程式語言的編譯程式, 資料庫搜尋引擎以及科學計算
實時程式(real-time process) 這些程式由很強的排程需要, 這樣的程式絕不會被低優先順序的程式阻塞. 並且他們的響應時間要儘可能的短 視訊音訊應用程式, 機器人控制程式以及從物理感測器上收集資料的程式

在linux中, 排程演算法可以明確的確認所有實時程式的身份, 但是沒辦法區分互動式程式和批處理程式, linux2.6的排程程式實現了基於程式過去行為的啟發式演算法, 以確定程式應該被當做互動式程式還是批處理程式. 當然與批處理程式相比, 排程程式有偏愛互動式程式的傾向

1.3 不同程式採用不同的排程策略


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

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

對於普通程式,則需要區分互動式和批處理式的不同。傳統Linux排程器提高互動式應用的優先順序,使得它們能更快地被排程。而CFS和RSDL等新的排程器的核心思想是”完全公平”。這個設計理念不僅大大簡化了排程器的程式碼複雜度,還對各種排程需求的提供了更完美的支援.

注意Linux通過將程式和執行緒排程視為一個,同時包含二者。程式可以看做是單個執行緒,但是程式可以包含共享一定資源(程式碼和/或資料)的多個執行緒。因此程式排程也包含了執行緒排程的功能.

目前非實時程式的排程策略比較簡單, 因為實時程式值只要求儘可能快的被響應, 基於優先順序, 每個程式根據它重要程度的不同被賦予不同的優先順序,排程器在每次排程時, 總選擇優先順序最高的程式開始執行. 低優先順序不可能搶佔高優先順序, 因此FIFO或者Round Robin的排程策略即可滿足實時程式排程的需求.

但是普通程式的排程策略就比較麻煩了, 因為普通程式不能簡單的只看優先順序, 必須公平的佔有CPU, 否則很容易出現程式飢餓, 這種情況下使用者會感覺作業系統很卡, 響應總是很慢,因此在linux排程器的發展歷程中經過了多次重大變動, linux總是希望尋找一個最接近於完美的排程策略來公平快速的排程程式.

1.4 linux排程器的演變


一開始的排程器是複雜度為O(n)

O(n)
的始排程演算法(實際上每次會遍歷所有任務,所以複雜度為O(n)), 這個演算法的缺點是當核心中有很多工時,排程器本身就會耗費不少時間,所以,從linux2.5開始引入赫赫有名的O(1)
O(1)
排程器

然而,linux是集全球很多程式設計師的聰明才智而發展起來的超級核心,沒有最好,只有更好,在O(1)

O(1)
排程器風光了沒幾天就又被另一個更優秀的排程器取代了,它就是CFS排程器Completely Fair Scheduler. 這個也是在2.6核心中引入的,具體為2.6.23,即從此版本開始,核心使用CFS作為它的預設排程器,O(1)
O(1)
排程器被拋棄了, 其實CFS的發展也是經歷了很多階段,最早期的樓梯演算法(SD), 後來逐步對SD演算法進行改進出RSDL(Rotating Staircase Deadline Scheduler), 這個演算法已經是”完全公平”的雛形了, 直至CFS是最終被核心採納的排程器, 它從RSDL/SD中吸取了完全公平的思想,不再跟蹤程式的睡眠時間,也不再企圖區分互動式程式。它將所有的程式都統一對待,這就是公平的含義。CFS的演算法和實現都相當簡單,眾多的測試表明其效能也非常優越

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

1.5 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演算法排程的普通非實時程式的排程實體

排程器整體框架

每個程式都屬於某個排程器類(由欄位task_struct->sched_class標識), 由排程器類採用程式對應的排程策略排程(由task_struct->policy )進行排程, task_struct也儲存了其對應的排程實體標識

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

排程器類 排程策略 排程策略對應的排程演算法 排程實體 排程實體對應的排程物件
stop_sched_class 特殊情況, 發生在cpu_stop_cpu_callback 進行cpu之間任務遷移migration或者HOTPLUG_CPU的情況下關閉任務
dl_sched_class SCHED_DEADLINE Earliest-Deadline-First最早截至時間有限演算法 sched_dl_entity 採用DEF最早截至時間有限演算法排程實時程式
rt_sched_class SCHED_RR

SCHED_FIFO
Roound-Robin時間片輪轉演算法

FIFO先進先出演算法
sched_rt_entity 採用Roound-Robin或者FIFO演算法排程的實時排程實體
fair_sched_class SCHED_NORMAL

SCHED_BATCH
CFS完全公平懂排程演算法 sched_entity 採用CFS演算法普通非實時程式
idle_sched_class SCHED_IDLE 特殊程式, 用於cpu空閒時排程空閒程式idle

2 linux優先順序的表示


2.1 優先順序的核心表示


linux優先順序概述

在使用者空間通過nice命令設定程式的靜態優先順序, 這在內部會呼叫nice系統呼叫, 程式的nice值在-20~+19之間. 值越低優先順序越高.

setpriority系統呼叫也可以用來設定程式的優先順序. 它不僅能夠修改單個執行緒的優先順序, 還能修改程式組中所有程式的優先順序, 或者通過制定UID來修改特定使用者的所有程式的優先順序

核心使用一些簡單的數值範圍0~139表示內部優先順序, 數值越低, 優先順序越高。

從0~99的範圍專供實時程式使用, nice的值[-20,19]則對映到範圍100~139

linux2.6核心將任務優先順序進行了一個劃分, 實時優先順序範圍是0到MAX_RT_PRIO-1(即99),而普通程式的靜態優先順序範圍是從MAX_RT_PRIO到MAX_PRIO-1(即100到139).

優先順序範圍 描述
0——99 實時程式
100——139 非實時程式


核心的優先順序標度

核心的優先順序表示

核心表示優先順序的所有資訊基本都放在include/linux/sched/prio.h中, 其中定義了一些表示優先順序的巨集和函式.

優先順序數值通過巨集來定義, 如下所示,

其中MAX_NICE和MIN_NICE定義了nice的最大最小值

而MAX_RT_PRIO指定了實時程式的最大優先順序, 而MAX_PRIO則是普通程式的最大優先順序數值

/*  http://lxr.free-electrons.com/source/include/linux/sched/prio.h?v=4.6#L4 */
#define MAX_NICE        19
#define MIN_NICE        -20
#define NICE_WIDTH      (MAX_NICE - MIN_NICE + 1)

/* http://lxr.free-electrons.com/source/include/linux/sched/prio.h?v=4.6#L24  */
#define MAX_PRIO        (MAX_RT_PRIO + 40)
#define DEFAULT_PRIO        (MAX_RT_PRIO + 20)
巨集 描述
MIN_NICE -20 對應於優先順序100, 可以使用NICE_TO_PRIO和PRIO_TO_NICE轉換
MAX_NICE 19 對應於優先順序139, 可以使用NICE_TO_PRIO和PRIO_TO_NICE轉換
NICE_WIDTH 40 nice值得範圍寬度, 即[-20, 19]共40個數字的寬度
MAX_RT_PRIO, MAX_USER_RT_PRIO 100 實時程式的最大優先順序
MAX_PRIO 140 普通程式的最大優先順序
DEFAULT_PRIO 120 程式的預設優先順序, 對應於nice=0
MAX_DL_PRIO 0 使用EDF最早截止時間優先排程演算法的實時程式最大的優先順序

而核心提供了一組巨集將優先順序在各種不同的表示形之間轉移

//  http://lxr.free-electrons.com/source/include/linux/sched/prio.h?v=4.6#L27
/*
 * Convert user-nice values [ -20 ... 0 ... 19 ]
 * to static priority [ MAX_RT_PRIO..MAX_PRIO-1 ],
 * and back.
 */
#define NICE_TO_PRIO(nice)      ((nice) + DEFAULT_PRIO)
#define PRIO_TO_NICE(prio)      ((prio) - DEFAULT_PRIO)

/*
 * 'User priority' is the nice value converted to something we
 * can work with better when scaling various scheduler parameters,
 * it's a [ 0 ... 39 ] range.
 */
#define USER_PRIO(p)            ((p)-MAX_RT_PRIO)
#define TASK_USER_PRIO(p)       USER_PRIO((p)->static_prio)
#define MAX_USER_PRIO           (USER_PRIO(MAX_PRIO))

還有一些nice值和rlimit值之間相互轉換的函式nice_to_rlimit和rlimit_to_nice, 這在nice系統呼叫進行檢查的時候很有用, 他們定義在include/linux/sched/prio.h, L47中, 如下所示

/*
 * Convert nice value [19,-20] to rlimit style value [1,40].
 */
static inline long nice_to_rlimit(long nice)
{
    return (MAX_NICE - nice + 1);
}

/*
 * Convert rlimit style value [1,40] to nice value [-20, 19].
 */
static inline long rlimit_to_nice(long prio)
{
    return (MAX_NICE - prio + 1);
}

DEF最早截至時間優先實時排程演算法的優先順序描述

此外新版本的核心還引入了EDF實時排程演算法, 它的優先順序比RT程式和NORMAL/BATCH程式的優先順序都要高, 關於EDF的優先順序的設定資訊都早核心標頭檔案include/linux/sched/deadline.h

因此核心將MAX_DL_PRIO設定為0, 可以參見核心檔案include/linux/sched/deadline.h

#define MAX_DL_PRIO             0

此外也提供了一些EDF優先順序處理所需的函式, 如下所示, 可以參見核心檔案include/linux/sched/deadline.h

static inline int dl_prio(int prio)
{
    if (unlikely(prio < MAX_DL_PRIO))
            return 1;
    return 0;
}

static inline int dl_task(struct task_struct *p)
{
    return dl_prio(p->prio);
}

static inline bool dl_time_before(u64 a, u64 b)
{
    return (s64)(a - b) < 0;
}

2.2 程式的優先順序表示


struct task_struct
{
    /* 程式優先順序
     * prio: 動態優先順序,範圍為100~139,與靜態優先順序和補償(bonus)有關
     * static_prio: 靜態優先順序,static_prio = 100 + nice + 20 (nice值為-20~19,所以static_prio值為100~139)
     * normal_prio: 沒有受優先順序繼承影響的常規優先順序,具體見normal_prio函式,跟屬於什麼型別的程式有關
     */
    int prio, static_prio, normal_prio;
    /* 實時程式優先順序 */
    unsigned int rt_priority;
}

動態優先順序 靜態優先順序 實時優先順序

其中task_struct採用了三個成員表示程式的優先順序:prio和normal_prio表示動態優先順序, static_prio表示程式的靜態優先順序.

為什麼表示動態優先順序需要兩個值prio和normal_prio

排程器會考慮的優先順序則儲存在prio. 由於在某些情況下核心需要暫時提高程式的優先順序, 因此需要用prio表示. 由於這些改變不是持久的, 因此靜態優先順序static_prio和普通優先順序normal_prio不受影響.

此外還用了一個欄位rt_priority儲存了實時程式的優先順序

欄位 描述
static_prio 用於儲存靜態優先順序, 是程式啟動時分配的優先順序, ,可以通過nice和sched_setscheduler系統呼叫來進行修改, 否則在程式執行期間會一直保持恆定
rt_priority 用於儲存實時優先順序
normal_prio 表示基於程式的靜態優先順序static_prio和排程策略計算出的優先順序. 因此即使普通程式和實時程式具有相同的靜態優先順序, 其普通優先順序也是不同的, 程式分叉(fork)時, 子程式會繼承父程式的普通優先順序
prio 儲存程式的動態優先順序

實時程式的優先順序用實時優先順序rt_priority來表示

3 程式優先順序的計算


前面說了task_struct中的幾個優先順序的欄位

靜態優先順序 實時優先順序 普通優先順序 動態優先順序
static_prio rt_priority normal_prio prio

但是這些優先順序是如何關聯的呢, 動態優先順序prio又是如何計算的呢?

3.1 normal_prio函式設定普通優先順序normal_prio



靜態優先順序static_prio(普通程式)和實時優先順序rt_priority(實時程式)是計算的起點

因此他們也是程式建立的時候設定好的, 我們通過nice修改的就是普通程式的靜態優先順序static_prio

首先通過靜態優先順序static_prio計算出普通優先順序normal_prio, 該工作可以由nromal_prio來完成, 該函式定義在kernel/sched/core.c#L861

/*
 * __normal_prio - return the priority that is based on the static prio
 * 普通程式(非實時程式)的普通優先順序normal_prio就是靜態優先順序static_prio
 */
static inline int __normal_prio(struct task_struct *p)
{
    return p->static_prio;
}

/*
 * Calculate the expected normal priority: i.e. priority
 * without taking RT-inheritance into account. Might be
 * boosted by interactivity modifiers. Changes upon fork,
 * setprio syscalls, and whenever the interactivity
 * estimator recalculates.
 */
static inline int normal_prio(struct task_struct *p)
{
    int prio;

    if (task_has_dl_policy(p))              /*  EDF排程的實時程式  */
            prio = MAX_DL_PRIO-1;
    else if (task_has_rt_policy(p))       /*  普通實時程式的優先順序  */
            prio = MAX_RT_PRIO-1 - p->rt_priority;
    else                                              /*  普通程式的優先順序  */
            prio = __normal_prio(p);
    return prio;
}
程式型別 排程器 普通優先順序normal_prio
EDF實時程式 EDF MAX_DL_PRIO-1 = -1
普通實時程式 RT MAX_RT_PRIO-1 - p->rt_priority = 99 - rt_priority
普通程式 CFS __normal_prio(p) = static_prio


普通優先順序normal_prio需要根據普通程式和實時程式進行不同的計算, 其中__normal_prio適用於普通程式, 直接將普通優先順序normal_prio設定為靜態優先順序static_prio. 而實時程式的普通優先順序計算依據其實時優先順序rt_priority.

3.1.1 輔助函式task_has_dl_policy和task_has_rt_policy


定義在kernel/sched/sched.h#L117

其本質其實就是傳入task->policy排程策略欄位看其值等於SCHED_NORMAL, SCHED_BATCH, SCHED_IDLE, SCHED_FIFO, SCHED_RR, SCHED_DEADLINE中的哪個, 從而確定其所屬的排程類, 進一步就確定了其程式型別

static inline int idle_policy(int policy)
{
    return policy == SCHED_IDLE;
}
static inline int fair_policy(int policy)
{
    return policy == SCHED_NORMAL || policy == SCHED_BATCH;
}

static inline int rt_policy(int policy)
{
    return policy == SCHED_FIFO || policy == SCHED_RR;
}

static inline int dl_policy(int policy)
{
        return policy == SCHED_DEADLINE;
}
static inline bool valid_policy(int policy)
{
        return idle_policy(policy) || fair_policy(policy) ||
                rt_policy(policy) || dl_policy(policy);
}

static inline int task_has_rt_policy(struct task_struct *p)
{
        return rt_policy(p->policy);
}

static inline int task_has_dl_policy(struct task_struct *p)
{
        return dl_policy(p->policy);
}

3.1.2 關於rt_priority數值越大, 實時程式優先順序越高的問題


我們前面提到了數值越小, 優先順序越高, 但是此處我們會發現rt_priority的值越大, 其普通優先順序越小, 從而優先順序越高.

因此網上出現了一種說法, 優先順序越高?這又是怎麼回事?難道有一種說法錯了嗎?

實際的原因是這樣的,對於一個實時程式,他有兩個引數來表明優先順序——prio 和 rt_priority,

prio才是排程所用的最終優先順序數值,這個值越小,優先順序越高;

而rt_priority 被稱作實時程式優先順序,他要經過轉化——prio=MAX_RT_PRIO - 1- p->rt_priority;

MAX_RT_PRIO = 100, ;這樣意味著rt_priority值越大,優先順序越高;

而核心提供的修改優先順序的函式,是修改rt_priority的值,所以越大,優先順序越高。

所以使用者在使用實時程式或執行緒,在修改優先順序時,就會有“優先順序值越大,優先順序越高的說法”,也是對的。

3.1.3 為什麼需要__normal_prio函式


我們肯定會奇怪, 為什麼增加了一個__normal_prio函式做了這麼簡單的工作, 這個其實是有歷史原因的: 在早期的O(1)

O(1)
排程器中, 普通優先順序的計算涉及相當多技巧性地工作, 必須檢測互動式程式並提高其優先順序, 而必須”懲罰”非互動程式, 以便是得系統獲得更好的互動體驗. 這需要很多啟發式的計算, 他們可能完成的很好, 也可能不工作

3.2 effective_prio函式設定動態優先順序prio



可以通過函式effective_prio用靜態優先順序static_prio計算動態優先順序prio, 即·

p->prio = effective_prio(p);

該函式定義在kernel/sched/core.c, line 861

/*
 * Calculate the current priority, i.e. the priority
 * taken into account by the scheduler. This value might
 * be boosted by RT tasks, or might be boosted by
 * interactivity modifiers. Will be RT if the task got
 * RT-boosted. If not then it returns p->normal_prio.
 */
static int effective_prio(struct task_struct *p)
{
    p->normal_prio = normal_prio(p);
    /*
     * If we are RT tasks or we were boosted to RT priority,
     * keep the priority unchanged. Otherwise, update priority
     * to the normal priority:
     */
    if (!rt_prio(p->prio))
            return p->normal_prio;
    return p->prio;
}


我們會發現函式首先effective_prio設定了普通優先順序, 顯然我們用effective_prio同時設定了兩個優先順序(普通優先順序normal_prio和動態優先順序prio)

因此計算動態優先順序的流程如下

  • 設定程式的普通優先順序(實時程式99-rt_priority, 普通程式為static_priority)

  • 計算程式的動態優先順序(實時程式則維持動態優先順序的prio不變, 普通程式的動態優先順序即為其普通優先順序)

最後, 我們綜述一下在針對不同型別程式的計算結果

程式型別 實時優先順序rt_priority 靜態優先順序static_prio 普通優先順序normal_prio 動態優先順序prio
EDF排程的實時程式 rt_priority 不使用 MAX_DL_PRIO-1 維持原prio不變
RT演算法排程的實時程式 rt_priority 不使用 MAX_RT_PRIO-1-rt_priority 維持原prio不變
普通程式 不使用 static_prio static_prio static_prio
優先順序提高的普通程式 不使用 static_prio(改變) static_prio 維持原prio不變

3.2.1 為什麼effective_prio使用優先順序數值檢測實時程式


t_prio會檢測普通優先順序是否在實時範圍內, 即是否小於MAX_RT_PRIO.參見include/linux/sched/rt.h#L6

static inline int rt_prio(int prio)
{
    if (unlikely(prio < MAX_RT_PRIO))
        return 1;
    return 0;
}

而前面我們在normal_prio的時候, 則通過task_has_rt_policy來判斷其policy屬性來確定

policy == SCHED_FIFO || policy == SCHED_RR;

那麼為什麼effective_prio重檢測實時程式是rt_prio基於優先順序數值, 而非task_has_rt_policy或者rt_policy?

對於臨時提高至實時優先順序的非實時程式來說, 這個是必要的, 這種情況可能發生在是哦那個實時互斥量(RT-Mutex)時.

3.3 設定prio的時機


  • 在新程式用wake_up_new_task喚醒時, 或者使用nice系統呼叫改變其靜態優先順序時, 則會通過effective_prio的方法設定p->prio

wake_up_new_task(), 計算此程式的優先順序和其他排程引數,將新的程式加入到程式排程佇列並設此程式為可被排程的,以後這個程式可以被程式排程模組排程執行。

  • 程式建立時copy_process通過呼叫sched_fork來初始化和設定排程器的過程中會設定子程式的優先順序

3.4 nice系統呼叫的實現


nice系統呼叫是的核心實現是sys_nice, 其定義在kernel/sched/core.c#L7498

它在通過一系列檢測後, 通過set_user_nice函式, 其定義在kernel/sched/core.c#L3497

關於其具體實現我們會在另外一篇部落格裡面詳細講

3.5 fork時優先順序的繼承


在程式分叉處子程式時, 子程式的靜態優先順序繼承自父程式. 子程式的動態優先順序p->prio則被設定為父程式的普通優先順序, 這確保了實時互斥量引起的優先順序提高不會傳遞到子程式.

可以參照sched_fork函式, 在程式複製的過程中copy_process通過呼叫sched_fork來設定子程式優先順序, 參見sched_fork函式

/*
 * fork()/clone()-time setup:
 */
int sched_fork(unsigned long clone_flags, struct task_struct *p)
{
    /*  ......  */
    /*
     * Make sure we do not leak PI boosting priority to the child.
     * 子程式的動態優先順序被設定為父程式普通優先順序 
     */
    p->prio = current->normal_prio;

    /*
     * Revert to default priority/policy on fork if requested.
     * sched_reset_on_fork標識用於判斷是否恢復預設的優先順序或排程策略

     */
    if (unlikely(p->sched_reset_on_fork))  /*  如果要恢復預設的排程策略, 即SCHED_NORMAL  */
    {
        /*   首先是設定靜態優先順序static_prio
         *   由於要恢復預設的排程策略
         *   對於父程式是實時程式的情況, 靜態優先順序就設定為DEFAULT_PRIO
         *
         *   對於父程式是非實時程式的情況, 要保證子程式優先順序不小於DEFAULT_PRIO
         *   父程式nice < 0即static_prio < 的重新設定為DEFAULT_PRIO的重新設定為DEFAULT_PRIO
         *   父程式nice > 0的時候, 則什麼也沒做
         *   */
        if (task_has_dl_policy(p) || task_has_rt_policy(p))
        {
            p->policy = SCHED_NORMAL;           /*  普通程式排程策略  */
            p->static_prio = NICE_TO_PRIO(0);   /*  靜態優先順序為nice = 0 即DEFAULT_PRIO*/
            p->rt_priority = 0;                             /*  實時優先順序為0  */
        }
        else if (PRIO_TO_NICE(p->static_prio) < 0)  /*  */
            p->static_prio = NICE_TO_PRIO(0);   /*  */

        /*  接著就通過__normal_prio設定其普通優先順序和動態優先順序
          *  這裡做了一個優化, 因為用sched_reset_on_fork標識設定恢復預設排程策略後
          *  建立的子程式是是SCHED_NORMAL的非實時程式
          *  因此就不需要繞一大圈用effective_prio設定normal_prio和prio了 
          *  直接用__normal_prio設定就可  */
        p->prio = p->normal_prio = __normal_prio(p); /*  設定*/

        /*  設定負荷權重  */
        set_load_weight(p);

        /*
         * We don't need the reset flag anymore after the fork. It has
         * fulfilled its duty:
         */
        p->sched_reset_on_fork = 0;
    }
    /*  ......  */
}

4 總結



task_struct採用了四個成員表示程式的優先順序:prio和normal_prio表示動態優先順序, static_prio表示程式的靜態優先順序. 同時還用了rt_priority表示實時程式的優先順序

欄位 描述
static_prio 用於儲存靜態優先順序, 是程式啟動時分配的優先順序, ,可以通過nice和sched_setscheduler系統呼叫來進行修改, 否則在程式執行期間會一直保持恆定
prio 程式的動態優先順序, 這個有顯示才是排程器重點考慮的程式優先順序
normal_prio 普通程式的靜態優先順序static_prio和排程策略計算出的優先順序. 因此即使普通程式和實時程式具有相同的靜態優先順序, 其普通優先順序也是不同的, 程式分叉(fork)時, 子程式會繼承父程式的普通優先順序, 可以通過normal_prio來計算(非實時程式用static_prIo計算, 實時程式用rt_priority計算)
rt_priority 實時程式的靜態優先順序

排程器會考慮的優先順序則儲存在prio. 由於在某些情況下核心需要暫時提高程式的優先順序, 因此需要用prio表示. 由於這些改變不是持久的, 因此靜態優先順序static_prio和普通優先順序normal_prio不受影響.
此外還用了一個欄位rt_priority儲存了實時程式的優先順序靜態優先順序static_prio(普通程式)和實時優先順序rt_priority(實時程式)是計算的起點, 通過他們計算程式的普通優先順序normal_prio和動態優先順序prio.


核心通過normal_prIo函式計算普通優先順序normal_prio
通過effective_prio函式計算動態優先順序prio

參考

程式排程之sys_nice()系統呼叫

linux排程器原始碼研究 - 概述(一)

深入 Linux 的程式優先順序

相關文章