核心内容摘要
蜕变新生:成年礼的温柔提醒,和一场关于成长的泪与笑
简介为什么内存会“越跑越碎”瑞芯微 RK35xx/RK3588采用 ARM Cortex-A55/A76 Mali GPU边缘盒子普遍配
GB LPDDR4/4X。
工业场景边缘视觉1080p×4 路 MJPEG 解码 → 用户态频繁malloc/free→ 物理页零散。
实时控制1 ms 控制环中触发缺页 → 延迟飙到 300 μs控制抖动超标。
后果系统连续运行 3 天后8 KB 以上大块分配失败GPU 驱动报-ENOMEM必须重启。
客户审厂失败“不具备长期稳定性”。
掌握“内存优化 碎片控制” 让国产化平台真正胜任 7×24 工业现场。
核心概念5 个关键词先搞懂关键词一句话本文出现场景伙伴系统BuddyLinux 按 2^n 页管理物理内存小对象分裂 → 外部碎片/proc/buddyinfo大页HugePage2 MB/1 GB 连续块减少 TLB Missmmap MAP_HUGETLB内存分区CMA预留连续区域专供 DMA/GPU 使用dtslinux,cma内存泄漏申请后未释放 → 可用内存递减Valgrind/LeakSanitizer延迟分配Lazy Alloc只在首次写时分配物理页vm.overcommit_memory1
环境准备10 分钟搭好“内存实验室”
硬件RK3568 EVB 或 RK3588 边缘盒子 ×1≥4 GB LPDDR4方便观察碎片趋势
软件组件版本说明瑞芯微 SDKRK Linux
10 Gen3含 RT 补丁交叉工具链gcc-arm-
1
3-
2
07aarch64-linux-gnu-gcc串口终端minicom 115200查看内核日志内存测试工具rt-tests、memtesterapt/yocto 安装
一键换 RT 内核可选但推荐# 在 SDK 根目录 ./build.sh kernel rt # 生成 boot.img 后烧写 upgrade_tool di boot boot.img重启确认uname -r #
5.
1
110-rt
应用场景300 字边缘视觉 运动控制混合场景某口罩质检产线采用 RK3568 边缘盒子4 路 1080p 相机 → MJPEG 解码 → YOLOv5 检测缺陷同时通过 EtherCAT 总线控制 6 轴伺服位置环周期 1 ms系统需 30 天无重启运行。
实测发现解码库频繁mmap/munmap1 MB 块 → 2 天后伙伴系统 8 KB 以上连续页耗尽控制任务 malloc 256 KB DMA 缓冲区 → 分配失败进入 retryEtherCAT 帧延迟 400 μs伺服报警 “位置超差”。
通过本文“内存分区 大页 提前映射”方案连续运行 45 天碎片率 1%控制抖动 50 μs一次性通过客户 7×24 稳定性审厂。
实际案例与步骤内存优化“四部曲”所有命令/脚本均在 RK3568 验证可直接复制路径以 SDK 实际为准。
1 伙伴系统现状先拿数据再开刀# 查看当前碎片 cat /proc/buddyinfo # 典型输出RK3568 4 GB Node 0, zone DMA 3 4 3 2 1 1 1 0 0 0 0 Node 0, zone Normal 145 129 100 80 60 40 20 10 5 2 1解读列 2^n 页04 KB18 KB…104 MB数字空闲块计数高阶6数量少 → 大块稀缺。
2 预留 CMA 区给 GPU/V4L2 吃“小灶”目标解码/显示驱动不再跟控制任务抢普通页。
设备树arch/arm64/boot/dts/rockchip/rk3568-evb.dtsreserved-memory { cma: cma { compatible shared-dma-pool; reusable; size 0x0 0x20000000; /* 512 MB */ linux,cma-default; alloc-ranges 0x0 0x40000000 0x0 0x80000000; }; }; rkvdec { memory-region cma; };验证dmesg | grep -i cma # CMA: reserved 512 MiB at 0x
0
3 配置 HugePage用户态 1 MB 块“一次到位”# 启动参数追加 setenv bootargs default_hugepagesz2M hugepagesz2M hugepages128 # 烧写后重启 cat /proc/meminfo | grep Huge # HugePages_Total: 128 256 MB 已预留用户态使用解码库修改 2 行即可void *ptr mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1,
;收益TLB Miss 下降 45%解码线程 CPU 占用 -8%。
4 编写“零碎片”内存池嵌入式常用思路提前mmap一大块 64 MB自己切割永不 free。
/* mempool.h */ #define POOL_SIZE (
/* 64 MB */ #define CHUNK_SHIFT 20 /* 1 MB */ #define CHUNK_SIZE (1CHUNK_SHIFT) void mempool_init(void); void* mempool_alloc(void); void mempool_free(void*); /* 仅回收到空闲链表不归还系统 */实现片段static void *pool_base; static LIST_HEAD(free_list); static pthread_mutex_t pool_lock PTHREAD_MUTEX_INITIALIZER; void mempool_init(void){ pool_base mmap(NULL, POOL_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB, -1,
; for (int i0; i64; i){ chunk_t *c (chunk_t*)(pool_base i*CHUNK_SIZE); list_add(c-node, free_list); } }解码线程改为void *frame mempool_alloc(); decode_into(frame); ... mempool_free(frame);结果运行 30 天/proc/buddyinfo 高阶块零变化。
5 内存泄漏检测开发阶段嵌入轻量级方案适合嵌入式#define LEAK_CHECK #ifdef LEAK_CHECK static atomic_t alloc_cnt 0; #define ALLOC() do { atomic_inc(alloc_cnt); } while(
#define FREE() do { atomic_dec(alloc_cnt); } while(
#else #define ALLOC() do {} while(
#define FREE() do {} while(
#endif启动后 shell 周期性打印while true; do echo alloc_cnt$(cat /sys/module/mydrv/alloc_cnt) sleep 60 done若 cnt 单调增 → 存在泄漏再用addr2line定位。
六、
常见问题与解答FAQ问题现象解决hugepages 申请失败mmap 返回 -1启动参数未加或 CMA 把内存占满减小 CMA 或增加hugepagesCMA 区无法分配dma_alloc_coherent失败dtssize写太大保留至少 1 GB 给 Normal 区伙伴信息高阶仍少预留后无改善检查是否有驱动vmalloc过大改用kvmallocmempool 造成 RSS 暴涨top RES 高正常池提前占用生产环境可接受leak 计数波动负值出现原子操作顺序错确保 ALLOC/FREE 成对
实践建议与最佳实践先量后切任何优化前跑 1 小时buddyinfo采样用 Excel 画趋势确认“高阶块下降”再动手。
分区哲学“控制任务用 Normal多媒体用 CMA大屏用 HugePage”——互不干扰。
锁粒度mempool 自旋锁改为每 CPU 空闲链 → 减少 SMP 竞争。
热升级支持mempool 支持echo extend /sys/mempool/ctl动态扩容 32 MB不重启设备。
Git 记录dts 修改、启动参数、驱动 patch 全部提交到rk3568-memopt分支可追溯。
审厂材料把 30 天buddyinfo曲线、泄漏计数截图放入《长期稳定性报告》客户一次通过。
八、