什么是CMA
CMA是reserved的一塊內(nèi)存,用于分配連續(xù)的大塊內(nèi)存。當(dāng)設(shè)備驅(qū)動不用時,內(nèi)存管理系統(tǒng)將該區(qū)域用于分配和管理可移動類型頁面;當(dāng)設(shè)備驅(qū)動使用時,此時已經(jīng)分配的頁面需要進行遷移,又用于連續(xù)內(nèi)存分配;其用法與DMA子系統(tǒng)結(jié)合在一起充當(dāng)DMA的后端,具體可參考《沒有IOMMU的DMA操作》。
數(shù)據(jù)結(jié)構(gòu)
struct cma {
//CMA區(qū)域物理地址的起始頁幀號
unsigned long base_pfn;
//CMA區(qū)域總體的頁數(shù)
unsigned long count;
//位圖,用于描述頁的分配情況
unsigned long *bitmap;
//位圖中每個bit描述的物理頁面的order值,其中頁面數(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來管理其內(nèi)存的分配,0表示free,1表示已經(jīng)分配。如果order_per_bit等于0,表示按照一個一個page來分配和釋放,如果order_per_bit等于1,表示按照2個page組成的block來分配和釋放,以此類推。count說明該cma_areas內(nèi)存有多少個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)建有兩種方法,一種是通過dts的reserved memory,另外一種是通過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)啟動的時候會打開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)在啟動的過程中會把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)存在不用時候的浪費,內(nèi)存管理模塊會將CMA區(qū)域添加到Buddy System中,用于可移動頁面的分配和管理。CMA區(qū)域是通過cma_init_reserved_areas接口來添加到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);
其實現(xiàn)比較簡單,主要分為兩步:
- 把該頁面設(shè)置為MIGRATE_CMA標(biāo)志通過__free_pages將頁面添加到buddy system中
CMA分配
《沒有IOMMU的DMA操作》里講過,CMA是通過cma_alloc分配的。cma_alloc->alloc_contig_range(..., MIGRATE_CMA,...),向剛才釋放給buddy system的MIGRATE_CMA類型頁面,重新“收集”過來。
用CMA的時候有一點需要注意:
也就是上圖中黃色部分的判斷。CMA內(nèi)存在分配過程是一個比較“重”的操作,可能涉及頁面遷移、頁面回收等操作,因此不適合用于atomic context。比如之前遇到過一個問題,當(dāng)內(nèi)存不足的情況下,向U盤寫數(shù)據(jù)的同時操作界面會出現(xiàn)卡頓的現(xiàn)象,這是因為CMA在遷移的過程中需要等待當(dāng)前頁面中的數(shù)據(jù)回寫到U盤之后,才會進一步的規(guī)整為連續(xù)內(nèi)存供gpu/display使用,從而出現(xiàn)卡頓的現(xiàn)象。