Linux 虛擬記憶體引數配置

Linksla發表於2023-10-30

一、問題出發點

    Jun 1 10:30:21 audit1 kernel: swapper: page allocation failure. order:1, mode:0x20 
    Jun 1 10:30:21 audit1 kernel: Pid: 0, comm: swapper Tainted: G --------------- T 2.6.32-431.20.3.el6.x86_64 #1 
    Jun 1 10:30:21 audit1 kernel: Call Trace: 
    Jun 1 10:30:21 audit1 kernel: <IRQ> [<ffffffff8112f80a>] ? __alloc_pages_nodemask+0x74a/0x8d0 
    Jun 1 10:30:21 audit1 kernel: [<ffffffff8116e242>] ? kmem_getpages+0x62/0x170 
    Jun 1 10:30:21 audit1 kernel: [<ffffffff8116ee5a>] ? fallback_alloc+0x1ba/0x270 
    Jun 1 10:30:21 audit1 kernel: [<ffffffff8116ebd9>] ? ____cache_alloc_node+0x99/0x160 
    Jun 1 10:30:21 audit1 kernel: [<ffffffff8116fb5b>] ? kmem_cache_alloc+0x11b/0x190 
    Jun 1 10:30:21 audit1 kernel: [<ffffffff8144cde8>] ? sk_prot_alloc+0x48/0x1c0 
    Jun 1 10:30:21 audit1 kernel: [<ffffffff8144dff2>] ? sk_clone+0x22/0x2e0 
    Jun 1 10:30:21 audit1 kernel: [<ffffffff8149f9c6>] ? inet_csk_clone+0x16/0xd0 
    Jun 1 10:30:21 audit1 kernel: [<ffffffff814b9293>] ? tcp_create_openreq_child+0x23/0x470
    在監控中,發現messages日誌中出現failure報錯資訊,發現如上記憶體堆疊報錯。

    二、虛擬記憶體介紹

    1.虛擬記憶體

    毋庸置疑,虛擬記憶體絕對是作業系統中最重要的概念之一。我想主要是由於記憶體的重要“戰略地位”。CPU太快,但容量小且功能單一,其他 I/O 硬體支援各種花式功能,可是相對於 CPU,它們又太慢。於是它們之間就需要一種潤滑劑來作為緩衝,這就是記憶體大顯身手的地方。

    而在現代作業系統中,多工已是標配。多工並行,大大提升了CPU 利用率,但卻引出了多個程式對記憶體操作的衝突問題,虛擬記憶體概念的提出就是為了解決這個問題。

    上圖是虛擬記憶體最簡單也是最直觀的解釋。

    作業系統有一塊實體記憶體(中間的部分),有兩個程式(實際會更多)P1 和 P2,作業系統偷偷地分別告訴 P1 和 P2,我的整個記憶體都是你的,隨便用,管夠。可事實上呢,作業系統只是給它們畫了個大餅,這些記憶體說是都給了 P1 和 P2,實際上只給了它們一個序號而已。只有當 P1 和 P2 真正開始使用這些記憶體時,系統才開始使用輾轉挪移,拼湊出各個塊給程式用,P2 以為自己在用 A 記憶體,實際上已經被系統悄悄重定向到真正的 B 去了,甚至,當 P1 和 P2 共用了 C 記憶體,他們也不知道。

    作業系統的這種欺騙程式的手段,就是虛擬記憶體。對P1 和 P2 等程式來說,它們都以為自己佔用了整個記憶體,而自己使用的實體記憶體的哪段地址,它們並不知道也無需關心。

    2.分頁和頁表

    虛擬記憶體是作業系統裡的概念,對作業系統來說,虛擬記憶體就是一張張的對照表,P1 獲取 A 記憶體裡的資料時應該去實體記憶體的 A 地址找,而找 B 記憶體裡的資料應該去實體記憶體的 C 地址。

    我們知道系統裡的基本單位都是Byte 位元組,如果將每一個虛擬記憶體的 Byte 都對應到實體記憶體的地址,每個條目最少需要 8位元組(32位虛擬地址->32位實體地址),在 4G 記憶體的情況下,就需要 32GB 的空間來存放對照表,那麼這張表就大得真正的實體地址也放不下了,於是作業系統引入了 頁(Page)的概念。

    在系統啟動時,作業系統將整個實體記憶體以4K 為單位,劃分為各個頁。之後進行記憶體分配時,都以頁為單位,那麼虛擬記憶體頁對應實體記憶體頁的對映表就大大減小了,4G 記憶體,只需要 8M 的對映表即可,一些程式沒有使用到的虛擬記憶體,也並不需要儲存對映關係,而且Linux 還為大記憶體設計了多級頁表,可以進一頁減少了記憶體消耗。作業系統虛擬記憶體到實體記憶體的對映表,就被稱為頁表。

    3.記憶體定址和分配

    我們知道透過虛擬記憶體機制,每個程式都以為自己佔用了全部記憶體,程式訪問記憶體時,作業系統都會把程式提供的虛擬記憶體地址轉換為實體地址,再去對應的實體地址上獲取資料。CPU 中有一種硬體,記憶體管理單元 MMU(Memory Management Unit)專門用來將翻譯虛擬記憶體地址。CPU 還為頁表定址設定了快取策略,由於程式的區域性性,其快取命中率能達到 98%。

    以上情況是頁表記憶體在虛擬地址到實體地址的對映,而如果程式訪問的實體地址還沒有被分配,系統則會產生一個缺頁中斷,在中斷處理時,系統切到核心態為程式虛擬地址分配實體地址。

    4.zone

    記憶體管理的相關邏輯都是以zone為單位的,這裡zone的含義是指記憶體的分割槽管理。Linux將記憶體分成多個區,主要有直接訪問區(DMA)、一般區(Normal)和高階記憶體區(HighMemory)。核心對記憶體不同區域的訪問因為硬體結構因素會有定址和效率上的差別。如果在NUMA架構上,不同CPU所管理的記憶體也是不同的zone。

    5.NUMA

    NUMA中,雖然記憶體直接訪問在CPU上,但是由於記憶體被平均分配在了各個CPU上。只有當CPU訪問自身直接訪問記憶體對應的實體地址時,才會有較短的響應時間(後稱Local Access)。而如果需要訪問其他CPU attach的記憶體的資料時,就需要透過互聯通道訪問,響應時間就相比之前變慢了(後稱Remote Access)。所以NUMA(Non-Uniform Memory Access)就此得名。


    三、分析

    1.紅帽官方解釋(Root Cause):

    在RHEL 6.4之前,kswapd不會嘗試釋放連續頁面。當系統中沒有其他碎片整理記憶體時,這可能導致GFP_ATOMIC分配請求反覆失敗。使用RHEL 6.4和更高版本時,如果需要,kswapd將壓縮(碎片整理)可用記憶體。

    請注意,分配失敗仍然可能發生。 例如,當出現較大的GFP_ATOMIC分配突發時,kswapd可能難以跟上。但是,這些分配最終應該會成功。

    2.紅旗郵件回覆

    建議設定如下核心引數:

    1)vm.min_free_kbytes  

    系統預設:  

      [root@localhost ~]# cat /proc/sys/vm/min_free_kbytes  
      45056  
      [root@node1 log]# sysctl -a |grep vm.min  
      vm.min_free_kbytes = 45056

      建議設定為:  

      vm.min_free_kbytes = 450560  

      即增大該值的設定。   

      2)vm.zone_reclaim_mode  

      系統預設為0: 

        [root@localhost ~]# sysctl -a |grep vm.zone_reclaim_mode  vm.zone_reclaim_mode = 0

        建議設定vm.zone_reclaim_mode = 1到/etc/sysctl.conf檔案。

        具體如下:  

          #vim /etc/sysctl.conf #開啟該檔案,追加或修改為如下設定(其他引數不變);
          vm.min_free_kbytes = 450560  
          vm.zone_reclaim_mode = 1

          設定之後,儲存退出。

          寫入到該檔案中的引數,執行sysctl -p可即時生效。  下次重啟將讀取該檔案的設定。

          3. Virtual Memory相關引數介紹

          3.1.vm.swappiness

          控制換出執行時記憶體的相對權重。swappiness引數值可設定範圍在0到100之間。低引數值會讓核心儘量少用交換,更高引數值會使核心更多的去使用交換空間。預設值為60(參考網路資料:當剩餘實體記憶體低於40%(40=100-60)時,開始使用交換空間)。對於大多數作業系統,設定為100可能會影響整體效能,而設定為更低值(甚至為0)則可能減少響應延遲。

          swappiness的值的大小對如何使用swap分割槽是有著很大的聯絡的。先前,人們建議把vm.swapiness設定為0,它意味著“除非發生記憶體益處,否則不要進行記憶體交換”。直到Linux核心3.5-rcl版本釋出,這個值的意義才發生了變化。這個變化被一直到其他的發行版本上,包括RedHat企業版核心2.6.32-303。在發生變化之後,0意味著“在任何情況下都不要發生交換”。所以現在建議把這個值設定為1。swappiness=100的時候表示積極的使用swap分割槽,並且把記憶體上的資料及時的搬運到swap空間裡面。

          推薦值:vm.swappiness = 10

          3.2.vm.min_free_kbytes

          用於強制Linux VM保留最小數量的千位元組。VM使用該數字為系統中的每個低記憶體區域計算水位線[WMARK_MIN]值。每個lowmem區域根據其大小成比例地獲得許多保留的空閒頁面。需要一些最小的記憶體來滿足PF_MEMALLOC分配;如果將此值設定為小於1024KB,則系統將被破壞,並在高負載下易於死鎖。設定得太高將立即使您的機器OOM。

          這個引數本身決定了系統中每個zone的watermark[min]的值大小,然後核心根據min的大小並參考每個zone的記憶體大小分別算出每個zone的low水位和high水位值。

          從上面的解釋中主要有如下兩個點:

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

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

          在系統空閒記憶體低於  watermark[low] 時,開始啟動核心執行緒 kswapd 進行記憶體回收,直到該 zone 的空閒記憶體數量達到  watermark[high] 後停止回收。

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

          三個watermark的計算方法:

            watermark[min] = min_free_kbytes換算為 page 單位即可,假設為 min_free_pages 。
            watermark[low] = watermark[min] * 5/4
            watermark[high] = watermark[min] * 3/2

            Defines a percentage value. Writeout of dirty data begins in the background (via pdflush) when dirty data comprises this percentage of total memory. The default value is 10. For database workloads,  Red Hat recommends a lower value of 3.

            Setting min_free_kbytes too high will cause system hangs,especially in i386 arch, using less than 5% of total memory can avoid it, s o choose %5 of free memory or 2% of total memory.

            推薦值:vm.min_free_kbytes = <記憶體值>*2% (上限5G)

            3.3.vm.zone_reclaim_mode

            zone_reclaim_mode模式是在2.6版本後期開始加入核心的一種模式,可以用來管理當一個記憶體區域(zone)內部的記憶體耗盡時,  是從其內部進行記憶體回收還是可以從其他zone進行回收的選項。

            在申請記憶體時,核心在當前zone內沒有足夠記憶體可用的情況下,會根據zone_reclaim_mode的設定來決策是從下一個zone找空閒記憶體還是在zone內部進行回收。

            這個值為0時表示可以從下一個zone找可用記憶體,非0表示在本地回收。

            預設情況下,zone_reclaim模式是關閉的。這在很多應用場景下可以提高效率,比如檔案伺服器,或者依賴記憶體中cache比較多的應用場景。

            如果確定應用場景是記憶體需求大於快取,而且儘量要避免記憶體訪問跨越NUMA節點造成的效能下降的話,則可以開啟zone_reclaim模式。

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

            (如果讀寫量很大,則應該關閉,命中率較低,因為快取意義不大。

            目前mongoDB的官方文件推薦,是關閉NUMA,關閉vm.zone_reclaim_mode。)

            開啟本地回收模式的寫回可能會引發其他記憶體節點上的大量的髒資料寫回處理。 ( 開啟,會引發髒資料寫回,該操作時間和應用協商好,建議重啟服務 );

            如果一個記憶體zone已經滿了,那麼髒資料的寫回也會導致程式處理速度收到影響,產生處理瓶頸。(程式所在CPU可用的記憶體減少,直接導致髒資料寫回的頻率提高了,寫回產生的影響也就增加了,cache寫回對cache的讀寫效能一定有短暫的影響)

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

            推薦值:vm.zone_reclaim_mode = 1

            (在記憶體分配不足,且記憶體需求較多,建議關閉,加速cache回收!)

            3.4.vm.dirty_ratio

            是可以用髒資料填充的絕對最大系統記憶體量,用佔系統 空閒記憶體 的百分比來表示,當系統到達此點時,必須將所有髒資料提交到磁碟,同時所有新的I/O塊都會被阻塞,直到髒資料被寫入磁碟。

            這通常是長I/O卡頓的原因,但這也是保證記憶體中不會存在過量髒資料的保護機制。

            表示當寫緩衝使用到系統記憶體多少的時候,開始向磁碟寫出資料。增大隻會使用更多系統記憶體用於磁碟寫緩衝,可以極大提高系統的寫效能。但是,當你需要持續、恆定的寫入場合時,應該降低其數值。

            推薦值:vm.dirty_ratio = 30 (保持預設)

            3.5.vm.dirty_background_ratio

            記憶體髒資料開始寫回的上限。用佔系統 空閒記憶體的百分比來表示,這些髒資料稍後會寫入磁碟,pdflush/flush/kdmflush這些後臺程式會稍後清理髒資料。

            控制檔案系統的pdflush程式,在何時重新整理磁碟,以髒資料是否達到佔系統 空閒記憶體的百分比,pdflush開始將記憶體中的內容和檔案系統進行同步。比如說,當一個檔案在記憶體中進行修改,pdflush負責將它寫回硬碟.每當記憶體中的垃圾頁(dirty page)超過10%的時候,pdflush就會將這些頁面備份回硬碟.增大之會使用更多系統記憶體用於磁碟寫緩衝,也可以極大提高系統的寫效能。但是,當你需要持續、恆定的寫入場合時,應該降低其數值:

            推薦值:vm.dirty_background_ratio = 10

            情景1:減少Cache
            你可以針對要做的事情,來制定一個合適的值。
            在一些情況下,我們有快速的磁碟子系統,它們有自帶的帶備用電池的NVRAM caches,這時候把資料放在作業系統層面就顯得相對高風險了。所以我們希望系統更及時地往磁碟寫資料。
            可以在/etc/sysctl.conf中加入下面兩行,並執行"sysctl -p"
            vm.dirty_background_ratio = 5
            vm.dirty_ratio = 10
            這是虛擬機器的典型應用。不建議將它設定成0,畢竟有點後臺IO可以提升一些程式的效能。

            情景2:增加Cache
            在一些場景中增加Cache是有好處的。例如,資料不重要丟了也沒關係,而且有程式重複地讀寫一個檔案。

            允許更多的cache,你可以更多地在記憶體上進行讀寫,提高速度。
            vm.dirty_background_ratio = 50
            vm.dirty_ratio = 80
            有時候還會提高vm.dirty_expire_centisecs 這個引數的值,來允許髒資料更長時間地停留。

            情景3:增減兼有
            有時候系統需要應對突如其來的高峰資料,它可能會拖慢磁碟。(比如說,每個小時開始時進行的批次操作等)
            這個時候需要容許更多的髒資料存到記憶體,讓後臺程式慢慢地透過非同步方式將資料寫到磁碟當中。
            vm.dirty_background_ratio = 5
            vm.dirty_ratio = 80
            這個時候,後臺進行在髒資料達到5%時就開始非同步清理,但在80%之前系統不會強制同步寫磁碟。

            這樣可以使IO變得更加平滑。
            從/proc/vmstat, /proc/meminfo, /proc/sys/vm中可以獲得更多資訊來作出調整。


            參考文獻

            紅帽官網:
            CONFIGURING SYSTEM MEMORY CAPACITY
            https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/performance_tuning_guide/sect-red_hat_enterprise_linux-performance_tuning_guide-configuration_tools-configuring_system_memory_capacity#sect-Red_Hat_Enterprise_Linux-Performance_Tuning_Guide-Configuring_system_memory_capacity-Virtual_Memory_parameters

            Aerospike_Knowledge:
            How to tune the Linux kernel for memory performance
            https://discuss.aerospike.com/t/how-to-tune-the-linux-kernel-for-memory-performance/4195

            紅帽官網:
            Several "page allocation failure. order:1, mode:0x20" messages are seen on the console after upgrade to Red Hat Enterprise Linux 6.2


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

            相關文章