日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

rt带宽限制浅析

發(fā)布時間:2023/12/18 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 rt带宽限制浅析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

(基于linux-4.4.42)

一、摘要

??? 由于實時任務的優(yōu)先級高于普通任務,因而為了防止cpu消耗型的實時任務一直占用cpu引發(fā)其他任務"饑餓"的情況發(fā)生,內(nèi)核采用了帶寬限制手段來抑制實時任務的運行時間。系統(tǒng)中將各個任務按層級組織成一個個任務組,組內(nèi)的所有任務視為一個整體掛在一個運行隊列上,而帶寬限制的單位也是針對一個組來進行的。
??? 那么究竟什么是帶寬限制呢?在任務調(diào)度中帶寬限制就是指一定周期內(nèi)一個隊列上任務可運行的最大時間,內(nèi)核中使用xxx_bandwidth結構來限制任務的運行時間。針對實時任務這個結構就是: 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上的實時任務在rt_period周期內(nèi)運行時間不能夠超過rt_runtime;而在SMP多cpu環(huán)境中rt_bandwidth限制了系統(tǒng)中實時任務在rt_period周期內(nèi)的cpu占用時間比例不能夠超過rt_runtime/rt_period。
??? 舉個例子,在4核的SMP環(huán)境中,rt_runtime為950000,而rt_period為1000000,系統(tǒng)中所有實時任務的cpu(4個核的占用總和)占用率不能夠超過95%。

??? 在實時任務組調(diào)度使能的情況下,帶寬限制與各個組相關,即一個任務組有一個rt_bandwidth,而這個組的rt_bandwidth限制了本組中實時任務的運行時間。

二、實現(xiàn)原理

2.1 相關數(shù)據(jù)結構

??? 內(nèi)核中通過static int sched_rt_runtime_exceeded(struct rt_rq *rt_rq)函數(shù)來判斷實時任務運行時間是否超出帶寬限制。函數(shù)的核心思想就是判斷這個運行隊列rt_rq的運行時間是否超過了額定的運行時間。而“運行時間”和“額定時間”都是放在struct rt_rq運行隊列這個結構中的,我們來看下這個結構相關的具體實現(xiàn):

/* Real-Time classes' related field in a runqueue: */ struct rt_rq {...... int rt_throttled; /* 調(diào)度限制標志,置1表示使能調(diào)度限制 */u64 rt_time; /* rt_rq隊列中的調(diào)度實體已經(jīng)消耗的cpu時間 */u64 rt_runtime; /* rt_rq在一個周期內(nèi)的最大額定運行時間 *//* Nests inside the rq lock: */......struct rq *rq; /* rt_rq所屬的就緒隊列rq */struct task_group *tg; /* rt_rq所屬的任務組 */ };??? 當一個調(diào)度組task_group被創(chuàng)建時(例如在cgroup中創(chuàng)建一個cpu子系統(tǒng)時就會創(chuàng)建一個task_group)時,內(nèi)核會為這個新的task_group在各個cpu上都創(chuàng)建一個運行隊列struct rt_rq,而所有的 實時調(diào)度實體 都是掛在各自的運行隊列rt_rq上的,整個結構如下所示: /* task group related information */ struct task_group {struct cgroup_subsys_state css;...... #ifdef CONFIG_RT_GROUP_SCHEDstruct sched_rt_entity **rt_se; /* 一個組在每個cpu上都有一個代表這個層級組的rt_se */struct rt_rq **rt_rq; /* 一個組在每個cpu上都有一個rt_rq里面掛著屬于這個組的rt_ses */struct rt_bandwidth rt_bandwidth; /* 帶寬結構 */ #endif......struct list_head list;struct task_group *parent; ......struct cfs_bandwidth cfs_bandwidth; };

2.2 組調(diào)度層級結構

??? 在引入了組調(diào)度之后,任務的調(diào)度的對象都是調(diào)度實體,而各個調(diào)度實體又有自己的父層級實體,父層級也可以再有自己的父層級...這樣層層組織起來成為一個樹狀結構。而各個cpu上的同一個組層級se又結合在一起組成了上面提到的層級任務組結構:struct task_group。下面是一個task_group的層級結構示意圖:

? ? ??????????????????????????????????????????????? 圖1 具有3個層的的任務組組織示意圖
??? 如上圖所示,每個task_group中都有一個rt_se**和rt_rq**,而子層級的調(diào)度實體rt_se[cpu]又都掛到父層級的的rt_rq[cpu]中。
??? 我們可以將task_group的兩個rt_se**和rt_rq**看成兩個指針數(shù)組sched_rt_entity * rt_se[cpu]和struct rt_rq *rt_rq[cpu],數(shù)組在每個cpu(cpu_possible_mask位圖中的所有cpu)上都有一個元素;
??? 子層級的調(diào)度實體都掛到本層級rt_rq隊列中,而這些調(diào)度實體都會被看成一個整體,即本層級的rt_se;一個task_group中兩者之間的關系為

tg.rt_se[cpu]->my_q == tg.rt_rq[cpu]; ? /* tg表示某一個task_group;cpu為某個具體的cpu */
tg.rt_rq[cpu]隊列掛著的所有任務都看成一個整體:tg.rt_se[cpu]。
當tg.rt_se[cpu]->my_q == NULL 時表示這個調(diào)度實體不再是一個組,而是一個實實在在的任務。

??? 有了上面的概念,我們再看一個運行隊列的運行時間rt_rq->rt_time和額定時間rt_rq->rt_runtime:
??? rt_rq->rt_time就表示屬于這個隊列(這個組中)的調(diào)度實體(子層級、子孫層級...一直到葉子節(jié)點層層累加)的運行時間;rt_rq->rt_runtime就表示屬于此隊列的實體的額定時間。
??? 調(diào)度器在檢查一個調(diào)度實體運行時間是否超額時,實際檢查的是它所在的運行隊列的rt_rq[cpu]->rt_time是否超過rt_rq[cpu]->rt_runtime;在SMP系統(tǒng)中,如果內(nèi)核使能了RT_RUNTIME_SHARE特性,如果運行隊列的運行時間已經(jīng)超額,則會嘗試去其他cpu上的rt_rq隊列中“借”時間以擴張rt_rq[cpu]->rt_runtime。
??? 不論如何,如果運行隊列的運行時間rt_rq->rt_time超額且在使能了帶寬限制的情況下,會使能rt_rq的調(diào)度限制標志:rt_rq->rt_throttled = 1,調(diào)度受限標志一旦使能,后續(xù)就rt_rq就無法得到調(diào)度。

三、帶寬限制的來龍去脈

3.1 帶寬和運行時間初始化

3.1.1 root組帶寬初始化

??? 最頂層的root組是所有task_group的祖先,內(nèi)核中用全局變量struct task_group root_task_group定義。它在內(nèi)核啟動初期由sche_init()進行初始化的:

start_kernel()|sched_init()??? 我們來看一下sched_init()做了哪些工作:
??? 1) 帶寬初始化
??? 針對實時任務,這里會對兩個帶寬結構進行初始化:全局struct rt_bandwidth def_rt_bandwidth和root_task_group.rt_bandwidth,并且將他們的帶寬周期和運行時間額度都初始化為相同的值: 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 分別查看;默認情況下這兩個變量的值分別為1000000 和 950000,單位都是是微秒。
??? 這樣初始化以后,默認情況下def_rt_bandwidth和root_task_group.rt_bandwidth的周期為1秒;而它們的額定時間為0.95秒。

??? 2)調(diào)度實體和運行隊列初始化
??? 根組root_task_group的 struct sched_rt_entity **rt_se 和 struct rt_rq **rt_rq 成員實際上是兩個指針數(shù)組,每個數(shù)組成員與一個cpu相對應;root_task_group中的這些調(diào)度實體和運行隊列最終是需要和每個cpu上就緒隊列rq中的成員關聯(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的時間額度設置為950000微秒 */??? 其中,rq->rt是一個就緒隊列上最頂層的運行隊列rt_rq,它的運行時間額度rq->rt.rt_runtime初始化為def_rt_bandwidth.rt_runtime,默認為0.95秒。

3.1.2 新建一個task_group

??? 一個task_group分組的創(chuàng)建是通過sched_create_group()函數(shù)來實現(xiàn)的。內(nèi)核中在執(zhí)行 setsid()系統(tǒng)調(diào)用函數(shù) 或者 創(chuàng)建一個cpu子系統(tǒng)控制組的時候都會調(diào)用sched_create_group()來創(chuàng)建一個task組。
??? 我們跟隨sched_create_group()函數(shù)的腳步看看一個task_group如何創(chuàng)建,rt_badwidth如何初始化,一個新的rt_rq的額定時間如何設置。 /* 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結構 */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有兩個主要部分,第一步分配task_group結構,第二步就是分配和初始化task_group中的調(diào)度實體和運行隊列。第二步中,針對普通任務調(diào)用alloc_fair_sched_group()函數(shù)來完成,針對實時任務調(diào)用alloc_rt_sched_group()來完成;我們重點分析實時任務的情況: 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); /* 準備nr_cpu_ids個rt_rq空間 */if (!tg->rt_rq)goto err;tg->rt_se = kzalloc(sizeof(rt_se) * nr_cpu_ids, GFP_KERNEL); /* 準備nr_cpu_ids個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; /* 設置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組進行關聯(lián) */}return 1;err_free_rq:kfree(rt_rq); err:return 0; }??? 函數(shù)alloc_rt_sched_group()的主要任務就是分配tg->rt_rq和tg->rt_re這兩個指針數(shù)組在各個cpu上對應的調(diào)度實體rt_se和運行隊列rt_rq結構體實例,并初始化這些結構體。
??? 可以看到一個新創(chuàng)建的tg->rt_bandwidth.rt_runtime以及各個cpu上的rt_rq->rt_runtime都初始化為0。這在創(chuàng)建一個cpu子系統(tǒng)控制組的的情況下更加容易驗證: # mount -t cgroup cpu -o cpu /cgroup/cpu /* 創(chuàng)建cpu子系統(tǒng) */ # mkdir /cgroup/cpu/child0 /* 創(chuàng)建一個child0子組 */??? 這個時候我們可以通過 "cat /cgroup/cpu/child0/cpu.rt_runtime_us"為0,即這個新創(chuàng)建的子組中tg->rt_bandwidth.rt_runtime為0,直到我們往通過"echo xxx > /cgroup/cpu/child0/cpu.rt_runtime_us"寫值是才會改變,此時tg->rt_bandwidth.rt_runtime和組內(nèi)各個cpu上的運行隊列的額定時間tg-rt_rq[cpu]->rt_runtime都設置為我們寫入的值。

3.2 帶寬限制的檢查流程

??? 內(nèi)核在多個關鍵點都會更新自己cpu上當前任務的運行時間信息,針對實時任務調(diào)用的是update_curr_rt()來進行更新。更新當前任務運行時間是任務調(diào)度一個非常重要的行為:任務運行多久、何時選擇下一個任務、選擇哪個任務運行以及cpu負載均衡等等都需要是基于任務信息不斷更新來進行的。
??? 對于實時調(diào)度,如果使能了帶寬限制(即sysctl_sched_rt_runtime >= 0),還要更新當前任務的運行隊列(即葉子節(jié)點)到其祖先(root 節(jié)點)所在的運行隊列的運行時間: rt_rq->rt_time += delta_exec;??? 更新完一個運行隊列后,還要通過 sched_rt_runtime_exceeded(rt_rq);??? 檢查運行隊列的運行時間是否超過額定運行時間,如果超過額定運行時間還要通過resched_curr(rq)將就緒隊列rq上的當前任務設置為TIF_NEED_RESCHED標志以被調(diào)度出去。
??? 下面我們就來看看sched_rt_runtime_exceeded(rt_rq)是如何檢查一個隊列的運行時間超額與否的。 static int sched_rt_runtime_exceeded(struct rt_rq *rt_rq) {u64 runtime = sched_rt_runtime(rt_rq); /* runtime為額定時間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的額定時間大于周期說明不會發(fā)生超時,返回0表示不超額 */return 0;balance_runtime(rt_rq); /* 對rt_rq的額定時間進行"balance" */runtime = sched_rt_runtime(rt_rq); /* balance后rt_rq的額定時間可能會改變,所以需要重新獲取rt_rq->rt_runtime */if (runtime == RUNTIME_INF) /* 額定時間"無限",也返回0表示沒有超額 */return 0;if (rt_rq->rt_time > runtime) { /* 如果 rt_rq上的運行時間大于了額定時間 */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)) { /* 一般情況下帶寬額定時間rt_b->rt_runtime都不為0 */rt_rq->rt_throttled = 1; /* 在rt_rq的運行時間超過額定時間的情況下設置調(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運行時間超額且設置了調(diào)度限制標志 */sched_rt_rq_dequeue(rt_rq); /* 先將rt_rq對應的實體從隊列刪除,再放到隊尾;* 注意:函數(shù)想將rt_rq這個組的rt_se及其所有的祖先rt_se出隊,然后再從祖先rt_se開始再依次放到隊尾;任何一個rt_rq的調(diào)度受限時,對應的rt_se在__enqueue_rt_entity(rt_se)是不能入隊的,所以這里的rt_rq對應組的調(diào)度實體不會入隊的;入不了隊也就意味著無法得到調(diào)度。 */return 1;}}return 0; }??? 我們來縷一縷思路:
??? 1) sched_rt_runtime_exceeded(rt_rq)用于判斷一個rt_rq上的運行時間是否超時,如果超時則返回1否則返回0;
??? 2) 這個函數(shù)首先檢查的是rt_rq有沒有調(diào)度限制,額定時間是否大于帶寬周期;第一種一般情況返回1(除非發(fā)生了優(yōu)先級翻轉),第二種情況返回0;
??? 3) 如果即沒有調(diào)度限制,額定時間也在帶寬周期范圍內(nèi),則首先要對對rt_rq->rt_runtime進行"balance",即在多核情況下,其他cpu上的rt_rq還有剩余時間,可以從其他cpu的rt_rq中"借"時間;
??? 4) 經(jīng)過"balance"后,rt_rq的額定時間可能會增加,最多增加到帶寬周期,此時再去檢查運行時間(rt_rq->rt_time)是否超過額定時間(rt_rq->rt_runtime);

???? 5)如果運行時間超額,且?guī)掝~定時間不為0的情況下將設置調(diào)度限制標志:rt_rq->rt_throttled = 1,并將rt_rq中的所有上層實體放到隊尾,rt_rq對應的本層實體則出隊;

??? 一旦調(diào)度被限制(rt_rq->rt_throttled不為0且沒有發(fā)生優(yōu)先級翻轉),會有下列影響:
? ?? 1) sched_rt_runtime_exceeded()檢查時返回1,會為當前任務rq->curr設置TIF_NEED_RESCHED標志,等到下次中斷返回時會將它調(diào)度出去;
???? 2) 在調(diào)度受限的情況下,任務的"加入運行隊列的"入口都會拒絕將一個調(diào)度實體入隊,如enqueue_top_rt_rq(rt_rq)和__enqueue_rt_entity(rt_se, head)函數(shù)都會跳過調(diào)度受限的隊列。

3.3 調(diào)度限制的解除

??? 上面已經(jīng)了解到,如果一個隊列調(diào)度限制使能的情況下,將無法得到調(diào)度運行的機會;但是任務不可能一直處于調(diào)度限制,因為那樣的話任務就永遠得不到執(zhí)行了。這個時候就需要一種檢查機制,一旦調(diào)度限制已經(jīng)讓任務得到了應有的“懲罰”,就需要解除這個限制,讓它重獲自由。
??? 內(nèi)核中在struct task_group結構rt_bandwidth的高精度時鐘rt_period_timer來實現(xiàn)此功能。高精度時鐘rt_period_timer在帶寬初始化函數(shù)init_rt_bandwidth()中進行初始化: 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, /* 初始化帶寬的高精度時鐘 */CLOCK_MONOTONIC, HRTIMER_MODE_REL);rt_b->rt_period_timer.function = sched_rt_period_timer; /* 設置時鐘到期處理函數(shù):sched_rt_period_timer */ }??? 帶寬初始化函數(shù)init_rt_bandwidth()在創(chuàng)建一個task_group時調(diào)用,這樣rt_bandwidth的高精度定時器也會在這個時候初始化;rt_bandwidth的高精度時鐘初始化后通過hrtimer_start_expires()激活運轉起來。每當調(diào)用__enqueue_rt_entity()函數(shù)將一個rt_se調(diào)度實體入隊時,都會檢查rt_se所在組的rt_bandwidth上的高精度時鐘是否激活,如果沒有激活則將其激活。 __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高精度時鐘的激活,如下所示: 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); /* 將定時器到期時間設置為一個帶寬周期 */hrtimer_start_expires(&rt_b->rt_period_timer, HRTIMER_MODE_ABS_PINNED); /* 激活定時器 */}raw_spin_unlock(&rt_b->rt_runtime_lock); }??? 鋪墊了這么多,終于等到了定時器的激活;激活后定時器開始飛速運轉,直到我們設置的定時器到期;而定時器到期意味著什么呢?意味著時鐘到期處理函數(shù)rt_b->rt_period_timer.function的調(diào)用執(zhí)行,而這個函數(shù)在帶寬初始化時設置為sched_rt_period_timer(),所以時鐘到期后實際回調(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); /* 更新時鐘,overrun返回時鐘超時期數(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)度的任務,時鐘標志設置為未激活 */raw_spin_unlock(&rt_b->rt_runtime_lock);return idle ? HRTIMER_NORESTART : HRTIMER_RESTART; }??? 再來瞧瞧這個核心的處理函數(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組上各個cpu的運行隊列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運行時間不為0:rt_rq的運行時間只有在rt_bandwidth高精度時鐘* 到期后才得以重新統(tǒng)計 */u64 runtime;raw_spin_lock(&rt_rq->rt_runtime_lock);if (rt_rq->rt_throttled)balance_runtime(rt_rq); /* 如果rt_rq調(diào)度受限進行"balcance",以嘗試從其他cpu的rt_rq偷時間* 這是第二次出現(xiàn)。*/runtime = rt_rq->rt_runtime;rt_rq->rt_time -= min(rt_rq->rt_time, overrun*runtime); /* 抹去周期運行時間;* @overrun:超過時鐘周期數(shù);@runtime:一個周期內(nèi)運行隊列的額定運行時間;* 沒有到一個周期,則將運行時間清0;否則 * 運行時間設置為過期超出的額定時間;*/if (rt_rq->rt_throttled && rt_rq->rt_time < runtime) { /* 如果剩余的運行時間小于一個周期額定時間 rt_rq->rt_throttled = 0; * 則清除調(diào)度限制標志,并將入隊標志設置為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沒有運行時間,但是rt_rq還有就緒的任務,idle = 0; * 且rt_rq沒有調(dià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帶寬超時后sched_rt_rq_dequeue()出隊后無法再入隊,直到這里解除了調(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上無可運行調(diào)度實體 */ }}
??? 到此我們匆匆瀏覽了一個task_group中高精度時鐘的運行流程;從上面do_sched_rt_period_timer(rt_b, overrun)函數(shù)也可以看到隊列的帶寬限制的解除條件:在時鐘到期后重新計算rt_rq的運行時間(也就是剩余的運行時間),如果更新后的運行時間小于一個周期的額定時間,則會解除rt_rq的調(diào)度限制rt_rq->rt_throttled = 0。

四、總結

??? 上面簡要分析了內(nèi)核中如何通過帶寬限制來防止實時任務無限制的占用cpu資源的實現(xiàn)方式。
??? 1 帶寬限制的對象是一個組,組內(nèi)的任務都掛到同一個運行隊列rt_rq上,最終帶寬限制的實施對象就是rt_rq;
??? 2 內(nèi)核在多個關鍵點更新任務的信息update_curr_rt(),而更新當前任務信息后,就會檢查組的運行時間是否超過帶寬限制;
??? 3 如果一個rt_rq超過帶寬限制,則會標記此rt_rq調(diào)度受限,此后rt_rq上的實體將被移出隊列,并且?guī)捪拗平獬盁o法再加入到隊列上;
??? 4 每個任務組都維護著一個高精度時鐘用以定期(rt_period)更新rt_rq上的運行時間,并對"被懲罰到位"的rt_rq解除調(diào)度限制。

上文中有兩處提到了對運行隊列rt_rq的額定時間進行"balance",這個"balcance"是如何工作的呢?敬請期待,我們下期再講。

總結

以上是生活随笔為你收集整理的rt带宽限制浅析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。