RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Linux內存管理中HVO的實現(xiàn)原理

Linux閱碼場 ? 來源:Linux內核遠航者 ? 2024-10-22 16:51 ? 次閱讀

以下文章來源于Linux內核遠航者 ,作者Linux內核遠航者

開場白

環(huán)境:

處理器架構:arm64

內核源碼:linux-6.6.29

ubuntu版本:20.04.1

代碼閱讀工具:vim+ctags+cscope

本文主要介紹內存管理中的HVO(HugeTLB Vmemmap Optimization)特性,通過HVO可以節(jié)省管理HugeTLB 頁面元數(shù)據(jù)(struct page)的內存占用,甚至在緩存的空間局部性表現(xiàn)上也更好。本文通過圖解結合源代碼分析的方式讓大家徹底理解HVO的實現(xiàn)原理,且本文主要以2M大小的HugeTLB 頁面為例講解。

1.術語解釋

文中會提到三種物理頁面,為了便于闡述,后面統(tǒng)一使用以下幾個概念講解:

例如2M大小的hugetlb頁面,struct page結構大小為64Byte, 則需要 2M/4K = 512個struct page結構來管理hugetlb頁面,那么這些struct page結構占用的物理內存為:512*64 = 32768Byte = 8個4k頁面,即是page0 - page7。

head vmemmap page:hugetlb頁面使用struct page結構占用的第一個物理頁面, 2M大小的hugetlb頁面則head vmemmap page就是page0。

tail vmemmap page:可以優(yōu)化釋放掉的struct page結構占用的物理頁面,2M大小的hugetlb頁面則tail vmemmap page就是page1 - page7。

new head vmemmap page:如果vmemmap page是連續(xù)的物理頁面,假如只釋放掉tail vmemmap page,可能會破壞掉連續(xù)性,HVO中會申請新的head vmemmap page,然后將head vmemmap page拷貝到這個頁面,最后同時釋放掉所有的struct page結構占用的物理頁面, 2M大小的hugetlb頁面則釋放掉page0 - page7。

2.HVO優(yōu)化原理及觸發(fā)場景

2.1 HVO優(yōu)化原理

下面我們從內核源碼角度來看以下HVO優(yōu)化原理。

//mm/hugetlb_vmemmap.c
hugetlb_vmemmap_optimize
->vmemmap_start=(unsignedlong)head//hugetlb頁面的head vmemmap page所在地址。
->vmemmap_should_optimize//判斷當前的hugetlb頁面大小是否適合做HVO優(yōu)化,
沒有打開vmemmap_optimize_enabled或者hugetlb頁面使用structpage結構占用的
內存小于一個4k頁面不做優(yōu)化。
->vmemmap_end=vmemmap_start+hugetlb_vmemmap_size(h);//獲得優(yōu)化的tailvmemmappage地址
vmemmap_reuse=vmemmap_start;//重復映射使用headvmemmappage地址
vmemmap_start+=HUGETLB_VMEMMAP_RESERVE_SIZE;//從第一個tailvmemmappage開始優(yōu)化

->vmemmap_remap_free(vmemmap_start,vmemmap_end,vmemmap_reuse)//釋放掉hugetlb頁面使用struct page結構冗余的物理頁面。
->walk.reuse_page=alloc_pages_node(nid,gfp_mask,0);
if(walk.reuse_page){
copy_page(page_to_virt(walk.reuse_page),
|(void*)walk.reuse_addr);
list_add(&walk.reuse_page->lru,&vmemmap_pages);
}
//優(yōu)化1:申請一個新的4k頁面,即new head vmemmap page,然后將head vmemmap page拷貝到這個頁面,然后將new head vmemmap page加入vmemmap_pages鏈表
(用于失敗釋放此頁面使用)

->mmap_read_lock(&init_mm)//讀方式獲得init_mm的mmap_lock
->vmemmap_remap_range//遍歷頁表,釋放冗余的物理頁面(由于優(yōu)化1,這里會釋放掉所有的管理hugetlb頁面使用的struct page結構占用的內存)。
->mmap_read_unlock(&init_mm)//讀方式釋放init_mm的mmap_lock
->free_vmemmap_page_list(&vmemmap_pages)//釋放調用hugetlb頁面使用structpage結構冗余的物理頁面(例如2M大小的hugetlb頁面,釋放掉8頁(page0-page7))
->SetHPageVmemmapOptimized(head)//為hugetlb頁面設置HVO優(yōu)化標記,定義在include/linux/hugetlb.h(HPAGEFLAG(VmemmapOptimized,vmemmap_optimized))

vmemmap_remap_pte的核心代碼如下:
staticvoidvmemmap_remap_pte(pte_t*pte,unsignedlongaddr,
|structvmemmap_remap_walk*walk)
{
/*
|*Remapthetailpagesasread-onlytocatchillegalwriteoperation
|*tothetailpages.
|*/
pgprot_tpgprot=PAGE_KERNEL_RO;//映射tailvmemmappage為只讀
structpage*page=pte_page(ptep_get(pte));//通過頁表項獲得structpage指針
pte_tentry;

/*Remappingtheheadpagerequiresr/w*/
if(unlikely(addr==walk->reuse_addr)){//如果當前的虛擬地址是reuse_addr
pgprot=PAGE_KERNEL;////映射headvmemmappage為可讀可寫
list_del(&walk->reuse_page->lru);//之前在hugetlb_vmemmap_optimize中將這個new head vmemmap page加入了vmemmap_pages,現(xiàn)在刪除,供頁面共享使用。

/*
|*Makessurethatprecedingstorestothepagecontentsfrom
|*vmemmap_remap_free()becomevisiblebeforetheset_pte_at()
|*write.
|*/
smp_wmb();
}

entry=mk_pte(walk->reuse_page,pgprot);//重要步驟:頁表映射尾頁到頭頁上!
list_add_tail(&page->lru,walk->vmemmap_pages);//將需要釋放的頁面加入vmemmap_pages鏈表
set_pte_at(&init_mm,addr,pte,entry);//設置頁表項
}

優(yōu)化之前圖解:

92425386-9052-11ef-a511-92fbcf53809c.png

注意:數(shù)據(jù)建立頁表映射是在內核初始化階段的start_kernel->setup_arch->paging_init來做的線性頁表映射,而元數(shù)據(jù)(struct page)建立頁表映射是在內核初始化階段的start_kernel->bootmem_init->sparse_init->sparse_init_nid->__populate_section_memmap來做,通過virt_to_page可以獲得數(shù)據(jù)的元數(shù)據(jù)地址,后面HVO優(yōu)化是改變之前的元數(shù)據(jù)的映射。

優(yōu)化之后圖解:

926f15f6-9052-11ef-a511-92fbcf53809c.png

可以看出對于2M大小的hugetlb頁面優(yōu)化之后節(jié)省元數(shù)據(jù)(struct page)占用內存:7/8= 87.5%, 如果是 1G 的大頁,可以節(jié)約的元數(shù)據(jù)(struct page)內存占用近乎 100%(讀者可自行計算)。

2.2 HVO觸發(fā)場景

HVO觸發(fā)場景主要為需要申請hugetlb頁面的時候:舉例如下,

場景1:解析cmdline的hugepages=參數(shù)

如hugepages=100,啟動階段申請100個2M的hugetlb頁面到大頁池。

mm/hugetlb.c
__setup("hugepages=",hugepages_setup)
->hugepages_setup
->hugetlb_hstate_alloc_pages
->alloc_pool_huge_page
->alloc_fresh_hugetlb_folio//分配2M的hugetlb頁面
->prep_new_hugetlb_folio
->__prep_new_hugetlb_folio
->hugetlb_vmemmap_optimize//觸發(fā)HVO優(yōu)化

場景2:寫相關hugetlb頁面的sys節(jié)點,增大相關頁池中hugetlb頁面數(shù)量

如:echo 1000 > /sys/kernel/mm/hugepages/hugepages-64B/nr_hugepages

mm/hugetlb.c
nr_hugepages_store
->nr_hugepages_store_common
->set_max_huge_pages
->alloc_pool_huge_page
->alloc_fresh_hugetlb_folio//分配2M的hugetlb頁面
->prep_new_hugetlb_folio
->__prep_new_hugetlb_folio
->hugetlb_vmemmap_optimize//觸發(fā)HVO優(yōu)化

場景3:寫proc節(jié)點,增大默認頁池(如2M)中hugetlb頁面數(shù)量

如:echo 1000 > /proc/sys/vm/nr_hugepages

mm/hugetlb.c
hugetlb_table[]
->hugetlb_sysctl_handler
->hugetlb_sysctl_handler_common
->__nr_hugepages_store_common
->set_max_huge_pages
->alloc_pool_huge_page
->alloc_fresh_hugetlb_folio//分配2M的hugetlb頁面
->prep_new_hugetlb_folio
->__prep_new_hugetlb_folio
->hugetlb_vmemmap_optimize//觸發(fā)HVO優(yōu)化

3.撤銷HVO優(yōu)化原理及觸發(fā)場景

3.1 撤銷HVO優(yōu)化原理

有的時候需要撤銷HVO所作的優(yōu)化,如需要縮小hugetlb頁池中頁面數(shù)量。

相關源碼分析如下:

mm/hugetlb_vmemmap.c
hugetlb_vmemmap_restore
->首先通過HPageVmemmapOptimized(head)判斷是否hugetlb頁面被HVO優(yōu)化了,沒有則直接返回
->vmemmap_end=vmemmap_start+hugetlb_vmemmap_size(h);//獲得優(yōu)化的tailvmemmappage地址
vmemmap_reuse=vmemmap_start;//重復映射使用的headvmemmappage地址
vmemmap_start+=HUGETLB_VMEMMAP_RESERVE_SIZE;//從第一個tailvmemmappage開始優(yōu)化
->vmemmap_remap_alloc(vmemmap_start,vmemmap_end,vmemmap_reuse)//還原HVO之前所作的優(yōu)化:重新映射vmemmap的虛擬地址范圍到vmemmap_pages頁面
->alloc_vmemmap_page_list(start,end,&vmemmap_pages)//分配所有的tailvmemmappage,如2M大小的hugetlb頁面,分配page1-page7,共7個頁面,page0已有不需要分配
->mmap_read_lock(&init_mm)//讀方式獲得init_mm的mmap_lock
->vmemmap_remap_range(reuse,end,&walk)//遍歷頁表,重新映射vmemmap的虛擬地址范圍到vmemmap_pages中分配的頁面
->vmemmap_restore_pte//對于每個頁表項,調用vmemmap_restore_pte處理
->mmap_read_unlock(&init_mm)//讀方式釋放init_mm的mmap_lock

->ClearHPageVmemmapOptimized(head)//清除hugetlb頁面的優(yōu)化標記

vmemmap_remap_pte的核心代碼如下:
/*
*Howmanystructpagestructsneedtobereset.Whenwereusethehead
*structpage,thespecialmetadata(e.g.page->flagsorpage->mapping)
*cannotcopytothetailstructpagestructs.Theinvalidvaluewillbe
*checkedinthefree_tail_page_prepare().Inordertoavoidthemessage
*of"corruptedmappingintailpage".Weneedtoresetatleast3(one
*headstructpagestructandtwotailstructpagestructs)structpage
*structs.
*/
#defineNR_RESET_STRUCT_PAGE3

staticinlinevoidreset_struct_pages(structpage*start)
{
structpage*from=start+NR_RESET_STRUCT_PAGE;

BUILD_BUG_ON(NR_RESET_STRUCT_PAGE*2>PAGE_SIZE/sizeof(structpage));
memcpy(start,from,sizeof(*from)*NR_RESET_STRUCT_PAGE);
}


staticvoidvmemmap_restore_pte(pte_t*pte,unsignedlongaddr,
structvmemmap_remap_walk*walk)
{
pgprot_tpgprot=PAGE_KERNEL;//頁表屬性可讀可寫
structpage*page;
void*to;

BUG_ON(pte_page(ptep_get(pte))!=walk->reuse_page);

page=list_first_entry(walk->vmemmap_pages,structpage,lru);//vmemmap_pages鏈表中獲得一個物理頁面
list_del(&page->lru);//page從vmemmap_pages鏈表中刪除
to=page_to_virt(page);
copy_page(to,(void*)walk->reuse_addr);//將headvmemmappage的頁面內容拷貝到這個物理頁面
reset_struct_pages(to);//由于描述hugetlb頁面的structpage結構,只有前3個structpage結構用于描述hugetlb頁面信息,
其他的structpage結構都只是有compound_head是有意義的,為了防止free_tail_page_prepare有錯誤的檢查報告,這里將所有tailvmemmappage的
內容都設置為正常值。

/*
|*Makessurethatprecedingstorestothepagecontentsbecomevisible
|*beforetheset_pte_at()write.
|*/
smp_wmb();
set_pte_at(&init_mm,addr,pte,mk_pte(page,pgprot));//重新映射頁表到這個物理頁面
}

3.2 撤銷HVO觸發(fā)場景

場景1:寫相關hugetlb頁面的sys節(jié)點,減小相關頁池中hugetlb頁面數(shù)量

例如 寫/sys/kernel/mm/hugepages/hugepages-xxxkB/nr_hugepages

echo 500 > /sys/kernel/mm/hugepages/hugepages-64B/nr_hugepages //從之前1000減小到500

mm/hugetlb.c
nr_hugepages_store
nr_hugepages_store_common
set_max_huge_pages
->flush_free_hpage_work(h);
->free_hpage_workfn
->__update_and_free_hugetlb_folio
->hugetlb_vmemmap_restore
->update_and_free_pages_bulk
->__update_and_free_hugetlb_folio
->hugetlb_vmemmap_restore

場景2:寫proc節(jié)點,減小默認頁池中hugetlb頁面數(shù)量

例如 寫/proc/sys/vm/nr_hugepages

echo 500 > /proc/sys/vm/nr_hugepages //從之前1000減小到500

mm/hugetlb.c
hugetlb_table[]
->hugetlb_sysctl_handler
->hugetlb_sysctl_handler_common
->__nr_hugepages_store_common
->set_max_huge_pages
->flush_free_hpage_work(h);
->free_hpage_workfn
->__update_and_free_hugetlb_folio
->hugetlb_vmemmap_restore
->update_and_free_pages_bulk
->__update_and_free_hugetlb_folio
->hugetlb_vmemmap_restore

場景3:寫/sys/kernel/mm/hugepages/hugepages-xxxkB/demote

mm/hugetlb.c
demote_store
->demote_pool_huge_page
->demote_free_hugetlb_folio
->hugetlb_vmemmap_restore

4.HVO中頭頁的獲取問題

通過上面的分析,我們知道通過將tail vmemmap page映射head vmemmap page,然后釋放掉tail vmemmap page,從而達到節(jié)省vmemmap page占用內存的目的,但是會出現(xiàn)我們獲得的尾頁的struct page指向頭頁的struct page的情況。如圖所示,以2M大小的hugetlb頁面為例:

929e6c20-9052-11ef-a511-92fbcf53809c.png

可以看到需要struct page0 - struct page511,512個struct page來描述hugetlb頁面,那么通過HVO優(yōu)化后:

[struct page0, struct page63]<----> head vmemmappage

[struct page64, struct page127] <----> tail vmemmap page

... <----> tail vmemmappage

[struct page448, struct page511] <----> tail vmemmappage

都頁表映射到head vmemmap page。

那么struct page0, struct page64,..., struct page448都會指向struct page0,可能會在判斷是否為頭頁的代碼中造成混亂,也可以看出這些struct page地址都是對齊4k的。

這里需要補充說明下:對于復合頁(THP、hugetlb都屬于復合頁),頭頁會設置PG_head標記,而尾頁的compound_head=head_page | 0x1。

像這些描述尾頁的struct page,被成為"偽造的頭頁",內核中處理如下(page_folio為例):

include/linux/page-flags.h
page_folio
->_compound_head
->staticinlineunsignedlong_compound_head(conststructpage*page)
{
unsignedlonghead=READ_ONCE(page->compound_head);//獲取頁面的compound_head成員

if(unlikely(head&1))//是真正的尾頁
returnhead-1;//計算出頭頁地址,返回
return(unsignedlong)page_fixed_fake_head(page);//為真正的頭頁或者為偽造的頭頁。
}

 page_fixed_fake_head處理如下:
/*
*Returntherealheadpagestructiffthe@pageisafakeheadpage,otherwise
*returnthe@pageitself.SeeDocumentation/mm/vmemmap_dedup.rst.
*/
static__always_inlineconststructpage*page_fixed_fake_head(conststructpage*page)
{
if(!static_branch_unlikely(&hugetlb_optimize_vmemmap_key))
returnpage;

/*
|*OnlyaddressesalignedwithPAGE_SIZEofstructpagemaybefakehead
|*structpage.Thealignmentcheckaimstoavoidaccessthefields(
|*e.g.compound_head)ofthe@page[1].Itcanavoidtoucha(possibly)
|*coldcachelineinsomecases.
|*/
if(IS_ALIGNED((unsignedlong)page,PAGE_SIZE)&&
|test_bit(PG_head,&page->flags)){//structpage結構地址只有對齊PAGE_SIZE才有可能為偽造的頭頁
/*
|*Wecansafelyaccessthefieldofthe@page[1]withPG_head
|*becausethe@pageisacompoundpagecomposedwithatleast
|*twocontiguouspages.
|*/
unsignedlonghead=READ_ONCE(page[1].compound_head);//獲得下一個structpage地址的compound_head成員,實際上就是獲取第一個尾頁的compound_head

if(likely(head&1))
return(conststructpage*)(head-1);//計算獲得真正的頭頁地址
}
returnpage;
}

通過上面計算我們就可以得到真正的頭頁,簡單來說就是通過struct page1->compound_head計算獲得頭頁。

所有打開HVO優(yōu)化后,可能描述hugetlb頁面的struct page有三種情況:

真正的頭頁,如上例子中的struct page0,計算頭頁的時候直接返回struct page0地址即可。

偽造的頭頁,如上例子中的struct page64,struct pageN(N=n*64, n=1-6) ,struct page448。通過struct page1->compound_head計算獲得頭頁地址。

真正的尾頁,除了1和2的所有情況,直接通過當前struct page->compound_head計算獲得頭頁地址。

5.總結

通過以上的分析,我們知道:HVO主要是并不節(jié)省實際用戶數(shù)據(jù)(如2M大小的HugeTLB 頁面)的內存占用,而是節(jié)省管理HugeTLB 頁面元數(shù)據(jù)(如描述2M大小的HugeTLB 頁面的512個struct page)的內存占用,巧妙的利用了HugeTLB機制的一些特性(如HugeTLB 頁面使用頭三個struct page來描述其頁面狀態(tài),不支持分裂,不支持部分unmap等),使得我們可以共享struct page所在的第一個物理頁面,釋放掉其他冗余的物理頁面,從而達到節(jié)省內存的目的。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 處理器
    +關注

    關注

    68

    文章

    19259

    瀏覽量

    229648
  • 內核
    +關注

    關注

    3

    文章

    1372

    瀏覽量

    40275
  • Linux
    +關注

    關注

    87

    文章

    11292

    瀏覽量

    209321
  • 內存管理
    +關注

    關注

    0

    文章

    168

    瀏覽量

    14134

原文標題:深入理解Linux內核之HVO(HugeTLB Vmemmap Optimization)

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    Linux內存管理是什么,Linux內存管理詳解

    Linux內存管理 Linux內存管理是一個非常復雜的過程,主要分成兩個大的部分:內核的
    的頭像 發(fā)表于 05-11 17:54 ?6038次閱讀
    <b class='flag-5'>Linux</b>的<b class='flag-5'>內存</b><b class='flag-5'>管理</b>是什么,<b class='flag-5'>Linux</b>的<b class='flag-5'>內存</b><b class='flag-5'>管理</b>詳解

    深度解析Linux內存管理體系

    Linux內存管理的整體模式是虛擬內存管理(分頁內存管理
    發(fā)表于 08-06 16:55 ?1733次閱讀

    走進Linux內存系統(tǒng)探尋內存管理的機制和奧秘

    Linux 內存是后臺開發(fā)人員,需要深入了解的計算機資源。合理的使用內存,有助于提升機器的性能和穩(wěn)定性。本文主要介紹Linux 內存組織結構
    的頭像 發(fā)表于 01-05 09:47 ?1621次閱讀

    關于Linux內存管理的詳細介紹

    Linux內存管理是指對系統(tǒng)內存的分配、釋放、映射、管理、交換、壓縮等一系列操作的管理。在
    發(fā)表于 03-06 09:28 ?1064次閱讀

    Linux內核的內存管理詳解

    內存管理的主要工作就是對物理內存進行組織,然后對物理內存的分配和回收。但是Linux引入了虛擬地址的概念。
    發(fā)表于 08-31 14:46 ?779次閱讀
    <b class='flag-5'>Linux</b>內核的<b class='flag-5'>內存</b><b class='flag-5'>管理</b>詳解

    linux內存管理

    公交,地鐵,睡前必備,方便大家查閱,持續(xù)更新,敬請期待!---更新于2020-02-12linux 內存管理Linux內存初始化CPU是
    發(fā)表于 07-22 08:41

    嵌入式Linux內存管理是什么

    點擊 嵌入式 Linux 內存管理
    發(fā)表于 11-05 07:01

    linux內存管理機制淺析

    本內容介紹了arm linux內存管理機制,詳細說明了linux內核內存管理,
    發(fā)表于 12-19 14:09 ?73次下載
    <b class='flag-5'>linux</b><b class='flag-5'>內存</b><b class='flag-5'>管理</b>機制淺析

    linux內存管理

    linux內存管理
    發(fā)表于 10-24 11:12 ?3次下載
    <b class='flag-5'>linux</b><b class='flag-5'>內存</b><b class='flag-5'>管理</b>

    你知道linux內存管理基礎及方法?

    linux內存管理采取的分頁存取機制,會將內存不經常使用的數(shù)據(jù)塊交換到虛擬內存
    發(fā)表于 04-28 17:12 ?1155次閱讀

    嵌入式 Linux 內存管理

    點擊 嵌入式 Linux 內存管理
    發(fā)表于 11-02 10:36 ?12次下載
    嵌入式 <b class='flag-5'>Linux</b> <b class='flag-5'>中</b>的<b class='flag-5'>內存</b><b class='flag-5'>管理</b>

    Linux內存管理的基礎知識科普

    Linux內存管理可謂是學好Linux的必經之路,也是Linux的關鍵知識點,有人說打通了內存
    的頭像 發(fā)表于 06-08 15:24 ?2106次閱讀

    Linux內核實現(xiàn)內存管理的基本概念

    本文概述Linux內核實現(xiàn)內存管理的基本概念,在了解基本概念后,逐步展開介紹實現(xiàn)內存
    發(fā)表于 06-23 11:56 ?831次閱讀
    <b class='flag-5'>Linux</b>內核<b class='flag-5'>實現(xiàn)</b><b class='flag-5'>內存</b><b class='flag-5'>管理</b>的基本概念

    Linux內存管理子系統(tǒng)開發(fā)必知的3個結構概念

    Linux內存管理子系統(tǒng)使用節(jié)點(node)、區(qū)域(zone)和頁(page)三級結構描述物理內存
    的頭像 發(fā)表于 08-28 09:34 ?887次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>中</b><b class='flag-5'>內存</b><b class='flag-5'>管理</b>子系統(tǒng)開發(fā)必知的3個結構概念

    Linux 內存管理總結

    一、Linux內存管理概述 Linux內存管理是指對系統(tǒng)內存
    的頭像 發(fā)表于 11-10 14:58 ?523次閱讀
    <b class='flag-5'>Linux</b> <b class='flag-5'>內存</b><b class='flag-5'>管理</b>總結
    RM新时代网站-首页