主頁 > 作業系統 > 【原創】(五)Linux行程調度-CFS調度器

【原創】(五)Linux行程調度-CFS調度器

2020-09-25 17:15:08 作業系統

背景

  • 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. 概述

  • Completely Fair Scheduler,完全公平調度器,用于Linux系統中普通行程的調度,
  • CFS采用了紅黑樹演算法來管理所有的調度物體sched_entity,演算法效率為O(log(n))CFS跟蹤調度物體sched_entity的虛擬運行時間vruntime,平等對待運行佇列中的調度物體sched_entity,將執行時間少的調度物體sched_entity排列到紅黑樹的左邊,
  • 調度物體sched_entity通過enqueue_entity()dequeue_entity()來進行紅黑樹的出隊入隊,

老規矩,先上張圖片來直觀了解一下原理:

  • 每個sched_latency周期內,根據各個任務的權重值,可以計算出運行時間runtime
  • 運行時間runtime可以轉換成虛擬運行時間vruntime
  • 根據虛擬運行時間的大小,插入到CFS紅黑樹中,虛擬運行時間少的調度物體放置到左邊;
  • 在下一次任務調度的時候,選擇虛擬運行時間少的調度物體來運行;

在開始本文之前,建議先閱讀下(一)Linux行程調度器-基礎

開始探索之旅!

2. 資料結構

2.1 調度類

Linux內核抽象了一個調度類struct sched_class,這是一種典型的面向物件的設計思想,將共性的特征抽象出來封裝成類,在實體化各個調度器的時候,可以根據具體的調度演算法來實作,這種方式做到了高內聚低耦合,同時又很容易擴展新的調度器,

  • 在調度核心代碼kernel/sched/core.c中,使用的方式是task->sched_class->xxx_func,其中task表示的是描述任務的結構體struct task_struck,在該結構體中包含了任務所使用的調度器,進而能找到對應的函式指標來完成呼叫執行,有點類似于C++中的多型機制,

2.2 rq/cfs_rq/task_struct/task_group/sched_entity

  • struct rq:每個CPU都有一個對應的運行佇列;
  • struct cfs_rq:CFS運行佇列,該結構中包含了struct rb_root_cached紅黑樹,用于鏈接調度物體struct sched_entityrq運行佇列中對應了一個CFS運行佇列,此外,在task_group結構中也會為每個CPU再維護一個CFS運行佇列;
  • struct task_struct:任務的描述符,包含了行程的所有資訊,該結構中的struct sched_entity,用于參與CFS的調度;
  • struct task_group:組調度(參考前文),Linux支持將任務分組來對CPU資源進行分配管理,該結構中為系統中的每個CPU都分配了struct sched_entity調度物體和struct cfs_rq運行佇列,其中struct sched_entity用于參與CFS的調度;
  • struct sched_entity:調度物體,這個也是CFS調度管理的物件了;

來一張圖看看它們之間的組織關系:

  • struct sched_entity結構體欄位注釋如下:
struct sched_entity {
	/* For load-balancing: */
	struct load_weight		load;                   //調度物體的負載權重值
	struct rb_node			run_node;             //用于連接到CFS運行佇列的紅黑樹中的節點
	struct list_head		group_node;          //用于連接到CFS運行佇列的cfs_tasks鏈表中的節點
	unsigned int			on_rq;              //用于表示是否在運行佇列中

	u64				exec_start;             //當前調度物體的開始執行時間
	u64				sum_exec_runtime;   //調度物體執行的總時間
	u64				vruntime;           //虛擬運行時間,這個時間用于在CFS運行佇列中排隊
	u64				prev_sum_exec_runtime;  //上一個調度物體運行的總時間

	u64				nr_migrations;      //負載均衡

	struct sched_statistics		statistics;     //統計資訊

#ifdef CONFIG_FAIR_GROUP_SCHED
	int				depth;      //任務組的深度,其中根任務組的深度為0,逐級往下增加
	struct sched_entity		*parent;        //指向調度物體的父物件
	/* rq on which this entity is (to be) queued: */
	struct cfs_rq			*cfs_rq;        //指向調度物體歸屬的CFS佇列,也就是需要入列的CFS佇列
	/* rq "owned" by this entity/group: */
	struct cfs_rq			*my_q;      //指向歸屬于當前調度物體的CFS佇列,用于包含子任務或子的任務組
#endif

#ifdef CONFIG_SMP
	/*
	 * Per entity load average tracking.
	 *
	 * Put into separate cache line so it does not
	 * collide with read-mostly values above.
	 */
	struct sched_avg		avg ____cacheline_aligned_in_smp;   //用于調度物體的負載計算(`PELT`)
#endif
};
  • struct cfs_rq結構體的關鍵欄位注釋如下:
/* CFS-related fields in a runqueue */
struct cfs_rq {
	struct load_weight load;        //CFS運行佇列的負載權重值
	unsigned int nr_running, h_nr_running;  //nr_running:運行的調度物體數(參與時間片計算)

	u64 exec_clock;     //運行時間
	u64 min_vruntime;   //最少的虛擬運行時間,調度物體入隊出隊時需要進行增減處理
#ifndef CONFIG_64BIT
	u64 min_vruntime_copy;
#endif

	struct rb_root_cached tasks_timeline;   //紅黑樹,用于存放調度物體

	/*
	 * 'curr' points to currently running entity on this cfs_rq.
	 * It is set to NULL otherwise (i.e when none are currently running).
	 */
	struct sched_entity *curr, *next, *last, *skip; //分別指向當前運行的調度物體、下一個調度的調度物體、CFS運行佇列中排最后的調度物體、跳過運行的調度物體

#ifdef	CONFIG_SCHED_DEBUG
	unsigned int nr_spread_over;
#endif

#ifdef CONFIG_SMP
	/*
	 * CFS load tracking
	 */
	struct sched_avg avg;       //計算負載相關
	u64 runnable_load_sum;
	unsigned long runnable_load_avg;        //基于PELT的可運行平均負載
#ifdef CONFIG_FAIR_GROUP_SCHED
	unsigned long tg_load_avg_contrib;      //任務組的負載貢獻
	unsigned long propagate_avg;
#endif
	atomic_long_t removed_load_avg, removed_util_avg;
#ifndef CONFIG_64BIT
	u64 load_last_update_time_copy;
#endif

#ifdef CONFIG_FAIR_GROUP_SCHED
	/*
	 *   h_load = weight * f(tg)
	 *
	 * Where f(tg) is the recursive weight fraction assigned to
	 * this group.
	 */
	unsigned long h_load;
	u64 last_h_load_update;
	struct sched_entity *h_load_next;
#endif /* CONFIG_FAIR_GROUP_SCHED */
#endif /* CONFIG_SMP */

#ifdef CONFIG_FAIR_GROUP_SCHED
	struct rq *rq;	/* cpu runqueue to which this cfs_rq is attached */     //指向CFS運行佇列所屬的CPU RQ運行佇列

	/*
	 * leaf cfs_rqs are those that hold tasks (lowest schedulable entity in
	 * a hierarchy). Non-leaf lrqs hold other higher schedulable entities
	 * (like users, containers etc.)
	 *
	 * leaf_cfs_rq_list ties together list of leaf cfs_rq's in a cpu. This
	 * list is used during load balance.
	 */
	int on_list;
	struct list_head leaf_cfs_rq_list;
	struct task_group *tg;	/* group that "owns" this runqueue */       //CFS運行佇列所屬的任務組

#ifdef CONFIG_CFS_BANDWIDTH
	int runtime_enabled;    //CFS運行佇列中使用CFS帶寬控制
	u64 runtime_expires;    //到期的運行時間
	s64 runtime_remaining;      //剩余的運行時間

	u64 throttled_clock, throttled_clock_task;  //限流時間相關
	u64 throttled_clock_task_time;
	int throttled, throttle_count;      //throttled:限流,throttle_count:CFS運行佇列限流次數
	struct list_head throttled_list;    //運行佇列限流鏈表節點,用于添加到cfs_bandwidth結構中的cfttle_cfs_rq鏈表中
#endif /* CONFIG_CFS_BANDWIDTH */
#endif /* CONFIG_FAIR_GROUP_SCHED */
};

3. 流程分析

整個流程分析,圍繞著CFS調度類物體:fair_sched_class中的關鍵函式來展開,

先來看看fair_sched_class都包含了哪些函式:

/*
 * All the scheduling class methods:
 */
const struct sched_class fair_sched_class = {
	.next			= &idle_sched_class,
	.enqueue_task		= enqueue_task_fair,
	.dequeue_task		= dequeue_task_fair,
	.yield_task		= yield_task_fair,
	.yield_to_task		= yield_to_task_fair,

	.check_preempt_curr	= check_preempt_wakeup,

	.pick_next_task		= pick_next_task_fair,
	.put_prev_task		= put_prev_task_fair,

#ifdef CONFIG_SMP
	.select_task_rq		= select_task_rq_fair,
	.migrate_task_rq	= migrate_task_rq_fair,

	.rq_online		= rq_online_fair,
	.rq_offline		= rq_offline_fair,

	.task_dead		= task_dead_fair,
	.set_cpus_allowed	= set_cpus_allowed_common,
#endif

	.set_curr_task          = set_curr_task_fair,
	.task_tick		= task_tick_fair,
	.task_fork		= task_fork_fair,

	.prio_changed		= prio_changed_fair,
	.switched_from		= switched_from_fair,
	.switched_to		= switched_to_fair,

	.get_rr_interval	= get_rr_interval_fair,

	.update_curr		= update_curr_fair,

#ifdef CONFIG_FAIR_GROUP_SCHED
	.task_change_group	= task_change_group_fair,
#endif
};

3.1 runtime與vruntime

CFS調度器沒有時間片的概念了,而是根據實際的運行時間和虛擬運行時間來對任務進行排序,從而選擇調度,
那么,運行時間和虛擬運行時間是怎么計算的呢?看一下流程呼叫:

  • Linux內核默認的sysctl_sched_latency是6ms,這個值用戶態可設,sched_period用于保證可運行任務都能至少運行一次的時間間隔;
  • 當可運行任務大于8個的時候,sched_period的計算則需要根據任務個數乘以最小調度顆粒值,這個值系統默認為0.75ms;
  • 每個任務的運行時間計算,是用sched_period值,去乘以該任務在整個CFS運行佇列中的權重占比;
  • 虛擬運行的時間 = 實際運行時間 * NICE_0_LOAD / 該任務的權重;

還是來看一個實體吧,以5個Task為例,其中每個Task的nice值不一樣(優先級不同),對應到的權重值在內核中提供了一個轉換陣列:

const int sched_prio_to_weight[40] = {
 /* -20 */     88761,     71755,     56483,     46273,     36291,
 /* -15 */     29154,     23254,     18705,     14949,     11916,
 /* -10 */      9548,      7620,      6100,      4904,      3906,
 /*  -5 */      3121,      2501,      1991,      1586,      1277,
 /*   0 */      1024,       820,       655,       526,       423,
 /*   5 */       335,       272,       215,       172,       137,
 /*  10 */       110,        87,        70,        56,        45,
 /*  15 */        36,        29,        23,        18,        15,
};

圖來了:

3.2 CFS調度tick

CFS調度器中的tick函式為task_tick_fair,系統中每個調度tick都會呼叫到,此外如果使用了hrtimer,也會呼叫到這個函式,
流程如下:

主要的作業包括:

  • 更新運行時的各類統計資訊,比如vruntime, 運行時間、負載值、權重值等;
  • 檢查是否需要搶占,主要是比較運行時間是否耗盡,以及vruntime的差值是否大于運行時間等;

來一張圖,感受一下update_curr函式的相關資訊更新吧:

3.3 任務出隊入隊

  • 當任務進入可運行狀態時,需要將調度物體放入到紅黑樹中,完成入隊操作;
  • 當任務退出可運行狀態時,需要將調度物體從紅黑樹中移除,完成出隊操作;
  • CFS調度器,使用enqueue_task_fair函式將任務入隊到CFS佇列,使用dequeue_task_fair函式將任務從CFS佇列中出隊操作,

  • 出隊與入隊的操作中,核心的邏輯可以分成兩部分:1)更新運行時的資料,比如負載、權重、組調度的占比等等;2)將sched_entity插入紅黑樹,或者從紅黑樹移除;
  • 由于dequeue_task_fair大體的邏輯類似,不再深入分析;
  • 這個程序中,涉及到了CPU負載計算task_group組調度CFS Bandwidth帶寬控制等,這些都在前邊的文章中分析過,可以結合進行理解;

3.3 任務創建

在父行程通過fork創建子行程的時候,task_fork_fair函式會被呼叫,這個函式的傳入引數是子行程的task_struct,該函式的主要作用,就是確定子任務的vruntime,因此也能確定子任務的調度物體在紅黑樹RB中的位置,

task_fork_fair本身比較簡單,流程如下圖:

3.4 任務選擇

每當行程任務切換的時候,也就是schedule函式執行時,調度器都需要選擇下一個將要執行的任務,
在CFS調度器中,是通過pick_next_task_fair函式完成的,流程如下:

  • 當需要行程任務切換的時候,pick_next_task_fair函式的傳入引數中包含了需要被切換出去的任務,也就是pre_task
  • pre_task不是普通行程時,也就是調度類不是CFS,那么它就不使用sched_entity的調度物體來參與調度,因此會執行simple分支,通過put_pre_task函式來通知系統當前的任務需要被切換,而不是通過put_prev_entity函式來完成;
  • pre_task是普通行程時,呼叫pick_next_entity來選擇下一個執行的任務,這個選擇程序實際是有兩種情況:當調度物體對應task時,do while()遍歷一次,當調度物體對應task_group是,則需要遍歷任務組來選擇下一個執行的任務了,
  • put_prev_entity,用于切換任務前的準備作業,更新運行時的統計資料,并不進行dequeue的操作,其中需要將CFS佇列的curr指標置位成NULL;
  • set_next_entity,用于設定下一個要運行的調度物體,設定CFS佇列的curr指標;
  • 如果使能了hrtimer,則將hrtimer的到期時間設定為調度物體的剩余運行時間;

暫且分析到這吧,CFS調度器涵蓋的內容還是挺多的,fair.c一個檔案就有將近一萬行代碼,相關內容的分析也分散在前邊的文章中了,感興趣的可以去看看,

打完收工,洗洗睡了,

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/126982.html

標籤:Linux

上一篇:作業系統-行程同步與信號量

下一篇:Ansible-免密登錄與主機清單Inventory

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more