[kernel]----理解kswapd的低水位min_free_kbytes

東北胖子發表於2019-05-27

1. min_free_kbytes

先看官方解釋:
This is used to force the Linux VM to keep a minimum number of kilobytes free. The VM uses this number to compute a watermark[WMARK_MIN] value for each lowmem zone in the system. Each lowmem zone gets a number of reserved free pages based proportionally on its size.
Some minimal amount of memory is needed to satisfy PF_MEMALLOC allocations; if you set this to lower than 1024KB, your system will become subtly broken, and prone to deadlock under high loads.
Setting this too high will OOM your machine instantly.
解釋已經很清楚了,主要有以下幾個關鍵點:

1. 代表系統所保留空閒記憶體的最低限。

在系統初始化時會根據記憶體大小計算一個預設值,計算規則是:
min_free_kbytes = sqrt(lowmem_kbytes * 16) = 4 * sqrt(lowmem_kbytes)(注:lowmem_kbytes即可認為是系統記憶體大小)
另外,計算出來的值有最小最大限制,最小為128K,最大為64M。
可以看出,min_free_kbytes隨著記憶體的增大不是線性增長,comments裡提到了原因“because network bandwidth does not increase linearly with machine size”。隨著記憶體的增大,沒有必要也線性的預留出過多的記憶體,能保證緊急時刻的使用量便足矣。

2.min_free_kbytes的主要用途是計算影響記憶體回收的三個引數 watermark[min/low/high]

1) watermark[high] > watermark [low] > watermark[min],各個zone各一套

2)在系統空閒記憶體低於 watermark[low]時,開始啟動核心執行緒kswapd進行記憶體回收(每個zone一個),直到該zone的空閒記憶體數量達到watermark[high]後停止回收。如果上層申請記憶體的速度太快,導致空閒記憶體降至watermark[min]後,核心就會進行direct reclaim(直接回收),即直接在應用程式的程式上下文中進行回收,再用回收上來的空閒頁滿足記憶體申請,因此實際會阻塞應用程式,帶來一定的響應延遲,而且可能會觸發系統OOM。這是因為watermark[min]以下的記憶體屬於系統的自留記憶體,用以滿足特殊使用,所以不會給使用者態的普通申請來用。

3)三個watermark的計算方法:
watermark[min] = min_free_kbytes換算為page單位即可,假設為min_free_pages。(因為是每個zone各有一套watermark引數,實際計算效果是根據各個zone大小所佔記憶體總大小的比例,而算出來的per zone min_free_pages)
watermark[low] = watermark[min] * 5 / 4
watermark[high] = watermark[min] * 3 / 2
所以中間的buffer量為 high - low = low - min = per_zone_min_free_pages * 1/4。因為min_free_kbytes = 4* sqrt(lowmem_kbytes),也可以看出中間的buffer量也是跟記憶體的增長速度成開方關係。

4)可以透過/proc/zoneinfo檢視每個zone的watermark
例如:

Node 0, zone      DMA
pages free     3960
       min      65
       low      81
       high     97

3.min_free_kbytes大小的影響
min_free_kbytes設的越大,watermark的線越高,同時三個線之間的buffer量也相應會增加。這意味著會較早的啟動kswapd進行回收,且會回收上來較多的記憶體(直至watermark[high]才會停止),這會使得系統預留過多的空閒記憶體,從而在一定程度上降低了應用程式可使用的記憶體量。極端情況下設定min_free_kbytes接近記憶體大小時,留給應用程式的記憶體就會太少而可能會頻繁地導致OOM的發生。
min_free_kbytes設的過小,則會導致系統預留記憶體過小。kswapd回收的過程中也會有少量的記憶體分配行為(會設上PF_MEMALLOC)標誌,這個標誌會允許kswapd使用預留記憶體;另外一種情況是被OOM選中殺死的程式在退出過程中,如果需要申請記憶體也可以使用預留部分。這兩種情況下讓他們使用預留記憶體可以避免系統進入deadlock狀態。

2. lowmem_reserve_ratio

官方解釋:

For some specialised workloads on highmem machines it is dangerous for the kernel to allow process memory to be allocated from the "lowmem" zone. This is because that memory could then be pinned via the mlock() system call, or by unavailability of swapspace.
And on large highmem machines this lack of reclaimable lowmem memory can be fatal.
So the Linux page allocator has a mechanism which prevents allocations which could use highmem from using too much lowmem. This means that a certain amount of lowmem is defended from the possibility of being captured into pinned user memory.
The `lowmem_reserve_ratio' tunable determines how aggressive the kernel is in defending these lower zones.
If you have a machine which uses highmem or ISA DMA and your applications are using mlock(), or if you are running with no swap then you probably should change the lowmem_reserve_ratio setting.

1.作用
除了min_free_kbytes會在每個zone上預留一部分記憶體外,lowmem_reserve_ratio是在各個zone之間進行一定的防衛預留,主要是防止高階zone在沒記憶體的情況下過度使用低端zone的記憶體資源。
例如現在常見的一個node的機器有三個zone: DMA,DMA32和NORMAL。DMA和DMA32屬於低端zone,記憶體也較小,如96G記憶體的機器兩個zone總和才1G左右,NORMAL就相對屬於高階記憶體(現在一般沒有HIGH zone),而且數量較大(>90G)。低端記憶體有一定的特殊作用比如發生DMA時只能分配DMA zone的低端記憶體,因此需要在 儘量可以使用高階記憶體時 而 不使用低端記憶體,同時防止高階記憶體分配不足的時候搶佔稀有的低端記憶體。

  1. 計算方法
cat /proc/sys/vm/lowmem_reserve_ratio256     256     32

核心利用上述的protection陣列計算每個zone的預留page量,計算出來也是陣列形式,從/proc/zoneinfo裡可以檢視:

Node 0, zone      DMA
 pages free     1355
       min      3
       low      3
       high     4
       :
       :
   numa_other   0       protection: (0, 2004, 2004, 2004)
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 pagesets   cpu: 0 pcp: 0
       :

在進行記憶體分配時,這些預留頁數值和watermark相加來一起決定現在是滿足分配請求,還是認為空閒記憶體量過低需要啟動回收。
例如,如果一個normal區(index = 2)的頁申請來試圖分配DMA區的記憶體,且現在使用的判斷標準是watermark[low]時,核心計算出 page_free = 1355,而watermark + protection[2] = 3 + 2004 = 2007 > page_free,則認為空閒記憶體太少而不予以分配。如果分配請求本就來自DMA zone,則 protection[0] = 0會被使用,而滿足分配申請。
zone[i] 的 protection[j] 計算規則如下:

(i < j):
 zone[i]->protection[j]
 = (total sums of present_pages from zone[i+1] to zone[j] on the node)
   / lowmem_reserve_ratio[i];
(i = j):
  (should not be protected. = 0;
(i > j):
  (not necessary, but looks 0)

預設的 lowmem_reserve_ratio[i] 值是:

   256 (if zone[i] means DMA or DMA32 zone)   32  (others).

從上面的計算規則可以看出,預留記憶體值是ratio的倒數關係,如果是256則代表 1/256,即為 0.39% 的高階zone記憶體大小。
如果想要預留更多頁,應該設更小一點的值,最小值是1(1/1 -> 100%)。

  1. 和min_free_kbytes(watermark)的配合示例
    下面是一段某線上伺服器(96G)記憶體申請失敗時列印出的log:



  2. [38905.295014] java: page allocation failure. order:1, mode:0x20, zone 2
    [38905.295020] Pid: 25174, comm: java Not tainted 2.6.32-220.23.1.tb750.el5.x86_64 #1
    ...
    [38905.295348] active_anon:5730961 inactive_anon:216708 isolated_anon:0
    [38905.295349]  active_file:2251981 inactive_file:15562505 isolated_file:0
    [38905.295350]  unevictable:1256 dirty:790255 writeback:0 unstable:0
    [38905.295351]  free:113095 slab_reclaimable:577285 slab_unreclaimable:31941
    [38905.295352]  mapped:7816 shmem:4 pagetables:13911 bounce:0
    [38905.295355] Node 0 DMA free:15796kB min:4kB low:4kB high:4kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB  isolated(anon):0kB isolated(file):0kB present:15332kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:0kB slab_reclaimable:0kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:0kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? yes
    [38905.295365] lowmem_reserve[]: 0 1951 96891 96891
    [38905.295369] Node 0 DMA32 free:380032kB min:800kB low:1000kB high:1200kB active_anon:46056kB inactive_anon:10876kB active_file:15968kB inactive_file:129772kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:1998016kB mlocked:0kB dirty:20416kB writeback:0kB mapped:0kB shmem:0kB slab_reclaimable:11716kB slab_unreclaimable:160kB kernel_stack:176kB pagetables:112kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:576 all_unreclaimable? no
    [38905.295379] lowmem_reserve[]: 0 0 94940 94940
    [38905.295383] Node 0 Normal free:56552kB min:39032kB low:48788kB high:58548kB active_anon:22877788kB inactive_anon:855956kB active_file:8991956kB inactive_file:62120248kB unevictable:5024kB isolated(anon):0kB isolated(file):0kB present:97218560kB mlocked:5024kB dirty:3140604kB writeback:0kB mapped:31264kB shmem:16kB slab_reclaimable:2297424kB slab_unreclaimable:127604kB kernel_stack:12528kB pagetables:55532kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? no
    [38905.295393] lowmem_reserve[]: 0 0 0 0
    [38905.295396] Node 0 DMA: 1*4kB 2*8kB 0*16kB 1*32kB 2*64kB 0*128kB 1*256kB 0*512kB 1*1024kB 1*2048kB 3*4096kB = 15796kB
    [38905.295405] Node 0 DMA32: 130*4kB 65*8kB 75*16kB 72*32kB 95*64kB 22*128kB 10*256kB 7*512kB 4*1024kB 2*2048kB 86*4096kB = 380032kB
    [38905.295414] Node 0 Normal: 12544*4kB 68*8kB 0*16kB 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 1*4096kB = 54816kB
    [38905.295423] 17816926 total pagecache pages
    

1)從第一行log“order:1, mode:0x20”可以看出來是GFP_ATOMIC型別的申請,且order = 1(page = 2 )

2)第一次記憶體申請嘗試

在__alloc_pages_nodemask()裡,首先呼叫 get_page_from_freelist() 嘗試第一次申請,使用的標誌位是 ALLOC_WMARK_LOW|ALLOC_CPUSET,它會對每個zone都做 zone_watermark_ok()的檢查,使用的就是傳進的watermark[low]閾值。
    在zone_watermark_ok()裡會考慮z->lowmem_reserve[],導致在normal上的申請不會落到低端zone。比如對於DMA32:
free pages = 380032KB = 95008 pages < low(1000KB = 250 pages) + lowmem_reserve normal = 95190
所以就認為DMA32也不平不ok,同理更用不了DMA的記憶體。
而對於normal自己記憶體來說,free pages = 56552 KB = 14138 pages,也不用考慮lowmem_reserve(0),但這時還會考慮申請order(1),減去order 0的12544個page後只剩 14138 - 12544 = 1594,也小於 low / 2 = (48788KB=12197pages) / 2 = 6098 pages。
所以初次申請嘗試失敗,進入__alloc_pages_slowpath() 嘗試進行更為積極一些的申請。

3)第二次記憶體申請嘗試
__alloc_pages_slowpath()首先是透過 gfp_to_alloc_flags() 修改alloc_pages,設上更為強硬的標誌位。這塊根據原來的GFP_ATOMIC會設上 ALLOC_WMARK_MIN | ALLOC_HARDER | ALLOC_HIGH。但注意的是不會設上 ALLOC_NO_WATERMARKS 標誌位。這個標誌位不再判斷zone的水位限制,屬於優先順序最高的申請,可以動用所有的reserve記憶體,但條件是(!in_interrupt() && ((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE)))),即要求不能在中斷上下文,且是正在進行回收(例如kswapd)或者正在退出的程式。
    之後進入拿著新的alloc_pages重新進入get_page_from_pagelist() 嘗試第二次申請,雖然有了 ALLOC_HARDER和ALLOC_HIGH,但是不幸的是在3個zone的zone_watermark_ok檢查中還是都無法透過,例如對於DMA32:
free pages = 380032KB = 95008 pages
因為設上了ALLOC_HIGH 所以會將得到的watermark[min]減半,即min = min/2 = 800K / 2 = 400K = 100pages
而又因為設上了ALLOC_HARDER,會再將min砍去1/4,即min = 3 * min / 4 = 100 pages * 3 / 4 = 75 pages
即便如此,min(75 pages) + lowmem_reserve normal = 95015,仍大於free pages,仍認為無法分配記憶體,同理DMA也不不成功,而normal中 free pages裡連續8K的頁太少也無法滿足分配
    第二次失敗後,由於沒有ALLOC_NO_WATERMARK也不會進入__alloc_pages_high_priority 進行最高優先順序的申請,同時由於是GFP_ATOMIC型別的分配不能阻塞回收或者進入OOM,因此就以申請失敗告終。
遇到此種情況可以適當調高 min_free_kbytes 使kswapd較早啟動回收,使系統一直留有較多的空閒記憶體,同時可以適度降低 lowmem_reserve_ratio(可選),使得記憶體不足的情況下(主要是normal zone)可以借用DMA32/DMA的記憶體救急(注意不能也不能過低)。


文章出處:https://www.cnblogs.com/muahao/p/6532527.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2317695/viewspace-2645826/,如需轉載,請註明出處,否則將追究法律責任。

相關文章