CMA的全称叫做contiguous memory allocator,它是为了便于进行连续物理内存申请的一块区域,一般我们把这块区域定义为reserved-memory。
早期的Linux内核中没有cma的实现,如果驱动想要申请一个大块的物理连续内存,那么只能通过预留专属内存的形式,然后在驱动中使用ioremap来映射后作为私有内存使用。这样带来的后果就是有一部分内存将被预留出来不能作为系统中的通用内存来使用,比如camera、audio设备,它们在工作时是需要大块连续内存进行DMA操作的,而当这些设备不工作时,预留的内存也无法被其他模块所使用。
如何使得操作系统能够充分的利用物理内存呢?比如当一些设备需要使用大块连续物理内存时,可以比较容易的申请到,而当这些设备不工作时,这些内存又可以当做普通的内存那样被系统其他模块申请使用。引入CMA就是为了解决这个问题的,定义为cma区域的内存,也是由操作系统来管理的,当一个驱动模块想要申请大块连续内存时,通过内存管理子系统把CMA区域的内存进行迁移,空出连续内存给驱动使用;而当驱动模块释放这块连续内存后,它又被归还给操作系统管理,可以给其他申请者分配使用。
我前面的文章有介绍过《对于MIGRATE_MOVABLE的理解》,其中有讲到,buddy system在对内存进行管理时,不同size的内存块是分类管理的,其中有一类就是 MIGRATE_CMA
类型,这种类型的内存必须是可以迁移的,以保证在分配给dma使用时能够申请成功。
enum {
MIGRATE_UNMOVABLE,
MIGRATE_RECLAIMABLE,
MIGRATE_MOVABLE,
MIGRATE_PCPTYPES, /* the number of types on the pcp lists */
MIGRATE_RESERVE = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
/*
* MIGRATE_CMA migration type is designed to mimic the way
* ZONE_MOVABLE works. Only movable pages can be allocated
* from MIGRATE_CMA pageblocks and page allocator never
* implicitly change migration type of MIGRATE_CMA pageblock.
*
* The way to use it is to change migratetype of a range of
* pageblocks to MIGRATE_CMA which can be done by
* __free_pageblock_cma() function. What is important though
* is that a range of pageblocks must be aligned to
* MAX_ORDER_NR_PAGES should biggest page be bigger then
* a single pageblock.
*/
MIGRATE_CMA,
#endif
#ifdef CONFIG_MEMORY_ISOLATION
MIGRATE_ISOLATE, /* can't allocate from here */
#endif
MIGRATE_TYPES
};
按照CMA的使用范围,它也可以分为两种类型,一种是通用的CMA区域,该区域是给整个系统分配使用的,另一种是专用的CMA区域,这种是专门为单个模块定义的,定义它的目的是不太希望和其他模块共享该区域,我们可以在dts中定义不同的CMA区域,每个区域实际上就是一个reserved memory,对于共享的CMA
reserved_memory: reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
/* global autoconfigured region for contiguous allocations */
linux,cma {
compatible = "shared-dma-pool";
alloc-ranges = <0x0 0x00000000 0x0 0xffffffff>;
reusable;
alignment = <0x0 0x400000>;
size = <0x0 0x2000000>;
linux,cma-default;
};
};
对于CMA区域的dts配置来说,有三个关键点:
对于一个专用的CMA,它的配置方式如下:
reserved_memory: reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
priv_mem: priv_region {
compatible = "shared-dma-pool";
alloc-ranges = <0x0 0x00000000 0x0 0xffffffff>;
reusable;
alignment = <0x0 0x400000>;
size = <0x0 0xC00000>;
};
};
先在reserved memory中定义专用的CMA区域,注意这里和上面共享的唯一区别就是在专用CMA区域中是不包含 linux,cma-default;
属性的。那么我们在使用时怎么用呢?参见如下:
qcom,testmodule {
compatible = "qcom,testmodule";
memory-region = <&priv_mem>;
};
在需要使用的模块中定义memory-region属性,并且把对应CMA handler通过dts传递给该模块。这样在模块中可以使用:
struct page *page = NULL;
page = cma_alloc(dev_get_cma_area(dev),mem_size, 0, GFP_KERNEL);
这里利用了dev_get_cma_area可以获取对应的cma handler,如果获取不到,比如对应模块中并未定义memory-region,那么就会返回共享的cma handler,还记的上面的 linux,cma-default;
属性吗,共享cma区域会被作为缺省cma来使用。
当一个内核模块要使用CMA内存时,使用的接口依然是dma的接口:
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的平台上述的两个接口都会最终运行到如下的实现中来:
struct page *dma_alloc_from_contiguous(struct device *dev, size_t count,
unsigned int align, bool no_warn)
{
if (align > CONFIG_CMA_ALIGNMENT)
align = CONFIG_CMA_ALIGNMENT;
return cma_alloc(dev_get_cma_area(dev), count, align, no_warn);
}
bool dma_release_from_contiguous(struct device *dev, struct page *pages,
int count)
{
return cma_release(dev_get_cma_area(dev), pages, count);
}
这里最终使用到的是CMA中的实现来分配和释放内存。
对于dma framwork来说,当我们使能并且配置了CMA区域时会使用CMA进行内存分配,但是内核依然对于旧的实现方式进行了兼容,可以通过 CONFIG_HAVE_GENERIC_DMA_COHERENT
来进行配置。
obj-$(CONFIG_DMA_CMA) += contiguous.o
obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += coherent.o removed.o
对于generic dma coherent的实现方式,依然借用了dts中的reserved memory节点,只不过在其中会定义 no-map
属性,进而这块内存就从系统中剥离出来了,无法被伙伴系统所使用,但是可以在dma核心层通过remap的形式创建页表映射来使用它。
参考文章链接:
https://blog.csdn.net/21cnbao/article/details/7309757