一篇讀懂|Linux系統(tǒng)平均負載
我們經常會使用 top 命令來查看系統(tǒng)的性能情況,在 top 命令的第一行可以看到 load average 這個數據,如下圖所示:

load average 包含 3 列,分別表示 1 分鐘、5 分鐘和 15 分鐘的 系統(tǒng)平均負載。
對于系統(tǒng)平均負載這個數值,可能很多同學并不完全理解其意義,并不知道數值達到多少時才表示系統(tǒng)負載過高。本文將會以簡單的語言來介紹系統(tǒng)平均負載這個概念,并且會介紹 Linux 內核是怎么計算這個數值。
系統(tǒng)平均負載
《Understanding Linux CPU Load(鏈接在文章最后)》這篇文章已經非常通俗的解釋了什么是 系統(tǒng)平均負載,這里借用一下此文中的例子。
如果將 CPU 比作是橋梁,對于單核的 CPU 就好比是單車道的橋梁。每次橋梁只能讓一輛汽車通過,并且要以規(guī)定的速度通過。那么:
如果每個時刻都只有一輛汽車通過,那么所有汽車都不用排隊,此時橋梁的使用率最高。以平均負載 1.0 表示,如下圖所示:

如果每隔一段時間才有一輛汽車通過,那么表示橋梁部分時間處于空閑的情況。并且間隔的時間越長,表示橋梁空閑率越高。此時的平均負載小于 1.0,如下圖所示:

當有大量的汽車通過橋梁時,有些汽車需要等待其他車輛通過后才能繼續(xù)通行,這時表示橋梁超負荷工作。此時平均負載大于1.0,如下圖所示:

系統(tǒng)的平均負載與上面的例子一樣,在單核 CPU 的環(huán)境下:
當平均負載等于 1.0 時,表示 CPU 使用率最高。
當平均負載小于 1.0 時,表示 CPU 使用率處于空閑狀態(tài)。
當平均負載大于 1.0 時,表示 CPU 使用率已經超過負荷。
對于單核 CPU 來說,平均負載 1.0 表示使用率最高。但對于多核 CPU 來說,平均負載要乘以核心數。比如在 4 核 CPU 的系統(tǒng)中,當平均負載為 4.0 時,才表示 CPU 的使用率最高。
【文章福利】小編推薦自己的Linux內核技術交流群:【891587639】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ?


Linux 平均負載計算原理
在介紹系統(tǒng)平均負載的計算原理前,先要介紹一下什么是系統(tǒng)負載。在 Linux 系統(tǒng)中,系統(tǒng)負載表示 系統(tǒng)中當前正在運行的進程數量 ,其包括 可運行狀態(tài) 的進程數和 不可中斷休眠狀態(tài) 的進程數的和。注意:不可中斷休眠狀態(tài)的進程一般是在等待 I/O 完成的進程。
系統(tǒng)負載 = 可運行狀態(tài)進程數 + 不可中斷休眠狀態(tài)進程數
知道了什么是 系統(tǒng)負載,那么 系統(tǒng)平均負載 就容易理解了。比如每 5 秒統(tǒng)計一次系統(tǒng)負載,1 分鐘內會統(tǒng)計 12 次。如下所示:
第5秒 ?-> 系統(tǒng)負載?
第10秒 -> 系統(tǒng)負載?
第15秒 -> 系統(tǒng)負載 ...?
第60秒 -> 系統(tǒng)負載
然后把每次統(tǒng)計到的系統(tǒng)負載加起來,再除以統(tǒng)計次數,即可得出 系統(tǒng)平均負載。如下圖所示:

但這種計算方式有些缺陷,就是預測系統(tǒng)負載的準確性不夠高,因為越老的數據越不能反映現在的情況。打個比方,要預測某條公路今天的車流量,使用昨天的數據作為預測依據,會比使用一個月之前的數據作為依據要準確得多。
所以,時間越近的數據,對未來的預測準確性越高。
Linux 內核使用一種名為 指數平滑法 的算法來解決這個問題,指數平滑法的核心思想是對新老數據進行加權,越老的數據權重越低。
指數平滑法:是由 Robert G..Brown 提出的一種加權移動平均法,有興趣了解其數學原理的可以搜索相關資料,本文不作詳細介紹。
其計算公式如下(來源于 Linux 內核代碼 kernel/sched/core.c):
解釋一下上面公式的意思:
load1 :表示時間 t + 1 的系統(tǒng)負載。
load0 :表示時間 t 的系統(tǒng)負載。
e :表示衰減系數。
active :表示系統(tǒng)中的活躍進程數(可運行狀態(tài)進程數 + 不可中斷休眠狀態(tài)進程數)。
所以,我們就可以使用上面的公式來預測任何時間的系統(tǒng)平均負載了。比如,我們要預測時間點 n 的系統(tǒng)平均負載,那么可以這樣來計算:
現在就只剩下 衰減系數 該如何計算了。
從 Linux 內核的注釋可以了解到,計算 1 分鐘內系統(tǒng)平均負載的 衰減系數 的計算方式如下:
其中:
5sec :表示統(tǒng)計的時間間隔,5秒。
1min :表示統(tǒng)計的時長,1分鐘。
exp :表示以自然常數 e 為底的指數函數。
也就是說,要計算一分鐘的系統(tǒng)平均負載時,需要使用上面的 衰減系數。對于 5 分鐘和 15 分鐘的 衰減系數 的計算方式分別為:
Linux 內核已經把 1 分鐘、5 分鐘和 15 分鐘的 衰減系數 結果計算出來,并且定義在 include/linux/sched.h 文件中,如下所示:
通過上述公式計算出來的 衰減系數 是個浮點數,而在內核中是不能進行浮點數運行的。解決方法是先對 衰減系數 進行擴大,然后在展示時最縮小。所以,上面的 衰減系數 數值是經過擴大 2048 倍后的結果。
Linux 平均負載計算實現
萬事俱備,只欠東風 。上面我們已經把所有的知識點介紹了,現在來分析一下 Linux 內核代碼是怎樣實現的。
1. 數據存儲
在 Linux 內核中,使用了 avenrun 數組來存儲 1 分鐘、5 分鐘和 15 分鐘的系統(tǒng)平均負載,如下代碼所示:
如元素 avenrun[0] 用于存儲 1 分鐘內的系統(tǒng)平均負載,而元素 avenrun[1] 用于存儲 5 分鐘的系統(tǒng)平均負載,如此類推。
2. 統(tǒng)計過程
由于統(tǒng)計需要定時進行,所以內核把統(tǒng)計過程放置到 時鐘中斷 中進行。當 時鐘中斷 觸發(fā)時,將會調用 do_timer() 函數,而 do_timer() 函數將會調用 calc_global_load() 來統(tǒng)計系統(tǒng)平均負載。
我們來看看 calc_global_load() 函數的實現:
calc_global_load() 函數主要完成 4 件事情:
判斷當前時間是否需要進行統(tǒng)計,如果還沒到統(tǒng)計的時間間隔,那么將不進行統(tǒng)計(5秒統(tǒng)計一次)。
獲取活躍進程數(可運行狀態(tài)進程數 + 不可中斷休眠狀態(tài)進程數)。
統(tǒng)計各個時間段系統(tǒng)平均負載(1分鐘、5分鐘和15分鐘)。
更新下次統(tǒng)計的時間(增加5秒)。
從上面的分析可知,calc_global_load() 函數將會調用 calc_load() 來計算系統(tǒng)平均負載。其代碼如下:
calc_load() 函數的各個參數意義如下:
load :t-1 時間點的系統(tǒng)負載。
exp :衰減系數。
active :活躍進程數。
可以看出,calc_load() 函數的實現就是按照 指數平滑法 來計算的。
原文作者:Linux內核那些事
