歡迎光臨
每天分享高質量文章

郭健:Linux記憶體管理系統引數配置之overcommit

一、前言

終於可以進入Linux kernel記憶體管理的世界了,但是從哪裡入手是一個問題,當面對一個複雜系統的時候,有時候不知道怎麼開始。遵守“一切以人為本”的原則,我最終選擇先從從userspace的視角來看內核的記憶體管理。最開始的系列文章選擇了vm執行引數這個主題。執行ls /proc/sys/vm的命令,你可以看到所有的vm執行引數,本文選擇了overcommit相關引數來介紹。

本文的程式碼來自4.0核心。

二、背景知識

要瞭解這類引數首先要理解什麼是committed virtual memory?使用版本管理工具的工程師都熟悉commit的含義,就是向程式碼倉庫提交自己更新的意思,對於這個場景,實際上就是各個行程提交自己的虛擬地址空間的請求。雖然我們總是宣稱每個行程都有自己獨立的地址空間,但素,這些地址空間都是虛擬地址,就像是鏡中花,水中月。當行程需要記憶體時(例如透過brk分配記憶體),行程從核心獲得的僅僅是一段虛擬地址的使用權,而不是實際的物理地址,行程並沒有獲得物理記憶體。實際的物理記憶體只有當行程真的去訪問新獲取的虛擬地址時,產生“缺頁”異常,從而進入分配實際物理地址的過程,也就是分配實際的page frame並建立page table。之後系統傳回產生異常的地址,重新執行記憶體訪問,一切好象沒有發生過。因此,看起來虛擬記憶體和物理記憶體的分配被分割開了,這是否意味著行程可以任意的申請虛擬地址空間呢?也不行,畢竟virtual memory需要physical memory做為支撐,如果分配了太多的virtual memory,和物理記憶體不成比例,對效能會有影響。對於這個狀況,我們稱之為overcommit。

三、引數介紹

1、overcommit_memory

overcommit_memory這個引數就是用來控制核心對overcommit的策略。該引數可以設定的值包括:

OVERCOMMIT_ALWAYS表示核心並不限制overcommit,無論行程們commit了多少的地址空間的申請,go ahead,do what you like,只不過後果需要您自己的負責。OVERCOMMIT_NEVER是另外的極端,永遠不要overcommit。OVERCOMMIT_GUESS的策略和其名字一樣,就是“你猜”,多麼調皮的設定啊(後面程式碼分析會進一步描述)。BTW,我不太喜歡這個引數的命名,更準確的命名應該類似vm_overcommit_policy什麼的,大概是歷史的原因,linux kernel一直都是保持了這個符號。

2、overcommit_kbytes 和overcommit_ration

OVERCOMMIT_ALWAYS可以很任性,總是允許出現overcommit現象,但是OVERCOMMIT_NEVER不行,這種策略下,系統不允許出現overcommit。不過要檢查overcommit,具體如何判斷呢,總得有個標準吧,這個標準可以從vm_commit_limit這個函式看出端倪:

overcommit的標準有兩個途徑來設定,一種是直接定義overcommit_kbytes,這時候標準值是overcommit_kbytes+total_swap_pages。什麼是total_swap_pages呢?這裡要稍微講一下關於頁面回收(page frame reclaim)機制。

就虛擬記憶體和物理記憶體的分配策略而言,inux kernel對虛擬地址空間的分配是比較寬鬆的(雖然有overcommit機制),但是,kernel對使用者空間的物理記憶體申請(建立使用者空間行程、使用者空間程式的malloc(就是堆的分配),使用者空間行程stack的分配等)是非常的吝嗇的(順便提及的是:記憶體管理模組對來自內核的記憶體申請是大方的,內核工程師的自豪感是否油然而生,呵呵~~),總是百般阻撓,直到最後一刻實在沒有辦法了才分配物理記憶體。這種機制其背後的思想是更好的使用記憶體,也就是說:在限定的物理記憶體資源下,可以儘量讓更多的使用者空間行程執行起來。如果讓物理地址和虛擬地址空間是一一對映的時候,那麼系統中的可以啟動行程數目必定是受限的,行程可以申請的記憶體數目也是受限的,你的程式不得不經常面記憶體分配失敗的issue。如果你想破解這個難題,就需要將一個較小的物理記憶體空間對映到一個較大的各個使用者行程組成的虛擬地址空間之上。怎麼辦,最簡單的方法就是“拆東牆補西牆”。感謝程式天生具備區域性性原理,可以讓內核有東牆可以拆,但是,拆東牆(swap out)也是技術活,不是所有的行程虛擬空間都可以拆。比如說程式的正文段的內容就是可以拆,因為這些記憶體中的內容有磁碟上的程式做支撐,當再次需要的時候(補西牆),可以從磁碟上的程式檔案中reload。不是所有的行程地址空間都是有file-backup的,堆、stack這些行程的虛擬地址段都是沒有磁碟檔案與之對應的,也就是傳說中的anonymous page。對於anonymous page,如果我們建立swap file或者swap device,那麼這些anonymous page也同樣可以被交換到磁碟,並且在需要的時候load進記憶體。

OK,我們回到total_swap_pages這個變數,它其實就是系統可以將anonymous page交換到磁碟的大小,如果我們建立32MB的swap file或者swap device,那麼total_swap_pages就是(32M/page size)。

overcommit的標準的另外一個標準(在overcommit_kbytes設定為0的時候使用)是和系統可以使用的page frame相關。並不是系統中的物理記憶體有多少,totalram_pages就有多少,實際上很多的page是不能使用的,例如linux kernel本身的正文段,資料段等就不能計入totalram_pages,還有一些系統reserve的page也不算數,最終totalram_pages實際上就是系統可以管理分配的總記憶體數目。overcommit_ratio是一個百分比的數字,50表示可以使用50%的totalram_pages,當然還有考慮total_swap_pages的數目,上文已經描述。

還有一個小細節就是和huge page相關的,傳統的4K的page和huge page的選擇也是一個平衡問題。normal page可以靈活的管理記憶體段,浪費少。但是不適合大段虛擬記憶體段的管理(因為要建立大量的頁表,TLB side有限,因此會導致TLB miss,影響效能),huge page和normal page相反。內核可以同時支援這兩種機制,不過是分開管理的。我們本節描述的引數都是和normal page相關的,因此在計算allowed page的時候要減去hugetlb_total_pages。

3、admin_reserve_kbytes和user_reserve_kbytes

做任何事情都要留有餘地,不要把自己逼到絕境。這兩個引數就是防止記憶體管理模組把自己逼到絕境。

上面我們提到拆東牆補西牆的機制,但是這種機制在某些情況下其實也不能正常的運作。例如行程A在訪問自己的記憶體的時候,出現page fault,透過scan,將其他行程(B、C、D…)的“東牆”拆掉,分配給行程A,以便讓A可以正常執行。需要註意的是,“拆東牆”不是那麼簡單的事情,有可能需要進行磁碟I/O操作(比如:將dirty的page cache flush到磁碟)。但是,系統很快排程到了B行程,而B行程立刻需要剛剛拆除的東牆,怎麼辦?B行程立刻需要分配物理記憶體,如果沒有free memory,這時候也只能啟動scan過程,繼續找新的東牆。在極端的情況下,很有可能把剛剛補好的西牆拆除,這時候,整個系統的效能就會顯著的下降,有的時候,使用者點選一個button,很可能半天才能響應。

面對這樣的情況,使用者當然想恢復,例如kill那個吞噬大量記憶體的行程。這個操作也需要記憶體(需要fork行程),因此,為了能夠讓使用者順利逃脫絕境,系統會保留user_reserve_kbytes的記憶體。

對於支援多使用者的GNU/linux系統而言,恢復系統可能需要root用來來完成,這時候需要保留一定的記憶體來支援root使用者的登入操作,支援root進行trouble shooting(使用ps,top等命令),找到那個鬧事的行程並kill掉它。這些為root使用者操作而保留的memory定義在admin_reserve_kbytes引數中。

四、程式碼分析

使用者空間行程在使用記憶體的時候(更準確的說是分配虛擬記憶體,其實使用者空間根本無法觸及物理記憶體的分配,那是內核的領域),核心都會呼叫__vm_enough_memory函式來驗證是否可以允許分配這段虛擬記憶體,程式碼如下:

int __vm_enough_memory(struct mm_struct *mm, long pages, int cap_sys_admin) {  
    …… 
    if (sysctl_overcommit_memory == OVERCOMMIT_ALWAYS) ----(1) 
        return 0;

    if (sysctl_overcommit_memory == OVERCOMMIT_GUESS) { 
        free = global_page_state(NR_FREE_PAGES); 
        free += global_page_state(NR_FILE_PAGES);  
        free -= global_page_state(NR_SHMEM);

        free += get_nr_swap_pages();  
        free += global_page_state(NR_SLAB_RECLAIMABLE); ------(2)


        if (free <= totalreserve_pages) ------------------(3) 
            goto error; 
        else 
            free -= totalreserve_pages;


        if (!cap_sys_admin) ----------------------(4) 
            free -= sysctl_admin_reserve_kbytes >> (PAGE_SHIFT – 10);

        if (free > pages) ------------------------(5) 
            return 0;

        goto error; 
    }

    allowed = vm_commit_limit(); -------------------(6) 
    if (!cap_sys_admin) 
        allowed -= sysctl_admin_reserve_kbytes >> (PAGE_SHIFT – 10); --參考(4)的解釋


    if (mm) { ----------------------------(7) 
        reserve = sysctl_user_reserve_kbytes >> (PAGE_SHIFT – 10); 
        allowed -= min_t(long, mm->total_vm / 32, reserve); 
    }

    if (percpu_counter_read_positive(&vm;_committed_as) < allowed) ----(8) 
        return 0;

…… 
}

(1)OVERCOMMIT_ALWAYS奏是辣麼自由,隨你overcommit,只要你喜歡。return 0表示目前有充足的virtual memory資源。


(2)OVERCOMMIT_GUESS其實就是讓核心自己根據當前的狀況進行判斷,因此首先進入收集資訊階段,看看系統有多少free page frame(NR_FREE_PAGES標記,位於Buddy system的free list中),這些是優質資源,沒有任何的開銷就可以使用。NR_FILE_PAGES是page cache使用的page frame,主要是使用者空間行程讀寫檔案造成的。這些cache都是為了加快系統效能而增加的,因此,如果直接操作到磁碟,本質上這些page cache都是free的。不過,這裡有一個特例就是NR_SHMEM,它主要是用於行程間的share memory機制,這些shmem page frame不能認為是free的,因此要減去。get_nr_swap_pages函式傳回swap file或者swap device上空閑的“page frame”數目。本質上,swap file或者swap device上的磁碟空間都是給anonymous page做騰挪之用,其實這裡的“page frame”不是真的page frame,我們稱之swap page好了。get_nr_swap_pages函式傳回了free swap page的數目。這裡把free swap page的數目也計入free主要是因為可以把使用中的page frame swap out到free swap page上,因此也算是free page,雖然開銷大了一點。至於NR_SLAB_RECLAIMABLE,那就更應該計入free page了,因為slab物件都已經標註自己的reclaim的了,當然是free page了。


(3)要解釋totalreserve_pages需要太長的篇幅,我們這裡略過,但這是一個能讓系統執行需要預留的page frame的數目,因此我們要從減去totalreserve_pages。如果當前free page數目小於totalreserve_pages,那麼當然拒絕vm的申請。


(4)如果是普通的行程,那麼還需要保留admin_reserve_kbytes的free page,以便在出問題的時候可以讓root使用者可以登入併進行恢復操作。


(5)最關鍵的來了,比對本次申請virtual memory的page數目和當前“free”(之所以加引號表示並真正free page frame數目)的資料,如果在保留了足夠的page frame之後,還有足夠的page可以滿足本次分配,那麼就批准本次vm的分配。


(6)從這裡開始,進入OVERCOMMIT_NEVER的處理。從vm_commit_limit函式中可以獲取一個基本的判斷overcommit的標準,當然要根據具體情況進行調整,例如說admin_reserve_kbytes。


(7)如果是使用者空間的行程,我們還要為使用者能夠從絕境中恢復而保留一些page frame,具體保留多少需要考量兩個因素,一個是單一行程的total virtual memory,一個使用者設定的執行時引數user_reserve_kbytes。更具體的考量因素可以參考

https://lkml.org/lkml/2013/3/18/812,這裡就不贅述了。


(8)allowed變數儲存了判斷overcommit的上限,vm_committed_as儲存了當前系統中已經申請(包括本次)的virtual memory的數目。如果大於這個上限就判斷overcommit,本次申請virtual memory失敗。


五、參考文獻

1、Documentation/vm/overcommit-accounting

2、Documentation/sysctl/vm.txt


文字來自:

http://www.wowotech.net/memory_management/overcommit.html

原標題:《Linux vm執行引數之(一):overcommit相關的引數》

(完)

贊(0)

分享創造快樂