0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
电子发烧友
开通电子发烧友VIP会员 尊享10大特权
海量资料免费下载
精品直播免费看
优质内容免费畅学
课程9折专享价
創(chuàng)作中心

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

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

知識(shí)總結(jié):一篇就讓你入Linux內(nèi)核的大門

strongerHuang ? 來(lái)源:人人都是極客 ? 作者:布道師Peter ? 2021-05-08 10:18 ? 次閱讀

Linux的內(nèi)存管理可謂是學(xué)好Linux的必經(jīng)之路,也是Linux的關(guān)鍵知識(shí)點(diǎn),有人說(shuō)打通了內(nèi)存管理的知識(shí),也就打通了Linux的任督二脈,這一點(diǎn)不夸張。有人問(wèn)網(wǎng)上有很多Linux內(nèi)存管理的內(nèi)容,為什么還要看你這一篇,這正是我寫此文的原因,網(wǎng)上碎片化的相關(guān)知識(shí)點(diǎn)大都是東拼西湊,先不說(shuō)正確性與否,就連基本的邏輯都沒(méi)有搞清楚,我可以負(fù)責(zé)任的說(shuō)Linux內(nèi)存管理只需要看此文一篇就可以讓你入Linux內(nèi)核的大門,省去你東找西找的時(shí)間,讓你形成內(nèi)存管理知識(shí)的閉環(huán)。

文章比較長(zhǎng),做好準(zhǔn)備,深呼吸,讓我們一起打開(kāi)Linux內(nèi)核的大門!

Linux內(nèi)存管理之CPU訪問(wèn)內(nèi)存的過(guò)程我喜歡用圖的方式來(lái)說(shuō)明問(wèn)題,簡(jiǎn)單直接:

debd9980-af2a-11eb-bf61-12bb97331649.png

藍(lán)色部分是cpu,灰色部分是內(nèi)存,白色部分就是cpu訪問(wèn)內(nèi)存的過(guò)程,也是地址轉(zhuǎn)換的過(guò)程。在解釋地址轉(zhuǎn)換的本質(zhì)前我們先理解下幾個(gè)概念:

TLB:MMU工作的過(guò)程就是查詢頁(yè)表的過(guò)程。如果把頁(yè)表放在內(nèi)存中查詢的時(shí)候開(kāi)銷太大,因此為了提高查找效率,專門用一小片訪問(wèn)更快的區(qū)域存放地址轉(zhuǎn)換條目。(當(dāng)頁(yè)表內(nèi)容有變化的時(shí)候,需要清除TLB,以防止地址映射出錯(cuò)。)

Caches:cpu和內(nèi)存之間的緩存機(jī)制,用于提高訪問(wèn)速率,armv8架構(gòu)的話上圖的caches其實(shí)是L2 Cache,這里就不做進(jìn)一步解釋了。

虛擬地址轉(zhuǎn)換為物理地址的本質(zhì)

我們知道內(nèi)核中的尋址空間大小是由CONFIG_ARM64_VA_BITS控制的,這里以48位為例,ARMv8中,Kernel Space的頁(yè)表基地址存放在TTBR1_EL1寄存器中,User Space頁(yè)表基地址存放在TTBR0_EL0寄存器中,其中內(nèi)核地址空間的高位為全1,(0xFFFF0000_00000000 ~ 0xFFFFFFFF_FFFFFFFF),用戶地址空間的高位為全0,(0x00000000_00000000 ~ 0x0000FFFF_FFFFFFFF)

ded4b408-af2a-11eb-bf61-12bb97331649.png

有了宏觀概念,下面我們以內(nèi)核態(tài)尋址過(guò)程為例看下是如何把虛擬地址轉(zhuǎn)換為物理地址的。

我們知道linux采用了分頁(yè)機(jī)制,通常采用四級(jí)頁(yè)表,頁(yè)全局目錄(PGD),頁(yè)上級(jí)目錄(PUD),頁(yè)中間目錄(PMD),頁(yè)表(PTE)。如下:

df02ef08-af2a-11eb-bf61-12bb97331649.png

從CR3寄存器中讀取頁(yè)目錄所在物理頁(yè)面的基址(即所謂的頁(yè)目錄基址),從線性地址的第一部分獲取頁(yè)目錄項(xiàng)的索引,兩者相加得到頁(yè)目錄項(xiàng)的物理地址。

第一次讀取內(nèi)存得到pgd_t結(jié)構(gòu)的目錄項(xiàng),從中取出物理頁(yè)基址取出,即頁(yè)上級(jí)頁(yè)目錄的物理基地址。

從線性地址的第二部分中取出頁(yè)上級(jí)目錄項(xiàng)的索引,與頁(yè)上級(jí)目錄基地址相加得到頁(yè)上級(jí)目錄項(xiàng)的物理地址。

第二次讀取內(nèi)存得到pud_t結(jié)構(gòu)的目錄項(xiàng),從中取出頁(yè)中間目錄的物理基地址。

從線性地址的第三部分中取出頁(yè)中間目錄項(xiàng)的索引,與頁(yè)中間目錄基址相加得到頁(yè)中間目錄項(xiàng)的物理地址。

第三次讀取內(nèi)存得到pmd_t結(jié)構(gòu)的目錄項(xiàng),從中取出頁(yè)表的物理基地址。

從線性地址的第四部分中取出頁(yè)表項(xiàng)的索引,與頁(yè)表基址相加得到頁(yè)表項(xiàng)的物理地址。

第四次讀取內(nèi)存得到pte_t結(jié)構(gòu)的目錄項(xiàng),從中取出物理頁(yè)的基地址。

從線性地址的第五部分中取出物理頁(yè)內(nèi)偏移量,與物理頁(yè)基址相加得到最終的物理地址。

第五次讀取內(nèi)存得到最終要訪問(wèn)的數(shù)據(jù)。

整個(gè)過(guò)程是比較機(jī)械的,每次轉(zhuǎn)換先獲取物理頁(yè)基地址,再?gòu)木€性地址中獲取索引,合成物理地址后再訪問(wèn)內(nèi)存。不管是頁(yè)表還是要訪問(wèn)的數(shù)據(jù)都是以頁(yè)為單位存放在主存中的,因此每次訪問(wèn)內(nèi)存時(shí)都要先獲得基址,再通過(guò)索引(或偏移)在頁(yè)內(nèi)訪問(wèn)數(shù)據(jù),因此可以將線性地址看作是若干個(gè)索引的集合。

Linux內(nèi)存初始化有了armv8架構(gòu)訪問(wèn)內(nèi)存的理解,我們來(lái)看下linux在內(nèi)存這塊的初始化就更容易理解了。

創(chuàng)建啟動(dòng)頁(yè)表:

匯編代碼階段的head.S文件中,負(fù)責(zé)創(chuàng)建映射關(guān)系的函數(shù)是create_page_tables。create_page_tables函數(shù)負(fù)責(zé)identity mapping和kernel image mapping。

identity map:是指把idmap_text區(qū)域的物理地址映射到相等的虛擬地址上,這種映射完成后,其虛擬地址等于物理地址。idmap_text區(qū)域都是一些打開(kāi)MMU相關(guān)的代碼。

kernel image map:將kernel運(yùn)行需要的地址(kernel txt、rodata、data、bss等等)進(jìn)行映射。

arch/arm64/kernel/head.S:

ENTRY(stext)

bl preserve_boot_args

bl el2_setup // Drop to EL1, w0=cpu_boot_mode

adrp x23, __PHYS_OFFSET

and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0

bl set_cpu_boot_mode_flag

bl __create_page_tables

/*

* The following calls CPU setup code, see arch/arm64/mm/proc.S for

* details.

* On return, the CPU will be ready for the MMU to be turned on and

* the TCR will have been set.

*/

bl __cpu_setup // initialise processor

b __primary_switch

ENDPROC(stext)

__create_page_tables主要執(zhí)行的就是identity map和kernel image map:

__create_page_tables:

。..。..

create_pgd_entry x0, x3, x5, x6

mov x5, x3 // __pa(__idmap_text_start)

adr_l x6, __idmap_text_end // __pa(__idmap_text_end)

create_block_map x0, x7, x3, x5, x6

/*

* Map the kernel image (starting with PHYS_OFFSET)。

*/

adrp x0, swapper_pg_dir

mov_q x5, KIMAGE_VADDR + TEXT_OFFSET // compile time __va(_text)

add x5, x5, x23 // add KASLR displacement

create_pgd_entry x0, x5, x3, x6

adrp x6, _end // runtime __pa(_end)

adrp x3, _text // runtime __pa(_text)

sub x6, x6, x3 // _end - _text

add x6, x6, x5 // runtime __va(_end)

create_block_map x0, x7, x3, x5, x6

。..。..

其中調(diào)用create_pgd_entry進(jìn)行PGD及所有中間level(PUD, PMD)頁(yè)表的創(chuàng)建,調(diào)用create_block_map進(jìn)行PTE頁(yè)表的映射。關(guān)于四級(jí)頁(yè)表的關(guān)系如下圖所示,這里就不進(jìn)一步解釋了。

匯編結(jié)束后的內(nèi)存映射關(guān)系如下圖所示:

df11e562-af2a-11eb-bf61-12bb97331649.png

等內(nèi)存初始化后就可以進(jìn)入真正的內(nèi)存管理了,初始化我總結(jié)了一下,大體分為四步:

物理內(nèi)存進(jìn)系統(tǒng)前

用memblock模塊來(lái)對(duì)內(nèi)存進(jìn)行管理

頁(yè)表映射

zone初始化

Linux是如何組織物理內(nèi)存的?

node目前計(jì)算機(jī)系統(tǒng)有兩種體系結(jié)構(gòu):

非一致性內(nèi)存訪問(wèn) NUMA(Non-Uniform Memory Access)意思是內(nèi)存被劃分為各個(gè)node,訪問(wèn)一個(gè)node花費(fèi)的時(shí)間取決于CPU離這個(gè)node的距離。每一個(gè)cpu內(nèi)部有一個(gè)本地的node,訪問(wèn)本地node時(shí)間比訪問(wèn)其他node的速度快

一致性內(nèi)存訪問(wèn) UMA(Uniform Memory Access)也可以稱為SMP(Symmetric Multi-Process)對(duì)稱多處理器。意思是所有的處理器訪問(wèn)內(nèi)存花費(fèi)的時(shí)間是一樣的。也可以理解整個(gè)內(nèi)存只有一個(gè)node。

zone

ZONE的意思是把整個(gè)物理內(nèi)存劃分為幾個(gè)區(qū)域,每個(gè)區(qū)域有特殊的含義

page

代表一個(gè)物理頁(yè),在內(nèi)核中一個(gè)物理頁(yè)用一個(gè)struct page表示。

page frame

為了描述一個(gè)物理page,內(nèi)核使用struct page結(jié)構(gòu)來(lái)表示一個(gè)物理頁(yè)。假設(shè)一個(gè)page的大小是4K的,內(nèi)核會(huì)將整個(gè)物理內(nèi)存分割成一個(gè)一個(gè)4K大小的物理頁(yè),而4K大小物理頁(yè)的區(qū)域我們稱為page frame

df404178-af2a-11eb-bf61-12bb97331649.png

page frame num(pfn)

pfn是對(duì)每個(gè)page frame的編號(hào)。故物理地址和pfn的關(guān)系是:

物理地址》》PAGE_SHIFT = pfn

pfn和page的關(guān)系

內(nèi)核中支持了好幾個(gè)內(nèi)存模型:CONFIG_FLATMEM(平坦內(nèi)存模型)CONFIG_DISCONTIGMEM(不連續(xù)內(nèi)存模型)CONFIG_SPARSEMEM_VMEMMAP(稀疏的內(nèi)存模型)目前ARM64使用的稀疏的類型模式。

系統(tǒng)啟動(dòng)的時(shí)候,內(nèi)核會(huì)將整個(gè)struct page映射到內(nèi)核虛擬地址空間vmemmap的區(qū)域,所以我們可以簡(jiǎn)單的認(rèn)為struct page的基地址是vmemmap,則:

vmemmap+pfn的地址就是此struct page對(duì)應(yīng)的地址。

Linux分區(qū)頁(yè)框分配器頁(yè)框分配在內(nèi)核里的機(jī)制我們叫做分區(qū)頁(yè)框分配器(zoned page frame allocator),在linux系統(tǒng)中,分區(qū)頁(yè)框分配器管理著所有物理內(nèi)存,無(wú)論你是內(nèi)核還是進(jìn)程,都需要請(qǐng)求分區(qū)頁(yè)框分配器,這時(shí)才會(huì)分配給你應(yīng)該獲得的物理內(nèi)存頁(yè)框。當(dāng)你所擁有的頁(yè)框不再使用時(shí),你必須釋放這些頁(yè)框,讓這些頁(yè)框回到管理區(qū)頁(yè)框分配器當(dāng)中。

有時(shí)候目標(biāo)管理區(qū)不一定有足夠的頁(yè)框去滿足分配,這時(shí)候系統(tǒng)會(huì)從另外兩個(gè)管理區(qū)中獲取要求的頁(yè)框,但這是按照一定規(guī)則去執(zhí)行的,如下:

如果要求從DMA區(qū)中獲取,就只能從ZONE_DMA區(qū)中獲取。

如果沒(méi)有規(guī)定從哪個(gè)區(qū)獲取,就按照順序從 ZONE_NORMAL -》 ZONE_DMA 獲取。

如果規(guī)定從HIGHMEM區(qū)獲取,就按照順序從 ZONE_HIGHMEM -》 ZONE_NORMAL -》 ZONE_DMA 獲取。

df72aa6e-af2a-11eb-bf61-12bb97331649.png

內(nèi)核中根據(jù)不同的分配需求有6個(gè)函數(shù)接口來(lái)請(qǐng)求頁(yè)框,最終都會(huì)調(diào)用到__alloc_pages_nodemask。

df80e8cc-af2a-11eb-bf61-12bb97331649.png

struct page *

__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid,

nodemask_t *nodemask)

{

page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);//fastpath分配頁(yè)面:從pcp(per_cpu_pages)和伙伴系統(tǒng)中正常的分配內(nèi)存空間

。..。..

page = __alloc_pages_slowpath(alloc_mask, order, &ac);//slowpath分配頁(yè)面:如果上面沒(méi)有分配到空間,調(diào)用下面函數(shù)慢速分配,允許等待和回收

。..。..

}

在頁(yè)面分配時(shí),有兩種路徑可以選擇,如果在快速路徑中分配成功了,則直接返回分配的頁(yè)面;快速路徑分配失敗則選擇慢速路徑來(lái)進(jìn)行分配??偨Y(jié)如下:

正常分配(或叫快速分配):

如果分配的是單個(gè)頁(yè)面,考慮從per CPU緩存中分配空間,如果緩存中沒(méi)有頁(yè)面,從伙伴系統(tǒng)中提取頁(yè)面做補(bǔ)充。

分配多個(gè)頁(yè)面時(shí),從指定類型中分配,如果指定類型中沒(méi)有足夠的頁(yè)面,從備用類型鏈表中分配。最后會(huì)試探保留類型鏈表。

慢速(允許等待和頁(yè)面回收)分配:

當(dāng)上面兩種分配方案都不能滿足要求時(shí),考慮頁(yè)面回收、殺死進(jìn)程等操作后在試。

Linux頁(yè)框分配器之伙伴算法static struct page *

get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,

const struct alloc_context *ac)

{

for_next_zone_zonelist_nodemask(zone, z, ac-》zonelist, ac-》high_zoneidx, ac-》nodemask)

{

if (!zone_watermark_fast(zone, order, mark, ac_classzone_idx(ac), alloc_flags))

{

ret = node_reclaim(zone-》zone_pgdat, gfp_mask, order);

switch (ret) {

case NODE_RECLAIM_NOSCAN

continue;

case NODE_RECLAIM_FULL:

continue;

default:

if (zone_watermark_ok(zone, order, mark, ac_classzone_idx(ac), alloc_flags))

goto try_this_zone;

continue;

}

}

try_this_zone: //本zone正常水位

page = rmqueue(ac-》preferred_zoneref-》zone, zone, order, gfp_mask, alloc_flags, ac-》migratetype);

}

return NULL;

}

首先遍歷當(dāng)前zone,按照HIGHMEM-》NORMAL的方向進(jìn)行遍歷,判斷當(dāng)前zone是否能夠進(jìn)行內(nèi)存分配的條件是首先判斷free memory是否滿足low water mark水位值,如果不滿足則進(jìn)行一次快速的內(nèi)存回收操作,然后再次檢測(cè)是否滿足low water mark,如果還是不能滿足,相同步驟遍歷下一個(gè)zone,滿足的話進(jìn)入正常的分配情況,即rmqueue函數(shù),這也是伙伴系統(tǒng)的核心。

Buddy 分配算法

在看函數(shù)前,我們先看下算法,因?yàn)槲乙恢闭J(rèn)為有了“道”的理解才好進(jìn)一步理解“術(shù)”。

df8d2b00-af2a-11eb-bf61-12bb97331649.png

假設(shè)這是一段連續(xù)的頁(yè)框,陰影部分表示已經(jīng)被使用的頁(yè)框,現(xiàn)在需要申請(qǐng)一個(gè)連續(xù)的5個(gè)頁(yè)框。這個(gè)時(shí)候,在這段內(nèi)存上不能找到連續(xù)的5個(gè)空閑的頁(yè)框,就會(huì)去另一段內(nèi)存上去尋找5個(gè)連續(xù)的頁(yè)框,這樣子,久而久之就形成了頁(yè)框的浪費(fèi)。為了避免出現(xiàn)這種情況,Linux內(nèi)核中引入了伙伴系統(tǒng)算法(Buddy system)。把所有的空閑頁(yè)框分組為11個(gè)塊鏈表,每個(gè)塊鏈表分別包含大小為1,2,4,8,16,32,64,128,256,512和1024個(gè)連續(xù)頁(yè)框的頁(yè)框塊。最大可以申請(qǐng)1024個(gè)連續(xù)頁(yè)框,對(duì)應(yīng)4MB大小的連續(xù)內(nèi)存。每個(gè)頁(yè)框塊的第一個(gè)頁(yè)框的物理地址是該塊大小的整數(shù)倍,如圖:

dfb9b0b2-af2a-11eb-bf61-12bb97331649.png

假設(shè)要申請(qǐng)一個(gè)256個(gè)頁(yè)框的塊,先從256個(gè)頁(yè)框的鏈表中查找空閑塊,如果沒(méi)有,就去512個(gè)頁(yè)框的鏈表中找,找到了則將頁(yè)框塊分為2個(gè)256個(gè)頁(yè)框的塊,一個(gè)分配給應(yīng)用,另外一個(gè)移到256個(gè)頁(yè)框的鏈表中。如果512個(gè)頁(yè)框的鏈表中仍沒(méi)有空閑塊,繼續(xù)向1024個(gè)頁(yè)框的鏈表查找,如果仍然沒(méi)有,則返回錯(cuò)誤。頁(yè)框塊在釋放時(shí),會(huì)主動(dòng)將兩個(gè)連續(xù)的頁(yè)框塊合并為一個(gè)較大的頁(yè)框塊。

從上面可以知道Buddy算法一直在對(duì)頁(yè)框做拆開(kāi)合并拆開(kāi)合并的動(dòng)作。Buddy算法牛逼就牛逼在運(yùn)用了世界上任何正整數(shù)都可以由2^n的和組成。這也是Buddy算法管理空閑頁(yè)表的本質(zhì)??臻e內(nèi)存的信息我們可以通過(guò)以下命令獲取:

dfee545c-af2a-11eb-bf61-12bb97331649.png

也可以通過(guò)echo m 》 /proc/sysrq-trigger來(lái)觀察buddy狀態(tài),與/proc/buddyinfo的信息是一致的:

dffcb43e-af2a-11eb-bf61-12bb97331649.png

Buddy 分配函數(shù)

static inline

struct page *rmqueue(struct zone *preferred_zone,

struct zone *zone, unsigned int order,

gfp_t gfp_flags, unsigned int alloc_flags,

int migratetype)

{

if (likely(order == 0)) { //如果order=0則從pcp中分配

page = rmqueue_pcplist(preferred_zone, zone, order, gfp_flags, migratetype);

}

do {

page = NULL;

if (alloc_flags & ALLOC_HARDER) {//如果分配標(biāo)志中設(shè)置了ALLOC_HARDER,則從free_list[MIGRATE_HIGHATOMIC]的鏈表中進(jìn)行頁(yè)面分配

page = __rmqueue_smallest(zone, order, MIGRATE_HIGHATOMIC);

}

if (!page) //前兩個(gè)條件都不滿足,則在正常的free_list[MIGRATE_*]中進(jìn)行分配

page = __rmqueue(zone, order, migratetype);

} while (page && check_new_pages(page, order));

。..。..

}

e1011f8c-af2a-11eb-bf61-12bb97331649.png

Linux分區(qū)頁(yè)框分配器之水位我們講頁(yè)框分配器的時(shí)候講到了快速分配和慢速分配,其中伙伴算法是在快速分配里做的,忘記的小伙伴我們?cè)倏聪拢?/p>

static struct page *

get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,

const struct alloc_context *ac)

{

for_next_zone_zonelist_nodemask(zone, z, ac-》zonelist, ac-》high_zoneidx, ac-》nodemask)

{

if (!zone_watermark_fast(zone, order, mark, ac_classzone_idx(ac), alloc_flags))

{

ret = node_reclaim(zone-》zone_pgdat, gfp_mask, order);

switch (ret) {

case NODE_RECLAIM_NOSCAN:

continue;

case NODE_RECLAIM_FULL:

continue;

default:

if (zone_watermark_ok(zone, order, mark, ac_classzone_idx(ac), alloc_flags))

goto try_this_zone;

continue;

}

}

try_this_zone: //本zone正常水位

page = rmqueue(ac-》preferred_zoneref-》zone, zone, order, gfp_mask, alloc_flags, ac-》migratetype);

}

return NULL;

}

可以看到在進(jìn)行伙伴算法分配前有個(gè)關(guān)于水位的判斷,今天我們就看下水位的概念。

簡(jiǎn)單的說(shuō)在使用分區(qū)頁(yè)面分配器中會(huì)將可以用的free pages與zone里的水位(watermark)進(jìn)行比較。

水位初始化

nr_free_buffer_pages 是獲取ZONE_DMA和ZONE_NORMAL區(qū)中高于high水位的總頁(yè)數(shù)nr_free_buffer_pages = managed_pages - high_pages

min_free_kbytes 是總的min大小,min_free_kbytes = 4 * sqrt(lowmem_kbytes)

setup_per_zone_wmarks 根據(jù)總的min值,再加上各個(gè)zone在總內(nèi)存中的占比,然后通過(guò)do_div就計(jì)算出他們各自的min值,進(jìn)而計(jì)算出各個(gè)zone的水位大小。min,low,high的關(guān)系如下:low = min *125%;

high = min * 150%

minhigh = 46

setup_per_zone_lowmem_reserve 當(dāng)從Normal失敗后,會(huì)嘗試從DMA申請(qǐng)分配,通過(guò)lowmem_reserve[DMA],限制來(lái)自Normal的分配請(qǐng)求。其值可以通過(guò)/proc/sys/vm/lowmem_reserve_ratio來(lái)修改。

e10cbdba-af2a-11eb-bf61-12bb97331649.png

從這張圖可以看出:

如果空閑頁(yè)數(shù)目min值,則該zone非常缺頁(yè),頁(yè)面回收壓力很大,應(yīng)用程序?qū)憙?nèi)存操作就會(huì)被阻塞,直接在應(yīng)用程序的進(jìn)程上下文中進(jìn)行回收,即direct reclaim。

如果空閑頁(yè)數(shù)目小于low值,kswapd線程將被喚醒,并開(kāi)始釋放回收頁(yè)面。

如果空閑頁(yè)面的值大于high值,則該zone的狀態(tài)很完美, kswapd線程將重新休眠。

Linux頁(yè)框分配器之內(nèi)存碎片化整理什么是內(nèi)存碎片化

Linux物理內(nèi)存碎片化包括兩種:內(nèi)部碎片化和外部碎片化。

內(nèi)部碎片化:

指分配給用戶的內(nèi)存空間中未被使用的部分。例如進(jìn)程需要使用3K bytes物理內(nèi)存,于是向系統(tǒng)申請(qǐng)了大小等于3Kbytes的內(nèi)存,但是由于Linux內(nèi)核伙伴系統(tǒng)算法最小顆粒是4K bytes,所以分配的是4Kbytes內(nèi)存,那么其中1K bytes未被使用的內(nèi)存就是內(nèi)存內(nèi)碎片。

外部碎片化:

指系統(tǒng)中無(wú)法利用的小內(nèi)存塊。例如系統(tǒng)剩余內(nèi)存為16K bytes,但是這16K bytes內(nèi)存是由4個(gè)4K bytes的頁(yè)面組成,即16K內(nèi)存物理頁(yè)幀號(hào)#1不連續(xù)。在系統(tǒng)剩余16K bytes內(nèi)存的情況下,系統(tǒng)卻無(wú)法成功分配大于4K的連續(xù)物理內(nèi)存,該情況就是內(nèi)存外碎片導(dǎo)致。

碎片化整理算法

Linux內(nèi)存對(duì)碎片化的整理算法主要應(yīng)用了內(nèi)核的頁(yè)面遷移機(jī)制,是一種將可移動(dòng)頁(yè)面進(jìn)行遷移后騰出連續(xù)物理內(nèi)存的方法。

假設(shè)存在一個(gè)非常小的內(nèi)存域如下:

e13a37e0-af2a-11eb-bf61-12bb97331649.png

藍(lán)色表示空閑的頁(yè)面,白色表示已經(jīng)被分配的頁(yè)面,可以看到如上內(nèi)存域的空閑頁(yè)面(藍(lán)色)非常零散,無(wú)法分配大于兩頁(yè)的連續(xù)物理內(nèi)存。

下面演示一下內(nèi)存規(guī)整的簡(jiǎn)化工作原理,內(nèi)核會(huì)運(yùn)行兩個(gè)獨(dú)立的掃描動(dòng)作:第一個(gè)掃描從內(nèi)存域的底部開(kāi)始,一邊掃描一邊將已分配的可移動(dòng)(MOVABLE)頁(yè)面記錄到一個(gè)列表中:

e14a58b4-af2a-11eb-bf61-12bb97331649.png

另外第二掃描是從內(nèi)存域的頂部開(kāi)始,掃描可以作為頁(yè)面遷移目標(biāo)的空閑頁(yè)面位置,然后也記錄到一個(gè)列表里面:

e450a388-af2a-11eb-bf61-12bb97331649.png

等兩個(gè)掃描在域中間相遇,意味著掃描結(jié)束,然后將左邊掃描得到的已分配的頁(yè)面遷移到右邊空閑的頁(yè)面中,左邊就形成了一段連續(xù)的物理內(nèi)存,完成頁(yè)面規(guī)整。

e45ee916-af2a-11eb-bf61-12bb97331649.png

碎片化整理的三種方式

static struct page *

__alloc_pages_direct_compact(gfp_t gfp_mask, unsigned int order,

unsigned int alloc_flags, const struct alloc_context *ac,

enum compact_priority prio, enum compact_result *compact_result)

{

struct page *page;

unsigned int noreclaim_flag;

if (!order)

return NULL;

noreclaim_flag = memalloc_noreclaim_save();

*compact_result = try_to_compact_pages(gfp_mask, order, alloc_flags, ac,

prio);

memalloc_noreclaim_restore(noreclaim_flag);

if (*compact_result 《= COMPACT_INACTIVE)

return NULL;

count_vm_event(COMPACTSTALL);

page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac);

if (page) {

struct zone *zone = page_zone(page);

zone-》compact_blockskip_flush = false;

compaction_defer_reset(zone, order, true);

count_vm_event(COMPACTSUCCESS);

return page;

}

count_vm_event(COMPACTFAIL);

cond_resched();

return NULL;

}

在linux內(nèi)核里一共有3種方式可以碎片化整理,我們總結(jié)如下:

e4710f60-af2a-11eb-bf61-12bb97331649.png

Linux slab分配器在Linux中,伙伴系統(tǒng)是以頁(yè)為單位分配內(nèi)存。但是現(xiàn)實(shí)中很多時(shí)候卻以字節(jié)為單位,不然申請(qǐng)10Bytes內(nèi)存還要給1頁(yè)的話就太浪費(fèi)了。slab分配器就是為小內(nèi)存分配而生的。slab分配器分配內(nèi)存以Byte為單位。但是slab分配器并沒(méi)有脫離伙伴系統(tǒng),而是基于伙伴系統(tǒng)分配的大內(nèi)存進(jìn)一步細(xì)分成小內(nèi)存分配。

他們之間的關(guān)系可以用一張圖來(lái)描述:

e4c22d82-af2a-11eb-bf61-12bb97331649.png

流程分析

kmem_cache_alloc 主要四步:

先從 kmem_cache_cpu-》freelist中分配,如果freelist為null

e4dce3e8-af2a-11eb-bf61-12bb97331649.png

接著去 kmem_cache_cpu-》partital鏈表中分配,如果此鏈表為null

e4fc976a-af2a-11eb-bf61-12bb97331649.png

接著去 kmem_cache_node-》partital鏈表分配,如果此鏈表為null

e51b5e84-af2a-11eb-bf61-12bb97331649.png

重新分配一個(gè)slab。

Linux 內(nèi)存管理之vmalloc根據(jù)前面的系列文章,我們知道了buddy system是基于頁(yè)框分配器,kmalloc是基于slab分配器,而且這些分配的地址都是物理內(nèi)存連續(xù)的。但是隨著碎片化的積累,連續(xù)物理內(nèi)存的分配就會(huì)變得困難,對(duì)于那些非DMA訪問(wèn),不一定非要連續(xù)物理內(nèi)存的話完全可以像malloc那樣,將不連續(xù)的物理內(nèi)存頁(yè)框映射到連續(xù)的虛擬地址空間中,這就是vmap的來(lái)源)(提供把離散的page映射到連續(xù)的虛擬地址空間),vmalloc的分配就是基于這個(gè)機(jī)制來(lái)實(shí)現(xiàn)的。

e538f37c-af2a-11eb-bf61-12bb97331649.png

vmalloc最小分配一個(gè)page,并且分配到的頁(yè)面不保證是連續(xù)的,因?yàn)関malloc內(nèi)部調(diào)用alloc_page多次分配單個(gè)頁(yè)面。

e56b72fc-af2a-11eb-bf61-12bb97331649.png

vmalloc的區(qū)域就是在上圖中VMALLOC_START - VMALLOC_END之間,可通過(guò)/proc/vmallocinfo查看。

e89b76f2-af2a-11eb-bf61-12bb97331649.png

vmalloc流程

主要分以下三步:

從VMALLOC_START到VMALLOC_END查找空閑的虛擬地址空間(hole)

根據(jù)分配的size,調(diào)用alloc_page依次分配單個(gè)頁(yè)面。

把分配的單個(gè)頁(yè)面,映射到第一步中找到的連續(xù)的虛擬地址。把分配的單個(gè)頁(yè)面,映射到第一步中找到的連續(xù)的虛擬地址。

e9057e12-af2a-11eb-bf61-12bb97331649.png

Linux進(jìn)程的內(nèi)存管理之缺頁(yè)異常當(dāng)進(jìn)程訪問(wèn)這些還沒(méi)建立映射關(guān)系的虛擬地址時(shí),處理器會(huì)自動(dòng)觸發(fā)缺頁(yè)異常。

ARM64把異常分為同步異常和異步異常,通常異步異常指的是中斷(可看《上帝視角看中斷》),同步異常指的是異常。關(guān)于ARM異常處理的文章可參考《ARMv8異常處理簡(jiǎn)介》。

當(dāng)處理器有異常發(fā)生時(shí),處理器會(huì)先跳轉(zhuǎn)到ARM64的異常向量表中:

ENTRY(vectors)

kernel_ventry 1, sync_invalid // Synchronous EL1t

kernel_ventry 1, irq_invalid // IRQ EL1t

kernel_ventry 1, fiq_invalid // FIQ EL1t

kernel_ventry 1, error_invalid // Error EL1t

kernel_ventry 1, sync // Synchronous EL1h

kernel_ventry 1, irq // IRQ EL1h

kernel_ventry 1, fiq_invalid // FIQ EL1h

kernel_ventry 1, error_invalid // Error EL1h

kernel_ventry 0, sync // Synchronous 64-bit EL0

kernel_ventry 0, irq // IRQ 64-bit EL0

kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0

kernel_ventry 0, error_invalid // Error 64-bit EL0

#ifdef CONFIG_COMPAT

kernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0

kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0

kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0

kernel_ventry 0, error_invalid_compat, 32 // Error 32-bit EL0

#else

kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0

kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0

kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0

kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0

#endif

END(vectors)

以el1下的異常為例,當(dāng)跳轉(zhuǎn)到el1_sync函數(shù)時(shí),讀取ESR的值以判斷異常類型。根據(jù)類型跳轉(zhuǎn)到不同的處理函數(shù)里,如果是data abort的話跳轉(zhuǎn)到el1_da函數(shù)里,instruction abort的話跳轉(zhuǎn)到el1_ia函數(shù)里:

el1_sync:

kernel_entry 1

mrs x1, esr_el1 // read the syndrome register

lsr x24, x1, #ESR_ELx_EC_SHIFT // exception class

cmp x24, #ESR_ELx_EC_DABT_CUR // data abort in EL1

b.eq el1_da

cmp x24, #ESR_ELx_EC_IABT_CUR // instruction abort in EL1

b.eq el1_ia

cmp x24, #ESR_ELx_EC_SYS64 // configurable trap

b.eq el1_undef

cmp x24, #ESR_ELx_EC_SP_ALIGN // stack alignment exception

b.eq el1_sp_pc

cmp x24, #ESR_ELx_EC_PC_ALIGN // pc alignment exception

b.eq el1_sp_pc

cmp x24, #ESR_ELx_EC_UNKNOWN // unknown exception in EL1

b.eq el1_undef

cmp x24, #ESR_ELx_EC_BREAKPT_CUR // debug exception in EL1

b.ge el1_dbg

b el1_inv

流程圖如下:

e91aebb2-af2a-11eb-bf61-12bb97331649.png

do_page_fault

static int __do_page_fault(struct mm_struct *mm, unsigned long addr,

unsigned int mm_flags, unsigned long vm_flags,

struct task_struct *tsk)

{

struct vm_area_struct *vma;

int fault;

vma = find_vma(mm, addr);

fault = VM_FAULT_BADMAP; //沒(méi)有找到vma區(qū)域,說(shuō)明addr還沒(méi)有在進(jìn)程的地址空間中

if (unlikely(!vma))

goto out;

if (unlikely(vma-》vm_start 》 addr))

goto check_stack;

/*

* Ok, we have a good vm_area for this memory access, so we can handle

* it.

*/

good_area://一個(gè)好的vma

/*

* Check that the permissions on the VMA allow for the fault which

* occurred.

*/

if (?。╲ma-》vm_flags & vm_flags)) {//權(quán)限檢查

fault = VM_FAULT_BADACCESS;

goto out;

}

//重新建立物理頁(yè)面到VMA的映射關(guān)系

return handle_mm_fault(vma, addr & PAGE_MASK, mm_flags);

check_stack:

if (vma-》vm_flags & VM_GROWSDOWN && !expand_stack(vma, addr))

goto good_area;

out:

return fault;

}

從__do_page_fault函數(shù)能看出來(lái),當(dāng)觸發(fā)異常的虛擬地址屬于某個(gè)vma,并且擁有觸發(fā)頁(yè)錯(cuò)誤異常的權(quán)限時(shí),會(huì)調(diào)用到handle_mm_fault函數(shù)來(lái)建立vma和物理地址的映射,而handle_mm_fault函數(shù)的主要邏輯是通過(guò)__handle_mm_fault來(lái)實(shí)現(xiàn)的。

__handle_mm_fault

static int __handle_mm_fault(struct vm_area_struct *vma, unsigned long address,

unsigned int flags)

{

。..。..

//查找頁(yè)全局目錄,獲取地址對(duì)應(yīng)的表項(xiàng)

pgd = pgd_offset(mm, address);

//查找頁(yè)四級(jí)目錄表項(xiàng),沒(méi)有則創(chuàng)建

p4d = p4d_alloc(mm, pgd, address);

if (!p4d)

return VM_FAULT_OOM;

//查找頁(yè)上級(jí)目錄表項(xiàng),沒(méi)有則創(chuàng)建

vmf.pud = pud_alloc(mm, p4d, address);

。..。..

//查找頁(yè)中級(jí)目錄表項(xiàng),沒(méi)有則創(chuàng)建

vmf.pmd = pmd_alloc(mm, vmf.pud, address);

。..。..

//處理pte頁(yè)表

return handle_pte_fault(&vmf);

}

e92c19dc-af2a-11eb-bf61-12bb97331649.png

do_anonymous_page

匿名頁(yè)缺頁(yè)異常,對(duì)于匿名映射,映射完成之后,只是獲得了一塊虛擬內(nèi)存,并沒(méi)有分配物理內(nèi)存,當(dāng)?shù)谝淮卧L問(wèn)的時(shí)候:

如果是讀訪問(wèn),會(huì)將虛擬頁(yè)映射到0頁(yè),以減少不必要的內(nèi)存分配

如果是寫訪問(wèn),用alloc_zeroed_user_highpage_movable分配新的物理頁(yè),并用0填充,然后映射到虛擬頁(yè)上去

如果是先讀后寫訪問(wèn),則會(huì)發(fā)生兩次缺頁(yè)異常:第一次是匿名頁(yè)缺頁(yè)異常的讀的處理(虛擬頁(yè)到0頁(yè)的映射),第二次是寫時(shí)復(fù)制缺頁(yè)異常處理。

從上面的總結(jié)我們知道,第一次訪問(wèn)匿名頁(yè)時(shí)有三種情況,其中第一種和第三種情況都會(huì)涉及到0頁(yè)。

do_fault

e93fd7ba-af2a-11eb-bf61-12bb97331649.png

do_swap_page

上面已經(jīng)講過(guò),pte對(duì)應(yīng)的內(nèi)容不為0(頁(yè)表項(xiàng)存在),但是pte所對(duì)應(yīng)的page不在內(nèi)存中時(shí),表示此時(shí)pte的內(nèi)容所對(duì)應(yīng)的頁(yè)面在swap空間中,缺頁(yè)異常時(shí)會(huì)通過(guò)do_swap_page()函數(shù)來(lái)分配頁(yè)面。

do_swap_page發(fā)生在swap in的時(shí)候,即查找磁盤上的slot,并將數(shù)據(jù)讀回。

換入的過(guò)程如下:

查找swap cache中是否存在所查找的頁(yè)面,如果存在,則根據(jù)swap cache引用的內(nèi)存頁(yè),重新映射并更新頁(yè)表;如果不存在,則分配新的內(nèi)存頁(yè),并添加到swap cache的引用中,更新內(nèi)存頁(yè)內(nèi)容完成后,更新頁(yè)表。

換入操作結(jié)束后,對(duì)應(yīng)swap area的頁(yè)引用減1,當(dāng)減少到0時(shí),代表沒(méi)有任何進(jìn)程引用了該頁(yè),可以進(jìn)行回收。

int do_swap_page(struct vm_fault *vmf)

{

。..。..

//根據(jù)pte找到swap entry, swap entry和pte有一個(gè)對(duì)應(yīng)關(guān)系

entry = pte_to_swp_entry(vmf-》orig_pte);

。..。..

if (!page)

//根據(jù)entry從swap緩存中查找頁(yè), 在swapcache里面尋找entry對(duì)應(yīng)的page

//Lookup a swap entry in the swap cache

page = lookup_swap_cache(entry, vma_readahead ? vma : NULL,

vmf-》address);

//沒(méi)有找到頁(yè)

if (!page) {

if (vma_readahead)

page = do_swap_page_readahead(entry,

GFP_HIGHUSER_MOVABLE, vmf, &swap_ra);

else

//如果swapcache里面找不到就在swap area里面找,分配新的內(nèi)存頁(yè)并從swap area中讀入

page = swapin_readahead(entry,

GFP_HIGHUSER_MOVABLE, vma, vmf-》address);

。..。..

//獲取一個(gè)pte的entry,重新建立映射

vmf-》pte = pte_offset_map_lock(vma-》vm_mm, vmf-》pmd, vmf-》address,

&vmf-》ptl);

。..。..

//anonpage數(shù)加1,匿名頁(yè)從swap空間交換出來(lái),所以加1

//swap page個(gè)數(shù)減1,由page和VMA屬性創(chuàng)建一個(gè)新的pte

inc_mm_counter_fast(vma-》vm_mm, MM_ANONPAGES);

dec_mm_counter_fast(vma-》vm_mm, MM_SWAPENTS);

pte = mk_pte(page, vma-》vm_page_prot);

。..。..

flush_icache_page(vma, page);

if (pte_swp_soft_dirty(vmf-》orig_pte))

pte = pte_mksoft_dirty(pte);

//將新生成的PTE entry添加到硬件頁(yè)表中

set_pte_at(vma-》vm_mm, vmf-》address, vmf-》pte, pte);

vmf-》orig_pte = pte;

//根據(jù)page是否為swapcache

if (page == swapcache) {

//如果是,將swap緩存頁(yè)用作anon頁(yè),添加反向映射rmap中

do_page_add_anon_rmap(page, vma, vmf-》address, exclusive);

mem_cgroup_commit_charge(page, memcg, true, false);

//并添加到active鏈表中

activate_page(page);

//如果不是

} else { /* ksm created a completely new copy */

//使用新頁(yè)面并復(fù)制swap緩存頁(yè),添加反向映射rmap中

page_add_new_anon_rmap(page, vma, vmf-》address, false);

mem_cgroup_commit_charge(page, memcg, false, false);

//并添加到lru鏈表中

lru_cache_add_active_or_unevictable(page, vma);

}

//釋放swap entry

swap_free(entry);

。..。..

if (vmf-》flags & FAULT_FLAG_WRITE) {

//有寫請(qǐng)求則寫時(shí)復(fù)制

ret |= do_wp_page(vmf);

if (ret & VM_FAULT_ERROR)

ret &= VM_FAULT_ERROR;

goto out;

}

。..。..

return ret;

}

e9e15644-af2a-11eb-bf61-12bb97331649.png

do_wp_page

走到這里說(shuō)明頁(yè)面在內(nèi)存中,只是PTE只有讀權(quán)限,而又要寫內(nèi)存的時(shí)候就會(huì)觸發(fā)do_wp_page。

do_wp_page函數(shù)用于處理寫時(shí)復(fù)制(copy on write),其流程比較簡(jiǎn)單,主要是分配新的物理頁(yè),拷貝原來(lái)頁(yè)的內(nèi)容到新頁(yè),然后修改頁(yè)表項(xiàng)內(nèi)容指向新頁(yè)并修改為可寫(vma具備可寫屬性)。

static int do_wp_page(struct vm_fault *vmf)

__releases(vmf-》ptl)

{

struct vm_area_struct *vma = vmf-》vma;

//從頁(yè)表項(xiàng)中得到頁(yè)幀號(hào),再得到頁(yè)描述符,發(fā)生異常時(shí)地址所在的page結(jié)構(gòu)

vmf-》page = vm_normal_page(vma, vmf-》address, vmf-》orig_pte);

if (!vmf-》page) {

//沒(méi)有page結(jié)構(gòu)是使用頁(yè)幀號(hào)的特殊映射

/*

* VM_MIXEDMAP !pfn_valid() case, or VM_SOFTDIRTY clear on a

* VM_PFNMAP VMA.

*

* We should not cow pages in a shared writeable mapping.

* Just mark the pages writable and/or call ops-》pfn_mkwrite.

*/

if ((vma-》vm_flags & (VM_WRITE|VM_SHARED)) ==

(VM_WRITE|VM_SHARED))

//處理共享可寫映射

return wp_pfn_shared(vmf);

pte_unmap_unlock(vmf-》pte, vmf-》ptl);

//處理私有可寫映射

return wp_page_copy(vmf);

}

/*

* Take out anonymous pages first, anonymous shared vmas are

* not dirty accountable.

*/

if (PageAnon(vmf-》page) && !PageKsm(vmf-》page)) {

int total_map_swapcount;

if (!trylock_page(vmf-》page)) {

//添加原來(lái)頁(yè)的引用計(jì)數(shù),方式被釋放

get_page(vmf-》page);

//釋放頁(yè)表鎖

pte_unmap_unlock(vmf-》pte, vmf-》ptl);

lock_page(vmf-》page);

vmf-》pte = pte_offset_map_lock(vma-》vm_mm, vmf-》pmd,

vmf-》address, &vmf-》ptl);

if (!pte_same(*vmf-》pte, vmf-》orig_pte)) {

unlock_page(vmf-》page);

pte_unmap_unlock(vmf-》pte, vmf-》ptl);

put_page(vmf-》page);

return 0;

}

put_page(vmf-》page);

}

//單身匿名頁(yè)面的處理

if (reuse_swap_page(vmf-》page, &total_map_swapcount)) {

if (total_map_swapcount == 1) {

/*

* The page is all ours. Move it to

* our anon_vma so the rmap code will

* not search our parent or siblings.

* Protected against the rmap code by

* the page lock.

*/

page_move_anon_rmap(vmf-》page, vma);

}

unlock_page(vmf-》page);

wp_page_reuse(vmf);

return VM_FAULT_WRITE;

}

unlock_page(vmf-》page);

} else if (unlikely((vma-》vm_flags & (VM_WRITE|VM_SHARED)) ==

(VM_WRITE|VM_SHARED))) {

//共享可寫,不需要復(fù)制物理頁(yè),設(shè)置頁(yè)表權(quán)限即可

return wp_page_shared(vmf);

}

/*

* Ok, we need to copy. Oh, well.。

*/

get_page(vmf-》page);

pte_unmap_unlock(vmf-》pte, vmf-》ptl);

//私有可寫,復(fù)制物理頁(yè),將虛擬頁(yè)映射到物理頁(yè)

return wp_page_copy(vmf);

}

Linux 內(nèi)存管理之CMACMA是reserved的一塊內(nèi)存,用于分配連續(xù)的大塊內(nèi)存。當(dāng)設(shè)備驅(qū)動(dòng)不用時(shí),內(nèi)存管理系統(tǒng)將該區(qū)域用于分配和管理可移動(dòng)類型頁(yè)面;當(dāng)設(shè)備驅(qū)動(dòng)使用時(shí),此時(shí)已經(jīng)分配的頁(yè)面需要進(jìn)行遷移,又用于連續(xù)內(nèi)存分配;其用法與DMA子系統(tǒng)結(jié)合在一起充當(dāng)DMA的后端,具體可參考《沒(méi)有IOMMU的DMA操作》。

CMA區(qū)域 cma_areas 的創(chuàng)建

CMA區(qū)域的創(chuàng)建有兩種方法,一種是通過(guò)dts的reserved memory,另外一種是通過(guò)command line參數(shù)和內(nèi)核配置參數(shù)。

dts方式:

reserved-memory {

/* global autoconfigured region for contiguous allocations */

linux,cma {

compatible = “shared-dma-pool”;

reusable;

size = 《0 0x28000000》;

alloc-ranges = 《0 0xa0000000 0 0x40000000》;

linux,cma-default;

};

};

device tree中可以包含reserved-memory node,系統(tǒng)啟動(dòng)的時(shí)候會(huì)打開(kāi)rmem_cma_setup

RESERVEDMEM_OF_DECLARE(cma, “shared-dma-pool”, rmem_cma_setup);

command line方式:cma=nn[MG]@[start[MG][-end[MG]]]

static int __init early_cma(char *p)

{

pr_debug(“%s(%s)

”, __func__, p);

size_cmdline = memparse(p, &p);

if (*p != ‘@’) {

/*

if base and limit are not assigned,

set limit to high memory bondary to use low memory.

*/

limit_cmdline = __pa(high_memory);

return 0;

}

base_cmdline = memparse(p + 1, &p);

if (*p != ‘-’) {

limit_cmdline = base_cmdline + size_cmdline;

return 0;

}

limit_cmdline = memparse(p + 1, &p);

return 0;

}

early_param(“cma”, early_cma);

系統(tǒng)在啟動(dòng)的過(guò)程中會(huì)把cmdline里的nn, start, end傳給函數(shù)dma_contiguous_reserve,流程如下:

setup_arch---》arm64_memblock_init---》dma_contiguous_reserve-》dma_contiguous_reserve_area-》cma_declare_contiguous

eaecb786-af2a-11eb-bf61-12bb97331649.png

將CMA區(qū)域添加到Buddy System

為了避免這塊reserved的內(nèi)存在不用時(shí)候的浪費(fèi),內(nèi)存管理模塊會(huì)將CMA區(qū)域添加到Buddy System中,用于可移動(dòng)頁(yè)面的分配和管理。CMA區(qū)域是通過(guò)cma_init_reserved_areas接口來(lái)添加到Buddy System中的。

static int __init cma_init_reserved_areas(void)

{

int i;

for (i = 0; i 《 cma_area_count; i++) {

int ret = cma_activate_area(&cma_areas[i]);

if (ret)

return ret;

}

return 0;

}

core_initcall(cma_init_reserved_areas);

其實(shí)現(xiàn)比較簡(jiǎn)單,主要分為兩步:

把該頁(yè)面設(shè)置為MIGRATE_CMA標(biāo)志

通過(guò)__free_pages將頁(yè)面添加到buddy system中

eaf72072-af2a-11eb-bf61-12bb97331649.png

CMA分配

《沒(méi)有IOMMU的DMA操作》里講過(guò),CMA是通過(guò)cma_alloc分配的。cma_alloc-》alloc_contig_range(。.., MIGRATE_CMA,。..),向剛才釋放給buddy system的MIGRATE_CMA類型頁(yè)面,重新“收集”過(guò)來(lái)。

eb245204-af2a-11eb-bf61-12bb97331649.png

用CMA的時(shí)候有一點(diǎn)需要注意:

也就是上圖中黃色部分的判斷。CMA內(nèi)存在分配過(guò)程是一個(gè)比較“重”的操作,可能涉及頁(yè)面遷移、頁(yè)面回收等操作,因此不適合用于atomic context。比如之前遇到過(guò)一個(gè)問(wèn)題,當(dāng)內(nèi)存不足的情況下,向U盤寫數(shù)據(jù)的同時(shí)操作界面會(huì)出現(xiàn)卡頓的現(xiàn)象,這是因?yàn)镃MA在遷移的過(guò)程中需要等待當(dāng)前頁(yè)面中的數(shù)據(jù)回寫到U盤之后,才會(huì)進(jìn)一步的規(guī)整為連續(xù)內(nèi)存供gpu/display使用,從而出現(xiàn)卡頓的現(xiàn)象。

eb7baf90-af2a-11eb-bf61-12bb97331649.png

總結(jié)至此,從CPU開(kāi)始訪問(wèn)內(nèi)存,到物理頁(yè)的劃分,再到內(nèi)核頁(yè)框分配器的實(shí)現(xiàn),以及slab分配器的實(shí)現(xiàn),最后到CMA等連續(xù)內(nèi)存的使用,把Linux內(nèi)存管理的知識(shí)串了起來(lái),算是形成了整個(gè)閉環(huán)。相信如果掌握了本篇內(nèi)容,肯定打開(kāi)了Linux內(nèi)核的大門,有了這個(gè)基石,祝愿大家接下來(lái)的內(nèi)核學(xué)習(xí)越來(lái)越輕松。

原文標(biāo)題:萬(wàn)字整理,肝翻Linux內(nèi)存管理所有知識(shí)點(diǎn)

文章出處:【微信公眾號(hào):strongerHuang】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

責(zé)任編輯:haq

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

    關(guān)注

    87

    文章

    11469

    瀏覽量

    212931
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    3111

    瀏覽量

    75036

原文標(biāo)題:萬(wàn)字整理,肝翻Linux內(nèi)存管理所有知識(shí)點(diǎn)

文章出處:【微信號(hào):strongerHuang,微信公眾號(hào):strongerHuang】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    如何配置和驗(yàn)證Linux內(nèi)核參數(shù)

    Linux系統(tǒng)運(yùn)維和性能優(yōu)化中,內(nèi)核參數(shù)(sysctl)的配置至關(guān)重要。合理的參數(shù)調(diào)整可以顯著提升網(wǎng)絡(luò)性能、系統(tǒng)穩(wěn)定性及資源利用率。然而,僅僅修改參數(shù)是不夠的,如何驗(yàn)證這些參數(shù)是否生效同樣關(guān)鍵。
    的頭像 發(fā)表于 05-29 17:40 ?193次閱讀

    樹(shù)莓派4 性能大比拼:標(biāo)準(zhǔn)Linux與實(shí)時(shí)Linux 4.19內(nèi)核的延遲測(cè)試

    引言本文是對(duì)我之前關(guān)于RaspberryPi3同主題的帖子的更新。與之前的帖子樣,我使用的是隨Raspbian鏡像提供的標(biāo)準(zhǔn)內(nèi)核,以及應(yīng)用了RT補(bǔ)丁的相似內(nèi)核版本。對(duì)于實(shí)時(shí)版,我
    的頭像 發(fā)表于 03-25 09:39 ?277次閱讀
    樹(shù)莓派4 性能大比拼:標(biāo)準(zhǔn)<b class='flag-5'>Linux</b>與實(shí)時(shí)<b class='flag-5'>Linux</b> 4.19<b class='flag-5'>內(nèi)核</b>的延遲測(cè)試

    迅為RK3568開(kāi)發(fā)板OpenHarmony實(shí)操HDF驅(qū)動(dòng)控制LED-添加內(nèi)核編譯

    編譯內(nèi)核時(shí)將該 HDF 驅(qū)動(dòng)編譯到鏡像中,接下來(lái)編寫驅(qū)動(dòng)編譯腳本 Makefile,代碼如下所示: 加入編譯體系,填加模塊目錄到 drivers/hdf_core/adapter/khdf/linux/Makefile 文件 更多內(nèi)容可以關(guān)注:迅為RK3568開(kāi)發(fā)板
    發(fā)表于 01-22 10:35

    騰訊云內(nèi)核團(tuán)隊(duì)修復(fù)Linux關(guān)鍵Bug

    騰訊云操作系統(tǒng)(Tencent OS)內(nèi)核團(tuán)隊(duì)近日在Linux社區(qū)取得了顯著成果。他們提交的兩項(xiàng)改進(jìn)方案,成功解決了自2021年以來(lái)直困擾眾多線廠商,并在近期讓多個(gè)
    的頭像 發(fā)表于 12-31 10:58 ?621次閱讀

    嵌入式學(xué)習(xí)-飛凌嵌入式ElfBoard ELF 1板卡-Linux內(nèi)核移植之內(nèi)核簡(jiǎn)介

    學(xué)到本章節(jié),大家應(yīng)該對(duì)Linux操作系統(tǒng)都有了定的了解,但可能還不知道我們拿到手的內(nèi)核源碼都經(jīng)歷了什么。linux個(gè)龐大的開(kāi)源社區(qū),每
    發(fā)表于 12-16 13:08

    飛凌嵌入式ElfBoard ELF 1板卡-Linux內(nèi)核移植之內(nèi)核簡(jiǎn)介

    學(xué)到本章節(jié),大家應(yīng)該對(duì)Linux操作系統(tǒng)都有了定的了解,但可能還不知道我們拿到手的內(nèi)核源碼都經(jīng)歷了什么。linux個(gè)龐大的開(kāi)源社區(qū),每
    發(fā)表于 12-13 09:03

    deepin社區(qū)亮相第19屆中國(guó)Linux內(nèi)核開(kāi)發(fā)者大會(huì)

    中國(guó) Linux 內(nèi)核開(kāi)發(fā)者大會(huì),作為中國(guó) Linux 內(nèi)核領(lǐng)域最具影響力的峰會(huì)之,直以來(lái)都
    的頭像 發(fā)表于 10-29 16:35 ?860次閱讀

    linux內(nèi)核中通用HID觸摸驅(qū)動(dòng)

    linux內(nèi)核中,為HID觸摸面板實(shí)現(xiàn)了個(gè)通用的驅(qū)動(dòng)程序,位于/drivers/hid/hid-multitouch.c文件中。hid觸摸驅(qū)動(dòng)是以struct hid_driver實(shí)現(xiàn),首先定義
    的頭像 發(fā)表于 10-29 10:55 ?2075次閱讀
    <b class='flag-5'>linux</b><b class='flag-5'>內(nèi)核</b>中通用HID觸摸驅(qū)動(dòng)

    Linux網(wǎng)絡(luò)基礎(chǔ)知識(shí)總結(jié)

    同 CPU、內(nèi)存以及 I/O 樣,網(wǎng)絡(luò)也是 Linux 系統(tǒng)最核心的功能。 網(wǎng)絡(luò)是種把不同計(jì)算機(jī)或網(wǎng)絡(luò)設(shè)備連接到起的技術(shù),它本質(zhì)上是
    的頭像 發(fā)表于 10-28 10:42 ?596次閱讀
    <b class='flag-5'>Linux</b>網(wǎng)絡(luò)基礎(chǔ)<b class='flag-5'>知識(shí)</b><b class='flag-5'>總結(jié)</b>

    詳解linux內(nèi)核的uevent機(jī)制

    linux內(nèi)核中,uevent機(jī)制是內(nèi)核和用戶空間通信的機(jī)制,用于通知用戶空間應(yīng)用程序各種硬件更改或其他事件,比如插入或移除硬件設(shè)備(如USB驅(qū)動(dòng)器或網(wǎng)絡(luò)接口)。uevent表示
    的頭像 發(fā)表于 09-29 17:01 ?1735次閱讀

    linux驅(qū)動(dòng)程序如何加載進(jìn)內(nèi)核

    ,需要了解Linux內(nèi)核的基本概念和API。以下是些關(guān)鍵概念: 1.1 內(nèi)核模塊:Linux內(nèi)核
    的頭像 發(fā)表于 08-30 15:02 ?1003次閱讀

    Linux 驅(qū)動(dòng)開(kāi)發(fā)與應(yīng)用開(kāi)發(fā),知道多少?

    、Linux驅(qū)動(dòng)開(kāi)發(fā)與應(yīng)用開(kāi)發(fā)的區(qū)別開(kāi)發(fā)層次不同:Linux驅(qū)動(dòng)開(kāi)發(fā)主要是針對(duì)硬件設(shè)備進(jìn)行編程,處于操作系統(tǒng)內(nèi)核層,直接與硬件交互,為上層應(yīng)用提供設(shè)備訪問(wèn)的接口。
    的頭像 發(fā)表于 08-30 12:16 ?1484次閱讀
    <b class='flag-5'>Linux</b> 驅(qū)動(dòng)開(kāi)發(fā)與應(yīng)用開(kāi)發(fā),<b class='flag-5'>你</b>知道多少?

    Linux內(nèi)核測(cè)試技術(shù)

    。內(nèi)核測(cè)試技術(shù)是實(shí)現(xiàn)這目標(biāo)的關(guān)鍵手段。本文將詳細(xì)介紹 Linux 內(nèi)核測(cè)試的各種技術(shù),包括單元測(cè)試、集成測(cè)試、功能測(cè)試和性能測(cè)試等,并討論不同測(cè)試方法的優(yōu)缺點(diǎn)及其適用場(chǎng)景。
    的頭像 發(fā)表于 08-13 13:42 ?939次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>測(cè)試技術(shù)

    Linux內(nèi)核中的頁(yè)面分配機(jī)制

    Linux內(nèi)核中是如何分配出頁(yè)面的,如果我們站在CPU的角度去看這個(gè)問(wèn)題,CPU能分配出來(lái)的頁(yè)面是以物理頁(yè)面為單位的。也就是我們計(jì)算機(jī)中常講的分頁(yè)機(jī)制。本文就看下Linux內(nèi)核是如何管
    的頭像 發(fā)表于 08-07 15:51 ?578次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>中的頁(yè)面分配機(jī)制

    歡創(chuàng)播報(bào) 華為宣布鴻蒙內(nèi)核已超越Linux內(nèi)核

    自誕生以來(lái)的最大次升級(jí)換代。HarmonyOS NEXT不依賴傳統(tǒng)的Unix內(nèi)核Linux內(nèi)核,而是依靠自主的鴻蒙內(nèi)核。這就意味著,鴻蒙
    的頭像 發(fā)表于 06-27 11:30 ?1185次閱讀

    電子發(fā)燒友

    中國(guó)電子工程師最喜歡的網(wǎng)站

    • 2931785位工程師會(huì)員交流學(xué)習(xí)
    • 獲取您個(gè)性化的科技前沿技術(shù)信息
    • 參加活動(dòng)獲取豐厚的禮品