最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

萬字詳解Linux內(nèi)核內(nèi)存規(guī)整!超詳細(xì)!

2023-11-11 15:03 作者:補給站Linux內(nèi)核  | 我要投稿

1.前言

伙伴系統(tǒng)作為內(nèi)核最基礎(chǔ)的物理頁內(nèi)存分配器,具有高效、實現(xiàn)邏輯簡介等優(yōu)點,其原理頁也盡可能降低內(nèi)存外部碎片產(chǎn)生,但依然無法杜絕碎片問題。外部碎片帶來的最大影響就是內(nèi)存足夠,但是卻無法滿足內(nèi)存分配需求,如下圖所示:

內(nèi)存外部碎片導(dǎo)致實際占用物理頁不多,但是已無法申請>=4個頁連續(xù)內(nèi)存,理想當(dāng)中我們希望內(nèi)存沒有外部碎片,如下圖所示:

內(nèi)核并未為此目標(biāo)設(shè)計新的內(nèi)存分配算法(伙伴系統(tǒng)足夠簡單和高效),其選擇在伙伴系統(tǒng)基礎(chǔ)上根據(jù)內(nèi)存使用需求進行內(nèi)存分配,將不可移動內(nèi)存和可移動內(nèi)存歸類,在內(nèi)存碎片問題出現(xiàn)時,嘗試進行內(nèi)存規(guī)整(compact),移動可移動的頁面,騰出更多連續(xù)內(nèi)存,如下圖簡述:

上圖中將一個頁移動到另一個頁的過程叫頁遷移,這并不是一件輕松的事情,數(shù)據(jù)的拷貝、進程映射信息更改等等都很耗時并且也是個復(fù)雜邏輯,這注定內(nèi)存規(guī)整的過程是一個重負(fù)載的過程。事實上,頁遷移是內(nèi)存管理的獨立邏輯,內(nèi)核對此單獨封裝接口migrate_pages,內(nèi)存規(guī)整只是其中一個應(yīng)用場景,類似場景還有NUMA Balance、Memory hotplug及CMA內(nèi)存等等。本文聚焦內(nèi)存規(guī)整,不描述內(nèi)存遷移邏輯。


站在開發(fā)者角度有了內(nèi)存遷移基礎(chǔ)能力,那么就有實現(xiàn)內(nèi)存規(guī)整基礎(chǔ),但依然有值得思考的問題,比如內(nèi)存規(guī)整的范圍,何時進行內(nèi)存規(guī)整等等。


對于內(nèi)存規(guī)整范圍問題,內(nèi)核通常選擇以zone為單位進行規(guī)整(實際范圍受到參數(shù)影響可能為zone一部分),并為此封裝compact_zone接口,作為內(nèi)存規(guī)整核心接口(alloc_contig_range例外)。


對于何時觸發(fā)問題,屬于觸發(fā)策略和場景問題,內(nèi)核當(dāng)前引入直接內(nèi)存規(guī)整、被動內(nèi)存規(guī)整、預(yù)應(yīng)性內(nèi)存規(guī)整及主動內(nèi)存規(guī)整四種策略場景,這些場景最終都會通過compact_zone進行內(nèi)存規(guī)整,但是他們觸發(fā)的時機不同、目標(biāo)不同、規(guī)整范圍不同、規(guī)整退出條件不同,規(guī)整強度不同等等?;A(chǔ)能力和策略分離設(shè)計是內(nèi)核的基礎(chǔ)設(shè)計理念。


如上圖所示,內(nèi)存規(guī)整是基于內(nèi)存遷移實現(xiàn)的功能,內(nèi)核根據(jù)策略在不同實際觸發(fā)內(nèi)存規(guī)整,用于緩解內(nèi)存外部碎片問題,可以分層分析看待內(nèi)存規(guī)整。


2.內(nèi)存規(guī)整場景

前言中已說明內(nèi)核當(dāng)前觸發(fā)內(nèi)存規(guī)整的策略有四種,為便于查看和直觀理解,優(yōu)先羅列四種場景特點,見下表:



上述表格中各規(guī)整策略詳見2.1~2.4節(jié)描述,compact_control各種含義,詳見3.1節(jié)描述。

FAQ:

(1)直接內(nèi)存規(guī)整較為特殊,內(nèi)存分配過程中如果觸發(fā)直接內(nèi)存規(guī)整依然無法分配內(nèi)存,那么有可能循環(huán)調(diào)用并且提高內(nèi)存規(guī)整的級別,因此出現(xiàn)首次和重試之分。

(2)規(guī)整頁類型中不包含不可回收頁,除非通過sysctl_compact_unevictable_allowed進行設(shè)置。

(3)內(nèi)存規(guī)整中有一個特例就是alloc_contig_range函數(shù),該函數(shù)用于分配指定地址區(qū)域內(nèi)存,若這部分內(nèi)存被占用,會嘗試對這段內(nèi)存進行規(guī)整遷移,其并非針對zone的規(guī)整,而是針對指定內(nèi)存區(qū)域的規(guī)整,它的規(guī)整類型與主動內(nèi)存規(guī)整類似,其實現(xiàn)核心是內(nèi)存規(guī)整機制,本文不對此邏輯進行說明。


2.1 直接內(nèi)存規(guī)整(direct compaction)


2.1.1 直接內(nèi)存規(guī)整觸發(fā)條件

伙伴系統(tǒng)分配內(nèi)存時,會先以low水線為基準(zhǔn)調(diào)用get_page_from_freelist函數(shù)嘗試進行內(nèi)存分配,如果失敗則會進入慢速內(nèi)存分配流程,即__alloc_pages_slowpath函數(shù),我們對此函數(shù)邏輯稍作刪減,內(nèi)容如下:

慢速內(nèi)存分配,會嘗試喚醒kswapd進行內(nèi)存回收,但并不會等待內(nèi)存回收的結(jié)果,而是直接先調(diào)用get_page_from_freelist函數(shù)嘗試內(nèi)存分配,但這次不同的是使用min水線進行嘗試,如果依然失敗,那么將會根據(jù)gfp標(biāo)識確認(rèn)當(dāng)前分配是否支持直接內(nèi)存回收,若支持,將會調(diào)用__alloc_pages_direct_compact嘗試第一次直接內(nèi)存規(guī)整以及內(nèi)存分配。如果依然失敗,則進入喚醒kswapd、get_page_from_freelist、__alloc_pages_direct_reclaim及__alloc_pages_direct_compact循環(huán)調(diào)用流程里面來,當(dāng)然這之中存在眾多條件判斷隨時可能返回頁分配失敗、頁分配成功、重試甚至是觸發(fā)OOM。值得注意的是在慢速內(nèi)存分配邏輯中,首次調(diào)用直接內(nèi)存規(guī)整時其優(yōu)先級設(shè)置為INIT_COMPACT_PRIORITY,這將影響內(nèi)存規(guī)整觸發(fā)頁遷移的類型,比如INIT_COMPACT_PRIORITY對應(yīng)的就是MIGRATE_ASYNC即異步遷移類型代表頁遷移時不會阻塞,當(dāng)然這樣帶來的效果就是規(guī)整或遷移的能力較弱。慢速內(nèi)存分配邏輯中后續(xù)直接內(nèi)存規(guī)整調(diào)用其規(guī)整優(yōu)先級可能會逐步降低(越低對應(yīng)規(guī)整強度越高)從而提升內(nèi)存規(guī)整效用,但是內(nèi)存規(guī)整可能變?yōu)樽枞?guī)整,這是相互對應(yīng)邏輯。


通過上述描述,可以初步了解直接內(nèi)存規(guī)整起到的作用,也可以感受到內(nèi)核內(nèi)存分配進入到慢速分配邏輯后性能的代價。


另一方面,直接內(nèi)存規(guī)整實際是由于伙伴系統(tǒng)無法分配內(nèi)存時觸發(fā),因此直接內(nèi)存規(guī)整目標(biāo)并也并非消除整個zone的外部碎片,而只是通過內(nèi)存規(guī)整遷移出目標(biāo)階連續(xù)內(nèi)存。


2.1.2 直接內(nèi)存規(guī)整邏輯說明


__alloc_pages_direct_compact函數(shù)是直接內(nèi)存規(guī)整運行入口,該函數(shù)核心內(nèi)容如下:


try_to_compact_pages函數(shù)將會進一步調(diào)用compact相關(guān)流程進行規(guī)整,規(guī)整完成后調(diào)用get_page_from_freelist進行內(nèi)存分配。try_to_compact_pages核心代碼邏輯如下:


try_to_compact_pages函數(shù)核心,遍歷規(guī)定范圍內(nèi)zone,針對每個zone調(diào)用compaction_deferred確認(rèn)其是否合適進行規(guī)整,若合適進一步調(diào)用compact_zone_order函數(shù)進行規(guī)整,規(guī)整成功則直接內(nèi)存規(guī)整將會直接返回。在try_to_compact_pages函數(shù)中我們重點說明一下zone延遲判斷邏輯,這部分邏輯同樣適用于后續(xù)kcompactd對于zone的判斷。


2.1.2.1 延遲規(guī)整


compaction_deferred函數(shù)用于判斷當(dāng)前zone是否需要進行延遲處理,延遲的目的是避免頻繁或無效的內(nèi)存規(guī)整,其引入兩個機制用于延遲,一個是內(nèi)存規(guī)整失敗階判斷,另一個是內(nèi)存規(guī)整延遲次數(shù)判斷(這更像一種計時器)。


A) 規(guī)整失敗階的判斷(compact_order_failed)

如果當(dāng)前規(guī)整階大于等于zone的最大規(guī)整失敗階,那么代表當(dāng)前再去規(guī)整失敗的可能性很高,建議延遲對當(dāng)前zone規(guī)整。


B) 內(nèi)存規(guī)整延遲次數(shù)超閾值判斷

如果失敗階判斷滿足,那么會對延遲次數(shù)進行判斷,compact_considered記錄了當(dāng)前zone延遲次數(shù),compaction_deferred每次調(diào)用時compact_considered都會累加,如果其小于閾值,那么建議zone不進行規(guī)整,標(biāo)識近期可能已經(jīng)進行過規(guī)整。

可以想象到,延遲判斷的這些參數(shù)會動態(tài)變化,實際如上圖所示。


A) 當(dāng)內(nèi)存規(guī)整成功時,調(diào)用compaction_defer_reset函數(shù)清空compact_considered延遲計數(shù),清空compact_defer_shift延遲計數(shù)閾值(defer_limit = compact_defer_shift << 1),同時如果當(dāng)前order大于等于compact_defer_shift ,則更新compact_order_failed最大規(guī)整失敗階。


總結(jié),當(dāng)規(guī)整成功時,會降低此zone延遲標(biāo)準(zhǔn),讓后續(xù)對zone規(guī)整判斷變得更為容易。


B) 當(dāng)內(nèi)存規(guī)整失敗時,依然會將compact_considered清零,若order大于更新更新compact_order_failed最大規(guī)整失敗階,增大compact_defer_shift延遲計數(shù)閾值。


總結(jié),當(dāng)規(guī)整失敗時,會增大此zone延遲標(biāo)準(zhǔn),讓后續(xù)對zone規(guī)整將會延遲更多次。


通過上述延遲方案,確保對于某個zone不做重復(fù)規(guī)整、不做成功率低的規(guī)整,當(dāng)一次對zone規(guī)整失敗時,內(nèi)核將會盡量給與zone足夠時間然后再進行嘗試。zone延遲判斷機制適用于直接內(nèi)存規(guī)整以及kcompactd內(nèi)存規(guī)整機制,這兩種機制對于耗時較為敏感,其它場景內(nèi)存規(guī)整通常不需要此機制。


2.1.2.2 capture_control說明


再次回到try_to_compact_pages函數(shù),一個zone在通過延遲判斷后,將會調(diào)用compact_zone_order函數(shù),該函數(shù)核心是定義compact_control并調(diào)用compact_zone完成規(guī)整。但是這里引入了一個很有意思的機制capture_control,因此需要額外進行說明,這筆修改可見如下內(nèi)容:


通常的邏輯通過內(nèi)存規(guī)整遷移出目標(biāo)階內(nèi)存塊,再進行內(nèi)存分配,而為了提效capture_control的思路則是在內(nèi)存規(guī)整的過程中就將內(nèi)存分配出來,只不過這個分配更像是截胡,在直接內(nèi)存規(guī)整的過程中,若發(fā)生內(nèi)存釋放,則在伙伴系統(tǒng)內(nèi)存釋放邏輯中截胡合適的內(nèi)存,下面詳細(xì)說明這個過程。


在compact_zone_order函數(shù)會填充capture_control變量,并將其賦值給當(dāng)前進程上下文,標(biāo)志著當(dāng)前進程進入到直接規(guī)整邏輯里面??梢韵胂笤趦?nèi)存規(guī)整過程中涉及內(nèi)存釋放,此時capture開始行動,代碼如下:



內(nèi)存釋放流程中通過compaction_capture嘗試捕獲已釋放的內(nèi)存,compaction_capture函數(shù)代碼實現(xiàn)如下:



釋放內(nèi)存階必須與直接內(nèi)存規(guī)整階相等才有可能捕獲,同時需要強調(diào)如果當(dāng)前釋放的是MIGRATE_MOVABLE類型頁盡量不去捕獲,避免污染可移動頁面,因為觸發(fā)直接規(guī)整的有可能是不可移動的內(nèi)存請求。


2.1.3 直接內(nèi)存規(guī)整特點


(1)指定了規(guī)整目標(biāo)階,降低規(guī)整范圍和難度;

(2)遷移頁掃描器和空閑頁掃描器,使用快速掃描能力;

(3)其指定highest_zoneidx和目標(biāo)階,因此存在水線判斷。

(4)direct_compaction設(shè)置直接規(guī)整標(biāo)識;

直接內(nèi)存規(guī)整優(yōu)先級COMPACT_PRIO_ASYNC逐步升高,內(nèi)存規(guī)整強度將會增強,內(nèi)容如下:

(5)隨著規(guī)整失敗,規(guī)整模式MIGRATE_ASYNC變?yōu)镸IGRATE_SYNC_LIGHT,即直接內(nèi)存規(guī)整可能是不阻塞也可能是阻塞模式;

(6)隨著規(guī)整失敗,規(guī)整范圍從根據(jù)上次規(guī)整結(jié)果制定范圍變?yōu)橥暾鹺one地址范圍;

(7)隨著規(guī)整失敗,pageblock將會被重新掃描,不會根據(jù)標(biāo)記skip,逐步加強規(guī)整強度;

(8)隨著規(guī)整失敗,空閑頁掃描器將變得嚴(yán)格,空閑頁必須來自于MIGRATE_MOVABLE和MIGRATE_CMA可移動的頁面;

上述compact_control結(jié)構(gòu)體參數(shù)含義見3.1節(jié);


【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ? ?

零聲白金VIP體驗卡(含基礎(chǔ)架構(gòu)/高性能存儲/golang/QT/音視頻/Linux內(nèi)核)課程:



2.2 被動內(nèi)存規(guī)整(kcompactd)


內(nèi)核在啟動過程中會調(diào)kcompactd_init函數(shù),為每個node啟動一個kcompactd內(nèi)核線程,并且kcompactd線程會運行在與node相對應(yīng)的CPU核上,在合適的時機kcompactd將會被喚醒進行內(nèi)存規(guī)整,這就是被動內(nèi)存規(guī)整邏輯。一個特殊場景是若開啟proactive compaction功能,那么kcompactd會被周期性喚醒。


本節(jié)主要從三個方面說明,分別是kcompactd喚醒條件、kcompactd運行條件、以及kcompactd內(nèi)存規(guī)整特點(kcompactd被喚醒不一定會進行內(nèi)存規(guī)整)。


2.2.1 kcompactd喚醒條件


內(nèi)存規(guī)整模塊向內(nèi)核提供wakeup_kcompactd口用于喚醒node對應(yīng)的kcompactd線程,內(nèi)核中kcompactd喚醒與kswapd強相關(guān),總結(jié)如下場景會被被動喚醒:


FAQ:這里指的觸發(fā)內(nèi)存規(guī)整,指的是調(diào)用wakeup_kcompactd函數(shù),未必真的進行內(nèi)存規(guī)整,wakeup_kcompactd還存在諸多判斷;


2.2.1.1 kswapd運行前觸發(fā)內(nèi)存規(guī)整


當(dāng)內(nèi)存分配失敗時經(jīng)各種判斷后,會進?內(nèi)存慢速分配過程,此時伙伴系統(tǒng)將嘗試喚醒內(nèi)存回收,在這個過程中,有如下關(guān)鍵代碼:



上述代碼為未喚醒kswapd前進行內(nèi)存規(guī)整的條件判斷,其意圖如下:


(1)kswapd內(nèi)存回收失敗多次;

(2)根據(jù)pgdat_balanced函數(shù)判斷當(dāng)前水位安全,即存在?夠可?內(nèi)存并且未出現(xiàn)”偷“內(nèi)存情況;本質(zhì)在于,當(dāng)前內(nèi)存無法分配的原因并非低內(nèi)存,此時內(nèi)存回收可能已經(jīng)無法解決此問題時,wakeup_kswapd函數(shù)將會提前進行內(nèi)存規(guī)整。這里還需要說明的是,內(nèi)存分配指定不支持直接內(nèi)存回收時上述邏輯才能生效,這是因為若支持直接內(nèi)存規(guī)整,則可以借助直接內(nèi)存回收來進行改善并且通常直接內(nèi)存規(guī)整有更好的性能表現(xiàn)。


2.2.1.2 kswapd運行中觸發(fā)內(nèi)存規(guī)整


watermark_boost_factor導(dǎo)致的內(nèi)存規(guī)整,歸類為kswapd運行中觸發(fā)內(nèi)存規(guī)整稍有牽強,不過其確實是在kswapd內(nèi)存規(guī)整核心邏輯中觸發(fā)。這里簡單介紹?下watermark_boost_factor特性,當(dāng)分配內(nèi)存時如果在對應(yīng)migrate type上沒有分配到內(nèi)存,那么系統(tǒng)將會從fall_back的migrate type進行內(nèi)存分配,有時將其叫做”偷“,由于分配了不匹配遷移類型的內(nèi)存,內(nèi)核會認(rèn)為這可能存在外部碎片的風(fēng)險,所以當(dāng)出現(xiàn)這種”偷“時內(nèi)核會提前進行內(nèi)存回收及規(guī)整,從而降低后續(xù)”偷“行為的發(fā)生,避免內(nèi)存碎片問題,提升內(nèi)存分配的效率,這就是watermark_boost_factor特性。


steal_suitable_fallback函數(shù)是從其它遷移類型上分配內(nèi)存的核心邏輯,此函數(shù)中會設(shè)置?個ZONE_BOOSTED_WATERMARK標(biāo)志位,這個標(biāo)志位只能被kswapd清除,伙伴系統(tǒng)在內(nèi)存分配成功后,如果發(fā)現(xiàn)ZONE_BOOSTED_WATERMARK被置位,將會喚醒kswapd線程。


kswapd函數(shù)通過調(diào)用balance_pgdat進行內(nèi)存回收,而balance_pgdat函數(shù)會在整體內(nèi)存回收結(jié)束后,嘗試喚醒kcompactd線程,當(dāng)然前提是本次內(nèi)存回收是boosted屬性的內(nèi)存回收。


boost特性引起內(nèi)存規(guī)整,其規(guī)整階為pageblock對應(yīng)階,在后續(xù)compact_zone函數(shù)介紹中將會說明其影響。上述僅僅簡單描述watermark_boost_factor特性,其還涉及到內(nèi)存回收時boost導(dǎo)致水線提升等特性,由于其與內(nèi)存回收關(guān)系較大,本文不深入分析,大家可通過如下合入記錄進?步了解:



2.2.1.3 kswapd運行后觸發(fā)內(nèi)存規(guī)整


kswapd內(nèi)核線程,每次在內(nèi)存回收完畢后將會調(diào)?kswapd_try_to_sleep嘗試休眠,此函數(shù)會在休眠前調(diào)用reset_isolation_suitable函數(shù)清空遷移掃描器和空閑頁掃描器掃描過程中產(chǎn)生的緩存數(shù)據(jù)(比如某個pageblock是否需要跳過信息等等),隨后調(diào)用wakeup_kcompactd函數(shù)嘗試進行內(nèi)存規(guī)整。


簡單說,每次kswapd運行完成后都會嘗試喚醒內(nèi)存規(guī)整線程,但是內(nèi)存規(guī)整線程是否真的需要運行,其有復(fù)雜的判斷邏輯。


2.2.2 kcompactd運行條件


當(dāng)kcompactd線程運行時,若當(dāng)前是非預(yù)應(yīng)性規(guī)整(詳見2.3節(jié)),那么將會調(diào)用

kcompactd_do_work函數(shù)進行被動內(nèi)存規(guī)整。kcompactd_do_work函數(shù)會遍歷當(dāng)前node的所有zone進行合法性判斷,若符合條件,則調(diào)用compact_zone針對zone進?內(nèi)存規(guī)整,其對zone的判斷邏輯如下:


根據(jù)上述代碼,可以總結(jié)如下判斷邏輯:

A. zone是否包含有效物理頁,若不包含,不需要規(guī)整;

B. 當(dāng)前規(guī)整目標(biāo)階是否大于等于之前規(guī)整失敗的最小階,若大于不需要規(guī)整;

C. 是否需要延遲規(guī)整,若延遲次數(shù)超過閾值則須規(guī)整,否則不規(guī)整,避免無效規(guī)整;

D. 當(dāng)前內(nèi)存水線是否滿足內(nèi)存申請,若滿足則不規(guī)整;

E. 若當(dāng)前order大于3同時不滿足內(nèi)存分配,則評估其內(nèi)存碎片程度,若小于閾值,則不規(guī)整;其中,populated_zone函數(shù)僅僅判斷當(dāng)前zone是否存在有效物理頁,其它邏輯在compaction_deferred和compaction_suitable函數(shù)中實現(xiàn)。


2.2.2.1 延遲規(guī)整


在直接內(nèi)存規(guī)整中已經(jīng)介紹了延遲判斷的邏輯,對于kcompactd同樣使用,下述代碼可以看到其對延遲參數(shù)的更新邏輯。


如果comapct_zone返回值是由于掃描器相遇導(dǎo)致(一輪掃描結(jié)束),而非規(guī)整目標(biāo)達成導(dǎo)致,代表規(guī)整并未達成目標(biāo),同樣按照失敗處理。

2.2.2.2 水線判斷


compaction_suitable函數(shù)一方面通過__compaction_suitable判斷水線是否滿足當(dāng)前order階內(nèi)存分配要求,若滿足則當(dāng)前zone不需要規(guī)整;另一方面,通過fragmentation_index函數(shù)獲取當(dāng)前zone對于order階內(nèi)存塊碎片程度評估,如果認(rèn)為碎片程度不高,則不進行規(guī)整。先來分析__compaction_suitable函數(shù),此函數(shù)用于評估當(dāng)前水線是否滿足內(nèi)存分配,若不滿足,則評估水線是否滿足內(nèi)存規(guī)整過程中內(nèi)存占用需求,關(guān)鍵實現(xiàn)如下:


此函數(shù)中,調(diào)用兩次__zone_watermark_ok進行水線判斷,__zone_watermark_ok函數(shù)功能是判斷當(dāng)前zone在分配order階內(nèi)存塊后水線是否達標(biāo)。第一次水線檢查成功,代表當(dāng)前zone可以滿足內(nèi)存分配訴求,因此當(dāng)前zone不用規(guī)整。若第一次調(diào)用失敗,則嘗試第二次判斷,第二次水線判斷的目的是確認(rèn)內(nèi)存規(guī)整過程中是否可能存在水線問題,因為內(nèi)存規(guī)整過程中需要order階空閑頁用于內(nèi)存遷移。因此第二次水線的判斷,僅對0階內(nèi)存判斷,因為遷移過程中申請空閑頁都是單頁,另一方面watermark增加order階內(nèi)存塊代表遷移內(nèi)存占用,但是需要注意的是理論上遷移只需要order階大小的內(nèi)存,但是實際watermark增加了2倍order階大小的內(nèi)存(compact_gap函數(shù)),這是由于實際內(nèi)存規(guī)整過程中由于遷移頁掃描器可能掃描出大于order階待遷移內(nèi)存,因此空閑頁掃描器也會占用大于order階空閑頁,為了確保評估的安全性,改為2倍order階內(nèi)存,這部分說明在compact_gap函數(shù)內(nèi)部注釋中有所描述。


2.2.2.3 各階碎片評分判斷


fragmentation_index函數(shù)用于獲取目標(biāo)階碎片程度評價,從而評估當(dāng)前內(nèi)存無法分配的原因是由于低內(nèi)存還是外部碎片導(dǎo)致。


其中fill_contig_page_info會遍歷zone內(nèi)存,統(tǒng)計當(dāng)前空閑頁以及對應(yīng)order階空閑區(qū)域數(shù)量,此函數(shù)下文將會詳細(xì)介紹,最后通過__fragmentation_index函數(shù)和上述遍歷空閑頁獲得的相關(guān)信息進行計算評估。代碼中計算公式如下:


先明確info->free_blocks_total代表當(dāng)前zone中各個order階內(nèi)存區(qū)域數(shù)量,info->free_pages代表當(dāng)前zone中空閑頁的數(shù)量,requested代表order階對應(yīng)頁數(shù)量。我們命名info->free_pages/requested為target_order_blocks,在當(dāng)前空閑頁狀態(tài)下若不存在內(nèi)存碎片問題這種理想狀態(tài)下?lián)碛卸嗌賯€order階內(nèi)存塊,現(xiàn)在重新簡化公式,即可獲得如下描述:



極限狀態(tài)info->free_blocks_total非常大時,意味著嚴(yán)重的內(nèi)存碎片問題,上述值趨近于1,反之0代表內(nèi)存不足問題。__fragmentation_index函數(shù)引入1000這個數(shù)值參與運算是為了避免小數(shù),導(dǎo)致返回值不易判斷,現(xiàn)在引入此值后,使函數(shù)返回值落入-1000,0~1000范圍中,其中-1000場景較為特殊,其代表當(dāng)前zone滿足內(nèi)存分配需求,但是在此之前卻通過了上文中__compaction_suitable函數(shù)的判斷,當(dāng)前-1000返回值實際在代碼中似乎并未被使用。重點還是回到0~1000返回值,那么數(shù)值越大代表對于當(dāng)前order階內(nèi)存塊而言碎片程度越高,難以分配。


再回到compaction_suitable函數(shù),有兩個關(guān)注點,一個是對于內(nèi)存分配階大于3的申請才會利用fragmentation_index進行額外判斷(對于較小內(nèi)存需求評估其內(nèi)存碎片程度意義降低,例如單頁內(nèi)存分配,伙伴系統(tǒng)基本分配單元,就不涉及內(nèi)存外部碎片問題);另一個是,fragmentation_index返回值需要和sysctl_extfrag_threshold閾值進行比較,如果小于閾值,則不進行規(guī)整,此值通過/proc/sys/vm/extfrag_threshold進行設(shè)置。


FAQ:基于上述邏輯,內(nèi)核中可以通過/sys/kernel/debug/extfrag/extfrag_index文件節(jié)點查看各個階外部碎片評估數(shù)據(jù),其數(shù)值對1000取余為小數(shù),越趨近于1,代表當(dāng)前order階內(nèi)存碎片程度高,相關(guān)代碼如下:


2.2.3 kcompactd規(guī)整特點

內(nèi)存規(guī)整的特點還是需要從其配置compact_control來進行說明,下述代碼為kcompactd規(guī)整CC設(shè)置。


(1)指定了規(guī)整目標(biāo)階,降低規(guī)整范圍和難度;

(2)遷移頁掃描器和空閑頁掃描器,使用快速掃描能力;

(3)其指定highest_zoneidx和目標(biāo)階,因此存在水線判斷。

(4)規(guī)整模式為MIGRATE_SYNC_LIGHT,輕度同步模式,會阻塞;

(5)規(guī)整范圍從根據(jù)上次規(guī)整結(jié)果繼續(xù)規(guī)整;

(6)pageblock將會根據(jù)標(biāo)記選擇跳過,避免頻繁掃描;

上述compact_control結(jié)構(gòu)體參數(shù)含義見3.1節(jié);


2.3 預(yù)應(yīng)性內(nèi)存規(guī)整(proactive compaction)


這屬于新增內(nèi)存規(guī)整特性,其最初目的是降低大頁分配延遲,通過大頁內(nèi)存塊碎片程度決策當(dāng)前是否啟動內(nèi)存規(guī)整,提前減少內(nèi)存碎片,提升大頁分配性能。以下鏈接對此特性做了原理性說明:

https://lwn.net/Articles/817905/


實際最終代碼與上文的最初設(shè)想已不相同,下文將依據(jù)代碼說明,代碼合?記錄如下:

https://lore.kernel.org/all/20200616204527.19185-1-nigupta@nvidia.com/T/#u


2.3.1 觸發(fā)預(yù)應(yīng)行規(guī)整


預(yù)應(yīng)性規(guī)整并非獨立存在,其融?kcompactd內(nèi)核線程,但又與kcompactd原有功能互斥。關(guān)鍵代碼如下:


上述代碼總結(jié)如下:


(1)若設(shè)置預(yù)應(yīng)性規(guī)整,kcompactd將會每500ms(HPAGE_FRAG_CHECK_INTERVAL_MSEC宏設(shè)置)喚醒判斷當(dāng)前是否需要進行規(guī)整;

(2)若設(shè)置預(yù)應(yīng)性規(guī)整,則會跳過kcompactd原有邏輯,調(diào)用預(yù)應(yīng)性規(guī)整判斷及規(guī)整邏輯;

(3)預(yù)應(yīng)性規(guī)整觸發(fā)的依據(jù)是根據(jù)整個node的碎片化程度決策;

(4)預(yù)應(yīng)性規(guī)整會在規(guī)整前后統(tǒng)計碎片得分,若得分增加,達標(biāo)碎片問題未解決,那么會增加kcompactd喚醒周期,避免頻繁無效的預(yù)應(yīng)性規(guī)整;

(5)每輪循環(huán)后,均會關(guān)閉預(yù)應(yīng)性規(guī)整,這是考慮到在某些嵌入式場景下并不需要頻繁的喚醒并判斷,將啟動預(yù)應(yīng)性規(guī)整策略交給用戶層,如下合入增加了這個功能:

https://lore.kernel.org/all/1627653207-12317-1-git-send-emailcharante@

codeaurora.org/T/#u


預(yù)應(yīng)性規(guī)整在內(nèi)核中引入vm.compaction_proactiveness文件節(jié)點,可以向其寫入0~100數(shù)值,其用于指定預(yù)應(yīng)性規(guī)整的積極性,數(shù)值越大積極性越高(見2.3節(jié)),如果設(shè)置為0相當(dāng)于關(guān)閉,設(shè)置此數(shù)值時,pgdat->proactive_compact_trigger將會被設(shè)置為true,預(yù)應(yīng)性內(nèi)存規(guī)整功能打開,此節(jié)點實現(xiàn)不過多描述。


2.3.2 碎片程度評估


預(yù)應(yīng)性內(nèi)存規(guī)整的碎片化評價,實際是對大頁碎片程度的評價,其本身也是為了解決大頁分配延遲而產(chǎn)生,與上文各order階內(nèi)存碎片評估方式不同,目前也并不針對其它order階內(nèi)存,通過如下代碼進?定義:


即便未開啟大頁功能,COMPACTION_HPAGE_ORDER通常也會被設(shè)置為9,代表要預(yù)應(yīng)性內(nèi)存規(guī)整主要是針對2MB內(nèi)存碎片程度進行評估,下面從最頂層代碼進行說明。


2.3.2.1 should_proactive_compact_node


should_proactive_compact_node 函數(shù)計算當(dāng)前node碎片程度,當(dāng)然是針對

COMPACTION_HPAGE_ORDER階內(nèi)存塊,其將會對每個zone進行評估得分,并將所有zone所得分?jǐn)?shù)累加,獲得最后得分。此得分如果高于預(yù)應(yīng)性規(guī)整水線線,代表碎片化程度較高,需要進行預(yù)應(yīng)性規(guī)整。之前通過vm.compaction_proactiveness節(jié)點設(shè)置積極性,將會影響預(yù)應(yīng)性規(guī)水線,其值越高,預(yù)應(yīng)性水線值越低,將越容易觸發(fā)規(guī)整。預(yù)應(yīng)性水線判斷邏輯如下:


注意,預(yù)應(yīng)性規(guī)整是?種預(yù)操作,應(yīng)盡可能降低對系統(tǒng)性能影響,因此當(dāng)kswapd運行時,預(yù)應(yīng)性規(guī)整不會啟動。


2.3.2.2 fragmentation_score_wmark


fragmentation_score_wmark 函數(shù)將會獲取當(dāng)前水線值,當(dāng)low入?yún)⒃O(shè)置為true時,獲取的是低水線,否則獲取高水線。


預(yù)應(yīng)性水線的計算不復(fù)雜,compaction_proactiveness設(shè)置的越低,低水線值越高,高水線通常比低水線高10,但是高低水線都在100數(shù)值以內(nèi)。


2.3.2.3 fragmentation_score_node


fragmentation_score_node函數(shù)實現(xiàn)清晰,即針對每?個zone計算一個得分,并進行累加,即為node的最后得分。



由于每個zone大小不同,因此得分應(yīng)有對應(yīng)比重,fragmentation_score_zone_weighted函數(shù)完成這個計算,實現(xiàn)方式較為直接。


zone碎片評分乘以zone的有效內(nèi)存頁數(shù)量,再除以整個node有效內(nèi)存數(shù)量,即為zone碎片程度得分的比重值。(zone有效頁數(shù)量 / node有效頁數(shù)量) * zone當(dāng)前得分簡單來說就是按照內(nèi)存大小重進?計算。


?fragmentation_score_zone函數(shù)用于計算每個zone碎片得分,其實現(xiàn)原理是統(tǒng)計當(dāng)前zone中?于等于COMPACTION_HPAGE_ORDER階空閑區(qū)域的個數(shù),并計算除此之外空閑內(nèi)存內(nèi)存與當(dāng)前zone空閑內(nèi)存百分比,這個占比可以說明碎片程度,此值越高,說明符合COMPACTION_HPAGE_ORDER階內(nèi)存塊越少。


fill_contig_page_info函數(shù)用于獲取當(dāng)前zone空閑頁、多少個COMPACTION_HPAGE_ORDER階內(nèi)存塊等等,此函數(shù)將會遍歷當(dāng)前zone上所有order的空閑鏈表進行累加計算,對于order等于COMPACTION_HPAGE_ORDER的階內(nèi)存塊個數(shù)累加,對于order大于COMPACTION_HPAGE_ORDER階內(nèi)存塊會拆分累加(因為包含多個COMPACTION_HPAGE_ORDER階空閑內(nèi)存區(qū)),此函數(shù)實現(xiàn)并不復(fù)雜,不展開描述。


2.3.3 預(yù)應(yīng)性規(guī)整特點


經(jīng)過上述的判斷,終于可以開始預(yù)應(yīng)性內(nèi)存規(guī)整,proactive_compact_node函數(shù)實現(xiàn)此功能,代碼如下:


從compact_control結(jié)構(gòu)體的設(shè)置可以看出,預(yù)應(yīng)性規(guī)整與下文主動規(guī)整類似,其指定參數(shù)有如下特點:

(1)不指定目標(biāo)階,規(guī)整持續(xù)進行,待掃描結(jié)束;

(2)遷移頁掃描器和空閑頁掃描器,不使用快速掃描能力;

(3)規(guī)整模式為MIGRATE_SYNC_LIGHT,輕度同步模式,會阻塞;

(4)規(guī)整范圍為完整zone地址空間;

(5)pageblock將會不會根據(jù)標(biāo)記選擇跳過;

上述compact_control結(jié)構(gòu)體參數(shù)含義見3.1節(jié);


2.4 主動內(nèi)存規(guī)整


主動內(nèi)存規(guī)整主要是指用戶通過設(shè)備節(jié)點觸發(fā)完整內(nèi)存規(guī)整或針對node內(nèi)存規(guī)整。內(nèi)核提供兩個設(shè)備節(jié)點用于觸發(fā)內(nèi)存規(guī)整。



通過compact_memory節(jié)點可以觸發(fā)當(dāng)前所有node以及下屬所有zone的內(nèi)存規(guī)整,這個操作的成本非常高。另一個compact節(jié)點只有在NUMA系統(tǒng)上存在,可以僅觸發(fā)某一個node進行規(guī)整。無論如何主動內(nèi)存規(guī)整觸發(fā)邏輯是簡單明了。


2.4.1 主動內(nèi)存規(guī)整特點


主動內(nèi)存規(guī)整compact_control設(shè)置較為簡單,解釋如下:

(1)不指定目標(biāo)階,規(guī)整持續(xù)進行,待掃描結(jié)束;

(2)遷移頁掃描器和空閑頁掃描器,不使用快速掃描能力;

(3)規(guī)整模式為MIGRATE_SYNC,同步模式,會阻塞;

(4)規(guī)整范圍為完整zone地址空間;

(5)pageblock將會不會根據(jù)標(biāo)記選擇跳過;

上述compact_control結(jié)構(gòu)體參數(shù)含義見3.1節(jié);

主動內(nèi)存規(guī)整,是內(nèi)存規(guī)整最全面的方法,當(dāng)然其帶來的性能影響也會最大,默認(rèn)情況下內(nèi)核并不會觸發(fā)這類內(nèi)存規(guī)整。


3.內(nèi)存規(guī)整


前言中已經(jīng)簡述規(guī)整的大致思路,將一些頁遷移聚攏,騰出更多連續(xù)空間。那么在真正實現(xiàn)時實際需要解決的問題是內(nèi)存規(guī)整范圍是什么,如何找到需要遷移的頁,什么頁適合遷移,規(guī)整何時結(jié)束等問題。上述的問題最終都在compact_zone函數(shù)中被解決。


compact_zone函數(shù)針對單個zone內(nèi)存區(qū)進行內(nèi)存規(guī)整,這是內(nèi)存規(guī)整的最小單元。其通過遷移頁掃描器從低地址到高地址尋找遷移頁,通過空閑頁掃描器從高地址到低地址尋找空閑頁,最終將掃描出的遷移頁遷移至掃描出的空閑頁,完成內(nèi)存規(guī)整,如下圖所示:

更細(xì)節(jié)一些來說,內(nèi)存規(guī)整開始后,先通過遷移頁掃描器掃描,并且掃描的單位為一個pageblock,將當(dāng)前pageblock中可遷移的頁隔離后放入到待遷移的鏈表。隨后調(diào)用空閑頁掃描器掃描,空閑頁掃描器依然以pageblock為步長,但不再限制掃描一個pageblock,其掃描的目標(biāo)是找到大于等于當(dāng)前遷移頁數(shù)量的空閑頁,上述中綠色和黃色箭頭長度不同即想表達這個邏輯。上述掃描過程將會產(chǎn)生遷移頁和空閑頁,用于后續(xù)內(nèi)存遷移,這樣就完成了內(nèi)存規(guī)整的一輪操作,可能與大家理解不同,內(nèi)存規(guī)整并非一次性掃描zone然后再遷移,而是以這種一步一步的方式進行遷移,這能平攤內(nèi)存規(guī)整對性能帶來的風(fēng)險,并且每輪處理后都有機會判斷當(dāng)前內(nèi)存規(guī)整是否可以退出。上文描述已經(jīng)非常清晰勾畫了compact_zone函數(shù)的核心邏輯,但是實際上實現(xiàn)可能更加復(fù)雜,比如什么條件下規(guī)整可以提前結(jié)束、遷移頁掃描器和空閑頁掃描器是否可以加速等等,這些都屬于更高層面優(yōu)化的話題,下文將會簡述。


再次回到compact_zone函數(shù),將復(fù)雜的代碼剝離可以很容易得到如下核心代碼邏輯:

(1)compact_finished函數(shù)用于判斷當(dāng)前規(guī)整是否結(jié)束

(2)isolate_migratepages是遷移頁掃描器實現(xiàn),用于查找需要移動的頁;

(3)isolate_freepages是空閑頁掃描器實現(xiàn),用于查找用于頁遷移的空閑頁;

(4)migrate_pages是頁遷移函數(shù),將上述兩個掃描器掃描結(jié)果進行頁遷移處理,完成規(guī)整;

至此,compact_zone大體邏輯已經(jīng)完成說明,下文將會對(1)~(3)函數(shù)進行細(xì)致描述。


3.1 內(nèi)存規(guī)整參數(shù)說明


compact_control控制了compact_zone的諸多行為,不同場景觸發(fā)內(nèi)存規(guī)整訴求不同,因此參數(shù)也不同,這里初步羅列常用參數(shù)含義,有助于理解不同場景下內(nèi)存規(guī)整的差異。


3.2 遷移頁掃描器(migrate scanner)


isolate_migratepages函數(shù)實際就是遷移頁掃描器的代碼實現(xiàn),其通常會從低地址到高地址完整或部分掃描zone區(qū)域,以pageblock為步長選擇一個合適pageblock調(diào)用isolate_migratepages_block函數(shù)進行內(nèi)存隔離,需要注意的是isolate_migratepages函數(shù)在處理完一個pageblock后就會退出,換句話說此函數(shù)一次調(diào)用只處理一個合適的pageblock。核心代碼如下所示:


總結(jié)一下,isolate_migratepages函數(shù)主要通過如下三個函數(shù)調(diào)用完成整個isolate工作:

A)快速尋找合適pageblock或返回而遷移掃描起始位置(fast_find_migrateblock)

B)判斷pageblock是否合適進行隔離(suitable_migration_source)

C)實際對一個pageblock進行隔離頁搜索操作(isolate_migratepages_block)

FAQ:注意若未找到合適pageblock,那么會持續(xù)進行線性遍歷查找,直到地址超過cc->free_pfn(最開始時此值應(yīng)為zone地址區(qū)域結(jié)尾處)。

下面將會對上述三個函數(shù)進行解析,完整描述isolate_migratepages函數(shù)功能細(xì)節(jié)。

isolate_migratepages函數(shù)會返回三個返回值,內(nèi)容如下:


這些返回值將會影響compact_zone函數(shù)的返回值。

3.2.1 遷移頁掃描器快速掃描

(fast_find_migrateblock)


fast_find_migrateblock函數(shù)會嘗試快速尋找一個pageblock來進行規(guī)整,如果無法找到則返回cc->migrate_pfn(注:此值初始應(yīng)為zone的起始地址,后續(xù)應(yīng)記錄上一次遷移掃描器掃描結(jié)束位置避免后續(xù)重復(fù)掃描)作為起始地址開始遍歷,通??紤]不就是從zone內(nèi)存區(qū)域的起始地址尋找一個么,但這樣可能并不高效,在之前版本中,遷移頁掃描器每次循環(huán)確實是基于zone的某個地址開始進行線性遍歷,這是一種線性搜索的過程,但是隨后引入了如下補丁,改變了這一現(xiàn)狀:

https://patchwork.kernel.org/project/linux-mm/patch/20181214230531.GC29005@techsingularity.net/


其目的是盡量選擇一個充斥著可移動空閑頁pageblock塊,這樣通過較少頁遷移,就可以滿足高階order內(nèi)存申請需求。通過查找freelist空閑內(nèi)存塊反向查找對應(yīng)pageblock,這樣效率非常高,另一方面,由于pageblock選擇不是簡單的順序查找,為了避免后續(xù)掃描重復(fù)pageblock還需要將其進行單獨標(biāo)識,通過set_pageblock_skip函數(shù)完成設(shè)置,確保再次進行掃描時會跳過這個pageblock內(nèi)存塊。以上就是主要思路,但是具體在實現(xiàn)上有很多細(xì)節(jié)比如:

(a)如果規(guī)整目標(biāo)order太小,那么完全沒必要去尋找,依然使用cc->migrate_pfn作為起始地址;

(b)由于對于cc->order有要求,因此僅適用于直接內(nèi)存回收和異步內(nèi)存回收(僅這兩種場景會指定cc->order,其它場景cc->order為-1);

(c)尋找空閑頁所在pageblock必須是在內(nèi)存搜索范圍的前1/2或1/8,這部分是最有可能被遷移頁掃描到得區(qū)域,避免影響到空閑頁掃描;

(d)空閑頁掃描會改變free_list布局,盡量保證下次掃描free_list不重復(fù)掃描空閑內(nèi)存塊;

(e)若這只cc->ignore_skip_hint,則遷移頁掃描器不采用遷移頁的fast機制;

(f)僅搜索可移動空閑頁,并且搜索范圍從order - 1階開始;


fast_find_migrateblock函數(shù)若無法找到合適pageblock,那么將會返回cc->migrate_pages退化為正常線性掃描,請注意的是fast_find_migrateblock函數(shù)每次也只找到一個pageblock并設(shè)置其為skip,通過上述方式確實在一定程度上能夠加速針對目標(biāo)階內(nèi)存規(guī)整,能夠更快整理出目標(biāo)階內(nèi)存需求,但是對于完整內(nèi)存規(guī)整并無實質(zhì)效果,因此fast加速查找僅適用于直接內(nèi)存規(guī)整和kcompactd被動內(nèi)存規(guī)整,因為這些場景下通常會指定規(guī)整目標(biāo)階。核心代碼如下:


無論如何,通過fast_find_migrateblock函數(shù)我們可以找到一個待遷移pageblock或者返回一個遷移頁掃描起始地址(cc->migrate_pfn),用于后續(xù)針對pageblock內(nèi)存遷移頁掃描使用。


3.2.2 suitable_migration_source函數(shù)解析


suitable_migration_source函數(shù)用于判斷當(dāng)前pageblock是否可以進行隔離及遷移,這部分邏輯相對簡單。


這里需要關(guān)注的是,如果非直接內(nèi)存規(guī)整或非遷移類型非ASYNC模式,則不需要判斷pageblock遷移類型與compact_control遷移類型是否匹配,盡可能進行內(nèi)存規(guī)整。


3.2.3 pageblock內(nèi)存隔離(isolate_migratepages_block)


isolate_migratepages_block函數(shù)會在單個pageblock內(nèi)進行遍歷,嘗試將符合規(guī)整要求的頁放入對應(yīng)compact_control所指向的migratepages鏈表,進行隔離,用于后續(xù)頁遷移操作。函數(shù)整體結(jié)構(gòu)大致如下:


起始會通過too_many_isolated函數(shù)檢查當(dāng)前內(nèi)存節(jié)點上是否存在過多isolated頁,如果數(shù)量過大(isolated > (inactive + active) / 2)那么根據(jù)MIGRATE模式選擇處理方法,比如對于異步模式就是直接退出,對于同步模式函數(shù)將會在這里進行等待一段時間,再循環(huán)檢查是否合適繼續(xù)向下執(zhí)行。隨后就是通過for循環(huán)開始遍歷這個pageblock里面所有頁,并對每一個頁進行判斷,決定其處理方法,這是一個復(fù)雜的過程,下文拆分代碼進行說明。


3.2.3.1 大頁處理


對于大頁處理,一般情況下內(nèi)存規(guī)整是會選擇略過,不進行整理。處理代碼如下:


從代碼看當(dāng)頁為復(fù)合頁并且cc->alloc_contig為false時,此頁將不會被規(guī)整。無論是hugetlbfs和THP大頁都屬于復(fù)合頁,那么問題的關(guān)鍵來到cc->alloc_contig是什么?


從代碼進一步推進可以看到,通過alloc_contig_range函數(shù)進行內(nèi)存分配時,此函數(shù)會指定申請內(nèi)存地址范圍并盡力實現(xiàn),如果指定范圍已經(jīng)被占用,會嘗試觸發(fā)直接規(guī)整進行頁遷移,如下代碼所示:


可以看到,這種情況下會將alloc_contig參數(shù)設(shè)置為true,在此邏輯中當(dāng)調(diào)用到isolate_migratepages_block函數(shù)是會嘗試規(guī)整大頁,其實際的做法是通過isolate_or_dissolve_huge_page函數(shù)實現(xiàn)大頁溶解,這部分涉及大頁邏輯不再發(fā)散。

總結(jié),只有當(dāng)頁是hugetlbfs大頁并且通過alloc_contig_range函數(shù)調(diào)用下來觸發(fā)內(nèi)存規(guī)整時才會進行處理,其它場景下大頁處理策略都是略過。


3.2.3.2 空閑物理頁處理


空閑頁的處理為直接跳過,這無可厚非,唯一需要注意的時,空閑頁跳過時并非單頁跳過,而是根據(jù)頁的order階進行跳過,代碼如下:


3.2.3.3 non-LRU物理頁


這個很有意思,上文談到大部分可移動頁應(yīng)該都是用戶態(tài)的匿名頁,這里怎么還會有不再LRU上的物理頁呢,實際這涉及到頁遷移特性的一種功能,有興趣的朋友可以閱讀一下"Documentation/vm/page_migration.rst"文章中"Non-LRU page migration"這一小節(jié)。內(nèi)核中申請的內(nèi)存通常都是non-LRU上并且不可移動,但是內(nèi)核提供了定制能力,開發(fā)者可以在內(nèi)核驅(qū)動中將自己申請的內(nèi)存標(biāo)記為可移動,為此內(nèi)核為page添加了兩個新的flag即PG_movable和PG_isolated用于標(biāo)識這種non-LRU并且可遷移的頁。開發(fā)者通常使用__SetPageMovable接口主動設(shè)置這些內(nèi)存頁PG_movable標(biāo)記,而PG_isolated標(biāo)識此頁已經(jīng)被隔離,開發(fā)者不需要主動設(shè)置此標(biāo)記。


現(xiàn)在我們應(yīng)該可以理解上述代碼中對于__PageMovable(page)判斷,如果一個non-LRU頁被設(shè)置了PG_movable并且PG_isolated還未被設(shè)置,那么代表這個頁也是可以進行遷移,隨后將會調(diào)用isolate_movable_page函數(shù)進行隔離操作。

問題還沒有結(jié)束,實際想要讓這些在內(nèi)核中直接申請的頁變?yōu)榭蛇w移,光設(shè)置標(biāo)記還不行,開發(fā)人員需要自定義這些頁如何隔離以及如何遷移,因此內(nèi)核要求,開發(fā)者需要在address_space_operations結(jié)構(gòu)體里面實現(xiàn)isolate_page、migratepage及putback_page函數(shù)?,F(xiàn)在回到isolate_movable_page函數(shù),此函數(shù)將會調(diào)用開發(fā)人員注冊的isolate_page函數(shù)完成這些頁隔離操作,代碼如下:


3.2.3.4 pinned匿名頁


如果匿名頁已經(jīng)被mlock等接口pin住,那么將會略過。


一方面,通過page_mapping判斷當(dāng)前頁是否為文件頁;另一方面,通過page_count(page) > page_mapcount(page)判斷是否被pin住,匿名頁被pin住時會增加_refcount數(shù)值。


3.2.3.5 GFP_NOFS配置下僅處理匿名頁


__GFP_FS表示內(nèi)存分配過程中可以觸發(fā)文件操作,如果compact_control中g(shù)fp_mask不帶__GFP_FS則結(jié)果依賴page_mapping返回值,對于匿名頁而言page_mapping返回NULL,因此上述代碼判斷實際的含義是當(dāng)分配上下文為無__GFP_FS并且是文件頁時將會略過,另一方面也可以解釋為GFP_NOFS時僅處理匿名頁。


那么什么時候compact_control中g(shù)fp_mask不帶__GFP_FS,前文說明了觸發(fā)內(nèi)存回收的場景,在內(nèi)存分配失敗時有可能導(dǎo)致直接觸發(fā)內(nèi)存規(guī)整,此時內(nèi)存分配GFP標(biāo)記將會被賦值到compact_control中用于配置內(nèi)存規(guī)整行為。


可以想象這么做的原因,實際在內(nèi)存分配的過程中直接觸發(fā)內(nèi)存規(guī)整其系統(tǒng)并不希望耗費過多時間,做有限度規(guī)整更為合適。


其它觸發(fā)內(nèi)存規(guī)整的場景,通常compact_control中g(shù)fp_mask為GFP_KERNEL,這是包含__GFP_FS,因此規(guī)整涉及的內(nèi)存范圍通常更廣。


3.2.3.6 不同isolate模式會影響頁的處理策略


__isolate_lru_page_prepare完成此任務(wù),關(guān)鍵代碼如下:


此處邏輯是根據(jù)隔離類型篩選可遷移的物理頁,隔離類型來源于cc-mode也就是遷移類型,代碼如下:


通常,僅當(dāng)MIGRATE_ASYNC和MIGRATE_SYNC_LIGHT模式時,其隔離模式為ISOLATE_ASYNC_MIGRATE異步模式,在這種模式下其會盡可能避免隔離可能會阻塞頁,比如代碼中正在回寫的頁或者是臟頁,這里注意如果頁為臟頁,但是其并非文件頁(swap匿名頁)或擁有自己migratepage函數(shù)那么頁被認(rèn)為遷移過程不會被阻塞,否則都無法隔離。如果isolate_mode為ISOLATE_UNEVICTABLE,代表本次隔離可以處理不可回收頁,這個主要是針對那些被lock住的unevictable頁,這些頁不能夠被回收但是支持遷移。


3.2.3.7 修改LRU即真正意義isolate(隔離)


在完成(1)~(6)的判斷后,剩下頁將能夠被隔離,隔離的含義就是將其從LRU鏈表去除(這個LRU有可能是來自于pglist_data也可能來自于頁對應(yīng)memcg),隨后將這些頁添加至cc->migratepages,用于后續(xù)頁遷移。


3.2.3.8 總結(jié)


isolate_migratepages_block函數(shù)是內(nèi)存規(guī)整過程中頁隔離的重要函數(shù),其確定哪些頁應(yīng)該被隔離,哪些頁應(yīng)該被略過,其基本策略如下:

(1)大頁不應(yīng)被隔離,但是alloc_contig_range場景下有可能觸發(fā)hugetlbfs大頁溶解,但這已不屬于內(nèi)存規(guī)整場景;

(2)空閑物理頁,不會被隔離;

(3)non-LRU物理頁,作為在內(nèi)核分配內(nèi)存,如果開發(fā)者為其實現(xiàn)isolate_page、migratepage及putback_page函數(shù),則可以被隔離或遷移;

(4)被Pin住的匿名頁,不會被隔離;

(5)GFP_NOFS分配上下文僅隔離匿名頁;

(6)不同isolate模式會影響頁的處理策略,比如ISOLATE_ASYNC_MIGRATE不會隔離正在回寫的頁或臟頁;


3.3 空閑頁掃描器(free scanner)


isolate_freepages函數(shù)是空閑頁掃描器的核心邏輯,但是compact_zone中對于空閑頁掃描器調(diào)用并不直接,而是通過migrate_pages間接調(diào)用。


migrate_pages是內(nèi)存遷移的基礎(chǔ)接口,其核心功能是將from鏈表中的頁遷移至空閑頁,空閑頁如何獲取則在get_new_page中實現(xiàn),這是一個函數(shù)指針,在compact_zone函數(shù)中,此接口的實際調(diào)用形態(tài)如下:


cc->migratepages就是之前通過isolate_migratepages隔離出來頁,compaction_alloc則實現(xiàn)如何獲取空閑頁,可以想象isolate_freepages函數(shù)會在此調(diào)用,也是本節(jié)分析的重點。compaction_alloc函數(shù)并不復(fù)雜,其用于為內(nèi)存規(guī)整頁遷移時申請遷移的目的內(nèi)存,代碼如下:


cc->migratepages保存了需要進行規(guī)整遷移的頁,也就是遷移掃描器掃描的結(jié)果。

cc->freepages保存了頁遷移的目的空閑頁,也就是空閑頁掃描器掃描的結(jié)果。

當(dāng)cc->freepages為空時,嘗試調(diào)用空閑頁掃描器isolate_freepages函數(shù)嘗試掃描隔離更多空閑頁用于頁遷移,為什么要隔離呢?隔離的本質(zhì)是將其從伙伴分配系統(tǒng)中取出不再參與系統(tǒng)內(nèi)存分配,僅用于內(nèi)存規(guī)整遷移使用。


isolate_freepages與上文isolate_migratepages函數(shù)相對應(yīng),用于隔離空閑頁,用于頁遷移。此函數(shù)也是free scanner(空閑頁掃描器的核心邏輯)。其實現(xiàn)邏輯也與isolate_migratepages函數(shù)相似,核心邏輯大致如下:

isolate_freepages函數(shù),會根據(jù)cc->free_pfn開始反向以pageblock為單位進行遍歷,如果遇到合適pageblock,則會進一步對pageblock中的頁進行遍歷,將其中合適的空閑頁進行隔離,放入cc->freepages鏈表中用于后續(xù)頁遷移使用,當(dāng)收集的空閑頁足夠遷移時將會退出。上圖僅描述核心邏輯與實際實現(xiàn)有一些出入,比如fast_isolate_freepages機制引入,就會導(dǎo)致上述反向線性遍歷的過程改變,但是上圖已經(jīng)比較簡要說明了空閑頁掃描器的工作原理。


上文從函數(shù)調(diào)用角度描述isolate_freepages函數(shù)邏輯,下文對部分重要函數(shù)調(diào)用做詳細(xì)分析。


3.3.1 空閑頁掃描器快速掃描(fast_isolate_freepages)


常規(guī)情況下,系統(tǒng)通過從cc->free_pfn開始反向遍歷尋找一個合適pageblock,隨后針對這個pageblock隔離其空閑頁。但是這有可能低效,比如第一個pageblock里面并沒有多少空閑頁,那么針對這個pageblock進行大部分操作都是無效,fast_isolate_freepages就是為改善這個問題,其并不從cc->free_pfn開始進行線性查找,而是借助伙伴系統(tǒng)中free_list快速找到一塊合適空閑區(qū)域進行隔離,從某種角度看這已經(jīng)不是基于pageblock的處理了。這與fast_find_migrateblock函數(shù)目標(biāo)類似均為提升掃描器效率。詳細(xì)代碼功能描述如下:

(1)首先選取合適order即cc->search_order,通常開始此值為cc->order - 1;

FAQ:此功能也是應(yīng)用在直接內(nèi)存回收和kcompactd場景下,因為其快速搜索的前提是cc->order和cc->search_order。其它場景下觸發(fā)的內(nèi)存規(guī)整,cc->order為-1,其直接返回cc->free_pfn,也就蛻變?yōu)榫€性搜索的模式;

(2)在order的free_list中進行空閑頁的遍歷查找;


(3)查找到合適空閑頁后,如果空閑頁落入圖中下圖中綠色區(qū)域,那么此空閑區(qū)域就會被優(yōu)先隔離,這里注意并非以pageblock為單位進行了(min_pfn為1/2處,low_pfn為3/4處);

這個邏輯并不復(fù)雜,內(nèi)存規(guī)整期望內(nèi)存向后方遷移,如果空閑頁太靠前,極端點,如果空閑頁落入紅色區(qū)域,內(nèi)存規(guī)整掃描器容易快速相遇導(dǎo)致無法解決內(nèi)存碎片的問題;

那么,如果空閑頁落入min_pfn和low_pfn之間,那么系統(tǒng)會降低在當(dāng)前階freelist遍歷機會,傾向于降階在新search_order階的freelist中尋找綠色區(qū)域的空閑頁;除此以外這種場景下,系統(tǒng)還會記錄當(dāng)前search_order對應(yīng)freelist搜索記錄(這會改變freelist內(nèi)存塊順序),后續(xù)可避免額外搜索,代碼如下:


(4)對于(3)已經(jīng)找到search_order次的空閑區(qū)域,將直接調(diào)用__isolate_free_page(接口分析詳見3.3.2.1節(jié))函數(shù)完成隔離,隨后將這些頁放入cc->freepages鏈表,完成整個操作;

(5)到這里,已經(jīng)成功隔離了search_order階空閑頁,這并不針對pageblock,并且對于是否已經(jīng)滿足遷移需求數(shù)量也并沒有約束,所以在函數(shù)末尾調(diào)用了fast_isolate_around函數(shù),此函數(shù)本質(zhì)是根據(jù)當(dāng)前需求,確認(rèn)是否需要針對當(dāng)前空閑區(qū)域所在pageblock再額外進行隔離,代碼如下:

簡單總結(jié),代碼邏輯如下:

如果當(dāng)前已隔離的綠色區(qū)域已經(jīng)滿足訴求,那么此函數(shù)將會直接退出,如果不滿足,將會嘗試將這個空閑區(qū)域?qū)?yīng)pageblock左側(cè)和右側(cè)區(qū)域通過isolate_freepages_block函數(shù)進行空閑頁隔離。


無論如何,fast_isolate_freepages接口都嘗試以更快速的方式獲取空閑頁,有可能這個空閑頁并不是緊貼著cc->free_pfn,但是它一定在后1/4范圍內(nèi),并且它會改變freelist的結(jié)構(gòu)避免重復(fù)判斷相同空閑頁,這是一個優(yōu)化功能。對于內(nèi)存規(guī)整若想簡單理解,可以忽略此處細(xì)節(jié)直接理解為從zone末尾開始線性查找。


3.3.2 空閑頁隔離(isolate_freepages_block)


isolate_freepages_block函數(shù),即在指定內(nèi)存范圍內(nèi)正向遍歷,將合適的空閑頁進行隔離加入到cc->freepages鏈表,用于后續(xù)頁遷移,這是isolate_freepages函數(shù)得核心函數(shù)調(diào)用,通常isolate_freepages每次調(diào)用會傳遞一個pageblock范圍進行空閑頁隔離。關(guān)鍵代碼如下:


函數(shù)關(guān)鍵邏輯說明:

(1)函數(shù)在pageblock內(nèi)遍歷并不一定按照頁為單位,參數(shù)stride作為步長存在;

(2)復(fù)合頁將會略過;

(3)非空閑頁將會略過;

(4)符合上述要求空閑頁通過__isolate_free_page進行隔離操作,隨后將這些加入到cc->freepages鏈表;

(5)如果當(dāng)前收集空閑頁已經(jīng)大于當(dāng)前已經(jīng)收集遷移頁則退出循環(huán);


上述即isolate_freepages_block函數(shù)的邏輯,這里面需要關(guān)注一個strict參數(shù),如果該參數(shù)為true,那么isolate_freepages_block函數(shù)將會以頁為遍歷單位進行遍歷及隔離,并且不會再根據(jù)上述(5)條件提前退出,而是完整隔離整個pageblock中合適的空閑頁。


3.3.2.1 伙伴系統(tǒng)處理(__isolate_free_page)


空閑頁隔離與遷移頁的隔離不太相同,由于空閑頁還屬于伙伴系統(tǒng)管轄范圍內(nèi),伙伴體統(tǒng)提供專用隔離接口,即__isolate_free_page函數(shù)。

此結(jié)構(gòu)邏輯清晰,不過多贅述。


3.4 內(nèi)存規(guī)整退出判斷(compact_finished)


compact_finished用于判斷當(dāng)前規(guī)整是否結(jié)束,有多種不同條件導(dǎo)致規(guī)整結(jié)束,并且返回值不同,由于compact_finished函數(shù)較長并且對于理解內(nèi)存規(guī)整較為重要,因此代碼拆分說明。


3.4.1 掃描器相遇


上文說明migrate scanner從正向掃描,free scanner反向掃描,當(dāng)兩者相遇,代表掃描和遷移操作結(jié)束,因此規(guī)整結(jié)束,這是最為正常的一種退出方式,代碼如下:


掃描器相遇場景退出,上述代碼注釋完整標(biāo)記其邏輯,對于如何判斷掃描器相遇,實際根據(jù)cc->free_pfn和cc->migrate_pfn的大小容易判斷,不進行函數(shù)代碼說明。


3.4.2 預(yù)應(yīng)性規(guī)整退出條件


這里預(yù)應(yīng)性規(guī)整除了掃描器相遇退出條件外,擁有額外退出條件。


fragmentation_score_zone和fragmentation_score_wmark均為預(yù)應(yīng)性規(guī)整碎片評估函數(shù),簡單說當(dāng)前如果對于大頁階碎片評估分?jǐn)?shù)低于預(yù)應(yīng)性碎片水線時,則停止規(guī)整,返回成功。關(guān)于預(yù)應(yīng)性規(guī)整碎片評估邏輯詳見2.3.2節(jié)。


3.4.3 direct規(guī)整模式額外退出條件


直接內(nèi)存規(guī)整在識別是否成功時,如果判斷當(dāng)前申請需求已滿足,并且分配遷移類型也滿足一定要求即可退出直接規(guī)整邏輯。為何在申請可以滿足的情況下還要滿足一定要求才能退出呢,主要考慮是即便滿足分配,但也不能引入潛在擴大內(nèi)存碎片化的情況,否則將會頻繁進入直接內(nèi)存規(guī)整。


3.4.4 返回值總結(jié)


1.COMPACT_CONTINUE:代表內(nèi)存規(guī)整未結(jié)束,繼續(xù)規(guī)整;

2.COMPACT_COMPLETE:在cc->whole_zone為true場景下,完成全區(qū)域掃描和規(guī)整,將返回此值;

3.COMPACT_PARTIAL_SKIPPED:多種場景下均會返回此值,例如:

(1)cc->whole_zone為false場景下,掃描和規(guī)整完成,將會范圍此值;

(2)在proactive_compaction模式下,如果此時kswapd運行,規(guī)整也將會停止,返回此值;


4.COMPACT_SUCCESS:代表規(guī)整成功,此值也是多種場景下均會返回:

(1)proactive規(guī)整模式下,碎片化得分達標(biāo),主動退出規(guī)整,即返回成功;

(2)direct規(guī)整模式下,需求order階及遷移類型鏈表上,已有足量內(nèi)存,即返回成功;

(3)direct規(guī)整模式下,需求order階上如果有足量CMA內(nèi)存,前提是本身需求也是可遷移頁(否則CMA內(nèi)存申請時無法遷移),即返回成功;

(4)direct規(guī)整模式下,可以從其它遷移類型偷到內(nèi)存,在滿足一定條件下也會范圍成功;


5.COMPACT_CONTENDED:若當(dāng)前進程被強制退出或依然持有zone lock,則規(guī)整邏輯返回此值,屬于一種異常退出狀態(tài);


4.內(nèi)存規(guī)整總結(jié)


代碼分析基本完成,再對內(nèi)存規(guī)整統(tǒng)計信息及可調(diào)文件節(jié)點進行簡要說明。


4.1 內(nèi)存規(guī)整統(tǒng)計信息


在上文的代碼描述中忽略內(nèi)存規(guī)整相關(guān)信息統(tǒng)計邏輯,統(tǒng)計信息可以通過/proc/vmstat文件節(jié)點進查詢,相關(guān)信息含義說明如下:


4.2 內(nèi)存規(guī)整文件節(jié)點


4.3 總結(jié)


內(nèi)存規(guī)整是一個較重內(nèi)存碎片優(yōu)化措施,在使用時內(nèi)核較為謹(jǐn)慎,當(dāng)前有直接內(nèi)存規(guī)整、kcompactd內(nèi)存規(guī)整、預(yù)應(yīng)性內(nèi)存規(guī)整及主動內(nèi)存規(guī)整四種場景,這些場景涵蓋在內(nèi)存分配、內(nèi)存回收等上下文,由于規(guī)整的訴求和緊迫程度不同,其通過compact_control結(jié)構(gòu)體參數(shù)控制compact_zone內(nèi)存規(guī)整行為包括但不限于內(nèi)存掃描范圍、頁遷移的能力、遷移頁是否適合規(guī)整及是否可以阻塞等等。

另一方面,內(nèi)存規(guī)整的核心邏輯在于遷移頁掃描器(migrate scanner)和空閑頁掃描器(free scanner)運作原理,包括哪些頁可以作為遷移頁或空閑頁,何時內(nèi)存規(guī)整結(jié)束等等這些直接影響對內(nèi)存規(guī)整理解。


原文作者:內(nèi)核工匠


萬字詳解Linux內(nèi)核內(nèi)存規(guī)整!超詳細(xì)!的評論 (共 條)

分享到微博請遵守國家法律
竹山县| 连江县| 北流市| 岳阳县| 张北县| 义马市| 奉化市| 虹口区| 门源| 鹤壁市| 苗栗县| 大埔区| 峨眉山市| 固阳县| 图木舒克市| 会昌县| 敖汉旗| 安庆市| 清流县| 宝坻区| 合江县| 铁岭市| 周宁县| 宾川县| 靖西县| 原阳县| 师宗县| 筠连县| 哈密市| 上杭县| 通渭县| 桃江县| 宜春市| 沈阳市| 格尔木市| 南召县| 黄山市| 澄城县| 望都县| 宜城市| 韩城市|