1. 程式人生 > 實用技巧 >Linux 下swap工作原理

Linux 下swap工作原理

系統記憶體不足是處理機制

記憶體不足時這其實會導致兩種可能結果,記憶體回收和 OOM 殺死程序

先來看後一個可能結果,記憶體資源緊張導致的 OOM(Out Of Memory),相對容易理解,指的是系統殺死佔用大量記憶體的程序,釋放這些記憶體,再分配給其他更需要的程序。

記憶體回收機制

大部分檔案頁,都可以直接回收,以後有需要時,再從磁碟重新讀取就可以了。而那些被應用程式修改過,並且暫時還沒寫入磁碟的資料(也就是髒頁),就得先寫入磁碟,然後才能進行記憶體釋放。這些髒頁,一般可以通過兩種方式寫入磁碟。

可以在應用程式中,通過系統呼叫 fsync ,把髒頁同步到磁碟中;

也可以交給系統,由核心執行緒 pdflush 負責這些髒頁的重新整理。

除了快取和緩衝區,通過記憶體對映獲取的檔案對映頁,也是一種常見的檔案頁。它也可以被釋放掉,下次再訪問的時候,從檔案重新讀取。

如,應用程式動態分配的堆記憶體,也就是記憶體管理中說到的匿名頁(Anonymous Page);本不應該被回收,因為它們很可能還要再次被訪問啊,當然不能直接回收了;但是,如果這些記憶體在分配後很少被訪問,似乎也是一種資源浪費。

其實,這正是 Linux 的 Swap 機制。Swap 把這些不常訪問的記憶體先寫到磁碟中,然後釋放這些記憶體,給其他更需要的程序使用。再次訪問這些記憶體時,重新從磁碟讀入記憶體就可以了。

Swap 原理

Swap 說白了就是把一塊磁碟空間或者一個本地檔案,當成記憶體來使用。它包括換出和換入兩個過程。

換出,就是把程序暫時不用的記憶體資料儲存到磁碟中,並釋放這些資料佔用的記憶體。

換入,則是在程序再次訪問這些記憶體的時候,把它們從磁碟讀到記憶體中來。

Swap 其實是把系統的可用記憶體變大了。這樣,即使伺服器的記憶體不足,也可以執行大記憶體的應用程式。

當然,現在的記憶體便宜多了,伺服器一般也會配置很大的記憶體,也並不是說swap機制就用不到了,事實上,記憶體再大,對應用程式來說,也有不夠用的時候。一個很典型的場景就是,即使記憶體不足時,有些應用程式也並不想被 OOM 殺死,而是希望能緩一段時間,等待人工介入,或者等系統自動釋放其他程序的記憶體,再分配給它。

除此之外,常見的膝上型電腦的休眠和快速開機的功能,也基於 Swap 。休眠時,把系統的記憶體存入磁碟,這樣等到再次開機時,只要從磁碟中載入記憶體就可以。這樣就省去了很多應用程式的初始化過程,加快了開機速度。

清理記憶體的時間

一個最容易想到的場景就是,有新的大塊記憶體分配請求,但是剩餘記憶體不足。這個時候系統就需要回收一部分記憶體(比如前面提到的快取),進而儘可能地滿足新記憶體請求。這個過程通常被稱為直接記憶體回收。

除了直接記憶體回收,還有一個專門的核心執行緒用來定期回收記憶體,也就是 kswapd0。為了衡量記憶體的使用情況,kswapd0 定義了三個記憶體閾值(watermark,也稱為水位),分別是頁最小閾值(pages_min)、頁低閾值(pages_low)和頁高閾值(pages_high)。剩餘記憶體,則使用 pages_free 表示

kswapd0 定期掃描記憶體的使用情況,並根據剩餘記憶體落在這三個閾值的空間位置,進行記憶體的回收操作。

剩餘記憶體小於頁最小閾值,說明程序可用記憶體都耗盡了,只有核心才可以分配記憶體。

剩餘記憶體落在頁最小閾值和頁低閾值中間,說明記憶體壓力比較大,剩餘記憶體不多了。這時 kswapd0 會執行記憶體回收,直到剩餘記憶體大於高閾值為止。

剩餘記憶體落在頁低閾值和頁高閾值中間,說明記憶體有一定壓力,但還可以滿足新記憶體請求。

剩餘記憶體大於頁高閾值,說明剩餘記憶體比較多,沒有記憶體壓力。

可以看到,一旦剩餘記憶體小於頁低閾值,就會觸發記憶體的回收。這個頁低閾值,其實可以通過核心選項 /proc/sys/vm/min_free_kbytes 來間接設定。min_free_kbytes 設定了頁最小閾值,而其他兩個閾值,都是根據頁最小閾值計算生成的,計算方法如下

pages_low = pages_min*5/4
pages_high = pages_min*3/2

NUMA 與 Swap的關係

很多情況下,明明發現了 Swap 升高,可是在分析系統的記憶體使用時,卻很可能發現,系統剩餘記憶體還多著呢。

這正是處理器的 NUMA (Non-Uniform Memory Access)架構導致的。

在 NUMA 架構下,多個處理器被劃分到不同 Node 上,且每個 Node 都擁有自己的本地記憶體空間

而同一個 Node 內部的記憶體空間,實際上又可以進一步分為不同的記憶體域(Zone),比如直接記憶體訪問區(DMA)、普通記憶體區(NORMAL)、偽記憶體區(MOVABLE)等,如下圖所示:

先不用特別關注這些記憶體域的具體含義,只要會檢視閾值的配置,以及快取、匿名頁的實際使用情況就夠了。

既然 NUMA 架構下的每個 Node 都有自己的本地記憶體空間,那麼,在分析記憶體的使用時,也應該針對每個 Node 單獨分析。

可以通過 numactl 命令,來檢視處理器在 Node 的分佈情況,以及每個 Node 的記憶體使用情況。比如,下面就是一個 numactl 輸出的示例:

[root@localhost ~]# numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1
node 0 size: 7966 MB
node 0 free: 7427 MB
node distances:
node   0 
  0:  10 

這個介面顯示,系統中只有一個 Node,也就是 Node 0 ,而且編號為 0 和 1 的兩個 CPU, 都位於 Node 0 上。另外,Node 0 的記憶體大小為 7966 MB,剩餘記憶體為 7427 MB。

提到的三個記憶體閾值(頁最小閾值、頁低閾值和頁高閾值),都可以通過記憶體域在 proc 檔案系統中的介面 /proc/zoneinfo 來檢視。

$ cat /proc/zoneinfo
...
Node 0, zone   Normal
 pages free     227894
       min      14896
       low      18620
       high     22344
...
     nr_free_pages 227894
     nr_zone_inactive_anon 11082
     nr_zone_active_anon 14024
     nr_zone_inactive_file 539024
     nr_zone_active_file 923986
...

這個輸出中有大量指標,解釋一下比較重要的幾個。

pages 處的 min、low、high,就是上面提到的三個記憶體閾值,而 free 是剩餘記憶體頁數,它跟後面的 nr_free_pages 相同。

nr_zone_active_anon 和 nr_zone_inactive_anon,分別是活躍和非活躍的匿名頁數。

nr_zone_active_file 和 nr_zone_inactive_file,分別是活躍和非活躍的檔案頁數。

從這個輸出結果可以發現,剩餘記憶體遠大於頁高閾值,所以此時的 kswapd0 不會回收記憶體。

當然,某個 Node 記憶體不足時,系統可以從其他 Node 尋找空閒記憶體,也可以從本地記憶體中回收記憶體。具體選哪種模式,你可以通過 /proc/sys/vm/zone_reclaim_mode 來調整。它支援以下幾個選項:

預設的 0 ,也就是剛剛提到的模式,表示既可以從其他 Node 尋找空閒記憶體,也可以從本地回收記憶體。

1、2、4 都表示只回收本地記憶體,2 表示可以回寫髒資料回收記憶體,4 表示可以用 Swap 方式回收記憶體。

swappines回收記憶體的方式

就可以理解記憶體回收的機制了。這些回收的記憶體既包括了檔案頁,又包括了匿名頁。

對檔案頁的回收,當然就是直接回收快取,或者把髒頁寫回磁碟後再回收。

而對匿名頁的回收,其實就是通過 Swap 機制,把它們寫入磁碟後再釋放記憶體。

Linux 提供了一個 /proc/sys/vm/swappiness 選項,用來調整使用 Swap 的積極程度。

swappiness 的範圍是 0-100,數值越大,越積極使用 Swap,也就是更傾向於回收匿名頁;數值越小,越消極使用 Swap,也就是更傾向於回收檔案頁。雖然 swappiness 的範圍是 0-100,不過要注意,這並不是記憶體的百分比,而是調整 Swap 積極程度的權重,即使你把它設定成 0,當剩餘記憶體 + 檔案頁小於頁高閾值時,還是會發生 Swap。