副标题:泄漏、碎片、栈溢出不是同一种死机;重连路径最容易漏缓冲
专栏:《嵌入式学生的方案测试课》第 13 篇(稳定性)
前置:第 2 篇堆栈;第 3 篇任务还活着;第 10 篇重连
标签:嵌入式、内存泄漏、RTOS、软件测试、拷机
读者:功能演示过,没看过剩余堆,拷机只会看灯还亮
学完交什么:故意泄漏的堆曲线、正确 free 的对照曲线、2 小时拷机表、一张「不是泄漏」对照
主实验:PC 上 C 程序就能画「只降不回」。有 Hi3861 则板上打印剩余堆。ESP8266 模组侧不一定能打堆,打主控。
0. 这篇结束你能干什么
- 能区分:泄漏、碎片、栈溢出、正常常驻缓冲。
- 会周期性打印剩余堆,画出「只降不回」和「降了会回」两条线。
- 能设计 2 小时拷机:每 10 分钟一行,看死机、看门狗、堆、任务、重连次数、误控、采样中断。正文告诉你 12 小时怎么排班,不必今晚扛到天亮。
- 能读懂公开 ReleaseNote 为什么要把 mjpeg 改成按节点申请:那是碎片,不是「内存变小一点」。
1. 为什么灯还亮也可以不及格
客户验收隔夜。第二天板子死了,或还能 ping 但一录像就复位。你昨天离开时灯是亮的。
泄漏的典型形状:功能仍在,剩余堆单边下降,第 N 小时 malloc 失败,复位或静默停发。重连路径最爱漏:每次断网申请一条缓冲,成功路径 free 了,失败路径忘了。
所以拷机不是「开着过夜」,是带观察点的过夜。
2. 最小原理
2.1 四张图,不要混
| 名字 | 形状 | 典型原因 | 现场 |
|---|---|---|---|
| 泄漏 | 剩余堆随时间或随次数单调下降,不回来 | malloc/free 不成对;失败路径漏 free | 越跑越少,最后申请失败 |
| 碎片 | 总空闲看起来够,但没有连续大块 | 反复申请不同大小;一次性要大块 | 某一功能(720P、mjpeg)突然失败 |
| 栈溢出 | 往往很快死,不一定越跑越慢 | 大数组在栈上、递归、中断嵌套 | 死机点固定,和运行时长弱相关 |
| 常驻 | 启动后下降一截然后平 | 缓冲池、协议栈、图片帧 | 不是缺陷 |
第 2 篇堆栈 5 点:栈自动、堆手动。拷机看的是堆的时间序列,以及任务还在不在(第 3 篇)。
2.2 失败路径比成功路径更漏
伪代码:
buf = malloc(n); if (connect() != 0) return; // 漏 free send(buf); free(buf);测泄漏要专打失败:错密钥、断网、超时。只测 happy path 会得出「无泄漏」的假阴性。
2.3 看门狗、死机、任务卡住
拷机观察列:
- 整板复位(启动日志再次出现)
- 看门狗(日志有 WDT)
- 剩余堆数字
- 任务:采集是否还按周期;AT 是否还响应(第 3 篇阻塞)
- 重连次数(计数器,不要凭感觉)
- 误控:灯自己亮、阀自己动
- 采样中断:时间戳间隔从 1s 变成 5s
灯亮且 ping 通,但采样停了,仍是失败。
2.4 公开缺陷语言(对齐 ReleaseNote)
FPV ReleaseNote 公开修过:
- mjpeg 一次性申请大空间导致碎片,改成按节点申请
- h264 图像过大空间不足死机
- lcd line_buf 申请大小错误死机
- 录风者 sd 格式化概率死机
测试该写:「改分辨率/开辅码流后剩余堆与死机率」,不是「我去改 ISP」。
3. 手把手实验
3.1 PC 上故意泄漏(必做)
/* leak_demo.c 教学用,不要拷进产品 */#include<stdio.h>#include<stdlib.h>#include<string.h>#ifdef_WIN32#include<windows.h>#definesleep_ms(x)Sleep(x)#else#include<unistd.h>#definesleep_ms(x)usleep((x)*1000)#endifintmain(void){inti;for(i=1;i<=20;i++){void*p=malloc(64*1024);(void)p;/* 故意不 free */printf("i=%d alloc 64k (leaked)\n",i);fflush(stdout);sleep_ms(200);}return0;}再写free_demo.c:同样循环,free(p)。
Windows 任务管理器看进程工作集,或用简单计数:泄漏版 20 次后占用明显高。更干净:自己维护outstanding计数。作业要的是曲线意识:泄漏版单调升(占用)或板上单调降(剩余堆)。
有 POSIXmallinfo或 MCUxPortGetFreeHeapSize就打印剩余。没有就用计数器模拟:
remaining = remaining - 64; // 泄漏 remaining = remaining; // 正确 free 后回到基线附近把 20 个点画进labs/week13/heap-leak.csv和heap-free.csv。
3.2 碎片直觉(不必上板)
讲清楚即可:总空闲 40KB,要 32KB 连续块,中间碎成 20×2KB,失败。ReleaseNote 的 mjpeg「一个一个节点申请」就是避免一次性连续大块。作业画一张示意图,不要求在 MCU 上复现碎片。
3.3 断网重连 100 次专看堆(能做多少写多少)
有 Hi3861:循环断热点 / 关 Wi-Fi,每次打印剩余堆和重连结果。目标 100 次;USB 不稳就 20 次,报告写次数。
期望:成功和失败两种路径下,剩余堆回到同一平台,而不是每次 -128 字节。
ESP8266 模组:模组内部堆你看不见。测主控:每次 AT 会话你是否泄漏了行缓冲。
3.4 2 小时拷机(必做排班)
不必 12 小时一次做完。2 小时是本周及格。12 小时用排班:晚上 22:00 开,早上 10:00 收,中间睡眠,但日志要自己打:每 10 分钟一行,用 RTC 或电脑侧脚本(第 14 篇)。没有脚本就闹钟起来抽查四次,诚实写抽查。
表头:
时间 剩余堆 AT探活 RSSI IP MQTT/HTTP 重连累计 复位累计 备注注入可选:中途关 2 分钟热点,看重连后堆是否回去。
失败判据(示例,产品有规格则用规格):
- 复位 ≥1
- 剩余堆相对起始下降 >30% 且不回
- 连续 3 个采样周期中断
- 误控
灯亮不是通过准则。
3.5 12 小时怎么排班(教方法,本周不强制做满)
21:50 环境表、起始堆、版本、天线照片 22:00 开始;确认业务基线 22:10 第一行表 过夜 电脑不休眠;串口日志重定向到文件;电源不要省电 次日 打开日志看有无重启横幅、堆是否单调 08:00 人工确认仍活着 10:00 结束,贴曲线宿舍断电、电脑休眠会毁掉实验。毁掉就重来,不要把空洞日志写成通过。
4. 缺陷单范文
标题:【内存】12 小时拷机剩余堆 40KB→6KB,功能尚在,第 13 小时 malloc 失败复位 环境:Hi3861 农业终端,固件 ____,MQTT KeepAlive 60,每 2s 上报 步骤:无额外打流;每 10min 打印 free heap 期望:堆在启动平台 ±10%,无复位 实际:单调下降;13h08min 复位;启动日志 malloc fail 附件:heap.csv 曲线;uart 含复位横幅 初步分层:堆泄漏(疑似重连或上报路径)。不是栈溢出(运行时长相关)。待开发核对未 free 路径。2 小时若只降很少,不要假装 12 小时结论。写「2h 未暴露,12h 未做」。
5. 泰芯对照
| 公开事实 | 测试语言 |
|---|---|
| mjpeg 改节点申请防碎片 | 开录像/拍照后是否还能申请大块;失败是否随时间 |
| h264 空间不足死机 | 升分辨率/开辅码流的拷机 |
| lcd line_buf 大小错误死机 | 改分辨率后的冒烟+拷机 |
| FPV 文档:事件注册消耗 heap,退出要 untake | 反复配网/插拔是否泄漏事件节点 |
| 初始化里 Socket 阻塞应放到任务 | 拷机看主任务是否被堵(第 3 篇) |
| 低功耗循环看 heap(第 16 篇) | 休眠唤醒后堆是否回到平台 |
学生板没有 mjpeg 就用「反复 malloc 不同大小」在 PC 上讲碎片。
6. 30 秒口径
拷机要看堆曲线、任务、重连次数、复位,不看灯亮。
泄漏是剩余堆只降不回;碎片是总空闲够但连续块不够;栈溢出常常立刻死。
重连失败路径最容易漏 free。
我做了 2 小时带表拷机(12 小时若没做会说明)。
ReleaseNote 里的死机很多是空间和碎片,测试要对着分辨率和功能组合做。
7. 作业
labs/week13/ heap-leak.csv heap-free.csv not-a-leak.md # 栈溢出/常驻/碎片对照 soak-2h.md # 每 10min 一行,没有就写抽查时间 reconnect-heap.md # 次数如实 bug-or-clean.md验收:两条曲线方向相反或平台对照明显;拷机表不是「灯亮」一句话。
8. 自测题
- 剩余堆启动时降 8KB 然后 2 小时平,算泄漏吗?
- 为什么只测配网成功路径会漏测泄漏?
- 看门狗复位和 malloc 失败复位,日志上如何区分?
- 碎片时
xPortGetFreeHeapSize可能仍显示够用,为什么功能失败? - 2 小时通过能否宣称 12 小时通过?
- ESP8266 模组测谁的堆?
- 拷机中途电脑休眠,数据怎么处理?
- 误控为什么要单独列?
9. 学生常见翻车
- 拷机只看灯。
- 把死机都写成泄漏。
- 没有起始堆,只有结束感觉。
- 12 小时日志被休眠截断仍写通过。
- 把演示用的 leak_demo 留在产品工程。
- 简历写精通内存检测,实际没有 csv。
10. 下一篇预告
第 14 篇:《方案测试的 Python 只有一种正当用法:替你发 AT、收日志、填表格》。
2 小时一行一行手抄会疯。下一篇 50 到 80 行 pyserial:错密码 10 次,统计耗时和 ERROR 原文。板端仍是 C。
附录 A. 自测题参考答案
- 像常驻,不是泄漏。
- 失败路径常漏 free。
- WDT 有看门狗打印;malloc 失败常有申请失败日志或返回 NULL 后崩溃栈。都以原文为准。
- 总空闲是碎片之和,malloc 要连续块。
- 不能。
- 主控堆;模组内部除非有 AT 查堆。
- 实验无效,重做。
- 稳定且「还活着」也可以伤害用户;功能错误不是死机。
附录 B. 公开资料
- TXW82x FPV ReleaseNote:mjpeg 碎片、h264/lcd 死机
- FPV 开发文档:heap 初始化、事件消耗 heap、CPU1 heap 配置
- 第 2、3 篇
(第 13 篇完)
11. 曲线怎么画才像测试而不是手绘心情
csv:
t_s,free_heap_or_outstanding_k,event 0,40,start 600,39,none 1200,38,reconnect 1800,30,reconnect_fail_path用 Excel / LibreOffice 散点图。纵轴写单位:剩余堆 KB 或 outstanding KB。标题含版本。泄漏:整体右下(剩余)或右上(占用)。正确 free:锯齿,每次回到平台。
没有 MCU 堆函数:PC 上用计数器模拟仍然算本篇「曲线意识」及格,但第 18 篇 SOAK 不能写「已证明无泄漏」,只能写「PC 演示 + 板上未采样堆」。
Hi3861 常见HiLink或自己在应用里printf("heap=%u\n", remaining);。找不到 API 就问课程工程是否已有 shell。不要臆造函数名写进博客当原厂 API。
12. 重连 20 次最小脚本逻辑(人也可以按键)
for i in 1..20: 记录 heap0 关热点 5s 开热点 等到 GOT IP 或 30s 失败 记录 heap1、结果、i 期望:heap1 回到 heap0 ± 边限 失败:每次 -n 字节,20 次后明显台阶边限要写死,例如 ±2KB(协议栈瞬时占用)。不要用眼睛看「差不多」。
13. 栈溢出对照实验(可选,PC)
voidboom(void){charhuge[1024*1024];/* 教学:大数组在栈上 */huge[0]=1;boom();}跑起来会很快崩。和泄漏对照:泄漏要时间或次数;栈溢出常常第一次深递归就死。拷机 2 小时才死,优先堆和碎片,不要第一句「栈溢出」。
中断里 malloc、大 printf:第 2 篇。拷机若只在 Wi-Fi 中断高峰死,再怀疑中断栈。
14. 公开 ReleaseNote 怎么写成测试用例
| 公开修复 | 学生/到岗用例 |
|---|---|
| mjpeg 按节点申请 | 连续拍照 100 张,中间看是否仍能申请;失败是否随时间 |
| h264 空间不足死机 | 升分辨率或开辅码流 30min |
| lcd line_buf 大小 | 改分辨率冒烟 |
| sd 格式化死机 | 格式化 10 次 N/M |
| 720P 辅码流未编码 | 打开辅流 RTSP 检查有无画面(第 16 篇) |
无摄像机:这些写在 week16 映射表,不写进 week13「我测过」。
15. 2 小时表空行示例
22:00 heap=40k AT=OK RSSI=-50 IP=yes MQTT=yes reconn=0 rst=0 22:10 40k OK -51 yes yes 0 0 22:20 39k OK -50 yes yes 0 0 # 1k 波动,观察 22:30 关热点注入 22:32 38k OK n/a no no 1 0 # 掉线中 22:35 40k OK -52 yes yes 1 0 # 回来,堆回到平台 —— 好 00:00 仍 40k 无复位若 22:35 堆停在 32k 再也不回:停实验,提单,不必硬撑 2 小时装完整。