1. 程式人生 > >linux核心分析——CFS(完全公平排程演算法)

linux核心分析——CFS(完全公平排程演算法)

1.1 CFS原理

    cfs定義了一種新的模型,它給cfs_rq(cfs的run queue)中的每一個程序安排一個虛擬時鐘,vruntime。如果一個程序得以執行,隨著時間的增長(也就是一個個tick的到來),其vruntime將不斷增大。沒有得到執行的程序vruntime不變。
    而排程器總是選擇vruntime跑得最慢的那個程序來執行。這就是所謂的“完全公平”。為了區別不同優先順序的程序,優先順序高的程序vruntime增長得慢,以至於它可能得到更多的執行機會。

1.2 CFS基本設計思路

CFS思路很簡單,就是根據各個程序的權重分配執行時間(權重怎麼來的後面再說)。
程序的執行時間計算公式為:
分配給程序的執行時間 = 排程週期 * 程序權重 / 所有程序權重之和   (公式1)
    排程週期很好理解,就是將所有處於TASK_RUNNING態程序都排程一遍的時間,差不多相當於O(1)排程演算法中執行佇列和過期佇列切換一次的時間(對O(1)排程演算法看得不是很熟,如有錯誤還望各位大蝦指出)。舉個例子,比如只有兩個程序A, B,權重分別為1和2,排程週期設為30ms,那麼分配給A的CPU時間為:30ms * (1/(1+2)) = 10ms而B的CPU時間為30ms * (2/(1+2)) = 20ms那麼在這30ms中A將執行10ms,B將執行20ms。
    公平怎麼體現呢?它們的執行時間並不一樣阿?
其實公平是體現在另外一個量上面,叫做virtual runtime(vruntime),它記錄著程序已經執行的時間,但是並不是直接記錄,而是要根據程序的權重將執行時間放大或者縮小一個比例
我們來看下從實際執行時間到vruntime的換算公式
vruntime = 實際執行時間 * 1024 / 程序權重 。 (公式2)
    為了不把大家搞暈,這裡我直接寫1024,實際上它等於nice為0的程序的權重,程式碼中是NICE_0_LOAD。也就是說,所有程序都以nice為0的程序的權重1024作為基準,計算自己的vruntime增加速度。還以上面AB兩個程序為例,B的權重是A的2倍,那麼B的vruntime增加速度只有A的一半。現在我們把公式2中的實際執行時間用公式1來替換,可以得到這麼一個結果:
vruntime = (排程週期 * 程序權重 / 所有程序總權重) * 1024 / 程序權重 = 排程週期 * 1024 / 所有程序總權重 
看出什麼眉目沒有?沒錯,雖然程序的權重不同,但是它們的 vruntime增長速度應該是一樣的 ,與權重無關。好,既然所有程序的vruntime增長速度巨集觀上看應該是同時推進的,
那麼就可以用這個vruntime來選擇執行的程序,誰的vruntime值較小就說明它以前佔用cpu的時間較短,受到了“不公平”對待,因此下一個執行程序就是它。這樣既能公平選擇程序,又能保證高優先順序程序獲得較多的執行時間。這就是CFS的主要思想了。

或者可以這麼理解:CFS的思想就是讓每個排程實體(沒有組排程的情形下就是程序,以後就說程序了)的vruntime互相追趕,而每個排程實體的vruntime增加速度不同,權重越大的增加的越慢,這樣就能獲得更多的cpu執行時間。

    再補充一下權重的來源,權重跟程序nice值之間有一一對應的關係,可以通過全域性陣列prio_to_weight來轉換,nice值越大,權重越低

1.3 CFS資料結構

介紹程式碼之前先介紹一下CFS相關的結構
第一個是排程實體sched_entity,它代表一個排程單位,在組排程關閉的時候可以把他等同為程序。每一個task_struct中都有一個sched_entity,程序的vruntime和權重都儲存在這個結構中。那麼所有的sched_entity怎麼組織在一起呢?紅黑樹。所有的sched_entity以vruntime為key(實際上是以vruntime-min_vruntime為key,是為了防止溢位反正結果是一樣的)插入到紅黑樹中,同時快取樹的最左側節點,也就是vruntime最小的節點,這樣可以迅速選中vruntime最小的程序。
    注意只有等待CPU的就緒態程序在這棵樹上,睡眠程序和正在執行的程序都不在樹上。

\

1.4 Vruntime溢位問題

    之前說過紅黑樹中實際的作為key的不是vruntime而是vruntime-min_vruntimemin_vruntime是當前紅黑樹中最小的key。這是為什麼呢,我們先看看vruntime的型別,是usigned long型別的,再看看key的型別,是signed long型別的,因為程序的虛擬時間是一個遞增的正值,因此它不會是負數,但是它有它的上限,就是unsigned long所能表示的最大值,如果溢位了,那麼它就會從0開始回滾,如果這樣的話,結果會怎樣?結果很嚴重啊,就是說會本末倒置的,比如以下例子,以unsigned char說明問題:

unsigned char a = 251,b = 254;

b += 5;//到此判斷a和b的大小

看看上面的例子,b回滾了,導致a遠遠大於b,其實真正的結果應該是b比a大8,怎麼做到真正的結果呢?改為以下:

unsigned char a = 251,b = 254;

b += 5;

signed char c = a - 250,d = b - 250;//到此判斷c和d的大小

結果正確了,要的就是這個效果,可是程序的vruntime怎麼用unsigned long型別而不處理溢位問題呢?因為這個vruntime的作用就是推進虛擬時鐘,並沒有別的用處,它可以不在乎,然而在計算紅黑樹的key的時候就不能不在乎了,於是減去一個最小的vruntime將所有程序的key圍繞在最小vruntime的周圍,這樣更加容易追蹤。執行佇列的min_vruntime的作用就是處理溢位問題的

1.5 組排程

    關於組排程,詳見:《linux組排程淺析 》。簡單來說,引入組排程是為了實現做一件事的一組程序與做另一件事的另一組程序的隔離。每件“事情”各自有自己的權重,而不管它需要使用多少程序來完成。在cfs中,task_group和程序是同等對待的,task_group的優先順序也由使用者來控制(通過cgroup檔案cpu.shares)。
實現上,task_group和程序都被抽象成schedule_entity(排程實體,以下簡稱se),上面說到的vruntime、load、等這些東西都被封裝在se裡面。而task_group除了有se之外,還有cfs_rq。屬於這個task_group的程序就被裝在它的cfs_rq中(“組”不僅是一個被排程的實體,也是一個容器)。組排程引入以後,一系列task_group的cfs_rq組成了一個樹型結構。樹根是cpu所對應的cfs_rq(也就是root group的cfs_rq)、樹的中間節點是各個task_group的cfs_rq、葉子節點是各個程序。
在一個task_group的兩頭,是兩個不同的世界,就像《盜夢空間》裡不同層次的夢境一樣。
\

以group-1為例,它所對應的se被加入到父組(cpu_rq)的cfs_rq中,接受排程。這個se有自己的load(由對應的cpu.shares檔案來配置),不管group-1下面有多少個程序,這個load都是這個值。父組看不到、也不關心group-1下的程序。父組只會根據這個se的load和它執行的時間來更新其vruntime。當group-1被排程器選中後,會繼續選擇其下面的task-11或task-12來執行。這裡又是一套獨立的體系,task-11與task-12的vruntime、load、等這些東西隻影響它們在group-1的cfs_rq中的排程情況。樹型結構中的每一個cfs_rq都是獨立完成自己的排程邏輯。不過,從cpu配額上看,task_group的配額會被其子孫層層瓜分。
    例如上圖中的task-11,它所在的group-1對應se的load是8,而group-1下兩個程序的load是9和3,task-11佔其中的3/4。於是,在group-1所對應的cfs_rq內部看,task-11的load是9,而從全域性來看,task-11的load是8*3/4=6。而task_group下的程序的時間片也是這樣層層瓜分而來的,比如說group-1的cfs_rq下只有兩個程序,計算得來的排程延遲是20ms。但是task-11並非佔其中的3/4(15ms)。因為group-1的se的load佔總額的8/(8+3+5)=1/2,所以task-11的load佔總額的1/2*3/4=3/8,時間片是20ms*3/8=7.5ms。
這樣的瓜分有可能使得task_group裡面的程序分得很小的時間片,從而導致頻繁re-schedule。不過好在這並不影響task_group外面的其他程序,並且也可以儘量讓task_group裡面的程序在每個排程延遲內都執行一次。
    cfs曾經有過時間片不層層瓜分的實現,比如上圖中的task-11,時間片算出來是15ms就是15ms,不用再瓜分了。這樣做的好處是不會有頻繁的re-schedule。但是task_group裡的程序可能會很久才被執行一次。瓜分與不瓜分兩種方案的示例如下(還是繼續上圖的例子,深藍色代表task-11、淺藍色是task-12,空白是其他程序):
\
     兩種方案好像很難說清孰優孰劣,貌似cfs也在這兩種方案間糾結了好幾次。
在程序用完其時間片之前,有可能它所在的task_group的se先用完了時間片,而被其父組re-schedule掉。這種情況下,當這個task_group的se再一次被其父組選中時,上次得到執行、且尚未用完時間片的那個程序將繼續執行,直到它用完時間片。(cfs_rq->last會記錄下這個尚未用完時間片的程序。)

1.6 CFS小結

    CFS還有一個重要特點,即排程粒度小。CFS之前的排程器中,除了程序呼叫了某些阻塞函式而主動參與排程之外,每個程序都只有在用完了時間片或者屬於自己的時間配額之後才被搶佔。而CFS則在每次tick都進行檢查,如果當前程序不再處於紅黑樹的左邊,就被搶佔。在高負載的伺服器上,通過調整排程粒度能夠獲得更好的排程效能。



感謝關注 Ithao123Linux頻道,ithao123.cn是專門為網際網路人打造的學習交流平臺,全面滿足網際網路人工作與學習需求,更多網際網路資訊盡在 IThao123!

相關推薦

linux核心分析——CFS完全公平排程演算法

1.1 CFS原理     cfs定義了一種新的模型,它給cfs_rq(cfs的run queue)中的每一個程序安排一個虛擬時鐘,vruntime。如果一個程序得以執行,隨著時間的增長(也就是一個個tick的到來),其vruntime將不斷增大。沒有得到執行的程序vruntime不變。    而排程器總是選

程序管理筆記三、完全公平排程演算法CFS

程序管理筆記三、CFS排程演算法 引言:CFS是英文Completely Fair Scheduler的縮寫,即完全公平排程器,負責程序排程。在Linux Kernel 2.6.23之後採用,它負責將CPU資源,分配給正在執行的程序,目標在於最大化程式互動

linux核心分析排程演算法

linux排程演算法在2.6.32中採用排程類實現模組式的排程方式。這樣,能夠很好的加入新的排程演算法。 linux排程器是以模組方式提供的,這樣做的目的是允許不同型別的程序可以有針對性地選擇排程演算法。這種模組化結構被稱為排程器類,他允許多種不同哦可動態新增的排程演算法並

Linux核心完全註釋之Linux核心體系結構

Linux核心完全註釋之Linux核心體系結構(續) 2.6 Linux 核心對記憶體的使用方法 2.8 Linux 核心原始碼的目錄結構 2.9 核心系統與使用者程式的關係 2.10 linux/Makefile 檔案 小結

網易公開課《Linux核心分析》學習心得-理解程序排程時機跟蹤分析程序排程與程序切換的過程

首先在核心程式碼中搜索schedule,發現以下結果 在core.c檔案中是 實驗 設定斷點 跟蹤schedule的程序 可以看到 struct task_struct *tsk = current; sched_subm

linux核心分析--核心中的資料結構之紅黑樹

#include<linux/rbtree.h> #include <linux/string.h> #include "kn_common.h" MODULE_LICENSE("Dual BSD/GPL"); struct student { int id;

linux核心分析--核心中的資料結構之紅黑樹

紅黑樹由於節點顏色的特性,保證其是一種自平衡的二叉搜尋樹。 紅黑樹的一系列規則雖然實現起來比較複雜,但是遵循起來卻比較簡單,而且紅黑樹的插入,刪除效能也還不錯。 所以紅黑樹在核心中的應用非常廣泛,掌握好紅黑樹,即有利於閱讀核心原始碼,也可以在自己的程式碼中借鑑這種資料結構。 紅黑樹必

linux核心分析--核心中使用的資料結構之雜湊表hlist

前言: 1.基本概念: 散列表(Hash table,也叫雜湊表),是根據關鍵碼值(Key value)而直接進行訪問的資料結構。也就是說,它通過把關鍵碼值對映到表中一個位置來訪問記錄,以加快查詢的速度。這個對映函式叫做雜湊函式,存放記錄的陣列叫做散列表。 2. 常用的構造雜湊函式的方法

linux核心分析--核心中的資料結構之佇列

核心中的佇列是以位元組形式儲存資料的,所以獲取資料的時候,需要知道資料的大小。 如果從佇列中取得資料時指定的大小不對的話,取得資料會不完整或過大。 核心中關於佇列定義的標頭檔案位於:<linux/kfifo.h> include/linux/kfifo.h 標頭檔案中定義的函

linux核心分析--核心中的資料結構之雙鏈表

關於核心中使用到的資料結構這一系列會有五篇文章, 分別介紹    連結串列    佇列    雜湊    對映    紅黑樹

Linux核心分析——總結篇

       處理資源主要我們指的是程式的執行,我們知道程式的一個可執行例項是程序,裡面包含了程式碼可以執行的最基本資料集合。那麼從時間的維度上看,作業系統如何載入一個程式就是我們首先需要關心的,Linux中是把這個程式的執行地址的開頭作為引數傳遞給作業系統然後由系統將它啟動,系列文章中有涉及。從巨集觀的角度

Linux核心分析:程序的描述和程序的建立

一、Linux中的程序簡析 程序是具有多道程式設計的作業系統的基本概念,關於程序的定義就是程式執行的一個例項,也是系統資源排程的最小單位。如果同一個程式被多個使用者同時執行,那麼這個程式就有多個相對獨立的程序,與此同時他們又共享相同的執行程式碼。在Li

Linux核心分析——扒開系統呼叫的三層皮

張瑜 《Linux核心分析》MOOC課程 http://mooc.study.163.com/course/USTC-1000029000 一、實驗內容 1. 通過核心的方式使用系統呼叫 上週是從使用者態來看系統呼叫,這周是從核心方面來看這個問題

Linux核心分析系統呼叫過程解析

禹曉博+ 原創作品轉載請註明出處 + 歡迎加入《Linux核心分析》MOOC網易雲課堂學習 一、系統呼叫流程分析         系統呼叫系統呼叫就是使用者空間應用程式和核心提供的服務之間的一個介面。由於服務是在核心中提供的,因此無法執行直接呼叫;相反,我們必須使用一個程序

第8節 理解程序排程時機跟蹤分析程序排程與程序切換的過程【Linux核心分析

一、實驗要求 分析並理解Linux中程序排程與程序切換過程,仔細分析程序的排程時機、switch_to及對應的堆疊狀態。需要總結並闡明自己對“Linux系統一般執行過程”的理解 二、實驗內容 理解Linux系統中程序排程的時機,可以在核心程式

Linux核心分析系統呼叫,使用者態及核心態

禹曉博+ 原創作品轉載請註明出處 + 歡迎加入《Linux核心分析》MOOC網易雲課堂學習 一、什麼是系統呼叫 我們知道由於種種原因(就是安全穩定性大部分)的考慮,作業系統是不能讓使用者直接進行一些有可能破換系統的行為,實際上還有另外一部分原因及時基於封裝性的考慮。作業系統

Linux核心分析第二次作業

  這周學習了《庖丁解牛Linux核心分析》並且學習了實驗樓的相關知識。 在實驗樓的虛擬環境下編寫程式碼: 通過gcc編譯後,使用檢視檔案命令:cat  -n 20189223.c        在vim中,通過“g/\.s

PostgreSQL資料庫核心分析學習

PostgreSQL 資料庫由連線管理系統(系統控制器)、編譯執行系統、儲存管理系統、事務系統和系統表五大部分組成。 2.1系統表 資料字典是關係資料庫系統管理控制資訊的核心 在postgreSQL資料庫系統中,系統表扮演資料字典的角色 存放結構元資料,

Linux 核心分析及應用

編輯推薦 本書分模組介紹了 Linux 作業系統的核心設計和實現,針對關鍵概念、演算法和資料結構做了重點的講解。同時,對諸多經典應用程式進行了剖析,如 Nginx、Memcached、Redis、LVS 等,講解如何利用作業系統提供的底層支援進行合理的應用設計和實現。 內容簡介 本書由架構師親

Linux核心分析第六次作業

分析system_call中斷處理過程 一、先在實驗樓的虛擬機器中MenuOs增加utsname和utsname-asm指令。 具體實現如下: 1、克隆最新新版本的menu,之後進入menu 2、進入test.c,完成之後make rootfs,使系統自動編譯自動執行 3.設定分割點,用gdb追