1. 概述
頁面遷移(page migrate)最早是為NUMA系統(tǒng)提供一種將進(jìn)程頁面遷移到指定內(nèi)存節(jié)點(diǎn)的能力用來提升訪問性能。后來在內(nèi)核中廣泛被使用,如內(nèi)存規(guī)整、CMA、內(nèi)存hotplug等。
頁面遷移對(duì)上層應(yīng)用業(yè)務(wù)來說是不可感知的,因?yàn)槠溥w移的是物理頁面,而應(yīng)用只訪問的是虛擬內(nèi)存。內(nèi)核遷移完成后,更新修改對(duì)應(yīng)頁表指向遷移后的頁面即可。當(dāng)然了這里說的不可感知是指業(yè)務(wù)不太關(guān)注,也不需要做對(duì)應(yīng)修改。實(shí)際上有些場景發(fā)生頁面遷移是業(yè)務(wù)性能是有影響的,下面會(huì)詳細(xì)描述。
2. 典型場景
我們列舉2個(gè)內(nèi)核中發(fā)生頁面遷移的典型場景。
2.1 NUMA Balancing引起的頁面遷移
在典型 NUMA 中,存在多個(gè) node, 本地 CPU 訪問本地 node 節(jié)點(diǎn)對(duì)應(yīng)的 memory 性能會(huì)快一些。
Linux 的 NUMA 自動(dòng)均衡機(jī)制會(huì)嘗試將內(nèi)存遷移到正在訪問它的 CPU 節(jié)點(diǎn)所在的 node。如下圖所示,CPU24 ~ CPU47訪問不是本地 node 對(duì)應(yīng)的 memory,性能會(huì)比較慢,系統(tǒng)會(huì)將其遷移到本地 node 對(duì)應(yīng)的 memory 以提升訪問性能。
遷移后如下圖:
2.2 內(nèi)存碎片整理
系統(tǒng)使用一段時(shí)候后,由于內(nèi)存碎片的原因,較難滿足連續(xù)內(nèi)存需求,如果需要分配連續(xù)大塊內(nèi)存,需要進(jìn)行內(nèi)存規(guī)整以形成大塊連續(xù)內(nèi)存,頁面遷移是內(nèi)存碎片整理的基礎(chǔ)。
3. 實(shí)現(xiàn)分析
3.1 遷移模式
內(nèi)核中通過接口migrate_pages實(shí)現(xiàn)頁而遷移, 分為3個(gè)模式。
模式 | 簡介 | 應(yīng)用場景 |
---|---|---|
MIGRATE_ASYNC | 異步遷移,過程中不會(huì)發(fā)生阻塞 | 內(nèi)存分配slowpath |
MIGRATE_SYNC_LIGHT | 輕度同步遷移,允許大部分的阻塞操作,唯獨(dú)不允許臟頁的回寫操作 | kcompactd觸發(fā)的規(guī)整 |
MIGRATE_SYNC | 同步遷移,遷移過程會(huì)發(fā)生阻塞,若需要遷移的某個(gè)page正在writeback或被locked會(huì)等待它完成 | sysfs主動(dòng)觸發(fā)的內(nèi)存規(guī)整 |
MIGRATE_SYNC_NO_COPY | 同步遷移,但不等待頁面的拷貝過程。頁面的拷貝通過回調(diào)migratepage(),過程可能會(huì)涉及DMA | migrate_vma_pages |
3.2 實(shí)現(xiàn)流程
內(nèi)核文檔有描述這個(gè)API是怎么工作的。不過這個(gè)描述著實(shí)是不太友好, 不容易在腦海形成畫面。
我們通過結(jié)合代碼實(shí)現(xiàn),把這個(gè)轉(zhuǎn)化為流程圖:
總結(jié)一下,頁面遷移過程本質(zhì)就是分配一個(gè) new_page, 解除原有 page 映射,把舊 page 復(fù)制到新 page 并建立新 page 的映射。
4. 頁面遷移過程用戶態(tài)訪問處理
到這里可能會(huì)有疑問:如果在頁面遷移過程中,應(yīng)用發(fā)生發(fā)訪問這個(gè)遷移中的頁面,會(huì)發(fā)生什么?
情景1: 舊頁面的頁表還未解映射, 此時(shí)發(fā)生缺頁可以正常訪問原來頁面。
情景2: 舊頁面解除了映射,但新頁面還未建立映射。這時(shí)訪問會(huì)發(fā)生等待,需要等新頁面建立映射并copy完成頁面后才能訪問。
情景3: 完成了頁面遷移動(dòng)作,可以正常訪問新頁面了。
下面我們重點(diǎn)分析一下,當(dāng)舊頁面解除了映射,且新頁面未建立映射這個(gè)過程中發(fā)生了用戶態(tài)訪問,內(nèi)核的處理流程是怎樣的。
首先我們看一下舊頁面解除了映射的過程:
staticbooltry_to_unmap_one(structpage*page,structvm_area_struct*vma, unsignedlongaddress,void*arg) { ... if(PageHWPoison(page)&&!(flags&TTU_IGNORE_HWPOISON)){ ... }elseif(pte_unused(pteval)&&!userfaultfd_armed(vma)){ ... }elseif(IS_ENABLED(CONFIG_MIGRATION)&& (flags&(TTU_MIGRATION|TTU_SPLIT_FREEZE))){ // 頁面遷移會(huì)設(shè)置TTU_MIGRATION標(biāo)記,走到這個(gè)分支來 swp_entry_tentry; pte_tswp_pte; if(arch_unmap_one(mm,vma,address,pteval)0)?{ ????set_pte_at(mm,?address,?pvmw.pte,?pteval); ????ret?=?false; ????page_vma_mapped_walk_done(&pvmw); ????break; ???} ???/* ????*?Store?the?pfn?of?the?page?in?a?special?migration ????*?pte.?do_swap_page()?will?wait?until?the?migration ????*?pte?is?removed?and?then?restart?fault?handling. ????*/ ????// 遷移中的頁面,?生成了一個(gè)swap?entry,?并寫到PTE頁表項(xiàng)中 ????// 當(dāng)再次發(fā)生缺頁時(shí)會(huì)走進(jìn)do_swap_page等待直到遷移完成. ???entry?=?make_migration_entry(subpage,?pte_write(pteval)); ???swp_pte?=?swp_entry_to_pte(entry); ???if?(pte_soft_dirty(pteval)) ????swp_pte?=?pte_swp_mksoft_dirty(swp_pte); ???if?(pte_uffd_wp(pteval)) ????swp_pte?=?pte_swp_mkuffd_wp(swp_pte); ????// 當(dāng)設(shè)置了遷移標(biāo)記的Swap?entry到pte后,?這個(gè)舊頁面就不能像原來那樣的順利被訪問了 ???set_pte_at(mm,?address,?pvmw.pte,?swp_pte); ???/* ????*?No?need?to?invalidate?here?it?will?synchronize?on ????*?against?the?special?swap?migration?pte. ????*/ ??}?else?if?(PageAnon(page))?{ ???swp_entry_t?entry?=?{?.val?=?page_private(subpage)?}; ???pte_t?swp_pte; ???/* ????*?Store?the?swap?location?in?the?pte. ????*?See?handle_pte_fault()?... ????*/ ???if?(unlikely(PageSwapBacked(page)?!=?PageSwapCache(page)))?{ ????WARN_ON_ONCE(1); ????ret?=?false; ????/*?We?have?to?invalidate?as?we?cleared?the?pte?*/ ????mmu_notifier_invalidate_range(mm,?address, address?+?PAGE_SIZE); ????page_vma_mapped_walk_done(&pvmw); ????break; ???} ... }
解除映射后,再次發(fā)生映射就走到do_swap_page中了。
vm_fault_tdo_swap_page(structvm_fault*vmf) { ... //獲取到這是一個(gè)在遷移過程的的PTE的標(biāo)識(shí) entry=pte_to_swp_entry(vmf->orig_pte); if(unlikely(non_swap_entry(entry))){//不是傳統(tǒng)的Swapentry if(is_migration_entry(entry)){//是遷移標(biāo)記進(jìn)來的 /*等待migration的完成。本質(zhì)是在等待舊page釋放其page lock *最終調(diào)用到wait_on_page_bit_common */ migration_entry_wait(vma->vm_mm,vmf->pmd,vmf->address); } ... }
總結(jié)一下:
頁面遷移前,首先會(huì)獲取舊頁面和新頁面的頁面鎖PG_lock,在解除映射的時(shí)候傳入了由于頁面遷移導(dǎo)致的解映射標(biāo)記TTU_MIGRATION,設(shè)置了此標(biāo)記會(huì)生成一個(gè)帶頁面遷移標(biāo)識(shí)的swap_entry設(shè)置到pte中。在設(shè)置好的那一刻走,應(yīng)用進(jìn)程無法很順利地訪問這個(gè)頁面了,需要通過do_swap_entry路徑。
假如此時(shí)應(yīng)用進(jìn)程訪問了這個(gè)頁面,會(huì)走進(jìn)到do_swap_entry,取出帶遷移標(biāo)識(shí)的swap_entry,識(shí)別到這個(gè)標(biāo)識(shí),會(huì)等待頁面鎖釋放。頁面鎖只有在頁面遷移完成后才會(huì)被釋放,也就是會(huì)發(fā)生等待直到頁面遷移完成。
5. 用戶態(tài)如何避免發(fā)生頁面遷移
上面我們已經(jīng)知道,如果有頁面遷移過程中發(fā)生用戶態(tài)訪問,很可能是需要發(fā)生等待其遷移完成, 這個(gè)過程需要一定耗時(shí)。而有時(shí)的場景我們是需要避免此種時(shí)延抖動(dòng),那有什么辦法呢?
方法就是讓這個(gè)頁面短時(shí)間內(nèi)變得不可移動(dòng)。
intmigrate_page_move_mapping(structaddress_space*mapping, structpage*newpage,structpage*page,intextra_count) { ... if(page_count(page)!=expected_count) return-EAGAIN; ... returnMIGRATEPAGE_SUCCESS; }
可以看到當(dāng)發(fā)生頁面復(fù)制過程中,如果 page 的引用計(jì)數(shù)不符合預(yù)期(期望為0)時(shí),這時(shí)系統(tǒng)認(rèn)為有人在使用,不適用做遷移。那么,我們只需要增加 page 的引用計(jì)數(shù)就可以。
可以在不想被遷移的時(shí)間段開始前通過pin_user_pages這樣的接口,結(jié)束時(shí)unpin就可以了。接口最終會(huì)調(diào)到try_grab_page增加引用計(jì)數(shù)。
bool__must_checktry_grab_page(structpage*page,unsignedintflags) { ... refs=GUP_PIN_COUNTING_BIAS;//#defineGUP_PIN_COUNTING_BIAS(1U<10) ???page_ref_add(page,?refs); ??} ??return?true; }
編輯:黃飛
-
cpu
+關(guān)注
關(guān)注
68文章
10804瀏覽量
210824 -
內(nèi)存
+關(guān)注
關(guān)注
8文章
2966瀏覽量
73812 -
CMA
+關(guān)注
關(guān)注
0文章
26瀏覽量
9784 -
虛擬內(nèi)存
+關(guān)注
關(guān)注
0文章
70瀏覽量
8046
原文標(biāo)題:圖解|內(nèi)存頁面遷移技術(shù)
文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論