固件的Flash和RAM的分析方法

固件Flash与静态RAM占用分析方法总结(沁恒 .map 分析法)

适用场景:沁恒 CH585(MounRiver 工程,LiteOS-M + IOTC + BLE)固件的 FLASH / 静态 RAM 占用分析。


一、分析思路总览

沁恒 MounRiver 工程编译链接
        │  (链接器输出所有输入段的最终地址、大小、归属)
        ▼
.map 文件(obj\CH585_LiteOS_m.map)
        │  (analyze_map.py 自动解析、归类、几何法计算)
        ▼
FLASH 总占用 + 静态 RAM 占用 + 按模块分类明细 + 校验

.map 作为数据源:它是链接器的最终产出,真实反映了每个目标文件(.o)/库(.a)的每个输入段被放到了哪个地址、占了多少字节,是 ROM/RAM 占用的权威依据,不受打印格式或估算误差影响。


二、操作步骤

第 1 步:编译生成 .map

用沁恒 MounRiver 打开工程编译,链接后在 obj\ 目录生成 .map 文件(本例为 CH585_LiteOS_m.map)。

第 2 步:用 analyze_map.py 解析
将analyze_map.py、CH585_LiteOS_m.map这两个文件放在同一目录下,并在当前目录打开控制台执行如下指令

python analyze_map.py CH585_LiteOS_m.map
  • 参数为 .map 文件路径;不传参则自动检测当前目录最新的 .map
  • 结果打印到控制台,同时在当前工作目录生成带时间戳的报告 mem_report_YYYYMMDD_HHMMSS.txt

工具:
analyze_map.py


三、输出报告结构

脚本输出一份 mem_report_*.txt,共五部分:

部分内容口径
一输出段(Section)总览每个顶层段的大小、是否占 F/R、含多少 fill
二RAM 按模块分类 + RAM 物理布局分类表是信息分解;物理布局是权威值
三FLASH 按模块分类模块累加 + fill = 合计,给出剩余量
四关键库明细libCH58xBLE / libiotc / libmbedtls / LiteOS 的分段构成(F/R/B 标记)
五校验段头合计 vs 模块累加的差额

固件的动态RAM的分析方法总结

一、原理

LiteOS-M 的动态内存是一个静态定义的堆数组:

// Kernel\kernel_liteos_m\kernel\src\mm\los_memory.c
STATIC UINT8 g_memStart[LOSCFG_SYS_HEAP_SIZE];
  • 该数组落在 .bss 段,大小由 target_config.h 中的 LOSCFG_SYS_HEAP_SIZE 决定;
  • 所有任务栈、队列、信号量、定时器、以及组件运行时的 malloc 类分配,都从这个堆里出;
  • 堆调小 → .bss 变小 → 静态 RAM 占用下降;但若小于真实需求,任务创建/内存分配失败,系统跑不起来。

LOSCFG_SYS_HEAP_SIZE 存在一个临界值:

堆 > 临界值  → 系统正常运行
堆 < 临界值  → 分配失败,系统异常/跑飞
临界值      ≈ 当前固件实际需要的动态内存总量

因此,逐步调小堆,用"系统是否还能正常运行"作为探针,找到的最小可用值就是动态内存需求。

二、如何把"组件"的消耗单独剥离

IOTC 组件的入口在 main.c 中被显式调用:

// Src\APP\main.c  (main/任务入口内)
extern void IotcOhDemoEntry(void);
IotcOhDemoEntry();

通过注释 / 放开这两行做对照实验,即可把消耗拆分为:

状态A(注释掉 IotcOhDemoEntry):系统堆 = 内核(+BLE) 基线消耗
状态B(放开 IotcOhDemoEntry)  :系统堆 = 内核(+BLE) + IOTC 组件消耗

→ IOTC 组件动态内存 = 临界值B − 临界值A

三、操作步骤

第 1 步:测内核(+BLE)基线临界值

  1. 注释 Src\APP\main.c 中两行,不启动 IOTC 组件:
// extern void IotcOhDemoEntry(void);
// IotcOhDemoEntry();
  1. 打开 Kernel\kernel_liteos_m\target_config.h,修改堆大小:
//#define LOSCFG_SYS_HEAP_SIZE    0x02800UL
#define LOSCFG_SYS_HEAP_SIZE        (XXXXX)   // 逐步调小
  1. 每改一次 → 重新编译烧录 → 观察系统是否正常运行;
  2. 用二分法逼近:能正常跑的最小值,即临界值A(内核基线)。

第 2 步:测 内核(+BLE)+IOTC 临界值

  1. 恢复 main.c 中两行(放开 IotcOhDemoEntry() 调用);
  2. 同样逐步调小 LOSCFG_SYS_HEAP_SIZE,逼近系统还能正常完成 IOTC 连接/业务的最小值;
  3. 即 临界值B(内核+IOTC)。

第 3 步:计算

内核(+BLE) 动态内存  ≈ 临界值A
IOTC 组件动态内存    ≈ 临界值B − 临界值A
固件总动态内存       ≈ 临界值B

四、"临界"的判定标准

堆不足时典型现象(任一出现即说明低于临界值):

  • 启动打印 LiteOS heap memory address:..., size:0x... 后系统卡死/跑飞;
  • 任务创建失败(LOS_TaskCreate 返回错误);
  • 运行中业务异常、 HardFault;
  • IOTC 无法完成连接或中途异常断开。

建议每档值至少跑完整业务流程并稳定运行数分钟再判定"能跑",避免临界值定得偏小留下隐患。

5、辅助验证(可选)

可在工程内添加内存打印信息代码,在沁恒 Src\APP\main.c 的工程文件下加入如下代码:

#include "los_memory.h"
#include "cmsis_os2.h"


void MemInfoTaskFunc(void)
{
    LOS_MEM_POOL_STATUS poolStatus = {0};

    /* pool为要统计信息的内存地址,此处以OS_SYS_MEM_ADDR为例 */
    void *pool = OS_SYS_MEM_ADDR;
    LOS_MemInfoGet(pool, &poolStatus);
    /* 算出内存池当前的碎片率百分比 */
    // float fragment = 100 - poolStatus.maxFreeNodeSize * 100.0 / poolStatus.totalFreeSize;
    /* 算出内存池当前的使用率百分比 */
    // float usage = LOS_MemTotalUsedGet(pool) * 100.0 / LOS_MemPoolSizeGet(pool);
    // printf("usage = %f, fragment = %f, maxFreeSize = %d, totalFreeSize = %d, waterLine = %d\n", usage, fragment, 
    //         poolStatus.maxFreeNodeSize, poolStatus.totalFreeSize, poolStatus.usageWaterLine);
            
    PRINTK("totalUsedSize = %d, maxFreeSize = %d, totalFreeSize = %d, waterLine = %d\n", 
            poolStatus.totalUsedSize, poolStatus.maxFreeNodeSize, poolStatus.totalFreeSize, poolStatus.usageWaterLine);
}

static void vXtsTask1(void *argument)
{
    PRINTK("vXtsTask1 enter\r\n");
    // osDelay(600 * osKernelGetTickFreq() );
    for (;;)
    {
        osDelay(1000 * osKernelGetTickFreq() / 1000);
        PRINTK("vXtsTask1\r\n");
        MemInfoTaskFunc();
    }
}

#define TEST_TASK_PRIO  29

static void memoryTask(void)
{
    osThreadAttr_t attrTemp = {0};
    attrTemp.name = "vXtsTask1";
    attrTemp.stack_size = 0x400;
    attrTemp.priority = TEST_TASK_PRIO;
    // Create semaphore
    PRINTK("vStartXtsTask !\r\n");
    // Create ble Task
    osThreadNew(vXtsTask1, NULL, &attrTemp);
}


LITE_OS_SEC_TEXT_INIT int main(void){
  ...
  memoryTask();
  ...
}

在Kernel\kernel_liteos_m\arch\WCH\Qingke_V3C\gcc\riscv_bits.h中修改如下配置:

#define REGBYTES                16

添加内存打印信息后可在运行时打印堆的总量/已用/空闲/水位。可与临界值法交叉验证:

  • 临界值法 → 得到"系统还能跑"的最小堆(下界);
  • 运行时水位 → 看到实际峰值使用量(更精确,且能定位是哪个阶段涨的内存)。

两者互相印证,临界值应 ≥ 运行时水位。


Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐