linux記憶體回收機制
1 回收哪些頁面
Page cache;
使用者地址空間的記憶體對映頁面;
Slab快取:如dentry和inode 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依據這兩個欄位將page在active_list和inactive_list之間移動;
注:1 表示函式 mark_page_accessed(),2 表示函式 page_referenced(),3 表示函式 activate_page(),4 表示函式 shrink_active_list()
不管是kswapd還是直接頁面回收,最終都呼叫shrink_slab和shrink_zone;
直接頁面回收:反覆呼叫這兩個函式,若特定迴圈次數內沒能成功釋放N個page,則呼叫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對映頁連結串列,總計4個LRU;
而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,且swappiness非0,則可能會進行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;
}
注:每個zone有min/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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- js記憶體回收機制JS記憶體
- jvm:記憶體模型、記憶體分配及GC垃圾回收機制JVM記憶體模型GC
- JVM記憶體回收機制——哪些記憶體需要被回收(JVM學習系列2)JVM記憶體
- Java記憶體模型,垃圾回收機制,常用記憶體命令及工具Java記憶體模型
- javascript的垃圾回收機制和記憶體管理JavaScript記憶體
- V8記憶體管理及垃圾回收機制記憶體
- Redis的記憶體回收機制和記憶體過期淘汰策略詳解Redis記憶體
- [譯] 通過垃圾回收機制理解 JavaScript 記憶體管理JavaScript記憶體
- js垃圾回收機制和引起記憶體洩漏的操作JS記憶體
- Node的垃圾回收機制與記憶體溢位捕獲(上)記憶體溢位
- Javascrip高程中的垃圾記憶體回收制(6)Java記憶體
- linux記憶體管理(十)- 頁面回收(二)Linux記憶體
- Linux核心記憶體保護機制:aslr和canaryLinux記憶體
- java基礎(一):談談java記憶體管理與垃圾回收機制Java記憶體
- 深入理解JVM記憶體回收機制(不包含垃圾收集器)JVM記憶體
- 你必須瞭解的java記憶體管理機制(四)-垃圾回收Java記憶體
- JVM記憶體分配機制與回收策略選擇-JVM學習筆記(2)JVM記憶體筆記
- Java記憶體管理機制Java記憶體
- jvm記憶體管理機制JVM記憶體
- javaScript 記憶體管理機制JavaScript記憶體
- 剖析 Python 面試知識點(二)- 記憶體管理和垃圾回收機制Python面試記憶體
- 記憶體回收介紹記憶體
- GVM回收機制-筆記筆記
- [譯] JavaScript如何工作:垃圾回收機制 + 常見的4種記憶體洩漏JavaScript記憶體
- Python如何管理記憶體?記憶體分配機制是什麼?Python記憶體
- 淺析java記憶體管理機制Java記憶體
- 關於JavaScript的記憶體機制JavaScript記憶體
- Java程式執行記憶體機制Java記憶體
- 記憶體管理機制的發展記憶體
- 【記憶體管理】頁面分配機制記憶體
- Redis 記憶體淘汰機制詳解Redis記憶體
- JS垃圾回收機制筆記JS筆記
- Java的記憶體管理機制之記憶體區域劃分Java記憶體
- Linux核心筆記002 - i386 的頁式記憶體管理機制Linux筆記記憶體
- Java虛擬機器記憶體分配與回收策略Java虛擬機記憶體
- JVM垃圾回收器、記憶體分配與回收策略JVM記憶體
- JS高程中的垃圾回收機制與常見記憶體洩露的解決方法JS記憶體洩露
- Node記憶體限制和垃圾回收記憶體
- Java記憶體管理 -JVM 垃圾回收Java記憶體JVM