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排程器的系列研究了。
本文也會從一些基礎的概念及資料結構入手,先打造一個粗略的輪廓,後續的文章將逐漸深入。

2. 概念

2.1 程序

  • 從教科書上,我們都能知道:程序是資源分配的最小單位,而執行緒是CPU排程的的最小單位。
  • 程序不僅包括可執行程式的程式碼段,還包括一系列的資源,比如:開啟的檔案、記憶體、CPU時間、訊號量、多個執行執行緒流等等。而執行緒可以共享程序內的資源空間。
  • 在Linux核心中,程序和執行緒都使用struct task_struct結構來進行抽象描述。
  • 程序的虛擬地址空間分為使用者虛擬地址空間和核心虛擬地址空間,所有程序共享核心虛擬地址空間,沒有使用者虛擬地址空間的程序稱為核心執行緒。

Linux核心使用task_struct結構來抽象,該結構包含了程序的各類資訊及所擁有的資源,比如程序的狀態、開啟的檔案、地址空間資訊、訊號資源等等。task_struct結構很複雜,下邊只針對與排程相關的某些欄位進行介紹。

struct task_struct {
    /* ... */
    
    /* 程序狀態 */
    volatile long           state;

    /* 排程優先順序相關,策略相關 */
    int             prio;
    int             static_prio;
    int             normal_prio;
    unsigned int            rt_priority;
    unsigned int            policy;
    
    /* 排程類,排程實體相關,任務組相關等 */
    const struct sched_class    *sched_class;
    struct sched_entity     se;
    struct sched_rt_entity      rt;
#ifdef CONFIG_CGROUP_SCHED
    struct task_group       *sched_task_group;
#endif
    struct sched_dl_entity      dl;
    
    /* 程序之間的關係相關 */
        /* Real parent process: */
    struct task_struct __rcu    *real_parent;

    /* Recipient of SIGCHLD, wait4() reports: */
    struct task_struct __rcu    *parent;

    /*
     * Children/sibling form the list of natural children:
     */
    struct list_head        children;
    struct list_head        sibling;
    struct task_struct      *group_leader;
    
    /* ... */
}

2.2 程序狀態

  • 上圖中左側為作業系統中通俗的程序三狀態模型,右側為Linux對應的程序狀態切換。每一個標誌描述了程序的當前狀態,這些狀態都是互斥的;
  • Linux中的就緒態執行態對應的都是TASK_RUNNING標誌位,就緒態表示程序正處在佇列中,尚未被排程;執行態則表示程序正在CPU上執行;

核心中主要的狀態欄位定義如下

/* Used in tsk->state: */
#define TASK_RUNNING            0x0000
#define TASK_INTERRUPTIBLE      0x0001
#define TASK_UNINTERRUPTIBLE        0x0002

/* Used in tsk->exit_state: */
#define EXIT_DEAD           0x0010
#define EXIT_ZOMBIE         0x0020
#define EXIT_TRACE          (EXIT_ZOMBIE | EXIT_DEAD)

/* Used in tsk->state again: */
#define TASK_PARKED         0x0040
#define TASK_DEAD           0x0080
#define TASK_WAKEKILL           0x0100
#define TASK_WAKING         0x0200
#define TASK_NOLOAD         0x0400
#define TASK_NEW            0x0800
#define TASK_STATE_MAX          0x1000

/* Convenience macros for the sake of set_current_state: */
#define TASK_KILLABLE           (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_STOPPED            (TASK_WAKEKILL | __TASK_STOPPED)
#define TASK_TRACED         (TASK_WAKEKILL | __TASK_TRACED)

#define TASK_IDLE           (TASK_UNINTERRUPTIBLE | TASK_NOLOAD)

2.3 scheduler 排程器

  • 所謂排程,就是按照某種排程的演算法,從程序的就緒佇列中選取程序分配CPU,主要是協調對CPU等的資源使用。程序排程的目標是最大限度利用CPU時間。

核心預設提供了5個排程器,Linux核心使用struct sched_class來對排程器進行抽象:

  1. Stop排程器, stop_sched_class:優先順序最高的排程類,可以搶佔其他所有程序,不能被其他程序搶佔;
  2. Deadline排程器, dl_sched_class:使用紅黑樹,把程序按照絕對截止期限進行排序,選擇最小程序進行排程執行;
  3. RT排程器, rt_sched_class:實時排程器,為每個優先順序維護一個佇列;
  4. CFS排程器, cfs_sched_class:完全公平排程器,採用完全公平排程演算法,引入虛擬執行時間概念;
  5. IDLE-Task排程器, idle_sched_class:空閒排程器,每個CPU都會有一個idle執行緒,當沒有其他程序可以排程時,排程執行idle執行緒;

Linux核心提供了一些排程策略供使用者程式來選擇排程器,其中Stop排程器IDLE-Task排程器,僅由核心使用,使用者無法進行選擇:

  • SCHED_DEADLINE:限期程序排程策略,使task選擇Deadline排程器來排程執行;
  • SCHED_RR:實時程序排程策略,時間片輪轉,程序用完時間片後加入優先順序對應執行佇列的尾部,把CPU讓給同優先順序的其他程序;
  • SCHED_FIFO:實時程序排程策略,先進先出排程沒有時間片,沒有更高優先順序的情況下,只能等待主動讓出CPU;
  • SCHED_NORMAL:普通程序排程策略,使task選擇CFS排程器來排程執行;
  • SCHED_BATCH:普通程序排程策略,批量處理,使task選擇CFS排程器來排程執行;
  • SCHED_IDLE:普通程序排程策略,使task以最低優先順序選擇CFS排程器來排程執行;

2.4 runqueue 執行佇列

  • 每個CPU都有一個執行佇列,每個排程器都作用於執行佇列;
  • 分配給CPU的task,作為排程實體加入到執行佇列中;
  • task首次執行時,如果可能,儘量將它加入到父task所在的執行佇列中(分配給相同的CPU,快取affinity會更高,效能會有改善);

Linux核心使用struct rq結構來描述執行佇列,關鍵欄位如下:

/*
 * This is the main, per-CPU runqueue data structure.
 *
 * Locking rule: those places that want to lock multiple runqueues
 * (such as the load balancing or the thread migration code), lock
 * acquire operations must be ordered by ascending &runqueue.
 */
struct rq {
    /* runqueue lock: */
    raw_spinlock_t lock;

    /*
     * nr_running and cpu_load should be in the same cacheline because
     * remote CPUs use both these fields when doing load calculation.
     */
    unsigned int nr_running;
    
    /* 三個排程佇列:CFS排程,RT排程,DL排程 */
    struct cfs_rq cfs;
    struct rt_rq rt;
    struct dl_rq dl;

    /* stop指向遷移核心執行緒, idle指向空閒核心執行緒 */
    struct task_struct *curr, *idle, *stop;
    
    /* ... */
}    

2.5 task_group 任務分組

  • 利用任務分組的機制,可以設定或限制任務組對CPU的利用率,比如將某些任務限制在某個區間內,從而不去影響其他任務的執行效率;
  • 引入task_group後,排程器的排程物件不僅僅是程序了,Linux核心抽象出了sched_entity/sched_rt_entity/sched_dl_entity描述排程實體,排程實體可以是程序或task_group
  • 使用資料結構struct task_group來描述任務組,任務組在每個CPU上都會維護一個CFS排程實體、CFS執行佇列,RT排程實體,RT執行佇列

Linux核心使用struct task_group來描述任務組,關鍵的欄位如下:

/* task group related information */
struct task_group {
    /* ... */

    /* 為每個CPU都分配一個CFS排程實體和CFS執行佇列 */
#ifdef CONFIG_FAIR_GROUP_SCHED
    /* schedulable entities of this group on each cpu */
    struct sched_entity **se;
    /* runqueue "owned" by this group on each cpu */
    struct cfs_rq **cfs_rq;
    unsigned long shares;
#endif

    /* 為每個CPU都分配一個RT排程實體和RT執行佇列 */
#ifdef CONFIG_RT_GROUP_SCHED
    struct sched_rt_entity **rt_se;
    struct rt_rq **rt_rq;

    struct rt_bandwidth rt_bandwidth;
#endif

    /* task_group之間的組織關係 */
    struct rcu_head rcu;
    struct list_head list;

    struct task_group *parent;
    struct list_head siblings;
    struct list_head children;

    /* ... */
};

3. 排程程式

排程程式依靠幾個函式來完成排程工作的,下邊將介紹幾個關鍵的函式。

  1. 主動排程 - schedule()
  • schedule()函式,是程序排程的核心函式,大體的流程如上圖所示。
  • 核心的邏輯:選擇另外一個程序來替換掉當前執行的程序。程序的選擇是通過程序所使用的排程器中的pick_next_task函式來實現的,不同的排程器實現的方法不一樣;程序的替換是通過context_switch()來完成切換的,具體的細節後續的文章再深入分析。
  1. 週期排程 - schedule_tick()
  • 時鐘中斷處理程式中,呼叫schedule_tick()函式;
  • 時鐘中斷是排程器的脈搏,核心依靠週期性的時鐘來處理器CPU的控制權;
  • 時鐘中斷處理程式,檢查當前程序的執行時間是否超額,如果超額則設定重新排程標誌(_TIF_NEED_RESCHED);
  • 時鐘中斷處理函式返回時,被中斷的程序如果在使用者模式下執行,需要檢查是否有重新排程標誌,設定了則呼叫schedule()排程;
  1. 高精度時鐘排程 - hrtick()
  • 高精度時鐘排程,與週期性排程類似,不同點在於週期排程的精度為ms級別,而高精度排程的精度為ns級別;
  • 高精度時鐘排程,需要有對應的硬體支援;
  1. 程序喚醒時排程 - wake_up_process()
  • 喚醒程序時呼叫wake_up_process()函式,被喚醒的程序可能搶佔當前的程序;

上述講到的幾個函式都是常用於排程時呼叫。此外,在建立新程序時,或是在核心搶佔時,也會出現一些排程點。

本文只是粗略的介紹了一個大概,後續將針對某些模組進行更加深入的分析,敬請期待。

相關推薦

原創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程序排程-實時排程

背景 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 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 - 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記憶體管理 - 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初識ES

       接觸ElasticSearch一段時間了,前期調研、專案接入、資料同步、列表查詢這些階段都已經經歷了,但是ES在腦海中的整體映像還是模模糊糊,做個梳理,認真地思考一下ElasticSearch中模糊的思維。     &nb

BFS抓住那頭牛poj 3278

思路 open表 用queue模板 closed表 用MAXN長的一維陣列即可 人的位置是起點,牛的位置是終點。走一步形成雙向邊,跳著走(乘2)形成單向邊,畫出圖。(需要查重。)然後愉快的

C入門總結

題記:重新認真學習C入門這段時間,發現自己之前的不足與許多能力裡不達標的地方。通過不斷地練習,今天總結一下前段時間學習內容。 ——————————————————————————————————————————————— 分子與迴圈語句 這部分的內容在C整個部分扮演者重要的角色使用頻

C入門

內容核心 鞏固練習C語言入門語法練習 1.編寫一個程式,可以一直接收鍵盤字元 詳細/* 如果是小寫字元就輸出對應的大寫字元, 如果接收的是大寫字元,就輸出對應的小寫字元, 如果是數字不輸出。 */ 個人看法:這道題我在練習時有兩種思路 1.將輸入的字元先轉化為int型 然後根據 AS

資訊學奧賽C++賦值語句

一、基本知識 在C/C++中,“=” 在語言中的作用並非是數學意義上的“等於號”,也不表示判斷。 “=”在這裡的意思是賦值:表示把它右邊的值賦給左邊。 一般形式為:變數=表示式 有的時候編譯器會提示不

ShiroShiro初瞭解

在ITOO裡面登入用到了Shrio,一直沒有好好的理解和學習,下面我們就來看一下Shiro到底是什麼東西。 (一)Shiro介紹      shiro是apache的一個開源框架,是一個許可權管理的框

VC++:MFC在Picture control控制元件中顯示Bitmap

今天在《VC++指紋模式識別系統及演算法概述》一書中,看到有一段程式碼——在Picture Control中顯示Bitmap。把它的程式碼和顯示結果摘出來,作為今天的小小學習成果,鼓勵一下自己。程式碼