国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
linux調(diào)度器源碼研究1/4--概述
  2015 

引言

    調(diào)度器作為操作系統(tǒng)的核心部件,具有非常重要的意義,其隨著linux內(nèi)核的更新也不斷進(jìn)行著更新。本系列文章通過(guò)linux-3.18.3源碼進(jìn)行調(diào)度器的學(xué)習(xí)和分析,一步一步將linux現(xiàn)有的調(diào)度器原原本本的展現(xiàn)出來(lái)。此篇文章作為開篇,主要介紹調(diào)度器的原理及重要數(shù)據(jù)結(jié)構(gòu)。

調(diào)度器介紹

    隨著時(shí)代的發(fā)展,linux也從其初始版本穩(wěn)步發(fā)展到今天,從2.4的非搶占內(nèi)核發(fā)展到今天的可搶占內(nèi)核,調(diào)度器無(wú)論從代碼結(jié)構(gòu)還是設(shè)計(jì)思想上也都發(fā)生了翻天覆地的變化,其普通進(jìn)程的調(diào)度算法也從O(1)到現(xiàn)在的CFS,一個(gè)好的調(diào)度算法應(yīng)當(dāng)考慮以下幾個(gè)方面:
  • 公平:保證每個(gè)進(jìn)程得到合理的CPU時(shí)間。
  • 高效:使CPU保持忙碌狀態(tài),即總是有進(jìn)程在CPU上運(yùn)行。
  • 響應(yīng)時(shí)間:使交互用戶的響應(yīng)時(shí)間盡可能短。
  • 周轉(zhuǎn)時(shí)間:使批處理用戶等待輸出的時(shí)間盡可能短。
  • 吞吐量:使單位時(shí)間內(nèi)處理的進(jìn)程數(shù)量盡可能多。
  • 負(fù)載均衡:在多核多處理器系統(tǒng)中提供更高的性能
   而整個(gè)調(diào)度系統(tǒng)至少包含兩種調(diào)度算法,是分別針對(duì)實(shí)時(shí)進(jìn)程普通進(jìn)程,所以在整個(gè)linux內(nèi)核中,實(shí)時(shí)進(jìn)程和普通進(jìn)程是并存的,但它們使用的調(diào)度算法并不相同,普通進(jìn)程使用的是CFS調(diào)度算法(紅黑樹調(diào)度)。之后會(huì)介紹調(diào)度器是怎么調(diào)度這兩種進(jìn)程。

進(jìn)程

    上一節(jié)已經(jīng)說(shuō)明,在linux中,進(jìn)程主要分為兩種,一種為實(shí)時(shí)進(jìn)程,一種為普通進(jìn)程
  • 實(shí)時(shí)進(jìn)程:對(duì)系統(tǒng)的響應(yīng)時(shí)間要求很高,它們需要短的響應(yīng)時(shí)間,并且這個(gè)時(shí)間的變化非常小,典型的實(shí)時(shí)進(jìn)程有音樂(lè)播放器,視頻播放器等。
  • 普通進(jìn)程:包括交互進(jìn)程和非交互進(jìn)程,交互進(jìn)程如文本編輯器,它會(huì)不斷的休眠,又不斷地通過(guò)鼠標(biāo)鍵盤進(jìn)行喚醒,而非交互進(jìn)程就如后臺(tái)維護(hù)進(jìn)程,他們對(duì)IO,響應(yīng)時(shí)間沒(méi)有很高的要求,比如編譯器。
    它們?cè)趌inux內(nèi)核運(yùn)行時(shí)是共存的,實(shí)時(shí)進(jìn)程的優(yōu)先級(jí)為0~99,實(shí)時(shí)進(jìn)程優(yōu)先級(jí)不會(huì)在運(yùn)行期間改變(靜態(tài)優(yōu)先級(jí)),而普通進(jìn)程的優(yōu)先級(jí)為100~139,普通進(jìn)程的優(yōu)先級(jí)會(huì)在內(nèi)核運(yùn)行期間進(jìn)行相應(yīng)的改變(動(dòng)態(tài)優(yōu)先級(jí))。
    

調(diào)度策略

    在linux系統(tǒng)中,調(diào)度策略分為
  • SCHED_NORMAL:普通進(jìn)程使用的調(diào)度策略,現(xiàn)在此調(diào)度策略使用的是CFS調(diào)度器。
  • SCHED_FIFO:實(shí)時(shí)進(jìn)程使用的調(diào)度策略,此調(diào)度策略的進(jìn)程一旦使用CPU則一直運(yùn)行,直到有比其更高優(yōu)先級(jí)的實(shí)時(shí)進(jìn)程進(jìn)入隊(duì)列,或者其自動(dòng)放棄CPU,適用于時(shí)間性要求比較高,但每次運(yùn)行時(shí)間比較短的進(jìn)程。
  • SCHED_RR:實(shí)時(shí)進(jìn)程使用的時(shí)間片輪轉(zhuǎn)法策略,實(shí)時(shí)進(jìn)程的時(shí)間片用完后,調(diào)度器將其放到隊(duì)列末尾,這樣每個(gè)實(shí)時(shí)進(jìn)程都可以執(zhí)行一段時(shí)間。適用于每次運(yùn)行時(shí)間比較長(zhǎng)的實(shí)時(shí)進(jìn)程。

調(diào)度

    首先,我們需要清楚,什么樣的進(jìn)程會(huì)進(jìn)入調(diào)度器進(jìn)行選擇,就是處于TASK_RUNNING狀態(tài)的進(jìn)程,而其他狀態(tài)下的進(jìn)程都不會(huì)進(jìn)入調(diào)度器進(jìn)行調(diào)度。系統(tǒng)發(fā)生調(diào)度的時(shí)機(jī)如下
  • 調(diào)用cond_resched()時(shí)
  • 顯式調(diào)用schedule()時(shí)
  • 從系統(tǒng)調(diào)用或者異常中斷返回用戶空間時(shí)
  • 從中斷上下文返回用戶空間時(shí)
    當(dāng)開啟內(nèi)核搶占(默認(rèn)開啟)時(shí),會(huì)多出幾個(gè)調(diào)度時(shí)機(jī),如下
  • 在系統(tǒng)調(diào)用或者異常中斷上下文中調(diào)用preempt_enable()時(shí)(多次調(diào)用preempt_enable()時(shí),系統(tǒng)只會(huì)在最后一次調(diào)用時(shí)會(huì)調(diào)度)
  • 在中斷上下文中,從中斷處理函數(shù)返回到可搶占的上下文時(shí)(這里是中斷下半部,中斷上半部實(shí)際上會(huì)關(guān)中斷,而新的中斷只會(huì)被登記,由于上半部處理很快,上半部處理完成后才會(huì)執(zhí)行新的中斷信號(hào),這樣就形成了中斷可重入)
    而在系統(tǒng)啟動(dòng)調(diào)度器初始化時(shí)會(huì)初始化一個(gè)調(diào)度定時(shí)器,調(diào)度定時(shí)器每隔一定時(shí)間執(zhí)行一個(gè)中斷,在中斷會(huì)對(duì)當(dāng)前運(yùn)行進(jìn)程運(yùn)行時(shí)間進(jìn)行更新,如果進(jìn)程需要被調(diào)度,在調(diào)度定時(shí)器中斷中會(huì)設(shè)置一個(gè)調(diào)度標(biāo)志位,之后從定時(shí)器中斷返回,因?yàn)樯厦嬉呀?jīng)提到從中斷上下文返回時(shí)是有調(diào)度時(shí)機(jī)的,在內(nèi)核源碼的匯編代碼中所有中斷返回處理都必須去判斷調(diào)度標(biāo)志位是否設(shè)置,如設(shè)置則執(zhí)行schedule()進(jìn)行調(diào)度。而我們知道實(shí)時(shí)進(jìn)程和普通進(jìn)程是共存的,調(diào)度器是怎么協(xié)調(diào)它們之間的調(diào)度的呢,其實(shí)很簡(jiǎn)單,每次調(diào)度時(shí),會(huì)先在實(shí)時(shí)進(jìn)程運(yùn)行隊(duì)列中查看是否有可運(yùn)行的實(shí)時(shí)進(jìn)程,如果沒(méi)有,再去普通進(jìn)程運(yùn)行隊(duì)列找下一個(gè)可運(yùn)行的普通進(jìn)程,如果也沒(méi)有,則調(diào)度器會(huì)使用idle進(jìn)程進(jìn)行運(yùn)行。之后的章節(jié)會(huì)放上代碼進(jìn)行詳細(xì)說(shuō)明。

數(shù)據(jù)結(jié)構(gòu)

    在這一節(jié)中,我們都是以普通進(jìn)程作為講解對(duì)象,因?yàn)槠胀ㄟM(jìn)程使用的調(diào)度算法為CFS調(diào)度算法,它是以紅黑樹為基礎(chǔ)的調(diào)度算法,其相比與實(shí)時(shí)進(jìn)程的調(diào)度算法復(fù)雜很多,而實(shí)時(shí)進(jìn)程在組織結(jié)構(gòu)上與普通進(jìn)程沒(méi)有太大差別,算法也較為簡(jiǎn)單。

組成形式

    圖1

    從圖1 中可以看出來(lái),每個(gè)CPU對(duì)應(yīng)包含一個(gè)運(yùn)行隊(duì)列結(jié)構(gòu)(struct rq),而每個(gè)運(yùn)行隊(duì)列又包含有其自己的實(shí)時(shí)進(jìn)程運(yùn)行隊(duì)列(struct rt_rq)、普通進(jìn)程運(yùn)行隊(duì)列(struct cfs_rq)、和一個(gè)我自己也不太知道有什么用的dl隊(duì)列(struct dl_rq),也就是說(shuō)每個(gè)CPU都有他們自己的實(shí)時(shí)進(jìn)程運(yùn)行隊(duì)列及普通進(jìn)程運(yùn)行隊(duì)列,為了方便,我們?cè)趫D中只描述普通進(jìn)程的組織結(jié)構(gòu)(最復(fù)雜的也是普通進(jìn)程的組織結(jié)構(gòu)),而紅色se則為當(dāng)前CPU上正在執(zhí)行的程序,藍(lán)色為下個(gè)將要執(zhí)行的程序,其實(shí)圖中并不規(guī)范,實(shí)際上當(dāng)進(jìn)程運(yùn)行時(shí),會(huì)從紅黑樹中剝離出來(lái),然后設(shè)定下一個(gè)調(diào)度進(jìn)程,當(dāng)進(jìn)程運(yùn)行時(shí)間結(jié)束時(shí),再重新放入紅黑樹中。而為什么CPU0上有兩個(gè)藍(lán)色將被調(diào)度進(jìn)程,將在組調(diào)度中解釋。而為什么紅黑樹中又有一個(gè)子紅黑樹,我們將在調(diào)度實(shí)體中解釋。

組調(diào)度(struct task_group)

    我們知道,linux是一個(gè)多用戶系統(tǒng),如果有兩個(gè)進(jìn)程分別屬于兩個(gè)用戶,而進(jìn)程的優(yōu)先級(jí)不同,會(huì)導(dǎo)致兩個(gè)用戶所占用的CPU時(shí)間不同,這樣顯然是不公平的(如果優(yōu)先級(jí)差距很大,低優(yōu)先級(jí)進(jìn)程所屬用戶使用CPU的時(shí)間就很小),所以內(nèi)核引入組調(diào)度。如果基于用戶分組,即使進(jìn)程優(yōu)先級(jí)不同,這兩個(gè)用戶使用的CPU時(shí)間都為50%。這就是為什么圖1 中CPU0有兩個(gè)藍(lán)色將被調(diào)度的程序,如果task_group1中的運(yùn)行時(shí)間還沒(méi)有使用完,而當(dāng)前進(jìn)程運(yùn)行時(shí)間使用完后,會(huì)調(diào)度task_group1中的下一個(gè)被調(diào)度進(jìn)程;相反,如果task_group1的運(yùn)行時(shí)間使用結(jié)束,則調(diào)用上一層的下一個(gè)被調(diào)度進(jìn)程。
    linux可以以以下兩種方式進(jìn)行進(jìn)程的分組:
  • 用戶ID:按照進(jìn)程的USER ID進(jìn)行分組,在對(duì)應(yīng)的/sys/kernel/uid/目錄下會(huì)生成一個(gè)cpu.share的文件,可以通過(guò)配置該文件來(lái)配置用戶所占CPU時(shí)間比例。
  • cgourp(control group):生成組用于限制其所有進(jìn)程,比如我生成一個(gè)組(生成后此組為空,里面沒(méi)有進(jìn)程),設(shè)置其CPU使用率為10%,并把一個(gè)進(jìn)程丟進(jìn)這個(gè)組中,那么這個(gè)進(jìn)程最多只能使用CPU的10%,如果我們將多個(gè)進(jìn)程丟進(jìn)這個(gè)組,這個(gè)組的所有進(jìn)程平分這個(gè)10%。
    注意的是,這里的進(jìn)程組概念和fork調(diào)用所產(chǎn)生的父子進(jìn)程組概念不一樣,文章所使用的進(jìn)程組概念全為組調(diào)度中進(jìn)程組的概念。為了管理組調(diào)度,內(nèi)核引進(jìn)了struct task_group結(jié)構(gòu),如下:

  1. /* 進(jìn)程組,用于實(shí)現(xiàn)組調(diào)度 */
  2. struct task_group {
  3.     /* 用于進(jìn)程找到其所屬進(jìn)程組結(jié)構(gòu) */
  4.     struct cgroup_subsys_state css;

  5. #ifdef CONFIG_FAIR_GROUP_SCHED
  6.     /* CFS調(diào)度器的進(jìn)程組變量,在 alloc_fair_sched_group() 中進(jìn)程初始化及分配內(nèi)存 */
  7.     /* 該進(jìn)程組在每個(gè)CPU上都有對(duì)應(yīng)的一個(gè)調(diào)度實(shí)體,因?yàn)橛锌赡艽诉M(jìn)程組同時(shí)在兩個(gè)CPU上運(yùn)行(它的A進(jìn)程在CPU0上運(yùn)行,B進(jìn)程在CPU1上運(yùn)行) */
  8.     struct sched_entity **se;
  9.     /* 進(jìn)程組在每個(gè)CPU上都有一個(gè)CFS運(yùn)行隊(duì)列(為什么需要,稍后解釋) */
  10.     struct cfs_rq **cfs_rq;
  11.     /* 用于保存優(yōu)先級(jí)默認(rèn)為NICE 0的優(yōu)先級(jí) */
  12.     unsigned long shares;

  13. #ifdef    CONFIG_SMP
  14.     atomic_long_t load_avg;
  15.     atomic_t runnable_avg;
  16. #endif
  17. #endif

  18. #ifdef CONFIG_RT_GROUP_SCHED
  19.     /* 實(shí)時(shí)進(jìn)程調(diào)度器的進(jìn)程組變量,同 CFS */
  20.     struct sched_rt_entity **rt_se;
  21.     struct rt_rq **rt_rq;

  22.     struct rt_bandwidth rt_bandwidth;
  23. #endif

  24.     struct rcu_head rcu;
  25.     /* 用于建立進(jìn)程鏈表(屬于此調(diào)度組的進(jìn)程鏈表) */
  26.     struct list_head list;

  27.     /* 指向其上層的進(jìn)程組,每一層的進(jìn)程組都是它上一層進(jìn)程組的運(yùn)行隊(duì)列的一個(gè)調(diào)度實(shí)體,在同一層中,進(jìn)程組和進(jìn)程被同等對(duì)待 */
  28.     struct task_group *parent;
  29.     /* 進(jìn)程組的兄弟結(jié)點(diǎn)鏈表 */
  30.     struct list_head siblings;
  31.     /* 進(jìn)程組的兒子結(jié)點(diǎn)鏈表 */
  32.     struct list_head children;

  33. #ifdef CONFIG_SCHED_AUTOGROUP
  34.     struct autogroup *autogroup;
  35. #endif

  36.     struct cfs_bandwidth cfs_bandwidth;
  37. };
    在struct task_group結(jié)構(gòu)中,最重要的成員為 struct sched_entity ** se 和 struct cfs_rq ** cfs_rq。在圖1 中,root_task_group與task_group1都只有一個(gè),它們?cè)诔跏蓟瘯r(shí)會(huì)根據(jù)CPU個(gè)數(shù)為se和cfs_rq分配空間,即在task_group1和root_task_group中會(huì)為每個(gè)CPU分配一個(gè)se和cfs_rq,同理用于實(shí)時(shí)進(jìn)程的 struct sched_rt_entity ** rt_se 和 struct rt_rq ** rt_rq也是一樣。為什么這樣呢,原因就是在多核多CPU的情況下,同一進(jìn)程組的進(jìn)程有可能在不同CPU上同時(shí)運(yùn)行,所以每個(gè)進(jìn)程組都必須對(duì)每個(gè)CPU分配它的調(diào)度實(shí)體(struct sched_entity 和 struct sched_rt_entity)和運(yùn)行隊(duì)列(struct cfs_rq 和 struct rt_rq)。

調(diào)度實(shí)體(struct sched_entity)

    在組調(diào)度中,也涉及到調(diào)度實(shí)體這個(gè)概念,它的結(jié)構(gòu)為struct sched_entity(簡(jiǎn)稱se),就是圖1 紅黑樹中的se。其實(shí)際上就代表了一個(gè)調(diào)度對(duì)象,可以為一個(gè)進(jìn)程,也可以為一個(gè)進(jìn)程組。對(duì)于根的紅黑樹而言,一個(gè)進(jìn)程組就相當(dāng)于一個(gè)調(diào)度實(shí)體,一個(gè)進(jìn)程也相當(dāng)于一個(gè)調(diào)度實(shí)體。我們可以先看看其結(jié)構(gòu),如下

  1. /* 一個(gè)調(diào)度實(shí)體(紅黑樹的一個(gè)結(jié)點(diǎn)),其包含一組或一個(gè)指定的進(jìn)程,包含一個(gè)自己的運(yùn)行隊(duì)列,一個(gè)父親指針,一個(gè)指向需要調(diào)度的運(yùn)行隊(duì)列指針 */
  2. struct sched_entity {
  3.     /* 權(quán)重,在數(shù)組prio_to_weight[]包含優(yōu)先級(jí)轉(zhuǎn)權(quán)重的數(shù)值 */
  4.     struct load_weight    load;        /* for load-balancing */
  5.     /* 實(shí)體在紅黑樹對(duì)應(yīng)的結(jié)點(diǎn)信息 */
  6.     struct rb_node        run_node;    
  7.     /* 實(shí)體所在的進(jìn)程組 */
  8.     struct list_head    group_node;
  9.     /* 實(shí)體是否處于紅黑樹運(yùn)行隊(duì)列中 */
  10.     unsigned int        on_rq;

  11.     /* 開始運(yùn)行時(shí)間 */
  12.     u64            exec_start;
  13.     /* 總運(yùn)行時(shí)間 */
  14.     u64            sum_exec_runtime;
  15.     /* 虛擬運(yùn)行時(shí)間,在時(shí)間中斷或者任務(wù)狀態(tài)發(fā)生改變時(shí)會(huì)更新
  16.      * 其會(huì)不停增長(zhǎng),增長(zhǎng)速度與load權(quán)重成反比,load越高,增長(zhǎng)速度越慢,就越可能處于紅黑樹最左邊被調(diào)度
  17.      * 每次時(shí)鐘中斷都會(huì)修改其值
  18.      * 具體見(jiàn)calc_delta_fair()函數(shù)
  19.      */
  20.     u64            vruntime;
  21.     /* 進(jìn)程在切換進(jìn)CPU時(shí)的sum_exec_runtime值 */
  22.     u64            prev_sum_exec_runtime;

  23.     /* 此調(diào)度實(shí)體中進(jìn)程移到其他CPU組的數(shù)量 */
  24.     u64            nr_migrations;

  25. #ifdef CONFIG_SCHEDSTATS
  26.     /* 用于統(tǒng)計(jì)一些數(shù)據(jù) */
  27.     struct sched_statistics statistics;
  28. #endif

  29. #ifdef CONFIG_FAIR_GROUP_SCHED
  30.     /* 代表此進(jìn)程組的深度,每個(gè)進(jìn)程組都比其parent調(diào)度組深度大1 */
  31.     int            depth;
  32.     /* 父親調(diào)度實(shí)體指針,如果是進(jìn)程則指向其運(yùn)行隊(duì)列的調(diào)度實(shí)體,如果是進(jìn)程組則指向其上一個(gè)進(jìn)程組的調(diào)度實(shí)體
  33.      * 在 set_task_rq 函數(shù)中設(shè)置
  34.      */
  35.     struct sched_entity    *parent;
  36.     /* 實(shí)體所處紅黑樹運(yùn)行隊(duì)列 */
  37.     struct cfs_rq        *cfs_rq;        
  38.     /* 實(shí)體的紅黑樹運(yùn)行隊(duì)列,如果為NULL表明其是一個(gè)進(jìn)程,若非NULL表明其是調(diào)度組 */
  39.     struct cfs_rq        *my_q;
  40. #endif

  41. #ifdef CONFIG_SMP
  42.     /* Per-entity load-tracking */
  43.     struct sched_avg    avg;
  44. #endif
  45. };
    實(shí)際上,紅黑樹是根據(jù) struct rb_node 建立起關(guān)系的,不過(guò) struct rb_node 與 struct sched_entity 是一一對(duì)應(yīng)關(guān)系,也可以簡(jiǎn)單看為一個(gè)紅黑樹結(jié)點(diǎn)就是一個(gè)調(diào)度實(shí)體??梢钥闯?,在 struct sched_entity 結(jié)構(gòu)中,包含了一個(gè)進(jìn)程(或進(jìn)程組)調(diào)度的全部數(shù)據(jù),其被包含在 struct task_struct 結(jié)構(gòu)中的se中。

  1. struct task_struct {
  2.     ........
  3.     /* 表示是否在運(yùn)行隊(duì)列 */
  4.     int on_rq;

  5.     /* 進(jìn)程優(yōu)先級(jí)
  6.      * prio: 動(dòng)態(tài)優(yōu)先級(jí),范圍為100~139,與靜態(tài)優(yōu)先級(jí)和補(bǔ)償(bonus)有關(guān)
  7.      * static_prio: 靜態(tài)優(yōu)先級(jí),static_prio = 100 + nice + 20 (nice值為-20~19,所以static_prio值為100~139)
  8.      * normal_prio: 沒(méi)有受優(yōu)先級(jí)繼承影響的常規(guī)優(yōu)先級(jí),具體見(jiàn)normal_prio函數(shù),跟屬于什么類型的進(jìn)程有關(guān)
  9.      */
  10.     int prio, static_prio, normal_prio;
  11.     /* 實(shí)時(shí)進(jìn)程優(yōu)先級(jí) */
  12.     unsigned int rt_priority;
  13.     /* 調(diào)度類,調(diào)度處理函數(shù)類 */
  14.     const struct sched_class *sched_class;
  15.     /* 調(diào)度實(shí)體(紅黑樹的一個(gè)結(jié)點(diǎn)) */
  16.     struct sched_entity se;
  17.     /* 調(diào)度實(shí)體(實(shí)時(shí)調(diào)度使用) */
  18.     struct sched_rt_entity rt;
  19. #ifdef CONFIG_CGROUP_SCHED
  20.     /* 指向其所在進(jìn)程組 */
  21.     struct task_group *sched_task_group;
  22. #endif
  23.     ........
  24. }
    在 struct sched_entity 結(jié)構(gòu)中,值得我們注意的成員是:
  • load:權(quán)重,通過(guò)優(yōu)先級(jí)轉(zhuǎn)換而成,是vruntime計(jì)算的關(guān)鍵。
  • on_rq:表明是否處于CFS紅黑樹運(yùn)行隊(duì)列中,需要明確一個(gè)觀點(diǎn)就是,CFS運(yùn)行隊(duì)列里面包含有一個(gè)紅黑樹,但這個(gè)紅黑樹并不是CFS運(yùn)行隊(duì)列的全部,因?yàn)榧t黑樹僅僅是用于選擇出下一個(gè)調(diào)度程序的算法。很簡(jiǎn)單的一個(gè)例子,普通程序運(yùn)行時(shí),其并不在紅黑樹中,但是還是處于CFS運(yùn)行隊(duì)列中,其on_rq為真。只有準(zhǔn)備退出、即將睡眠等待和轉(zhuǎn)為實(shí)時(shí)進(jìn)程的進(jìn)程其CFS運(yùn)行隊(duì)列的on_rq為假。
  • vruntime:虛擬運(yùn)行時(shí)間,調(diào)度的關(guān)鍵,其計(jì)算公式:一次調(diào)度間隔的虛擬運(yùn)行時(shí)間 = 實(shí)際運(yùn)行時(shí)間 * (NICE_0_LOAD / 權(quán)重)??梢钥闯龈鷮?shí)際運(yùn)行時(shí)間和權(quán)重有關(guān),紅黑樹就是以此作為排序的標(biāo)準(zhǔn),優(yōu)先級(jí)越高的進(jìn)程在運(yùn)行時(shí)其vruntime增長(zhǎng)的越慢,其可運(yùn)行時(shí)間相對(duì)就長(zhǎng),而且也越有可能處于紅黑樹的最左結(jié)點(diǎn),調(diào)度器每次都選擇最左邊的結(jié)點(diǎn)為下一個(gè)調(diào)度進(jìn)程。注意其值為單調(diào)遞增,在每個(gè)調(diào)度器的時(shí)鐘中斷時(shí)當(dāng)前進(jìn)程的虛擬運(yùn)行時(shí)間都會(huì)累加。單純的說(shuō)就是進(jìn)程們都在比誰(shuí)的vruntime最小,最小的將被調(diào)度。
  • cfs_rq:此調(diào)度實(shí)體所處于的CFS運(yùn)行隊(duì)列。
  • my_q:如果此調(diào)度實(shí)體代表的是一個(gè)進(jìn)程組,那么此調(diào)度實(shí)體就包含有一個(gè)自己的CFS運(yùn)行隊(duì)列,其CFS運(yùn)行隊(duì)列中存放的是此進(jìn)程組中的進(jìn)程,這些進(jìn)程就不會(huì)在其他CFS運(yùn)行隊(duì)列的紅黑樹中被包含(包括頂層紅黑樹也不會(huì)包含他們,他們只屬于這個(gè)進(jìn)程組的紅黑樹)。
    對(duì)于怎么理解一個(gè)進(jìn)程組有它自己的CFS運(yùn)行隊(duì)列,其實(shí)很好理解,比如在根CFS運(yùn)行隊(duì)列的紅黑樹上有一個(gè)進(jìn)程A一個(gè)進(jìn)程組B,各占50%的CPU,對(duì)于根的紅黑樹而言,他們就是兩個(gè)調(diào)度實(shí)體。調(diào)度器調(diào)度的不是進(jìn)程A就是進(jìn)程組B,而如果調(diào)度到進(jìn)程組B,進(jìn)程組B自己選擇一個(gè)程序交給CPU運(yùn)行就可以了,而進(jìn)程組B怎么選擇一個(gè)程序給CPU,就是通過(guò)自己的CFS運(yùn)行隊(duì)列的紅黑樹選擇,如果進(jìn)程組B還有個(gè)子進(jìn)程組C,原理都一樣,就是一個(gè)層次結(jié)構(gòu)。
    而在 struct task_struct 結(jié)構(gòu)中,我們注意到有個(gè)調(diào)度類,里面包含的是調(diào)度處理函數(shù),它具體如下

  1. struct sched_class {
  2.     /* 下一優(yōu)先級(jí)的調(diào)度類
  3.      * 調(diào)度類優(yōu)先級(jí)順序: stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
  4.      */
  5.     const struct sched_class *next;

  6.     /* 將進(jìn)程加入到運(yùn)行隊(duì)列中,即將調(diào)度實(shí)體(進(jìn)程)放入紅黑樹中,并對(duì) nr_running 變量加1 */
  7.     void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
  8.     /* 從運(yùn)行隊(duì)列中刪除進(jìn)程,并對(duì) nr_running 變量中減1 */
  9.     void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
  10.     /* 放棄CPU,在 compat_yield sysctl 關(guān)閉的情況下,該函數(shù)實(shí)際上執(zhí)行先出隊(duì)后入隊(duì);在這種情況下,它將調(diào)度實(shí)體放在紅黑樹的最右端 */
  11.     void (*yield_task) (struct rq *rq);
  12.     bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt);

  13.     /* 檢查當(dāng)前進(jìn)程是否可被新進(jìn)程搶占 */
  14.     void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);

  15.     /*
  16.      * It is the responsibility of the pick_next_task() method that will
  17.      * return the next task to call put_prev_task() on the @prev task or
  18.      * something equivalent.
  19.      *
  20.      * May return RETRY_TASK when it finds a higher prio class has runnable
  21.      * tasks.
  22.      */
  23.     /* 選擇下一個(gè)應(yīng)該要運(yùn)行的進(jìn)程運(yùn)行 */
  24.     struct task_struct * (*pick_next_task) (struct rq *rq,
  25.                         struct task_struct *prev);
  26.     /* 將進(jìn)程放回運(yùn)行隊(duì)列 */
  27.     void (*put_prev_task) (struct rq *rq, struct task_struct *p);

  28. #ifdef CONFIG_SMP
  29.     /* 為進(jìn)程選擇一個(gè)合適的CPU */
  30.     int (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags);
  31.     /* 遷移任務(wù)到另一個(gè)CPU */
  32.     void (*migrate_task_rq)(struct task_struct *p, int next_cpu);
  33.     /* 用于上下文切換后 */
  34.     void (*post_schedule) (struct rq *this_rq);
  35.     /* 用于進(jìn)程喚醒 */
  36.     void (*task_waking) (struct task_struct *task);
  37.     void (*task_woken) (struct rq *this_rq, struct task_struct *task);
  38.     /* 修改進(jìn)程的CPU親和力(affinity) */
  39.     void (*set_cpus_allowed)(struct task_struct *p,
  40.                  const struct cpumask *newmask);
  41.     /* 啟動(dòng)運(yùn)行隊(duì)列 */
  42.     void (*rq_online)(struct rq *rq);
  43.     /* 禁止運(yùn)行隊(duì)列 */
  44.     void (*rq_offline)(struct rq *rq);
  45. #endif
  46.     /* 當(dāng)進(jìn)程改變它的調(diào)度類或進(jìn)程組時(shí)被調(diào)用 */
  47.     void (*set_curr_task) (struct rq *rq);
  48.     /* 該函數(shù)通常調(diào)用自 time tick 函數(shù);它可能引起進(jìn)程切換。這將驅(qū)動(dòng)運(yùn)行時(shí)(running)搶占 */
  49.     void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
  50.     /* 在進(jìn)程創(chuàng)建時(shí)調(diào)用,不同調(diào)度策略的進(jìn)程初始化不一樣 */
  51.     void (*task_fork) (struct task_struct *p);
  52.     /* 在進(jìn)程退出時(shí)會(huì)使用 */
  53.     void (*task_dead) (struct task_struct *p);

  54.     /* 用于進(jìn)程切換 */
  55.     void (*switched_from) (struct rq *this_rq, struct task_struct *task);
  56.     void (*switched_to) (struct rq *this_rq, struct task_struct *task);
  57.     /* 改變優(yōu)先級(jí) */
  58.     void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
  59.              int oldprio);

  60.     unsigned int (*get_rr_interval) (struct rq *rq,
  61.                      struct task_struct *task);

  62.     void (*update_curr) (struct rq *rq);

  63. #ifdef CONFIG_FAIR_GROUP_SCHED
  64.     void (*task_move_group) (struct task_struct *p, int on_rq);
  65. #endif
  66. };
    這個(gè)調(diào)度類具體有什么用呢,實(shí)際上在內(nèi)核中不同的調(diào)度算法它們的操作都不相同,為了方便修改、替換調(diào)度算法,使用了調(diào)度類,每個(gè)調(diào)度算法只需要實(shí)現(xiàn)自己的調(diào)度類就可以了,CFS算法有它的調(diào)度類,SCHED_FIFO也有它自己的調(diào)度類,當(dāng)一個(gè)進(jìn)程創(chuàng)建時(shí),用什么調(diào)度算法就將其 task_struct->sched_class 指向其相應(yīng)的調(diào)度類,調(diào)度器每次調(diào)度處理時(shí),就通過(guò)當(dāng)前進(jìn)程的調(diào)度類函數(shù)進(jìn)程操作,大大提高了可移植性和易修改性。

CFS運(yùn)行隊(duì)列(struct cfs_rq)

    我們現(xiàn)在知道,在系統(tǒng)中至少有一個(gè)CFS運(yùn)行隊(duì)列,其就是根CFS運(yùn)行隊(duì)列,而其他的進(jìn)程組和進(jìn)程都包含在此運(yùn)行隊(duì)列中,不同的是進(jìn)程組又有它自己的CFS運(yùn)行隊(duì)列,其運(yùn)行隊(duì)列中包含的是此進(jìn)程組中的所有進(jìn)程。當(dāng)調(diào)度器從根CFS運(yùn)行隊(duì)列中選擇了一個(gè)進(jìn)程組進(jìn)行調(diào)度時(shí),進(jìn)程組會(huì)從自己的CFS運(yùn)行隊(duì)列中選擇一個(gè)調(diào)度實(shí)體進(jìn)行調(diào)度(這個(gè)調(diào)度實(shí)體可能為進(jìn)程,也可能又是一個(gè)子進(jìn)程組),就這樣一直深入,直到最后選出一個(gè)進(jìn)程進(jìn)行運(yùn)行為止。
    對(duì)于 struct cfs_rq 結(jié)構(gòu)沒(méi)有什么好說(shuō)明的,只要確定其代表著一個(gè)CFS運(yùn)行隊(duì)列,并且包含有一個(gè)紅黑樹進(jìn)行選擇調(diào)度進(jìn)程即可。

  1. /* CFS調(diào)度的運(yùn)行隊(duì)列,每個(gè)CPU的rq會(huì)包含一個(gè)cfs_rq,而每個(gè)組調(diào)度的sched_entity也會(huì)有自己的一個(gè)cfs_rq隊(duì)列 */
  2. struct cfs_rq {
  3.     /* CFS運(yùn)行隊(duì)列中所有進(jìn)程的總負(fù)載 */
  4.     struct load_weight load;
  5.     /*
  6.      * nr_running: cfs_rq中調(diào)度實(shí)體數(shù)量
  7.      * h_nr_running: 只對(duì)進(jìn)程組有效,其下所有進(jìn)程組中cfs_rq的nr_running之和
  8.      */
  9.     unsigned int nr_running, h_nr_running;

  10.     u64 exec_clock;
  11.     /* 當(dāng)前CFS隊(duì)列上最小運(yùn)行時(shí)間,單調(diào)遞增
  12.      * 兩種情況下更新該值:
  13.      * 1、更新當(dāng)前運(yùn)行任務(wù)的累計(jì)運(yùn)行時(shí)間時(shí)
  14.      * 2、當(dāng)任務(wù)從隊(duì)列刪除去,如任務(wù)睡眠或退出,這時(shí)候會(huì)查看剩下的任務(wù)的vruntime是否大于min_vruntime,如果是則更新該值。
  15.      */
  16.     u64 min_vruntime;
  17. #ifndef CONFIG_64BIT
  18.     u64 min_vruntime_copy;
  19. #endif
  20.     /* 該紅黑樹的root */
  21.     struct rb_root tasks_timeline;
  22.     /* 下一個(gè)調(diào)度結(jié)點(diǎn)(紅黑樹最左邊結(jié)點(diǎn),最左邊結(jié)點(diǎn)就是下個(gè)調(diào)度實(shí)體) */
  23.     struct rb_node *rb_leftmost;

  24.     /*
  25.      * 'curr' points to currently running entity on this cfs_rq.
  26.      * It is set to NULL otherwise (i.e when none are currently running).
  27.      */
  28.     /*
  29.      * curr: 當(dāng)前正在運(yùn)行的sched_entity(對(duì)于組雖然它不會(huì)在cpu上運(yùn)行,但是當(dāng)它的下層有一個(gè)task在cpu上運(yùn)行,那么它所在的cfs_rq就把它當(dāng)做是該cfs_rq上當(dāng)前正在運(yùn)行的sched_entity)
  30.      * next: 表示有些進(jìn)程急需運(yùn)行,即使不遵從CFS調(diào)度也必須運(yùn)行它,調(diào)度時(shí)會(huì)檢查是否next需要調(diào)度,有就調(diào)度next
  31.      *
  32.      * skip: 略過(guò)進(jìn)程(不會(huì)選擇skip指定的進(jìn)程調(diào)度)
  33.      */
  34.     struct sched_entity *curr, *next, *last, *skip;

  35. #ifdef    CONFIG_SCHED_DEBUG
  36.     unsigned int nr_spread_over;
  37. #endif

  38. #ifdef CONFIG_SMP
  39.     /*
  40.      * CFS Load tracking
  41.      * Under CFS, load is tracked on a per-entity basis and aggregated up.
  42.      * This allows for the description of both thread and group usage (in
  43.      * the FAIR_GROUP_SCHED case).
  44.      */
  45.     unsigned long runnable_load_avg, blocked_load_avg;
  46.     atomic64_t decay_counter;
  47.     u64 last_decay;
  48.     atomic_long_t removed_load;

  49. #ifdef CONFIG_FAIR_GROUP_SCHED
  50.     /* Required to track per-cpu representation of a task_group */
  51.     u32 tg_runnable_contrib;
  52.     unsigned long tg_load_contrib;

  53.     /*
  54.      * h_load = weight * f(tg)
  55.      *
  56.      * Where f(tg) is the recursive weight fraction assigned to
  57.      * this group.
  58.      */
  59.     unsigned long h_load;
  60.     u64 last_h_load_update;
  61.     struct sched_entity *h_load_next;
  62. #endif /* CONFIG_FAIR_GROUP_SCHED */
  63. #endif /* CONFIG_SMP */

  64. #ifdef CONFIG_FAIR_GROUP_SCHED
  65.     /* 所屬于的CPU rq */
  66.     struct rq *rq;    /* cpu runqueue to which this cfs_rq is attached */

  67.     /*
  68.      * leaf cfs_rqs are those that hold tasks (lowest schedulable entity in
  69.      * a hierarchy). Non-leaf lrqs hold other higher schedulable entities
  70.      * (like users, containers etc.)
  71.      *
  72.      * leaf_cfs_rq_list ties together list of leaf cfs_rq's in a cpu. This
  73.      * list is used during load balance.
  74.      */
  75.     int on_list;
  76.     struct list_head leaf_cfs_rq_list;
  77.     /* 擁有該CFS運(yùn)行隊(duì)列的進(jìn)程組 */
  78.     struct task_group *tg;    /* group that "owns" this runqueue */

  79. #ifdef CONFIG_CFS_BANDWIDTH
  80.     int runtime_enabled;
  81.     u64 runtime_expires;
  82.     s64 runtime_remaining;

  83.     u64 throttled_clock, throttled_clock_task;
  84.     u64 throttled_clock_task_time;
  85.     int throttled, throttle_count;
  86.     struct list_head throttled_list;
  87. #endif /* CONFIG_CFS_BANDWIDTH */
  88. #endif /* CONFIG_FAIR_GROUP_SCHED */
  89. };
  • load:其保存的是進(jìn)程組中所有進(jìn)程的權(quán)值總和,需要注意子進(jìn)程計(jì)算vruntime時(shí)需要用到進(jìn)程組的load。

CPU運(yùn)行隊(duì)列(struct rq)

     每個(gè)CPU都有自己的 struct rq 結(jié)構(gòu),其用于描述在此CPU上所運(yùn)行的所有進(jìn)程,其包括一個(gè)實(shí)時(shí)進(jìn)程隊(duì)列和一個(gè)根CFS運(yùn)行隊(duì)列,在調(diào)度時(shí),調(diào)度器首先會(huì)先去實(shí)時(shí)進(jìn)程隊(duì)列找是否有實(shí)時(shí)進(jìn)程需要運(yùn)行,如果沒(méi)有才會(huì)去CFS運(yùn)行隊(duì)列找是否有進(jìn)行需要運(yùn)行,這就是為什么常說(shuō)的實(shí)時(shí)進(jìn)程優(yōu)先級(jí)比普通進(jìn)程高,不僅僅體現(xiàn)在prio優(yōu)先級(jí)上,還體現(xiàn)在調(diào)度器的設(shè)計(jì)上,至于dl運(yùn)行隊(duì)列,我暫時(shí)還不知道有什么用處,其優(yōu)先級(jí)比實(shí)時(shí)進(jìn)程還高,但是創(chuàng)建進(jìn)程時(shí)如果創(chuàng)建的是dl進(jìn)程創(chuàng)建會(huì)錯(cuò)誤(具體見(jiàn)sys_fork)。

總結(jié)

    關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)也介紹得差不多了,之后的章節(jié)將會(huì)從代碼上為大家詳細(xì)講解內(nèi)核調(diào)度器是怎么實(shí)現(xiàn)的。
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
linux2.6 CFS調(diào)度算法分析
linux進(jìn)程調(diào)度機(jī)制剖析(基于3.16
Linux進(jìn)程調(diào)度:CFS調(diào)度器的設(shè)計(jì)框架
六萬(wàn)字 | 深入理解Linux進(jìn)程調(diào)度
【原創(chuàng)】(四)Linux進(jìn)程調(diào)度-組調(diào)度及帶寬控制
【原創(chuàng)】(五)Linux進(jìn)程調(diào)度-CFS調(diào)度器
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服