合并 docs/待处理内容.md(N/S/T-x)、审查记录/项目审计改进清单.md(P-x)、 审查记录/下一步任务规划.md(阶段与基线)、审查记录/测试与架构改进建议.md (S1x/A-x)为单一《待处理内容(统一待办登记簿)》。 - 结构:编号空间与来源 / 未闭环一览 / 任务排序(阶段 A~D)/ 逐条详述 / 待验证与待确认 / 设计基线 / 诊断基线 / 内存预算 / 维护约定。 - 编号沿用且不回收;E-xx 仍由 错误码传播审计.md 独立维护。 - 删除 3 份被合并文档;重定向下游引用:README、AGENTS、审查报告、 实机测试实施指南、Net 层架构、NET/cfg 模块 README、脚本注释。 - 同步订正计数:P1=2(+1 收尾) / P2=10 / P3=6。 pre-commit 17/17 Passed。
44 KiB
STM32F4-Base RAM 使用分析与优化方案(内存管理主文档)
版本:v1.2 日期:2026-09-21 变更:v1.2 数据现状全量同步——§2.1/§2.2/§5/§6/§8.6 等"现状"数据全部更新为 2026-09-21 lftpd 静态化后的最新 map 与实测值;清除 2026-09-11 历史口径残留(历史快照保留于
data/) 适用工程:STM32F4-Base(STM32F407ZGTx @168MHz,FreeRTOS V10.3.1 + CMSIS-RTOS2) 数据来源:MDK-ARM/STM32F407-Demo/STM32F407-Demo.map(@build重建)+scripts/ram_report.py(静态台账)+scripts/stack_report.py(静态调用图 × 运行时栈水位)+ 源码静态分析 + 网络调研 本文档是内存管理主文档(2026-09-21 合并):原《STM32F4-Base 内存使用分析报告》(现状审计/快照)与《内存使用分析规划》(执行规划,已全部闭环)已并入本文;区域放置与 DMA 可达性规范单列于内存内容分配策略.md(本文只引用、不重复)。 快照产物:data/ram_report_<日期>.md(静态台账)、data/stack_report_<日期>.md(栈预算)。
1. 背景与目标
待处理内容.md 已提出:"所有任务的内存都是往大的给的,导致内存不够用,要仔细排查内存的预算情况"。
分析起点(2026-09-11 快照,逐符号见 data/ram_report_20260911.md):
- 主 SRAM 128KB 中静态区曾占 122.7KB,余量仅 5.3KB
- FreeRTOS 堆 48KB 里装下 ~33.5KB 任务栈,可分配余量只有 ~12KB
- F407 另有 64KB CCM RAM:已于 2026-09-21 启用并把 FreeRTOS 堆迁入(§1.2)
- 起步时没有任何运行时测量手段:不知道每个任务实际用了多少栈,改栈全靠拍脑袋
当前状态(以 §2.1 受管快照为准,由 .map 自动生成):RW_IRAM1 84,960 / 131,072 B(64.8%)、RW_CCM 49,184 / 65,280 B(75.3%)。⚠️ 任务栈:2026-10-04 重采(产品构建 V5 30 min 负载)显示 defaultTask 峰值升至 3664B/4096(余量 432B ≈ 10%,已低于 30% 判据),详见 §2.3 注与 ../待处理内容.md §6(登记 待处理内容.md S-1)。
本方案三个目标:
- 可测量:建立"每任务 RAM 消耗 + 每模块静态占用 + 堆余量"的常态化观测手段(0.1/0.2/0.3/0.6 已落地;0.4/0.5 可选项未做)
- 降消耗:通过 LVGL 内存池、任务栈、CCM RAM、驱动缓冲等手段释放 RAM
- 可持续:建立 RAM 预算台账与门禁,防止再次"往大了给"(§3 长效机制,部分为
[计划])
2. 现状测量
2.1 总预算
本表由
.map自动生成,勿手改——改代码后跑python scripts/check_mem_numbers.py --write同步; 提交时 pre-commitmem-numbers钩子会校验一致性(见docs/文档规范.md§4)。
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 265,940 B (259.71 KB) | 1,048,576 B (1024 KB) | 25.4% |
| RW_IRAM1 | 84,960 B (82.97 KB) | 131,072 B (128 KB) | 64.8% |
| RW_CCM | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% |
| Flash (ROM 合计) | 266,676 B (260.4 KB) | 1,048,576 B (1024 KB) | 25.4% |
各区的容量与常量来源:
| 项 | 说明 | 来源 |
|---|---|---|
| 主 SRAM (0x20000000) | 128 KB(RW_IRAM1 Max 0x20000) |
scatter STM32F407-Demo.sct:22 |
| CCM SRAM (0x10000000) | 64 KB;FreeRTOS 堆于 2026-09-21 迁入(§1.2) | scatter STM32F407-Demo.sct:25;RM0090 §3.1.1 |
| FreeRTOS 堆 (heap_4) | 48 KB(49,152 B),位于 RW_CCM;实测 free=8,784 / min_ever=4,848 B |
Inc/FreeRTOSConfig.h:71 configTOTAL_HEAP_SIZE |
| C 库堆/栈 (startup) | 512 B / 1 KB,启动期 main() 用 | map startup_stm32f407xx.o HEAP/STACK 节 |
2.2 静态大项排行(2026-09-21 当前,完整台账 data/ram_report_20260921.md)
RW_IRAM1(主 SRAM),按 ZI/RW 合计降序:
| # | 大小 (B) | 对象文件 | 内容 |
|---|---|---|---|
| 1 | 40,960 | lv_mem_core_builtin.o |
LVGL 静态内存池 ucMemPool[LV_MEM_SIZE](§2.6.2) |
| 2 | 6,746 | ui_net_sink.o |
远程显示收发缓冲 g_tx[3209] + g_lcdmem_data[3204] + g_rx[64] |
| 3 | 6,408 | ui_port.o |
g_ui_fbuf[3200] + g_ui_draw_buf[3200](160×160 1bpp 帧缓冲) |
| 4 | 6,200 | nand_ftl.o |
s_page_buf/s_cache_buf/s_copy_buf 各 2,048 B + dhara 结构(DMA) |
| 5 | 4,616 | lftpd.o |
FTP 会话静态缓冲(2026-09-21 由栈迁入,§1.4) |
| 6 | 4,104 | ch395f.o |
s_spi2_dma_tx_buf[2052] + s_spi2_dma_rx_buf[2052](DMA) |
| 7 | 2,576 | gd5f2gq5ue.o |
s_bbt[256] + s_bbt_best[256] + s_bbt_page_buf[2048](DMA) |
| 8 | 1,720 | cmsis_os2.o |
CMSIS-RTOS2 封装静态区 |
| 9 | 1,608 | freertos.o |
default_fatfs(含 512 B 扇区窗)+ fs_work[512] + RTOS 句柄 |
| 10 | 1,220 | tasks.o |
FreeRTOS 内核调度静态区 |
| 11 | 1,024 | startup_stm32f407xx.o |
C 库启动栈(仅启动期) |
| 12 | 704 | cfg_file.o |
配置引擎静态态(双端共享核心) |
| 13–17 | ≤560 | spi.o / usart.o / lftpd_io.o / lv_init.o / net_socket.o |
外设句柄、FIL、LVGL 全局、socket 表 |
| 其余 | ~1.7 KB | 约 25 个 .o | 小型 RW/ZI |
RW_CCM(CCM):heap_4.o 49,184 B(FreeRTOS 堆 49,152 B + RW 32 B)——2026-09-21 由主 SRAM 迁入(§1.2),不再计入主 SRAM。
静态合计(以 §2.1 受管快照为准):RW_IRAM1 87,040 B(85.00 KB / 128 KB,66.4%) + RW_CCM 49,184 B(48.03 KB / 64 KB,75.0%) = 136,224 B(RW+ZI)。
历史(2026-09-21):RW_IRAM1 82,048 B(62.6%)+ RW_CCM 49,184 B = 131,232 B。
DMA 可达性:第 4/6/7 项(NAND FTL、CH395F SPI2、GD5F BBT)为 DMA 缓冲,必须留在主 SRAM、禁止迁 CCM/SRAM3——权威清点与规则见
内存内容分配策略.md(本文不重复)。 2026-09-11 历史排行(heap_4.o49,152 B 尚在 SRAM1、静态合计 122.7 KB 的旧布局)见data/ram_report_20260911.md,仅供回溯。
2.3 任务栈(从 48KB 堆动态分配)
Src/freertos.c 中 osThreadAttr_t.stack_size 单位为字节(CubeMX 生成代码,1024 * 4 表示 4096 字节):
⚠️ 下表"实测峰值"列为 2026-09-21 快照,已过时:2026-10-04 产品构建(
suite=NONE)V5 30 min 真实负载重采(串口[STACK],证据/tmp/v5_soak_serial_20261003.log)——defaultTask 3664B/4096 (余量 432B ≈ 10%,已低于 30% 判据,登记 S-1)、ftpTask 2516B/4096(38%)、netTask 1228B/4096(70%)、 uiTask 4060B/16384(75%)、timeSyncTask 792B/2048(61%)、testTask 496B/8192(93%)。 栈水位随 A2/A3/A4 + IWDG + 卷锁 + 堆告警等陆续入链持续上升,本表需随stack_report.py重新基线。
| 任务 | 配置栈 | 字节 | 实际职责 | 2026-09-21 实测峰值(§0.6) |
|---|---|---|---|---|
| uiTask | 4096*4 |
16KB | LVGL 1bpp 离屏渲染(渲染缓冲在静态区,栈只走调用链) | 3824 B(余量 76%)→ 可大幅缩减 |
| netTask | 1024*4 |
4KB | CH395F 轮询(DMA 搬运,栈压力小) | 588 B(余量 85%) |
| ftpTask | 1024*4 |
4KB | lftpd FTP 服务 | 1184 B(余量 71%,§1.4 静态化后) |
| testTask | 2048*4 |
8KB | 测试套件(产品构建空转;存储套件深链需要) | 348 B(产品构建) |
| defaultTask | 1024*4 |
4KB | FatFS 挂载/探针 + 配置引擎 + syslog + 栈水位探针 | 2136 B(余量 47%) |
| timeSyncTask | 512*4 |
2KB | TCP 时间同步 | 420 B(余量 79%) |
| idle(内核) | configMINIMAL_STACK_SIZE=128 |
0.5KB | 空闲 | 未观测(未取句柄) |
| timer(内核) | configTIMER_TASK_STACK_DEPTH=256 |
1KB | 软定时器 | 未观测 |
任务栈全部从 FreeRTOS 堆(48KB,2026-09-21 已迁 CCM)分配。"配置栈"列以
Src/freertos.c为唯一来源;实测峰值/余量判定以 §0.6 的stack_report基线为准(本表数值同步自该基线)。
2.4 动态分配点与隐患
- lftpd 的 C 库
malloc隐患(✅ 2026-09-10 已修,见阶段 2.1):lftpd_io.c的 3 处malloc/free(路径规范化/DIR)与lftpd.c的 7 处free已全部改用pvPortMalloc/vPortFree(C 库堆仅 startup 的 512B,原写法长路径必炸);当前源码无裸malloc残留。 - lftpd 大栈局部变量(✅ 2026-09-21 已整改,见 §1.4):
lftpd.c:67-68(message[256]+response[512])、:109,134(name[256]×2)、:156,185(buffer[1024]×2)、:551(read_buffer[512])、:583(arg_buf[512])以及lftpd_log.c的buffer[256]已全部改为static;实测 ftpTask 峰值 3480 → 1184 B,不再需要 4KB+ 栈。 - DMA 缓冲位置:CH395F(SPI2 DMA)、GD5F(SPI1 DMA)、UART DMA 缓冲必须由 DMA 访问 → 只能放主 SRAM,不能放 CCM(CCM 仅 CPU 可达,见 4.3)。
2.5 结论:紧张的三个根因(含 2026-09-11 审计量化)
| 根因 | 占用 | 可回收性 |
|---|---|---|
| ① LVGL 内存池 40KB 对 1bpp 小屏菜单过大 | 40KB | 高(目标 8~16KB;开机实测 UI 仅用 ~3KB,见 §2.6.2) |
| ② 任务栈"往大了给",无实测依据 | 38,912 B(38 KB 现值,§2.6.1) | 高(§0.6 已有实测:uiTask 3.8/16KB、netTask 0.6/4KB → 阶段 1.3 缩减) |
| ③ 64KB CCM 曾完全未利用,缓冲挤在主 SRAM | — | ✅ 2026-09-21 已启用(堆迁入 CCM),CCM 余量 ~16KB |
量化补充(来源:2026-09-11 审计快照,口径与方法见 §8.4):
- 两池(FreeRTOS 48KB + LVGL 40KB)曾占主 SRAM 的 68%,是当时"SRAM 几乎耗尽(95.4%,余 5.8KB)"的直接原因;堆迁 CCM 后该结构性压力已解除(现 RW_IRAM1 66.4%,见 §2.1)
- heap_4 不合并相邻空闲块 → 长期运行需盯碎片,
min_ever(现 4,848 B)是硬指标而非free - 未映射资源:SRAM3 64KB(scatter 未映射;clone 芯片可能无此区);CCM 已用
- 曾经的三条中风险项:新任务无处安置(已缓解)、堆碎片(仍存)、栈溢出检测缺失 → 已于阶段 0 开启
configCHECK_FOR_STACK_OVERFLOW=2并实测抓到过溢出(§0.1)
小项:FF_FS_TINY=0(每打开文件 512B 扇区窗,阶段 1.5 可改 1)、ui_net_sink PC 远程显示缓冲常驻 6.4KB(产品构建可加开关关闭,阶段 1.5);lftpd 栈局部变量 ✅ 2026-09-21 已静态化(§1.4)。
2.6 堆 / LVGL 池 / C 库 / Flash 明细
2.6.1 FreeRTOS 堆(48 KB;2026-09-21 已迁 CCM RW_CCM)
configTOTAL_HEAP_SIZE = 49,152(heap_4)。内部构成:
| 组件 | 大小 | 依据 |
|---|---|---|
| 6 × 应用任务栈 | 38,912 B | 各 stack_size 求和(§2.3;2026-09-11 快照为 34,304 B) |
| 2 × 内核任务栈(idle/timer) | 1,536 B | configMINIMAL_STACK_SIZE / configTIMER_TASK_STACK_DEPTH |
| 8 × TCB | ~640 B | sizeof(TCB_t) ≈ 76 B(8 字节对齐后 80 B),逐字段见 tasks.c:252-329 |
| netMsgQueue | ~448 B | osMessageQueueNew(8, 48) = Queue_t 56 B + 8×48 B |
| sys_evt 事件组 | ~24 B | osEventFlagsNew |
| 实测水位(2026-09-21,含 FTP 负载) | free = 8,784 B / min_ever = 4,848 B |
串口 [STACK] heap … |
lftpd 会话期间还会
pvPortMalloc路径缓冲/DIR(<1 KB 瞬态)。堆余量底线按 ≥4 KB 守住(min_ever是全负载历史最低点,比free更有约束力)。
2.6.2 LVGL 内存池(40 KB)
| 参数 | 值 | 来源 |
|---|---|---|
LV_MEM_SIZE |
40,960 B (40 KB) | Lib/lv_conf.h(模拟器 simulator/lv_conf.h 需同步) |
| 分配器 / 色深 / OS 抽象 | TLSF / 1 (1bpp) / 无 | 同上 |
池内装 UI 对象树、样式、字符串、动画对象,均为运行时分配;TLSF 元数据开销约 1015%,净可用 ≈3436 KB。开机实测(2026-09-21 日志):menu build start: total=39476 free=37248 → menu build done: used=2976,即当前 UI 仅占约 3 KB——这是 §1.1 把池缩到 8~16 KB 的直接依据。静态无法估算池占用,需运行时 LV_USE_MEM_MONITOR(lv_mem_monitor_t)。
2.6.3 C 库堆/栈(microlib)
| 组件 | 大小 | 说明 |
|---|---|---|
| 启动栈 | 1,024 B | startup_stm32f407xx.o,仅 HAL_Init/app_main_init 期间 |
| C 库堆 | 512 B | Heap_Size EQU 0x200 → 自研代码禁用 C 库 malloc(AGENTS.md 铁律;lftpd 的 malloc 隐患见 §2.4-1 与阶段 2.1) |
| C 库代码(Flash) | ~4.2 KB | printf / 浮点 / 字符串 |
2.6.4 Flash 1 MB 分布
| 段 | 大小 (B) | 占比(对 1 MB) |
|---|---|---|
| Code (inc. data) | 246,456 | 23.5% |
| RO Data | 15,476 | 1.5% |
| RW 初值 | 728 | — |
| 合计 ROM | 262,660 | 25.0%(余 ~786 KB) |
本表随代码变化;权威数字与占比见 §2.1 受管快照(
ER_IROM1/ Flash (ROM 合计) 行)。 历史快照:2026-09-21 为 Code 234,976 / RO 13,824 / 合计 ROM 249,432(23.8%)。
2026-09-11 快照的模块级分解(LVGL ~72 KB / 应用+UI ~47 KB / FatFS+dhara ~11 KB / CH395F ~10 KB / HAL ~9 KB / C 库 ~4.3 KB / FreeRTOS ~4.6 KB)见
data/ram_report_20260911.md;重跑scripts/ram_report.py即得当前分解。
3. 网络调研摘要
| 主题 | 结论 | 来源 |
|---|---|---|
| 任务栈测量(高水位) | FreeRTOS 提供 stack "high water mark"(历史最小剩余栈),"you have data you can use to adjust the stack accordingly to avoid RAM being wasted" —— 官方创始人 Richard Barry 明确推荐的数据驱动定栈方法 | FreeRTOS 官方论坛 Stack size calculation(rtel 2011-07-20 回复) |
| 栈溢出检测 | configCHECK_FOR_STACK_OVERFLOW=2:任务栈顶部预填特征值(0xEFFE...),每次上下文切换 + tick 时校验,溢出立即报告;方法 1 只查栈指针越界 |
论坛 Stack Overflow Detection - Method 2;本工程内核源码 Middlewares/.../include/stack_macros.h:36-122 语义一致 |
| 堆测量 | xPortGetFreeHeapSize() / xPortGetTotalHeapSize() 随时可查;heap_4 支持 free 但有碎片化,需对比"理论余量 vs 实测 free"观察碎片 |
FreeRTOS 内核源码 heap_4.c(工程内);FreeRTOS 文档 |
| 运行时统计 | vTaskGetRunTimeStats 需 configGENERATE_RUN_TIME_STATS=1 + portCONFIGURE_TIMERS_FOR_RUN_TIME_STATS(占用一个硬件定时器),给出每任务 CPU 占比 |
本工程内核 task.h:1694-1756 注释 |
| LVGL 缓冲 | 官方文档:PARTIAL 渲染模式"buffers can be smaller than the display to save RAM,至少 1/10 屏";单色 I1 格式缓冲 = (W×H/8)+8;双缓冲可并行渲染/搬运 |
LVGL v9.2 文档 Display interface(lvgl.io/docs/porting/display 的源文件) |
| STM32F4 CCM | F407 含 64KB CCM @0x10000000;只能由 CPU 访问,DMA 不可达,无 cache/ECC;适合纯 CPU 读写的大缓冲 | ST RM0090(STM32F405/407/415/417/42x 参考手册)§3.1.1 |
| FatFs | FF_FS_TINY=1:FIL 结构体去掉 buf[FF_MAX_SS] 私有扇区窗,改用卷内共享 9 字节窗口(FAT32),每打开文件省 512B |
本工程 Lib/FatFs/ff.h FIL 结构注释、Lib/FatFs/ffconf.h:32 |
| 静态占用测量 | Keil map 文件 "Image component sizes" 给出每个 .o 的 RO/RW/ZI 精确字节数;fromelf --image_info xxx.axf 可输出各 Region 用量 |
本工程 STM32F407-Demo.map "Image component sizes" 表(行号随每次重建变化,勿引用行号) |
注:freertos.org 官网页面为 JS 动态渲染,直接抓取拿不到正文;上表结论通过官方论坛 JSON API、GitHub 文档源文件与工程内内核源码(V10.3.1 头文件自带完整 API 注释)三方交叉核实。
4. 方案
阶段 0:建立测量能力(先做,1~2 天)
原则:没有数据不动刀。本阶段全部是"只增不减"的改动,零功能风险。
0.1 栈溢出检测(防身)
Inc/FreeRTOSConfig.h 增加:
#define configCHECK_FOR_STACK_OVERFLOW 2 /* 特征值法,每次上下文切换+tick 校验 */
#define configUSE_MALLOC_FAILED_HOOK 1 /* 堆分配失败立即可见 */
Src/main.c USER CODE 区实现钩子:
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
DBG_ERROR("STACK OVERFLOW: task=%s", pcTaskName);
/* 挂起或复位,按调试需要 */
for (;;);
}
void vApplicationMallocFailedHook(void)
{
DBG_ERROR("HEAP ALLOC FAILED (free=%u)", (unsigned)xPortGetFreeHeapSize());
for (;;);
}
成本:每次上下文切换几十周期,不占 RAM。
0.2 运行时栈水位(已落地:stack_probe_poll,2026-09-21)
实现位置:App/task/default_task.c 的 stack_probe_poll(2026-09-21 落地于 Src/freertos.c USER CODE 区,2026-09-25 随 defaultTask 任务体整体迁出至 App/task/ 并注册 Keil 工程 App 组)。
数据源是本版 CMSIS/FreeRTOS 已有的 API:osThreadGetStackSpace()(剩余字节,内部走
uxTaskGetStackHighWaterMark,特征值法与 configCHECK_FOR_STACK_OVERFLOW=2 同源)。
本版没有 osThreadGetStackSize 实现,也没有 ulTaskGetStackDepth /
xPortGetTotalHeapSize,因此配置栈统一取各任务 *_attributes.stack_size(唯一定义处,不重复常量)。
行为:挂在 defaultTask 主循环——首次 5s 后报告一次(bring-up 深链 mount/mkfs/探针/cfg/syslog
均已跑过),此后每 2s 采样,只在出现更深峰值时打印:既不破坏串口静默窗口,也不漏掉
之后才走到的更深分支(卷修复、配置保存等)。输出纯 ASCII,便于 @test 采集:
[STACK] <task> peak_used=<B>/<cfg> free_min=<B> (<pct>%)
[STACK] heap free=<B> min_ever=<B>
要点:
uxTaskGetStackHighWaterMark返回任务创建以来的最小剩余栈(单位 word=4B);used = 配置 − 剩余即该任务历史峰值栈占用。- 测量必须覆盖全功能路径(高水位是历史最小值,只反映跑过的路径):全菜单遍历、FTP 上传/下载大文件、CH395F 断链重连、NAND 格式化、时间同步。建议
@test全回归 + 1 小时浸泡。 - 未覆盖 idle/timer(内核静态小栈、纯循环,未取句柄);构建期静态侧见 §0.6。
0.3 map 静态占用报告脚本
脚本 scripts/ram_report.py(Python,已随仓库提供):
- 解析
MDK-ARM/STM32F407-Demo/STM32F407-Demo.map:- RW_IRAM1 Region 的
Size/Max→ 主 SRAM 静态水位 - "Image component sizes" 表 → 每 .o 的 ZI/RW 排序 Top20
- RW_IRAM1 Region 的
- 用法:
& '.venv\Scripts\python.exe' scripts/ram_report.py -o docs/内存管理/data/ram_report_<日期>.md,把结果贴进改动说明 - (待补)与基线 TSV 做 diff,任何对象 ZI 增量 >1KB 即高亮
0.4 (可选)运行时间统计
configGENERATE_RUN_TIME_STATS=1 + portCONFIGURE_TIMER_FOR_RUN_TIME_STATS 用 TIM2(HAL 时基已占 TIM7,TIM2 当前空闲,需核对 .ioc)。vTaskGetRunTimeStats() 给出每任务 CPU 占比——用于发现"CPU 大户",与内存问题正交,优先级低于 0.1/0.2。
0.5 (可选)main() 启动期栈峰值
启动期跑在 startup 的 1KB C 栈上(CubeMX 初始化 + 各驱动 init,含 BBT 全扫描)。ARMCC 可用 __current_stack_usage() 在 app_main_init 末尾打点,确认 1KB 够用;不够则调 scatter 的 STACK 节(与任务栈无关,单独预算)。
0.6 静态栈分析 + 汇总工具(构建期,零目标依赖;2026-09-21 落地)
UV4 每次构建已自动生成静态调用图 MDK-ARM/STM32F407-Demo/STM32F407-Demo.htm
(标题 "Static Call Graph",随 @build 刷新,无需给 armlink 加 --callgraph)。每个函数
条目自带 Stack size N bytes(该函数自身帧)与 Max Depth = N(其子树最坏累计深度)
以及完整调用链——即"不烧板子也能看到最坏链有多深、多宽"。
用法:scripts/stack_report.py(纯标准库,任意 python3 均可;解析上述 HTM +
Src/freertos.c 的栈配置,带 -s 时叠加串口 [STACK] 运行时水位):
# 最小用法(只看静态:每任务最坏链 + 余量判定)
& '.venv\Scripts\python.exe' scripts/stack_report.py
# 推荐用法(静态 + 实测一起判,判定取两者更坏者;报告归档到 data/)
& '.venv\Scripts\python.exe' scripts/stack_report.py -s tools\serial_tool\state\serial.log `
-o 'docs\内存管理\data\stack_report_<日期>.md'
| 参数 | 默认 | 说明 |
|---|---|---|
-i/--html |
MDK-ARM/STM32F407-Demo/STM32F407-Demo.htm |
静态调用图(Keil 构建产物) |
-s/--serial |
无 | 串口日志,解析其中 [STACK] 行(daemon 全量历史:tools/serial_tool/state/serial.log) |
-o/--output |
无(打印到 stdout) | 报告输出文件(Markdown) |
--isr-margin |
1024 |
ISR 嵌套预留(字节) |
--min-free-pct |
30 |
要求的最小余量百分比 |
- 判定规则:
余量 = 配置 − max(静态最坏深度, 实测峰值),须≥ max(1024 B, 配置×30%);任一任务不达标 → 脚本退出码 1(可挂门禁),全达标 → 0,HTM 缺失 → 2。 - 何时跑:① 改栈或加大局部变量前后;② 改 FatFs/lftpd/LVGL 等第三方配置后;③ 任务职责变化时。串口日志覆盖的路径越全越准(路径覆盖要求见 §0.2)。
- 报告内容:每任务预算表(配置/静态/实测/最坏/余量/判定)、各任务最坏调用链、帧宽 Top15(找"宽度大户")、堆 free/min_ever。
首份基线(2026-09-21,产品构建 + FTP 会话实测;产物 docs/内存管理/data/stack_report_20260921.md):
| 任务 | 配置 (B) | 静态最坏 (B) | 实测峰值 (B) | 最坏 (B) | 余量 | 判定 |
|---|---|---|---|---|---|---|
| defaultTask | 4096 | 2776 | 2136 | 2776 | 1320 (32%) | PASS |
| ftpTask(优化前) | 4096 | 2528 | 3480 | 3480 | 616 (15%) | FAIL |
| ftpTask(§1.4 静态化后) | 4096 | 472 | 1184 | 1184 | 2912 (71%) | PASS |
| netTask | 4096 | 440 | 588 | 588 | 3508 (85%) | PASS |
| uiTask | 16384 | 912 | 3824 | 3824 | 12560 (76%) | PASS |
| timeSyncTask | 2048 | 336 | 420 | 420 | 1628 (79%) | PASS |
| testTask(产品构建) | 8192 | 168 | 348 | 348 | 7844 (95%) | PASS |
实测堆:free=8784 B / min_ever=4848 B(48KB 池)。
处置结果(2026-09-21 当日闭环,走 ②→①):先做 §1.4(lftpd 大局部变量静态化)——复测 ftpTask 峰值 3480 → 1184 B(余量 616 → 2912 B / 71%),已满足 ≥ max(1024 B, 配置×30%),①(涨栈到 1600 words)不再需要。代价:静态 BSS +4,616 B(RW_IRAM1 77.4 → 82.0 KB / 128 KB,余量仍 ~48 KB)。
静态与实测是互补的两套数据(必须都留):
- defaultTask:静态 2776 > 实测 2136——静态是上界(最深分支未必走到),实测是已走路径。判栈够不够看两者更坏者,做定栈优化看实测。
- netTask / uiTask / testTask / timeSyncTask / ftpTask:实测 > 静态——静态是下界(互递归、间接调用、ISR 上下文保存 ~100B 未计入)。ftpTask 最典型:优化前静态 2528 B(控制通道握手链)低估了 950 B,真实峰值 3480 B 只在 128KB STOR 传输时暴露 → 不跑真实业务就定栈 = 拍脑袋。 §1.4 静态化后静态 472 B / 实测 1184 B,两者重新收敛(差距回到 ISR/上下文量级)。
- 本次可观测性的第一个产出不是数字而是缺陷:ftpTask 因就绪位被其它等待者消费而从未启动(21 端口一直无监听),修好后才有了上面这组数据——详见
docs/测试说明/FTP_Test_Guide.md§8.8。
要点与边界:
- defaultTask 的静态最坏链是配置保存/旧档迁移路径,不是首启探针路径:
net_cfg_load → net_cfg_legacy_migrate → net_cfg_save → cfg_file_save → s_write_template → s_write_doc → cfg_store_write_atomic → f_rename → follow_path → dir_next → create_chain → put_fat → move_window → sync_window → disk_write → ftl_flush_cache → dhara_map_write → prepare_write → auto_gc → dhara_map_gc → ... → trace_path → dhara_journal_read_meta → dhara_nand_read → gd5f_read_from_cache → HAL_SPI_Receive → ... → SPI_WaitFlagStateUntilTimeout。 2776 B > 2048 B → 512 words 栈必然溢出,与 2026-09-21 实测完全一致。 - 帧宽榜直接点名宽度大户(自身帧):
s_append704 B(FIL 512 + 记录 64 + 时间戳)、StartDefaultTask560 B(探针FIL __chk)、cfg_store_read/cfg_store_write_atomic576 B、LVGL 绘制 624/560/400 B——与 §1.4 的整改项对得上。lftpd 三项已于 2026-09-21 静态化(§1.4),已退出帧宽榜前 15(原handle_control_channel1104 B、receive_file/send_file1088 B、send_response800 B)。 - 静态分析的天然盲区:递归/互递归(LVGL widget 树、
layout_update_core、apply_theme等)与间接调用只能按 "Cycle" 近似,armlink 明确标注+ Unknown(Cycles, Untraceable Function Pointers)。因此 uiTask 的 912 B 是下限(实测 3824 B),必须靠运行时水位。 - testTask 在产品构建里只是
osDelay空循环(168 B);2048 words 是给存储套件构建(TEST_SUITE_STORAGE)预留的,测套件时应重跑本脚本看该构建的静态值。 - 测 ftpTask 的复现配方:设备就绪后跑
& '.venv\Scripts\python.exe' test/host/ftp_speed_test.py --size 128(STOR 128KB + RETR + 逐字节比对),再按上面命令叠加串口日志出报告。不要用裸 TCP 工具探测 21 端口——CH395F 单客户端复用,探测会占掉唯一会话(见 FTP 指南 §8.9)。
阶段 0 验收
- 串口可见
[STACK]报告:每任务peak_used/free_min+ 堆free/min_ever - 人为制造栈溢出(临时把某任务栈改到 64 字节)能触发
STACK OVERFLOW钩子 scripts/ram_report.py输出 .map 静态占用 Top20;scripts/stack_report.py输出每任务静态余量(可叠加运行时水位)
阶段 1:静态大项削减(收益最大,3~5 天)
1.1 LVGL 内存池 40KB → 16KB → 8KB(省 24~32KB)
- 依据:屏幕仅 160×160×1bpp,菜单为轻量控件树;LVGL 内存池只装对象/样式/字符串缓冲,与像素缓冲无关。40KB 来自模板默认值,无适配依据。
- 做法:
Lib/lv_conf.h:34LV_MEM_SIZE先改(16*1024),跑 UI 全量回归(PC 模拟器 + 实机),无LV_ASSERT_MALLOC/分配失败再试 8KB。 - 同步在
simulator/lv_conf.h:26保持一致(两端共用 UI 源码,配置必须同步)。 - 验证:UI 全菜单遍历 + 动画/滚动/输入框 + 中文字体渲染无缺字;LVGL 内置内存监控(
LV_USE_MEM_MONITOR,调试构建开启)观察池占用峰值。 - 风险:个别复杂控件/长字符串可能吃紧 → 若 8KB 不够就停在 16KB,不勉强。
1.2 启用 CCM RAM(+64KB 预算空间)
依据 RM0090 §3.1.1:F407 的 64KB CCM @0x10000000 仅 CPU 可访问(DMA 不可达、无 cache)。
进度(2026-09-21):第一阶段已落地——scatter 新增 RW_CCM 区(sct:25,注释 "FreeRTOS heap only")并把 FreeRTOS 堆整体迁入(49,184 B,CCM 占用 75.3%)。下表缓冲迁入清单为后续剩余项(LVGL 池/UI 缓冲现仍在主 SRAM);APP_CCM_ENABLE 门禁宏尚未实现(scatter 无开关,见下"风险门禁")。
迁入 CCM 的缓冲(全部纯 CPU 读写):
| 缓冲 | 大小 | 方式 |
|---|---|---|
LVGL 内存池 ucMemPool(LV_MEM_ATTR 挂载 section) |
8~16KB | Lib/lv_conf.h 定义 LV_MEM_ATTR __attribute__((section("CCM_DATA"))) |
g_ui_fbuf + g_ui_draw_buf(ui_port.c:16,18) |
6.4KB | __attribute__((section("CCM_DATA"))) |
g_tx + g_lcdmem_data(ui_net_sink.c:31,33) |
6.4KB | 同上 |
s_bbt/s_bbt_best(gd5f2gq5ue.c:28,39) |
0.5KB | 同上(小,顺手) |
严禁迁入(DMA 缓冲,必须留主 SRAM): CH395F SPI DMA 发/收缓冲(ch395f.c:42,44)、GD5F 页缓冲(nand_ftl.c:42-50,SPI DMA 传输)、所有 UART/SPI DMA 缓冲。
scatter 文件(MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct)增加:
RW_CCM 0x10000000 0x00010000 { ; CCM 64KB, CPU-only(2026-09-21 实际落地区名)
*(CCM_DATA)
}
风险门禁:scatter 注释已提示"clone MCU 可能缺 SRAM3",同理 CCM 在克隆芯片上不可靠 → 加编译开关 APP_CCM_ENABLE(App/app/app_cfg.h),关闭时 LV_MEM_ATTR 等定义退化为空,缓冲全部落回主 SRAM(阶段 1.1 池缩到 16KB 后主 SRAM 也放得下)。真 ST 芯片默认开。
验证:map 中出现 RW_CCM 且 *(CCM_DATA) 各对象地址落在 0x10000000~0x100FFFF;ram_report.py 同时输出两个 Region 水位。
1.3 任务栈按实测数据缩减(省 ~17KB;目标值已按 2026-09-21 实测修正)
定栈规则(写入台账,长期执行):
新栈 = max( 实测峰值 used × 1.5 , 1024B ) + ISR 余量
ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/UART/CH395F EXTI),
统一按 +1KB 计;中断执行在当前任务栈上,必须计入。
目标值(2026-09-21 实测数据修正):
| 任务 | 现值 | 目标 | 依据(max(最坏×1.5, 1024B) + 1KB ISR 余量) |
|---|---|---|---|
| uiTask | 16KB | 8KB | 实测 3824B → 5736+1024=6760B → 8KB(LVGL 渲染走静态缓冲) |
| netTask | 4KB | 2KB | 实测 588B → 1024+1024=2048B |
| ftpTask | 4KB | 3KB | 实测 1184B(§1.4 静态化后)→ 1776+1024=2800B → 3KB;再降须放宽 ISR 余量并复测 |
| testTask | 8KB | 2KB | 产品构建空转(实测 348B)→ 1024+1024;存储套件构建按该构建静态值另放大 |
| defaultTask | 4KB | 保持 4KB | 静态最坏 2776B(配置保存/旧档迁移深链,§0.6)→ 缩减必溢出 |
| timeSyncTask | 2KB | 保持 2KB | 实测 420B → 1024+1024=2048B,无缩减空间 |
缩减后任务栈合计 38,912 → 21,504 B(省 17,408 B / ~17 KB,全部在 CCM 堆内,主 SRAM 不受影响)。
每改一批 → @build + 全功能回归 + 复测高水位(free_min 应 ≥ 新栈×30%),不满足则回调。
1.4 lftpd 大栈局部变量 → 静态缓冲(✅ 2026-09-21 完成;ftpTask 峰值 3480 → 1184 B)
已把 lftpd.c / lftpd_log.c 中 9 个大局部缓冲改为函数级 static(lftpd 单客户端,全部调用与日志都在 ftpTask 内串行执行,静态化安全;各函数各自独立缓冲,无嵌套/别名冲突):
| 位置 | 原栈占用 (B) | 说明 |
|---|---|---|
lftpd.c:69,70 send_response |
256 + 512 | 每条响应都用到 |
lftpd.c:111,136 send_list / send_nlst |
256 × 2 | LIST/NLST 目录名 |
lftpd.c:158,187 send_file / receive_file |
1024 × 2 | 传输缓冲(原峰值主因) |
lftpd.c:553,587 handle_control_channel |
512 + 512 | 命令行 / 参数(arg 指向它,静态化后生命周期只增不减) |
lftpd.c:652 lftpd_start 的 client 上下文 |
~264 | lftpd_client_t 会话上下文 |
lftpd_log.c:9 lftpd_log_internal |
256 | 深层日志点 |
- 结果:ftpTask 实测峰值 3480 → 1184 B(余量 616 → 2912 B / 71%,判定 PASS);静态调用图 2528 → 472 B;帧宽榜中 lftpd 全部退出前 15。
- 代价(2026-09-21 实施时):静态 BSS +4,616 B(RW_IRAM1 77,432 → 82,048 B / 128 KB;CCM 与 Flash 不变)。
- 验证:
@build0/0 → 烧录 →@testPASS →ftp_up_dl_test.py10/10 PASS +ftp_speed_test.py --size 128逐字节一致(功能与搬迁前完全一致,反证无别名/重入问题)。 - 结论:原 ① 方案(ftpTask 涨到 1600 words)不再需要,栈保持 1024 words(4096 B)。
lftpd 属第三方代码(pre-commit 排除范围),只做最小侵入改动(每处仅加
static+ 单行注释),改动点在本台账登记。
1.5 其他小项
Lib/FatFs/ffconf.h:32FF_FS_TINY0→1(每打开文件省 512B 扇区窗;FAT32 适用;FF_FS_REENTRANT=0现状下安全)ui_net_sink(PC 远程显示,6.4KB):加APP_UI_NET_SINK编译开关,产品固件可关;保留时缓冲已迁 CCM(1.2)CH395F_TCP_MSS=1024已是压缩值,DMA 缓冲MSS×2+4为协议决定,不动
阶段 2:堆与动态分配治理(1~2 天)
2.1 修复 lftpd 的 malloc 隐患(必修)
lftpd_io.c:44,49,54 的 malloc / 对应 free 与 :189 的 malloc(sizeof(DIR)) 改为 pvPortMalloc / vPortFree(加 #include "FreeRTOS.h")。消除"512B C 堆"地雷,FTP 长路径不再随机失败。
✅ 2026-09-10 已落地:
lftpd_io.c(3 处malloc+ 3 处free)与lftpd.c(7 处free,释放lftpd_io_canonicalize_path返回的堆指针)全部改用pvPortMalloc/vPortFree;两文件加#include "FreeRTOS.h"。lftpd 不参与模拟器构建,仅 MCU 端。Keil 重建 0 错 0 警。
2.2 缩减 configTOTAL_HEAP_SIZE
阶段 1 完成后复测:堆内需容纳 ≈ 栈(15KB) + TCB(2.3KB) + 队列/事件组(0.5KB) + lftpd 临时分配(1KB) + 余量。configTOTAL_HEAP_SIZE 49152 → 32768,要求全功能负载下 heap free ≥ 8KB 持续成立(浸泡 1 小时验证)。
2.3 (可选,进阶)任务/队列全静态化
configSUPPORT_STATIC_ALLOCATION=1 已开。用 osThreadNew 的 attr.cb_mem(StaticTask_t + 静态栈数组)可把栈完全移出堆,预算 100% 透明、堆只剩小对象分配。改动面涉及 CubeMX 生成的 freertos.c 属性块,收益(~15KB 确定性)与改动成本权衡后决定,不作为必做项。
阶段 3:长效机制(0.5 天 + 长期)
- RAM 预算台账
[计划](模板见 §8.2;落地文件docs/内存管理/RAM预算台账.md尚未创建):记录每个 Region/缓冲/栈的计划值、实测值、变更历史;每次改内存相关代码必须更新 - AGENTS.md 增补(✅ 2026-09-21 已完成:"内存预算紧张"条目已含
ram_report.py/stack_report.py数据源与"改栈前后各跑一次"要求) - pre-commit 门禁(可选):钩子解析最新 map,RW_IRAM1 使用率 >92% 或 RW_CCM 超限 → 提交拦截并提示
- @test 扩展:
verify_log.py增加可选 Required 标记"[MEM] heap free",把堆余量下限纳入自动化回归 - map diff 常态化:每次
@build后跑ram_report.py,ZI 增量 >1KB 的改动必须在提交说明中解释
5. 预期效果
| 项目 | 现状(2026-09-21 实测,主 SRAM 数字已随代码更新,以 §2.1 为准) | 剩余项全部落地后(1.1/1.3/1.5/2.2) |
|---|---|---|
| 主 SRAM 静态占用 | 87,040 B / 128KB(66.4%) | ~27KB / 128KB(LVGL 池 + UI 缓冲迁 CCM、ui_net_sink 关) |
| 主 SRAM 余量 | 44,032 B(43.0KB) | ~101KB |
| CCM 使用 | 49,184 B / 64KB(75.0%,FreeRTOS 堆) | ~56KB / 64KB(堆 32K + 池 16K + UI 缓冲 + BBT) |
| 任务栈合计 | 38,912 B(38.0KB) | ~21.5KB(§1.3 修正目标) |
| FreeRTOS 堆 | 49,152 B(free=8,784 / min_ever=4,848 B) |
32,768 B(余 ≥8KB,全负载验证) |
| 总余量 | ~68KB(47.9 + 16.0 + 4.8) | ~120KB |
已落地项(不再计入预期):1.2 堆迁 CCM(2026-09-21)、1.4 lftpd 静态化、2.1 lftpd
malloc→pvPortMalloc(2026-09-10)。 估算假设:LVGL 池缩到 16KB(8KB 未验证)且迁 CCM;ui_net_sink产品构建关闭;ui_port帧缓冲 + BBT 位图迁 CCM;堆缩 48K→32K。落地顺序与每步验证见 §6。
6. 实施顺序与验证
| 步骤 | 内容 | 验证 | 状态 |
|---|---|---|---|
| S1 | 0.1 + 0.2(溢出钩子 + stack_probe_poll 栈水位探针,Src/freertos.c USER CODE 区) |
@build 0错0警;@test 串口出现每任务 [STACK] 报告 |
✅ 2026-09-21 |
| S2 | 0.3 ram_report.py + 基线 |
脚本输出与 map 人工核对一致 | ✅ 2026-09-21(data/ram_report_20260921.md) |
| S3 | 全功能回归跑数(菜单/FTP/网络/格式化/浸泡 1h) | 拿到各任务 free_min 与堆 free 底值 |
◐ boot + FTP 路径已实测(09-21);全菜单遍历 + 1h 浸泡待补 |
| S4 | 1.4 lftpd 静态化 + 2.1 malloc 修复 | FTP STOR/RETR 大文件 + 长路径回归 | ✅(2.1 于 2026-09-10、1.4 于 2026-09-21 完成) |
| S5 | 1.1 LVGL 池 40K→16K | UI 全量回归(模拟器 + 实机),无断言失败 | 待办 |
| S6 | 1.2 CCM(scatter + section + 门禁宏) | map 显示 RW_CCM;关 APP_CCM_ENABLE 也能编译通过 |
◐ RW_CCM + 堆迁入已完成(09-21);缓冲 section 迁移与门禁宏待办 |
| S7 | 1.3 栈缩减(按 S3 数据)+ 复测 | 每任务 free_min ≥ 新栈×30%,无溢出钩子触发 |
待办(目标值已按 09-21 实测修正) |
| S8 | 2.2 堆 48K→32K + 复测 | 全负载 heap free ≥ 8KB |
待办 |
| S9 | 1.5 小项 + 台账/门禁/AGENTS.md | @build + @test 全绿 |
◐ AGENTS.md 内存条目已增补(09-21);台账/门禁待办(§3) |
每步都必须 @build 通过(0 错误 0 警告)后再进入下一步;涉及 UI 的步骤用 simulator 先过一遍再上实机。
7. 风险与注意事项
| 风险 | 说明 | 缓解 |
|---|---|---|
| 栈定小 → 溢出 | 高水位只覆盖跑过的路径;中断嵌套深度难预估 | 1.5× 系数 + 1KB ISR 余量 + 溢出钩子 + 全功能回归后才定稿 |
| CCM 依赖真芯片 | 克隆 MCU 可能无 CCM;DMA 缓冲误入 CCM → 传输全错 | APP_CCM_ENABLE 门禁;DMA 缓冲清单硬性排除;map 地址落点检查 |
| LVGL 池缩小 → 分配失败 | 复杂界面/长字符串吃池 | 分步降(40→16→8)+ 每步全 UI 回归 + LV_USE_MEM_MONITOR 观测 |
| 高水位低估 | 启动后未触发的路径(如格式化失败分支)没算进去 | 测试套件覆盖异常分支;保留 ≥30% free_min 底线 |
| 第三方代码改动 | lftpd 被 pre-commit 排除,改动不受检查 | 最小侵入、台账登记、升级 lftpd 时需重放补丁 |
| CubeMX 重生成 | freertos.c/FreeRTOSConfig.h 的 USER CODE 区受保护,但 CubeMX 改配置会覆盖部分生成区 |
所有内核宏改动集中在 USER CODE 区或 app_cfg.h;.ioc 与代码双向核对 |
| 编码安全 | 新增 .c/.h 为 UTF-8 无 BOM;Drivers/BSP/UI/* 为 UTF-8 带 BOM |
遵守 AGENTS.md"源码编辑与编码安全"铁律:只用 Edit 工具改,不整文件重写 |
8. 附录
8.1 参考链接
- FreeRTOS 论坛(官方):Stack size calculation · Stack Overflow Detection - Method 2 · checking stack usage
- FreeRTOS 文档站:https://docs.freertos.org/ (官网旧页为 JS 渲染,正文以工程内 V10.3.1 内核头文件注释为准)
- LVGL v9.2 文档源:docs/porting/display.rst(Draw buffers / Monochrome 章节)
- STM32F4 参考手册 RM0090 §3.1(Memory map / CCM SRAM):https://www.st.com/resource/en/reference_manual/cd00901901-stm32f40-5xx-and-stm32f42-7xx-advanced-connection-line-arm-based-32bit-mcus-st-stmicroelectronics.pdf
- FatFs(工程内
Lib/FatFs/ff.h、ffconf.h配置注释)
8.2 RAM 预算台账模板
| Region/项目 | 计划值 | 实测值(map/串口) | 承载内容 | 变更历史(日期/人/原因) |
|---|---|---|---|---|
| RW_IRAM1 静态(除堆) | ≤75KB | | LVGL UI 缓冲、DMA 缓冲、FTL、BBT、内核 | |
| FreeRTOS 堆 | 32KB | | 6 任务栈+TCB+队列+lftpd 临时 | |
| └ 任务栈小计 | ~15KB | | 见 freertos.c 属性表 | |
| └ 堆余量底线 | ≥8KB | | 全负载 free 底值 | |
| RW_CCM(CCM) | ≤32KB | | LVGL 池、UI 帧缓冲、BBT 位图(2026-09-21 起先承载 FreeRTOS 堆) | |
| C 库堆/栈 | 512B/1KB | | 仅启动期;lftpd 不得再用 | |
8.3 关键配置位置速查
| 配置 | 位置 |
|---|---|
| 堆大小 | Inc/FreeRTOSConfig.h:71 |
| 栈溢出检测 | Inc/FreeRTOSConfig.h(新增,阶段 0) |
| 任务栈 | Src/freertos.c:61-101(各 osThreadAttr_t.stack_size,字节) |
| idle/timer 栈 | FreeRTOSConfig.h:70,94 |
| LVGL 池 | Lib/lv_conf.h:34(simulator/lv_conf.h:26 同步) |
| UI 屏参/缓冲 | Drivers/BSP/UI/ui_port.h:21-23、ui_port.c:16-18 |
| 远程 UI 缓冲 | Drivers/BSP/UI/ui_net_sink.c:28-33 |
| CH395F MSS/DMA 缓冲 | Drivers/BSP/CH395F/ch395f.h:119,137、ch395f.c:42-44 |
| NAND 页缓冲/BBT | Drivers/BSP/GD5F2GQ5UE/nand_ftl.c:42-50、gd5f2gq5ue.c:28-39 |
| FatFs | Lib/FatFs/ffconf.h:32(FF_FS_TINY) |
| scatter | MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct |
| lftpd 缓冲/malloc | Drivers/BSP/NET/lftpd/lftpd.c(大缓冲已静态化,§1.4)、lftpd_io.c(malloc→pvPortMalloc 已修,阶段 2.1) |
8.4 分析口径与数据来源(原《内存使用分析规划》并入)
芯片物理内存(数据源 RM0090 §3.1、STM32F407-Demo.sct):Flash 1024 KB @0x0800_0000;SRAM1 112 KB + SRAM2 16 KB 构成 RW_IRAM1 的 128 KB @0x2000_0000;SRAM3 64 KB @0x2002_0000 未映射(clone 芯片可能无此区);CCM 64 KB @0x1000_0000 仅 CPU 可达(DMA 不可达)。区域放置规则见 内存内容分配策略.md。
口径:
- 静态区 =
RW_IRAM1/RW_CCM内由链接器直接分配的 RW + ZI(含两个池的本体、驱动缓冲、C 库 HEAP/STACK 节) - 运行时动态区 = 两个池内部的再分配(任务栈 + TCB + 队列;LVGL 对象/样式/字符串)——池本体已在静态区全额计入,内部细分不重复加总,只做占比说明
- Flash =
Total ROM Size(Code + RO + RW 初值)
四视角 + 交叉校验:
V1 链接视图(map:Grand Totals / Image component sizes / Symbol Table)
← 静态数字的唯一权威来源
│ 交叉校验
├── V2 运行时堆视图(手算 48KB 池占用;分配点由源码遍历确认)
├── V3 LVGL 池视图(本体在静态区,只说明用途与运行时占用量级)
└── V4 源码/配置视图(给符号赋语义:用途 / 大小 / 是否 DMA 可达)
V4 逐项相加须与 V1 的 Image component sizes 逐 .o 吻合;不等则以 V1 为准并记录差异原因。
数据来源清单:STM32F407-Demo.sct(区域)、Inc/FreeRTOSConfig.h(堆/优先级/静态分配开关)、cmsis_os2.c:472-476(栈大小单位 = 字节:内部 / sizeof(StackType_t))、Src/freertos.c(任务/队列/事件组属性)、Lib/lv_conf.h、Lib/FatFs/ffconf.h、App/app/app_cfg.h + test/board/test_config.h(构建口径)、Drivers/BSP/** + App/**(缓冲语义/用途/DMA 归属);权威数据 = @build 重建后的 .map(历史 map 不得用于结论)。
8.5 图表与台账重生成
& '.venv\Scripts\python.exe' scripts/ram_report.py -o "docs\内存管理\data\ram_report_<日期>.md" # 静态台账
& '.venv\Scripts\python.exe' scripts/stack_report.py -s tools\serial_tool\state\serial.log `
-o "docs\内存管理\data\stack_report_<日期>.md" # 栈预算(静态 × 实测)
& '.venv\Scripts\python.exe' tools/arch_diagram/gen_memory_svg.py # 内存布局/任务栈预算图
产物:docs/images/内存布局_RAM.svg、内存布局_FLASH.svg、任务栈预算.svg(脚本与 SVG 一并提交;PNG 本地预览不提交)。
8.6 自洽校验(2026-09-21 快照)
| 层 | 指标 | Per-file 求和 | Grand Totals | 判定 |
|---|---|---|---|---|
| Flash | Code | 234,976 | 234,976 | PASS |
| Flash | RO | 13,824 | 13,824 | PASS |
| SRAM | RW | 632 | 632 | PASS |
| SRAM | ZI | 130,600 | 130,600 | PASS |
per-file 求和与 Grand Totals 的微小差异来自 linker 的
(incl. Padding)行(2026-09-21:326 B code + 74 B RO + 9 B RW);scripts/ram_report.py按'incl. Generated'−'incl. Padding'口径计入后与 Grand Totals 精确一致(见data/ram_report_20260921.md末尾自洽校验行)。