最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

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

一篇解決Linux 中的負(fù)載高低和 CPU 開銷并不完全對應(yīng)

2023-02-15 16:55 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿

負(fù)載是查看 Linux 服務(wù)器運(yùn)行狀態(tài)時很常用的一個性能指標(biāo)。在觀察線上服務(wù)器運(yùn)行狀況的時候,我們也是經(jīng)常把負(fù)載找出來看一看。在線上請求壓力過大的時候,經(jīng)常是也伴隨著負(fù)載的飆高。

但是負(fù)載的原理你真的理解了嗎?我來列舉幾個問題,看看你對負(fù)載的理解是否足夠的深刻。

  • 負(fù)載是如何計算出來的?

  • 負(fù)載高低和 CPU 消耗正相關(guān)嗎?

  • 內(nèi)核是如何暴露負(fù)載數(shù)據(jù)給應(yīng)用層的?

如果你對以上問題的理解還拿捏不是很準(zhǔn),那么今天就帶你來深入地了解一下 Linux 中的負(fù)載?!

一、理解負(fù)載查看過程

我們經(jīng)常用 top 命令查看 Linux 系統(tǒng)的負(fù)載情況。一個典型的 top 命令輸出的負(fù)載如下所示。

輸出中的 Load Avg 就是我們常說的負(fù)載,也叫系統(tǒng)平均負(fù)載。因?yàn)閱渭兡骋粋€瞬時的負(fù)載值并沒有太大意義。所以 Linux 是計算了過去一段時間內(nèi)的平均值,這三個數(shù)分別代表的是過去 1 分鐘、過去 5 分鐘和過去 15 分鐘的平均負(fù)載值。

那么 top 命令展示的數(shù)據(jù)數(shù)是如何來的呢?事實(shí)上,top 命令里的負(fù)載值是從 /proc/loadavg 這個偽文件里來的。通過 strace 命令跟蹤 top 命令的系統(tǒng)調(diào)用可以看的到這個過程。

內(nèi)核中定義了 loadavg 這個偽文件的 open 函數(shù)。當(dāng)用戶態(tài)訪問 /proc/loadavg 會觸發(fā)內(nèi)核定義的函數(shù),在這里會讀取內(nèi)核中的平均負(fù)載變量,簡單計算后便可展示出來。整體流程如下圖所示。

我們根據(jù)上述流程圖再展開了看下。偽文件 /proc/loadavg 在 kernel 中定義是在 /fs/proc/loadavg.c 中。在該文件中會創(chuàng)建 /proc/loadavg,并為其指定操作方法 loadavg_proc_fops。

在 loadavg_proc_fops 中包含了打開該文件時對應(yīng)的操作方法。

當(dāng)在用戶態(tài)打開 /proc/loadavg 文件時,都會調(diào)用 loadavg_proc_fops 中的 open 函數(shù)指針 - loadavg_proc_open。loadavg_proc_open 接下來會調(diào)用 loadavg_proc_show 進(jìn)行處理,核心的計算是在這里完成的。

在 loadavg_proc_show 函數(shù)中做了兩件事。

  • 調(diào)用 get_avenrun 讀取當(dāng)前負(fù)載值

  • 將平均負(fù)載值按照一定的格式打印輸出

在上面的源碼中,大家看到了 FIXED_1/200、LOAD_INT、LOAD_FRAC 等奇奇怪怪的定義,代碼寫的這么猥瑣是因?yàn)閮?nèi)核中并沒有 float、double 等浮點(diǎn)數(shù)類型,而是用整數(shù)來模擬的。這些代碼都是為了在整數(shù)和小數(shù)之間轉(zhuǎn)化使的。知道這個背景就行了,不用過度展開剖析。

這樣用戶通過訪問 /proc/loadavg 文件就可以讀取到內(nèi)核計算的負(fù)載數(shù)據(jù)了。其中獲取 get_avenrun 只是在訪問 avenrun 這個全局?jǐn)?shù)組而已。

現(xiàn)在可以總結(jié)一下我們開篇中的一個問題:?內(nèi)核是如何暴露負(fù)載數(shù)據(jù)給應(yīng)用層的

內(nèi)核定義了一個偽文件 /proc/loadavg,每當(dāng)用戶打開這個文件的時候,內(nèi)核中的 loadavg_proc_show 函數(shù)就會被調(diào)用到,接著訪問 avenrun 全局?jǐn)?shù)組變量 并將平均負(fù)載從整數(shù)轉(zhuǎn)化為小數(shù),并打印出來。

好了,另外一個新問題又來了,avenrun 全局?jǐn)?shù)組變量中存儲的數(shù)據(jù)是何時,又是被如何計算出來的呢?


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


二、內(nèi)核中負(fù)載的計算過程

接上小節(jié),我們繼續(xù)查看 avenrun 全局?jǐn)?shù)組變量的數(shù)據(jù)來源。這個數(shù)組的計算過程分為如下兩步:

1.PerCPU 定期匯總瞬時負(fù)載:定時刷新每個 CPU 當(dāng)前任務(wù)數(shù)到 calc_load_tasks,將每個 CPU 的負(fù)載數(shù)據(jù)匯總起來,得到系統(tǒng)當(dāng)前的瞬時負(fù)載。
2.定時計算系統(tǒng)平均負(fù)載:定時器根據(jù)當(dāng)前系統(tǒng)整體瞬時負(fù)載,使用指數(shù)加權(quán)移動平均法(一種高效計算平均數(shù)的算法)計算過去 1 分鐘、過去 5 分鐘、過去 15 分鐘的平均負(fù)載。

接下來我們分成兩個小節(jié)來分別介紹。

2.1 PerCPU 定期匯總負(fù)載

在 Linux 內(nèi)核中,有一個子系統(tǒng)叫做時間子系統(tǒng)。在時間子系統(tǒng)里,初始化了一個叫高分辨率的定時器。在該定時器中會定時將每個 CPU 上的負(fù)載數(shù)據(jù)(running 進(jìn)程數(shù) + uninterruptible 進(jìn)程數(shù))匯總到系統(tǒng)全局的瞬時負(fù)載變量 calc_load_tasks 中。整體流程如下圖所示。

我們把上述流程圖展開看一下,我們找到了高分辨率定時器的源碼如下:

在高分辨率初始化的時候,將到期函數(shù)設(shè)置成了 tick_sched_timer。通過這個函數(shù)讓每個 CPU 都會周期性地執(zhí)行一些任務(wù)。其中刷新當(dāng)前系統(tǒng)負(fù)載就是在這個時機(jī)進(jìn)行的。這里有一點(diǎn)要注意一個前提是每個 CPU 都有自己獨(dú)立的運(yùn)行隊(duì)列,。

我們根據(jù) tick_sched_timer 的源碼進(jìn)行追蹤,它依次通過調(diào)用 tick_sched_handle => update_process_times => scheduler_tick。最終在 scheduler_tick 中會刷新當(dāng)前 CPU 上的負(fù)載值到 calc_load_tasks 上。因?yàn)槊總€ CPU 都在定時刷,所以 calc_load_tasks 上記錄的就是整個系統(tǒng)的瞬時負(fù)載值。

我們來看下負(fù)責(zé)刷新的 scheduler_tick 這個核心函數(shù):

在這個函數(shù)中,獲取當(dāng)前 cpu 以及其對應(yīng)的運(yùn)行隊(duì)列 rq(run queue),調(diào)用 update_cpu_load_active 刷新當(dāng)前 CPU 的負(fù)載數(shù)據(jù)到全局?jǐn)?shù)組中。

在 calc_load_account_active 中看到,通過 calc_load_fold_active 獲取當(dāng)前運(yùn)行隊(duì)列的負(fù)載相對值,并把它加到全局瞬時負(fù)載值 calc_load_tasks 上。至此,calc_load_tasks 上就有了當(dāng)前系統(tǒng)當(dāng)前時間下的整體瞬時負(fù)載總數(shù)了。

我們再展開看看是如何根據(jù)運(yùn)行隊(duì)列計算負(fù)載值的:

哦,原來是同時計算了 nr_running 和 nr_uninterruptible 兩種狀態(tài)的進(jìn)程的數(shù)量。對應(yīng)于用戶空間中的 R 和 D 兩種狀態(tài)的 task 數(shù)(進(jìn)程 OR 線程)。

由于 calc_load_tasks 是一個長期存在的數(shù)據(jù)。所以在刷新 rq 里的進(jìn)程數(shù)到其上的時候,只需要刷變化的量就行,不用全部重算。因此上述函數(shù)返回的是一個 delta。

2.2 定時計算系統(tǒng)平均負(fù)載

上一小節(jié)中我們找到了系統(tǒng)當(dāng)前瞬時負(fù)載 calc_load_tasks 變量的更新過程?,F(xiàn)在我們還缺一個計算過去 1 分鐘、過去 5 分鐘、過去 15 分鐘平均負(fù)載的機(jī)制。

傳統(tǒng)意義上,我們在計算平均數(shù)的時候采取的方法都是把過去一段時間的數(shù)字都加起來然后平均一下。把過去 N 個時間點(diǎn)的所有瞬時負(fù)載都加起來取一個平均數(shù)不完事了。這其實(shí)是我們傳統(tǒng)意義上理解的平均數(shù),假如有 n 個數(shù)字,分別是 x1, x2, ..., xn。那么這個數(shù)據(jù)集合的平均數(shù)就是 (x1 + x2 + ... + xn) / N。

但是如果用這種簡單的算法來計算平均負(fù)載的話,存在以下幾個問題:

1.需要存儲過去每一個采樣周期的數(shù)據(jù)
假設(shè)我們每 10 毫秒都采集一次,那么就需要使用一個比較大的數(shù)組將每一次采樣的數(shù)據(jù)全部都存起來,那么統(tǒng)計過去 15 分鐘的平均數(shù)就得存 1500 個數(shù)據(jù)(15 分鐘 * 每分鐘 100 次) 。而且每出現(xiàn)一個新的觀察值,就要從移動平均中減去一個最早的觀察值,再加上一個最新的觀察值,內(nèi)存數(shù)組會頻繁地修改和更新。

2.計算過程較為復(fù)雜
計算的時候再把整個數(shù)組全加起來,再除以樣本總數(shù)。雖然加法很簡單,但是成百上千個數(shù)字的累加仍然很是繁瑣。

3.不能準(zhǔn)確表示當(dāng)前變化趨勢傳統(tǒng)的平均數(shù)計算過程中,所有數(shù)字的權(quán)重是一樣的。但對于平均負(fù)載這種實(shí)時應(yīng)用來說,其實(shí)越靠近當(dāng)前時刻的數(shù)值權(quán)重應(yīng)該越要大一些才好。因?yàn)檫@樣能更好反應(yīng)近期變化的趨勢。

所以,在 Linux 里使用的并不是我們所以為的傳統(tǒng)的平均數(shù)的計算方法,而是采用的一種指數(shù)加權(quán)移動平均(Exponential Weighted Moving Average,EMWA)的平均數(shù)計算法。

這種指數(shù)加權(quán)移動平均數(shù)計算法在深度學(xué)習(xí)中有很廣泛的應(yīng)用。另外股票市場里的 EMA 均線也是使用的是類似的方法求均值的方法。該算法的數(shù)學(xué)表達(dá)式是:a1 = a0 * factor + a * (1 - factor)。這個算法想理解起來有點(diǎn)小復(fù)雜,感興趣的同學(xué)可以 Google 自行搜索。

我們只需要知道這種方法在實(shí)際計算的時候只需要上一個時間的平均數(shù)即可,不需要保存所有瞬時負(fù)載值。另外就是越靠近現(xiàn)在的時間點(diǎn)權(quán)重越高,能夠很好地表示近期變化趨勢。

這其實(shí)也是在時間子系統(tǒng)中定時完成的,通過一種叫做指數(shù)加權(quán)移動平均計算的方法,計算這三個平均數(shù)。

我們來詳細(xì)看下上圖中的執(zhí)行過程。時間子系統(tǒng)將在時鐘中斷中會注冊時鐘中斷的處理函數(shù)為 timer_interrupt 。

當(dāng)每次時鐘節(jié)拍到來時會調(diào)用到 timer_interrupt,依次會調(diào)用到 do_timer 函數(shù)。

其中 calc_global_load 是平均負(fù)載計算的核心。它會獲取系統(tǒng)當(dāng)前瞬時負(fù)載值 calc_load_tasks,然后來計算過去 1 分鐘、過去 5 分鐘、過去 15 分鐘的平均負(fù)載,并保存到 avenrun 中,供用戶進(jìn)程讀取。

獲取瞬時負(fù)載比較簡單,就是讀取一個內(nèi)存變量而已。在 calc_load 中就是采用了我們前面說的指數(shù)加權(quán)移動平均法來計算過去 1 分鐘、過去 5 分鐘、過去 15 分鐘的平均負(fù)載的。具體實(shí)現(xiàn)的代碼如下:

雖然這個算法理解起來挺復(fù)雜,但是代碼看起來確實(shí)要簡單不少,計算量看起來很少。而且看不懂也沒有關(guān)系,只需要知道內(nèi)核并不是采用的原始的平均數(shù)計算方法,而是采用了一種計算快,且能更好表達(dá)變化趨勢的算法就行。

至此,我們開篇提到的“負(fù)載是如何計算出來的?”這個問題也有結(jié)論了。

Linux 定時將每個 CPU 上的運(yùn)行隊(duì)列中 running 和 uninterruptible 的狀態(tài)的進(jìn)程數(shù)量匯總到一個全局系統(tǒng)瞬時負(fù)載值中,然后再定時使用指數(shù)加權(quán)移動平均法來統(tǒng)計過去 1 分鐘、過去 5 分鐘、過去 15 分鐘的平均負(fù)載。

三、平均負(fù)載和 CPU 消耗的關(guān)系

現(xiàn)在很多同學(xué)都將平均負(fù)載和 CPU 給聯(lián)系到了一起。認(rèn)為負(fù)載高、CPU 消耗就會高,負(fù)載低,CPU 消耗就會低。

在很老的 Linux 的版本里,統(tǒng)計負(fù)載的時候確實(shí)是只計算了 runnable 的任務(wù)數(shù)量,這些進(jìn)程只對 CPU 有需求。在那個年代里,負(fù)載和 CPU 消耗量確實(shí)是正相關(guān)的。負(fù)載越高就表示正在 CPU 上運(yùn)行,或等待 CPU 執(zhí)行的進(jìn)程越多,CPU 消耗量也會越高。

但是前面我們看到了,本文使用的 3.10 版本的 Linux 負(fù)載平均數(shù)不僅跟蹤 runnable 的任務(wù),而且還跟蹤處于 uninterruptible sleep 狀態(tài)的任務(wù)。而 uninterruptible 狀態(tài)的進(jìn)程其實(shí)是不占 CPU 的。

所以說,負(fù)載高并一定是 CPU 處理不過來,也有可能會是因?yàn)榇疟P等其他資源調(diào)度不過來而使得進(jìn)程進(jìn)入 uninterruptible 狀態(tài)的進(jìn)程導(dǎo)致的!

為什么要這么修改。我從網(wǎng)上搜到了遠(yuǎn)在 1993 年的一封郵件里找到了原因,以下是郵件原文。

可見這個修改是在 1993 年就引入了。在這封郵件所示的 Linux 源碼變化中可以看到,負(fù)載正式把 TASK_UNINTERRUPTIBLE 和 TASK_SWAPPING 狀態(tài)(交換狀態(tài)后來從 Linux 中刪除)的進(jìn)程也給添加了進(jìn)來。在這封郵件中的正文中,作者也清楚地表達(dá)了為什么要把 TASK_UNINTERRUPTIBLE 狀態(tài)的進(jìn)程添加進(jìn)來的原因。我把他的說明翻譯一下,如下:

“內(nèi)核在計算平均負(fù)載時只計算“可運(yùn)行”進(jìn)程。我不喜歡那樣;問題是正在“快速”交換或等待的進(jìn)程,即不可中斷的 I/O,也會消耗資源。當(dāng)您用慢速交換磁盤替換快速交換磁盤時,平均負(fù)載下降似乎有點(diǎn)不直觀...... 無論如何,下面的補(bǔ)丁似乎使負(fù)載平均值更加一致 WRT 系統(tǒng)的主觀速度。而且,最重要的是,當(dāng)沒有人做任何事情時,負(fù)載仍然為零。;-)”

這一補(bǔ)丁提交者的主要思想是平均負(fù)載應(yīng)該表現(xiàn)對系統(tǒng)所有資源的需求情況,而不應(yīng)該只表現(xiàn)對 CPU 資源的需求。

假設(shè)某個 TASK_UNINTERRUPTIBLE 狀態(tài)的進(jìn)程因?yàn)榈却疟P IO 而排隊(duì)的話,此時它并不消耗 CPU,但是正在等磁盤等硬件資源。那么它是應(yīng)該體現(xiàn)在平均負(fù)載的計算里的。所以作者把 TASK_UNINTERRUPTIBLE 狀態(tài)的進(jìn)程都表現(xiàn)到平均負(fù)載里了。

所以,負(fù)載高低表明的是當(dāng)前系統(tǒng)上對系統(tǒng)資源整體需求更情況。如果負(fù)載變高,可能是 CPU 資源不夠了,也可能是磁盤 IO 資源不夠了,所以還需要配合其它觀測命令具體分情況分析。

四、總結(jié)

今天我?guī)Т蠹疑钊氲貙W(xué)習(xí)了一下 Linux 中的負(fù)載。我們根據(jù)一幅圖來總結(jié)一下今天學(xué)到的內(nèi)容。

我把負(fù)載工作原理分成了如下三步。

  • 1.內(nèi)核定時匯總每 CPU 負(fù)載到系統(tǒng)瞬時負(fù)載

  • 2.內(nèi)核使用指數(shù)加權(quán)移動平均快速計算過去1、5、15分鐘的平均數(shù)

  • 3.用戶進(jìn)程通過打開 loadavg 讀取內(nèi)核中的平均負(fù)載

我們再回頭來總結(jié)一下開篇提到的幾個問題。

1.負(fù)載是如何計算出來的?
是定時將每個 CPU 上的運(yùn)行隊(duì)列中 running 和 uninterruptible 的狀態(tài)的進(jìn)程數(shù)量匯總到一個全局系統(tǒng)瞬時負(fù)載值中,然后再定時使用指數(shù)加權(quán)移動平均法來統(tǒng)計過去 1 分鐘、過去 5 分鐘、過去 15 分鐘的平均負(fù)載。

2.負(fù)載高低和 CPU 消耗正相關(guān)嗎?
負(fù)載高低表明的是當(dāng)前系統(tǒng)上對系統(tǒng)資源整體需求更情況。如果負(fù)載變高,可能是 CPU 資源不夠了,也可能是磁盤 IO 資源不夠了。所以不能說看著負(fù)載變高,就覺得是 CPU 資源不夠用了。

3.內(nèi)核是如何暴露負(fù)載數(shù)據(jù)給應(yīng)用層的?
內(nèi)核定義了一個偽文件 /proc/loadavg,每當(dāng)用戶打開這個文件的時候,內(nèi)核中的 loadavg_proc_show 函數(shù)就會被調(diào)用到,該函數(shù)中訪問 avenrun 全局?jǐn)?shù)組變量,并將平均負(fù)載從整數(shù)轉(zhuǎn)化為小數(shù),然后打印出來。


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



一篇解決Linux 中的負(fù)載高低和 CPU 開銷并不完全對應(yīng)的評論 (共 條)

分享到微博請遵守國家法律
连城县| 清河县| 张北县| 齐河县| 呈贡县| 哈尔滨市| 五原县| 徐闻县| 临澧县| 五家渠市| 沅江市| 仙桃市| 新乡县| 肥东县| 新余市| 葵青区| 正安县| 东乡族自治县| 简阳市| 仁化县| 潼关县| 卓资县| 越西县| 剑川县| 盈江县| 清徐县| 轮台县| 秦安县| 曲麻莱县| 九龙坡区| 沧源| 皮山县| 山西省| 南开区| 武清区| 甘孜| 益阳市| 利辛县| 昌平区| 平昌县| 古浪县|