本文討論的 swap基于Linux4.4內核代碼 ,Linux記憶體管理是一套非常復雜的系統,而swap只是其中一個很小的處理邏輯,
希望本文能讓讀者了解Linux對swap的使用大概是什么樣子,閱讀完本文,應該可以幫你解決以下問題:
- swap到底是干嘛的?
- swappiness到底是用來調節什么的?
- kswapd什么時候會進行swap操作?
- 什么是記憶體水位標記?
- 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的關系,我們需要思考以下幾個問題:
- 為什么要進行記憶體回收?
- 哪些記憶體可能會被回收呢?
- 回收的程序中什么時候會進行交換呢?
- 具體怎么交換?
下面我們就從這些問題出發,一個一個進行分析,
為什么要進行記憶體回收?
內核之所以要進行記憶體回收,主要原因有兩個:
-
內核需要為任何時刻突發到來的記憶體申請提供足夠的記憶體,所以一般情況下保證有足夠的free空間對于內核來說是必要的,
另外,Linux內核使用cache的策略雖然是不用白不用,內核會使用記憶體中的page cache對部分檔案進行快取,以便提升檔案的讀寫效率,
所以內核有必要設計一個周期性回收記憶體的機制,以便cache的使用和其他相關記憶體的使用不至于讓系統的剩余記憶體長期處于很少的狀態, -
當真的有大于空閑記憶體的申請到來的時候,會觸發強制記憶體回收,
所以,內核在應對這兩類回收的需求下,分別實作了兩種不同的機制:
所以,內核在應對這兩類回收的需求下,分別實作了兩種不同的機制:
- 一個是使用 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演算法進行處理并進行標記,我們這里不詳細解釋這個程序,
整個掃描的程序分幾個回圈:
- 首先掃描每個zone上的cgroup組;
- 然后再以cgroup的記憶體為單元進行page鏈表的掃描;
- 內核會先掃描anon的active鏈表,將不頻繁的放進inactive鏈表中,然后掃描inactive鏈表,將里面活躍的移回active中;
- 進行swap的時候,先對inactive的頁進行換出;
- 如果是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的記憶體寫回并清空,剩余部分記憶體出來,我們將怎么設計?
我想應該主要考慮這樣幾個問題:
- 如果回收記憶體可以有兩種途徑(匿名頁交換和file快取清空),那么我應該考慮在本次回收的時候,什么情況下多進行file寫回,什么情況下應該多進行swap交換,說白了就是平衡兩種回收手段的使用,以達到最優,
- 如果符合交換條件的記憶體較長,是不是可以不用全部交換出去?比如可以交換的記憶體有100M,但是目前只需要50M記憶體,實際只要交換50M就可以了,不用把能交換的都交換出去,
分析代碼會發現,Linux內核對這部分邏輯的實作代碼在 get_scan_count() 這個方法中,這個方法被 shrink_lruvec() 呼叫,
get_sacn_count()就是處理上述邏輯的,swappiness是它所需要的一個引數,這個引數實際上是指導內核在清空記憶體的時候,是更傾向于清空file-backed記憶體還是更傾向于進行匿名頁的交換的,
當然,這只是個傾向性,是指在兩個都夠用的情況下,更愿意用哪個,如果不夠用了,那么該交換還是要交換,
簡單看一下get_sacn_count()函式的處理部分代碼,其中關于swappiness的第一個處理是:
這里注釋的很清楚:
-
如果swappiness設定為100,那么匿名頁和檔案將用同樣的優先級進行回收,
很明顯,使用清空檔案的方式將有利于減輕記憶體回收時可能造成的IO壓力,
因為如果file-backed中的資料不是臟資料的話,那么可以不用寫回,這樣就沒有IO發生,而一旦進行交換,就一定會造成IO,
所以系統默認將swappiness的值設定為60,這樣回收記憶體時,對file-backed的檔案cache記憶體的清空比例會更大,內核將會更傾向于進行快取清空而不是交換, -
這里的swappiness值如果是60,那么是不是說內核回收的時候,會按照60:140的比例去做相應的swap和清空file-backed的空間呢?并不是,
在做這個比例計算的時候,內核還要參考當前記憶體使用的其他資訊,對這里具體是怎么處理感興趣的人,可以自己詳細看get_sacn_count()的實作,本文就不多解釋了,
我們在此要明確的概念是: swappiness的值是用來控制記憶體回收時,回收的匿名頁更多一些還是回收的file cache更多一些 , -
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表示在本地回收,
這個檔案可以設定的值及其含義如下:
- echo 0 > /proc/sys/vm/zone_reclaim_mode:意味著關閉zone_reclaim模式,可以從其他zone或NUMA節點回收記憶體,
- echo 1 > /proc/sys/vm/zone_reclaim_mode:表示打開zone_reclaim模式,這樣記憶體回收只會發生在本地節點內,
- echo 2 > /proc/sys/vm/zone_reclaim_mode:在本地回收記憶體時,可以將cache中的臟資料寫回硬碟,以回收記憶體,
- 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:
- 一個記憶體剩余還比較大的系統中,是否有可能使用swap?
A: 有可能,如果運行中的某個階段出發了這個條件”zonefile+zonefree<=high_wmark_pages(zone) “,就可能會swap,
- swappiness設定為0就相當于關閉swap么?
A: 不是的,關閉swap要使用swapoff命令,swappiness只是在記憶體發生回收操作的時候用來平衡cache回收和swap交換的一個引數,調整為0意味著,盡量通過清快取來回收記憶體,
- A: swappiness設定為100代表系統會盡量少用剩余記憶體而多使用swap么?
不是的,這個值設定為100表示記憶體發生回收時,從cache回收記憶體和swap交換的優先級一樣,就是說,如果目前需求100M記憶體,那么較大機率會從cache中清除50M記憶體,再將匿名頁換出50M,把回收到的記憶體給應用程式使用,但是這還要看cache中是否能有空間,以及swap是否可以交換50m,內核只是試圖對它們平衡一些而已,
- kswapd行程什么時候開始記憶體回收?
A: kswapd根據記憶體水位標記決定是否開始回收記憶體,如果標記達到low就開始回收,回收到剩余記憶體達到high標記為止,
- 如何查看當前系統的記憶體水位標記?
A: cat /proc/zoneinfo,
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/40766.html
標籤:嵌入式
上一篇:makefile學習之函式
