什么是CMA
CMA是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操作》。
數(shù)據(jù)結(jié)構(gòu)
struct cma {
//CMA區(qū)域物理地址的起始頁(yè)幀號(hào)
unsigned long base_pfn;
//CMA區(qū)域總體的頁(yè)數(shù)
unsigned long count;
//位圖,用于描述頁(yè)的分配情況
unsigned long *bitmap;
//位圖中每個(gè)bit描述的物理頁(yè)面的order值,其中頁(yè)面數(shù)為2^order值
unsigned int order_per_bit; /* Order of pages represented by one bit */
struct mutex lock;
#ifdef CONFIG_CMA_DEBUGFS
struct hlist_head mem_head;
spinlock_t mem_head_lock;
#endif
const char *name;
};
extern struct cma cma_areas[MAX_CMA_AREAS];
extern unsigned cma_area_count;
- bitmap來(lái)管理其內(nèi)存的分配,0表示free,1表示已經(jīng)分配。如果order_per_bit等于0,表示按照一個(gè)一個(gè)page來(lái)分配和釋放,如果order_per_bit等于1,表示按照2個(gè)page組成的block來(lái)分配和釋放,以此類推。count說(shuō)明該cma_areas內(nèi)存有多少個(gè)page。它和order_per_bit一起決定了bitmap指針指向內(nèi)存的大小。base_pfn定義了該cma_areas的起始page frame number,base_pfn和count一起定義了該cma_areas在內(nèi)存中的范圍。
from loyenwang
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)n", __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
將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中
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)。
用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)象。