1. 程式人生 > >Linux記憶體回收之LRU連結串列和第二次機會法

Linux記憶體回收之LRU連結串列和第二次機會法

一  LRU回收演算法

記憶體回收的核心是圍繞LRU連結串列來進行操作,Linux核心實現了5種LRU連結串列型別

1. 不活躍匿名頁錶鏈表(LRU_INACTIVE_ANON)//shmem

2. 活躍匿名頁錶鏈表(LRU_ACTIVE_ANON)//

3. 不活躍檔案對映頁錶鏈表(LRU_INACTIVE_FILE)

4. 活躍檔案對映頁錶鏈表(LRU_ACTIVE_FILE)

5. 不可回收頁錶鏈表(LRU_UNEVICTABL)

核心巨集定義:

enum lru_list {
    LRU_INACTIVE_ANON = LRU_BASE,
    LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE,
    LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE,
    LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE,
    LRU_UNEVICTABLE,
    NR_LRU_LISTS
};

LRU演算法遵循先進先出(FIFO)原則

1.新分配的page連結到不活躍或活躍連結串列頭部

 2. 從活躍連結串列的尾部摘取頁面連結到不活躍連結串列頭部

3. 從不活躍連結串列尾部摘取頁表進行記憶體回收.

每個zone都有一個lruvec結構體

struct lruvec {
    struct list_head lists[NR_LRU_LISTS];//LRU連結串列
    struct zone_reclaim_stat reclaim_stat;
};

  整個記憶體回收,就是讓頁面在活躍連結串列/不活躍連結串列和Buddy system之間流動.

匿名頁面回收時,如果沒有使用者,頁面直接被釋放,如果有程序使用,先交換到swap分割槽,然後再釋放,

檔案對映頁面只能回寫磁碟,然後被釋放. 

二,第二次機會法

   單純基於LRU演算法,INACTIVE LIST表頭的page頁面最容易被回收,這樣可能存在頻繁使用的page被換出去,為了防止這種情況,核心開發者提交了第二次機會法補丁,也就是標記頁面是否頻繁被引用/訪問,如果頻繁被使用,會多一次機會停留在不活躍連結串列,甚至還有機會遷移到活躍連結串列. 為了實現第二次機會法,核心定義了三個標誌位

PG_active, PG_referenced,L_PTE_YOUNG,以及相關函式

mark_page_accessed: 標記頁面訪問

page_referenced: 獲取page引用計數

page_check_references:跟page引用pte計數和訪問計數,決定頁面是否可被回收.

void mark_page_accessed(struct page *page)
{
    if (!PageActive(page) && !PageUnevictable(page) &&
            PageReferenced(page)) {

        if (PageLRU(page))
            activate_page(page);
        else
            __lru_cache_activate_page(page);
        ClearPageReferenced(page);
        
    } else if (!PageReferenced(page)) {
        SetPageReferenced(page);
    }
    
}
規則如下:
 inactive,unreferenced    ->    inactive,referenced
 inactive,referenced    ->    active,unreferenced
 active,unreferenced    ->    active,referenced

page_referenced(page)主要通過RMAP機制,計算引用了這個page,且最近被cpu訪問過的pte數量(L_PTE_YOUNG置1),然後清除L_PTE_YOUNG,當pte再次被訪問時,會產生缺頁異常,handle_pte_fault呼叫pte_mkyoung再次標記L_PTE_YOUNG標誌位

page_check_references:決定頁面是否可以被回收.

呼叫關係:shrink_inactive_list->shrink_page_list->page_check_references:

static enum page_references page_check_references(struct page *page,
                          struct scan_control *sc)
{
    int referenced_ptes, referenced_page;
    
    /*計算最近被訪問過pte數量 */
    referenced_ptes = page_referenced(page, 1, sc->target_mem_cgroup,
                      &vm_flags);
/*讀取page引用標誌位,並清楚標誌位 */
    referenced_page = TestClearPageReferenced(page);

    /*如果有引用pte */
    if (referenced_ptes) {
        /*匿名頁面,重新連結到active list */
        if (PageSwapBacked(page))
            return PAGEREF_ACTIVATE;
        /*設定Referenced標誌位 */
        SetPageReferenced(page);
        /*有引用標誌位,或者共享的page cache,重新連結到active list */
        /*referenced_ptes > 1,這裡可以過濾大量只讀一次的檔案頁面遷移到active list  */
                   if (referenced_page || referenced_ptes > 1)
            return PAGEREF_ACTIVATE;

        /*可執行檔案頁面,重新連線到active list  */
        if (vm_flags & VM_EXEC)
            return PAGEREF_ACTIVATE;
        /*如果只有引用pte,則繼續保留在inactive list ,這就是所謂的第二次機會法??? */
        return PAGEREF_KEEP;
    }
    /*頁面沒有引用pte,則可以嘗試回收 */
    if (referenced_page && !PageSwapBacked(page))
        return PAGEREF_RECLAIM_CLEAN;
    /*可回收最佳物件page */
    return PAGEREF_RECLAIM;
}

page_check_references函式總結如下:

1. 有引用pte時 

  1.1 匿名頁面直接遷移回活躍連結串列

  1.2 可執行檔案頁面,直接遷移回活躍連結串列

        1.3 共享page cache,最近第二次訪問頁面

2.沒有引用pte,則是回收的最佳候選者.

三, 第二次機會法舉例

3.1 直接讀取檔案(read)

使用者程序直接read檔案時,核心呼叫了vfs_read,

第一次讀: vfs_read->do_generic_file_read->page_cache_sync_readhead(預讀檔案)->read_pages(分配page)->add_to_page_cache_list(清PG_active,新增到inactive list),

                                     do_generic_file_read->mark_page_accessed(置標誌位PG_referenced)

第一次read,返回後,page cache處於inactive list,且PG_referenced=1,PG_active=0,因為是直接讀取,這個page沒有對應的使用者空間pte(也就是沒有進行對映),如果此時頁面回收對這個page呼叫page_check_references進行檢查,會返回PAGEREF_RECLAIM_CLEAN

第二次讀: vfs_read->do_generic_file_read->mark_page_accessed,因為PG_referenced=1,則標記PG_active,並新增到active list (這裡體現第二次機會法)

3.2 mmap方式讀取檔案(ext4)

    第一次讀時,建立mmap對映->ext4_file_mmap->filemap_fault->ext4_readpages->add_to_page_cache_lru(清PG_active,並新增到inactive list )

第二次讀寫: 後面讀取,就跟操作記憶體一樣,

記憶體回收第一次對page呼叫page_check_references時,發現有引用pte,PG_referenced=0,則置PG_referenced=1,返回PAGEREF_KEEP,繼續保留在不活躍連結串列(這裡也體現第二次機會法).

記憶體回收第二次對page呼叫page_check_references時,

     1. 如果第一次呼叫page_check_references後,又通過pte訪問了對應的page(L_PTE_YOUNG為1),則直接新增到ACTIVE LIST(這裡體現了第二次機會法)

    2. 如果第一次呼叫page_check_references後,沒有在對page進行讀取,則返回PAGEREF_RECLAIM_CLEAN

這裡也可以看出mark_page_accessed函式是用於直接讀取檔案場景,而page_references用於mmap場景.

 

四 匿名/檔案頁面的產生

通過新增到LRU連結串列操作,可以知道哪些情況下會引數匿名頁面和檔案對映頁面

4.1 匿名頁面的產生

swap read和shmem通訊時,產生不活躍匿名頁面,呼叫lru_cache_add_anon新增到inactive lru

下面幾種情況引數活躍頁面,呼叫lru_cache_add_active_or_unevictable新增到active lru

do_wp_page:寫時複製缺頁異常
do_swap_page: KSM缺頁異常

do_anonymous_page:malloc/mmap匿名缺頁異常

4.2 檔案對映頁面產生

檔案頁面產生時,都是新增到不活躍LRU,主要通過檔案直接read,和mmap操作產生page cache

add_to_page_cache_lru:檔案系統預讀時呼叫
lru_cache_add_file:cifs檔案系統條用

五,page 的lru連結串列遷移

總的遷移圖如下,

5.1 鄰居子系統->活躍連結串列->臨時連結串列->不活躍連結串列

產生邏輯: 使用者空間malloc分配記憶體,產生匿名缺頁中斷(do_anonymous_page)進入鄰居子系統分配記憶體(alloc_page)->新增到活躍連結串列(lru_cache_add_active_or_unevictable),當kswap執行緒掃描活躍連結串列時(shrink_active_list),會把頁面從活躍連結串列分離到臨時連結串列(isolate_lru_pages),然後把臨時連結串列的page,遷移到不活躍連結串列.

5.2 鄰居子系統->活躍連結串列->臨時連結串列->鄰居子系統

當活躍連結串列的page遷移到臨時連結串列後,從臨時連結串列遷移到不活躍連結串列會減少page引用計數,如果page計數為0,則直接釋放到鄰居子系統.

5.3 鄰居子系統->不活躍連結串列->臨時連結串列->活躍連結串列

使用者空間mmap一個檔案讀操作時,產生缺頁異常,從鄰居子系統分配記憶體,然後新增到不活躍連結串列(add_to_page_cache_lru),

kswap執行緒掃描不活躍連結串列時(shrink_inactive_list),先分離頁面到臨時連結串列(isolate_lru_pages),然後通過page_check_references函式,判斷頁面是否要遷移到活躍連結串列(有pte引用計數,PG_refeferencs)

5.4 鄰居子系統->不活躍連結串列->臨時連結串列->swap/磁碟->鄰居子系統

kswap執行緒掃描不活躍連結串列時(shrink_inactive_list),先分離頁面到臨時連結串列(isolate_lru_pages),然後通過page_check_references函式,判斷頁面是否能夠被回收,如果能夠被回收,先把page的資料回寫writeback,或swap出去.然後再釋放page到鄰居子系統