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到鄰居子系統