cma,全称(contiguous memory allocation),在内存初始化时预留一块连续内存,可以在内存碎片化严重时通过调用dma_alloc_contiguous接口并且gfp指定为__GFP_DIRECT_RECLAIM从预留的那块连续内存中分配大块连续内存。
原来 dma_alloc_coherent 在 arm 平台上会禁止页表项中的 C (Cacheable) 域以及 B (Bufferable)域。
而 dma_alloc_writecombine 只禁止 C (Cacheable) 域。
什么是CMA
Linux内核的连续内存分配器 (CMA)——避免预留大块内存。嵌入式Linux系统的时候,GPU,Camera,HDMI等都需要预留大量连续内存,随着内核的运行,内核中的物理内存越来越趋向于碎片化,但是某些特定的设备在使用时用到的 DMA 需要大量的连续物理内存但是一般的做法又必须先预留着。通过这套机制,我们可以做到不预留内存,这些内存平时是可用的,只有当需要的时候才被分配给Camera,HDMI等设备。
在使用LCD显示功能驱动时遇到不断打开和关闭显示设备,出现dma_alloc_writecombine 申请内存失败的情况。理论上程序应该允许申请失败,失败后再重新申请能获取到空间即可。 尽管dma_alloc_writecombine 函数和dma_free_writecombine 成对的调用,实际申请内存空间的物理地址不断的在累加,并不是完全复用释放的内存空间地址。 内存释放相关后台程序周期有一定关系,内存释放并不是实时的。不过程序测试过程中比较奇怪的事,很多分配的空间好像长时间没有释放,导致空间不足感觉不太应该。
通过错误信息上图中2部分信息,可以获知是在arm_dma_alloc 函数获取DMA内存失败导致异常。该函数在内核/Linux/arch/arm/mm/dma-mapping.c。 对于DMA使用内存,在我们LINUX 系统下的DTS是这样定义的:
一个DMA很好的介绍网页
图中dts的linux,cma 定义的保留内存区域是预留给DMA专用的内存区域,配置只设置了size没有像其下面vpu_mem定义使用了reg,系统会使用内存的最高端的对应size大小区域用于做DMA申请大段连续内存。其他关键字的定义和说明如下:
通常访问指定物理地址的内存方式有很多种:
通过实际测试, memblock_reserve保留的内存段,可以采用memremap ,phy_to_vir 方式将物理地址映射出并使用。
由于我们没有开启DMA使用CMA,导致一直在申请NORMAL内存的空间。导致内存碎片化严重,由于内存回收机制效率较低长时间后无法申请到有效的大内存作为显示。使用CMA作为显示内存和DMA使用,可以避免碎片化的问题有效解决了相关问题。
在内核中有CMA的相关配置,可以打开CMA debug 运行程序能获取更多的相关信息。
DMA使用CMA的设置,不开启设置DMA将不能使用CMA内存。在dma_alloc_coherent 调用时使用或上__GFP_HIGH参数可以避免使用Normal 空间。 CMA 内存相关介绍
注意Normal 空间最大申请内存的大小通过这个参数控制。
extern void *
dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle,
gfp_t flag);
extern void
dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,
dma_addr_t dma_handle);
CMA 的一个使用例子链接
/*
* kernel module helper for testing CMA
*
* Licensed under GPLv2 or later.
*/
#include
#include
#include
#include
#include
#define CMA_NUM 10
static struct device *cma_dev;
static dma_addr_t dma_phys[CMA_NUM];
static void *dma_virt[CMA_NUM];
/* any read request will free coherent memory, eg.
* cat /dev/cma_test
*/
static ssize_t
cma_test_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
int i;
for (i = 0; i < CMA_NUM; i++) {
if (dma_virt[i]) {
dma_free_coherent(cma_dev, (i + 1) * SZ_1M, dma_virt[i], dma_phys[i]);
_dev_info(cma_dev, "free virt: %p phys: %p\n", dma_virt[i], (void *)dma_phys[i]);
dma_virt[i] = NULL;
break;
}
}
return 0;
}
/*
* any write request will alloc coherent memory, eg.
* echo 0 > /dev/cma_test
*/
static ssize_t
cma_test_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
int i;
int ret;
for (i = 0; i < CMA_NUM; i++) {
if (!dma_virt[i]) {
dma_virt[i] = dma_alloc_coherent(cma_dev, (i + 1) * SZ_1M, &dma_phys[i], GFP_KERNEL);
if (dma_virt[i]) {
void *p;
/* touch every page in the allocated memory */
for (p = dma_virt[i]; p < dma_virt[i] + (i + 1) * SZ_1M; p += PAGE_SIZE)
*(u32 *)p = 0;
_dev_info(cma_dev, "alloc virt: %p phys: %p\n", dma_virt[i], (void *)dma_phys[i]);
} else {
dev_err(cma_dev, "no mem in CMA area\n");
ret = -ENOMEM;
}
break;
}
}
return count;
}
static const struct file_operations cma_test_fops = {
.owner = THIS_MODULE,
.read = cma_test_read,
.write = cma_test_write,
};
static struct miscdevice cma_test_misc = {
.name = "cma_test",
.fops = &cma_test_fops,
};
static int __init cma_test_init(void)
{
int ret = 0;
ret = misc_register(&cma_test_misc);
if (unlikely(ret)) {
pr_err("failed to register cma test misc device!\n");
return ret;
}
cma_dev = cma_test_misc.this_device;
cma_dev->coherent_dma_mask = ~0;
_dev_info(cma_dev, "registered.\n");
return ret;
}
module_init(cma_test_init);
static void __exit cma_test_exit(void)
{
misc_deregister(&cma_test_misc);
}
module_exit(cma_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Barry Song <[email protected]>");
MODULE_DESCRIPTION("kernel module to help the test of CMA");
MODULE_ALIAS("CMA test");
# echo 0 > /dev/cma_test
# cat /dev/cma_test