Linux CFS调度器之负荷权重load_weight--Linux进程的管理与调度(二十五)
| 2016-07-29 | Linux-4.6 | X86 & arm | gatieme | LinuxDeviceDrivers | Linux進程管理與調度 |
Linux內核使用CFS是來調度我們最常見的普通進程, 其所屬調度器類為fair_sched_class, 使用的調度策略包括SCHED_NORMAL和SCHED_BATCH, 進程task_struct中struct sched_entity se;字段標識的就是CFS調度器類的調度實體.
前面我們詳細的了解了linux下進程優(yōu)先級的表示以及其計算的方法, 我們了解到linux針對普通進程和實時進程分別使用靜態(tài)優(yōu)先級static_prio和實時優(yōu)先級rt_priority來指定其默認的優(yōu)先級別, 然后通過normal_prio函數將他們分別轉換為普通優(yōu)先級normal_prio, 最終換算出動態(tài)優(yōu)先級prio, 動態(tài)優(yōu)先級prio才是內核調度時候有限考慮的優(yōu)先級字段
但是CFS完全公平調度器在調度進程的時候, 進程的重要性不僅是由優(yōu)先級指定的, 而且還需要考慮保存在task_struct->se.load的負荷權重.
1 前景回顧
1.1 linux調度器的演變
一開始的調度器是復雜度為O(n)的始調度算法(實際上每次會遍歷所有任務,所以復雜度為O(n)), 這個算法的缺點是當內核中有很多任務時,調度器本身就會耗費不少時間,所以,從linux2.5開始引入赫赫有名的O(1)調度器
然而,linux是集全球很多程序員的聰明才智而發(fā)展起來的超級內核,沒有最好,只有更好,在O(1)調度器風光了沒幾天就又被另一個更優(yōu)秀的調度器取代了,它就是CFS調度器Completely Fair Scheduler. 這個也是在2.6內核中引入的,具體為2.6.23,即從此版本開始,內核使用CFS作為它的默認調度器,O(1)調度器被拋棄了, 其實CFS的發(fā)展也是經歷了很多階段,最早期的樓梯算法(SD), 后來逐步對SD算法進行改進出RSDL(Rotating Staircase Deadline Scheduler), 這個算法已經是”完全公平”的雛形了, 直至CFS是最終被內核采納的調度器, 它從RSDL/SD中吸取了完全公平的思想,不再跟蹤進程的睡眠時間,也不再企圖區(qū)分交互式進程。它將所有的進程都統(tǒng)一對待,這就是公平的含義。CFS的算法和實現(xiàn)都相當簡單,眾多的測試表明其性能也非常優(yōu)越
| O(n)的始調度算法 | linux-0.11~2.4 |
| O(1)調度器 | linux-2.5 |
| CFS調度器 | linux-2.6~至今 |
1.2 Linux的調度器組成
2個調度器
可以用兩種方法來激活調度
一種是直接的, 比如進程打算睡眠或出于其他原因放棄CPU
另一種是通過周期性的機制, 以固定的頻率運行, 不時的檢測是否有必要
因此當前l(fā)inux的調度程序由兩個調度器組成:主調度器,周期性調度器(兩者又統(tǒng)稱為通用調度器(generic scheduler)或核心調度器(core scheduler))
并且每個調度器包括兩個內容:調度框架(其實質就是兩個函數框架)及調度器類
6種調度策略
linux內核目前實現(xiàn)了6中調度策略(即調度算法), 用于對不同類型的進程進行調度, 或者支持某些特殊的功能
SCHED_NORMAL和SCHED_BATCH調度普通的非實時進程
SCHED_FIFO和SCHED_RR和SCHED_DEADLINE則采用不同的調度策略調度實時進程
SCHED_IDLE則在系統(tǒng)空閑時調用idle進程.
5個調度器類
而依據其調度策略的不同實現(xiàn)了5個調度器類, 一個調度器類可以用一種種或者多種調度策略調度某一類進程, 也可以用于特殊情況或者調度特殊功能的進程.
其所屬進程的優(yōu)先級順序為
stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class3個調度實體
調度器不限于調度進程, 還可以調度更大的實體, 比如實現(xiàn)組調度.
這種一般性要求調度器不直接操作進程, 而是處理可調度實體, 因此需要一個通用的數據結構描述這個調度實體,即seched_entity結構, 其實際上就代表了一個調度對象,可以為一個進程,也可以為一個進程組.
linux中針對當前可調度的實時和非實時進程, 定義了類型為seched_entity的3個調度實體
sched_dl_entity 采用EDF算法調度的實時調度實體
sched_rt_entity 采用Roound-Robin或者FIFO算法調度的實時調度實體
sched_entity 采用CFS算法調度的普通非實時進程的調度實體
調度器整體框架
每個進程都屬于某個調度器類(由字段task_struct->sched_class標識), 由調度器類采用進程對應的調度策略調度(由task_struct->policy )進行調度, task_struct也存儲了其對應的調度實體標識
linux實現(xiàn)了6種調度策略, 依據其調度策略的不同實現(xiàn)了5個調度器類, 一個調度器類可以用一種或者多種調度策略調度某一類進程, 也可以用于特殊情況或者調度特殊功能的進程.
| stop_sched_class | 無 | 無 | 無 | 特殊情況, 發(fā)生在cpu_stop_cpu_callback 進行cpu之間任務遷移migration或者HOTPLUG_CPU的情況下關閉任務 |
| dl_sched_class | SCHED_DEADLINE | Earliest-Deadline-First最早截至時間有限算法 | sched_dl_entity | 采用DEF最早截至時間有限算法調度實時進程 |
| rt_sched_class | SCHED_RR SCHED_FIFO | Roound-Robin時間片輪轉算法 FIFO先進先出算法 | sched_rt_entity | 采用Roound-Robin或者FIFO算法調度的實時調度實體 |
| fair_sched_class | SCHED_NORMAL SCHED_BATCH | CFS完全公平懂調度算法 | sched_entity | 采用CFS算法普通非實時進程 |
| idle_sched_class | SCHED_IDLE | 無 | 無 | 特殊進程, 用于cpu空閑時調度空閑進程idle |
它們的關系如下圖
調度器的組成
1.3 優(yōu)先級的內核表示
內核使用一些簡單的數值范圍0~139表示內部優(yōu)先級, 數值越低, 優(yōu)先級越高。
從0~99的范圍專供實時進程使用, nice的值[-20,19]則映射到范圍100~139
實時優(yōu)先級范圍是0到MAX_RT_PRIO-1(即99),而普通進程的靜態(tài)優(yōu)先級范圍是從MAX_RT_PRIO到MAX_PRIO-1(即100到139)。
| 0——99 | 實時進程 |
| 100——139 | 非實時進程 |
1.4 進程的優(yōu)先級表示
struct task_struct {/* 進程優(yōu)先級* prio: 動態(tài)優(yōu)先級,范圍為100~139,與靜態(tài)優(yōu)先級和補償(bonus)有關* static_prio: 靜態(tài)優(yōu)先級,static_prio = 100 + nice + 20 (nice值為-20~19,所以static_prio值為100~139)* normal_prio: 沒有受優(yōu)先級繼承影響的常規(guī)優(yōu)先級,具體見normal_prio函數,跟屬于什么類型的進程有關*/int prio, static_prio, normal_prio;/* 實時進程優(yōu)先級 */unsigned int rt_priority; }
動態(tài)優(yōu)先級 靜態(tài)優(yōu)先級 實時優(yōu)先級
其中task_struct采用了三個成員表示進程的優(yōu)先級:prio和normal_prio表示動態(tài)優(yōu)先級, static_prio表示進程的靜態(tài)優(yōu)先級.
此外還用了一個字段rt_priority保存了實時進程的優(yōu)先級
| static_prio | 用于保存靜態(tài)優(yōu)先級, 是進程啟動時分配的優(yōu)先級, ,可以通過nice和sched_setscheduler系統(tǒng)調用來進行修改, 否則在進程運行期間會一直保持恒定 |
| prio | 保存進程的動態(tài)優(yōu)先級 |
| normal_prio | 表示基于進程的靜態(tài)優(yōu)先級static_prio和調度策略計算出的優(yōu)先級. 因此即使普通進程和實時進程具有相同的靜態(tài)優(yōu)先級, 其普通優(yōu)先級也是不同的, 進程分叉(fork)時, 子進程會繼承父進程的普通優(yōu)先級 |
| rt_priority | 用于保存實時優(yōu)先級 |
實時進程的優(yōu)先級用實時優(yōu)先級rt_priority來表示
2 負荷權重
2.1 負荷權重結構struct load_weight
負荷權重用struct load_weight數據結構來表示, 保存著進程權重值weight。其定義在/include/linux/sched.h, v=4.6, L1195, 如下所示
struct load_weight {unsigned long weight; /* 存儲了權重的信息 */u32 inv_weight; /* 存儲了權重值用于重除的結果 weight * inv_weight = 2^32 */ };2.2 調度實體的負荷權重load
既然struct load_weight保存著進程的權重信息, 那么作為進程調度的實體, 必須將這個權重值與特定的進程task_struct, 更一般的與通用的調度實體sched_entity相關聯(lián)
struct sched_entity作為進程調度的實體信息, 其內置了load_weight結構用于保存當前調度實體的權重, 參照http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.6#L1195
struct sched_entity {struct load_weight load; /* for load-balancing *//* ...... */ };2.3 進程的負荷權重
而進程可以被作為一個調度的實時, 其內部通過存儲struct sched_entity se而間接存儲了其load_weight信息, 參照http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.6#L1415
struct task_struct {/* ...... */struct sched_entity se;/* ...... */ }因此我們就可以通過task_statuct->se.load獲取負荷權重的信息, 而set_load_weight負責根據進程類型及其靜態(tài)優(yōu)先級計算符合權重.
3 優(yōu)先級和權重的轉換
3.1 優(yōu)先級->權重轉換表
一般這個概念是這樣的, 進程每降低一個nice值(優(yōu)先級提升), 則多獲得10%的CPU時間, 沒升高一個nice值(優(yōu)先級降低), 則放棄10%的CPU時間.
為執(zhí)行該策略, 內核需要將優(yōu)先級轉換為權重值, 并提供了一張優(yōu)先級->權重轉換表sched_prio_to_weight, 內核不僅維護了負荷權重自身, 還保存另外一個數值, 用于負荷重除的結果, 即sched_prio_to_wmult數組, 這兩個數組中的數據是一一對應的.
其中相關的數據結構定義在kernel/sched/sched.h?v=4.6, L1132
// http://lxr.free-electrons.com/source/kernel/sched/sched.h?v=4.6#L1132 /** To aid in avoiding the subversion of "niceness" due to uneven distribution* of tasks with abnormal "nice" values across CPUs the contribution that* each task makes to its run queue's load is weighted according to its* scheduling class and "nice" value. For SCHED_NORMAL tasks this is just a* scaled version of the new time slice allocation that they receive on time* slice expiry etc.*/#define WEIGHT_IDLEPRIO 3 /* SCHED_IDLE進程的負荷權重 */ #define WMULT_IDLEPRIO 1431655765 /* SCHED_IDLE進程負荷權重的重除值 */extern const int sched_prio_to_weight[40]; extern const u32 sched_prio_to_wmult[40];// http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L8484 /* * Nice levels are multiplicative, with a gentle 10% change for every * nice level changed. I.e. when a CPU-bound task goes from nice 0 to * nice 1, it will get ~10% less CPU time than another CPU-bound task * that remained on nice 0. * * The "10% effect" is relative and cumulative: from _any_ nice level, * if you go up 1 level, it's -10% CPU usage, if you go down 1 level * it's +10% CPU usage. (to achieve that we use a multiplier of 1.25. * If a task goes up by ~10% and another task goes down by ~10% then * the relative distance between them is ~25%.) */ 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, };/* * Inverse (2^32/x) values of the sched_prio_to_weight[] array, precalculated. * * In cases where the weight does not change often, we can use the * precalculated inverse to speed up arithmetics by turning divisions * into multiplications: */ const u32 sched_prio_to_wmult[40] = { /* -20 */ 48388, 59856, 76040, 92818, 118348, /* -15 */ 147320, 184698, 229616, 287308, 360437, /* -10 */ 449829, 563644, 704093, 875809, 1099582, /* -5 */ 1376151, 1717300, 2157191, 2708050, 3363326, /* 0 */ 4194304, 5237765, 6557202, 8165337, 10153587, /* 5 */ 12820798, 15790321, 19976592, 24970740, 31350126, /* 10 */ 39045157, 49367440, 61356676, 76695844, 95443717, /* 15 */ 119304647, 148102320, 186737708, 238609294, 286331153, };對內核使用的范圍[-20, 19]中的每個nice級別, sched_prio_to_weight數組都有一個對應項
nice [-20, 19] -=> 下標 [0, 39]
而由于權重weight 用unsigned long 表示, 因此內核無法直接存儲1/weight, 而必須借助于乘法和位移來執(zhí)行除法的技術. sched_prio_to_wmult數組就存儲了這些值, 即sched_prio_to_wmult每個元素的值是2^32/prio_to_weight$每個元素的值.
可以驗證
sched_prio_to_wmult[i]=232sched_prio_to_weight[i]
同時我們可以看到其定義了兩個宏WEIGHT_IDLEPRIO和WMULT_IDLEPRIO這兩個宏對應的就是SCHED_IDLE調度的進程的負荷權重信息, 因為要保證SCHED_IDLE進程的最低優(yōu)先級和最低的負荷權重. 這點信息我們可以在后面分析set_load_weight函數的時候可以看到
可以驗證
232WEIGHTIDLEPRIO=WMULTIDLEPRIO
3.2 linux-4.4之前的shced_prio_to_weight和sched_prio_to_wmult
關于優(yōu)先級->權重轉換表sched_prio_to_weight
在linux-4.4之前的內核中, 優(yōu)先級權重轉換表用prio_to_weight表示, 定義在kernel/sched/sched.h, line 1116, 與它一同定義的還有prio_to_wmult, 在kernel/sched/sched.h, line 1139
均被定義為static const
但是其實這種能夠方式不太符合規(guī)范的編碼風格, 因此常規(guī)來說, 我們的頭文件中不應該存儲結構的定義, 即為了是程序的模塊結構更加清晰, 頭文件中盡量只包含宏或者聲明, 而將具體的定義, 需要分配存儲空間的代碼放在源文件中.
否則如果在頭文件中定義全局變量,并且將此全局變量賦初值,那么在多個引用此頭文件的C文件中同樣存在相同變量名的拷貝,關鍵是此變量被賦了初值,所以編譯器就會將此變量放入DATA段,最終在連接階段,會在DATA段中存在多個相同的變量,它無法將這些變量統(tǒng)一成一個變量,也就是僅為此變量分配一個空間,而不是多份空間,假定這個變量在頭文件沒有賦初值,編譯器就會將之放入BSS段,連接器會對BSS段的多個同名變量僅分配一個存儲空間
因此在新的內核中, 內核黑客們將這兩個變量存放在了kernel/sched/core.c, 并加上了sched_前綴, 以表明這些變量是在進程調度的過程中使用的, 而在kernel/sched/sched.h, line 1144中則只包含了他們的聲明.
下面我們列出優(yōu)先級權重轉換表定義更新后對比項
| <= linux-4.4 | static const int prio_to_weight[40] | kernel/sched/sched.h, line 1116 |
| >=linux-4.5 | const int sched_prio_to_weight[40] | 聲明在kernel/sched/sched.h, line 1144, 定義在kernel/sched/core.c |
其定義并沒有發(fā)生變化, 依然是一個一對一NICE to WEIGHT的轉換表
3.3 1.25的乘積因子
各數組之間的乘積因子是1.25. 要知道為何使用該因子, 可考慮下面的例子
兩個進程A和B在nice級別0, 即靜態(tài)優(yōu)先級120運行, 因此兩個進程的CPU份額相同, 都是50%, nice級別為0的進程, 查其權重表可知是1024. 每個進程的份額是1024/(1024+1024)=0.5, 即50%
如果進程B的優(yōu)先級+1(優(yōu)先級降低), 成為nice=1, 那么其CPU份額應該減少10%, 換句話說進程A得到的總的CPU應該是55%, 而進程B應該是45%. 優(yōu)先級增加1導致權重減少, 即1024/1.25=820, 而進程A仍舊是1024, 則進程A現(xiàn)在將得到的CPU份額是1024/(1024+820=0.55, 而進程B的CPU份額則是820/(1024+820)=0.45. 這樣就正好產生了10%的差值.
4 進程負荷權重的計算
set_load_weight負責根據非實時進程類型極其靜態(tài)優(yōu)先級計算符合權重
而實時進程不需要CFS調度, 因此無需計算其負荷權重值
早期的代碼中實時進程也是計算其負荷權重的, 但是只是采用一些方法保持其權重值較大
在早期有些版本中, set_load_weight中實時進程的權重是普通進程的兩倍, 后來又設置成0, 直到后來linux-2.6.37開始不再設置實時進程的優(yōu)先級, 因此這本身就是一個無用的工作
而另一方面, SCHED_IDLE進程的權值總是非常小, 普通非實時進程則根據其靜態(tài)優(yōu)先級設置對應的負荷權重
4.1 set_load_weight依據靜態(tài)優(yōu)先級設置進程的負荷權重
static void set_load_weight(struct task_struct *p) {/* 由于數組中的下標是0~39, 普通進程的優(yōu)先級是[100~139]因此通過static_prio - MAX_RT_PRIO將靜態(tài)優(yōu)先級轉換成為數組下標*/int prio = p->static_prio - MAX_RT_PRIO;/* 取得指向進程task負荷權重的指針load,下面修改load就是修改進程的負荷權重 */struct load_weight *load = &p->se.load;/** SCHED_IDLE tasks get minimal weight:* 必須保證SCHED_IDLE進程的負荷權重最小* 其權重weight就是WEIGHT_IDLEPRIO* 而權重的重除結果就是WMULT_IDLEPRIO*/if (p->policy == SCHED_IDLE) {load->weight = scale_load(WEIGHT_IDLEPRIO);load->inv_weight = WMULT_IDLEPRIO;return;}/* 設置進程的負荷權重weight和權重的重除值inv_weight */load->weight = scale_load(prio_to_weight[prio]);load->inv_weight = prio_to_wmult[prio]; }
4.2 scale_load取得負荷權重的值
其中scale_load是一個宏, 定義在include/linux/sched.h, line 785
#if 0 /* BITS_PER_LONG > 32 -- currently broken: it increases power usage under light load */ # define SCHED_LOAD_RESOLUTION 10 # define scale_load(w) ((w) << SCHED_LOAD_RESOLUTION) # define scale_load_down(w) ((w) >> SCHED_LOAD_RESOLUTION) #else # define SCHED_LOAD_RESOLUTION 0 # define scale_load(w) (w) # define scale_load_down(w) (w) #endif我們可以看到目前版本的scale_load其實什么也沒做就是簡單取了個值, 但是我們注意到負荷權重仍然保留了SCHED_LOAD_RESOLUTION不為0的情形, 只不過目前因為效率原因和功耗問題沒有啟用而已
4.3 set_load_weight的演變
linux內核的調度器經過了不同階段的發(fā)展, 但是即使是同一個調度器其算法也不是一成不變的, 也在不停的改進和優(yōu)化
| 2.6.18~2.6.22 | 實時進程的權重用RTPRIO_TO_LOAD_WEIGHT(p->rt_priority);轉換 | kernel/sched.c#L746 |
| 2.6.23~2.6.34 | 實時進程的權重為非實時權重的二倍 | kernel/sched.c#L1836 |
| 2.6.35~2.6.36 | 實時進程的權重設置為0, 重除值設置為WMULT_CONST | kernel/sched.c, L1859 |
| 2.6.37~至今4.6 | 實時進程不再設置權重 | 其中<= linux-3.2時, 代碼在sched.c中 3.3~4.4之后, 增加了sched/core.c文件調度的核心代碼在此存放 4.5~至今, 修改prio_to_weight為sched_prio_to_weight, 并將聲明存放頭文件中 |
5 就緒隊列的負荷權重
不僅進程, 就緒隊列也關聯(lián)到一個負荷權重. 這個我們在前面講Linux進程調度器的設計–Linux進程的管理與調度(十七)的時候提到過了在cpu的就緒隊列rq和cfs調度器的就緒隊列cfs_rq中都保存了其load_weight.
這樣不僅確保就緒隊列能夠跟蹤記錄有多少進程在運行, 而且還能將進程的權重添加到就緒隊列中.
5.1 cfs就緒隊列的負荷權重
// http://lxr.free-electrons.com/source/kernel/sched/sched.h?v=4.6#L596 struct rq {/* ...... *//* capture load from *all* tasks on this cpu: */struct load_weight load;/* ...... */ };// http://lxr.free-electrons.com/source/kernel/sched/sched.h?v=4.6#L361 /* CFS-related fields in a runqueue */ struct cfs_rq {struct load_weight load;unsigned int nr_running, h_nr_running;/* ...... */ };// http://lxr.free-electrons.com/source/kernel/sched/sched.h?v=4.6#L596 struct rt_rq中不需要負荷權重// http://lxr.free-electrons.com/source/kernel/sched/sched.h?v=4.6#L490 struct dl_rq中不需要負荷權重
由于負荷權重僅用于調度普通進程(非實時進程), 因此只在cpu的就緒隊列隊列rq和cfs調度器的就緒隊列cfs_rq上需要保存其就緒隊列的信息, 而實時進程的就緒隊列rt_rq和dl_rq
是不需要保存負荷權重的.
5.2 就緒隊列的負荷權重計算
就緒隊列的負荷權重存儲的其實就是隊列上所有進程的負荷權重的總和, 因此每次進程被加到就緒隊列的時候, 就需要在就緒隊列的負荷權重中加上進程的負荷權重, 同時由于就緒隊列的不是一個單獨被調度的實體, 也就不需要優(yōu)先級到負荷權重的轉換, 因而其不需要負荷權重的重除字段, 即inv_weight = 0;
因此進程從就緒隊列上入隊或者出隊的時候, 就緒隊列的負荷權重就加上或者減去進程的負荷權重, 但是
//struct load_weight {/* 就緒隊列的負荷權重 +/- 入隊/出隊進程的負荷權重 */unsigned long weight +/- task_struct->se->load->weight;/* 就緒隊列負荷權重的重除字段無用途,所以始終置0 */u32 inv_weight = 0; //};因此內核為我們提供了增加/減少/重置就緒隊列負荷權重的的函數, 分別是update_load_add, update_load_sub, update_load_set
/* 使得lw指向的負荷權重的值增加inc, 用于進程進入就緒隊列時調用* 進程入隊 account_entity_enqueue kernel/sched/fair.c#L2422*/static inline void update_load_add(struct load_weight *lw, unsigned long inc) {lw->weight += inc;lw->inv_weight = 0; }/* 使得lw指向的負荷權重的值減少inc, 用于進程調出就緒隊列時調用* 進程出隊 account_entity_dequeue kernel/sched/fair.c#L2422*/ static inline void update_load_sub(struct load_weight *lw, unsigned long dec) {lw->weight -= dec;lw->inv_weight = 0; }static inline void update_load_set(struct load_weight *lw, unsigned long w) {lw->weight = w;lw->inv_weight = 0; }| update_load_add | 使得lw指向的負荷權重的值增加inc | 用于進程進入就緒隊列時調用 | kernel/sched/fair.c, L117 | account_entity_enqueue兩處, sched_slice |
| update_load_sub | 使得lw指向的負荷權重的值減少inc | 用于進程調出就緒隊列時調用 | update_load_sub, L123 | account_entity_dequeue兩處 |
| update_load_set |
其中sched_slice函數計算當前進程在調度延遲內期望的運行時間, 它根據cfs就緒隊列中進程數確定一個最長時間間隔,然后看在該時間間隔內當前進程按照權重比例執(zhí)行
6 總結
負荷權重load_weight
CFS完全公平調度器在調度非實時進程的時候, 進程的重要性不僅是由優(yōu)先級指定的, 還需要考慮保存在task_struct->se.load的負荷權重.
轉換表prio_to_weight和重除表ched_prio_to_wmult
這個負荷權重用struct load_weight, 其包含了名為weight的負荷權重信息, 為了方便快速的將靜態(tài)優(yōu)先級轉換成權重值, 內核提供了一個長為40的prio_to_weight數組方便轉換, 靜態(tài)優(yōu)先級[100~139], 對應nice值[-20, 19], 對應數組中的下標[0, 39]
由于權重weight 用unsigned long 表示, 因此內核無法直接存儲1/weight, 而必須借助于乘法和位移來執(zhí)行除法的技術. sched_prio_to_wmult數組就存儲了這些值, 即sched_prio_to_wmult每個元素的值是2^32/prio_to_weight$每個元素的值.
對于SCHED_IDLE進程其優(yōu)先級最低, 因此其負荷權重也要盡可能的小, 因此內核用WEIGHT_IDLEPRIO( = 3)和WMULT_IDLEPRIO分別表示了SCHED_IDLE進程的負荷權重和重除值.
調度實體負荷權重的計算
既然CFS把負荷權重作為進程調度的一個重要依據, 那么我們就需要了解調度器是如何計算進程或者調度實體的負荷權重的.
有了prio_to_weight和ched_prio_to_wmult這兩個轉換表, 我們就可以很方便的將非實時進程的靜態(tài)優(yōu)先級轉換成負荷權重, 這個其實就是一個很簡單的查表得過程, 內核用set_load_weight完成這個工作, 同時也保證了SCHED_LDLE進程的負荷權重最小
將進程的靜態(tài)優(yōu)先級[100, 139]轉換成數組下標[0, 39]
如果進程是SCHED_IDLE調度, 則負荷權重直賦值為WEIGHT_IDLEPRIO( = 3)和WMULT_IDLEPRIO
對于普通進程, 從prio_to_weight和sched_prio_to_wmult中查找出對應優(yōu)先級的負荷權重值和重除值
現(xiàn)在的內核中是實時進程是不依靠負荷權重的, 因此也就不需要計算實時進程的負荷權重, 但是早期的內核中實時進程的負荷權重設置為普通進程的兩倍, 以保證權重比非實時進程大
調度實體的負荷權重
既然load_weight保存著進程的權重信息, 那么作為進程調度的實體, 必須將這個權重值與特定的進程task_struct, 更一般的與通用的調度實體sched_entity相關聯(lián)
sched_entity作為進程調度的實體信息, 其內置了load_weight結構用于保存當前調度實體的權重, 參照
同時進程作為調度實體的最一般單位, 其load_weight就存儲在其struct sched_entity *se成員中, 可以通過task_struct->se.load訪問進程的負荷權重.
就緒隊列的負荷權重
然后內核也在全局cpu就緒隊列rq中cfs的就緒隊列cfs_rq中保存了load_weight, 這就可以很方便的統(tǒng)計出整個就緒隊列的負荷權重總和, 為進程調度提供參考, 因此在每次進程入隊或者出隊的時候就需要通過修改就緒隊列的負荷權重, 內核為我們提供了增加/減少/重置就緒隊列負荷權重的的函數, 分別是update_load_add, update_load_sub, update_load_set, 而由于就緒隊列的負荷權重只關心權重值, 因此其重除字段inv_weight恒為0
同時需要注意的是, 由于實時進程不依賴于負荷權重的, 因此實時進程的就緒隊列rt_qt和dl_rq中不需要存儲load_weight.
總結
以上是生活随笔為你收集整理的Linux CFS调度器之负荷权重load_weight--Linux进程的管理与调度(二十五)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: App红海
- 下一篇: 微信模板消息跳转小程序指定页面(非首页)