
1. 当DMA遇上大块内存从CMA到保留内存的技术演进之路在嵌入式Linux开发中给FPGA这样的硬件设备分配大块连续DMA内存是个经典难题。我最近就遇到一个真实案例需要为FPGA分配600MB非缓存(non-cached)的连续内存区域。这个看似简单的需求却让我把内核内存管理机制翻了个底朝天。最初尝试用dma_alloc_coherent接口时系统直接拒绝了200MB的申请。排查发现默认CMA(Contiguous Memory Allocator)区域只有128MB而其他驱动已经占用了一部分。这就像去银行取钱却发现账户限额太低——解决方法很简单要么修改内核配置CONFIG_CMA_SIZE_MBYTES384要么通过启动参数cma384M临时调整。但当需求飙升到600MB时问题开始变得棘手。把CMA区域设为700MB后内核启动直接报错failed to reserve 512 MiB。这就好比要租个能容纳500人的会议室却发现整栋楼的承重墙把空间分割得支离破碎。2. CMA机制的先天限制与突破尝试2.1 CMA的工作原理与内存碎片化CMA的设计初衷是解决嵌入式环境中的连续内存分配问题。它通过在启动时预留一块连续物理内存供DMA设备专用。但就像城市规划中的预留地要受到多重限制物理连续性要求DMA设备通常需要物理连续的内存块地址空间限制32位系统内核空间通常只有1GB(3G/1G划分时)内存布局冲突DTB(设备树)、initrd等固定加载地址可能割裂连续空间在我的案例中通过dmesg发现DTB被加载到0x20000000(512MB处)就像在内存中间插了根钉子。修改uboot的fdt_high参数调整DTB位置后700MB的CMA区域终于可以保留。2.2 内核地址空间的隐形天花板即使解决了物理连续性问题又遇到新障碍——内核虚拟地址空间不足。在3G/1G的内存划分下内核只有1GB虚拟地址空间还要扣除以下部分# 典型32位Linux内存布局 Virtual kernel memory layout: vector : 0xffff0000 - 0xffff1000 fixmap : 0xfff00000 - 0xfffe0000 vmalloc : 0xc8800000 - 0xff000000 lowmem : 0xc0000000 - 0xc8800000特别是vmalloc区域默认240MB内核3.3版本就像办公室里的公共走廊占用了太多工位面积。通过关闭CONFIG_HIGHMEM和调整内存划分参数才勉强挤出了足够空间。3. 保留内存方案当CMA不够用时3.1 保留内存ioremap的实战操作当CMA无法满足需求时保留内存(reserved memory)是最后的救命稻草。具体操作分三步走uboot阶段保留内存通过mem256M参数告诉内核只使用部分内存剩下的留给FPGA驱动中映射内存使用ioremap_nocache将保留的物理地址映射到内核空间配置DMA参数设置正确的总线地址和缓存属性// 典型保留内存映射代码示例 #define FPGA_MEM_SIZE (600 * 1024 * 1024) #define FPGA_MEM_PHYS 0x20000000 void *fpga_mem_virt; fpga_mem_virt ioremap_nocache(FPGA_MEM_PHYS, FPGA_MEM_SIZE);但这个方法也有坑——在我的案例中内核报错address not valid for ioremap。就像拿着大额支票去取现却发现银行当日现金额度不足。3.2 地址空间扩展技巧解决ioremap失败的关键在于调整内核地址空间布局。将传统的3G/1G划分改为1G/3G后内核可用地址空间立即扩大三倍# 修改内核启动参数 mem1G memmap600M$400M vmalloc2000M这个操作相当于给内核办公室换了更大的楼层。通过/proc/iomem可以验证保留区域是否生效# 查看内存保留区域 cat /proc/iomem | grep -A 5 reserved4. 综合调优指南与避坑经验4.1 方案选型决策树根据多年踩坑经验我总结出大块DMA内存分配的决策流程需求评估100MB直接使用CMA100-500MB尝试扩展CMA区域500MB考虑保留内存方案系统检查清单物理内存总量是否足够内核地址空间剩余多少是否有DTB/initrd等占用关键地址是否需要关闭HIGHMEM4.2 关键参数调优表参数典型值作用风险提示CONFIG_CMA_SIZE_MBYTES128-512MB设置CMA区域大小过大可能导致启动失败vmalloc240M-2G调整vmalloc区域大小影响其他内核功能mem系统内存总量保留部分内存需配合memmap使用memmap如600M$400M精确控制内存保留区域格式必须严格正确4.3 性能优化注意事项使用保留内存方案时要特别注意缓存一致性必须使用nocache映射否则可能导致FPGA写入数据不可见TLB抖动大块映射会影响TLB效率必要时可分块处理DMA寻址限制某些设备有DMA地址宽度限制需检查总线地址5. 从内核版本差异看内存管理演进在调试过程中发现内核3.3版本将默认vmalloc大小从128MB提升到240MB。这个变化源于社区提交commit a5ade8341c29d8f6d65f8099a829c1b693d7a9d7 Author: Russell King rmkkernelarm.linux.org.uk Date: Wed Dec 14 18:20:42 2011 0000 ARM: 7182/1: ARM cpu topology: fix warning ... To accommodate all static mappings on machines with possible highmem usage, the default vmalloc area size is changed to 240 MB so that VMALLOC_START is no higher than 0xf0000000 by default.这个改动对嵌入式设备影响显著——就像突然把办公室的公共区域扩大一倍虽然方便了集体活动但挤占了工位空间。理解这类历史变更对调试内存问题至关重要。在实际项目中我最终采用了保留内存1G/3G地址划分的方案。这个选择基于以下考量系统总内存1GB600MB占比过高FPGA需要长期占用大块内存避免CMA区域过大影响系统稳定性调试过程中最深的体会是Linux内存管理就像个精密钟表每个齿轮的转动都会影响整体运行。理解/proc/meminfo、/proc/iomem和/proc/vmallocinfo等关键信息才能准确诊断问题所在。