Linux Kernel 排程機制介紹

Linux Kernel 排程機制介紹

[email protected]

by loda.


Android/Linux Source Code Tags
App BizOrz 
BizOrz.COM 
BizOrz Blog


http://loda.hala01.com/oldarticles/

多核心架構儼然是目前智慧型手機方案的新趨勢,隨著省電與效能上的考量,多核心的架構各家方案也都有所差異.為能讓同一個Linux Kernel在不同效能的處理器上能即時運作,其中關鍵的部份便是核心的排程機制,也因此本文將以此為主題進行介紹,希望能對目前投入這領域的開發者有所助益.

前一段時間,看到Qualcomm關於Snapdragon S4產品的新聞,這產品最讓人印象深刻的部份在於它支援aSMP(Asynchronous Symmetrical Multi-Processing)的處理器架構,基於ARMv7 Dual/Quad-Core 的Krait多核心處理器架構,可讓個別處理器根據執行時期的負荷,動態調整時脈(根據晶片型號差異,時脈最高可達 2.5Ghz)與電壓的DCVS (Dynamic Clock and Voltage Scaling)技術. 每個aSMP的處理器都能獨立的調整電壓與時脈,並能夠關閉用不到的處理器,節省功耗. 由於可以根據需求個別調整主處理器的時脈,因此在aSMP架構下,當需要有類似Companion處理器的角色時,就可以把其中一個主處理器降頻,同時兼顧到省電與執行低算運需求的目的 (Qualcomm採用的是28nm,省電製程.). 也因此,就無需在主處理器之外,額外提供一個運算能力較低且低功耗的處理器,用來支援待機或是背景音樂播放的需求.

在同一則新聞中也提到NvidiaTegra3支援的Quad-Core與vSMP(Variable Symmetric Multiprocessing)架構.參考Nvidia的技術文件「Variable SMP – A Multi-Core CPU Architecture for Low Power and High Performance」,我們知道 Tegra3不同於前一代Tegra2是由兩個Cortex A9搭配一個低階ARM7的架構,以NVIDIA Kal -El的vSMP方案來說,會提供五個Cortex A9處理器(組合方式為4 個高效能Cortex A9主處理器 搭配 1個效能較低但強調省電的Cortex A9 Companion處理器),其中4個Cortex A9主處理器採用標準製程(時脈可到GHz.),以訴求較高的執行效率,而剩下一個Companion Cortex A9處理器(時脈最高為500MHz),會採用低功耗(Low Power)的製程,可用於運算量較低的待機或背景執行需求.

基於vSMP的架構,這五個處理器,可以根據執行的實際狀況,選擇主處理器或是Companion處理器運作,並透過DVFS(Dynamic Voltage and Frequency Scaling) 動態調整主處理器的頻率與電壓降低功耗,進行Power Gating與利用目前Linux Kernel所支援的CPU Up/Down HotPlug機制,讓4個主處理器可以動態的 CPU-Up/Down 掛起或移出Linux Kernel Tasks的排程範圍(ex,Task Migration),以便讓閒置的處理器關閉節省功耗.

參考Tegra的WhitePaper,vSMP並不允許Companion處理器與主處理器被同時啟用,也因此Tegra3有提供軟硬體配套的CPU Governor 與 CPU Management Logic 去監控處理器的執行負荷,能在系統繁忙時,關閉Companion CPU,把工作轉到4個主處理器上執行,並監控處理器執行負載,動態調整主時脈與哪個主處理器能被卸載. 也就是說,如果是處於待機或是背景音樂播放,系統就只開啟Companion 處理器,但若是需要上網或執行更繁重的工作,就會卸載Companion 處理器,然後根據需求開啟1個,2個或同時把4個主處理器致能. 基於這樣的設計,也能對主處理器的時脈進行調整,但不同於aSMP架構的是,這4個主處理器的時脈會調整為一樣的時脈,要拉高就一起拉高,要降低就一起降低,因此對Linux Kernel的排程來說,並不會有參與排程的處理器運算能力不一致的問題.

相對於上述提到的aSMP或vSMP架構,我們比較跟使用一個效率高的主處理器搭配一個省電且運算能力較弱的Companion處理器的差異,例如 ARM9/ARM11 +DSP 或是一個 Cortex A9 + ARM7,由於這兩個主處理器與Compaion處理器基礎架構不同,兩個處理器的Memory Management與L2 Cache並沒有共用,甚至兩者在指令集的層次就完全不同,例如ARM環境中運作的Linux Kernel配置好的記憶體分頁,或是多工環境下的Tasks,就不能直接透過排程分到DSP上運作.而是必須在設計階段,就考慮到這兩個處理器協同運作的配套機制,例如Linux Kernel,人機介面與檔案系統是運作在ARM端,而Audio Codec是放在DSP上執行,此時會由ARM去處理人機介面互動並透過檔案系統把音樂檔案載入,讓DSP端可以播放. 兩個處理器之間,需要設計彼此溝通的介面機制,隨著應用情境的多元化,在設計上的複雜度也會相對增加.

而aSMP與vSMP的優點在於上面的主處理器與Companion處理器採用同樣的核心技術,有一樣的指令集,共用同樣的Memory Management記憶體管理與L2 Cache共用. 以vSMP來說, Companion處理器跟主處理器有各自的L1 Cache,但共用同樣的L2 Cache與Memory Management,因此當Linux Kernel在上面運作時,包括4GB的記憶體分頁,應用程式的多工執行環境,就能在有開啟Linux Kernel CPU Up/Down HotPlug的機制下無縫銜接,且上面運作的Task能跨不同的處理器進行Task Migration,無需解決跨到不同處理器與映射到不同記憶體空間的複雜問題.

談到這,不論是aSMP或是vSMP都有其擁護者,並能充分發揮Linux Kernel CPU Up/Down HotPlug的特性,也都各自具備相當的發展潛力. 但要能讓這樣的設計充分發揮效益,還有一些在核心與排程機制上,需要優化的空間. 以目前Linux Kernel的Tasks排程機制實作來說,並沒有考慮到每個處理器具備不同運算能力的條件,就算是對處理器有Load Weight的計算,也是以該處理器所負荷的排程單元Load Weight來做統計,只能反映出哪個處理器目前排程單元的負荷,但並不能根據個別處理器的效能,而給予合理的排程負荷計算參數. 因此,如果有一個aSMP架構的處理器動態把時脈降低,而這處理器又跟其他運算能力較高的處理器一起進行Tasks排程,就有可能讓重要的Task被放到運算能力較低,且當下負荷又已經過重的處理器上執行,而導致在產品端的執行效果不如預期.

本文,會把焦點放在排程機制的介紹上,有關產品上的實作與技術問題,開發者可以根據自己所面對的平台與對於核心排程技術的了解,來進行設計. 本文主要參考的Linux核心有 2.6.10, 2.6.38.6 與 3.0.4,但由於Linux核心仍在持續的演進中,若你使用的版本跟筆者不同,還請以你手中的Linux Kernel Source Code為依據. 最後,所有的內容筆者會盡力確保資訊的正確性,若有不足之處,還請不吝指教.

Linux Kernel 的排程機制.

如下圖所示,在linux Kernel 2.4 時,多核心的架構下是共用同一個Task Queue,且採用O(N)排程算法,在排程效率與對多核心的支援是相對較弱的.


在linux Kernel 2.6 時,每個處理器都有兩個Task Queue,當位於Active Task Queue的行程執行完畢,就會依據行程的優先級,計算Priority與Time Slice,然後放到Expired Task Queue中,當Active Task Queue執行完畢後,就會Switch這兩個Task Queue,讓經過計算後的新Active Task Queue可以立刻加入排程.


此時Linux Kernel的核心排程採用的是 O(1)排程算法,我們可以參考Linux Kernel 2.6.10的原始碼,首先,依據O(1)的概念,Task Struct(如下所示)中的run_list會以Link List方式把同一個Task Priority 的行程串起來.

struct task_struct {

volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */

struct thread_info *thread_info;

atomic_t usage;

unsigned long flags;    /* per process flags, defined below */

unsigned long ptrace;

int lock_depth;         /* Lock depth */

int prio, static_prio;

struct list_head run_list;

…..

}

而結構list_head的宣告如下所示,

struct list_head {

struct list_head *next, *prev;

};

在每個Task被產生時,會透過巨集INIT_LIST_HEAD初始化Task的run_list,把Link List的頭尾 next/prev都指向自己,而在Task被排入執行時就會透過函式enqueue_task執行巨集list_add_tail把Task的Link List放到對應的Priority Link List 結尾,而在Task被移出執行時,就會透過函式dequeue_task執行巨集list_del,把Task的Link List從對應的Priority  Link List移出. 執行的概念如下圖所示,


對同一個Task Priority來說,被排定執行的Task就會透過 run_list Link List串連起來,依序執行,並且在移除執行時,從Link List Node中移出.

參考kernel/sched.c中的原始碼, Priority Array會根據目前140個Priority層級 (MAX_PRIO=140),幫每個Task Priority都配置一個Link List的 Queue Head,並且用unsigned long針對這140個Priority層級,透過Bits為0或1,來表示該Task Priority層級是否有需要被執行的Task在排程等待執行中,相關代碼如下所示

#define BITMAP_SIZE ((((MAX_PRIO+1+7)/8)+sizeof(long)-1)/sizeof(long))

struct prio_array {

unsigned int nr_active;

unsigned long bitmap[BITMAP_SIZE];

struct list_head queue[MAX_PRIO];

};

如下圖所示,以目前140bits的Task Priroty來說,會透過五個unsigned long,來表示完整的140bits.


執行時期,再透過巨集sched_find_first_bit (這部份會依據平台以處理器優化的指令集來處理),針對這140bits,找出第一個目前有Task在其中等待執行的Task Priority.

整體運作的概念,如下圖所示


雖然有了O(1)的實作,而且基於這實作,也確實可以很快讓排程機制很有效率的運作,然而處理器雖然是具備多工的能力,但真實的情況下,一個處理器同一時間只能執行一個Task,也就是說,在不考慮觸發中斷與重新觸發排程機制的情況下,在當前Task的Time Slice尚未執行完畢前,其他的Task是沒有機會被執行的. 也因此,在Linux Kernel 2.6.23之後,Linux核心採用了新的 CFS(Completely Fair Scheduler)排程機制(作者為Ingo Molnar),希望可以基於排程的改善,盡可能的讓每個不同等級的Task可以更公平的分配到處理器的時間,基於RB Tree的概念,可以讓越少被執行到的Task,有更高的優先機率被處理器執行到,避免優先級較低的Task,被延遲較久的時間才得到被處理器執行的機會.

參考Linux Kernel 3.0.4中有關CFS的Design文件 (路徑在Documentation/scheduler/sched-design-CFS.txt),作者試著用一句 "CFS basically models an "ideal, precise multi-tasking CPU" on real hardware." 來簡要說明CFS 80%設計精神 .所謂理想的多工處理器,就是當處理器有100%的能力,且系統中有兩個Tasks正在運作,這兩個Tasks可以各自取得處理器50%的執行能力,並被處理器平行執行,也就是說任意時間間隔來看(例如,隨機取10ms間隔),這兩個Tasks都是分配到處理器50%的執行能力.

以實際的處理器運作行為來說,一個處理器同一時間只能執行一個Task,當這個Task Time-Slice結束後,會透過Context-Switch切換下一個Task進來執行,當系統中的Tasks個數便多時,在特定的時間間隔來看,處理器等於執行特定Task的時間較長,而對於優先級較低的Task,卻可能在該區間中都沒有得到執行權. 換個角度來說,Tasks有優先級也有Time Slice長度的區別,假設Task #A Time Slice長度為10ms,以總Tasks數量與總Time Slice長度來說,會取得處理器5%的執行時間,最理想的狀況是,我們在任意一秒的間隔來看,Task#A都可以分到處理器5%的執行時間,但在現實的狀況來說,在任意一秒的間隔來看,只要有優先級高的Task被排程到,且該Task分配到的Time Slice較長,就會讓Task#A呈現在一個不公平的時間分配中. 雖然,用較長的時間來看一定是公平的,但CFS希望解決的就是讓目前處理器執行的機制上,可以盡可能的做到即時的公平.

因此,CFS導入了"Virtual Runtime"的概念(單位為nanosec),根據目前總共的Tasks數量,用以作為選擇下一個執行Task Time Slice的依據.例如,CFS會選擇目前p->se.vruntime值最低的Task來執行(也就是距離上一次被執行時間間隔最久的Task),會在每一次觸動排程機制時,盡可能的達到"理想的多工處理器"的目標.

然而,在實際Linux Kernel的Time Slice與Priority來看,CFS的概念還必須要考慮到依據每個Task的Nice Value所計算的Time Slice長度不一定每個Task都一切,而且每個Task的Real-Time Priority值也不盡相同,有關Task 計算Time Slice與Real-Time Priority的說明,會另外在下一段文章中介紹.

每次Scheduler Tick觸發時,所進行的動作

不論是O(N),O(1)與CFS,系統的排程都是基於固定的Scheduling Tick來觸發,也因此,每一次的Scheduling Tick觸發,都攸關Task執行的週期與是否要進行Task的切換,也是排程機制核心的部分.

Linux Kernel的Scheduling機制,會根據編譯核心時配置的HZ值來決定,一般來說是每1/100秒或每1/1000秒觸發一次Scheduling Tick,讓系統的排程機制可以去計算每個Task執行的時間,並藉此決定是不是要進行Task Context-Switch.

以支援CFS的Linux Kernel 2.6.38.6來說,每次觸發Scheduling Tick都會執行下述的動作

1,取得目前的CPU ID,與目前CPU的 RunQueue指標,與目前RunQueue中正在執行的Curr Task指標

2,呼叫 sched_clock_tick (實作在kernel/sched_clock.c中),這函式需在有設定Unstable CPU Clock時才有作用,如果全域變數sched_clock_stable為1,這個函式就會不作用直接返回.(ARM環境中,這個變數並無作用.)

3,取得 RunQueue SpinLock

4,呼叫update_rq_clock (實作在kernel/sched.c中), 若runQueue沒有設定skip_clock_update,這函式會更新RunQueue的Clock值.並呼叫函式update_rq_clock_task, 更新RunQueue clock_task值,clock_task會把前後兩次RunQueue Clock的Delta值,減去處理器進行Soft-IRQ與Hardware-IRQ的時間差值後,對clock_task進行累加. 可用以反應出RunQueue中Task實際被處理器執行的時間累加值.

5,呼叫update_cpu_load_active,會透過函式update_cpu_load在每次Tick觸發時,更新RunQueue cpu_load Array,用以反應目前RunQueue所在的處理器負載.

6,執行目前Task 所在Class的Tick函式(ex, curr->sched_class->task_tick),若Current Task是屬於Fair Class,就會呼叫task_tick_fair (實作在kernel/sched_fair.c中). 依序若Current Task為Stop/Real-Time/Idle Scheduling Class就會呼叫函式 task_tick_stop/ task_tick_rt/ task_tick_idle.

7,釋放RunQueue SpinLock

8,呼叫perf_event_task_tick (若編譯時,沒設定CONFIG_PERF_EVENTS,這函式會是inline的空函式),主要用以支援核心處理器效能的監聽動作.

9,在SMP架構下,會呼叫函式idle_cpu,用以更新RunQueue的idle_at_tick值,為1表示目前CPU RunQueue執行的是Idle Task,反之為0,則非Idle Task. 可供判斷目前這RunQueue所在處理器是否處於閒置的狀態.

10,在SMP架構下,會呼叫trigger_load_balance, 若已達到RunQueue設定下一次觸發Load Balance的jiffies時間值(next_balance),就會觸發Scheduling Softare IRQ,並在Software IRQ中透過函式rebalance_domains,執行Scheduling Domain Load Balance動作.若未達RunQueue下一次觸發Load Balance的jiffies時間值,且在編譯核心時有設定CONFIG_NO_HZ (NO HZ可支援處理器執行Idle Task時,停止系統的Scheduling Tick,可在進入pm_idle時,減少Timer中斷觸發(例如HZ=100,每秒就有一百次Tick Timer中斷觸發),減少系統被觸發醒來的機會.),就會呼叫函式nohz_balancer_kick,Kick一個處理器進行No Hz Load Balance.被Kick選擇為進行No Hz Load Balance的處理器所屬RunQueue的nohz_balance_kick值會設定為1.

以上總結每次觸發Scheduling Tick時,主要進行的工作內容.接下來我們再透過pick_next_task,看在目前系統支援四種Scheduling Class的排程下,一個Task是如何被撿選出來執行的.

從pick_next_task看Scheduling Class對排程的影響

檢視CFS對不同Scheduling Class行為最好的入口就是函式pick_next_task (實作在 kernel/sched.c中),在函式pick_next_task中會執行如下的流程找到下一個要被載入執行的Task

1,檢查目前總Runnable Task數量是否跟放在Fair Class中的數量一致 (rq->nr_running == rq->cfs.nr_running),若成立,就執行執行Fair Scheduling Class的pick_next_task抓取下一個執行的Task

2,若1不成立,就會執行 for_each_class(class) {….}迴圈,可以參考如下define宣告

#define sched_class_highest (&stop_sched_class)

#define for_each_class(class) \

for (class = sched_class_highest; class; class = class->next)

目前優先級最高的Scheduling Class為 Stop-Task Scheduling Class,而在函式pick_next_task中,就會依序執行 Stop-Task, Real-Time,Fair 與 Idle-Task Scheduling Class所實作的pick_next_task函式,先找到的Task就會由函式pick_next_task回傳. 也因此,當有Task存在 Stop-Task或是Real-Time Scheduling Class中時,這時屬於Fair Scheduling Class中的Task執行的優先級就會降低.

總結運作的概念,可以參考下圖所示


CSF RBTREE(Red-Black Tree) – O(Log2(N))

CFS挑選Task的機制為RB-Tree,概念如下圖所示,包括Task或是Task Group,都會是RBTree中的一個Node支點,每次選擇Task來執行時,都會選擇左邊目前Virtual RunTime最低的Task(也就是最久沒有被執行到的Task)來執行,雖然Virtual RunTime會根據Task的Priority而有增長的差異,例如:Nice Priority低的Task Virutal RunTime會增長的比較快,相對佔據處理器執行的時間就會比較短,反之,Nice Priority 高的Task會因為Virutal RunTime增長的慢,而得到相對比較多實際的處理器執行時間. 但在理想的情況下,如果大家都是Nice 0的情況,基於CFS RBTree的機制,可以讓多數的Task能相對均勻的得到處理器的執行時間,而避免有特定的Task等待比較久的時間沒有得到處理器的執行機會.


對CFS與RBTree建立基本的概念後,接下來讓我們進一步的探討處理器排程RunQueue的資料結構,更清楚的掌握排程運作的內涵.

處理器的RunQueue

在目前的Linux Kernel中,每個處理器都會配置一個RunQueue,而從了解RunQueue的資料結構著手,我們能對排程的運作有更清楚的藍圖,進而在程式碼的閱讀上,可以更加清晰. 在本節,筆者會把RunQueue的資料結構struct rq (宣告在 kernel/sched.c中)進行介紹,讓各位可以知道核心排程是基於哪些參數進行運作,這參數的內容比較繁瑣,建議可以先有個概念,若有興趣進一步探究的,再回到本節即可.

如下所示,處理器RunQueue主要包含以下的參數與對應的說明

參數名稱 說明
raw_spinlock_t lock 用以作為RunQueue的SpinLock
unsigned long nr_running 用以記錄目前處理器RunQueue中執行的Task數量
unsigned long cpu_load[] cpu_load主要用以表示處理器的負載,目前這個Array大小為5 (定義在CPU_LOAD_IDX_MAX),在每個處理器的RunQueue中都會有對應到該處理器的cpu_load參數配置,在每次處理器觸發Scheduler Tick時, 都會呼叫函式update_cpu_load_active,進行cpu_load的更新.

在系統初始化時,會呼叫函式sched_init把RunQueue的cpu_load Array初始化為0.

了解cpu_load Array更新最好的方式應該是透過函式update_cpu_load (實作在kernel/sched.c),基於這函式的實作,我們可以把cpu_load Array每個數值公式說明如下.

cpu_load[0]會直接等於RunQueue中load.weight的值.

cpu_load[1]=(cpu_load[1]*(2-1)+cpu_load[0])/2

cpu_load[2]=(cpu_load[2]*(4-1)+cpu_load[0])/4

cpu_load[3]=(cpu_load[3]*(8-1)+cpu_load[0])/8

cpu_load[4]=(cpu_load[4]*(16-1)+cpu_load[0])/16

當呼叫函式this_cpu_load時,所傳回的 CPU Load值是cpu_load[0] (也就是會等於RunQueue中的load.weight).而在進行CPU Blance或Migration時,就會呼叫函式source_load與target_load取得對應處理器與對應cpu_load index值,來進行計算.

unsigned long last_load_update_tick 用以記錄最後一次更新CPU Load的時間值. (會把當時的jiffies值儲存到這變數).
ifdef CONFIG_NO_HZ
u64 nohz_stamp 在linux Kerel 2.6.38.6中,這個參數沒有作用.
unsigned char nohz_balance_kick 初始值為0,用在當NO Hz機制有開啟時的CFS Fair Scheduling的Load Balance機制.可參考kernel/sched_fair.c 中的函式trigger_load_balance實作.

在SMP架構下,且有設置NO HZ時,會在每次Scheduling Tick觸發時,由函式scheduler_tick(in kernel/sched.c)呼叫trigger_load_balance,再呼叫函式nohz_balancer_kick,Kick一個處理器進行No Hz Load Balance.而被選擇進行No Hz Load Balance工作處理器的RunQueue中的nohz_balance_kick數值會設定為1.

unsigned int skip_clock_update 如果skip_clock_update的值被設定為1,此時透過函式update_rq_clock要進行RunQueue Clock值的更新時,就會直接返回.(每次呼叫update_rq_clock函式,都會讓RunQueue中的Clock值等於sched_clock_cpu的返回值.
struct load_weight load 參考include/linux/sched.h中struct load_weight的宣告如下

struct load_weight {

unsigned long weight, inv_weight;

};

RunQueue中的load->weight值,會是目前所執行的schedule entity的load->weight的總和,也就是說,RunQueue的load->weight越高,也表示所負責的排程單元load->weight總合越高,表示處理器所負荷的執行單元也越重.

unsigned long nr_load_updates 在每次Scheduler Tick中呼叫update_cpu_load時,這個值都會加一,可以用來反應目前CPU Load更新的次數.
u64 nr_switches 用來累加處理器進行Context Switch的次數,會在函式schedule呼叫時進行累加,並可以透過函式nr_context_switches統計目前所有處理器總共的Context Switch次數. 或是可以透過查看檔案/proc/stat中的ctxt欄位得知目前整個系統觸發Context Switch的總數.
struct cfs_rq cfs 為CFS Fair Scheduling Class的 RunQueue
struct rt_rq rt 為Real-Time Scheduling Class的RunQueue
ifdef CONFIG_FAIR_GROUP_SCHED (用以支援可以Group CFS Tasks的機制.)
struct list_head leaf_cfs_rq_list 在有設置Fair Group Scheduling的環境下,會基於原本CFS RunQueue與包含有若干Task的Group所成的排程集合,也就是說當有一個Group A,其下有20個Tasks,而這Group A就會有自己的CFS RunQueue用來排程自己所屬的Tasks,而屬於這Group A的Tasks所使用到的處理器時間,就會以這Group A總體所分得的時間為上限. 基於 Cgroup的 Fair Group Scheduling架構,可以創造出有階層性的Tasks組織,根據不同Tasks的功能群組化,在配置給該群組對應的處理器資源,讓屬於該群組下的Tasks可以透過RunQueue機制排程,使用屬於該群組所配置到的資源.

這個變數主要是用來管理CFS RunQueue List,操作上可以透過函式list_add_leaf_cfs_rq把一個Group CFS RunQueue加入到List中,或透過函式list_del_leaf_cfs_rq把一個Group CFS RunQueue移除. 並可透過for_each_leaf_cfs_rq把一個 RunQueue上的所有leaf cfs_rq走過一遍.

ifdef CONFIG_RT_GROUP_SCHED  (用以支援可以Group Real-Time Tasks的機制.
struct list_head leaf_rt_rq_list 類似於leaf_cfs_rq_list所扮演的角色,只是這是針對屬於Real-Time的Task,在實際操作上可以透過函式list_add_leaf_rt_rq,list_del_leaf_rt_rq或巨集for_each_leaf_rt_rq.
unsigned long nr_uninterruptible 一般來說,Linux Kernel的Task狀態可以為TASK_RUNNING, TASK_INTERRUPTIBLE (Sleep) , TASK_UNINTERRUPTIBLE (Deactivate Task,此時Task會從RunQueue移除)或TASK_STOPPED. (在此先忽略Trace,Zombie,Dead..etc). 透過這個變數會統計目前RunQueue中有多少Task屬於TASK_UNINTERRUPTIBLE的狀態.當呼叫函式activate_task時,會把nr_uninterruptible值減一,並透過函式enqueue_task把對應的Task依據所在的Scheduling Class放到對應的RunQueue中,並把目前RunQueue的nr_running值加一.
struct task_struct *curr, *idle, *stop 為指向三個Task的指標. (Current Task,Idle Task與最高執行級的Stop Task).

curr : 指向目前處理器正在執行的Task

idle: 指向屬於Idle-Task Scheduling Class的Idle Task.

stop: 指向目前最高等級屬於 Stop-Task Scheduling Class的Task

unsigned long next_balance 基於處理器的jiffies值,用以記錄下次進行處理器Balancing的時間點.
struct mm_struct *prev_mm 用以儲存Context-Switch發生時,前一個Task的Memory Management結構,並可用在函式finish_task_switch中,透過函式mmdrop釋放前一個Task的記憶體資源.
u64 clock 用以記錄目前RunQueue的Clock值,基本上該值會等於透過sched_clock_cpu(cpu_of(rq))的回傳值,並會在每次呼叫scheduler_tick時透過函式update_rq_clock更新目前RunQueue clock值.

在實作部分,函式sched_clock_cpu會透過sched_clock_local或ched_clock_remote取得對應的sched_clock_data,而處理器的sched_clock_data值,會透過函式sched_clock_tick在每次呼叫scheduler_tick時進行更新.

補充….

從函式update_rq_clock的實作來說,其實

delta = sched_clock_cpu(cpu_of(rq)) – rq->clock;

rq->clock += delta;

可以直接寫成

rq->clock= sched_clock_cpu(cpu_of(rq));

u64 clock_task 這個值會等於從上一次更新RunQueue Clock時,到這次要更新RunQueue Clock時的時間間隔 Delta 減去在這段時間中,處理器用來進行Soft-IRQ與Hardware-IRQ的時間差值的累加.

簡要來說,這個值會透過函式update_rq_clock_task進行更新,用來反應出RunQueue中Task實際執行時所佔用到的處理器時間Clock累加值.

atomic_t nr_iowait 用以記錄目前RunQueue中有多少Task處於等待I/O的Sleep狀態.

在實際的使用上,例如當Driver接收來自Task的調用,但處於等待I/O回覆的階段時,為了充分利用處理器的執行資源,這時就可以在Driver中呼叫函式io_schedule,此時就會把目前RunQueue中的nr_iowait加一,並設定目前Task的in_iowait為1,然後觸發Scheduling讓其他Task有機會可以得到處理器執行時間.

ifdef CONFIG_SMP
struct root_domain *rd Root Domain是基於多核心架構下的機制,會由RunQueue結構記住目前採用的R
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值