RAM主文档v1.2:现状数据全量同步至09-21实测(总预算/静态排行/预期效果/实施状态)

This commit is contained in:
2026-09-21 04:55:01 +08:00
parent 2505cf1e48
commit 80fc7ffaa5
@@ -1,6 +1,7 @@
# STM32F4-Base RAM 使用分析与优化方案(内存管理主文档)
> 版本:v1.1 日期:2026-09-21
> 版本: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`(本文只引用、不重复)。
@@ -10,7 +11,7 @@
`待处理内容.md` 已提出:"所有任务的内存都是往大的给的,导致内存不够用,要仔细排查内存的预算情况"。
分析起点(2026-09-11 快照,逐符号见 §2.2.1):
分析起点(2026-09-11 快照,逐符号见 `data/ram_report_20260911.md`):
- 主 SRAM 128KB 中静态区曾占 **122.7KB**,余量仅 5.3KB
- FreeRTOS 堆 48KB 里装下 ~33.5KB 任务栈,可分配余量只有 ~12KB
@@ -21,7 +22,7 @@
本方案三个目标:
1. **可测量**:建立"每任务 RAM 消耗 + 每模块静态占用 + 堆余量"的常态化观测手段(§0.1~§0.6,已落地)
1. **可测量**:建立"每任务 RAM 消耗 + 每模块静态占用 + 堆余量"的常态化观测手段(0.1/0.2/0.3/0.6 已落地;0.4/0.5 可选项未做)
2. **降消耗**:通过 LVGL 内存池、任务栈、CCM RAM、驱动缓冲等手段释放 RAM
3. **可持续**:建立 RAM 预算台账与门禁,防止再次"往大了给"(§3 长效机制,部分为 `[计划]`)
@@ -29,34 +30,16 @@
### 2.1 总预算
| 区域 | 大小 | 当前使用 | 来源 |
| 区域 | 大小 | 当前使用(2026-09-21 最新) | 来源 |
|---|---|---|---|
| 主 SRAM (0x20000000) | 128KB | 静态 122.7KB | scatter `STM32F407-Demo.sct:14`(RW_IRAM1 Max 0x20000) |
| CCM SRAM (0x10000000) | 64KB | **未使用** | STM32F4 RM0090 §3.1.1 |
| FreeRTOS 堆 (heap_4) | 48KB | 装栈 + 队列 + 余量 | `Inc/FreeRTOSConfig.h:71` `configTOTAL_HEAP_SIZE` |
| 主 SRAM (0x20000000) | 128KB | 静态 82,048 B(80.1 KB / **62.6%**,余 49,024 B) | scatter `STM32F407-Demo.sct:22`(RW_IRAM1 Max 0x20000) |
| CCM SRAM (0x10000000) | 64KB | 49,184 B(**75.0%**,FreeRTOS 堆 2026-09-21 迁入,§1.2;余 16,352 B) | scatter `STM32F407-Demo.sct:25`;RM0090 §3.1.1 |
| FreeRTOS 堆 (heap_4) | 48KB(49,152 B) | 装栈 + 队列 + 余量;**位于 RW_CCM**(实测 `free=8,784 / min_ever=4,848` B) | `Inc/FreeRTOSConfig.h:71` `configTOTAL_HEAP_SIZE` |
| C 库堆/栈 (startup) | 512B / 1KB | 启动期 main() 用 | map `startup_stm32f407xx.o` HEAP/STACK 节 |
### 2.2 静态大项排行(map "Image component sizes",ZI/RW 合计)
### 2.2 静态大项排行(2026-09-21 当前,完整台账 `data/ram_report_20260921.md`)
| # | 对象 (map 行号) | 大小 | 内容 | 位置 |
|---|---|---|---|---|
| 1 | heap_4.o (map:16166) | **48.0KB** | FreeRTOS 堆(内含任务栈) | `FreeRTOSConfig.h:71` |
| 2 | lv_mem_core_builtin.o (map:16168) | **40.0KB** | LVGL 内存池 | `Lib/lv_conf.h:34` `LV_MEM_SIZE (40*1024)` |
| 3 | ui_net_sink.o (map:16171) | 6.4KB | `g_tx[3209]` + `g_lcdmem_data[3204]`(PC 远程显示) | `ui_net_sink.c:31,33` |
| 4 | ui_port.o (map:16169) | 6.4KB | `g_ui_fbuf[3200]` + `g_ui_draw_buf[3200]`(160×160 1bpp 全帧×2) | `ui_port.c:16,18` |
| 5 | nand_ftl.o (map:16160) | 6.1KB | 3×2048B 页缓冲(page/cache/copy)+ dhara | `nand_ftl.c:42-50` |
| 6 | ch395f.o (map:16155) | 4.1KB | SPI DMA 发/收缓冲 2×(1024×2+4) | `ch395f.h:119,137`、`ch395f.c:42,44` |
| 7 | gd5f2gq5ue.o (map:16156) | 2.6KB | BBT 页缓冲 2048 + BBT/best 位图 2×256 | `gd5f2gq5ue.c:28-39` |
| 8 | cmsis_os2.o (map:16165) | 1.7KB | CMSIS-RTOS2 封装静态区 | — |
| 9 | tasks.o (map:16163) | 1.2KB | 内核任务管理静态区 | — |
| 10 | 其余(HAL/queue/usart/lftpd/rs485/…) | ~4KB | 小项若干 | — |
| 11 | startup HEAP+STACK (map:16173-16174) | 1.5KB | C 库 512B 堆 + 1KB 主栈 | scatter |
**静态合计 125600B(122.7KB)/ 128KB**(map:16118 `Size: 0x0001ec78`,Max `0x00020000`)。
#### 2.2.1 逐符号明细(RW_IRAM1,2026-09-21 当前)
按大小降序(完整台账:`data/ram_report_20260921.md`):
**RW_IRAM1(主 SRAM)**,按 ZI/RW 合计降序:
| # | 大小 (B) | 对象文件 | 内容 |
|---|---|---|---|
@@ -73,10 +56,14 @@
| 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.4 KB | 约 30 个 .o | 小型 RW/ZI |
| 其余 | ~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。
**静态合计(2026-09-21,map Grand Totals)**:RW_IRAM1 **82,048 B(80.1 KB / 128 KB,62.6%)** + RW_CCM **49,184 B(75.0% / 64 KB)** = **131,232 B(RW+ZI)**。
> **DMA 可达性**:第 4/6/7 项(NAND FTL、CH395F SPI2、GD5F BBT)为 DMA 缓冲,**必须留在主 SRAM**、禁止迁 CCM/SRAM3——权威清点与规则见 `内存内容分配策略.md`(本文不重复)。
> 2026-09-11 快照(含 `heap_4.o` 49,152 B 在 SRAM1 的历史布局)见 `data/ram_report_20260911.md`;堆已于 2026-09-21 迁至 CCM(§1.2)。
> 2026-09-11 历史排行(`heap_4.o` 49,152 B 尚在 SRAM1、静态合计 122.7 KB 的旧布局)见 `data/ram_report_20260911.md`,仅供回溯。
### 2.3 任务栈(从 48KB 堆动态分配)
@@ -99,7 +86,7 @@
### 2.4 动态分配点与隐患
1. **lftpd 用 C 库 `malloc`(不是 FreeRTOS 堆)**:`Drivers/BSP/NET/lftpd/lftpd_io.c:44,49,54,189` 调用 `malloc()`。工程没有把 C 库 malloc 重定向到 `pvPortMalloc`,C 库堆只有 startup 的 **512B**(map:16173)→ FTP 路径名稍长(>250 字符左右)就必然分配失败。**必须修**(见 4.4)。
1. **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` 残留。
2. **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+ 栈。
3. **DMA 缓冲位置**:CH395F(SPI2 DMA)、GD5F(SPI1 DMA)、UART DMA 缓冲必须由 DMA 访问 → **只能放主 SRAM,不能放 CCM**(CCM 仅 CPU 可达,见 4.3)。
@@ -108,7 +95,7 @@
| 根因 | 占用 | 可回收性 |
|---|---|---|
| ① LVGL 内存池 40KB 对 1bpp 小屏菜单过大 | 40KB | 高(目标 8~16KB;**开机实测 UI 仅用 ~3KB**,见 §2.6.2) |
| ② 任务栈"往大了给",无实测依据 | ~39KB | 高(§0.6 已有实测:uiTask 3.8/16KB、netTask 0.6/4KB) |
| ② 任务栈"往大了给",无实测依据 | 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):
@@ -118,7 +105,7 @@
- 未映射资源:SRAM3 64KB(scatter 未映射;clone 芯片可能无此区);CCM 已用
- 曾经的三条中风险项:新任务无处安置(已缓解)、堆碎片(仍存)、栈溢出检测缺失 → **已于阶段 0 开启 `configCHECK_FOR_STACK_OVERFLOW=2` 并实测抓到过溢出**(§0.1)
小项:FF_FS_TINY=0(每打开文件 512B 扇区窗)、ui_net_sink 调试缓冲常驻(lftpd 栈局部变量 ✅ 2026-09-21 已静态化,见 §1.4)。
小项: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 明细
@@ -130,7 +117,7 @@
|---|---|---|
| 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` |
| 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 …` |
@@ -178,7 +165,7 @@
| LVGL 缓冲 | 官方文档:PARTIAL 渲染模式"buffers can be smaller than the display to save RAM,**至少 1/10 屏**";单色 I1 格式缓冲 = `(W×H/8)+8`;双缓冲可并行渲染/搬运 | LVGL v9.2 文档 [Display interface](https://github.com/lvgl/lvgl/blob/release/v9.2/docs/porting/display.rst)(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:16179-16419` |
| 静态占用测量 | 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 注释)三方交叉核实。
@@ -315,7 +302,7 @@ void vApplicationMallocFailedHook(void)
`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_append` 704 B(FIL 512 + 记录 64 + 时间戳)、`StartDefaultTask` 560 B(探针 `FIL __chk`)、`cfg_store_read`/`cfg_store_write_atomic` 576 B、LVGL 绘制 624/560/400 B——与 §1.4 的整改项对得上。**lftpd 三项已于 2026-09-21 静态化(§1.4),已退出帧宽榜前 15**(原 `handle_control_channel` 1104 B、`receive_file`/`send_file` 1088 B、`send_response` 800 B)。
- **静态分析的天然盲区**:递归/互递归(LVGL widget 树、`layout_update_core`、`apply_theme` 等)与间接调用只能按 "Cycle" 近似,armlink 明确标注 `+ Unknown(Cycles, Untraceable Function Pointers)`。因此 uiTask 的 912 B 是**下限**(实测 3804 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/ftp_speed_test.py --size 128`(STOR 128KB + RETR + 逐字节比对),再按上面命令叠加串口日志出报告。**不要用裸 TCP 工具探测 21 端口**——CH395F 单客户端复用,探测会占掉唯一会话(见 FTP 指南 §8.9)。
@@ -335,10 +322,12 @@ void vApplicationMallocFailedHook(void)
- 验证:UI 全菜单遍历 + 动画/滚动/输入框 + 中文字体渲染无缺字;LVGL 内置内存监控(`LV_USE_MEM_MONITOR`,调试构建开启)观察池占用峰值。
- 风险:个别复杂控件/长字符串可能吃紧 → 若 8KB 不够就停在 16KB,不勉强。
#### 1.2 启用 CCM RAM(+64KB 预算空间,迁出 ~29KB)
#### 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.0%)。下表缓冲迁入清单为**后续剩余项**(LVGL 池/UI 缓冲现仍在主 SRAM);`APP_CCM_ENABLE` 门禁宏**尚未实现**(scatter 无开关,见下"风险门禁")。
**迁入 CCM 的缓冲(全部纯 CPU 读写):**
| 缓冲 | 大小 | 方式 |
@@ -353,16 +342,16 @@ void vApplicationMallocFailedHook(void)
**scatter 文件**(`MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct`)增加:
```
RW_IRAM2 0x10000000 0x00010000 { ; CCM 64KB, CPU-only
RW_CCM 0x10000000 0x00010000 { ; CCM 64KB, CPU-only(2026-09-21 实际落地区名)
*(CCM_DATA)
}
```
**风险门禁**:scatter 注释已提示"clone MCU 可能缺 SRAM3",同理 CCM 在克隆芯片上不可靠 → 加编译开关 `APP_CCM_ENABLE`(`App/app_cfg.h`),关闭时 `LV_MEM_ATTR` 等定义退化为空,缓冲全部落回主 SRAM(阶段 1.1 池缩到 16KB 后主 SRAM 也放得下)。真 ST 芯片默认开。
验证:map 中出现 `RW_IRAM2` 且 `*(CCM_DATA)` 各对象地址落在 0x10000000~0x100FFFF;`ram_report.ps1` 同时输出两个 Region 水位。
验证:map 中出现 `RW_CCM` 且 `*(CCM_DATA)` 各对象地址落在 0x10000000~0x100FFFF;`ram_report.py` 同时输出两个 Region 水位。
#### 1.3 任务栈按实测数据缩减(省 ~15KB,需阶段 0 数据)
#### 1.3 任务栈按实测数据缩减(省 ~17KB;目标值已按 2026-09-21 实测修正)
定栈规则(写入台账,长期执行):
@@ -372,16 +361,18 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
统一按 +1KB 计;中断执行在当前任务栈上,必须计入。
```
初始目标(待阶段 0 实测修正,先按此改一轮再复测):
目标值(2026-09-21 实测数据修正):
| 任务 | 现值 | 目标 | 依据(实测后修正) |
| 任务 | 现值 | 目标 | 依据(max(最坏×1.5, 1024B) + 1KB ISR 余量) |
|---|---|---|---|
| uiTask | 16KB | 6KB | LVGL 渲染走静态缓冲,栈只走调用链;1bpp 绘制函数浅 |
| netTask | 4KB | 2KB | SPI DMA 搬运,栈压力小 |
| ftpTask | 4KB | 3KB | lftpd 有大局部变量,1.4 改静态后预期可降到 2KB |
| testTask | 4KB | 1KB | 产品构建空转;测试构建单独放大(测试模式允许临时加大) |
| defaultTask | 2KB | 1KB | 仅 f_mount + GPIO |
| timeSyncTask | 2KB | 1KB | 小报文解析 |
| 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%),不满足则回调。
@@ -430,35 +421,38 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
### 阶段 3:长效机制(0.5 天 + 长期)
1. **RAM 预算台账 `[计划]`**(模板见 §8.2;落地文件 `docs/内存管理/RAM预算台账.md` **尚未创建**):记录每个 Region/缓冲/栈的计划值、实测值、变更历史;每次改内存相关代码必须更新
2. **AGENTS.md 增补**:新增任务/缓冲/栈前必须先查台账余量(把现有"内存预算紧张"条目升级为可执行 checklist:先跑 `ram_report.ps1`,算总账,再动手)
3. **pre-commit 门禁**(可选):钩子解析最新 map,RW_IRAM1 使用率 >92% 或 RW_IRAM2 超限 → 提交拦截并提示
2. **AGENTS.md 增补**(✅ 2026-09-21 已完成:"内存预算紧张"条目已含 `ram_report.py`/`stack_report.py` 数据源与"改栈前后各跑一次"要求)
3. **pre-commit 门禁**(可选):钩子解析最新 map,RW_IRAM1 使用率 >92% 或 RW_CCM 超限 → 提交拦截并提示
4. **@test 扩展**:`verify_log.py` 增加可选 Required 标记 `"[MEM] heap free"`,把堆余量下限纳入自动化回归
5. **map diff 常态化**:每次 `@build` 后跑 `ram_report.ps1`,ZI 增量 >1KB 的改动必须在提交说明中解释
5. **map diff 常态化**:每次 `@build` 后跑 `ram_report.py`,ZI 增量 >1KB 的改动必须在提交说明中解释
## 5. 预期效果
| 项目 | 现状 | 优化后(1.1+1.2+1.3+2.2 全部落地) |
| 项目 | 现状(2026-09-21 实测) | 剩余项全部落地后(1.1/1.3/1.5/2.2) |
|---|---|---|
| 主 SRAM 静态占用 | 122.7KB / 128KB | **~71KB / 128KB**(LVGL 池、UI 缓冲迁 CCM,堆 48K→32K) |
| 主 SRAM 余量 | 5.3KB | **~57KB** |
| CCM 使用 | 0 / 64KB | ~29KB / 64KB(余 ~35KB) |
| 任务栈合计 | 32KB | ~15KB |
| FreeRTOS 堆 | 48KB(余 ~12KB) | 32KB(余 ≥8KB,全负载验证) |
| **总余量** | **~17KB** | **~90KB+** |
| 主 SRAM 静态占用 | 82,048 B / 128KB(62.6%) | **~27KB / 128KB**(LVGL 池 + UI 缓冲迁 CCM、`ui_net_sink` 关) |
| 主 SRAM 余量 | 49,024 B(47.9KB) | **~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(溢出钩子 + mem_stats 模块,加进 Keil 工程) | `@build` 0错0警;`@test -Required "[MEM]"` 串口出现任务报告 |
| S2 | 0.3 `ram_report.ps1` + 基线 | 脚本输出与 map 人工核对一致 |
| S3 | 全功能回归跑数(菜单/FTP/网络/格式化/浸泡 1h) | 拿到各任务 `free_min` 与堆 free 底值 |
| S4 | 1.4 lftpd 静态化 + 2.1 malloc 修复 | FTP STOR/RETR 大文件 + 长路径回归 |
| S5 | 1.1 LVGL 池 40K→16K | UI 全量回归(模拟器 + 实机),无断言失败 |
| S6 | 1.2 CCM(scatter + section + 门禁宏) | map 显示 RW_IRAM2;关 `APP_CCM_ENABLE` 也能编译通过 |
| S7 | 1.3 栈缩减(按 S3 数据)+ 复测 | 每任务 `free_min ≥ 新栈×30%`,无溢出钩子触发 |
| S8 | 2.2 堆 48K→32K + 复测 | 全负载 `heap free ≥ 8KB` |
| S9 | 1.5 小项 + S10 台账/门禁/AGENTS.md | `@build` + `@test` 全绿 |
| 步骤 | 内容 | 验证 | 状态 |
|---|---|---|---|
| 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` 先过一遍再上实机。
@@ -493,7 +487,7 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
| FreeRTOS 堆 | 32KB | | 6 任务栈+TCB+队列+lftpd 临时 | |
| └ 任务栈小计 | ~15KB | | 见 freertos.c 属性表 | |
| └ 堆余量底线 | ≥8KB | | 全负载 free 底值 | |
| RW_IRAM2(CCM) | ≤32KB | | LVGL 池、UI 帧缓冲、BBT 位图 | |
| RW_CCM(CCM) | ≤32KB | | LVGL 池、UI 帧缓冲、BBT 位图(2026-09-21 起先承载 FreeRTOS 堆) | |
| C 库堆/栈 | 512B/1KB | | 仅启动期;lftpd 不得再用 | |
```
@@ -512,7 +506,7 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
| 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:44-54,189`(`malloc` 隐患,阶段 2.1) |
| lftpd 缓冲/malloc | `Drivers/BSP/NET/lftpd/lftpd.c`(大缓冲已静态化,§1.4)、`lftpd_io.c`(`malloc`→`pvPortMalloc` 已修,阶段 2.1) |
### 8.4 分析口径与数据来源(原《内存使用分析规划》并入)
@@ -550,13 +544,13 @@ V4 逐项相加须与 V1 的 `Image component sizes` 逐 .o 吻合;不等则
产物:`docs/images/内存布局_RAM.svg`、`内存布局_FLASH.svg`、`任务栈预算.svg`(脚本与 SVG 一并提交;PNG 本地预览不提交)。
### 8.6 自洽校验(2026-09-11 快照)
### 8.6 自洽校验(2026-09-21 快照)
| 层 | 指标 | Per-file 求和 | Grand Totals | 判定 |
|---|---|---|---|---|
| Flash | Code | 219,976 | 219,976 | PASS |
| Flash | RO | 11,496 | 11,496 | PASS |
| SRAM | RW | 528 | 528 | PASS |
| SRAM | ZI | 124,576 | 124,576 | PASS |
| 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 |
> 差异来自 linker 的 `(incl. Padding)` 行(约 302 B code + 64 B RO + 8 B RW + 2 B ZI);`scripts/ram_report.py` 已排除该行,故 per-file 求和与 Grand Totals 对不上时先查它。
> 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` 末尾自洽校验行)。