linux記憶體回收機制

531968912發表於2017-12-19

1 回收哪些頁面

Page cache;

使用者地址空間的記憶體對映頁面;

Slab快取:如dentryinode cache

匿名頁:程式堆疊和mmap匿名對映記憶體區;回收前先換置到swap

 

 

2 何時回收

Kswapd定期喚醒:當系統空閒記憶體小於閾值則進行頁面回收;

直接頁面回收:假設作業系統需要透過夥伴系統為使用者程式分配一大塊記憶體,或者需要建立一個很大的緩衝區,而當時系統中的記憶體沒有辦法提供足夠多的實體記憶體以滿足這種記憶體請求,這時候,作業系統就必須儘快進行頁面回收操作;

OS嘗試記憶體回收後仍無法獲取足夠頁面,則呼叫find_bad_process並進行OOM kill

 

 

3 如何回收

基於LRU演算法;每個zone維護兩個LRU連結串列

struct zone {

    ……

          spinlock_t                   lru_lock;          

          struct list_head   active_list;

          struct list_head     inactive_list;

          unsigned long                  nr_active;

          unsigned long                  nr_inactive;

    ……

        

 }

PG_active/PG_referenced用於標識頁面活躍度,前者標識頁面時活躍的;後者表示頁面最近是否被訪問過,每訪問一次便會置位;

注:假如只是用一個標誌符,在頁面被訪問時,置位該標誌符,之後該頁面一直處於活躍狀態,如果作業系統不清除該標誌位,那麼即使之後很長一段時間內該頁面都沒有或很少被訪問過,該頁面也還是處於活躍狀態。為了能夠有效清除該標誌位,需要有定時器的支援以便於在超時時間之後該標誌位可以自動被清除。然而,很多 Linux 支援的體系結構並不能提供這樣的硬體支援,所以 Linux 中使用兩個標誌符來判斷頁面的活躍程度。

 

Linux依據這兩個欄位將pageactive_listinactive_list之間移動;

注:1 表示函式 mark_page_accessed()2 表示函式 page_referenced()3 表示函式 activate_page()4 表示函式 shrink_active_list()


 

不管是kswapd還是直接頁面回收,最終都呼叫shrink_slabshrink_zone

直接頁面回收:反覆呼叫這兩個函式,若特定迴圈次數內沒能成功釋放Npage,則呼叫OOM killer

Kswapd:對每個zone都呼叫shrink_zone()

 

3.1 Shrink_slab原理

先向作業系統核心註冊 shrinker函式,會在記憶體較少的時候主動釋放一些該磁碟快取佔用的空間。

函式 shrink_slab() 會遍歷 shrinker 連結串列,從而對所有註冊了 shrinker 函式的磁碟快取進行處理。

註冊 shrinker 是透過函式 set_shrinker() 實現的,解除 shrinker 註冊是透過函式 remove_shrinker() 實現的。當前,Linux 作業系統中主要的 shrinker 函式有如下幾種:

shrink_dcache_memory():該 shrinker 函式負責 dentry 快取。

shrink_icache_memory():該 shrinker 函式負責 inode 快取。

mb_cache_shrink_fn():該 shrinker 函式負責用於檔案系統後設資料的快取。

 

3.2 Shrink_zone原理

1 透過shrink_active_list()將頁面從active移到inactive list

2 呼叫shrink_inactive_list()inactive list的頁放入臨時連結串列,最終呼叫shrink_page_list()回收


 

3.2.1 Swappiness的意義

上文提到的shrink_zone() 會呼叫 shrink_lruvec(),而active/inactive list又各分為anon匿名頁和file cache對映頁連結串列,總計4LRU

swappines只針對anon page,即便其為0也有可能執行swap

vmscan.c中的get_scan_coun()

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

 程式碼:

       if (!sc->may_swap || (get_nr_swap_pages() <= 0)) {

                scan_balance = SCAN_FILE;

                goto out;

        }

 

2. 如果當前進行的不是全域性頁回收(cgroup資源限額引起的頁回收),並且swappiness設為0,則不進行匿名頁連結串列掃描,

程式碼:

        if (!global_reclaim(sc) && !vmscan_swappiness(sc)) {

                scan_balance = SCAN_FILE;

                goto out;

        }

 

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

程式碼:

        if (!sc->priority && vmscan_swappiness(sc)) {

                scan_balance = SCAN_EQUAL;

                goto out;

        }

 

4. 如果是全域性頁回收,並且當前空閒記憶體和所有file based連結串列page數目的加和都小於系統的high watermark,則必須進行匿名頁回收,則必然會發生swap

程式碼:

        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;

        }

注:每個zonemin/low/high 3個值,而high watermark指的是最後一個,這3個值依據vm.min_free_kbytes設定

 

 

3.2.2 反向對映

回收物理頁前需要解除所有關聯該頁的頁表項,而共享記憶體中的頁可能被多個程式引用,因此需要一種機制快速定位頁表項;

2.4要遍歷所有程式;

2.5引入反向對映,每個物理頁維護一個頁表項鍊表;

2.6引入基於物件的反向對映,每個物理頁設定一個反向對映連結串列,連結串列節點為vm_area_struct結構,其透過mm_struct找到pgd進而找到相應頁表項;

struct page {

          atomic_t _mapcount; --初始值是 -1,每增加一個使用者,該計數器加 1

          union {

        ……

             struct {

                   ……           

                    struct address_space *mapping; --如果最低位置位,為指向 anon_vma 結構(用於匿名頁面)的指標;否則為 address_space 指標(用於基於檔案對映的頁面)。

             };

        ……

 };

對於匿名頁面來說,頁面雖然可以是共享的,但是一般情況下,共享匿名頁面的使用者的數目不會很多;而對於基於檔案對映的頁面來說,共享頁面的使用者的數目可能會非常多,使用優先順序搜尋樹這種結構可以更加快速地定位那些引用了該頁面的虛擬記憶體區域。作業系統會為每一個檔案都建立一個優先順序搜尋樹,其根節點可以透過結構 address_space 中的 i_mmap 欄位獲取。


 

 

 

注:LRU快取

頁面根據其活躍程度會在 active 連結串列和 inactive 連結串列之間來回移動,如果要將某個頁面插入到這兩個連結串列中去,必須要透過自旋鎖以保證對連結串列的併發訪問操作不會出錯。為了降低鎖的競爭,Linux 提供了一種特殊的快取:LRU 快取,用以批次地向 LRU 連結串列中快速地新增頁面。有了 LRU 快取之後,新頁不會被馬上新增到相應的連結串列上去,而是先被放到一個緩衝區中去,當該緩衝區快取了足夠多的頁面之後,緩衝區中的頁面才會被一次性地全部新增到相應的 LRU 連結串列中去。

LRU 快取用到了 pagevec 結構,如下所示 :

 struct pagevec {

          unsigned long nr;

          unsigned long cold;

          struct page *pages[PAGEVEC_SIZE];

 };

lru_cache_add() lru_cache_add_active()。前者用於延遲將頁面新增到 inactive 連結串列上去,後者用於延遲將頁面新增到 active 連結串列上去。這兩個函式都會將要移動的頁面先放到頁向量 pagevec 中,當 pagevec 滿了(已經裝了 14 個頁面的描述符指標),pagevec 結構中的所有頁面才會被一次性地移動到相應的連結串列上去。

 

 

 

參考資料

http://www.ibm.com/developerworks/cn/linux/l-cn-pagerecycle/index.html?ca=dat

 

 

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

相關文章