五月天青色头像情侣网名,国产亚洲av片在线观看18女人,黑人巨茎大战俄罗斯美女,扒下她的小内裤打屁股

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

一文解決內(nèi)核是如何給容器中的進(jìn)程分配CPU資源的?

2023-03-20 20:51 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿

現(xiàn)在很多公司的服務(wù)都是跑在容器下,我來(lái)問(wèn)幾個(gè)容器 CPU 相關(guān)的問(wèn)題,看大家對(duì)天天在用的技術(shù)是否熟悉。

  • 容器中的核是真的邏輯核嗎?

  • Linux 是如何對(duì)容器下的進(jìn)程進(jìn)行 CPU 限制的,底層是如何工作的?

  • 容器中的 throttle 是什么意思?

  • 為什么關(guān)注容器 CPU 性能的時(shí)候,除了關(guān)注使用率,還要關(guān)注 throttle 的次數(shù)和時(shí)間?

和真正使用物理機(jī)不同,Linux 容器中所謂的核并不是真正的 CPU 核。所以在理解容器 CPU 性能的時(shí)候,必然要有一些特殊的地方需要考慮。

各家公司的容器云上,底層不管使用的是 docker 引擎,還是 containerd 引擎,都是依賴 Linux 的 cgroup 的 cpu 子系統(tǒng)來(lái)工作的,所以今天我們就來(lái)深入地學(xué)習(xí)一下 cgroup cpu 子系統(tǒng) 。理解了這個(gè),你將會(huì)對(duì)容器進(jìn)程的 CPU 性能有更深入的把握。

一、cgroup 的 cpu 子系統(tǒng)

在 Linux 下, ?cgroup 提供了對(duì) CPU、內(nèi)存等資源實(shí)現(xiàn)精細(xì)化控制的能力。它的全稱是 control groups。允許對(duì)某一個(gè)進(jìn)程,或者一組進(jìn)程所用到的資源進(jìn)行控制?,F(xiàn)在流行的 Docker 就是在這個(gè)底層機(jī)制上成長(zhǎng)起來(lái)的。

在你的機(jī)器執(zhí)行執(zhí)行下面的命令可以查看當(dāng)前 cgroup 都支持對(duì)哪些資源進(jìn)行控制。

其中 cpu 和 cpuset 都是對(duì) CPU 資源進(jìn)行控制的子系統(tǒng)。cpu 是通過(guò)執(zhí)行時(shí)間來(lái)控制進(jìn)程對(duì) cpu 的使用,cpuset 是通過(guò)分配邏輯核的方式來(lái)分配 cpu。其它可控制的資源還包括 memory(內(nèi)存)、net_cls(網(wǎng)絡(luò)帶寬)等等。

cgroup 提供了一個(gè)原生接口并通過(guò) cgroupfs 提供控制。類似于 procfs 和 sysfs,是一種虛擬文件系統(tǒng)。默認(rèn)情況下 cgroupfs 掛載在 /sys/fs/cgroup 目錄下,我們可以通過(guò)修改 /sys/fs/cgroup 下的文件和文件內(nèi)容來(lái)控制進(jìn)程對(duì)資源的使用。

比如,想實(shí)現(xiàn)讓某個(gè)進(jìn)程只使用兩個(gè)核,我們可以通過(guò) cgroupfs 接口這樣來(lái)實(shí)現(xiàn),如下:

其中?cfs_period_us?用來(lái)配置時(shí)間周期長(zhǎng)度,cfs_quota_us??用來(lái)配置當(dāng)前 cgroup 在設(shè)置的周期長(zhǎng)度內(nèi)所能使用的 CPU 時(shí)間。這兩個(gè)文件配合起來(lái)就可以設(shè)置 CPU 的使用上限。

上面的配置就是設(shè)置改 cgroup 下的進(jìn)程每 100 ms 內(nèi)只能使用 200 ms 的 CPU 周期,也就是說(shuō)限制使用最多兩個(gè)“核”。

要注意的是這種方式只限制的是 CPU 使用時(shí)間,具體調(diào)度的時(shí)候是可能會(huì)調(diào)度到任意 CPU 上執(zhí)行的。如果想限制進(jìn)程使用的 CPU 核,可以使用 cpuset 子系統(tǒng),詳情參見(jiàn)一次限制進(jìn)程的 CPU 用量的實(shí)操過(guò)程

docker 默認(rèn)情況下使用的就是 cgroupfs 接口,可以通過(guò)如下的命令來(lái)確認(rèn)。

二、內(nèi)核中進(jìn)程和 cgroup 的關(guān)系

在上一節(jié)中,我們?cè)?/sys/fs/cgroup/cpu,cpuacct 創(chuàng)建了一個(gè)目錄 test,這其實(shí)是創(chuàng)建了一個(gè) cgroup 對(duì)象。當(dāng)我們把某個(gè)進(jìn)程的 pid 添加到 cgroup 后,又是建立了進(jìn)程結(jié)構(gòu)體和 cgroup 之間的關(guān)系。

所以要想理解清 cgroup 的工作過(guò)程,就得先來(lái)了解一下 cgroup 和 task_struct 結(jié)構(gòu)體之間的關(guān)系。

2.1 cgroup 內(nèi)核對(duì)象

一個(gè) cgroup 對(duì)象中可以指定對(duì) cpu、cpuset、memory 等一種或多種資源的限制。我們先來(lái)找到 cgroup 的定義。

每個(gè) cgroup 都有一個(gè) cgroup_subsys_state 類型的數(shù)組 subsys,其中的每一個(gè)元素代表的是一種資源控制,如 cpu、cpuset、memory 等等。

這里要注意的是,其實(shí) cgroup_subsys_state 并不是真實(shí)的資源控制統(tǒng)計(jì)信息結(jié)構(gòu),對(duì)于 CPU 子系統(tǒng)真正的資源控制結(jié)構(gòu)是 task_group。它是 cgroup_subsys_state 結(jié)構(gòu)的擴(kuò)展,類似父類和子類的概念。

當(dāng) task_group 需要被當(dāng)成 cgroup_subsys_state 類型使用的時(shí)候,只需要強(qiáng)制類型轉(zhuǎn)換就可以。

對(duì)于內(nèi)存子系統(tǒng)控制統(tǒng)計(jì)信息結(jié)構(gòu)是 mem_cgroup,其它子系統(tǒng)也類似。

之所以要這么設(shè)計(jì),目的是各個(gè) cgroup 子系統(tǒng)都統(tǒng)一對(duì)外暴露 cgroup_subsys_state,其余部分不對(duì)外暴露,在自己的子系統(tǒng)內(nèi)部維護(hù)和使用。

2.2 進(jìn)程和 cgroup 子系統(tǒng)

一個(gè) Linux 進(jìn)程既可以對(duì)它的 cpu 使用進(jìn)行限制,也可以對(duì)它的內(nèi)存進(jìn)行限制。所以,一個(gè)進(jìn)程 task_struct 是可以和多種子系統(tǒng)有關(guān)聯(lián)關(guān)系的。

和 cgroup 和多個(gè)子系統(tǒng)關(guān)聯(lián)定義類似,task_struct 中也定義了一個(gè) cgroup_subsys_state 類型的數(shù)組 subsys,來(lái)表達(dá)這種一對(duì)多的關(guān)系。

我們來(lái)簡(jiǎn)單看下源碼的定義。

其中subsys是一個(gè)指針數(shù)組,存儲(chǔ)一組指向 cgroup_subsys_state 的指針。一個(gè) cgroup_subsys_state 就是進(jìn)程與一個(gè)特定的子系統(tǒng)相關(guān)的信息。

通過(guò)這個(gè)指針,進(jìn)程就可以獲得相關(guān)聯(lián)的 cgroups 控制信息了。能查到限制該進(jìn)程對(duì)資源使用的 task_group、cpuset、mem_group 等子系統(tǒng)對(duì)象。

2.3 內(nèi)核對(duì)象關(guān)系圖匯總

我們把上面的內(nèi)核對(duì)象關(guān)系圖匯總起來(lái)看一下。

可以看到無(wú)論是進(jìn)程、還是 cgroup 對(duì)象,最后都能找到和其關(guān)聯(lián)的具體的 cpu、內(nèi)存等資源控制自系統(tǒng)的對(duì)象。

2.4 cpu 子系統(tǒng)

因?yàn)榻裉煳覀冎攸c(diǎn)是介紹進(jìn)程的 cpu 限制,所以我們把 cpu 子系統(tǒng)相關(guān)的對(duì)象 task_group 專門拿出來(lái)理解理解。

第一個(gè) cgroup_subsys_state css 成員我們?cè)谇懊嬲f(shuō)過(guò)了,這相當(dāng)于它的“父類”。再來(lái)看 parent、siblings、children 等幾個(gè)對(duì)象。這些成員是樹相關(guān)的數(shù)據(jù)結(jié)構(gòu)。在整個(gè)系統(tǒng)中有一個(gè) root_task_group。

所有的 task_group 都是以 root_task_group 為根節(jié)點(diǎn)組成了一棵樹。

接下來(lái)的 se 和 cfs_rq 是完全公平調(diào)度的兩個(gè)對(duì)象。它們兩都是數(shù)組,元素個(gè)數(shù)等于當(dāng)前系統(tǒng)的 CPU 核數(shù)。每個(gè) task_group 都會(huì)在上一級(jí) task_group(比如 root_task_group)的 N 個(gè)調(diào)度隊(duì)列中有一個(gè)調(diào)度實(shí)體。

cfs_rq 是 task_group 自己所持有的完全公平調(diào)度隊(duì)列。是的,你沒(méi)看錯(cuò)。每一個(gè) task_group 內(nèi)部都有自己的一組調(diào)度隊(duì)列,其數(shù)量和 CPU 的核數(shù)一致。

假如當(dāng)前系統(tǒng)有兩個(gè)邏輯核,那么一個(gè) task_group 樹和 cfs_rq 的簡(jiǎn)單示意圖大概是下面這個(gè)樣子。

Linux 中的進(jìn)程調(diào)度是一個(gè)層級(jí)的結(jié)構(gòu)。對(duì)于容器來(lái)講,宿主機(jī)中進(jìn)行進(jìn)程調(diào)度的時(shí)候,先調(diào)度到的實(shí)際上不是容器中的具體某個(gè)進(jìn)程,而是一個(gè) task_group。然后接下來(lái)再進(jìn)入容器 task_group 的調(diào)度隊(duì)列 cfs_rq 中進(jìn)行調(diào)度,才能最終確定具體的進(jìn)程 pid。

還有就是 cpu 帶寬限制 cfs_bandwidth, cpu 分配的管控相關(guān)的字段都是在 cfs_bandwidth 中定義維護(hù)的。

cgroup 相關(guān)的內(nèi)核對(duì)象我們就先介紹到這里,接下來(lái)我們看一下 cpu 子系統(tǒng)到底是如何實(shí)現(xiàn)的。


【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ?


三、CPU 子系統(tǒng)的實(shí)現(xiàn)

在第一節(jié)中我們展示通過(guò) cgroupfs 對(duì) cpu 子系統(tǒng)使用,使用過(guò)程大概可以分成三步:

  • 第一步:通過(guò)創(chuàng)建目錄來(lái)創(chuàng)建 cgroup

  • 第二步:在目錄中設(shè)置 cpu 的限制情況

  • 第三步:將進(jìn)程添加到 cgroup 中進(jìn)行資源管控

那本小節(jié)我們就從上面三步展開,看看在每一步中,內(nèi)核都具體做了哪些事情。限于篇幅所限,我們只講 cpu 子系統(tǒng),對(duì)于其他的子系統(tǒng)也是類似的分析過(guò)程。

3.1 創(chuàng)建 cgroup 對(duì)象

內(nèi)核定義了對(duì) cgroupfs 操作的具體處理函數(shù)。在 /sys/fs/cgroup/ 下的目錄創(chuàng)建操作都將由下面 cgroup_kf_syscall_ops 定義的方法來(lái)執(zhí)行。

創(chuàng)建目錄執(zhí)行整個(gè)過(guò)程鏈條如下

其中關(guān)鍵的創(chuàng)建過(guò)程有:

  • cgroup_mkdir:在這里創(chuàng)建了 cgroup 內(nèi)核對(duì)象

  • css_create:創(chuàng)建每一個(gè)子系統(tǒng)資源管理對(duì)象,對(duì)于 cpu 子系統(tǒng)會(huì)創(chuàng)建 task_group

cgroup 內(nèi)核對(duì)象是在 cgroup_mkdir 中創(chuàng)建的。除了 cgroup 內(nèi)核對(duì)象,這里還創(chuàng)建了文件系統(tǒng)重要展示的目錄。

在 cgroup 中,是有層次的概念的,這個(gè)層次結(jié)構(gòu)和 cgroupfs 中的目錄層次結(jié)構(gòu)一樣。所以在創(chuàng)建 cgroup 對(duì)象之前的第一步就是先找到其父 cgroup, 然后創(chuàng)建自己,并創(chuàng)建文件系統(tǒng)中的目錄以及文件。

在 cgroup_apply_control_enable 中,執(zhí)行子系統(tǒng)對(duì)象的創(chuàng)建。

通過(guò) for_each_subsys 遍歷每一種 cgroup 子系統(tǒng),并調(diào)用其 css_alloc 來(lái)創(chuàng)建相應(yīng)的對(duì)象。

上面的 css_alloc 是一個(gè)函數(shù)指針,對(duì)于 cpu 子系統(tǒng)來(lái)說(shuō),它指向的是 cpu_cgroup_css_alloc。這個(gè)對(duì)應(yīng)關(guān)系在 kernel/sched/core.c 文件仲可以找到

通過(guò) cpu_cgroup_css_alloc => sched_create_group 調(diào)用后,創(chuàng)建出了 cpu 子系統(tǒng)的內(nèi)核對(duì)象 task_group。

3.2 設(shè)置 CPU 子系統(tǒng)限制

第一節(jié)中,我們通過(guò)對(duì) cpu 子系統(tǒng)目錄下的 cfs_period_us 和 cfs_quota_us 值的修改,來(lái)完成了 cgroup 中限制的設(shè)置。我們這個(gè)小節(jié)再看看看這個(gè)設(shè)置過(guò)程。

當(dāng)用戶讀寫這兩個(gè)文件的時(shí)候,內(nèi)核中也定義了對(duì)應(yīng)的處理函數(shù)。

寫處理函數(shù) cpu_cfs_quota_write_s64、cpu_cfs_period_write_u64 最終又都是調(diào)用 tg_set_cfs_bandwidth 來(lái)完成設(shè)置的。

在 task_group 中,其帶寬管理控制都是由 cfs_bandwidth 來(lái)完成的,所以一開始就需要先獲取 cfs_bandwidth 對(duì)象。接著將用戶設(shè)置的值都設(shè)置到 cfs_bandwidth 類型的對(duì)象 cfs_b 上。

3.3 寫 proc 進(jìn) group

cgroup 創(chuàng)建好了,cpu 限制規(guī)則也制定好了,下一步就是將進(jìn)程添加到這個(gè)限制中。在 cgroupfs 下的操作方式就是修改 cgroup.procs 文件。

內(nèi)核定義了修改 cgroup.procs 文件的處理函數(shù)為 cgroup_procs_write。

在 cgroup_procs_write 的處理中,主要做了這么幾件事情。

  • 第一、邏根據(jù)用戶輸入的 pid 來(lái)查找 task_struct 內(nèi)核對(duì)象。

  • 第二、從舊的調(diào)度組中退出,加入到新的調(diào)度組 task_group 中

  • 第三、修改進(jìn)程其 cgroup 相關(guān)的指針,讓其指向上面創(chuàng)建好的 task_group。

我們來(lái)看下加入新調(diào)度組的過(guò)程,內(nèi)核的調(diào)用鏈條如下。

在 cgroup_migrate_execute 中遍歷各個(gè)子系統(tǒng),完成每一個(gè)子系統(tǒng)的遷移。

對(duì)于 cpu 子系統(tǒng)來(lái)講,attach 對(duì)應(yīng)的處理方法是 cpu_cgroup_attach。這也是在 kernel/sched/core.c 下的 cpu_cgrp_subsys 中定義的。

cpu_cgroup_attach 調(diào)用 sched_move_task 來(lái)完成將進(jìn)程加入到新調(diào)度組的過(guò)程。

這個(gè)函數(shù)做了三件事。

  • 第一、先調(diào)用 dequeue_task 從原歸屬的 queue 中退出來(lái),

  • 第二、修改進(jìn)程的 task_group

  • 第三、重新將進(jìn)程添加到新 task_group 的 runqueue 中。

進(jìn)程 task_struct 的 sched_task_group 是表示其歸屬的 task_group, 這里設(shè)置到新歸屬上。

四、進(jìn)程 CPU 帶寬控制過(guò)程

在前面的操作完畢之后,我們只是將進(jìn)程添加到了 cgroup 中進(jìn)行管理而已。相當(dāng)于只是初始化,而真正的限制是貫穿在 Linux 運(yùn)行是的進(jìn)程調(diào)度過(guò)程中的。

所添加的進(jìn)程將會(huì)受到 cpu 子系統(tǒng) task_group 下的 cfs_bandwidth 中記錄的 period 和 quota 的限制。

在你的新進(jìn)程是如何被內(nèi)核調(diào)度執(zhí)行到的?一文中我們介紹過(guò)完全公平調(diào)度器在選擇進(jìn)程時(shí)的核心方法 pick_next_task_fair。

這個(gè)方法的整個(gè)執(zhí)行過(guò)程一個(gè)自頂向下搜索可執(zhí)行的 task_struct 的過(guò)程。整個(gè)系統(tǒng)中有一個(gè) root_task_group。

CFS 中調(diào)度隊(duì)列是一顆紅黑樹, 紅黑樹的節(jié)點(diǎn)是 struct sched_entity, sched_entity 中既可以指向 struct task_struct 也可以指向 struct cfs_rq(可理解為 task_group)

調(diào)度 pick_next_task_fair()函數(shù)中的 prev 是本次調(diào)度時(shí)在執(zhí)行的上一個(gè)進(jìn)程。該函數(shù)通過(guò) do {} while 循環(huán),自頂向下搜索到下一步可執(zhí)行進(jìn)程。

如果新進(jìn)程和上一次運(yùn)行的進(jìn)程不是同一個(gè),則要調(diào)用 put_prev_entity 做兩件和 CPU 的帶寬控制有關(guān)的事情。

在上述代碼中,和 CPU 帶寬控制相關(guān)的操作有兩個(gè)。

  • 運(yùn)行隊(duì)列帶寬的更新與申請(qǐng)

  • 判斷是否需要進(jìn)行帶寬限制

接下來(lái)我們分兩個(gè)小節(jié)詳細(xì)展開看看這兩個(gè)操作具體都做了哪些事情。

4.1 運(yùn)行隊(duì)列帶寬的更新與申請(qǐng)

在這個(gè)小節(jié)中我們專門來(lái)看看 cfs_rq 隊(duì)列中 runtime_remaining 的更新與申請(qǐng)

在實(shí)現(xiàn)上帶寬控制是在 task_group 下屬的 cfs_rq 隊(duì)列中進(jìn)行的。cfs_rq 對(duì)帶寬時(shí)間的操作歸總起來(lái)就是更新與申請(qǐng)。申請(qǐng)到的時(shí)間保存在字段 runtime_remaining 字段中,每當(dāng)有時(shí)間支出需要更新的時(shí)候也是從這個(gè)字段值從去除。

其實(shí)除了上述場(chǎng)景外,系統(tǒng)在很多情況下都會(huì)調(diào)用 update_curr,包括任務(wù)在入隊(duì)、出隊(duì)時(shí),調(diào)度中斷函數(shù)也會(huì)周期性地調(diào)用該方法,以確保任務(wù)的各種時(shí)間信息隨時(shí)都是最新的狀態(tài)。在這里會(huì)更新 cfs_rq 隊(duì)列中的 runtime_remaining 時(shí)間。如果 runtime_remaining 不足,會(huì)觸發(fā)時(shí)間申請(qǐng)。

在 update_curr 先計(jì)算當(dāng)前執(zhí)行了多少時(shí)間。然后在 cfs_rq 的 runtime_remaining 減去該時(shí)間值,具體減的過(guò)程是在 account_cfs_rq_runtime 中處理的。

更新帶寬時(shí)間的邏輯比較簡(jiǎn)單,先從 cfs->runtime_remaining 減去本次執(zhí)行的物理時(shí)間。如果減去之后仍然大于 0 ,那么本次更新就算是結(jié)束了。

如果相減后發(fā)現(xiàn)是負(fù)數(shù),表示當(dāng)前 cfs_rq 的時(shí)間余額已經(jīng)耗盡,則會(huì)立即嘗試從任務(wù)組中申請(qǐng)。具體的申請(qǐng)函數(shù)是 assign_cfs_rq_runtime。如果申請(qǐng)沒(méi)能成功,調(diào)用 resched_curr 標(biāo)記 cfs_rq->curr 的 TIF_NEED_RESCHED 位,以便隨后將其調(diào)度出去。

我們展開看下申請(qǐng)過(guò)程 assign_cfs_rq_runtime 。

首先,獲取當(dāng)前 task_group 的 cfs_bandwidth,因?yàn)檎麄€(gè)任務(wù)組的帶寬數(shù)據(jù)都是封裝在這里的。接著調(diào)用 sched_cfs_bandwidth_slice 來(lái)獲取后面要留有多長(zhǎng)時(shí)間,這個(gè)函數(shù)訪問(wèn)的 sysctl 下的 sched_cfs_bandwidth_slice 參數(shù)。

這個(gè)參數(shù)在我的機(jī)器上是 5000 us(也就是說(shuō)每次申請(qǐng) 5 ms)。

在計(jì)算要申請(qǐng)的時(shí)間的時(shí)候,還需要考慮現(xiàn)在有多少時(shí)間。如果 cfs_rq->runtime_remaining 為正的話,那可以少申請(qǐng)一點(diǎn),如果已經(jīng)變?yōu)樨?fù)數(shù)的話,需要在 sched_cfs_bandwidth_slice 基礎(chǔ)之上再多申請(qǐng)一些。

所以,最終要申請(qǐng)的時(shí)間值 ?min_amount = sched_cfs_bandwidth_slice() - cfs_rq->runtime_remaining

計(jì)算出 min_amount 后,直接在向自己所屬的 task_group 下的 cfs_bandwidth 把時(shí)間申請(qǐng)出來(lái)。整個(gè) task_group 下可用的時(shí)間是保存在?cfs_b->runtime?中的。

這里你可能會(huì)問(wèn)了,那 task_group 下的 cfs_b->runtime 的時(shí)間又是哪兒給分配的呢?我們將在 5.1 節(jié)來(lái)討論這個(gè)過(guò)程。

4.2 帶寬限制

check_cfs_rq_runtime 這個(gè)函數(shù)檢測(cè) task group 的帶寬是否已經(jīng)耗盡, 如果是則調(diào)用 throttle_cfs_rq 對(duì)進(jìn)程進(jìn)行限流。

我們?cè)賮?lái)看看 throttle_cfs_rq 的執(zhí)行過(guò)程。

在 throttle_cfs_rq 中,找到其所屬的 task_group 下的調(diào)度實(shí)體 se 數(shù)組,遍歷每一個(gè)元素,并從其隸屬的 cfs_rq 的紅黑樹上刪除。這樣下次再調(diào)度的時(shí)候,就不會(huì)再調(diào)度到這些進(jìn)程了。

那么 start_cfs_bandwidth 是干啥的呢?這正好是下一節(jié)的引子。

五、進(jìn)程的可運(yùn)行時(shí)間的分配

在第四小節(jié)我們看到,task_group 下的進(jìn)程的運(yùn)行時(shí)間都是從它的 cfs_b->runtime 中申請(qǐng)的。這個(gè)時(shí)間是在定時(shí)器中分配的。負(fù)責(zé)給 task_group 分配運(yùn)行時(shí)間的定時(shí)器包括兩個(gè),一個(gè)是 period_timer,另一個(gè)是 slack_timer。

peroid_timer 是周期性給 task_group 添加時(shí)間,缺點(diǎn)是 timer 周期比較長(zhǎng),通常是100ms。而 slack_timer 用于有 cfs_rq 處于 throttle 狀態(tài)且全局時(shí)間池有時(shí)間供分配但是 period_timer 有還有比較長(zhǎng)時(shí)間(通常大于7ms)才超時(shí)的場(chǎng)景。這個(gè)時(shí)候我們就可以激活比較短的slack_timer(5ms超時(shí))進(jìn)行throttle,這樣的設(shè)計(jì)可以提升系統(tǒng)的實(shí)時(shí)性。

這兩個(gè) timer 在 cgroup 下的 cfs_bandwidth 初始化的時(shí)候,都設(shè)置好了到期回調(diào)函數(shù),分別是 sched_cfs_period_timer 和 sched_cfs_slack_timer。

在上一節(jié)最后提到的 start_cfs_bandwidth 就是在打開 period_timer 定時(shí)器。

在 hrtimer_forward_now 調(diào)用時(shí)傳入的第二個(gè)參數(shù)表示是觸發(fā)的延遲時(shí)間。這個(gè)就是在 cgroup 是設(shè)置的 period,一般為 100 ms。

我們來(lái)分別看看這兩個(gè) timer 是如何給 task_group 定期發(fā)工資(分配時(shí)間)的。

5.1 period_timer

在 period_timer 的回調(diào)函數(shù) sched_cfs_period_timer 中,周期性地為任務(wù)組分配帶寬時(shí)間,并且解掛當(dāng)前任務(wù)組中所有掛起的隊(duì)列。

分配帶寬時(shí)間是在 __refill_cfs_bandwidth_runtime 中執(zhí)行的,它的調(diào)用堆棧如下。

可見(jiàn),這里直接給 cfs_b->runtime 增加了 cfs_b->quota 這么多的時(shí)間。其中 cfs_b->quota 你就可以認(rèn)為是在 cgroupfs 目錄下,我們配置的那個(gè)值。在第一節(jié)中,我們配置的是 500 ms。

5.2 slack_timer

設(shè)想一下,假如說(shuō)某個(gè)進(jìn)程申請(qǐng)了 5 ms 的執(zhí)行時(shí)間,但是當(dāng)進(jìn)程剛一啟動(dòng)執(zhí)行便執(zhí)行了同步阻塞的邏輯,這時(shí)候所申請(qǐng)的時(shí)間根本都沒(méi)有用完。在這種情況下,申請(qǐng)但沒(méi)用完的時(shí)間大部分是要返還給 task_group 中的全局時(shí)間池的。

在內(nèi)核中的調(diào)用鏈如下

具體的返還是在 __return_cfs_rq_runtime 中處理的。

這個(gè)函數(shù)做了這么幾件事情。

  • min_cfs_rq_runtime 的值是 1 ms,我們選擇至少保留 1ms 時(shí)間給自己

  • 剩下的時(shí)間 slack_runtime 歸還給當(dāng)前的 cfs_b->runtime

  • 如果時(shí)間又足夠多了,并且還有進(jìn)程被限制的話,開啟slack_timer,嘗試接觸進(jìn)程 CPU 限制

在 start_cfs_slack_bandwidth 中啟動(dòng)了 slack_timer。

可見(jiàn) slack_timer 的延遲回調(diào)時(shí)間是 cfs_bandwidth_slack_period,它的值是 5 ms。這就比 period_timer 要實(shí)時(shí)多了。

slack_timer 的回調(diào)函數(shù) sched_cfs_slack_timer 我們就不展開看了,它主要就是操作對(duì)進(jìn)程解除 CPU 限制

六、總結(jié)

今天我們介紹了 Linux cgroup 的 cpu 子系統(tǒng)給容器中的進(jìn)程分配 cpu 時(shí)間的原理。

和真正使用物理機(jī)不同,Linux 容器中所謂的核并不是真正的 CPU 核,而是轉(zhuǎn)化成了執(zhí)行時(shí)間的概念。在容器進(jìn)程調(diào)度的時(shí)候給其滿足一定的 CPU 執(zhí)行時(shí)間,而不是真正的分配邏輯核。

cgroup 提供了的原生接口是通過(guò) cgroupfs 提供控制各個(gè)子系統(tǒng)的設(shè)置的。默認(rèn)是在 /sys/fs/cgroup/ 目錄下,內(nèi)核這個(gè)文件系統(tǒng)的處理是定義了特殊的處理,和普通的文件完全不一樣的。

內(nèi)核處理 cpu 帶寬控制的核心對(duì)象就是下面這個(gè) cfs_bandwidth。


用戶在創(chuàng)建 cgroup cpu 子系統(tǒng)控制過(guò)程主要分成三步:

  • 第一步:通過(guò)創(chuàng)建目錄來(lái)創(chuàng)建 cgroup 對(duì)象。在 /sys/fs/cgroup/cpu,cpuacct 創(chuàng)建一個(gè)目錄 test,實(shí)際上內(nèi)核是創(chuàng)建了 cgroup、task_group 等內(nèi)核對(duì)象。

  • 第二步:在目錄中設(shè)置 cpu 的限制情況。在 task_group 下有個(gè)核心的 cfs_bandwidth 對(duì)象,用戶所設(shè)置的 cfs_quota_us 和 cfs_period_us 的值最后都存到它下面了。

  • 第三步:將進(jìn)程添加到 cgroup 中進(jìn)行資源管控。當(dāng)在 cgroup 的 cgroup.proc 下添加進(jìn)程 pid 時(shí),實(shí)際上是將該進(jìn)程加入到了這個(gè)新的 task_group 調(diào)度組了。將使用 task_group 的 runqueue,以及它的時(shí)間配額

當(dāng)創(chuàng)建完成后,內(nèi)核的 period_timer 會(huì)根據(jù) task_group->cfs_bandwidth 下用戶設(shè)置的 period 定時(shí)給可執(zhí)行時(shí)間 runtime 上加上 quota 這么多的時(shí)間(相當(dāng)于按月發(fā)工資),以供 task_group 下的進(jìn)程執(zhí)行(消費(fèi))的時(shí)候使用。

在完全公平器調(diào)度的時(shí)候,每次 pick_next_task_fair 時(shí)會(huì)做兩件事情

  • 第一件:將從 cpu 上拿下來(lái)的進(jìn)程所在的運(yùn)行隊(duì)列進(jìn)行執(zhí)行時(shí)間的更新與申請(qǐng)。會(huì)將 cfs_rq 的 runtime_remaining 減去已經(jīng)執(zhí)行了的時(shí)間。如果減為了負(fù)數(shù),則從 cfs_rq 所在的 task_group 下的 cfs_bandwidth 去申請(qǐng)一些。

  • 第二件:判斷 cfs_rq 上是否申請(qǐng)到了可執(zhí)行時(shí)間,如果沒(méi)有申請(qǐng)到,需要將這個(gè)隊(duì)列上的所有進(jìn)程都從完全公平調(diào)度器的紅黑樹上取下。這樣再次調(diào)度的時(shí)候,這些進(jìn)程就不會(huì)被調(diào)度了。

當(dāng) period_timer 再次給 task_group 分配時(shí)間的時(shí)候,或者是自己有申請(qǐng)時(shí)間沒(méi)用完回收后觸發(fā) slack_timer 的時(shí)候,被限制調(diào)度的進(jìn)程會(huì)被解除調(diào)度限制,重新正常參與運(yùn)行。

這里要注意的是,一般 period_timer 分配時(shí)間的周期都是 100 ms 左右。假如說(shuō)你的進(jìn)程前 50 ms 就把 cpu 給用光了,那你收到的請(qǐng)求可能在后面的 50 ms 都沒(méi)有辦法處理,對(duì)請(qǐng)求處理耗時(shí)會(huì)有影響。這也是為啥在關(guān)注 CPU 性能的時(shí)候要關(guān)注對(duì)容器 throttle 次數(shù)和時(shí)間的原因了。


原文作者:開發(fā)內(nèi)功修煉



一文解決內(nèi)核是如何給容器中的進(jìn)程分配CPU資源的?的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
新绛县| 华蓥市| 浮山县| 肥城市| 偏关县| SHOW| 伊吾县| 衡阳市| 泾川县| 深泽县| 贞丰县| 隆回县| 精河县| 永寿县| 洞口县| 吉安市| 五指山市| 成武县| 丰原市| 寿光市| 平乐县| 团风县| 新泰市| 平利县| 延寿县| 新余市| 琼海市| 苍溪县| 招远市| 嵊州市| 九龙坡区| 西乡县| 湖南省| 神农架林区| 乐清市| 成武县| 运城市| 云南省| 合作市| 大邑县| 康保县|