cgroup學習(七) cpu子系統

2021-06-16 10:13:00 字數 4545 閱讀 4282

cpu子系統

對於cpu子系統最常見的引數就是cpu.shares,我們來通過《cgroup學習(三)——偽檔案》的**來跟蹤一下對該引數的讀寫操作。

通過systemtap我們可以看到讀的bt:(cat cpu.shares)

2327 (cat) cpu_shares_read_u64 call trace:

0xffffffff8104d0a0 : cpu_shares_read_u64+0x0/0x20[kernel]

0xffffffff810be3aa :cgroup_file_read+0xaa/0x100 [kernel]

0xffffffff811786a5 : vfs_read+0xb5/0x1a0[kernel]

0xffffffff811787e1 : sys_read+0x51/0x90[kernel]

0xffffffff8100b0f2 :system_call_fastpath+0x16/0x1b [kernel]

在上面我們已經說過,在建立cgroup的時候將對檔案的操作cftype儲存到file->f_dentry->d_fsdata,同時cgroup資訊儲存在目錄的dentry->d_fsdata,所以當通過vfs進入cgroup檔案系統裡時,通過cgroup_file_read獲得這些資訊後,直接呼叫該檔案的cpu_shares_read_u64:
static u64 cpu_shares_read_u64(struct cgroup *cgrp, struct cftype *cft)

#define container_of(ptr, type, member) ()

/* return corresponding task_group object of a cgroup */

static inline struct task_group *cgroup_tg(struct cgroup *cgrp)

static inline struct cgroup_subsys_state *cgroup_subsys_state(

struct cgroup *cgrp, int subsys_id)

上面的四個函式我們可以清楚的看到如何從乙個cgroup轉換到對應子系統的控制體的抽象類(cgroup_subsys_state),然後再轉換到實現類(task_group)的過程,最後從實現類中取得shares值。

寫操作與上面的流程差不多,不過在介紹寫效果前,我們先簡單了解一下linux的cfs組排程。

在linux核心中,使用task_group結構來管理組排程的組。所有存在的task_group組成乙個樹型結構(與cgroup一樣)。乙個組也是乙個排程實體(最終被抽象為sched_entity,跟普通task一樣),這個排程實體被新增到其父task_group的執行佇列(se->cfs_rq)。與普通task不一樣的是:task_group的sched_entity是每個cpu有乙個,並且每個cpu也有對應的執行佇列cfs_rq。

乙個task_group可以包含具有任意排程類別的程序(具體來說是實時程序和普通程序兩種類別),task_group包含實時程序對應的排程實體和排程佇列,以及普通程序對應的排程實體和排程佇列。見下面的結構定義:

struct task_group
如果組在該cpu上有可以執行的程序,則組在該cpu上的排程實體se(組本身的排程實體)就會掛到該cpu上cfs_rq的紅黑樹上(樹的最左邊葉子節點最先被呼叫);組在該cpu上可以執行的程序(組內程序的排程實體)則掛到了組排程實體se中my_q指向的紅黑樹上。即從根組開始遞迴排程直到底層組內的普通程序被排程,它們採用的是一樣的cfs排程演算法。

cfs組的優先順序:cfs 不直接使用優先順序而是將其用作允許任務執行的時間的衰減係數。低優先順序任務具有更高的衰減係數,而高優先順序任務具有較低的衰減係數。這意味著與高優先順序任務相比,低優先順序任務允許任務執行的時間消耗得更快。組在建立時其優先順序是固定的,其nice值為0(它對應的wegiht值是1024,其實在cfs排程器中所有的優先順序nice值最終都會變轉換為它唯一識別的weight值prio_to_weight)。組在某cpu上預設所能獲得的執行時間和乙個單獨的nice為0的程序獲得的執行時間相同。組的se在獲得了一定執行時間後,按照cfs演算法相同的方法把實際執行時間分配給它的my_q上的所有程序(se本身所在的執行隊列為se->cfs_rq,se下面的se存在的執行隊列為se->my_q)。

上面我們說過執行佇列是一棵紅黑樹,那麼這棵樹的key是什麼?在cfs排程演算法裡維護著乙個vruntime,它表示該排程實體的虛擬執行時間,而它也就是這棵紅黑樹的key。另外,每個排程實體的理想執行時間為ideal_time:

vruntime +=  delta*nice_0_load/se.load->weight;

ideal_time = __sched_period(nr_running)*se.load->weight/cfs_rq.load->weight

其中delta為當前se從上次被排程執行到當前的實際執行時間,__sched_period確定延遲排程的週期長度(它由當前cfs_rq的長度線性擴充套件),從上面兩個公式可以知道:在執行時間相等的條件下(delta相同),排程實體的weight值越大,它的vruntime增長的越慢,它也就越容易再被排程(在樹的左邊);同樣獲得的理想執行時間也越多,注:該值只是用來確定當前程序是否該被換出,它並不是程序被排程時能夠執行的時間(對於cfs不存在這樣的時間片),在cfs裡程序的換入換出原則上都是由自己決定的。上面兩個公司也是cpu.shares最終起作用的地方。所以當兩個cgroup它們的shares值為1:2時,那麼這兩個組的整體執行時間將保持在1:2,而與它們組內的task個數及優先順序無關。group執行時間已由shares值,等待時間等確定了,它們內部的所有tasks只能去共享這些時間(如果組內的程序有優先順序不同,那麼它們同樣按照cfs演算法去分配這個總的時間,高優先順序的獲得的時間多,低優先順序的獲得的時間少),而不會去增加組的總共執行時間。

下面我們再來看一下寫過程,它最終會呼叫sched_group_set_shares來修改該task_group的權重:

tg->shares = shares;

for_each_possible_cpu(i)

首先更新該task_group的shares值,然後更新該task_group在每個cpu上的執行佇列上的該排程實體的相應值se->load->weight(update_cfs_shares):
load = cfs_rq->load.weight;  //這個值在reweight_entity裡可能被更新

load_weight = atomic_read(&tg->load_weight);

load_weight -= cfs_rq->load_contribution;

load_weight += load;

shares = (tg->shares * load);

if (load_weight)

shares /= load_weight;

reweight_entity(cfs_rq_of(se), se, shares);

可以看到這個se->load->weight是經過tg->shares重新計算的結果,最終呼叫reweight_entity去update_load_set(&se->load, weight);這樣不是更新完該tg的在每個cpu上的se->load->weight嗎,為什麼在sched_group_set_shares還需要呼叫for_each_sched_entity(se)來對該se至頂層root的所有se進行更新?原因在於當我們更新該層的se->load時,該se所在的上層se->cfs_rq權重也會被更新(reweight_entity先減去原來的se->load值,再加上新的值),通過上面update_cfs_shares函式我們可以看到se->load->weight是由當前層的cfs_rq->load.weight決定的,即當下層的se->load->weight被更新時,它可能會更新該se所在的cfs_rq的權重(而不是它管理的下層執行佇列my_q),從而影響到上層se的load->weight。這些更新將最終體現在下次計算vruntime的結果上。

上面我們介紹了對shares這個偽檔案的操作,及這個值是如何去影響組內的tasks。其它的引數偽檔案也是類似的分析過程。另外,在前面的attach task中我們介紹了attach的第乙個過程,下面我們分析一下第二個過程在cpu子系統中的實現,簡單的跟蹤一下**可以查詢該過程最終呼叫__sched_move_task:

void __sched_move_task(struct task_struct *tsk)

這樣我們就把cpu子系統的shares檔案讀寫及attach task操作介紹完了。寫操作的簡單理解就是通過更新task_group的shares值來更新排程實體的weight,最終影響該group及上層group的vruntime;attach則是簡單地把乙個task從乙個cgroup轉換到另乙個cgroup(這其中要考慮程序是否已經在執行佇列裡或者已經在執行)。雖然盡力想把cfs排程看明白,但由於時間及能力有限,所以上面關係cfs的內容可能有出錯,期待大家指正。

參考:

cpu子系統的組排程

組排程屬於cgroup中的cpu子系統 cpu子系統的所有操作都在cpu cgrp subsys 中有定義 struct cgroup subsys cpu cgrp subsys 我們這裡以css alloc 為例 static struct cgroup subsys state cpu cgr...

Linux MTD子系統學習(一)

mtd memory technology device 記憶體技術裝置,是linux用於描述rom,nand,nor等裝置的子系統的抽象,mtd裝置可以按塊讀寫也可以按位元組讀寫,也就是說mtd裝置既可以是塊裝置也可以是字元裝置,塊裝置 mtdblackx 操作針對檔案系統,字元裝置 mtdx 操...

mmc子系統學習筆記二 關於mmc子系統筆記的說明

mmc子系統是linux裝置驅動中乙個不可缺少的部分,但科技進步,時代發展,現在mmc已經不是從前的mmc card 現在mmc子系統已經衍生到sd,sdio相關的技術了,也就是說mmc子系統已經能夠管理控制sd和sdio相關了,但是由於歷史的原因,仍稱呼為mmc子系統。mmc子系統差異說明 本mm...