jiejie的mqtt的日志打印用的是异步的任务打印,会使用CPU资源和信号量/mutex的资源,还专门单开了个任务,对于kawaii的并不频繁的日志打印,我感觉大可不必,如果有需求就用jlink的rtt打印好了,速度很快比串口好用很多

在资源极其受限的单片机(如 STM32F103xx 系列,只有 48~64KB SRAM)上运行 FreeRTOS + MQTT 客户端时,你是否经常遇到过玄学的设备崩溃、看门狗复位或者跑飞报 OOM(内存溢出)异常

这大多是因为动态堆(Heap)和任务栈(Stack)被过度消耗,乃至产生了严重碎片化所导致的。

近期在排查一款开源且广泛使用的 Kawaii-MQTT 客户端源码时,我发现了一个隐藏的“内存吞金兽”。只要注释掉其配置头文件中的区区一行宏定义代码,你就能为你的单片机夺回将近 10KB 的物理内存

这到底是一行什么样的代码?它的背后吃干抹净了你多少内存?本文带你硬核扒空源码算一算账。

罪魁祸首:“异步日志系统”宏定义

打开 Kawaii-MQTT 配置头文件 

mqtt_config.h,映入眼帘的往往是下面这行默认被开启的宏定义:

#define MQTT_LOG_IS_SALOF

这就是开启内置 SALOF(Synchronous / Asynchronous Log Output Framework)异步日志框架的开关。大家都觉得开启日志能方便用串口排查问题,所以默认不去管它。

但如果你深挖一下 salof 这个框架底层的实现,你会惊出一身冷汗。在单片机这种寸土寸金的地方,它的奢华配置简直让人无法承受。

详细算盘:这行注释到底能为你省下多少内存?

我们从底层的 全局静态区(BSS) / 动态堆(Heap) / 任务栈(Stack) 三个维度来算总账。

1. 瞬间抹平 4.1 KB 动态堆(Heap)开销 - 环形缓冲区

SALOF 是个标准的异步日志:当你在业务中调用 MQTT_LOG_E 打印内容时,它并不会阻塞住当前进程去操作 UART 发送,而是先把你要发的数据塞进一个极大的内存队列里交由专属任务后台处理。 这必定需要缓冲区。看看配置:

#define SALOF_FIFO_SIZE 4096

也就是说,在客户端初始化时,系统会在 salof_init() 里,强

malloc() 从 FreeRTOS 仅有那么三十几 KB 的公共可用堆中,**直接挖走整整连成一片的 4096 字节(4KB)**去作为它专用的异步 FIFO 环形缓存。

2. 还魂 4 KB 任务分配堆 - 专职后台输出任务

有了缓冲区,就必定要起一个对应的后台任务去不断监控 FIFO 队列,并将数据慢吞吞地吐到串口上。

#define SALOF_TASK_STACK_SIZE 1024

我们知道,在 FreeRTOS 下 xTaskCreate 创建任务时分配的单位常常是 Word(4 字节)。 **1024 个字,这意味着为了打日志,它向系统请求创建了一个独享 4KB (4096 字节) 深度执行栈的超级大任务!**这还不算 FreeRTOS 分配给它的外围 TCB(任务控制块)的内存。这 4KB 同样是“一去不复返”地被霸占掉。

3. 释放 1 KB 静态全局变量区 (BSS 段) - 内部格式化数组

除开堆和栈分配的庞然大物,如果你点进 salof.c 源码内部,你会发现还有以下分配:

static char _out_buff[SALOF_BUFF_SIZE];

static char _salof_format_buff[SALOF_BUFF_SIZE];

根据源码 #define SALOF_BUFF_SIZE 512 的设定,这会在代码编译阶段雷打不动地占用全局静态 RAM (BSS/DATA 段) 总共 1024 字节

总结:注释 1 行,换回 9.2 KB 无价之宝 💎

只要简单地把那一行的宏定义注释掉:

//#define MQTT_LOG_IS_SALOF

系统将不再编译并启动这套庞大的异步日志框架,你将总计夺回:

  • 约 4.1 KB 的动态申请大块内存 (Heap)
  • 约 4.1 KB 的独立打印任务专属执行栈 (Stack)
  • 约 1.0 KB 的静态格式化字符数组 (BSS)

总计释放了至少 9.2 KB 的物理内存(SRAM)!

优化建议与后话

对于总运存才 64KB,分给 FreeRTOS Heap_4 的堆空间可能仅有区区三十多 KB 的 STM32F103 系统而言,高达 9.2KB 的释放是什么概念? 这意味着之前动不动跑到业务处理深处,或者遇到网络堵塞、MQTT 底层组装 QOS1 的重传封包链表时报分配失败而崩溃宕机的情况,绝大部分都将被直接化解。你的设备会瞬间获得极其健康的生命力。

如果我想简单打印调试信息该怎么做? 直接重定向 printf 函数或者自己封装一层极简的同步串口发送就足够了。真的没必要为了看几行 debug 的字眼,在 64KB RAM 级别的单片机中去跑一个带有 4KB FIFO 管道且独立挂着大栈重型任务的“航空母舰级”异步日志轮子。

希望大家在资源受限的环境下进行移植或代码复用时,不仅要看能不能跑起来,还要警惕这种由第三方开源库默认带来的“重量级奢靡配置”。


欢迎在评论区分享你在开发单片机时遇到的玄学死机与内存爆炸奇葩瞬间。喜欢本文的话不要忘记点个赞关注一波!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐