1. 程式人生 > >Linux核心頁回收swappiness引數確切含義

Linux核心頁回收swappiness引數確切含義

這兩天看微博上有人討論swappiness相關問題,mysqlperformanceblog上也有人說將swappiness設成0後MySQL被“意外” kill掉,另外自己所在團隊線上也遇到過類似問題,大家對這個引數的含義可能還不是很清楚,個人嘗試寫篇文章詳細解釋下這個引數。

本文主要嘗試解釋兩個問題:
1. swappiness的確切含義是什麼,它對核心進行頁回收機制的影響。
2. swappiness設定成0,為什麼系統仍然可能會有swap發生。

一. 關於記憶體分配與頁回收(page reclaim)

page reclaim發生的場景主要有兩類,一個是kswapd後臺執行緒進行的活動,另一個是direct reclaim,即分配頁時沒有空閒記憶體滿足,需要立即直接進行的頁回收。大體上記憶體分配的流程會分為兩部分,一部分是fast path,另一部分是slow path,通常記憶體使用非緊張情況下,都會在fast path就可以滿足要求。並且fast path下的記憶體分配不會出現dirty writeback及swap等頁回收引起的IO阻塞情況。

fast path大體流程如下:
1.如果系統掛載使用了memory cgroup,則首先檢查是否超過cgroup限額,如果超過則進行direct reclaim,通過do_try_to_free_pages完成。如果沒超過則進行cgroup的charge工作(charge是通過兩階段提交完成的,這裡不展開了)。
2.從本地prefered zone記憶體節點查詢空閒頁,需要判斷是否滿足系統watermark及dirty ratio的要求,如果滿足則從buddy system上摘取相應page,否則嘗試對本地prefered zone進行頁回收,本次fast path下頁回收只會回收clean page,即不會考慮dirty page以及mapped page,這樣就不會產生任何swap及writeback,即不會引起任何blocking的IO操作,如果這次回收仍然無法滿足請求的記憶體頁數目則進入slow path

slow path大體流程如下:
1. 首先喚醒kswapd進行page reclaim後臺操作。
2. 重新嘗試本地prefered zone進行分配記憶體,如果失敗會根據請求的GFP相關引數決定是否嘗試忽略watermark, dirty ratio以及本地節點分配等要求進行再次重試,這一步中如果分配頁時有指定__GFP_NOFAIL標記,則分配失敗會一直等待重試。
3. 如果沒有__GFP_NOFAIL標記,則會需開始進行page compact及page direct reclaim操作,之後如果仍然沒有可用記憶體,則進入OOM流程。

相關內容可以參閱核心程式碼__alloc_pages函式的邏輯,另外無論page reclaim是由誰發起的,最終都會統一入口到shrink_zone,即針對每個zone獨立進行reclaim操作,最終會進入shrink_lruvec函式,進行每個zone相應page lru連結串列的掃描與回收操作。

二. 關於頁回收的一些背景知識

頁回收大體流程會先在每個zone上掃描相應的page連結串列,主要包括inactive anon/active anon(匿名頁連結串列)以及inactive file/active file連結串列(file cache/對映頁連結串列),一共四條連結串列,我們所有使用過的page在被回收前基本是儲存在這四條連結串列中的某一條中的(還有一部分在unevictable連結串列中,忽略),根據其被引用的次數會決定其處於active還是inactive連結串列中,根據其型別決定處於anon還是file連結串列中。

頁回收總體會掃描逐個記憶體節點的所有zone,然後先掃描active,將不頻繁訪問的頁挪到inactive連結串列中,隨後掃描inactive連結串列,會將其中被頻繁引用的頁重新挪回到active中,確認不頻繁的頁則最終被回收,如果是file based的頁則根據是否clean進行釋放或回寫(writeback,filecache則直接釋放),如果是anon則進行swap,所以本文實際關心的是swappiness引數對anon連結串列掃描的影響。

另外還需要了解前面描述的四個連結串列原來是放在zone資料結構上的,後來引入了mem_cgroup則,重新定義了一組mem_cgroup_per_zone/mem_cgroup_per_node的資料結構,這四個連結串列同時定義在這組資料結構上,如果系統開啟了mem cgroup則使用後者,否則用前者。

另外再重點說下swap只是page reclaim的一種處理措施,主要針對anon page,我們最終來看下swappiness的確切含義

三. swappiness對page reclaim的確切影響

page reclaim邏輯中對前面所述四個連結串列進行掃描的邏輯在vmscan.c中的get_scan_count函式內,該函式大部分邏輯註釋寫得非常清楚,我們簡單梳理下,主要關注scan_balance變數的取值:

1. 首先如果系統禁用了swap或者沒有swap空間,則只掃描file based的連結串列,即不進行匿名頁連結串列掃描
程式碼如下:

if (!sc->may_swap || (get_nr_swap_pages() <= 0)) {
scan_balance = SCAN_FILE;
goto out;
}

2. 如果當前進行的不是全域性頁回收(cgroup資源限額引起的頁回收),並且swappiness設為0,則不進行匿名頁連結串列掃描,這個是沒得商量,這裡swappiness值直接決定了是否有swap發生,設成0則肯定不會發生,另外需要注意,這種情況下需要設定的是cgroup配置檔案memory.swappiness,而不是全域性的sysctl vm.swappiness
程式碼如下:

if (!global_reclaim(sc) && !vmscan_swappiness(sc)) {
scan_balance = SCAN_FILE;
goto out;
}

3. 如果進行連結串列掃描前設定的priority(這個值決定掃描多少分之一的連結串列元素)為0,且swappiness非0,則可能會進行swap
程式碼如下:

if (!sc->priority && vmscan_swappiness(sc)) {
scan_balance = SCAN_EQUAL;
goto out;
}

4. 如果是全域性頁回收,並且當前空閒記憶體和所有file based連結串列page數目的加和都小於系統的high watermark,則必須進行匿名頁回收,則必然會發生swap,可以看到這裡swappiness的值如何設定是完全無關的,這也解釋了為什麼其為0,系統也會進行swap的原因,另外最後我們會詳細解釋系統page watermark是如何計算的。
程式碼如下:

anon  = get_lru_size(lruvec, LRU_ACTIVE_ANON) +
get_lru_size(lruvec, LRU_INACTIVE_ANON);
file  = get_lru_size(lruvec, LRU_ACTIVE_FILE) +
get_lru_size(lruvec, LRU_INACTIVE_FILE);

if (global_reclaim(sc)) {
free = zone_page_state(zone, NR_FREE_PAGES);
if (unlikely(file + free <= high_wmark_pages(zone))) {
scan_balance = SCAN_ANON;
goto out;
}
}

5. 如果系統inactive file連結串列比較充足,則不考慮進行匿名頁的回收,即不進行swap
程式碼如下:

if (!inactive_file_is_low(lruvec)) {
scan_balance = SCAN_FILE;
goto out;
}

6. 最後一種情況則要根據swappiness值與之前統計的file與anon哪個更有價值來綜合決定file和anon連結串列掃描的比例,這時如果swappiness設定成0,則也不會掃描anon連結串列,即不進行swap,程式碼比較多,不再貼出。

四. 系統記憶體watermark的計算

前面看到系統記憶體watermark對頁回收機制是有決定影響的,其實在記憶體分配中也會頻繁用到這個值,確切的說它有三個值,分別是low,min和high,根據分配頁時來指定用哪個,如果系統空閒記憶體低於相應watermark則分配會失敗,這也是進入slow path或者wakeup kswapd的依據。

實際這個值的計算是通過sysctl裡的vm.min_free_kbytes來決定的,大體的計算公式如下:

pages_min = min_free_kbytes >> (PAGE_SHIFT – 10);
tmp = (u64)pages_min * zone->managed_pages;
do_div(tmp, lowmem_pages);
zone->watermark[WMARK_MIN] = tmp;
zone->watermark[WMARK_LOW]  = min_wmark_pages(zone) + (tmp >> 2);
zone->watermark[WMARK_HIGH] = min_wmark_pages(zone) + (tmp >> 1);

即根據min_free_kbytes的值按照每個zone管理頁面的比例算出zone的min_watermark,然後再加min的1/4就是low,加1/2就是high了

總結:

swappiness的值是個參考值,是否會發生swap跟當前是哪種page reclaim及系統當前狀態都有關係,所以設定了swappiness=0並不代表一定沒有swap發生,同時設為0也確實會可能發生OOM。

個人仍然認為線上環境設定swappiness=0是沒有任何問題的。

文章來自微信公眾號:運維幫