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

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

一文幾下?Linux內(nèi)核透明巨型頁(yè)支持

Linux閱碼場(chǎng) ? 來(lái)源:Linux內(nèi)核遠(yuǎn)航者 ? 作者:Linux內(nèi)核遠(yuǎn)航者 ? 2022-10-27 09:11 ? 次閱讀

==目標(biāo) ==

處理大內(nèi)存的性能關(guān)鍵計(jì)算應(yīng)用程序工作集已經(jīng)運(yùn)行在libhugetlbfs之上,然后依次運(yùn)行hugetlbfs。透明的巨型頁(yè)面支持是另一種使用大頁(yè)為虛擬內(nèi)存提供大頁(yè)支持的方法, 該支持自動(dòng)提升和降低頁(yè)面大小和沒(méi)有hugetlbfs的缺點(diǎn)。

目前它只適用于匿名內(nèi)存映射和tmpfs/shmem。但是將來(lái)它可以擴(kuò)展到其他文件系統(tǒng)。實(shí)際上,已經(jīng)支持了只讀的文件映射。

應(yīng)用程序運(yùn)行更快的原因有兩個(gè)的因素。第一個(gè)因素幾乎完全無(wú)關(guān)緊要,事實(shí)并非如此,這很重要,因?yàn)樗灿腥秉c(diǎn)在頁(yè)錯(cuò)誤中需要更大的清除頁(yè)拷貝有潛在的負(fù)面影響。第一個(gè)因素是采取每個(gè)2M的虛擬區(qū)域都有一個(gè)頁(yè)面錯(cuò)誤(將內(nèi)核的進(jìn)入/退出頻率減少512倍)。這的生命周期中,一個(gè)內(nèi)存映射只有第一次訪(fǎng)問(wèn)內(nèi)存。

第二個(gè)更持久,也更重要因子將會(huì)影響應(yīng)用程序的運(yùn)行時(shí)整個(gè)內(nèi)存的所有后續(xù)訪(fǎng)問(wèn)。第二個(gè)因素有兩個(gè)組件: 1)TLB miss將運(yùn)行更快(特別是使用嵌套分頁(yè)的虛擬化,但幾乎總是在沒(méi)有虛擬化的裸系統(tǒng)上。2)單個(gè)TLB條目將是映射更大數(shù)量的虛擬內(nèi)存,從而減少TLB miss次數(shù)。使用虛擬化和嵌套分頁(yè)只有KVM和Linux客戶(hù)端同時(shí)支持映射更大的TLB正在使用大頁(yè)面,但顯著的速度已經(jīng)發(fā)生了,如果其中一個(gè)使用大頁(yè)面只是因?yàn)門(mén)LB miss會(huì)跑得更快。

== 設(shè)計(jì) ==

“優(yōu)雅回退”:內(nèi)存組件沒(méi)有透明的巨型頁(yè)面知識(shí)可以回退到將巨型的PMD映射分解成ptes表,如果有必要,分裂一個(gè)透明的大頁(yè)面。因此這些組件可以繼續(xù)在常規(guī)頁(yè)面或常規(guī)pte映射上工作。

如果由于內(nèi)存碎片而導(dǎo)致大頁(yè)面分配失敗,常規(guī)頁(yè)面應(yīng)該優(yōu)雅地分配和混合在相同的vma中,沒(méi)有任何故障或重大延遲,沒(méi)有用戶(hù)感知。

如果某個(gè)任務(wù)退出了,并且出現(xiàn)了更多可用的大頁(yè)面(要么立即在buddy中或者通過(guò)VM),由常規(guī)頁(yè)面支持的guest物理內(nèi)存應(yīng)該重新自動(dòng)的安放在大頁(yè)面上(通過(guò)khugepaged線(xiàn)程)。

它不需要內(nèi)存預(yù)留,并且盡可能地使用大頁(yè)(這里唯一可能的預(yù)留是kernelcore=,以避免不可移動(dòng)的頁(yè)面碎片化所有內(nèi)存,但這樣的調(diào)整不是針對(duì)透明大頁(yè)支持的,它是通用的適用于內(nèi)核中所有動(dòng)態(tài)高階分配的特性)

透明大頁(yè)支持最大限度地利用空閑內(nèi)存,如果與hugetlbfs的保留方法相比,允許所有未使用的內(nèi)存用作緩存或其他可移動(dòng)(甚至不可移動(dòng)的對(duì)象)。它不需要預(yù)留來(lái)防止從用戶(hù)空間發(fā)現(xiàn)大頁(yè)面分配失敗。它允許分頁(yè)和所有其他高級(jí)vm功能在大頁(yè)上。應(yīng)用程序不需要修改就可以利用它。

然而,應(yīng)用程序可以進(jìn)一步優(yōu)化以利用這個(gè)功能,就像他們之前優(yōu)化過(guò)避免每個(gè)malloc(4k)都需要大量的mmap系統(tǒng)調(diào)用。優(yōu)化用戶(hù)空間到目前為止不是強(qiáng)制性的,khugepaged已經(jīng)可以照顧長(zhǎng)生命周期的頁(yè)面分配,即使對(duì)于處理大量?jī)?nèi)存的不知道大頁(yè)的應(yīng)用程序也是如此。

在某些情況下,當(dāng)啟用大頁(yè)面時(shí),系統(tǒng)范圍內(nèi),應(yīng)用程序可能最終會(huì)分配更多的內(nèi)存資源。一個(gè)應(yīng)用程序可以映射一個(gè)大的區(qū)域,但只觸及其中1字節(jié),在這種情況下,一個(gè)2M的頁(yè)面可能被分配而不是分配一個(gè)4k頁(yè)面是沒(méi)有好處的。這就是為什么可以在系統(tǒng)范圍內(nèi)禁用大頁(yè)面,并且只在內(nèi)部使用它們MADV_HUGEPAGE的madvise的區(qū)域。

嵌入式系統(tǒng)應(yīng)該只在madvise區(qū)域內(nèi)啟用大頁(yè)面為了消除浪費(fèi)寶貴內(nèi)存字節(jié)的風(fēng)險(xiǎn),并且只會(huì)跑得更快。

應(yīng)用程序可以從大頁(yè)中獲得很多好處,而不可以冒著丟失內(nèi)存的風(fēng)險(xiǎn)使用大頁(yè),應(yīng)該使用madvise(MADV_HUGEPAGE)在他們關(guān)鍵映射區(qū)域。

== sysfs ==

透明大頁(yè)支持匿名內(nèi)存能被完全的禁用(主要是為了調(diào)試)或僅在MADV_HUGEPAGE區(qū)域內(nèi)啟用(避免占用更多內(nèi)存資源的風(fēng)險(xiǎn))或者系統(tǒng)范圍內(nèi)啟用。這可以通過(guò)以下方式實(shí)現(xiàn):

echo never >/sys/kernel/mm/transparent_hugepage/enabled
echo always >/sys/kernel/mm/transparent_hugepage/enabled 
echomadvise>/sys/kernel/mm/transparent_hugepage/enabled

還可以限制VM中的碎片整理工作,以生成匿名的巨型頁(yè)面,以防它們不能立即自由地使用madvise區(qū)域,或者永遠(yuǎn)不要嘗試對(duì)內(nèi)存進(jìn)行碎片整理,而只是回退到常規(guī)頁(yè)面,除非巨型頁(yè)面立即可用。顯然,如果我們花費(fèi)CPU時(shí)間對(duì)內(nèi)存進(jìn)行碎片整理,那么我們將期望獲得更多的好處,因?yàn)槲覀兩院笫褂昧舜箜?yè)面而不是普通頁(yè)面。這不是總能保證的,更可能的情況是分配給一個(gè)MADV_HUGEPAGE區(qū)域。

echo always >/sys/kernel/mm/transparent_hugepage/defrag 
echo defer >/sys/kernel/mm/transparent_hugepage/defrag 
echo defer+madvise >/sys/kernel/mm/transparent_hugepage/defrag 
echo madvise >/sys/kernel/mm/transparent_hugepage/defrag
echo never >/sys/kernel/mm/transparent_hugepage/defrag

“always”意味著請(qǐng)求THP的應(yīng)用程序?qū)⒃诜峙涫r(shí)暫停,并直接回收頁(yè)面和規(guī)整內(nèi)存,以便立即分配THP。對(duì)于那些從THP使用中受益頗多并愿意延遲虛擬機(jī)開(kāi)始使用它們的虛擬機(jī)來(lái)說(shuō),這可能是可取的。

“defer”意味著應(yīng)用程序?qū)⒃诤笈_(tái)喚醒kswapd來(lái)回收頁(yè)面,并喚醒kcompactd來(lái)規(guī)整內(nèi)存,以便在不久的將來(lái)THP可用。khugepage負(fù)責(zé)隨后安裝THP頁(yè)面。

"defer+madvise"只對(duì)已經(jīng)使用madvise(MADV_HUGEPAGE)的區(qū)域,后臺(tái)喚醒kswapd以回收頁(yè)面,并喚醒kcompactd以規(guī)整內(nèi)存,以便THP在不久的將來(lái)可用。

"madvise"將進(jìn)入直接回收,像"always",但只對(duì)madvise(MADV_HUGEPAGE)的區(qū)域。這是默認(rèn)行為。

“never”應(yīng)該是不言自明的,它不采取任何措施。

默認(rèn)情況下,內(nèi)核嘗試在讀取頁(yè)面錯(cuò)誤時(shí)使用巨型零頁(yè)來(lái)進(jìn)行匿名映射??梢酝ㄟ^(guò)寫(xiě)入0來(lái)禁用巨型0頁(yè),也可以通過(guò)寫(xiě)入1來(lái)啟用巨型0頁(yè):

echo 0 >/sys/kernel/mm/transparent_hugepage/use_zero_page 
echo 1 >/sys/kernel/mm/transparent_hugepage/use_zero_page

一些用戶(hù)空間(比如一個(gè)測(cè)試程序,或者一個(gè)優(yōu)化的內(nèi)存分配庫(kù))可能想知道一個(gè)透明大頁(yè)的大小(以字節(jié)為單位):

cat /sys/kernel/mm/transparent_hugepage/hpage_pmd_size

當(dāng)transparent_hugepage/enabled設(shè)置為“always”或“madvise”時(shí),khugepaged將自動(dòng)啟動(dòng),如果設(shè)置為“never”,它將自動(dòng)關(guān)閉。

khugepaged的運(yùn)行頻率通常較低,因此,雖然人們可能不希望在缺頁(yè)異常期間同步調(diào)用碎片整理算法,但至少在khugepaged中調(diào)用碎片整理是值得的。但是,也可以通過(guò)寫(xiě)0來(lái)禁用khugepaged中的碎片整理,或者通過(guò)寫(xiě)1來(lái)啟用khugepaged中的碎片整理:

echo 0 >/sys/kernel/mm/transparent_hugepage/khugepaged/defrag 
echo 1 >/sys/kernel/mm/transparent_hugepage/khugepaged/defrag

你也可以控制khugepaged每次通過(guò)時(shí)應(yīng)該掃描多少頁(yè)面:

/sys/kernel/mm/transparent_hugepage/khugepaged/pages_to_scan

以及每次通過(guò)之間在khugepaged中等待毫秒數(shù)(你可以設(shè)置為0來(lái)運(yùn)行khugepaged,在一個(gè)核的100%利用率):

/sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs

以及在khugepage中等待多少毫秒,如果有一個(gè)巨大的頁(yè)面分配失敗,以阻止下一次分配嘗試。

/sys/kernel/mm/transparent_hugepage/khugepaged/alloc_sleep_millisecs

khugepaged的進(jìn)度可以從坍縮的頁(yè)面數(shù)中看到:

/sys/kernel/mm/transparent_hugepage/khugepaged/pages_collapsed

每次通過(guò):

/sys/kernel/mm/transparent_hugepage/khugepaged/full_scans

max_ptes_none指定有多少額外的小頁(yè)面(即尚未映射的)可以在踏縮一組小頁(yè)到大頁(yè)中被分配(查詢(xún)到相應(yīng)的頁(yè)表項(xiàng)為空)。

/sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_none

較高的值會(huì)導(dǎo)致程序使用額外的內(nèi)存。數(shù)值越低,獲得的thp性能越低。max_ptes_none值只會(huì)浪費(fèi)很少的cpu時(shí)間,你可以忽略它。

max_ptes_swap指定當(dāng)將一組頁(yè)面坍縮(collapse)成一個(gè)透明的大頁(yè)面時(shí),可以從交換區(qū)換入多少頁(yè)面(查詢(xún)到相應(yīng)的頁(yè)表項(xiàng)為換出頁(yè)標(biāo)識(shí)符)。。

/sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_swap

較高的值會(huì)導(dǎo)致過(guò)多的交換IO并浪費(fèi)內(nèi)存。較低的值可以防止thp被坍縮,從而導(dǎo)致更少的頁(yè)面坍縮進(jìn)thp,內(nèi)存訪(fǎng)問(wèn)性能較低。

== 啟動(dòng)參數(shù) ==

你可以更改透明大頁(yè)sysfs啟動(dòng)時(shí)的默認(rèn)值,通過(guò)傳遞參數(shù)"transparent_hugepage=always" 或"transparent_hugepage=madvise" 或 "transparent_hugepage=never"到內(nèi)核命令行。

== tmpfs/shmem 中的大頁(yè)面 ==

您可以使用掛載選項(xiàng)控制tmpfs中的大頁(yè)分配策略"huge="。它可以有以下值:

"always": 每次需要新頁(yè)面時(shí),嘗試分配大頁(yè)面;

"never": 不要分配大頁(yè)面;

"within_size": 只有它將完全在i_size內(nèi)時(shí)才分配大頁(yè)。也尊重fadvise()/madvise()提示;

"advise": 只有在fadvise()/madvise()請(qǐng)求時(shí)才分配大頁(yè)面;

默認(rèn)策略為“never”。

“mount -o remount,huge= /mountpoint”在掛載后工作良好:重新掛載huge=never根本不會(huì)分解大頁(yè)面,只是停止更多的分配。

還有一個(gè)sysfs接口可以控制內(nèi)部shmem掛載的大頁(yè)分配策略:

/sys/kernel/mm/transparent_hugepage/shmem_enabled。

掛載用于SysV SHM, memfds,共享匿名映射(/dev/zero或MAP_ANONYMOUS)GPU驅(qū)動(dòng)的DRM對(duì)象,Ashmem。

除了上面列出的策略之外,shmem_enabled還允許另外兩個(gè)值:

"deny":用于在緊急情況下使用,以強(qiáng)制關(guān)閉所有掛載的大頁(yè)選項(xiàng);

"force": 為所有人強(qiáng)制提供大頁(yè)的選項(xiàng)——這對(duì)測(cè)試非常有用;

==需要重新啟動(dòng)應(yīng)用程序==

transparent_hugepage/enabled值和tmpfs掛載選項(xiàng)只影響未來(lái)的行為。因此,為了使它們有效,您需要重新啟動(dòng)任何可能使用大頁(yè)面的應(yīng)用程序。這也適用于在khugepaged中注冊(cè)的區(qū)域。

==監(jiān)控使用情況==

當(dāng)前使用的匿名透明大頁(yè)面的數(shù)量系統(tǒng)可以通過(guò)讀取/proc/meminfo中的AnonHugePages字段來(lái)訪(fǎng)問(wèn)。為了識(shí)別哪些應(yīng)用程序正在使用匿名透明的大頁(yè)面,讀取/proc/PID/smaps并統(tǒng)計(jì)為每個(gè)映射的AnonHugePages字段是必要的。

映射到用戶(hù)空間的文件透明大頁(yè)面數(shù)量可用通過(guò)讀取/proc/meminfo中的ShmemPmdMapped和ShmemHugePages字段。為了確定哪些應(yīng)用程序正在映射文件透明的巨大頁(yè)面,它讀取/proc/PID/smaps并統(tǒng)計(jì)為每個(gè)映射FileHugeMapped字段是必要的。

注意,讀取smaps文件時(shí)昂貴的,且經(jīng)常會(huì)產(chǎn)生開(kāi)銷(xiāo)。

在/proc/vmstat中有許多計(jì)數(shù)器可以用于監(jiān)視系統(tǒng)提供大頁(yè)面的成功程度。

thp_fault_alloc : 每當(dāng)處理缺頁(yè)異常時(shí),一個(gè)大頁(yè)面被成功分配,thp_fault_alloc就會(huì)增加。這適用于第一次出現(xiàn)缺頁(yè)異常和COW錯(cuò)誤。

thp_collapse_alloc:當(dāng)它發(fā)現(xiàn)一個(gè)范圍的頁(yè)面坍縮成一個(gè)大頁(yè),并有成功分配一個(gè)新的巨大頁(yè)來(lái)存儲(chǔ)數(shù)據(jù),thp_collapse_alloc會(huì)被khugepaged增加。

thp_fault_fallback:如果缺頁(yè)異常失敗的分配一個(gè)大頁(yè),則thp_fault_fallback被增加,而回退使用小頁(yè)面。

thp_collapse_alloc_failed:當(dāng)它發(fā)現(xiàn)一個(gè)范圍的頁(yè)面應(yīng)該被坍縮成一個(gè)大頁(yè),但是分配大頁(yè)失敗,thp_collapse_alloc_failed會(huì)被khugepaged增加。 thp_file_alloc:在文件大頁(yè)成功分配時(shí)遞增。

thp_file_mapped:每映射到一個(gè)文件大頁(yè)到用戶(hù)地址空間,thp_file_mapped就增加一次。

thp_split_page:在每次將一個(gè)巨大的頁(yè)面分裂為普通頁(yè)時(shí)遞增。發(fā)生這種情況的原因有很多,但都很常見(jiàn)原因是一個(gè)巨大的頁(yè)面是舊的,正在被回收。這個(gè)操作意味著分裂頁(yè)面映射的所有PMD。

thp_split_page_failed:如果內(nèi)核無(wú)法分裂大頁(yè),則增加thp_split_page_failed計(jì)數(shù)。如果頁(yè)面被人pin住了,就會(huì)發(fā)生這種情況。

thp_deferred_split_page:當(dāng)大頁(yè)被放到分裂隊(duì)列時(shí),thp_deferred_split_page計(jì)數(shù)被增加。當(dāng)一個(gè)巨大的頁(yè)面部分被unmap且分裂它將釋放一些內(nèi)存就會(huì)發(fā)生這種情況。分裂隊(duì)列上的頁(yè)將在內(nèi)存壓力下分裂。

thp_split_pmd:每當(dāng)pmd分裂成pte表時(shí),thp_split_pmd就會(huì)遞增。例如,當(dāng)應(yīng)用程序調(diào)用mprotect()或unmap()在大頁(yè)面的一部分。它不會(huì)分割大頁(yè)面,只是頁(yè)表?xiàng)l目。

thp_zero_page_alloc:thp_zero_page_alloc在每出現(xiàn)一個(gè)巨型零頁(yè)被成功地分配時(shí)遞增。它包括分配,放棄了與其他分配的競(jìng)爭(zhēng)。注意,這不算每次巨型零頁(yè)的映射,只有它的分配。

thp_zero_page_alloc_failed:如果內(nèi)核分配巨型零頁(yè)失敗并回退到使用小頁(yè),則thp_zero_page_alloc_failed會(huì)增加。

隨著系統(tǒng)老化,分配大頁(yè)的開(kāi)銷(xiāo)可能會(huì)很大,因?yàn)橄到y(tǒng)會(huì)使用內(nèi)存規(guī)整在內(nèi)存周?chē)鷣?lái)復(fù)制數(shù)據(jù),以釋放大頁(yè)供使用。在/proc/vmstat中有一些計(jì)數(shù)器可以幫助監(jiān)視這種開(kāi)銷(xiāo)。

compact_stall:每當(dāng)進(jìn)程停滯去允許內(nèi)存規(guī)整時(shí),compact_stall就會(huì)增加,以便一個(gè)巨大的頁(yè)面被釋放供使用。

compact_success:如果系統(tǒng)規(guī)整內(nèi)存和釋放一個(gè)大頁(yè)面供使用,則compact_success會(huì)增加(成功規(guī)整的次數(shù))。

compact_fail:如果系統(tǒng)試圖規(guī)整內(nèi)存但是失敗了,則compact_fail會(huì)增加(失敗規(guī)整的次數(shù))。

compact_pages_moved:每次移動(dòng)頁(yè)面時(shí),compact_pages_moved會(huì)增加。如果這個(gè)值是迅速增加的,說(shuō)明該系統(tǒng)就是復(fù)制大量的數(shù)據(jù)來(lái)滿(mǎn)足大頁(yè)面分配。復(fù)制的成本可能超過(guò)任何減少TLB misse的節(jié)省。

compact_pagemigrate_failed:在底層機(jī)制遞增移動(dòng)頁(yè)面失敗,compact_pagemigrate_failed會(huì)增加(規(guī)整時(shí),遷移頁(yè)面失敗次數(shù)) 。

compact_blocks_moved:每次內(nèi)存規(guī)整檢查時(shí)一個(gè)大頁(yè)面對(duì)齊的頁(yè)面范圍,compact_blocks_moved會(huì)增加。

可以使用函數(shù)跟蹤器來(lái)記錄在__alloc_pages_nodemask中花費(fèi)了多長(zhǎng)時(shí)間,并使用mm_page_alloc跟蹤點(diǎn)來(lái)確定哪些分配用于巨大的頁(yè)面。

== get_user_pages and follow_page ==

get_user_pages和follow_page如果在一個(gè)巨型的頁(yè)面上運(yùn)行,將返回往常一樣的頭頁(yè)或尾頁(yè)(就像他們?cè)趆ugetlbfs上做的一樣)。大多數(shù)gup用戶(hù)只關(guān)心實(shí)際的物理屬性頁(yè)的地址和它的臨時(shí)固定在I/O之后釋放是完整的,所以他們不會(huì)注意到頁(yè)面是巨型的。但如果有任何驅(qū)動(dòng)程序會(huì)在尾部的頁(yè)面結(jié)構(gòu)上損壞 page(用于檢查page->mapping或其他相關(guān)的位對(duì)于頭頁(yè)而不是尾頁(yè)),應(yīng)該更新為跳轉(zhuǎn)改為檢查頭頁(yè)。在任何頭/尾頁(yè)上引用都可以防止頁(yè)面被任何人分裂。

注意:這些不是GUP API的新約束,它們與hugetlbfs上的約束相同,所以任何能夠在hugetlbfs上處理GUP的驅(qū)動(dòng)程序也可以很好地處理透明的大頁(yè)面支持映射。

如果您不能處理由follow_page返回的復(fù)合頁(yè)面,那么可以將FOLL_SPLIT位指定為follow_page的參數(shù),這樣它將在返回大頁(yè)面之前分裂它們。例如,遷移將FOLL_SPLIT作為參數(shù)傳遞給follow_page,因?yàn)樗恢谰扌晚?yè)面,事實(shí)上它根本不能在hugetlbfs上工作(但由于FOLL_SPLIT,它在透明的巨型頁(yè)面上工作得很好)。遷移根本無(wú)法處理返回的大頁(yè)面(因?yàn)樗粌H檢查頁(yè)面的PFN并在復(fù)制期間pin住它,而且?guī)в谐R?guī)的pte/pmd映射)。

==優(yōu)化應(yīng)用程序==

為了保證內(nèi)核將立即在任何內(nèi)存區(qū)域映射2M頁(yè),mmap區(qū)域必須自然對(duì)齊。posix_memalign()可以提供這種保證。

== Hugetlbfs ==

您可以在內(nèi)核中使用hugetlbfs,并且始終很好地啟用了透明的超大頁(yè)支持。hugetlbfs中除了整體碎片更少之外,沒(méi)有什么不同。所有屬于hugetlbfs的常見(jiàn)特性都被保留且不受影響。libhugetlbfs也會(huì)像往常一樣正常工作。

==優(yōu)雅回退==

代碼遍歷頁(yè)表但不能感知巨型的pmds,可以簡(jiǎn)單地調(diào)用split_huge_pmd(vma, pmd, addr),其中pmd是pmd_offset返回的那個(gè)。通過(guò)查詢(xún)“pmd_offset”并在pmd_offset返回pmd后丟失的地方添加split_huge_pmd,使代碼透明地感知大頁(yè)是很簡(jiǎn)單的。多虧了優(yōu)雅的回退設(shè)計(jì),只需一行代碼的更改,就可以避免編寫(xiě)數(shù)百行(如果不是數(shù)千行的話(huà))的復(fù)雜代碼,從而使代碼具有超大頁(yè)面的感知能力。

如果您沒(méi)有遍歷頁(yè)表,但是遇到了一個(gè)物理的大頁(yè),但是您不能在代碼中原生地處理它,您可以通過(guò)調(diào)用split_huge_page(page)來(lái)分裂它。這就是Linux VM在嘗試切換大頁(yè)面之前所做的。如果頁(yè)面被pin住,那么split_huge_page()可能會(huì)失敗,您必須正確處理這個(gè)問(wèn)題。

讓mremap.c透明感知hugepage的例子,只需要一行代碼的改變:

diff --git a/mm/mremap.c b/mm/mremap.c
--- a/mm/mremap.c
+++ b/mm/mremap.c
@@ -41,6 +41,7 @@ static pmd_t *get_old_pmd(struct mm_stru
    return NULL;


  pmd = pmd_offset(pud, addr);
+  split_huge_pmd(vma, pmd, addr);
  if (pmd_none_or_clear_bad(pmd))
    return NULL;

== 鎖定大頁(yè)面感知代碼 ==

我們希望盡可能多的代碼能夠感知大頁(yè),因?yàn)檎{(diào)用 split_huge_page()或split_huge_pmd()是有代價(jià)的。

要使頁(yè)表遍歷感知巨型pmd,您所需要做的就是調(diào)用pmd_trans_huge()在由pmd_offset返回的PMD上。你必須持有mmap_sem處于讀(或?qū)?模式,以確保不能出現(xiàn)巨型PMD由khugepaged創(chuàng)建 (khugepaged坍縮巨型頁(yè)collapse_huge_page除anon_vma鎖外,還以寫(xiě)模式持有mmap_sem)。

如果pmd_trans_huge返回false,您只需返回到舊代碼路徑。如果pmd_trans_huge返回true,則必須持有頁(yè)表鎖(pmd_lock()),然后重新運(yùn)行pmd_trans_huge。持有頁(yè)表鎖將防止巨型的PMD被轉(zhuǎn)換成一個(gè)常規(guī)的PMD(split_huge_pmd可以與頁(yè)表遍歷并行)。如果第二個(gè)pmd_trans_huge返回false,則應(yīng)該釋放頁(yè)表鎖并回退到之前的舊代碼中。否則,您可以繼續(xù)處理巨型的pmd和hugepage本身。一旦完成,您可以釋放頁(yè)表鎖。

== 引用計(jì)數(shù)和透明大頁(yè) ==

THP上的引用計(jì)數(shù)和其他復(fù)合頁(yè)的引用計(jì)數(shù)基本一致:

get_page()/put_page() and GUP 在首頁(yè)的->_refcount中操作。

尾頁(yè)的->_refcoun總是0:get_page_unless_zero()從來(lái)不會(huì)在尾頁(yè)上成功。

map/unmap具有帶有PTE條目的頁(yè)面,增加/減小復(fù)合頁(yè)相關(guān)子頁(yè)上的->_mapcount。

map/unmap 整個(gè)復(fù)合頁(yè)的被記賬在compound_mapcount(存儲(chǔ)在第一個(gè)尾頁(yè))。對(duì)于文件巨型頁(yè),我們也增加所有子頁(yè)面的->_mapcount,以便無(wú)競(jìng)爭(zhēng)檢測(cè)子頁(yè)面的最后一次unmap。

PageDoubleMap()表示頁(yè)面可能映射了pte。

對(duì)于匿名頁(yè)面,PageDoubleMap()還表示->_mapcount在所有子頁(yè)面中被抵消了一個(gè)。此附加引用是必需的,當(dāng)子頁(yè)面同時(shí)被映射到PMDs和 PTEs時(shí),獲得對(duì)其子頁(yè)面unmap的無(wú)競(jìng)爭(zhēng)檢測(cè)。

這是降低每個(gè)子頁(yè)面的mapcount跟蹤開(kāi)銷(xiāo)所需的優(yōu)化。另一種方法是在整個(gè)復(fù)合頁(yè)面的每個(gè)map/unmap上的所有子頁(yè)面中添加 ->_mapcount。

對(duì)于匿名頁(yè)面,當(dāng)頁(yè)面的PMD被分裂時(shí),但仍有PMD映射,我們?cè)O(shè)置PG_double_map。額外的引用去掉最后一個(gè)compound_mapcount。

文件頁(yè)面在帶有PTE和的頁(yè)面的第一個(gè)映射上設(shè)置PG_double_map ,當(dāng)頁(yè)面從頁(yè)面緩存中被驅(qū)逐時(shí),該頁(yè)面就會(huì)消失。

split_huge_page內(nèi)部必須在從頭頁(yè)到尾頁(yè)分配refcount,然后清除頁(yè)面結(jié)構(gòu)中所有的PG_head/尾位。它可以很容易地實(shí)現(xiàn)頁(yè)表?xiàng)l目的引用計(jì)數(shù)。但我們沒(méi)有足夠的信息來(lái)分發(fā)額外的pins(即get_user_pages)。split_huge_page()請(qǐng)求去分裂pin住的大頁(yè)面是失敗的: 它期望頁(yè)面計(jì)數(shù)等于所有子頁(yè)面的mapcount之和加上1 (split_huge_page調(diào)用者必須有頭頁(yè)引用)。

split_huge_page使用遷移條目來(lái)穩(wěn)定匿名頁(yè)面的page->_refcount和page->_mapcount。文件頁(yè)面被取消映射。

我們和物理內(nèi)存掃描器(頁(yè)面回收的掃描器)競(jìng)爭(zhēng)也是安全的:掃描器來(lái)獲取對(duì)頁(yè)面的引用唯一合法的方式是get_page_unless_zero()。

在atomic_add()之前,所有尾頁(yè)的->_refcount都為0。這可以防止掃描器獲取到尾頁(yè)的引用。在atomic_add()之后,我們不關(guān)心->_refcount值。我們已經(jīng)從頭頁(yè)上知道有多少引用是取消記賬的。

對(duì)于頭頁(yè),get_page_unless_zero()會(huì)成功,我們不介意。它是明確拆分后引用應(yīng)該去哪里:它將停留在首頁(yè)。

注意split_huge_pmd()對(duì)refcount沒(méi)有任何限制:PMD可以在任何點(diǎn)被拆分并且永不失敗。

== 部分 unmap and deferred_split_huge_page() ==

解除THP部分映射(使用munmap()或其他方式)不會(huì)立即釋放內(nèi)存。相反,我們?cè)趐age_remove_rmap()中檢測(cè)到THP的一個(gè)子頁(yè)面沒(méi)有被使用,并在內(nèi)存壓力時(shí),將THP排隊(duì)以進(jìn)行拆分。分裂將釋放未使用的子頁(yè)面。

由于將上下文鎖住在我們可以檢測(cè)到部分unmap的地方,所以不能立即拆分頁(yè)面。這也可能會(huì)適得其反,因?yàn)樵谠S多情況下,如果THP跨越VMA邊界,在exit(2)期間會(huì)發(fā)生部分unmap。

用于對(duì)頁(yè)面進(jìn)行排隊(duì)以進(jìn)行拆分。當(dāng)我們通過(guò)shrinker收縮器接口獲得內(nèi)存壓力時(shí),分裂本身就會(huì)發(fā)生。






審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀(guān)點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • cpu
    cpu
    +關(guān)注

    關(guān)注

    68

    文章

    10898

    瀏覽量

    212599
  • LINUX內(nèi)核
    +關(guān)注

    關(guān)注

    1

    文章

    316

    瀏覽量

    21698
  • 虛擬內(nèi)存
    +關(guān)注

    關(guān)注

    0

    文章

    77

    瀏覽量

    8072
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    基于Linux內(nèi)核透明代理配置方案

    本內(nèi)容提供了基于Linux內(nèi)核透明代理配置方案,先解釋為什么要配置透明代理,如何利用Linux內(nèi)核
    發(fā)表于 11-03 16:47 ?879次閱讀

    詳解Linux內(nèi)核源碼組織結(jié)構(gòu)

    概要:本文內(nèi)容包含Linux源碼樹(shù)結(jié)構(gòu)分析、Linux Makefile分析、Kconfig文件分析、Linux內(nèi)核配置選項(xiàng)分析。這些知識(shí)是為了理解
    的頭像 發(fā)表于 05-10 19:28 ?5806次閱讀

    Linux內(nèi)核之內(nèi)存映射原理分析

    Linux 內(nèi)核采用延遲分配物理內(nèi)存的策略,在進(jìn)程第次訪(fǎng)問(wèn)虛擬頁(yè)的時(shí)候,產(chǎn)生缺頁(yè)異常。如果是文件映射,那么分配物理頁(yè),把文件指定區(qū)間的數(shù)據(jù)
    發(fā)表于 07-21 17:06 ?2374次閱讀

    ?Linux內(nèi)核透明巨型頁(yè)支持

    處理大內(nèi)存的性能關(guān)鍵計(jì)算應(yīng)用程序工作集已經(jīng)運(yùn)行在libhugetlbfs之上,然后依次運(yùn)行hugetlbfs。透明巨型頁(yè)面支持是另種使用大頁(yè)
    發(fā)表于 10-27 09:15 ?542次閱讀

    搞懂Linux內(nèi)核鏈表

    hello 大家好,今天給大家介紹linux 內(nèi)核鏈表的分析,在寫(xiě)這篇文章前,筆者自己以前也只是停留在應(yīng)用層面,沒(méi)有深究其中的細(xì)節(jié),很多也是理解的不是很透徹。寫(xiě)完此文后,發(fā)現(xiàn)對(duì)鏈表的理解更加深刻了。很多現(xiàn)代計(jì)算機(jī)的思想在
    發(fā)表于 11-14 09:17 ?1084次閱讀

    Linux內(nèi)核地址映射模型與Linux內(nèi)核高端內(nèi)存詳解

    的數(shù)據(jù)可能不在內(nèi)存中。 Linux內(nèi)核地址映射模型 x86 CPU采用了段頁(yè)式地址映射模型。進(jìn)程代碼中的地址為邏輯地址,經(jīng)過(guò)段頁(yè)式地址映射后,才真正訪(fǎng)問(wèn)物理內(nèi)存。 段
    發(fā)表于 05-08 10:33 ?3476次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>地址映射模型與<b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>高端內(nèi)存詳解

    請(qǐng)教內(nèi)核默認(rèn)支持網(wǎng)卡的巨型幀(Jumbo Frame)嗎?

    問(wèn)題描述及復(fù)現(xiàn)步驟:請(qǐng)教內(nèi)核默認(rèn)支持網(wǎng)卡的巨型幀(Jumbo Frame)嗎?現(xiàn)在想把網(wǎng)卡的MTU設(shè)置到9000,但是直失敗。操作指令
    發(fā)表于 11-14 17:54

    詳解Linux內(nèi)核測(cè)試現(xiàn)狀

    is going on there? Is stable kernel really stable? 剛好今年9月在洛杉磯舉辦的《Linux Plumbers Conference》有個(gè)BOF(birds
    的頭像 發(fā)表于 01-01 09:06 ?3221次閱讀

    Linux內(nèi)核5.4系列宣布全面可用,支持微軟exFAT文件系統(tǒng)

    近期,Linux內(nèi)核5.4系列宣布全面可用,添加了許多新功能,更強(qiáng)的安全性和更新的驅(qū)動(dòng)程序,以提供更好的硬件支持Linux內(nèi)核5.4增加對(duì)
    的頭像 發(fā)表于 11-28 16:07 ?4256次閱讀

    Linux 5.7將支持Zstd壓縮算法

    Linux 5.6 引入了可選的 F2FS 透明數(shù)據(jù)壓縮支持,并通過(guò) LZO 和 LZ4 壓縮算法實(shí)現(xiàn)。現(xiàn)在,Linux 5.7 內(nèi)核正在
    的頭像 發(fā)表于 03-26 15:15 ?2851次閱讀

    ARM64 Linux內(nèi)核頁(yè)表的塊映射

    內(nèi)核文檔Documentation/arm64/memory.rst描述了ARM64 Linux內(nèi)核空間的內(nèi)存映射情況,應(yīng)該是此方面最權(quán)威文檔。 以典型的4K頁(yè)和48位虛擬地址為例,整
    的頭像 發(fā)表于 01-04 13:37 ?2626次閱讀
    ARM64 <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>頁(yè)</b>表的塊映射

    解析Linux內(nèi)核頁(yè)表管理中那些鮮為人知的秘密

    虛擬內(nèi)存管理,而頁(yè)表管理是在虛擬內(nèi)存管理中尤為重要,本文主要以回答幾個(gè)頁(yè)表管理中關(guān)鍵性問(wèn)題來(lái)解析Linux內(nèi)核頁(yè)表管理,看
    的頭像 發(fā)表于 06-11 16:32 ?1685次閱讀

    學(xué)習(xí)linux內(nèi)核些建議

    學(xué)習(xí)linux內(nèi)核,這個(gè)可不像學(xué)門(mén)語(yǔ)言,c或者java個(gè)月或者3月你就能精通掌握。學(xué)習(xí)linux內(nèi)核
    發(fā)表于 05-07 15:20 ?634次閱讀
    學(xué)習(xí)<b class='flag-5'>linux</b><b class='flag-5'>內(nèi)核</b>的<b class='flag-5'>一</b>些建議

    Linux內(nèi)核中整合對(duì) Rust 的支持

    Linux Plumbers Conference 2022 大會(huì)上舉行了個(gè) Rust 相關(guān)的小型會(huì)議,該會(huì)議討論的大方向大致為:正在進(jìn)行的使 Rust 成為種合適的系統(tǒng)編程語(yǔ)言的工作,以及在主線(xiàn)
    的頭像 發(fā)表于 09-19 11:06 ?1201次閱讀

    搞懂Linux系統(tǒng)內(nèi)核的重要性

    今天我要跟大家分享Linux內(nèi)核的重要性。內(nèi)核就像Linux系統(tǒng)運(yùn)行的大心臟,對(duì)系統(tǒng)的運(yùn)行起到了至關(guān)重要的作用。那么
    的頭像 發(fā)表于 03-24 15:16 ?957次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b>搞懂<b class='flag-5'>Linux</b>系統(tǒng)<b class='flag-5'>內(nèi)核</b>的重要性