rt带宽限制浅析
(基于linux-4.4.42)
一、摘要
??? 由于實(shí)時(shí)任務(wù)的優(yōu)先級(jí)高于普通任務(wù),因而為了防止cpu消耗型的實(shí)時(shí)任務(wù)一直占用cpu引發(fā)其他任務(wù)"饑餓"的情況發(fā)生,內(nèi)核采用了帶寬限制手段來抑制實(shí)時(shí)任務(wù)的運(yùn)行時(shí)間。系統(tǒng)中將各個(gè)任務(wù)按層級(jí)組織成一個(gè)個(gè)任務(wù)組,組內(nèi)的所有任務(wù)視為一個(gè)整體掛在一個(gè)運(yùn)行隊(duì)列上,而帶寬限制的單位也是針對(duì)一個(gè)組來進(jìn)行的。??? 那么究竟什么是帶寬限制呢?在任務(wù)調(diào)度中帶寬限制就是指一定周期內(nèi)一個(gè)隊(duì)列上任務(wù)可運(yùn)行的最大時(shí)間,內(nèi)核中使用xxx_bandwidth結(jié)構(gòu)來限制任務(wù)的運(yùn)行時(shí)間。針對(duì)實(shí)時(shí)任務(wù)這個(gè)結(jié)構(gòu)就是: struct rt_bandwidth {/* nests inside the rq lock: */raw_spinlock_t rt_runtime_lock;ktime_t rt_period; u64 rt_runtime;struct hrtimer rt_period_timer;unsigned int rt_period_active; };??? 在單cpu環(huán)境中,rt_bandwidth限制了cpu上的實(shí)時(shí)任務(wù)在rt_period周期內(nèi)運(yùn)行時(shí)間不能夠超過rt_runtime;而在SMP多cpu環(huán)境中rt_bandwidth限制了系統(tǒng)中實(shí)時(shí)任務(wù)在rt_period周期內(nèi)的cpu占用時(shí)間比例不能夠超過rt_runtime/rt_period。
??? 舉個(gè)例子,在4核的SMP環(huán)境中,rt_runtime為950000,而rt_period為1000000,系統(tǒng)中所有實(shí)時(shí)任務(wù)的cpu(4個(gè)核的占用總和)占用率不能夠超過95%。
??? 在實(shí)時(shí)任務(wù)組調(diào)度使能的情況下,帶寬限制與各個(gè)組相關(guān),即一個(gè)任務(wù)組有一個(gè)rt_bandwidth,而這個(gè)組的rt_bandwidth限制了本組中實(shí)時(shí)任務(wù)的運(yùn)行時(shí)間。
二、實(shí)現(xiàn)原理
2.1 相關(guān)數(shù)據(jù)結(jié)構(gòu)
??? 內(nèi)核中通過static int sched_rt_runtime_exceeded(struct rt_rq *rt_rq)函數(shù)來判斷實(shí)時(shí)任務(wù)運(yùn)行時(shí)間是否超出帶寬限制。函數(shù)的核心思想就是判斷這個(gè)運(yùn)行隊(duì)列rt_rq的運(yùn)行時(shí)間是否超過了額定的運(yùn)行時(shí)間。而“運(yùn)行時(shí)間”和“額定時(shí)間”都是放在struct rt_rq運(yùn)行隊(duì)列這個(gè)結(jié)構(gòu)中的,我們來看下這個(gè)結(jié)構(gòu)相關(guān)的具體實(shí)現(xiàn):
/* Real-Time classes' related field in a runqueue: */ struct rt_rq {...... int rt_throttled; /* 調(diào)度限制標(biāo)志,置1表示使能調(diào)度限制 */u64 rt_time; /* rt_rq隊(duì)列中的調(diào)度實(shí)體已經(jīng)消耗的cpu時(shí)間 */u64 rt_runtime; /* rt_rq在一個(gè)周期內(nèi)的最大額定運(yùn)行時(shí)間 *//* Nests inside the rq lock: */......struct rq *rq; /* rt_rq所屬的就緒隊(duì)列rq */struct task_group *tg; /* rt_rq所屬的任務(wù)組 */ };??? 當(dāng)一個(gè)調(diào)度組task_group被創(chuàng)建時(shí)(例如在cgroup中創(chuàng)建一個(gè)cpu子系統(tǒng)時(shí)就會(huì)創(chuàng)建一個(gè)task_group)時(shí),內(nèi)核會(huì)為這個(gè)新的task_group在各個(gè)cpu上都創(chuàng)建一個(gè)運(yùn)行隊(duì)列struct rt_rq,而所有的 實(shí)時(shí)調(diào)度實(shí)體 都是掛在各自的運(yùn)行隊(duì)列rt_rq上的,整個(gè)結(jié)構(gòu)如下所示: /* task group related information */ struct task_group {struct cgroup_subsys_state css;...... #ifdef CONFIG_RT_GROUP_SCHEDstruct sched_rt_entity **rt_se; /* 一個(gè)組在每個(gè)cpu上都有一個(gè)代表這個(gè)層級(jí)組的rt_se */struct rt_rq **rt_rq; /* 一個(gè)組在每個(gè)cpu上都有一個(gè)rt_rq里面掛著屬于這個(gè)組的rt_ses */struct rt_bandwidth rt_bandwidth; /* 帶寬結(jié)構(gòu) */ #endif......struct list_head list;struct task_group *parent; ......struct cfs_bandwidth cfs_bandwidth; };2.2 組調(diào)度層級(jí)結(jié)構(gòu)
??? 在引入了組調(diào)度之后,任務(wù)的調(diào)度的對(duì)象都是調(diào)度實(shí)體,而各個(gè)調(diào)度實(shí)體又有自己的父層級(jí)實(shí)體,父層級(jí)也可以再有自己的父層級(jí)...這樣層層組織起來成為一個(gè)樹狀結(jié)構(gòu)。而各個(gè)cpu上的同一個(gè)組層級(jí)se又結(jié)合在一起組成了上面提到的層級(jí)任務(wù)組結(jié)構(gòu):struct task_group。下面是一個(gè)task_group的層級(jí)結(jié)構(gòu)示意圖:? ? ??????????????????????????????????????????????? 圖1 具有3個(gè)層的的任務(wù)組組織示意圖
??? 如上圖所示,每個(gè)task_group中都有一個(gè)rt_se**和rt_rq**,而子層級(jí)的調(diào)度實(shí)體rt_se[cpu]又都掛到父層級(jí)的的rt_rq[cpu]中。
??? 我們可以將task_group的兩個(gè)rt_se**和rt_rq**看成兩個(gè)指針數(shù)組sched_rt_entity * rt_se[cpu]和struct rt_rq *rt_rq[cpu],數(shù)組在每個(gè)cpu(cpu_possible_mask位圖中的所有cpu)上都有一個(gè)元素;
??? 子層級(jí)的調(diào)度實(shí)體都掛到本層級(jí)rt_rq隊(duì)列中,而這些調(diào)度實(shí)體都會(huì)被看成一個(gè)整體,即本層級(jí)的rt_se;一個(gè)task_group中兩者之間的關(guān)系為
tg.rt_se[cpu]->my_q == tg.rt_rq[cpu]; ? /* tg表示某一個(gè)task_group;cpu為某個(gè)具體的cpu */
tg.rt_rq[cpu]隊(duì)列掛著的所有任務(wù)都看成一個(gè)整體:tg.rt_se[cpu]。
當(dāng)tg.rt_se[cpu]->my_q == NULL 時(shí)表示這個(gè)調(diào)度實(shí)體不再是一個(gè)組,而是一個(gè)實(shí)實(shí)在在的任務(wù)。
??? 有了上面的概念,我們?cè)倏匆粋€(gè)運(yùn)行隊(duì)列的運(yùn)行時(shí)間rt_rq->rt_time和額定時(shí)間rt_rq->rt_runtime:
??? rt_rq->rt_time就表示屬于這個(gè)隊(duì)列(這個(gè)組中)的調(diào)度實(shí)體(子層級(jí)、子孫層級(jí)...一直到葉子節(jié)點(diǎn)層層累加)的運(yùn)行時(shí)間;rt_rq->rt_runtime就表示屬于此隊(duì)列的實(shí)體的額定時(shí)間。
??? 調(diào)度器在檢查一個(gè)調(diào)度實(shí)體運(yùn)行時(shí)間是否超額時(shí),實(shí)際檢查的是它所在的運(yùn)行隊(duì)列的rt_rq[cpu]->rt_time是否超過rt_rq[cpu]->rt_runtime;在SMP系統(tǒng)中,如果內(nèi)核使能了RT_RUNTIME_SHARE特性,如果運(yùn)行隊(duì)列的運(yùn)行時(shí)間已經(jīng)超額,則會(huì)嘗試去其他cpu上的rt_rq隊(duì)列中“借”時(shí)間以擴(kuò)張rt_rq[cpu]->rt_runtime。
??? 不論如何,如果運(yùn)行隊(duì)列的運(yùn)行時(shí)間rt_rq->rt_time超額且在使能了帶寬限制的情況下,會(huì)使能rt_rq的調(diào)度限制標(biāo)志:rt_rq->rt_throttled = 1,調(diào)度受限標(biāo)志一旦使能,后續(xù)就rt_rq就無(wú)法得到調(diào)度。
三、帶寬限制的來龍去脈
3.1 帶寬和運(yùn)行時(shí)間初始化
3.1.1 root組帶寬初始化
??? 最頂層的root組是所有task_group的祖先,內(nèi)核中用全局變量struct task_group root_task_group定義。它在內(nèi)核啟動(dòng)初期由sche_init()進(jìn)行初始化的:
start_kernel()|sched_init()??? 我們來看一下sched_init()做了哪些工作:??? 1) 帶寬初始化
??? 針對(duì)實(shí)時(shí)任務(wù),這里會(huì)對(duì)兩個(gè)帶寬結(jié)構(gòu)進(jìn)行初始化:全局struct rt_bandwidth def_rt_bandwidth和root_task_group.rt_bandwidth,并且將他們的帶寬周期和運(yùn)行時(shí)間額度都初始化為相同的值: def_rt_bandwidth.rt_period = sysctl_sched_rt_period; def_rt_bandwidth.rt_runtime = sysctl_sched_rt_runtime; root_task_group.rt_bandwidth.rt_period = sysctl_sched_rt_period; root_task_group.rt_bandwidth.rt_runtime = sysctl_sched_rt_runtime;??? 其中sysctl_sched_rt_period和sysctl_sched_rt_runtime值都是內(nèi)核定義的全局變量,可以通過/proc/sys/kernel/sched_rt_period_us 和 /proc/sys/kernel/sched_rt_runtime_us 分別查看;默認(rèn)情況下這兩個(gè)變量的值分別為1000000 和 950000,單位都是是微秒。
??? 這樣初始化以后,默認(rèn)情況下def_rt_bandwidth和root_task_group.rt_bandwidth的周期為1秒;而它們的額定時(shí)間為0.95秒。
??? 2)調(diào)度實(shí)體和運(yùn)行隊(duì)列初始化
??? 根組root_task_group的 struct sched_rt_entity **rt_se 和 struct rt_rq **rt_rq 成員實(shí)際上是兩個(gè)指針數(shù)組,每個(gè)數(shù)組成員與一個(gè)cpu相對(duì)應(yīng);root_task_group中的這些調(diào)度實(shí)體和運(yùn)行隊(duì)列最終是需要和每個(gè)cpu上就緒隊(duì)列rq中的成員關(guān)聯(lián)起來的,效果如下偽代碼所示: rq[cpu]->rt.tg = root_task_group; root_task_group.rt_rq[cpu] = rq[cpu]->rt; root_task_group->rt_se[cpu] = NULL; rq[cpu]->rt.rt_runtime = def_rt_bandwidth.rt_runtime; /* 這里將top rt_rq的時(shí)間額度設(shè)置為950000微秒 */??? 其中,rq->rt是一個(gè)就緒隊(duì)列上最頂層的運(yùn)行隊(duì)列rt_rq,它的運(yùn)行時(shí)間額度rq->rt.rt_runtime初始化為def_rt_bandwidth.rt_runtime,默認(rèn)為0.95秒。
3.1.2 新建一個(gè)task_group
??? 一個(gè)task_group分組的創(chuàng)建是通過sched_create_group()函數(shù)來實(shí)現(xiàn)的。內(nèi)核中在執(zhí)行 setsid()系統(tǒng)調(diào)用函數(shù) 或者 創(chuàng)建一個(gè)cpu子系統(tǒng)控制組的時(shí)候都會(huì)調(diào)用sched_create_group()來創(chuàng)建一個(gè)task組。??? 我們跟隨sched_create_group()函數(shù)的腳步看看一個(gè)task_group如何創(chuàng)建,rt_badwidth如何初始化,一個(gè)新的rt_rq的額定時(shí)間如何設(shè)置。 /* allocate runqueue etc for a new task group */ struct task_group *sched_create_group(struct task_group *parent) {struct task_group *tg;tg = kzalloc(sizeof(*tg), GFP_KERNEL); /* 分配task_group結(jié)構(gòu) */if (!tg)return ERR_PTR(-ENOMEM);if (!alloc_fair_sched_group(tg, parent)) /* fair調(diào)度的情況 */goto err;if (!alloc_rt_sched_group(tg, parent)) /* 真正的初始化發(fā)生在這里 */goto err;return tg;err:sched_free_group(tg);return ERR_PTR(-ENOMEM); }??? 函數(shù)sched_create_group有兩個(gè)主要部分,第一步分配task_group結(jié)構(gòu),第二步就是分配和初始化task_group中的調(diào)度實(shí)體和運(yùn)行隊(duì)列。第二步中,針對(duì)普通任務(wù)調(diào)用alloc_fair_sched_group()函數(shù)來完成,針對(duì)實(shí)時(shí)任務(wù)調(diào)用alloc_rt_sched_group()來完成;我們重點(diǎn)分析實(shí)時(shí)任務(wù)的情況: int alloc_rt_sched_group(struct task_group *tg, struct task_group *parent) {struct rt_rq *rt_rq;struct sched_rt_entity *rt_se;int i;tg->rt_rq = kzalloc(sizeof(rt_rq) * nr_cpu_ids, GFP_KERNEL); /* 準(zhǔn)備nr_cpu_ids個(gè)rt_rq空間 */if (!tg->rt_rq)goto err;tg->rt_se = kzalloc(sizeof(rt_se) * nr_cpu_ids, GFP_KERNEL); /* 準(zhǔn)備nr_cpu_ids個(gè)rt_se空間 */if (!tg->rt_se)goto err;init_rt_bandwidth(&tg->rt_bandwidth, /* 將tg->rt_period初始化為def_rt_bandwidth.rt_period ;ktime_to_ns(def_rt_bandwidth.rt_period), 0); * tg->rt_runtime初始化為 0 */for_each_possible_cpu(i) {rt_rq = kzalloc_node(sizeof(struct rt_rq), /* */GFP_KERNEL, cpu_to_node(i));if (!rt_rq)goto err;rt_se = kzalloc_node(sizeof(struct sched_rt_entity),GFP_KERNEL, cpu_to_node(i));if (!rt_se)goto err_free_rq;init_rt_rq(rt_rq); /* 初始化rt_rq成員 */rt_rq->rt_runtime = tg->rt_bandwidth.rt_runtime; /* 設(shè)置rt_rq->rt_runtime = 0 */init_tg_rt_entry(tg, rt_rq, rt_se, i, parent->rt_se[i]); /* 將新分配的rt_rq、rt_se與tg以及parent組進(jìn)行關(guān)聯(lián) */}return 1;err_free_rq:kfree(rt_rq); err:return 0; }??? 函數(shù)alloc_rt_sched_group()的主要任務(wù)就是分配tg->rt_rq和tg->rt_re這兩個(gè)指針數(shù)組在各個(gè)cpu上對(duì)應(yīng)的調(diào)度實(shí)體rt_se和運(yùn)行隊(duì)列rt_rq結(jié)構(gòu)體實(shí)例,并初始化這些結(jié)構(gòu)體。
??? 可以看到一個(gè)新創(chuàng)建的tg->rt_bandwidth.rt_runtime以及各個(gè)cpu上的rt_rq->rt_runtime都初始化為0。這在創(chuàng)建一個(gè)cpu子系統(tǒng)控制組的的情況下更加容易驗(yàn)證: # mount -t cgroup cpu -o cpu /cgroup/cpu /* 創(chuàng)建cpu子系統(tǒng) */ # mkdir /cgroup/cpu/child0 /* 創(chuàng)建一個(gè)child0子組 */??? 這個(gè)時(shí)候我們可以通過 "cat /cgroup/cpu/child0/cpu.rt_runtime_us"為0,即這個(gè)新創(chuàng)建的子組中tg->rt_bandwidth.rt_runtime為0,直到我們往通過"echo xxx > /cgroup/cpu/child0/cpu.rt_runtime_us"寫值是才會(huì)改變,此時(shí)tg->rt_bandwidth.rt_runtime和組內(nèi)各個(gè)cpu上的運(yùn)行隊(duì)列的額定時(shí)間tg-rt_rq[cpu]->rt_runtime都設(shè)置為我們寫入的值。
3.2 帶寬限制的檢查流程
??? 內(nèi)核在多個(gè)關(guān)鍵點(diǎn)都會(huì)更新自己cpu上當(dāng)前任務(wù)的運(yùn)行時(shí)間信息,針對(duì)實(shí)時(shí)任務(wù)調(diào)用的是update_curr_rt()來進(jìn)行更新。更新當(dāng)前任務(wù)運(yùn)行時(shí)間是任務(wù)調(diào)度一個(gè)非常重要的行為:任務(wù)運(yùn)行多久、何時(shí)選擇下一個(gè)任務(wù)、選擇哪個(gè)任務(wù)運(yùn)行以及cpu負(fù)載均衡等等都需要是基于任務(wù)信息不斷更新來進(jìn)行的。??? 對(duì)于實(shí)時(shí)調(diào)度,如果使能了帶寬限制(即sysctl_sched_rt_runtime >= 0),還要更新當(dāng)前任務(wù)的運(yùn)行隊(duì)列(即葉子節(jié)點(diǎn))到其祖先(root 節(jié)點(diǎn))所在的運(yùn)行隊(duì)列的運(yùn)行時(shí)間: rt_rq->rt_time += delta_exec;??? 更新完一個(gè)運(yùn)行隊(duì)列后,還要通過 sched_rt_runtime_exceeded(rt_rq);??? 檢查運(yùn)行隊(duì)列的運(yùn)行時(shí)間是否超過額定運(yùn)行時(shí)間,如果超過額定運(yùn)行時(shí)間還要通過resched_curr(rq)將就緒隊(duì)列rq上的當(dāng)前任務(wù)設(shè)置為TIF_NEED_RESCHED標(biāo)志以被調(diào)度出去。
??? 下面我們就來看看sched_rt_runtime_exceeded(rt_rq)是如何檢查一個(gè)隊(duì)列的運(yùn)行時(shí)間超額與否的。 static int sched_rt_runtime_exceeded(struct rt_rq *rt_rq) {u64 runtime = sched_rt_runtime(rt_rq); /* runtime為額定時(shí)間rt_rq->rt_runtime */if (rt_rq->rt_throttled) /* 如果受到調(diào)度限制直接返回 */return rt_rq_throttled(rt_rq); if (runtime >= sched_rt_period(rt_rq)) /* 如果rt_rq的額定時(shí)間大于周期說明不會(huì)發(fā)生超時(shí),返回0表示不超額 */return 0;balance_runtime(rt_rq); /* 對(duì)rt_rq的額定時(shí)間進(jìn)行"balance" */runtime = sched_rt_runtime(rt_rq); /* balance后rt_rq的額定時(shí)間可能會(huì)改變,所以需要重新獲取rt_rq->rt_runtime */if (runtime == RUNTIME_INF) /* 額定時(shí)間"無(wú)限",也返回0表示沒有超額 */return 0;if (rt_rq->rt_time > runtime) { /* 如果 rt_rq上的運(yùn)行時(shí)間大于了額定時(shí)間 */struct rt_bandwidth *rt_b = sched_rt_bandwidth(rt_rq);/** Don't actually throttle groups that have no runtime assigned* but accrue some time due to boosting.*/if (likely(rt_b->rt_runtime)) { /* 一般情況下帶寬額定時(shí)間rt_b->rt_runtime都不為0 */rt_rq->rt_throttled = 1; /* 在rt_rq的運(yùn)行時(shí)間超過額定時(shí)間的情況下設(shè)置調(diào)度限制rt_throttled */printk_deferred_once("sched: RT throttling activated\n");} else {/** In case we did anyway, make it go away,* replenishment is a joke, since it will replenish us* with exactly 0 ns.*/rt_rq->rt_time = 0;}if (rt_rq_throttled(rt_rq)) { /* 如果rt_rq運(yùn)行時(shí)間超額且設(shè)置了調(diào)度限制標(biāo)志 */sched_rt_rq_dequeue(rt_rq); /* 先將rt_rq對(duì)應(yīng)的實(shí)體從隊(duì)列刪除,再放到隊(duì)尾;* 注意:函數(shù)想將rt_rq這個(gè)組的rt_se及其所有的祖先rt_se出隊(duì),然后再?gòu)淖嫦萺t_se開始再依次放到隊(duì)尾;任何一個(gè)rt_rq的調(diào)度受限時(shí),對(duì)應(yīng)的rt_se在__enqueue_rt_entity(rt_se)是不能入隊(duì)的,所以這里的rt_rq對(duì)應(yīng)組的調(diào)度實(shí)體不會(huì)入隊(duì)的;入不了隊(duì)也就意味著無(wú)法得到調(diào)度。 */return 1;}}return 0; }??? 我們來縷一縷思路:
??? 1) sched_rt_runtime_exceeded(rt_rq)用于判斷一個(gè)rt_rq上的運(yùn)行時(shí)間是否超時(shí),如果超時(shí)則返回1否則返回0;
??? 2) 這個(gè)函數(shù)首先檢查的是rt_rq有沒有調(diào)度限制,額定時(shí)間是否大于帶寬周期;第一種一般情況返回1(除非發(fā)生了優(yōu)先級(jí)翻轉(zhuǎn)),第二種情況返回0;
??? 3) 如果即沒有調(diào)度限制,額定時(shí)間也在帶寬周期范圍內(nèi),則首先要對(duì)對(duì)rt_rq->rt_runtime進(jìn)行"balance",即在多核情況下,其他cpu上的rt_rq還有剩余時(shí)間,可以從其他cpu的rt_rq中"借"時(shí)間;
??? 4) 經(jīng)過"balance"后,rt_rq的額定時(shí)間可能會(huì)增加,最多增加到帶寬周期,此時(shí)再去檢查運(yùn)行時(shí)間(rt_rq->rt_time)是否超過額定時(shí)間(rt_rq->rt_runtime);
???? 5)如果運(yùn)行時(shí)間超額,且?guī)掝~定時(shí)間不為0的情況下將設(shè)置調(diào)度限制標(biāo)志:rt_rq->rt_throttled = 1,并將rt_rq中的所有上層實(shí)體放到隊(duì)尾,rt_rq對(duì)應(yīng)的本層實(shí)體則出隊(duì);
??? 一旦調(diào)度被限制(rt_rq->rt_throttled不為0且沒有發(fā)生優(yōu)先級(jí)翻轉(zhuǎn)),會(huì)有下列影響:? ?? 1) sched_rt_runtime_exceeded()檢查時(shí)返回1,會(huì)為當(dāng)前任務(wù)rq->curr設(shè)置TIF_NEED_RESCHED標(biāo)志,等到下次中斷返回時(shí)會(huì)將它調(diào)度出去;
???? 2) 在調(diào)度受限的情況下,任務(wù)的"加入運(yùn)行隊(duì)列的"入口都會(huì)拒絕將一個(gè)調(diào)度實(shí)體入隊(duì),如enqueue_top_rt_rq(rt_rq)和__enqueue_rt_entity(rt_se, head)函數(shù)都會(huì)跳過調(diào)度受限的隊(duì)列。
3.3 調(diào)度限制的解除
??? 上面已經(jīng)了解到,如果一個(gè)隊(duì)列調(diào)度限制使能的情況下,將無(wú)法得到調(diào)度運(yùn)行的機(jī)會(huì);但是任務(wù)不可能一直處于調(diào)度限制,因?yàn)槟菢拥脑捜蝿?wù)就永遠(yuǎn)得不到執(zhí)行了。這個(gè)時(shí)候就需要一種檢查機(jī)制,一旦調(diào)度限制已經(jīng)讓任務(wù)得到了應(yīng)有的“懲罰”,就需要解除這個(gè)限制,讓它重獲自由。??? 內(nèi)核中在struct task_group結(jié)構(gòu)rt_bandwidth的高精度時(shí)鐘rt_period_timer來實(shí)現(xiàn)此功能。高精度時(shí)鐘rt_period_timer在帶寬初始化函數(shù)init_rt_bandwidth()中進(jìn)行初始化: void init_rt_bandwidth(struct rt_bandwidth *rt_b, u64 period, u64 runtime) {rt_b->rt_period = ns_to_ktime(period);rt_b->rt_runtime = runtime;raw_spin_lock_init(&rt_b->rt_runtime_lock);hrtimer_init(&rt_b->rt_period_timer, /* 初始化帶寬的高精度時(shí)鐘 */CLOCK_MONOTONIC, HRTIMER_MODE_REL);rt_b->rt_period_timer.function = sched_rt_period_timer; /* 設(shè)置時(shí)鐘到期處理函數(shù):sched_rt_period_timer */ }??? 帶寬初始化函數(shù)init_rt_bandwidth()在創(chuàng)建一個(gè)task_group時(shí)調(diào)用,這樣rt_bandwidth的高精度定時(shí)器也會(huì)在這個(gè)時(shí)候初始化;rt_bandwidth的高精度時(shí)鐘初始化后通過hrtimer_start_expires()激活運(yùn)轉(zhuǎn)起來。每當(dāng)調(diào)用__enqueue_rt_entity()函數(shù)將一個(gè)rt_se調(diào)度實(shí)體入隊(duì)時(shí),都會(huì)檢查rt_se所在組的rt_bandwidth上的高精度時(shí)鐘是否激活,如果沒有激活則將其激活。 __enqueue_rt_entity()|inc_rt_tasks(rt_se, rt_rq)|inc_rt_group(rt_se, rt_rq)|start_rt_bandwidth(&rt_rq->tg->rt_bandwidth)??? start_rt_bandwidth調(diào)用函數(shù)來完成rt_bandwidth高精度時(shí)鐘的激活,如下所示: static void start_rt_bandwidth(struct rt_bandwidth *rt_b) {if (!rt_bandwidth_enabled() || rt_b->rt_runtime == RUNTIME_INF)return;raw_spin_lock(&rt_b->rt_runtime_lock);if (!rt_b->rt_period_active) {rt_b->rt_period_active = 1;hrtimer_forward_now(&rt_b->rt_period_timer, rt_b->rt_period); /* 將定時(shí)器到期時(shí)間設(shè)置為一個(gè)帶寬周期 */hrtimer_start_expires(&rt_b->rt_period_timer, HRTIMER_MODE_ABS_PINNED); /* 激活定時(shí)器 */}raw_spin_unlock(&rt_b->rt_runtime_lock); }??? 鋪墊了這么多,終于等到了定時(shí)器的激活;激活后定時(shí)器開始飛速運(yùn)轉(zhuǎn),直到我們?cè)O(shè)置的定時(shí)器到期;而定時(shí)器到期意味著什么呢?意味著時(shí)鐘到期處理函數(shù)rt_b->rt_period_timer.function的調(diào)用執(zhí)行,而這個(gè)函數(shù)在帶寬初始化時(shí)設(shè)置為sched_rt_period_timer(),所以時(shí)鐘到期后實(shí)際回調(diào)的是sched_rt_period_timer()。 static enum hrtimer_restart sched_rt_period_timer(struct hrtimer *timer) {struct rt_bandwidth *rt_b =container_of(timer, struct rt_bandwidth, rt_period_timer);int idle = 0;int overrun;raw_spin_lock(&rt_b->rt_runtime_lock);for (;;) {overrun = hrtimer_forward_now(timer, rt_b->rt_period); /* 更新時(shí)鐘,overrun返回時(shí)鐘超時(shí)期數(shù) */if (!overrun)break;raw_spin_unlock(&rt_b->rt_runtime_lock);idle = do_sched_rt_period_timer(rt_b, overrun); /* 主要的處理函數(shù) */raw_spin_lock(&rt_b->rt_runtime_lock);}if (idle)rt_b->rt_period_active = 0; /* idle==1表示此task_group中沒有可調(diào)度的任務(wù),時(shí)鐘標(biāo)志設(shè)置為未激活 */raw_spin_unlock(&rt_b->rt_runtime_lock);return idle ? HRTIMER_NORESTART : HRTIMER_RESTART; }??? 再來瞧瞧這個(gè)核心的處理函數(shù): static int do_sched_rt_period_timer(struct rt_bandwidth *rt_b, int overrun) {int i, idle = 1, throttled = 0;const struct cpumask *span;span = sched_rt_period_mask(); #ifdef CONFIG_RT_GROUP_SCHED/** FIXME: isolated CPUs should really leave the root task group,* whether they are isolcpus or were isolated via cpusets, lest* the timer run on a CPU which does not service all runqueues,* potentially leaving other CPUs indefinitely throttled. If* isolation is really required, the user will turn the throttle* off to kill the perturbations it causes anyway. Meanwhile,* this maintains functionality for boot and/or troubleshooting.*/if (rt_b == &root_task_group.rt_bandwidth)span = cpu_online_mask; #endiffor_each_cpu(i, span) { /* 分析此帶寬所在的task_group組上各個(gè)cpu的運(yùn)行隊(duì)列rt_rq */int enqueue = 0;struct rt_rq *rt_rq = sched_rt_period_rt_rq(rt_b, i);struct rq *rq = rq_of_rt_rq(rt_rq);raw_spin_lock(&rq->lock);if (rt_rq->rt_time) { /* rt_rq運(yùn)行時(shí)間不為0:rt_rq的運(yùn)行時(shí)間只有在rt_bandwidth高精度時(shí)鐘* 到期后才得以重新統(tǒng)計(jì) */u64 runtime;raw_spin_lock(&rt_rq->rt_runtime_lock);if (rt_rq->rt_throttled)balance_runtime(rt_rq); /* 如果rt_rq調(diào)度受限進(jìn)行"balcance",以嘗試從其他cpu的rt_rq偷時(shí)間* 這是第二次出現(xiàn)。*/runtime = rt_rq->rt_runtime;rt_rq->rt_time -= min(rt_rq->rt_time, overrun*runtime); /* 抹去周期運(yùn)行時(shí)間;* @overrun:超過時(shí)鐘周期數(shù);@runtime:一個(gè)周期內(nèi)運(yùn)行隊(duì)列的額定運(yùn)行時(shí)間;* 沒有到一個(gè)周期,則將運(yùn)行時(shí)間清0;否則 * 運(yùn)行時(shí)間設(shè)置為過期超出的額定時(shí)間;*/if (rt_rq->rt_throttled && rt_rq->rt_time < runtime) { /* 如果剩余的運(yùn)行時(shí)間小于一個(gè)周期額定時(shí)間 rt_rq->rt_throttled = 0; * 則清除調(diào)度限制標(biāo)志,并將入隊(duì)標(biāo)志設(shè)置為1 */enqueue = 1;/** When we're idle and a woken (rt) task is* throttled check_preempt_curr() will set* skip_update and the time between the wakeup* and this unthrottle will get accounted as* 'runtime'.*/if (rt_rq->rt_nr_running && rq->curr == rq->idle)rq_clock_skip_update(rq, false);}if (rt_rq->rt_time || rt_rq->rt_nr_running)idle = 0;raw_spin_unlock(&rt_rq->rt_runtime_lock);} else if (rt_rq->rt_nr_running) { /* 如果此周期rt_rq沒有運(yùn)行時(shí)間,但是rt_rq還有就緒的任務(wù),idle = 0; * 且rt_rq沒有調(diào)度限制則入隊(duì)標(biāo)志置1 */if (!rt_rq_throttled(rt_rq))enqueue = 1;}if (rt_rq->rt_throttled)throttled = 1;if (enqueue)sched_rt_rq_enqueue(rt_rq); /* 在3.2中可以看到rt_rq帶寬超時(shí)后sched_rt_rq_dequeue()出隊(duì)后無(wú)法再入隊(duì),直到這里解除了調(diào)度限制 */raw_spin_unlock(&rq->lock);}if (!throttled && (!rt_bandwidth_enabled() || rt_b->rt_runtime == RUNTIME_INF))return 1;return idle; /* idle返回0表示有cpu上無(wú)可運(yùn)行調(diào)度實(shí)體 */ }}
??? 到此我們匆匆瀏覽了一個(gè)task_group中高精度時(shí)鐘的運(yùn)行流程;從上面do_sched_rt_period_timer(rt_b, overrun)函數(shù)也可以看到隊(duì)列的帶寬限制的解除條件:在時(shí)鐘到期后重新計(jì)算rt_rq的運(yùn)行時(shí)間(也就是剩余的運(yùn)行時(shí)間),如果更新后的運(yùn)行時(shí)間小于一個(gè)周期的額定時(shí)間,則會(huì)解除rt_rq的調(diào)度限制rt_rq->rt_throttled = 0。
四、總結(jié)
??? 上面簡(jiǎn)要分析了內(nèi)核中如何通過帶寬限制來防止實(shí)時(shí)任務(wù)無(wú)限制的占用cpu資源的實(shí)現(xiàn)方式。??? 1 帶寬限制的對(duì)象是一個(gè)組,組內(nèi)的任務(wù)都掛到同一個(gè)運(yùn)行隊(duì)列rt_rq上,最終帶寬限制的實(shí)施對(duì)象就是rt_rq;
??? 2 內(nèi)核在多個(gè)關(guān)鍵點(diǎn)更新任務(wù)的信息update_curr_rt(),而更新當(dāng)前任務(wù)信息后,就會(huì)檢查組的運(yùn)行時(shí)間是否超過帶寬限制;
??? 3 如果一個(gè)rt_rq超過帶寬限制,則會(huì)標(biāo)記此rt_rq調(diào)度受限,此后rt_rq上的實(shí)體將被移出隊(duì)列,并且?guī)捪拗平獬盁o(wú)法再加入到隊(duì)列上;
??? 4 每個(gè)任務(wù)組都維護(hù)著一個(gè)高精度時(shí)鐘用以定期(rt_period)更新rt_rq上的運(yùn)行時(shí)間,并對(duì)"被懲罰到位"的rt_rq解除調(diào)度限制。
上文中有兩處提到了對(duì)運(yùn)行隊(duì)列rt_rq的額定時(shí)間進(jìn)行"balance",這個(gè)"balcance"是如何工作的呢?敬請(qǐng)期待,我們下期再講。
總結(jié)
- 上一篇: linux命名空间(namespace)
- 下一篇: 北京中国石油大学计算机考研分数线,中国石