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

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

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

Linux內(nèi)核內(nèi)存管理之ZONE內(nèi)存分配器

jf_0tjVfeJz ? 來源:嵌入式ARM和Linux ? 2024-02-21 09:29 ? 次閱讀

分配頁幀

分配頁幀的具體實(shí)現(xiàn)

釋放頁幀

分配頁幀

內(nèi)核中使用ZONE分配器滿足內(nèi)存分配請求。該分配器必須具有足夠的空閑頁幀,以便滿足各種內(nèi)存大小請求。為此,ZONE分配器必須能夠:

它應(yīng)該保護(hù)預(yù)留頁幀池;

當(dāng)內(nèi)存不足并且允許阻塞當(dāng)前進(jìn)程時,能夠觸發(fā)頁幀回收機(jī)制。一旦某些頁幀被釋放,ZONE分配器重新分配;

盡可能保留小的、珍貴的ZONE_DMA內(nèi)存區(qū)。如果請求正常內(nèi)存或高端內(nèi)存,ZONE分配器不太可能分配ZONE_DMA內(nèi)存區(qū)中的頁幀。

對于每次連續(xù)頁幀的申請,ZONE頁幀分配器調(diào)用alloc_pages()宏實(shí)現(xiàn)。該宏其實(shí)是__alloc_pages()的封裝,而該函數(shù)才是ZONE分配器的核心。它需要三個參數(shù)

gfp_mask

內(nèi)存分配請求中指定的標(biāo)志。

order

連續(xù)物理頁幀的對數(shù)。

zonelist

指向zonelist數(shù)據(jù)結(jié)構(gòu),按照優(yōu)先順序,選擇適合內(nèi)存分配的內(nèi)存區(qū)。

__alloc_pages()掃描zonelist數(shù)據(jù)結(jié)構(gòu)中每一個內(nèi)存區(qū),代碼大概如下所示:

for(i=0;(z=zonelist->zones[i])!=NULL;i++){
if(zone_watermark_ok(z,order,...)){
page=buffered_rmqueue(z,order,gfp_mask);
if(page)
returnpage;
}
}

對于每個內(nèi)存區(qū)域,該函數(shù)將空閑頁幀的數(shù)量與一個閾值進(jìn)行比較,該閾值取決于內(nèi)存分配標(biāo)志、當(dāng)前進(jìn)程的類型以及該函數(shù)已經(jīng)檢查該區(qū)域的次數(shù)。實(shí)際上,如果可用內(nèi)存很少,通常會對每個內(nèi)存區(qū)域掃描幾次,每次都對分配所需的最小可用內(nèi)存設(shè)置較低的閾值。因此,前面的代碼塊在__alloc_pages()函數(shù)的主體中被復(fù)用了幾次(只有很小的變化)。buffered_rmqueue()函數(shù)已經(jīng)在前面的“CPU頁幀緩存”一節(jié)中描述過了:它返回第一個分配的頁幀的頁描述符,如果內(nèi)存區(qū)域不包含一組請求大小的連續(xù)頁幀,則返回NULL。

zone_watermark_ok()輔助函數(shù)接收幾個參數(shù),這些參數(shù)決定內(nèi)存ZONE中可用頁幀數(shù)量的閾值min。特別是,如果滿足以下兩個條件,該函數(shù)返回值1,也就是具有足夠的內(nèi)存:

/*
*如果空閑頁幀在閾值之上,則返回1.考慮分配的大?。╫rder密數(shù)決定)
*/
intzone_watermark_ok(structzone*z,intorder,unsignedlongmark,
intclasszone_idx,intcan_try_harder,intgfp_high)
{
/*free_pages可能會變成負(fù)值,但是沒有關(guān)系*/
longmin=mark,free_pages=z->free_pages-(1<lowmem_reserve[classzone_idx])
return0;
/*除了要分配的頁幀,
*在`1`到`order`之間的空閑頁幀列表中的每一個`k`,
*至少有`min/(2^k)`個空閑頁幀。
*因此,如果`order`大于0,在大小為`2`的內(nèi)存塊列表中,
*至少有`min/2`個空閑頁幀;
*如果`order`大于0,在大小為`4`的內(nèi)存塊列表中,
*至少有`min/4`個空閑頁幀;以此類推。
*/
for(o=0;ofree_area[o].nr_free<>=1;

if(free_pages<=?min)
????????????return?0;
????}
????return?1;

閾值min的值由zone_watermark_ok()確定,如下所示:

可以將pages_min,pages_low和pages_high三個內(nèi)存ZONE區(qū)之一作為基本值作為函數(shù)的參數(shù)(參見本章前面的“預(yù)留頁幀池”一節(jié))。

如果設(shè)置了gfp_high標(biāo)志,則將基值除以2。通常,如果在gfp_mask中設(shè)置了__GFP_HIGHMEM標(biāo)志,也就是說,如果可以從高端內(nèi)存中分配頁幀的話,則該標(biāo)志等于1。

如果設(shè)置了can_try_harder標(biāo)志,則閾值將進(jìn)一步減少四分之一。如果在gfp_mask中設(shè)置了__GFP_WAIT標(biāo)志,或者當(dāng)前進(jìn)程是實(shí)時進(jìn)程,并且內(nèi)存分配是在進(jìn)程上下文中完成的(在中斷處理程序和可延遲函數(shù)之外),則該標(biāo)志通常等于1。

分配頁幀的具體實(shí)現(xiàn)

__alloc_pages()函數(shù)主要執(zhí)行以下步驟:

structpage*fastcall
__alloc_pages(unsignedintgfp_mask,unsignedintorder,
structzonelist*zonelist)
{
//...省略

/*如果調(diào)用方不能運(yùn)行直接回收算法,
*或者調(diào)用方具有實(shí)時調(diào)度策略,
*則調(diào)用方可能會更多地使用預(yù)留頁幀
*/
can_try_harder=(unlikely(rt_task(p))&&!in_interrupt())||!wait;
zones=zonelist->zones;/*內(nèi)存ZONE列表*/
if(unlikely(zones[0]==NULL)){
returnNULL;/*這應(yīng)該發(fā)生嗎?*/
}
classzone_idx=zone_idx(zones[0]);

restart:
/* 1. 執(zhí)行內(nèi)存區(qū)域的第一次掃描。
*在第一次掃描中,min閾值設(shè)置為z->pages_low,
*其中z指向正在分析的zone描述符
*(can_try_harder和gfp_high參數(shù)設(shè)置為零)。
*/
for(i=0;(z=zones[i])!=NULL;i++){

if(!zone_watermark_ok(z,order,z->pages_low,
classzone_idx,0,0))
continue;

page=buffered_rmqueue(z,order,gfp_mask);
if(page)
gotogot_pg;
}

/* 2. 如果在前一步中沒有終止,那么剩余的空閑內(nèi)存就不多了;
*應(yīng)該喚醒kswapd內(nèi)核線程,開始異步回收頁幀。
*/
for(i=0;(z=zones[i])!=NULL;i++)
wakeup_kswapd(z,order);

/* 3. 對內(nèi)存區(qū)域執(zhí)行第二次掃描:
*將值z->pages_min作為基本閾值傳遞。
*實(shí)際閾值還與can_try_harder和gfp_high標(biāo)志有關(guān)。
*(允許內(nèi)核和實(shí)時任務(wù)訪問預(yù)留頁幀池)
*這一步幾乎與步驟1相同,只是函數(shù)使用了較低的閾值。
*/
for(i=0;(z=zones[i])!=NULL;i++){
if(!zone_watermark_ok(z,order,z->pages_min,
classzone_idx,can_try_harder,
gfp_mask&__GFP_HIGH))
continue;

page=buffered_rmqueue(z,order,gfp_mask);
if(page)
gotogot_pg;
}

/* 4. 執(zhí)行第三次內(nèi)存區(qū)域掃描:
*如果前面沒有分配到內(nèi)存頁幀,則說明系統(tǒng)內(nèi)存應(yīng)該非常低了。
*如果內(nèi)核代碼不是中斷處理程序或可延遲函數(shù),
*且它正在嘗試回收頁幀(設(shè)置了PF_MEMALLOC或PF_MEMDIE標(biāo)志)。
*此時應(yīng)該進(jìn)行第3次掃描。
*此時應(yīng)該忽略低內(nèi)存閾值,即不調(diào)用zone_watermark_ok()。
*這應(yīng)該是耗盡低內(nèi)存預(yù)留頁幀的唯一情況
*(這些頁幀由zone描述符的lowmem_reserve字段指定)。
*在這種情況下,發(fā)送內(nèi)存請求的內(nèi)核代碼最終通過嘗試釋放頁幀,
*獲得它想要的內(nèi)存請求。
*如果沒有內(nèi)存ZONE包含足夠的頁幀,
*則函數(shù)返回NULL,并通知調(diào)用者分配失敗。
*/
if(((p->flags&PF_MEMALLOC)||
unlikely(test_thread_flag(TIF_MEMDIE)))&&
!in_interrupt()){
/*再一次遍歷zonelist,忽略min*/
for(i=0;(z=zones[i])!=NULL;i++){
page=buffered_rmqueue(z,order,gfp_mask);
if(page)
gotogot_pg;
}
gotonopage;
}

/*5.原子分配-這種情況我們不能做任何均衡處理
*這種情況下,該函數(shù)返回NULL以通知內(nèi)核代碼內(nèi)存分配失敗:
*這種情況下,沒有辦法在不阻塞當(dāng)前進(jìn)程的情況下滿足請求。
*/
if(!wait)
gotonopage;

rebalance:
/*6.在這里,當(dāng)前進(jìn)程可以被阻塞:
*調(diào)用cond_resched()來檢查其他進(jìn)程是否需要CPU。
*/
cond_resched();

/*7.設(shè)置當(dāng)前的PF_MEMALLOC標(biāo)志,
*表示進(jìn)程已準(zhǔn)備好執(zhí)行異步內(nèi)存回收。
*/
p->flags|=PF_MEMALLOC;

/*8.reclaim_state只包含一個字段reclaimed_slab,初始化為0*/
reclaim_state.reclaimed_slab=0;
p->reclaim_state=&reclaim_state;

/* 9. 尋找一些要回收的頁幀。
*該函數(shù)可能會阻塞當(dāng)前進(jìn)程。
*一旦該函數(shù)返回,重置當(dāng)前的PF_MEMALLOC標(biāo)志,
*并再次調(diào)用cond_resched()。
*/
did_some_progress=try_to_free_pages(zones,gfp_mask,order);

p->reclaim_state=NULL;
p->flags&=~PF_MEMALLOC;

cond_resched();

if(likely(did_some_progress)){
/*10.說明前一步釋放了一些頁幀,
*那么該函數(shù)將執(zhí)行與步驟3中相同的另一次內(nèi)存區(qū)域掃描。
*如果內(nèi)存分配請求不能被滿足,
* zone_watermark_ok函數(shù)決定是否應(yīng)該繼續(xù)掃描內(nèi)存區(qū)域。
*這兒使用高閾值,僅是為了捕獲并行的oom kill;
*(也就是說,如果內(nèi)存壓力還是很大,則應(yīng)該失敗)
*/
for(i=0;(z=zones[i])!=NULL;i++){
if(!zone_watermark_ok(z,order,z->pages_min,
classzone_idx,can_try_harder,
gfp_mask&__GFP_HIGH))
continue;

page=buffered_rmqueue(z,order,gfp_mask);
if(page)
gotogot_pg;
}
}
/*11.如果在步驟9中沒有釋放頁幀,那么內(nèi)核就有大麻煩了,
*因?yàn)榭捎脙?nèi)存非常低,無法回收任何頁幀。
*也許是時候做出一個關(guān)鍵的決定了:
*如果此時設(shè)置了__GFP_FS標(biāo)志,且清零了__GFP_NORETRY標(biāo)志
*如果內(nèi)核控制路徑允許執(zhí)行與文件系統(tǒng)相關(guān)的操作來終止進(jìn)程(gfp_mask中的'__GFP_FS'標(biāo)志已設(shè)置),并且'__GFP_NORETRY'標(biāo)志已清除,則執(zhí)行以下子步驟:
*/
elseif((gfp_mask&__GFP_FS)&&!(gfp_mask&__GFP_NORETRY)){
/* 11.a zone_watermark_ok函數(shù)決定是否應(yīng)該繼續(xù)掃描內(nèi)存區(qū)域。
*這兒使用高閾值z->pages_high,僅是為了捕獲并行的oom kill;
*(也就是說,如果內(nèi)存壓力還是很大,則應(yīng)該失?。?*
*因?yàn)樵摬绞褂玫拈撝当戎暗亩几撸源蟾怕蕰 ?*實(shí)際上,只有當(dāng)內(nèi)核的其他代碼已經(jīng)殺死了一個進(jìn)程并回收內(nèi)存后
*該步才能成功。但是,這一步避免了殺死兩個進(jìn)程的情況。
*/
for(i=0;(z=zones[i])!=NULL;i++){
if(!zone_watermark_ok(z,order,z->pages_high,
classzone_idx,0,0))
continue;

page=buffered_rmqueue(z,order,gfp_mask);
if(page)
gotogot_pg;
}

/*11.b殺死一些進(jìn)程,釋放內(nèi)存*/
out_of_memory(gfp_mask);

/*11.c跳轉(zhuǎn)回第1步*/
gotorestart;
}

/*如果__GFP_NORETRY標(biāo)志是清除的,并且內(nèi)存分配請求跨越最多8頁幀
*也就是說,盡量不要重復(fù)分配大于8個頁幀以上的內(nèi)存。
*或者_(dá)_GFP_REPEAT和__GFP_NOFAIL標(biāo)志之一被設(shè)置,
*函數(shù)調(diào)用blk_congestion_wait使進(jìn)程休眠一段時間,
*然后它跳回步驟6。
*否則,該函數(shù)返回NULL以通知調(diào)用者內(nèi)存分配失敗。
*/
do_retry=0;
if(!(gfp_mask&__GFP_NORETRY)){
if((order<=?3)?||?(gfp_mask?&?__GFP_REPEAT))
????????????do_retry?=?1;
????????if?(gfp_mask?&?__GFP_NOFAIL)
????????????do_retry?=?1;
????}
????if?(do_retry)?{
????????blk_congestion_wait(WRITE,?HZ/50);
????????goto?rebalance;
????}

nopage:
????if?(!(gfp_mask?&?__GFP_NOWARN)?&&?printk_ratelimit())?{
????????//?...省略
????}
????return?NULL;
got_pg:
????zone_statistics(zonelist,?z);
????return?page;
}

釋放頁幀

zone分配器還負(fù)責(zé)釋放頁幀,但要比分配頁幀簡單。

內(nèi)核中,所有釋放頁幀的宏和函數(shù),都是基于__free_pages()函數(shù)實(shí)現(xiàn)的。該函數(shù)的參數(shù)是page,待要釋放的第一個頁幀的頁描述符的地址;order,要釋放的連續(xù)頁幀組的對數(shù)大小。函數(shù)執(zhí)行以下步驟:

檢查第1個頁幀是否真的屬于動態(tài)內(nèi)存(它的PG_reserved標(biāo)志被清除);如果不是,則終止。

減少page->_count使用計(jì)數(shù)器;如果仍然大于等于0,終止。

如果order等于零,該函數(shù)調(diào)用free_hot_page()將頁幀釋放到相應(yīng)內(nèi)存區(qū)域的CPU本地?zé)峋彺嬷小?/p>

如果order大于0,它將頁幀添加到本地列表中,并調(diào)用free_pages_bulk()函數(shù)將它們釋放到適當(dāng)內(nèi)存區(qū)域的buddy系統(tǒng)中。

審核編輯:湯梓紅

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

    關(guān)注

    3

    文章

    1336

    瀏覽量

    40083
  • cpu
    cpu
    +關(guān)注

    關(guān)注

    68

    文章

    10698

    瀏覽量

    209330
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11123

    瀏覽量

    207895
  • 分配器
    +關(guān)注

    關(guān)注

    0

    文章

    192

    瀏覽量

    25611

原文標(biāo)題:Linux內(nèi)核8.6-內(nèi)存管理之ZONE內(nèi)存分配器

文章出處:【微信號:嵌入式ARM和Linux,微信公眾號:嵌入式ARM和Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    Linux內(nèi)核內(nèi)存規(guī)整總結(jié)

    1.前言 伙伴系統(tǒng)作為內(nèi)核最基礎(chǔ)的物理頁內(nèi)存分配器,具有高效、實(shí)現(xiàn)邏輯簡介等優(yōu)點(diǎn),其原理頁也盡可能降低內(nèi)存外部碎片產(chǎn)生,但依然無法杜絕碎片問題。外部碎片帶來的最大影響就是
    的頭像 發(fā)表于 11-11 11:17 ?1147次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>內(nèi)存</b>規(guī)整總結(jié)

    Linux內(nèi)核內(nèi)存管理slab分配器

    本文在行文的過程中,會多次提到cache或緩存的概念。如果沒有特殊在前面添加硬件的限定詞,就說明cache指的是slab分配器使用的軟件緩存的意思。如果添加了硬件限定詞,則指的是處理器的硬件緩存,比如L1-DCache、L1-ICache之類的。
    的頭像 發(fā)表于 02-22 09:25 ?928次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b><b class='flag-5'>之</b>slab<b class='flag-5'>分配器</b>

    如何去制作一個高效的內(nèi)存分配器

    高效內(nèi)存分配機(jī)制是什么意思?如何去制作一個高效的內(nèi)存分配器呢?
    發(fā)表于 01-20 06:57

    有關(guān)RT-Thread操作系統(tǒng)的內(nèi)存管理模塊基本知識簡析

    zone達(dá)到一定數(shù)目后,系統(tǒng)就會把這個全空閑的zone釋放到頁面分配器中去?! ?b class='flag-5'>內(nèi)存管理的應(yīng)用場景  RT-Threadd操作系統(tǒng)將
    發(fā)表于 05-11 15:14

    關(guān)于RTT支持的內(nèi)存分配算法

    的,能夠分配相同大小內(nèi)存塊的zone會鏈接在一個鏈表中,而72種對象的zone鏈表則放在一個數(shù)組(zone arry)中統(tǒng)一
    發(fā)表于 04-27 14:40

    關(guān)于RTT支持的內(nèi)存分配算法

    是固定的,能夠分配相同大小內(nèi)存塊的zone會鏈接在一個鏈表中,而72種對象的zone鏈表則放在一個數(shù)組(zone arry)中統(tǒng)一
    發(fā)表于 04-27 14:42

    linux內(nèi)存管理中的SLAB分配器詳解

    管理區(qū)頁框分配器,這里我們簡稱為頁框分配器,在頁框分配器中主要是管理物理內(nèi)存,將物理
    發(fā)表于 05-17 15:01 ?2093次閱讀
    <b class='flag-5'>linux</b><b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>中的SLAB<b class='flag-5'>分配器</b>詳解

    深入剖析SLUB分配器和SLAB分配器的區(qū)別

    首先為什么要說slub分配器,內(nèi)核里小內(nèi)存分配一共有三種,SLAB/SLUB/SLOB,slub分配器是slab
    發(fā)表于 05-17 16:05 ?1019次閱讀
    深入剖析SLUB<b class='flag-5'>分配器</b>和SLAB<b class='flag-5'>分配器</b>的區(qū)別

    Linux內(nèi)核深度解析》之內(nèi)存地址空間

    內(nèi)核空間提供了把頁劃分成小內(nèi)存分配的塊分配器,提供分配內(nèi)存的接口 kmalloc()和釋放
    的頭像 發(fā)表于 07-15 14:22 ?2163次閱讀

    bootmem分配器使用的數(shù)據(jù)結(jié)構(gòu)

    內(nèi)核初始化的過程中需要分配內(nèi)存,內(nèi)核提供了臨時的引導(dǎo)內(nèi)存分配器,在頁
    的頭像 發(fā)表于 07-22 11:18 ?1346次閱讀

    Linux引導(dǎo)內(nèi)存分配器

    早期使用的引導(dǎo)內(nèi)存分配器是 bootmem,目前正在使用 memblock 取代 bootmem。如果開啟配置宏 CONFIG_NO_BOOTMEM,memblock 就會取代 bootmem。為了保證兼容性,bootmem 和 memblock 提供了相同的接口。
    的頭像 發(fā)表于 07-22 11:17 ?1363次閱讀

    Linux內(nèi)核伙伴分配器

    內(nèi)核初始化完畢后,使用頁分配器管理物理頁,當(dāng)前使用的頁分配器是伙伴分配器,伙伴分配器的特點(diǎn)是算法
    的頭像 發(fā)表于 07-25 14:06 ?1606次閱讀

    Linux內(nèi)核分配器

    為了解決小塊內(nèi)存分配問題,Linux 內(nèi)核提供了塊分配器,最早實(shí)現(xiàn)的塊分配器是SLAB
    的頭像 發(fā)表于 07-27 09:35 ?1477次閱讀

    Linux內(nèi)核引導(dǎo)內(nèi)存分配器的原理

    Linux內(nèi)核引導(dǎo)內(nèi)存分配器使用的是伙伴系統(tǒng)算法。這種算法是一種用于動態(tài)內(nèi)存分配的高效算法,它將
    發(fā)表于 04-03 14:52 ?343次閱讀

    Linux 內(nèi)存管理總結(jié)

    一、Linux內(nèi)存管理概述 Linux內(nèi)存管理是指對系統(tǒng)內(nèi)存
    的頭像 發(fā)表于 11-10 14:58 ?429次閱讀
    <b class='flag-5'>Linux</b> <b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>總結(jié)