一文讀懂 Linux 系統平均負載
我們經常會使用 top
命令來查看系統的性能情況,在 top
命令的第一行可以看到 load average
這個數據,如下圖所示:
load average
包含 3 列,分別表示 1 分鐘、5 分鐘和 15 分鐘的 系統平均負載
。
對於系統平均負載這個數值,可能很多同學並不完全理解其意義,並不知道數值達到多少時才表示系統負載過高。本文將會以簡單的語言來介紹系統平均負載這個概念,並且會介紹 Linux 內核是怎麼計算這個數值。
系統平均負載
《Understanding Linux CPU Load(鏈接在文章最後)》這篇文章已經非常通俗的解釋了什麼是 系統平均負載
,這裏借用一下此文中的例子。
如果將 CPU 比作是橋樑,對於單核的 CPU 就好比是單車道的橋樑。每次橋樑只能讓一輛汽車通過,並且要以規定的速度通過。那麼:
- 如果每個時刻都只有一輛汽車通過,那麼所有汽車都不用排隊,此時橋樑的使用率最高。以平均負載 1.0 表示,如下圖所示:
- 如果每隔一段時間纔有一輛汽車通過,那麼表示橋樑部分時間處於空閒的情況。並且間隔的時間越長,表示橋樑空閒率越高。此時的平均負載小於 1.0,如下圖所示:
- 當有大量的汽車通過橋樑時,有些汽車需要等待其他車輛通過後才能繼續通行,這時表示橋樑超負荷工作。此時平均負載大於 1.0,如下圖所示:
系統的平均負載與上面的例子一樣,在單核 CPU 的環境下:
-
當平均負載等於 1.0 時,表示 CPU 使用率最高。
-
當平均負載小於 1.0 時,表示 CPU 使用率處於空閒狀態。
-
當平均負載大於 1.0 時,表示 CPU 使用率已經超過負荷。
對於單核 CPU 來說,平均負載 1.0 表示使用率最高。但對於多核 CPU 來說,平均負載要乘以核心數。比如在 4 核 CPU 的系統中,當平均負載爲 4.0 時,才表示 CPU 的使用率最高。
Linux 平均負載計算原理
在介紹系統平均負載的計算原理前,先要介紹一下什麼是系統負載。在 Linux 系統中,系統負載表示 系統中當前正在運行的進程數量,其包括 可運行狀態
的進程數和 不可中斷休眠狀態
的進程數的和。注意:不可中斷休眠狀態的進程一般是在等待 I/O 完成的進程。
系統負載 = 可運行狀態進程數 + 不可中斷休眠狀態進程數
知道了什麼是 系統負載
,那麼 系統平均負載
就容易理解了。比如每 5 秒統計一次系統負載,1 分鐘內會統計 12 次。如下所示:
第5秒 -> 系統負載
第10秒 -> 系統負載
第15秒 -> 系統負載
...
第60秒 -> 系統負載
然後把每次統計到的系統負載加起來,再除以統計次數,即可得出 系統平均負載
。如下圖所示:
但這種計算方式有些缺陷,就是預測系統負載的準確性不夠高,因爲越老的數據越不能反映現在的情況。打個比方,要預測某條公路今天的車流量,使用昨天的數據作爲預測依據,會比使用一個月之前的數據作爲依據要準確得多。
所以,時間越近的數據,對未來的預測準確性越高。
Linux 內核使用一種名爲 指數平滑法
的算法來解決這個問題,指數平滑法的核心思想是對新老數據進行加權,越老的數據權重越低。
指數平滑法:是由 Robert G..Brown 提出的一種加權移動平均法,有興趣瞭解其數學原理的可以搜索相關資料,本文不作詳細介紹。
其計算公式如下(來源於 Linux 內核代碼 kernel/sched/core.c):
load1 = load0 * e + active * (1 - e)
解釋一下上面公式的意思:
-
load1:表示時間 t + 1 的系統負載。
-
load0:表示時間 t 的系統負載。
-
e:表示衰減係數。
-
active:表示系統中的活躍進程數(可運行狀態進程數 + 不可中斷休眠狀態進程數)。
所以,我們就可以使用上面的公式來預測任何時間的系統平均負載了。比如,我們要預測時間點 n 的系統平均負載,那麼可以這樣來計算:
load1 = load0 * e + active * (1 - e)
load2 = load1 * e + active * (1 - e)
load3 = load2 * e + active * (1 - e)
...
loadn = loadn-1 * e + active * (1 - e)
現在就只剩下 衰減係數
該如何計算了。
從 Linux 內核的註釋可以瞭解到,計算 1 分鐘內系統平均負載的 衰減係數
的計算方式如下:
1 / exp(5sec / 1min)
其中:
-
5sec:表示統計的時間間隔,5 秒。
-
1min:表示統計的時長,1 分鐘。
-
exp:表示以自然常數 e 爲底的指數函數。
也就是說,要計算一分鐘的系統平均負載時,需要使用上面的 衰減係數
。對於 5 分鐘和 15 分鐘的 衰減係數
的計算方式分別爲:
1 / exp(5sec / 5min)
1 / exp(5sec / 15min)
Linux 內核已經把 1 分鐘、5 分鐘和 15 分鐘的 衰減係數
結果計算出來,並且定義在 include/linux/sched.h
文件中,如下所示:
#define EXP_1 1884 /* 1/exp(5sec/1min) as fixed-point */
#define EXP_5 2014 /* 1/exp(5sec/5min) */
#define EXP_15 2037 /* 1/exp(5sec/15min) */
通過上述公式計算出來的 衰減係數
是個浮點數,而在內核中是不能進行浮點數運行的。解決方法是先對 衰減係數
進行擴大,然後在展示時最縮小。所以,上面的 衰減係數
數值是經過擴大 2048 倍後的結果。
Linux 平均負載計算實現
萬事俱備,只欠東風。上面我們已經把所有的知識點介紹了,現在來分析一下 Linux 內核代碼是怎樣實現的。
1. 數據存儲
在 Linux 內核中,使用了 avenrun
數組來存儲 1 分鐘、5 分鐘和 15 分鐘的系統平均負載,如下代碼所示:
unsigned long avenrun[3];
如元素 avenrun[0]
用於存儲 1 分鐘內的系統平均負載,而元素 avenrun[1]
用於存儲 5 分鐘的系統平均負載,如此類推。
2. 統計過程
由於統計需要定時進行,所以內核把統計過程放置到 時鐘中斷
中進行。當 時鐘中斷
觸發時,將會調用 do_timer()
函數,而 do_timer()
函數將會調用 calc_global_load()
來統計系統平均負載。
我們來看看 calc_global_load()
函數的實現:
void calc_global_load(unsigned long ticks)
{
long active, delta;
// 1. 如果還沒到統計的時間間隔,那麼將不進行統計(5秒統計一次)
if (time_before(jiffies, calc_load_update + 10))
return;
// 2. 獲取活躍進程數
delta = calc_load_fold_idle();
if (delta)
atomic_long_add(delta, &calc_load_tasks);
active = atomic_long_read(&calc_load_tasks);
active = active > 0 ? active * FIXED_1 : 0;
// 3. 統計各個時間段系統平均負載
avenrun[0] = calc_load(avenrun[0], EXP_1, active);
avenrun[1] = calc_load(avenrun[1], EXP_5, active);
avenrun[2] = calc_load(avenrun[2], EXP_15, active);
// 4. 更新下次統計的時間(增加5秒)
calc_load_update += LOAD_FREQ;
...
}
calc_global_load()
函數主要完成 4 件事情:
-
判斷當前時間是否需要進行統計,如果還沒到統計的時間間隔,那麼將不進行統計(5 秒統計一次)。
-
獲取活躍進程數(可運行狀態進程數 + 不可中斷休眠狀態進程數)。
-
統計各個時間段系統平均負載(1 分鐘、5 分鐘和 15 分鐘)。
-
更新下次統計的時間(增加 5 秒)。
從上面的分析可知,calc_global_load()
函數將會調用 calc_load()
來計算系統平均負載。其代碼如下:
/*
* a1 = a0 * e + a * (1 - e)
*/
static unsigned long
calc_load(unsigned long load, unsigned long exp, unsigned long active)
{
load *= exp;
load += active * (FIXED_1 - exp);
load += 1UL << (FSHIFT - 1);
return load >> FSHIFT;
}
calc_load()
函數的各個參數意義如下:
-
load:
t-1
時間點的系統負載。 -
exp:衰減係數。
-
active:活躍進程數。
可以看出,calc_load()
函數的實現就是按照 指數平滑法
來計算的。
參考文獻:
《Understanding Linux CPU Load》
https://scoutapm.com/blog/unders> tanding-load-averages
《Linux 系統平均負載是如何計算的》
https://blog.csdn.net/rikeyone/article/details/108309665
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/aVCQvxAA8itQ3PheEUFBcg