什么是swap
swap主要是在內存不夠用的時候,將部分內存上的數據交換到swap空間上,以便讓系統不會因為內存不夠用而導致oom或者更致命的情況出現。當內存使用存在壓力的時候,開始觸發內存回收行為,就可能會使用swap空間。
內核將很少使用的部分內存換出到塊設備,相當於提供了更多的主內存,這種機制成為頁交換(swapping)或者換頁(paging),由內核實現,對應用程序是透明的。
如果一個很少使用的頁的后備存儲器是一個塊設備,那么就無需換出被修改的頁,而是可以直接與塊設備同步。騰出的頁幀可以重用,如果再次需要修改數據,可以從來源重新建立該頁。
如果頁的后備存儲器是一個文件,但不能在內存中修改,那么在當前不需要的情況下,可以直接丟棄該頁。
以上這三種技術,連同選擇很少使用頁的策略,統稱為頁面回收。
為什么要進行內存回收?
1.內核需要為突發到來的內存申請提供足夠的內存,所以一般情況下保證有足夠的free空間對於內核來說是必要的。
另外,Linux內核使用cache策略雖然是不用白不用,內核會使用內存中的page cache對部分文件進行緩存,一邊提升文件的讀寫效率。
所以內核有必要設計一個周期行回收內存機制,以便cache的使用和其他相關內存的使用不至於讓系統的剩余內存長期處於很少的狀態。
2.當真的有大於空閑內存的申請到來的是偶,會觸發強制內存回收。
所以內核在應對這兩類回收的需求下,分別實現兩種不同的機制:
一個是使用kswapd進程對內存進行周期檢查,以保證平常狀態下剩余內存盡可能夠用。
另一個是頁面回收(page reclaim),就是當內存分配時沒有空閑內存可以滿足要求時,觸發直接內存回收。
這兩種內存回收的觸發路徑不同:
kswapd作為一個后台守護進程,會定期檢查內存的使用情況,並檢測即將發生的內存不足。可使用該守護進程換出頁,並釋放那些最不常用的。
參見mm/vmscan.c中的kswapd()主邏輯。
kswapd -->balance_pgdat -->kswapd_shrink_zone -->shrink_zone -->shrink_lruvec |
如果內存檢測到在某個操作期間內存嚴重不足,將調用try_to_free_pages。該函數檢查當前內存域中所有頁,並釋放最不常用的那些。
參見內核代碼中的mm/page_alloc.c中的__alloc_pages_slowpath方法。
__alloc_pages_nodemask -->__alloc_pages_slowpath -->__alloc_pages_direct_reclaim 直接頁面回收然后分配內存 -->__perform_reclaim 執行同步直接頁面回收 -->try_to_free_pages -->do_try_to_free_pages -->shrink_zones -->shrink_zone -->shrink_lruvec |
這兩個方法實際進行內存回收的過程殊途同歸,最終頭時調用shrink_zone()方法針對每個zone的內存頁縮減。
這個方法中在調用shrink_lruvec()這個方法對每個組織頁的鏈表進行進程檢查,找到這個線索之后,我們就可以清晰地看到內存回收操作究竟這對的page有哪些了。
static void shrink_lruvec(struct lruvec *lruvec, int swappiness, for_each_evictable_lru(lru) { nr_reclaimed += shrink_list(lru, nr_to_scan, |
內存回收主要需要進行少秒的鏈表有如下4個:
anon的inactive,anon的active,file的inactive,file的active。
就是說內存回收主要針對的就是內存中的文件頁(file cache)和匿名頁。
關於活躍還是不活躍的判斷內核會使用lru算法進行並行處理並進行標記。
#define LRU_BASE 0 enum lru_list { |
整個掃描的過程分幾個循環:
1.首先掃描每個zone刪的cgroup組;
2.然后再以cgroup的內存為單元進行page鏈表的掃描;
3.內核會先掃描anon的active鏈表,將不頻繁的放進inactive鏈表中,然后掃描inactive鏈表,將里面活躍的移回active鏈表中;
4.進行swap的時候,先對inactive的頁進行換出;
5.如果是file的文件映射page頁,則判斷其是否為臟數據,如果是臟數據就寫回,不是臟數據可以直接釋放。
這樣看來,內存回收這個行為會對兩種內存的使用進行回收:
一種是anon的匿名頁內存,主要回收手段是swap;另一種是file-backed的文件映射頁,主要釋放手段是寫回和清空。
內存對匿名頁和文件緩存一共用了四條鏈表進行組織,回收過程主要是針對這四條鏈表進行掃描和操作。
kswapd什么時候進行swap操作?
內核的兩種內存回收機制kswapd周期檢查和直接內存回收,直接內存回收比較好理解,當申請的內存大於剩余內存的時候,就會觸發直接回收。
kswapd進程在周期檢查的時候觸發回收的條件是什么呢?
kswapd進程要周期對內存進行檢測,達到一定閾值的時候開始進行內存回收。這個所謂的閾值可以理解為內存目前的使用壓力。雖然我們還有剩余內存,但是當剩余內存比較小的時候,就是內存壓力較大的時候,就應該開始試圖回收寫內存了,這樣才能保證系統盡可能有足夠的內存給突發的內存申請所使用。
基於swappiness調優
詳情請參考:http://blog.csdn.net/tenfyguo/article/details/50185915
swappiness是一個swap相關百分比參數,默認值是60,范圍是0-100。
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. |
從上面這段話可知,swappiness表示內核swap內存頁的積極程度。值越高越是積極的進行swap;降低表示積極性越低。當swappiness值為0是,不是不進行swap,只是一個zone中內存的free和file-backed頁低於高水位標記才會進行swap。
shrink_lruvec()調用get_scan_count()方法,get_scan_count用於決定anon和文件LRU列表被掃描的積極程度。swappiness參數實際上是知道內核在清空內存的時候,是更傾向於清空file-backed內存還是匿名頁的swap。
static void get_scan_count(struct lruvec *lruvec, int swappiness, /* /* If we have no swap space, do not bother scanning anon pages. */ /* /* /* zonefree = zone_page_state(zone, NR_FREE_PAGES); if (unlikely(zonefile + zonefree <= high_wmark_pages(zone))) { /* scan_balance = SCAN_FRACT; /* 1.如果swappiness設置為100,那么匿名頁和文件將用同樣的優先級進行回收。 2.如果swappiness值是60,內核回收的時候也不是完全按照60:140比例清空。在計算具體回收大小的時候,還需要參考當前內存使用的其他信息。 3.swappiness為0的話,也不是根本不進行swap。 /* anon = get_lru_size(lruvec, LRU_ACTIVE_ANON) + spin_lock_irq(&zone->lru_lock); if (unlikely(reclaim_stat->recent_scanned[1] > file / 4)) { /* fp = file_prio * (reclaim_stat->recent_scanned[1] + 1); fraction[0] = ap; size = get_lru_size(lruvec, lru); if (!scan && pass && force_scan) switch (scan_balance) { *lru_pages += size; /* |
可以使用sysctl vm.swappiness=10對swappiness進行設置。
dirty_ratio
同步刷臟頁,會阻塞應用程序。這個參數控制文件系統的同步寫緩沖區的大小,單位是百分比,表示當寫緩沖區使用到系統內存多少的時候(即指定了當文件系統緩存臟頁數量達到系統內存百分比時),開始向磁盤寫出數據,及系統不得不開始處理緩存臟頁,在此過程中很多應用程序可能會因為系統轉而處理文件I/O而阻塞變慢。
增大會使系統內存更多用於磁盤寫緩沖,也可以極大提高系統的寫性能。
dirty_background_ratio
異步刷臟頁,不會阻塞應用程序。這個參數控制文件系統的后台進程,在合適刷新磁盤。當寫緩沖使用到系統內存一定百分比的時候,就會觸發pdflush/flush/kdmflush等后台回寫進程運行,將一定緩存的臟頁異步地輸入外存。
一般dirty_ratio比dirty_background_radio要大,先達到dirty_background_ratio的條件然后觸發flush進程進行異步的回寫操作,但是這一過程應用進程仍然可以進行寫操作,如果多個應用程序寫入的量大於刷出的量那自然會達到dirty_ratio的標准,此時操作系統會進入同步刷出臟頁的過程,阻塞應用進程。
增大會使更多系統內存用於磁盤寫緩沖。
dirty_expire_centisecs
申明Linux內核寫緩沖區里面的數據多舊了之后,pdflush進程就開始考慮寫到磁盤中去,單位是1/100秒。缺省是3000,即20秒的緩存就會被刷新到磁盤。如果太小,刷新磁盤就會比較頻繁,導致I/O占用太多時間。如過太大,在某些異常情況會造成數據丟失,同時暫用太多內存。
dirty_writeback_centisecs
這個參數控制內核額臟數據樹新進程pdflush的運行間隔,單位是1/100秒,缺省是500,即5秒。
vfs_cache_pressure
設置了虛擬內存回收directory和inode緩存的傾向,這個值越大,越傾向於回收。
缺省值為100;低於100,將導致內核更傾向於保留directory和inode cache;高於100,將導致內核傾向於回收directory和inode cache。
以上這些數據還是要根據系統實際內存以及不同的工作場景進行調優。
內存水位標記(watermark)
Linux為內存的使用設置了三種內存水位標記:high、low、min。
high:剩余的內存在high以上表示內存剩余較多,目前內存使用壓力不大。
high-low:表示剩余內存存在一定壓力。
low-min:表示內存開始使用有較大壓力,剩余內存不多了。
min:是最小水位標記,當剩余內存達到這個狀態時,就說明內存面臨很大壓力。小於min這部分內存,內核是保留給特定情況下使用的,一般不分配。
內存回收行為就是基於剩余內存的水位標記進行的:
當系統內存低於watermark[low]的時候,內核的kswapd開始起作用,進行內存回收。直到剩余內存達到watermark[high]的時候停止。
如果內存消耗導致達到了或者低於watermark[min]時,就會觸發直接回收(direct reclaim)。
min_free_kbytes
內存的watermark標記是根據當前內存總大小和min_free_kbytes進行運算的來的。
/proc/sys/vm/min_free_kbytes決定了每個zone的watermark[min]的大小,然后根據min的大小和每個zone內存大小分別算出每個zone的low和high水位標記。
init_per_zone_wmark_min 初始化min_free_kbytes -->setup_per_zone_wmarks -->__setup_per_zone_wmarks 根據min_free_kbytes計算low和high的值 |
下面來分析一下low和high水位值是如何計算的:
static void __setup_per_zone_wmarks(void) /* Calculate total number of !ZONE_HIGHMEM pages */ for_each_zone(zone) { 遍歷所有zone spin_lock_irqsave(&zone->lock, flags); if (is_highmem(zone)) { min_pages = zone->managed_pages / 1024; zone->watermark[WMARK_LOW] = min_wmark_pages(zone) + __mod_zone_page_state(zone, NR_ALLOC_BATCH, spin_unlock_irqrestore(&zone->lock, flags); /* update totalreserve_pages */ |
通過cat /proc/zoneinfo可以看到不同zone的watermark:
cat /proc/zoneinfo |
zone_reclaim_mode
當一個內存區域內部的內存耗盡時,是從其內部進行內存回收還是可以從其他zone進行回收的選項。在申請內存時(get_page_from_freelist()),內核在當前zone內沒有足夠的內存可用的情況下,會根據zone_reclaim_mode的設置來決策是從下一個zone找空閑內存還是在zone內部進行回收。
0:意味着關閉zone_claim模式,可以從其他zone或NUMA節點回收內存。
1:打開zone_claim模式,這樣內存回收只會發生在本地節點內。
2:在本地回收內存時,可以將可以將cache中的臟數據回寫硬盤,以回收內存。
3:可以用swap方式回收內存。
不同的參數配置會在NUMA環境中對其他內存節點的內存使用產生不同的影響,大家可以根據自己的情況進行設置以優化你的應用。
默認情況下,zone_reclaim模式是關閉的。這在很多應用場景下可以提高效率,比如文件服務器,或者依賴內存中cache比較多的應用場景。
這樣的場景對內存cache速度的依賴要高於進程進程本身對內存速度的依賴,所以我們寧可讓內存從其他zone申請使用,也不願意清本地cache。
如果確定應用場景是內存需求大於緩存,而且盡量要避免內存訪問跨越NUMA節點造成的性能下降的話,則可以打開zone_reclaim模式。
此時頁分配器會優先回收容易回收的可回收內存(主要是當前不用的page cache頁),然后再回收其他內存。
打開本地回收模式的寫回可能會引發其他內存節點上的大量的臟數據寫回處理。如果一個內存zone已經滿了,那么臟數據的寫回也會導致進程處理速度收到影響,產生處理瓶頸。
這會降低某個內存節點相關的進程的性能,因為進程不再能夠使用其他節點上的內存。但是會增加節點之間的隔離性,其他節點的相關進程運行將不會因為另一個節點上的內存回收導致性能下降。
除非針對本地節點的內存限制策略或者cpuset配置有變化,對swap的限制會有效約束交換只發生在本地內存節點所管理的區域上。
page-cluster
page-cluster是用來控制從swap空間換入數據的時候,一次連續讀取的頁數這相當於對交換空間的預讀取。這里的連續指的是swap空間上的連續,而不是內存地址上的連續。
這個文件中設置的是2的指數。0,則預讀取1頁;3,則預讀取8頁。
創建、刪除swap分區
查看swap相關信息
swapon –s和cat /proc/swaps的結果是一樣的,可以用於查看當前系統的swap分區總結。
Filename Type Size Used Priority |
free -m用於顯示系統空閑和使用的內存量,其中包括swap信息。
total used free shared buffers cached |
創建swap分區
有兩種方式創建swap分區:一種是基於物理分區,另一種是基於一個文件。
基於物理分區創建
- 創建一個swap分區:fdisk -l /dev/sdb1
- 格式化分區:mkswap -c v1 /dev/sdb1
- 修改/etc/fstab文件,增加:
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point> <type> <options> <dump> <pass>
# swap was on /dev/sdb1 during installation
UUID=47b966f2-8157-4650-a2c2-32f0907081bf none swap sw 0 0 - 激活swap分區:swapon –a /dev/sdb1
- 查看swap分區:swapon -s 或cat /proc/swaps
基於文件創建
- dd if=/dev/zero of=/home/lubaoquan/swap/tmp.swap bs=1G count=1,創建空文件
- mkswap tmp.swap,創建為swap文件
Setting up swapspace version 1, size = 1048572 KiB |
- swapon /tmp/tmp.swap,激活swap分區
- 修改/etc/fstab文件
/tmp/tmp.swap swap swap default 0 0 |
- 查看swap分區信息swapon –s
Filename Type Size Used Priority |
刪除swap分區
- swapoff /dev/sdb1
- 修改/etc/fstab文件
關於swap的一個測試
關閉打開清空swap
sudo swapoff -a關閉swap,然后free –m查看使用情況
total used free shared buffers cached |
sudo swapon -a再打開,查看free -m:
total used free shared buffers cached |
swap變化
使用eat_mem.c不斷分配大內存。
#include <stdlib.h> #include <stdio.h> #include <string.h> int main(int argc, char** argv) { int max = -1; int mb = 0; char* buffer; if(argc > 1) max = atoi(argv[1]); while((buffer=malloc(10*1024*1024)) != NULL && mb != max) { memset(buffer, 0,10*1024*1024); mb = mb + 10; printf("Allocated %d MB\n", mb); sleep(1); } return 0; } |
執行./eat_mem,使用watch 'free -m'可以動態查看Mem和Swap的使用情況:
Every 2.0s: free -m Fri Jan 20 16:41:31 2017 total used free shared buffers cached |
在eat_mem執行到11710 MB的時候,被Killed了。
./eat_mem |
在這個過程中可以看到,Mem的free越來越小,Swap的used空間越來越多,越來越多的內容被置換到Swap區域。
在內容開始置換到swap分區過程中,系統的響應速度越來越慢。最后達到swap分區也承受不了,eat_mem就會被Killed。
swap分區的優先級
在使用多個swap分區或者文件的時候,還有一個優先級的概念。
在swapon的時候,可以使用-p指定相關swap空間的優先級,值越大優先級越高,范圍是-1到32767。
內核在使用swap空間的時候總是先使用優先級搞得空間,后使用優先級低的。
如果把多個swap空間的優先級設置成一樣的,那么swap空間將會以輪詢的方式並行使用。
如果兩個swap放在不同硬盤上,相同優先級可以起到類似的RAID效果,增大swap的讀寫效率。
編程時使用mlock()可以將指定的內存標記為不換出。
參考文檔
http://blog.csdn.net/cyuyan112233/article/details/18803589
http://blog.csdn.net/tenfyguo/article/details/50185915
http://blog.chinaunix.net/uid-24774106-id-3954137.html