成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

剖析Buddy算法中內(nèi)存的申請和釋放

原創(chuàng) 精選
存儲 存儲軟件
內(nèi)存的合理利用一直是系統(tǒng)的頭等大事。目前系統(tǒng)中,除了采用Buddy和slab管理內(nèi)存外,還會采用內(nèi)存水線檢測處理,PCP機制,CMA機制等進行內(nèi)存的優(yōu)化。在本文中,我們將從Buddy算法中內(nèi)存的申請和釋放,來探索內(nèi)存的奧秘。

作者 | 趙青窕

審校 | 孫淑娟

內(nèi)存的合理利用一直是系統(tǒng)的頭等大事。目前系統(tǒng)中,除了采用Buddy和slab管理內(nèi)存外,還會采用內(nèi)存水線檢測處理,PCP機制,CMA機制等進行內(nèi)存的優(yōu)化。在本文中,我們將從Buddy算法中內(nèi)存的申請和釋放,來探索內(nèi)存的奧秘。

基本概念

zone:有的地方把zone稱為管理區(qū),每個node下會劃分成不同的zone。有的系統(tǒng)會劃分成3個zone區(qū),有的會劃分成2個zone區(qū)。zone區(qū)的個數(shù)會因平臺,內(nèi)核,系統(tǒng)的位數(shù)等有差異。

free_area:每個zone區(qū)根據(jù)2的order次方(order的范圍從0到MAX_ORDER)進一步劃分,劃分后的每個小區(qū)域通過free_area[order]表示。

如下圖紅色方框中所示,按照紅色方框從左到右分別是node,zone和free_area。

水線:每個zone存在三個水線,若當(dāng)前zone中空閑頁高于WMARK_HIGH,則當(dāng)前zone區(qū)的空閑內(nèi)存較多;若空閑頁低于WMARK_LOW,則交換守護進程開始將內(nèi)存交換到磁盤上;若空閑頁低于WMARK_MIN,則內(nèi)存回收系統(tǒng)還需要大量回收內(nèi)存。

order:每個zone區(qū)根據(jù)order,把內(nèi)存按照2的order繼續(xù)劃分為不同的area。

PCP鏈表:該鏈表中的每一個成員大小均是2的0次方個頁面,每次申請和釋放1個頁面,都會優(yōu)先考慮PCP。當(dāng)PCP為空時,會從Buddy中申請;當(dāng)PCP中頁面比較多,超過限制時,會把頁面釋放到Buddy中。 

內(nèi)存申請

比較常用的內(nèi)存申請函數(shù)是kmalloc,當(dāng)申請的內(nèi)存大于KMALLOC_MAX_CACHE_SIZE時,會通過函數(shù)kmalloc_large從Buddy中申請內(nèi)存,否則從slab中申請內(nèi)存。本文中暫不分析從slab申請內(nèi)存的情況。

kmalloc_large函數(shù)實現(xiàn)如下,Buddy算法中,內(nèi)存的分配和釋放均離不開order,我們可以看到,在該函數(shù)內(nèi)部通過size來計算出對應(yīng)的order,就很好地把Buddy和slab連接在一起了。

static __always_inline void *kmalloc_large(size_t size, gfp_t flags)
{
unsigned int order = get_order(size);
return kmalloc_order_trace(size, flags, order);
}

函數(shù)kmalloc_order_trace會調(diào)用函數(shù)alloc_pages,進而調(diào)用函數(shù)struct page *__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid, nodemask_t *nodemask)來實現(xiàn)內(nèi)存的分配。實際上,Buddy提供的對外申請內(nèi)存函數(shù)是alloc_pages,但其內(nèi)部實現(xiàn)大部分情況下均是通過__alloc_pages_nodemask來實現(xiàn)。該函數(shù)分三步進行處理,分別如下:

  • 構(gòu)建內(nèi)存分配的上下文結(jié)構(gòu),內(nèi)核中采用結(jié)構(gòu)體struct alloc_context來表示
  • 快速分配
  • 慢速分配

1.內(nèi)存分配上下文結(jié)構(gòu)

內(nèi)存分析上下文采用結(jié)構(gòu)體struct alloc_context來表示,其結(jié)構(gòu)體定義如下:

/*
* Structure for holding the mostly immutable allocation parameters passed
* between functions involved in allocations, including the alloc_pages*
* family of functions.
*
* nodemask, migratetype and high_zoneidx are initialized only once in
* __alloc_pages_nodemask() and then never change.
*
* zonelist, preferred_zone and classzone_idx are set first in
* __alloc_pages_nodemask() for the fast path, and might be later changed
* in __alloc_pages_slowpath(). All other functions pass the whole strucure
* by a const pointer.
*/
struct alloc_context {
struct zonelist *zonelist;
nodemask_t *nodemask;
struct zoneref *preferred_zoneref;
int migratetype;
enum zone_type high_zoneidx;
bool spread_dirty_pages;
};

各個成員含義如下:

  • zonelist:用于分配內(nèi)存的zone區(qū)列鏈表。在內(nèi)存分配時,內(nèi)核會通過函數(shù)numa_node_id()來獲取當(dāng)前CPU的NUMA ID,進而根據(jù)這個ID號獲取對應(yīng)的zonelist。內(nèi)存的分配實際上就是在zonelist找合適的內(nèi)存進行分配,該成員在后面兩步中具有關(guān)鍵作用;
  • nodemask:用來指定從哪一個node中進行內(nèi)存分配。若沒有指定,則會在所有節(jié)點中嘗試分配,通常情況下該值為NULL;
  • high_zoneidx:該成員從字面意思看就是最高的zone區(qū)id號,其實它表示的是在分配時,所能分配的最高zone區(qū)。通常一般是從HIGH區(qū)---->NORMAL---->DMA的方式進行分配。內(nèi)存的需求方在請求進行內(nèi)存分配時,會通過gfp_mask來對該成員進行設(shè)置,Buddy在內(nèi)存分配及逆行內(nèi)存分配時需要通過函數(shù)gfp_zone(gfp_mask)來提取gfp_mask中對應(yīng)的high_zoneidx;
  • migratetype:該成員指明了需要內(nèi)存的頁面遷移類型。Buddy進行內(nèi)存分配時需要通過函數(shù)gfpflags_to_migratetype(gfp_mask)來獲取內(nèi)存請求方的具體需求;
  • preferred_zone:結(jié)合成員high_zoneidx和zonelist,計算出首先從那個zone區(qū)開始進行內(nèi)存的分配,即第一個將要被遍歷的zone,內(nèi)核中是通過函數(shù)first_zones_zonelist來計算該成員的;
  • spread_dirty_pages:當(dāng)申請內(nèi)存時,采用了標(biāo)志__GFP_WRITE,則說明此次申請的物理頁面將會生成臟頁,內(nèi)核中就是通過語句ac->spread_dirty_pages = (gfp_mask & __GFP_WRITE)來設(shè)置該成員的。

從上面的結(jié)構(gòu)體struct alloc_context的說明可以看出,該結(jié)構(gòu)體具體細化了內(nèi)存分配的各種需求,其具體實現(xiàn)如下圖中紅色方框所示:

2.快速分配

在完成第一步后,就可以通過函數(shù)get_page_from_freelist進行一次快速分配。該函數(shù)才是內(nèi)存分配真正的開始位置,接下來我將詳細說明該過程,為了簡化描述,同時為了讓大家容易理解,暫時不考慮CPUSET的情況。

該函數(shù)本質(zhì)就是從preferred_zone開始,遍歷zonelist,其每一次遍歷時,處理流程如下:

  • 臟頁面判斷

每個node節(jié)點會對臟頁數(shù)進行限制,當(dāng)超過限制后,將無法申請具有__GFP_WRITE標(biāo)志的內(nèi)存塊,需要跳出當(dāng)前zone區(qū),轉(zhuǎn)而掃描下一個zone區(qū),其內(nèi)核處理代碼如下圖所示,圖中進行了標(biāo)注,方便大家理解。

  • 水位處理

前面小節(jié)中有提到每個zone中存在三個水線,在內(nèi)存申請時,默認采用WMARK_LOW,使用函數(shù)zone_watermark_fast進行水線判斷。

假如通過水線檢測,發(fā)現(xiàn)內(nèi)存不夠,則會判斷當(dāng)前申請內(nèi)存的請求是否采用ALLOC_NO_WATERMARKS,若采用,則說明當(dāng)前剩余內(nèi)存多少與當(dāng)前申請沒有任何關(guān)系,會調(diào)用rmqueue進行內(nèi)存分配;若沒有ALLOC_NO_WATERMARKS聲明,則進行下一步reclaim操作;

假如通過水線檢測,發(fā)現(xiàn)當(dāng)前還有足夠內(nèi)存,則調(diào)用函數(shù)rmqueue進行內(nèi)存分配。

  • reclaim操作

reclaim操作首先是通過函數(shù)zone_allows_reclaim來判斷當(dāng)前的node是否支撐reclaim操作,如果不支持,就退出當(dāng)前循環(huán),執(zhí)行下一個循環(huán)操作;若支持,就調(diào)用node_reclaim執(zhí)行內(nèi)存回收的工作。

當(dāng)函數(shù)node_reclaim返回值是NODE_RECLAIM_NOSCAN或者NODE_RECLAIM_FULL時,表示當(dāng)前雖然內(nèi)存不夠,但我無能為力了。這種情況下,只能退出循環(huán),執(zhí)行下一個操作;當(dāng)返回值是其余的情況時,就會重新進行水位檢測,若此時內(nèi)存足夠,則調(diào)用rmqueue進行內(nèi)存分配,否則退出循環(huán),執(zhí)行下一個循環(huán)操作。

假如當(dāng)前系統(tǒng)使用的是非NUMA,則不會進行reclaim操作,當(dāng)水位線檢測發(fā)現(xiàn)內(nèi)存不夠時,會跳出循環(huán),嘗試下一個zone;假如當(dāng)前系統(tǒng)是NUMA,才會進行上述描述中的判斷,來決定是否進行內(nèi)存回收。

  • rmqueue內(nèi)存分配處理

在內(nèi)存分配時,分兩種情況進行處理,分別是order = 0及order != 0。

當(dāng)order = 0時,會首先從PCP鏈表中進行內(nèi)存申請,其具體流程如下:

當(dāng)order != 0,即要申請多頁,下面是其處理過程,根據(jù)實際情況調(diào)用__rmqueue_smallest,__rmqueue_cma或者__rmqueue進行內(nèi)存的分配。

對于設(shè)置了ALLOC_HARDER的情況,先嘗試通過函數(shù)__rmqueue_smallest來分配MIGRATE_HIGHATOMIC類型的內(nèi)存塊,具體實現(xiàn)就是從zone->free_area[order]中根據(jù)需要的內(nèi)存類型進行分配。該函數(shù)實現(xiàn)比較簡單,就是遍歷free_area以便找到合適的內(nèi)存塊,下圖是__rmqueue_smallest的實現(xiàn),增加了注釋方便大家理解。

假如通過上面的__rmqueue_smallest沒有找到合適的內(nèi)存塊,在申請內(nèi)存時,使用標(biāo)志__GFP_CMA申請的MIGRATE_MOVABLE,則再次使用函數(shù)__rmqueue_cma申請內(nèi)存,實際上__rmqueue_cma內(nèi)部是調(diào)用__rmqueue_smallest(zone, order, MIGRATE_CMA)實現(xiàn)的。

若上面的兩步__rmqueue_smallest,__rmqueue_cma均失敗,則會調(diào)用__rmqueue。該函數(shù)內(nèi)部實際上也是通過__rmqueue_smallest實現(xiàn)的,當(dāng)__rmqueue_smallest只會從指定的migtatetype中進行分配,當(dāng)分配失敗后,會通過函數(shù)__rmqueue_fallback從后備fallbacks中找到一個遷移類型頁塊,將其遷移到目標(biāo)遷移類型中后重新進行分配。

至此快速分配結(jié)束,若已經(jīng)分配到內(nèi)存,則會退出分配流程,否則進行下一步操作:慢速分配。

3.慢速分配

慢速分配是通過函數(shù)__alloc_pages_slowpath來實現(xiàn)的。從快速分配發(fā)現(xiàn)無法分配到需要的內(nèi)存,緊接著內(nèi)核通過慢速分配對內(nèi)存進行整理,嘗試找到合適的內(nèi)存。其整理過程包含:

  • 重新計算內(nèi)存分配上下文;
  • 如果設(shè)置了__GFP_KSWAPD_RECLAIM,則會調(diào)用函數(shù)wake_all_kswapds來喚醒負責(zé)換出內(nèi)存頁的守護進程kswapds;
  • 因更新了內(nèi)存分配上下文,因此再次使用快速分配嘗試內(nèi)存分配。若分配成功,則退出本次分配;否則繼續(xù)進行下一步操作;
  • 若申請內(nèi)存時,設(shè)置了__GFP_DIRECT_RECLAIM,且非pfmemalloc情況下,會通過函數(shù)__alloc_pages_direct_compact進行內(nèi)存壓縮后,再次嘗試分配頁面。若分配成功則退出;否則進入下一步;
  • 接下來的操作代碼中采用了retry代碼標(biāo)簽,這個過程比較繁瑣,其本質(zhì)就是采用各種內(nèi)存優(yōu)化手段盡量促使本次分配成功,優(yōu)化手段主要有以下四種:
  • 通過函數(shù)__alloc_pages_direct_reclaim嘗試進行內(nèi)存回收后,再分配內(nèi)存;
  • 通過函數(shù)__alloc_pages_direct_compact嘗試進行內(nèi)存整合后,再分配內(nèi)存;
  • 通過函數(shù)__alloc_pages_may_oom嘗試殺掉一些優(yōu)先級不高的進程后,再分配內(nèi)存;
  • 在retry過程中,仍會調(diào)用wake_all_kswapds來喚醒kswapds,防止意外休眠。

這四種方式都會伴隨著調(diào)用函數(shù)get_page_from_freelist來進行內(nèi)存分配。

至此內(nèi)存分配函數(shù)就完成了。從上面的描述可以看出,當(dāng)內(nèi)存足夠時,通常情況下快速分配就足夠了。只有在內(nèi)存不夠時,會進行慢速分配,慢速分配里面進行內(nèi)存回收,整理等操作后再進行分配。若此時還沒有足夠的內(nèi)存可以分配,說明內(nèi)存耗盡,可能是因為內(nèi)存泄漏導(dǎo)致內(nèi)存不足,這個時候就需要去定位內(nèi)存泄漏問題了。

內(nèi)存釋放

Buddy中內(nèi)存釋放入口函數(shù)是free_pages。該函數(shù)的實現(xiàn)如下,從下面的函數(shù)中可以看出最后是通過free_unref_page或者__free_pages_ok來實現(xiàn)的,其余的部分均合法性判斷。

void free_pages(unsigned long addr, unsigned int order)
{
if (addr != 0) {
VM_BUG_ON(!virt_addr_valid((void *)addr));
__free_pages(virt_to_page((void *)addr), order);
}
}

void __free_pages(struct page *page, unsigned int order)
{
if (put_page_testzero(page))
free_the_page(page, order);
}

static inline void free_the_page(struct page *page, unsigned int order)
{
if (order == 0) /* Via pcp? */
free_unref_page(page);
else
__free_pages_ok(page, order);
}

函數(shù)free_pages 接受兩個參數(shù),分別是虛擬地址和需要釋放的頁面數(shù),該函數(shù)內(nèi)部利用virt_to_page把虛擬地址轉(zhuǎn)化成Buddy算法需要的struct page結(jié)構(gòu)體。

__free_pages函數(shù)先將對應(yīng)的struct page->_refcount 減去1,之后檢測_refcount是否為0,若為0,繼續(xù)進行釋放操作,否則不進行內(nèi)存釋放操作。通過該函數(shù)__free_pages可以看到,不管是否進行了內(nèi)存釋放操作,該函數(shù)都可以正常退出且沒有返回值。假如內(nèi)存釋放操作異常,就會引發(fā)內(nèi)存泄漏問題,且代碼中沒有任何日志和錯誤碼,這種泄漏通常很難排查。

free_the_page是真正的內(nèi)存釋放函數(shù),該函數(shù)根據(jù)order的不同,分別進行兩種不同的處理:

  • order為0的情況
  • order不為0的情況

接下來我們分別來了解這兩種情況的處理方式。

1.order為0的情況

函數(shù)內(nèi)存會根據(jù)order是否為0來進行相應(yīng)的操作,對于order = 0的情況,此處是調(diào)用函數(shù)free_unref_page。有些內(nèi)核中會調(diào)用函數(shù)free_hot_cold_page(page, false)來實現(xiàn),但不管調(diào)用哪一個函數(shù),其內(nèi)部均是進行相應(yīng)的判斷后,通過把page插入PCP鏈表相應(yīng)位置處實現(xiàn)。實際上內(nèi)核在把內(nèi)存釋放到PCP鏈表時,會進行PCP鏈表成員個數(shù)pcp->count的判斷,當(dāng)pcp->count >= pcp->high時,會調(diào)用函數(shù)free_pcppages_bulk釋放一部分PCP中的頁面到 Buddy 子系統(tǒng)中。

此處我們需要注意,并不是所有order = 0的內(nèi)存全部釋放到PCP鏈表中,在結(jié)構(gòu)體struct page中有個成員index,該成員指明了該部分內(nèi)存的類型,若類型為MIGRATE_ISOLATE,則其內(nèi)存(實際上是一個頁面)會釋放到Buddy中,若類型對應(yīng)的數(shù)據(jù)大于或等于MIGRATE_PCPTYPES,則釋放到類型為MIGRATE_MOVABLE的PCP鏈表中,其余的釋放到對應(yīng)類型的PCP鏈表中。下圖是order = 0時的核心處理代碼,圖中已經(jīng)標(biāo)注了各個關(guān)鍵地方,供大家參考。

2.order不為0的情況

當(dāng)order不為0時,會通過函數(shù)__free_pages_ok調(diào)用free_one_page來實現(xiàn)。其核心代碼如下圖所示,圖中對代碼進行了標(biāo)注,從其代碼我們可以發(fā)現(xiàn)其實現(xiàn)是通過while循環(huán)來查找可以合并的頁塊,查找的方式就是按照order的次序挨個查找,其整個流程就是查找--->確認--->刪除--->合并。

此時,我們來思考一個問題,有些特殊內(nèi)存區(qū)是無法進行合并的,在內(nèi)核代碼中特別表明了如下注釋:

/* If we are here, it means order is >= pageblock_order.
* We want to prevent merge between freepages on isolate
* pageblock and normal pageblock. Without this, pageblock
* isolation could cause incorrect freepage or CMA accounting.
*
* We don't want to hit this code for the more frequent
* low-order merging.
*/

其對應(yīng)的代碼處理如下圖所示,其代碼主要目的有兩點,其一是保證可以充分地進行頁塊的合并,從而盡量減少內(nèi)存碎片化;其二是保證特殊用途的內(nèi)存塊不受影響。

最后根據(jù)實際情況,通過函數(shù)list_add(&page->lru, &zone->free_area[order].free_list[migratetype])或者函數(shù)list_add_tail(&page->lru,&zone->free_area[order].free_list[migratetype])把合并后的page添加到對應(yīng)的鏈表中。

總結(jié)

不同平臺,不同內(nèi)核版本的系統(tǒng),在內(nèi)存處理上或許會存在或多或少的差異,但其核心思想是相同的。通過本文,我們可以詳細地了解Buddy中內(nèi)存申請和釋放的處理方式,以及當(dāng)內(nèi)存不足時,Buddy是如何處理的。

作者介紹

趙青窕,51CTO社區(qū)編輯,從事多年驅(qū)動開發(fā)。研究興趣包含安全OS和網(wǎng)絡(luò)安全領(lǐng)域,發(fā)表過網(wǎng)絡(luò)相關(guān)專利。

責(zé)任編輯:華軒 來源: 51CTO
相關(guān)推薦

2024-01-01 18:59:15

KubernetesCPU內(nèi)存

2018-12-06 10:22:54

Linux內(nèi)核內(nèi)存

2022-07-19 13:31:18

Buddy算法內(nèi)存管理框架

2022-07-10 20:47:39

linux中虛擬內(nèi)存

2016-08-11 14:49:34

Java垃圾回收機制異常

2013-06-04 14:21:20

Vector內(nèi)存釋放

2024-05-06 11:19:20

內(nèi)存池計算機編程

2022-05-18 10:49:57

運維數(shù)據(jù)

2018-01-19 10:37:00

2020-10-23 06:56:00

C語言動態(tài)字符串

2021-05-17 09:28:59

鴻蒙HarmonyOS應(yīng)用

2021-05-21 09:25:11

鴻蒙HarmonyOS應(yīng)用

2009-06-10 22:03:40

JavaScript內(nèi)IE內(nèi)存泄漏

2011-08-16 15:13:49

IOS編程內(nèi)存

2024-12-12 09:24:28

RocksDB服務(wù)器

2012-09-13 15:37:21

linux內(nèi)存

2017-05-04 20:15:51

iOSNSTimer循環(huán)引用

2022-11-11 08:00:00

決策樹機器學(xué)習(xí)監(jiān)督學(xué)習(xí)

2024-02-05 21:07:51

C++內(nèi)存編程語言

2009-09-03 16:58:49

C#內(nèi)存管理
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 日本黄色大片免费看 | 亚洲精品二区 | 日本国产高清 | 精品一区二区三区中文字幕 | 精品国产乱码久久久久久久久 | 日本不卡视频在线播放 | 99精品国产一区二区青青牛奶 | 91精品国产综合久久久久 | 久久高清亚洲 | 国产黄色在线观看 | 操操日| 日本福利一区 | 特黄毛片视频 | 欧美中文一区 | 日韩一区二区三区视频 | 久久爱一区 | 成人天堂 | 成人午夜电影网 | 成人深夜福利 | 天天操天天射天天舔 | 亚洲男人的天堂网站 | 粉嫩一区二区三区国产精品 | 亚洲精品一区二区三区蜜桃久 | 成人精品鲁一区一区二区 | 国产福利视频 | 国产精品一区二区无线 | 精品欧美一区二区在线观看 | 91视视频在线观看入口直接观看 | 久久99精品久久久久久 | 亚洲国产一区二区在线 | 蜜桃视频在线观看免费视频网站www | 久久精品视频一区二区三区 | 精品国产欧美一区二区 | 欧美日韩一区在线 | 午夜不卡一区二区 | 一区二区三区在线 | 国产一区二区三区四区在线观看 | 一区二区视频 | 在线看成人av | 一区视频在线 | 国产日产欧产精品精品推荐蛮挑 |