闲话

最近分析一个高清视频播放故障时,遇到了内存分配失败的问题,顺道了解了一下内核中的CMA(连续内存分配器,Contiguous Memory Allocator),这里简单记录一下,内容有限,闲聊。

什么CMA?

CMA,即Contiguous Memory Allocator,联系内存分配器,顾名思义,就是用来分配连续内存的。

为什么需要CMA?

我们知道,Linux内核中采用虚拟内存技术,应用程序可见的只是线性地址(可以理解为虚拟地址,VA),当应用程序分配连续内存时,得到的是连续的虚拟内存,而其物理内存通常是不连续的,物理内存在缺页时通过buddy system分配和管理,通过页表来建立虚拟地址和物理地址之间的关系,由此可以提升内存利用率、内存管理的效率、而且可以实现进程地址空间的隔离,好处多多。

但在这样的环境中,如果企图分配到一段连续的物理内存,基本是不可能的,应用程序做不了主。但是一些硬件(比如GPU、HDMI)或应用又必须要使用连续的物理内存,原因很多,比如出于性能考虑,此时该怎么办呢?

从前,通常的做法是在启动时预留一段内存出来专用(内核中有相应的启动参数控制),预留后,这段内存不再由buddy system管理,而通过专用的接口的分配和释放。 如此做法,最大的问题在于“内存浪费”,预留后意味着这段内存将不能再用作他用,预留得多,浪费越多,预留少则担心不够,通常难以抉择和通用,在内存资源紧张的环境中,这样的问题异常严重。

CMA就是用来解决这个问题的。

CMA基本原理

这里仅简单的从宏观上描述了,不讲代码了,闲聊而已!

跟之前的预留内存的方法类似,CMA也需要“预留内存”,其在内核启动过程中,调用dma_contiguous_reserve接口预留一段连续的内存区域,该区域的大小由内核配置项:CMA_SIZE_MBYTES 控制,默认为64M,分配后的内存page会被设置上MIGRATE_CMA标记,表明这些page是给CMA用的(当然不完全,也可以用作他用,这也是CMA的特点)。在free page过程中相应的page也会被加入到free_list中去。

“预留”出来的内存并非只给需要“连续内存”的应用专用,其可以“用作他用”,其他程序也可以通过buddy system分配到CMA区域中的内存,这样就不会造成“内存浪费”,放着不用确实有点可惜了。

应用程序如果需要使用连续的物理内存,需要使用专用接口,如dma_alloc_from_contiguous,进行申请,过程中,会到之前预留的CMA区域中查找空闲的内存(未被CMA分配出去的),并设置上MIGRATE_ISOLATE标记,如此buddy system就不会再管理这样的page了,然后,会遍历分配到的page range中是否有已经被buddy system分配“另作他用”的内存,如果有,则需要将这些page迁移出去。 所谓“迁移”,本身上,就是为其找一个新的家:分配新的page,然后将原page拷贝过去,然后将原page释放回CMA中,这样CMA就可以重新利用,并且保证内存连续了。

如此而已。

CMA有什么好处?

CMA相对于之前单纯的保留内存的方法,最大的好处就在于,其解决了“内存浪费”的问题,虽然CMA也需要“保留内存”,但其保留的内存是可以用作他用的,其他应用(包括内核)可以使用其保留内存,当真正需要连续内存的应用调用相应接口分配连续内存时,可以将“用作他用”的这部分内存migrate到其他地方,然后使用为自己腾出连续的空间。 这相当于一个动态内存管理器。 其在一定程度上,即满足了“连续内存”分配的需求,又解决了“空间浪费”的问题,看起来perfect。

CMA有何问题?

问题还是比较明显的:性能问题。 CMA涉及的初衷之一原本是为了为客户提供“连续内存”,从而提升性能的,为何这里性能反而成了问题。 问题还是在于CMA为了避免内存浪费做的“内存迁移”操作,当CMA区域中的内存被用作他用后,如果需要使用已被“用作他用”的这些内存,必然需要内存迁移操作,而该操作必然会有性能损耗,在内存紧张时,尤为明显。在我的环境中,播放高清视频时,其能导致CPU占用率冲高,主要的损耗点就在内存迁移上。

这样的问题,很难平衡,不能不说CMA是个好东西,但如之前所说,真的没有一个“皆准”的性能优化手段,性能是跟业务模型、环境息息相关的,根据实际场景,做针对性的“自上而下”(请理解)的剖析和优化,方为“大同”之道。