­

linux 的swap、swappiness及kswapd原理【轉】

  • 2019 年 10 月 7 日
  • 筆記

本文討論的 swap基於Linux4.4內核代碼 。Linux內存管理是一套非常複雜的系統,而swap只是其中一個很小的處理邏輯。

希望本文能讓讀者了解Linux對swap的使用大概是什麼樣子。閱讀完本文,應該可以幫你解決以下問題:

  1. swap到底是幹嘛的?
  2. swappiness到底是用來調節什麼的?
  3. kswapd什麼時候會進行swap操作?
  4. 什麼是內存水位標記?
  5. swap分區的優先級(priority)有啥用?

1、什麼是SWAP,到底是幹嘛的?

我們一般所說的swap,指的是一個交換分區或文件。在Linux上可以使用swapon -s命令查看當前系統上正在使用的交換空間有哪些,以及相關信息:

[zorro@zorrozou-pc0 linux-4.4]$ swapon -s  Filename            Type        Size        Used    Priority  /dev/dm-4           partition    33554428    0        -1

從功能上講,交換分區主要是在內存不夠用的時候,將部分內存上的數據交換到swap空間上,以便讓系統不會因內存不夠用而導致oom或者更致命的情況出現。

所以,當內存使用存在壓力,開始觸發內存回收的行為時,就可能會使用swap空間。

內核對swap的使用實際上是跟內存回收行為緊密結合的。那麼關於內存回收和swap的關係,我們需要思考以下幾個問題:

  1. 為什麼要進行內存回收?
  2. 哪些內存可能會被回收呢?
  3. 回收的過程中什麼時候會進行交換呢?
  4. 具體怎麼交換?

下面我們就從這些問題出發,一個一個進行分析。

為什麼要進行內存回收?

內核之所以要進行內存回收,主要原因有兩個:

  1. 內核需要為任何時刻突發到來的內存申請提供足夠的內存。所以一般情況下保證有足夠的free空間對於內核來說是必要的。 另外,Linux內核使用cache的策略雖然是不用白不用,內核會使用內存中的page cache對部分文件進行緩存,以便提升文件的讀寫效率。 所以內核有必要設計一個周期性回收內存的機制,以便cache的使用和其他相關內存的使用不至於讓系統的剩餘內存長期處於很少的狀態。
  2. 當真的有大於空閑內存的申請到來的時候,會觸發強制內存回收。 所以,內核在應對這兩類回收的需求下,分別實現了兩種不同的機制:

所以,內核在應對這兩類回收的需求下,分別實現了兩種不同的機制:

  • 一個是使用 kswapd進程對內存進行周期檢查 ,以保證平常狀態下剩餘內存儘可能夠用。
  • 另一個是 直接內存回收(directpagereclaim) ,就是當內存分配時沒有空閑內存可以滿足要求時,觸發直接內存回收。

這兩種內存回收的觸發路徑不同:

  • 一個是使用 kswapd進程對內存進行周期檢查 ,以保證平常狀態下剩餘內存儘可能夠用。
  • 另一個是 直接內存回收(directpagereclaim) ,就是當內存分配時沒有空閑內存可以滿足要求時,觸發直接內存回收。

這兩種內存回收的觸發路徑不同:

  • 一個是由內核進程kswapd直接調用內存回收的邏輯進行內存回收; 參見mm/vmscan.c中的kswapd()主邏輯
  • 另一個是內存申請的時候進入slow path的內存申請邏輯進行回收。 參見內核代碼中的mm/page_alloc.c中的__alloc_pages_slowpath方法

這兩個方法中實際進行內存回收的過程殊途同歸,最終都是 調用shrink_zone() 方法進行針對每個zone的內存頁縮減。

這個方法中會再調用shrink_lruvec()這個方法對每個組織頁的鏈表進程檢查。找到這個線索之後,我們就可以清晰的看到內存回收操作究竟針對的page有哪些了。

這些鏈表主要定義在mm/vmscan.c一個enum中:

根據這個enum可以看到,內存回收主要需要進行掃描的鏈表有如下4個:

  • anon的inactive
  • anon的active
  • file的inactive
  • file的active

就是說,內存回收操作主要針對的就是內存中的文件頁(file cache)和匿名頁。

關於活躍(active)還是不活躍(inactive)的判斷內核會使用lru算法進行處理並進行標記,我們這裡不詳細解釋這個過程。

整個掃描的過程分幾個循環:

  1. 首先掃描每個zone上的cgroup組;
  2. 然後再以cgroup的內存為單元進行page鏈表的掃描;
  3. 內核會先掃描anon的active鏈表,將不頻繁的放進inactive鏈表中,然後掃描inactive鏈表,將裏面活躍的移回active中;
  4. 進行swap的時候,先對inactive的頁進行換出;
  5. 如果是file的文件映射page頁,則判斷其是否為臟數據,如果是臟數據就寫回,不是臟數據可以直接釋放。

這樣看來, 內存回收這個行為會對兩種內存的使用進行回收:

  • 一種是anon的匿名頁內存,主要回收手段是swap;
  • 另一種是file-backed的文件映射頁,主要的釋放手段是寫回和清空。

因為針對filebased的內存,沒必要進行交換,其數據原本就在硬盤上,回收這部分內存只要在有臟數據時寫回,並清空內存就可以了,以後有需要再從對應的文件讀回來。

內存對匿名頁和文件緩存一共用了 四條鏈表 進行組織,回收過程主要是針對這四條鏈表進行掃描和操作。

2、swappiness到底是用來調節什麼的?

很多人應該都知道 /proc/sys/vm/swappiness 這個文件,是個可以用來調整跟swap相關的參數。這個文件的默認值是60,可以的取值範圍是0-100。

這很容易給大家一個暗示:我是個百分比哦!

那麼這個文件具體到底代表什麼意思呢?我們先來看一下說明:

======    swappiness    This control is used to define how aggressive the kernel will swap memory pages. Higher values will increase agressiveness, lower values decrease the amount of swap.    A value of 0 instructs the kernel not to initiate swap until the amount of free and file-backed pages is less than the high water mark in a zone.    The default value is 60.    ======

這個文件的值用來定義內核使用swap的積極程度:

  • 值越高,內核就會越積極的使用swap;
  • 值越低,就會降低對swap的使用積極性。
  • 如果這個值為0,那麼內存在free和file-backed使用的頁面總量小於高水位標記(high water mark)之前,不會發生交換。

在這裡我們可以理解file-backed這個詞的含義了,實際上就是上文所說的文件映射頁的大小。

那麼這個swappiness到底起到了什麼作用呢?

我們換個思路考慮這個事情。假設讓我們設計一個內存回收機制,要去考慮將一部分內存寫到swap分區上,將一部分file-backed的內存寫回並清空,剩餘部分內存出來,我們將怎麼設計?

我想應該主要考慮這樣幾個問題:

  1. 如果回收內存可以有兩種途徑(匿名頁交換和file緩存清空),那麼我應該考慮在本次回收的時候,什麼情況下多進行file寫回,什麼情況下應該多進行swap交換。說白了就是平衡兩種回收手段的使用,以達到最優。
  2. 如果符合交換條件的內存較長,是不是可以不用全部交換出去?比如可以交換的內存有100M,但是目前只需要50M內存,實際只要交換50M就可以了,不用把能交換的都交換出去。

分析代碼會發現,Linux內核對這部分邏輯的實現代碼在 get_scan_count() 這個方法中,這個方法被 shrink_lruvec() 調用。

get_sacn_count()就是處理上述邏輯的,swappiness是它所需要的一個參數,這個參數實際上是指導內核在清空內存的時候,是更傾向於清空file-backed內存還是更傾向於進行匿名頁的交換的。

當然,這只是個傾向性,是指在兩個都夠用的情況下,更願意用哪個,如果不夠用了,那麼該交換還是要交換。

簡單看一下get_sacn_count()函數的處理部分代碼,其中關於swappiness的第一個處理是:

這裡注釋的很清楚:

  1. 如果swappiness設置為100,那麼匿名頁和文件將用同樣的優先級進行回收。 很明顯,使用清空文件的方式將有利於減輕內存回收時可能造成的IO壓力。 因為如果file-backed中的數據不是臟數據的話,那麼可以不用寫回,這樣就沒有IO發生,而一旦進行交換,就一定會造成IO。 所以系統默認將swappiness的值設置為60,這樣回收內存時,對file-backed的文件cache內存的清空比例會更大,內核將會更傾向於進行緩存清空而不是交換。
  2. 這裡的swappiness值如果是60,那麼是不是說內核回收的時候,會按照60:140的比例去做相應的swap和清空file-backed的空間呢?並不是。 在做這個比例計算的時候,內核還要參考當前內存使用的其他信息。對這裡具體是怎麼處理感興趣的人,可以自己詳細看get_sacn_count()的實現,本文就不多解釋了。 我們在此要明確的概念是: swappiness的值是用來控制內存回收時,回收的匿名頁更多一些還是回收的file cache更多一些 。
  3. swappiness設置為0的話,是不是內核就根本不會進行swap了呢?這個答案也是否定的。 首先是內存真的不夠用的時候,該swap的話還是要swap。 其次在內核中還有一個邏輯會導致直接使用swap, 內核代碼 是這樣處理的:

3、kswapd什麼時候會進行swap操作?

我們回到kswapd周期檢查和直接內存回收的兩種內存回收機制。

直接內存回收比較好理解,當申請的內存大於剩餘內存的時候,就會觸發直接回收。

那麼kswapd進程在周期檢查的時候觸發回收的條件是什麼呢?

還是從設計角度來看,kswapd進程要周期對內存進行檢測,達到一定閾值的時候開始進行內存回收。

這個所謂的閾值可以理解為內存目前的使用壓力,就是說,雖然我們還有剩餘內存,但是當剩餘內存比較小的時候,就是內存壓力較大的時候,就應該開始試圖回收些內存了,這樣才能保證系統儘可能的有足夠的內存給突發的內存申請所使用。

4、什麼是內存水位標記?(watermark)

那麼如何描述內存使用的壓力呢?

Linux內核使用水位標記(watermark)的概念來描述這個壓力情況。

  • Linux為內存的使用設置了三種內存水位標記:high、low、min。他們 所標記的含義分別為:
  • 剩餘內存在high以上表示內存剩餘較多,目前內存使用壓力不大;
  • high-low的範圍表示目前剩餘內存存在一定壓力;
  • low-min表示內存開始有較大使用壓力,剩餘內存不多了;
  • min是最小的水位標記,當剩餘內存達到這個狀態時,就說明內存面臨很大壓力。
  • 小於min這部分內存,內核是保留給特定情況下使用的,一般不會分配。

內存回收行為就是基於剩餘內存的水位標記進行決策的:

當系統剩餘內存低於watermark[low]的時候,內核的kswapd開始起作用,進行內存回收。直到剩餘內存達到watermark[high]的時候停止。

如果內存消耗導致剩餘內存達到了或超過了watermark[min]時,就會觸發直接回收(direct reclaim)。

明白了水位標記的概念之後,zonefile + zonefree <= high_wmark_pages(zone)這個公式就能理解了。

這裡的zonefile相當於內存中文件映射的總量,zonefree相當於剩餘內存的總量。

內核一般認為,如果zonefile還有的話,就可以盡量通過清空文件緩存獲得部分內存,而不必只使用swap方式對anon的內存進行交換。

整個判斷的概念是說,在全局回收的狀態下(有global_reclaim(sc)標記),如果當前的文件映射內存總量+剩餘內存總量的值評估小於等於watermark[high]標記的時候,就可以進行直接swap了。

這樣是為了防止進入cache陷阱,具體描述可以見代碼注釋。

這個判斷對系統的影響是, swappiness設置為0時,有剩餘內存的情況下也可能發生交換。

那麼watermark相關值是如何計算的呢?

所有的內存watermark標記都是根據當前內存總大小和一個可調參數進行運算得來的,這個參數是: /proc/sys/vm/min_free_kbytes

  • 首先這個參數本身決定了系統中每個zone的watermark[min]的值大小。
  • 然後內核根據min的大小並參考每個zone的內存大小分別算出每個zone的low水位和high水位值。

想了解具體邏輯可以參見源代碼目錄下的該文件:

mm/page_alloc.c

在系統中可以從/proc/zoneinfo文件中查看當前系統的相關的信息和使用情況。

我們會發現以上內存管理的相關邏輯都是以zone為單位的,這裡zone的含義是指內存的分區管理。

Linux將內存分成多個區,主要有:

  • 直接訪問區(DMA)
  • 一般區(Normal)
  • 高端內存區(HighMemory)

內核對內存不同區域的訪問因為硬件結構因素會有尋址和效率上的差別。如果在NUMA架構上,不同CPU所管理的內存也是不同的zone。

相關參數設置

  • zone_reclaim_mode:

zone_reclaim_mode模式是在2.6版本後期開始加入內核的一種模式,可以用來管理當一個內存區域(zone)內部的內存耗盡時,是從其內部進行內存回收還是可以從其他zone進行回收的選項,我們可以通過 /proc/sys/vm/zone_reclaim_mode 文件對這個參數進行調整。

在申請內存時(內核的get_page_from_freelist()方法中),內核在當前zone內沒有足夠內存可用的情況下,會根據zone_reclaim_mode的設置來決策是從下一個zone找空閑內存還是在zone內部進行回收。這個值為0時表示可以從下一個zone找可用內存,非0表示在本地回收。

這個文件可以設置的值及其含義如下:

  1. echo 0 > /proc/sys/vm/zone_reclaim_mode:意味着關閉zone_reclaim模式,可以從其他zone或NUMA節點回收內存。
  2. echo 1 > /proc/sys/vm/zone_reclaim_mode:表示打開zone_reclaim模式,這樣內存回收只會發生在本地節點內。
  3. echo 2 > /proc/sys/vm/zone_reclaim_mode:在本地回收內存時,可以將cache中的臟數據寫回硬盤,以回收內存。
  4. echo 4 > /proc/sys/vm/zone_reclaim_mode:可以用swap方式回收內存。

不同的參數配置會在NUMA環境中對其他內存節點的內存使用產生不同的影響,大家可以根據自己的情況進行設置以優化你的應用。

默認情況下,zone_reclaim模式是關閉的。這在很多應用場景下可以提高效率,比如文件服務器,或者依賴內存中cache比較多的應用場景。

這樣的場景對內存cache速度的依賴要高於進程進程本身對內存速度的依賴,所以我們寧可讓內存從其他zone申請使用,也不願意清本地cache。

如果確定應用場景是內存需求大於緩存,而且盡量要避免內存訪問跨越NUMA節點造成的性能下降的話,則可以打開zone_reclaim模式。

此時頁分配器會優先回收容易回收的可回收內存(主要是當前不用的page cache頁),然後再回收其他內存。

打開本地回收模式的寫回可能會引發其他內存節點上的大量的臟數據寫回處理。如果一個內存zone已經滿了,那麼臟數據的寫回也會導致進程處理速度收到影響,產生處理瓶頸。

這會降低某個內存節點相關的進程的性能,因為進程不再能夠使用其他節點上的內存。但是會增加節點之間的隔離性,其他節點的相關進程運行將不會因為另一個節點上的內存回收導致性能下降。

除非針對本地節點的內存限制策略或者cpuset配置有變化,對swap的限制會有效約束交換隻發生在本地內存節點所管理的區域上。

  • min_unmapped_ratio:

這個參數只在NUMA架構的內核上生效。這個值表示NUMA上每個內存區域的pages總數的百分比。

在zone_reclaim_mode模式下,只有當相關區域的內存使用達到這個百分比,才會發生區域內存回收。

在zone_reclaim_mode設置為4的時候,內核會比較所有的file-backed和匿名映射頁,包括swapcache佔用的頁以及tmpfs文件的總內存使用是否超過這個百分比。

其他設置的情況下,只比較基於一般文件的未映射頁,不考慮其他相關頁。

  • page-cluster:

page-cluster是用來控制從swap空間換入數據的時候,一次連續讀取的頁數,這相當於對交換空間的預讀。這裡的連續是指在swap空間上的連續,而不是在內存地址上的連續。

因為swap空間一般是在硬盤上,對硬盤設備的連續讀取將減少磁頭的尋址,提高讀取效率。

這個文件中設置的值是2的指數。就是說,如果設置為0,預讀的swap頁數是2的0次方,等於1頁。如果設置為3,就是2的3次方,等於8頁。

同時,設置為0也意味着關閉預讀功能。文件默認值為3。我們可以根據我們的系統負載狀態來設置預讀的頁數大小。

swap的相關操縱命令

可以使用mkswap將一個分區或者文件創建成swap空間。swapon可以查看當前的swap空間和啟用一個swap分區或者文件。swapoff可以關閉swap空間。

我們使用一個文件的例子來演示一下整個操作過程

製作swap文件:

啟用swap文件:

關閉swap空間:

5、swap分區的優先級(priority)有啥用?

在使用多個swap分區或者文件的時候,還有一個優先級的概念(Priority)。

在swapon的時候,我們可以使用-p參數指定相關swap空間的優先級, 值越大優先級越高 ,可以指定的數字範圍是-1到32767。

內核在使用swap空間的時候總是先使用優先級高的空間,後使用優先級低的。

當然如果把多個swap空間的優先級設置成一樣的,那麼兩個swap空間將會以輪詢方式並行進行使用。

如果兩個swap放在兩個不同的硬盤上,相同的優先級可以起到類似RAID0的效果,增大swap的讀寫效率。

另外,編程時使用mlock()也可以將指定的內存標記為不會換出,具體幫助可以參考man 2 mlock。

最後 關於swap的使用建議,針對不同負載狀態的系統是不一樣的。有時我們希望swap大一些,可以在內存不夠用的時候不至於觸發oom-killer導致某些關鍵進程被殺掉,比如數據庫業務。

也有時候我們希望不要swap,因為當大量進程爆發增長導致內存爆掉之後,會因為swap導致IO跑死,整個系統都卡住,無法登錄,無法處理。

這時候我們就希望不要swap,即使出現oom-killer也造成不了太大影響,但是不能允許服務器因為IO卡死像多米諾骨牌一樣全部死機,而且無法登陸。跑cpu運算的無狀態的apache就是類似這樣的進程池架構的程序。

所以:

  • swap到底怎麼用?
  • 要還是不要?
  • 設置大還是小?
  • 相關參數應該如何配置?

是要根據我們自己的生產環境的情況而定的。

閱讀完本文後希望大家可以明白一些swap的深層次知識。

Q&A:

  1. 一個內存剩餘還比較大的系統中,是否有可能使用swap?

A: 有可能,如果運行中的某個階段出發了這個條件」zonefile+zonefree<=high_wmark_pages(zone) 「,就可能會swap。

  1. swappiness設置為0就相當於關閉swap么?

A: 不是的,關閉swap要使用swapoff命令。swappiness只是在內存發生回收操作的時候用來平衡cache回收和swap交換的一個參數,調整為0意味着,盡量通過清緩存來回收內存。

  1. A: swappiness設置為100代表系統會盡量少用剩餘內存而多使用swap么?

不是的,這個值設置為100表示內存發生回收時,從cache回收內存和swap交換的優先級一樣。就是說,如果目前需求100M內存,那麼較大機率會從cache中清除50M內存,再將匿名頁換出50M,把回收到的內存給應用程序使用。但是這還要看cache中是否能有空間,以及swap是否可以交換50m。內核只是試圖對它們平衡一些而已。

  1. kswapd進程什麼時候開始內存回收?

A: kswapd根據內存水位標記決定是否開始回收內存,如果標記達到low就開始回收,回收到剩餘內存達到high標記為止。

  1. 如何查看當前系統的內存水位標記?

A: cat /proc/zoneinfo。