您好,登錄后才能下訂單哦!
1 回收哪些頁(yè)面
Page cache;
用戶地址空間的內(nèi)存映射頁(yè)面;
Slab緩存:如dentry和inode cache;
匿名頁(yè):進(jìn)程堆棧和mmap匿名映射內(nèi)存區(qū);回收前先換置到swap;
2 何時(shí)回收
Kswapd定期喚醒:當(dāng)系統(tǒng)空閑內(nèi)存小于閾值則進(jìn)行頁(yè)面回收;
直接頁(yè)面回收:假設(shè)操作系統(tǒng)需要通過(guò)伙伴系統(tǒng)為用戶進(jìn)程分配一大塊內(nèi)存,或者需要?jiǎng)?chuàng)建一個(gè)很大的緩沖區(qū),而當(dāng)時(shí)系統(tǒng)中的內(nèi)存沒(méi)有辦法提供足夠多的物理內(nèi)存以滿足這種內(nèi)存請(qǐng)求,這時(shí)候,操作系統(tǒng)就必須盡快進(jìn)行頁(yè)面回收操作;
OS嘗試內(nèi)存回收后仍無(wú)法獲取足夠頁(yè)面,則調(diào)用find_bad_process并進(jìn)行OOM kill;
3 如何回收
基于LRU算法;每個(gè)zone維護(hù)兩個(gè)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用于標(biāo)識(shí)頁(yè)面活躍度,前者標(biāo)識(shí)頁(yè)面時(shí)活躍的;后者表示頁(yè)面最近是否被訪問(wèn)過(guò),每訪問(wèn)一次便會(huì)置位;
注:假如只是用一個(gè)標(biāo)志符,在頁(yè)面被訪問(wèn)時(shí),置位該標(biāo)志符,之后該頁(yè)面一直處于活躍狀態(tài),如果操作系統(tǒng)不清除該標(biāo)志位,那么即使之后很長(zhǎng)一段時(shí)間內(nèi)該頁(yè)面都沒(méi)有或很少被訪問(wèn)過(guò),該頁(yè)面也還是處于活躍狀態(tài)。為了能夠有效清除該標(biāo)志位,需要有定時(shí)器的支持以便于在超時(shí)時(shí)間之后該標(biāo)志位可以自動(dòng)被清除。然而,很多 Linux 支持的體系結(jié)構(gòu)并不能提供這樣的硬件支持,所以 Linux 中使用兩個(gè)標(biāo)志符來(lái)判斷頁(yè)面的活躍程度。
Linux依據(jù)這兩個(gè)字段將page在active_list和inactive_list之間移動(dòng);
注:1 表示函數(shù) mark_page_accessed(),2 表示函數(shù) page_referenced(),3 表示函數(shù) activate_page(),4 表示函數(shù) shrink_active_list()
不管是kswapd還是直接頁(yè)面回收,最終都調(diào)用shrink_slab和shrink_zone;
直接頁(yè)面回收:反復(fù)調(diào)用這兩個(gè)函數(shù),若特定循環(huán)次數(shù)內(nèi)沒(méi)能成功釋放N個(gè)page,則調(diào)用OOM killer;
Kswapd:對(duì)每個(gè)zone都調(diào)用shrink_zone();
3.1 Shrink_slab原理
先向操作系統(tǒng)內(nèi)核注冊(cè) shrinker函數(shù),會(huì)在內(nèi)存較少的時(shí)候主動(dòng)釋放一些該磁盤緩存占用的空間。
函數(shù) shrink_slab() 會(huì)遍歷 shrinker 鏈表,從而對(duì)所有注冊(cè)了 shrinker 函數(shù)的磁盤緩存進(jìn)行處理。
注冊(cè) shrinker 是通過(guò)函數(shù) set_shrinker() 實(shí)現(xiàn)的,解除 shrinker 注冊(cè)是通過(guò)函數(shù) remove_shrinker() 實(shí)現(xiàn)的。當(dāng)前,Linux 操作系統(tǒng)中主要的 shrinker 函數(shù)有如下幾種:
shrink_dcache_memory():該 shrinker 函數(shù)負(fù)責(zé) dentry 緩存。
shrink_icache_memory():該 shrinker 函數(shù)負(fù)責(zé) inode 緩存。
mb_cache_shrink_fn():該 shrinker 函數(shù)負(fù)責(zé)用于文件系統(tǒng)元數(shù)據(jù)的緩存。
3.2 Shrink_zone原理
1 通過(guò)shrink_active_list()將頁(yè)面從active移到inactive list;
2 調(diào)用shrink_inactive_list()將inactive
list的頁(yè)放入臨時(shí)鏈表,最終調(diào)用shrink_page_list()回收
3.2.1 Swappiness的意義
上文提到的shrink_zone() 會(huì)調(diào)用 shrink_lruvec(),而active/inactive list又各分為anon匿名頁(yè)和file cache映射頁(yè)鏈表,總計(jì)4個(gè)LRU;
而swappines只針對(duì)anon page,即便其為0也有可能執(zhí)行swap。
vmscan.c中的get_scan_coun()
1. 首先如果系統(tǒng)禁用了swap或者沒(méi)有swap空間,則只掃描file based的鏈表,即不進(jìn)行匿名頁(yè)鏈表掃描
代碼:
if (!sc->may_swap || (get_nr_swap_pages() <= 0)) {
scan_balance = SCAN_FILE;
goto out;
}
2. 如果當(dāng)前進(jìn)行的不是全局頁(yè)回收(cgroup資源限額引起的頁(yè)回收),并且swappiness設(shè)為0,則不進(jìn)行匿名頁(yè)鏈表掃描,
代碼:
if (!global_reclaim(sc) && !vmscan_swappiness(sc)) {
scan_balance = SCAN_FILE;
goto out;
}
3. 如果進(jìn)行鏈表掃描前設(shè)置的priority(這個(gè)值決定掃描多少分之一的鏈表元素)為0,且swappiness非0,則可能會(huì)進(jìn)行swap
代碼:
if (!sc->priority && vmscan_swappiness(sc)) {
scan_balance = SCAN_EQUAL;
goto out;
}
4. 如果是全局頁(yè)回收,并且當(dāng)前空閑內(nèi)存和所有file based鏈表page數(shù)目的加和都小于系統(tǒng)的high watermark,則必須進(jìn)行匿名頁(yè)回收,則必然會(huì)發(fā)生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. 如果系統(tǒng)inactive file鏈表比較充足,則不考慮進(jìn)行匿名頁(yè)的回收,即不進(jìn)行swap
代碼:
if (!inactive_file_is_low(lruvec)) {
scan_balance = SCAN_FILE;
goto out;
}
注:每個(gè)zone有min/low/high 3個(gè)值,而high watermark指的是最后一個(gè),這3個(gè)值依據(jù)vm.min_free_kbytes設(shè)置
3.2.2 反向映射
回收物理頁(yè)前需要解除所有關(guān)聯(lián)該頁(yè)的頁(yè)表項(xiàng),而共享內(nèi)存中的頁(yè)可能被多個(gè)進(jìn)程引用,因此需要一種機(jī)制快速定位頁(yè)表項(xiàng);
2.4要遍歷所有進(jìn)程;
2.5引入反向映射,每個(gè)物理頁(yè)維護(hù)一個(gè)頁(yè)表項(xiàng)鏈表;
2.6引入基于對(duì)象的反向映射,每個(gè)物理頁(yè)設(shè)置一個(gè)反向映射鏈表,鏈表節(jié)點(diǎn)為vm_area_struct結(jié)構(gòu),其通過(guò)mm_struct找到pgd進(jìn)而找到相應(yīng)頁(yè)表項(xiàng);
struct page {
atomic_t _mapcount; --初始值是 -1,每增加一個(gè)使用者,該計(jì)數(shù)器加 1
union {
……
struct {
……
struct address_space *mapping; --如果最低位置位,為指向 anon_vma 結(jié)構(gòu)(用于匿名頁(yè)面)的指針;否則為 address_space 指針(用于基于文件映射的頁(yè)面)。
};
……
};
對(duì)于匿名頁(yè)面來(lái)說(shuō),頁(yè)面雖然可以是共享的,但是一般情況下,共享匿名頁(yè)面的使用者的數(shù)目不會(huì)很多;而對(duì)于基于文件映射的頁(yè)面來(lái)說(shuō),共享頁(yè)面的使用者的數(shù)目可能會(huì)非常多,使用優(yōu)先級(jí)搜索樹這種結(jié)構(gòu)可以更加快速地定位那些引用了該頁(yè)面的虛擬內(nèi)存區(qū)域。操作系統(tǒng)會(huì)為每一個(gè)文件都建立一個(gè)優(yōu)先級(jí)搜索樹,其根節(jié)點(diǎn)可以通過(guò)結(jié)構(gòu) address_space 中的 i_mmap 字段獲取。
注:LRU緩存
頁(yè)面根據(jù)其活躍程度會(huì)在 active 鏈表和 inactive 鏈表之間來(lái)回移動(dòng),如果要將某個(gè)頁(yè)面插入到這兩個(gè)鏈表中去,必須要通過(guò)自旋鎖以保證對(duì)鏈表的并發(fā)訪問(wèn)操作不會(huì)出錯(cuò)。為了降低鎖的競(jìng)爭(zhēng),Linux 提供了一種特殊的緩存:LRU 緩存,用以批量地向 LRU 鏈表中快速地添加頁(yè)面。有了 LRU 緩存之后,新頁(yè)不會(huì)被馬上添加到相應(yīng)的鏈表上去,而是先被放到一個(gè)緩沖區(qū)中去,當(dāng)該緩沖區(qū)緩存了足夠多的頁(yè)面之后,緩沖區(qū)中的頁(yè)面才會(huì)被一次性地全部添加到相應(yīng)的 LRU 鏈表中去。
LRU 緩存用到了 pagevec 結(jié)構(gòu),如下所示 :
struct pagevec {
unsigned long nr;
unsigned long cold;
struct page *pages[PAGEVEC_SIZE];
};
lru_cache_add() 和 lru_cache_add_active()。前者用于延遲將頁(yè)面添加到 inactive 鏈表上去,后者用于延遲將頁(yè)面添加到 active 鏈表上去。這兩個(gè)函數(shù)都會(huì)將要移動(dòng)的頁(yè)面先放到頁(yè)向量 pagevec 中,當(dāng) pagevec 滿了(已經(jīng)裝了 14 個(gè)頁(yè)面的描述符指針),pagevec 結(jié)構(gòu)中的所有頁(yè)面才會(huì)被一次性地移動(dòng)到相應(yīng)的鏈表上去。
參考資料
http://www.ibm.com/developerworks/cn/linux/l-cn-pagerecycle/index.html?ca=dat
http://www.douban.com/note/349467816/
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。