Linux Scheduler 之 rt 選核流程

前言

在 Linux 中,有些線程需要被公平調度,保證每個線程不會長時間的調度不到,這就是我們熟知的 CFS 調度類(sched class),但是也有一些關鍵線程(比如一些顯示刷幀的支撐線程),我們需要保證線程能夠及時被調度到,針對普通負載較輕的場景,線程的調度及時性都能得到保證。但是爲了滿足人們的日常使用需求,操作系統後臺駐留任務越來越多(這種現象在 Android 設備上表現尤爲嚴重),而系統的 CPU 始終只有一個,即使這個 CPU 有 8 個核甚至更多,CPU 也有可能被塞滿 task,爲了保證一些重要 task 的及時運行,這裏就有了實時進程的概念。

Linux 中,系統是通過優先級來區分非實時線程和實時線程的,Linux 將線程優先級分爲 140 個等級,從 0~139,這個值越小其優先級越高,實時線程也叫 rt(real time) 線程,其優先級範圍爲 [0,99],非實時線程爲 [100,139]。非實時線程也叫 cfs 線程,他的 default 優先級爲 120,通過 nice 值轉化爲最終的線程優先級。Nice 值的取值範圍爲 - 20~19,可以通過 ps 命令查看 NI 列:

圖片

本文主要講述 rt 線程的選核流程。

rt 選核流程介紹

每個 task 在被 wakup 喚醒時候都會從 try_to_wake_up 開始執行,這裏會爲 task 選擇合適的 CPU 運行,其核心邏輯位於 select_task_rq 函數里面,它會根據 task 的 sched_class 確定調用的具體函數,而 task 的 sched_class 初始化則是在系統更改或者設置 task 的 priority 時,根據 task 的 priority 進行設置:

圖片

如果一個線程爲 rt,它的 sched_class 則爲 rt_sched_class,結構體初始化定義如下:

圖片

那麼 rt 線程對應調度類的選核函數爲 select_task_rq_rt,其基本的執行邏輯如下圖 1:

圖片

rt 選核流程比較簡單,其核心邏輯位於 find_lowest_rq 函數中,此函數主要用於尋找符合當前 rt 線程運行的 cpu,其核心邏輯如下圖 2:

圖片

cpupri_find_fitness 負責從所有系統中所有的符合 task 運行條件的 cpu 找出來,並更新到 lowest_mask 裏面,然後 find_lowest_rq 再從最終的 lowest_mask 裏面選擇合適的 CPU,選擇邏輯:

  1. 如果 lowest_mask 裏面包含 task 的 prev cpu,則直接選擇 prev cpu。

  2. 選擇 lowest_mask 在 task 的 sched domain 裏面的第一個 cpu。

  3. 如果前面都沒找到 CPU,則判斷 lowest_mask 裏面是否包含了 wake cpu,如果包含則直接返回 wake cpu

  4. 最後如果都沒找到,lowest 又不爲空,則從 lowest_mask 裏面找一個隨機的 cpu 返回。

下面來看下 cpupri_find_fitness 如何找到合適的所有適合 task 運行的 cpu,主要分爲 3 個部分:

圖片

  1. 首先對 task 優先級進行一個轉化,將系統中的 task 分爲 0~102 個等級,優先級由低到高,可以分爲 invalid,idle,normal 和 rt 四類。

圖片

其中 invalid 優先級爲 0,idle 優先級爲 1,所有的 cfs 線程佔用一類,優先級爲 2,rt 則每個優先級佔用一個等級。

  1. 因爲 rt 線程是可以搶佔的,for 循環從最低優先級開始遍歷,這裏的優先級爲已經轉化爲 103 個級別的優先級狀態,其選核邏輯一般爲首先選擇 idle 的 cpu 運行,其次是搶佔有 cfs task 的 cpu 運行,最後纔會考慮取搶佔其他低優先級 rt task 的 cpu,通過__cpupri_find 查找各個優先級在 cpu 上的運行狀態,找到可以使用的 cpu。cpupri_vec 結構體有兩個成員,count 用來存儲各個優先級 task 在哪些 CPU 上屬於最高優先級 task,mask 則用來存儲當前優先級所在 CPU 上是最高優先級的的 cpumask。例如當前系統中 cpu0~5 上沒有全是 cfs task 在運行,那麼 normal task 的 count 值爲 6,mask 爲 0x3f。

圖片

__cpupri_find 的基本邏輯:

  1. 判斷當前優先級在哪些 CPU 上是最高優先級,如果沒有的話,說明當前系統要麼沒有此優先級 task,要麼是當前優先級 task 並非系統中各個 CPU 的最高優先級。

  2. 當前優先級 task 在有 cpu 是處於最高優先級,從這些 cpu 裏面去除掉 task not allowed cpu,再去除掉被 isolation 的 cpu,最後如果 lowest_mask 還有 CPU,則將 lowest_mask 作爲後面選核的基礎。

經過__cpupri_find 找到 lowest_mask 後,再從 lowest_mask 裏面過濾掉 capacity 無法滿足當前 task 的 cpu,最後剩下的就是可以用來運行當前 task 的 cpumask,如果沒有剩餘,則說明當前優先級的 task 所運行的 cpu 沒有滿足條件的,此時需要進入下一個循環,找到更高優先級的 task 運行的 cpu。

  1. 如果此次循環沒有找到合適的 cpu,會再進行一次循環,嘗試找到合適的 CPU。

至此,rt 選核的整體流程介紹完畢。

結語

本文主要從代碼的角度講述了 Linux rt 選核的主要流程,旨在讓讀者對於 rt 線程及其選核邏輯有一個初步的認識,利用 rt 線程的優點,可以解決當前系統中因爲調度延遲引起的一些性能問題。

rt 線程選核相對 cfs 線程而言要簡單一些,他不會考慮 energy 等因素,但系統中過多的 rt task 可能會帶來一定的功耗影響,同時由於 rt 主要是爲了解決重要 task 的調度延遲問題,如果系統中過多的 rt 線程可能導致一些重負載場景可能所有 CPU 都是 rt task,引發 rt 線程的調度延遲,從而導致更嚴重的性能問題,所以也不宜在系統中設置過多的 rt task。

引文:

[1] https://mirrors.edge.kernel.org/pub/linux/

[2] 深入理解 LINUX 內核(第三版)(美) 博韋, 西斯特 著, 陳莉君, 馮銳, 牛欣源 譯

[3] http://www.wowotech.net/process_management/process-priority.html Linux 調度器 - 進程優先級

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/DByOOnJYTA2BDrwTXSDBmQ