内存管理文档4→2合并:报告/规划并入主文档,lftpd静态化降栈峰值,同步架构/FTP/部署文档

This commit is contained in:
2026-09-21 04:37:27 +08:00
parent bbdd059e25
commit 57632e910a
14 changed files with 394 additions and 626 deletions
+1 -1
View File
@@ -56,7 +56,7 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v
| uiTask | 4096 | BelowNormal |
| timeSyncTask | 512 | Normal |
(实际值以 `Src/freertos.c` 为准;testTask 仅在启用测试套件时创建。改动栈/缓冲前先核对此表与实测。2026-09-21 上调:defaultTask 512→1024(扛 mount/mkfs/FAT探针/cfg/syslog 深层 FatFS I/O,旧值经 `configCHECK_FOR_STACK_OVERFLOW=2` 钩子实测溢出)、testTask 1024→2048(存储套件 deep call chain + 局部缓冲溢出)。FreeRTOS 堆已迁 CCM,任务栈合计已不占 SRAM;2026-09-21 可观测性落地后实测:**ftpTask 峰值 3480B/4096B(余量仅 616B = 15%,低于 1024B 底线)**,处置见 `docs/内存管理/RAM使用分析与优化方案.md` §0.6——建议先做 §1.4(lftpd 大局部变量静态化)再定栈)
(实际值以 `Src/freertos.c` 为准;testTask 仅在启用测试套件时创建。改动栈/缓冲前先核对此表与实测。2026-09-21 上调:defaultTask 512→1024(扛 mount/mkfs/FAT探针/cfg/syslog 深层 FatFS I/O,旧值经 `configCHECK_FOR_STACK_OVERFLOW=2` 钩子实测溢出)、testTask 1024→2048(存储套件 deep call chain + 局部缓冲溢出)。FreeRTOS 堆已迁 CCM,任务栈合计已不占 SRAM;2026-09-21 可观测性落地后实测:ftpTask 峰值曾达 3480B/4096B(余量 15%,低于 1024B 底线)→ 已按 `docs/内存管理/RAM使用分析与优化方案.md` §1.4 把 lftpd 9 处大局部缓冲静态化(BSS +4.6KB),复测峰值 **1184B/4096B(余量 71%)**,6 任务全 PASS)
## 架构要点
+9 -9
View File
@@ -66,8 +66,8 @@ static int send_response(int socket, int code, bool include_code,
bool multiline_start, const char *format, ...)
{
va_list args;
char message[256];
char response[512];
static char message[256]; /* static: off-stack, single-client ftpTask */
static char response[512]; /* static: off-stack, single-client ftpTask */
va_start(args, format);
vsnprintf(message, sizeof(message), format, args);
@@ -108,7 +108,7 @@ static int send_list(int socket, const char *path)
return -1;
}
char name[256];
static char name[256]; /* static: off-stack, single-client ftpTask */
uint32_t size;
int is_dir;
@@ -133,7 +133,7 @@ static int send_nlst(int socket, const char *path)
return -1;
}
char name[256];
static char name[256]; /* static: off-stack, single-client ftpTask */
uint32_t size;
int is_dir;
@@ -155,7 +155,7 @@ static int send_file(int socket, const char *path)
return -1;
}
unsigned char buffer[1024];
static unsigned char buffer[1024]; /* static: 1KB off-stack, single-client ftpTask */
int read_len;
while ((read_len = lftpd_io_read(fh, buffer, sizeof(buffer))) > 0) {
@@ -184,7 +184,7 @@ static int receive_file(int socket, const char *path)
return -1;
}
unsigned char buffer[1024];
static unsigned char buffer[1024]; /* static: 1KB off-stack, single-client ftpTask */
int err;
int total = 0;
@@ -550,7 +550,7 @@ static int handle_control_channel(lftpd_client_t *client)
goto cleanup;
}
char read_buffer[512];
static char read_buffer[512]; /* static: off-stack, single-client ftpTask */
while (err == 0) {
int line_len = lftpd_inet_read_line(client->socket, read_buffer,
@@ -584,7 +584,7 @@ static int handle_control_channel(lftpd_client_t *client)
bool matched = false;
for (int i = 0; commands[i].command; i++) {
if (strcmp(commands[i].command, command_tmp) == 0) {
char arg_buf[512];
static char arg_buf[512]; /* static: off-stack, single-client ftpTask */
const char *arg = NULL;
if (index < (int)strlen(read_buffer)) {
@@ -649,7 +649,7 @@ int lftpd_start(const char *directory, int port, lftpd_t *lftpd)
}
}
lftpd_client_t client;
static lftpd_client_t client; /* static: off-stack session context (single session) */
memset(&client, 0, sizeof(client));
strncpy(client.directory, directory, sizeof(client.directory) - 1);
client.directory[sizeof(client.directory) - 1] = '\0';
+1 -1
View File
@@ -6,7 +6,7 @@
void lftpd_log_internal(const char* level, const char* format, ...) {
char buffer[256];
static char buffer[256]; /* static: called at depth in ftpTask, keep off-stack */
va_list args;
va_start(args, format);
int err = vsnprintf(buffer, sizeof(buffer), format, args);
+1 -1
View File
@@ -772,7 +772,7 @@ ESC/LEFT : d>0 → menu_back() (根框 d=0 不响
| **小计** | | **~0.35 KB**(逻辑层 17 B + 显示层 ~0.33 KB) |
**LVGL 池(动态,`LV_MEM` 40 KB 预算内)**:行自绘无对象,池只占**屏上框体**(2~3 个 `lv_obj`)+ 根框装饰/状态行,
典型 ≈ **1~2 KB**,仍远小于 40 KB(池可再缩减,见 `docs/待处理内容/RAM使用分析与优化方案.md`)。
典型 ≈ **1~2 KB**,仍远小于 40 KB(池可再缩减,见 `docs/内存管理/RAM使用分析与优化方案.md` §1.1;开机实测 UI 池仅用约 3 KB)。
---
@@ -1,25 +1,29 @@
# STM32F4-Base RAM 使用分析与优化方案
# STM32F4-Base RAM 使用分析与优化方案(内存管理主文档)
> 版本:v1.0 日期:2026-09-07
> 版本:v1.1 日期:2026-09-21
> 适用工程:STM32F4-Base(STM32F407ZGTx @168MHz,FreeRTOS V10.3.1 + CMSIS-RTOS2)
> 数据来源:`MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`(2026-09-07 构建)+ 源码静态分析 + 网络调研
> 数据来源:`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` 已提出:"所有任务的内存都是往大的给的,导致内存不够用,要仔细排查内存的预算情况"。
当前状态(详见第 2 节):
分析起点(2026-09-11 快照,逐符号见 §2.2.1):
- 主 SRAM 128KB 中静态区已占 **122.7KB**(余量仅 5.3KB)
- FreeRTOS 堆 48KB 里还要装下 **~33.5KB 的任务栈**,真正可分配余量只有 ~12KB
- F407 另有 **64KB CCM RAM 完全未用**
- 没有任何运行时测量手段:不知道每个任务实际用了多少栈,改栈全靠拍脑袋
- 主 SRAM 128KB 中静态区曾占 **122.7KB**,余量仅 5.3KB
- FreeRTOS 堆 48KB 里装下 ~33.5KB 任务栈,可分配余量只有 ~12KB
- F407 另有 **64KB CCM RAM**:**已于 2026-09-21 启用**并把 FreeRTOS 堆迁入(§1.2)
- 起步时没有任何运行时测量手段:不知道每个任务实际用了多少栈,改栈全靠拍脑袋
当前状态(2026-09-21,`data/ram_report_20260921.md` + `data/stack_report_20260921.md`):RW_IRAM1 **82,048 / 131,072 B(62.6%)**、RW_CCM **49,184 / 65,536 B(75.0%)**、6 个任务栈全部 PASS(ftpTask 余量 71%,§0.6)。
本方案三个目标:
1. **可测量**:建立"每任务 RAM 消耗 + 每模块静态占用 + 堆余量"的常态化观测手段
2. **降消耗**:通过 LVGL 内存池、任务栈、CCM RAM、驱动缓冲等手段释放 RAM,把余量从 ~17KB 提升到 ~90KB+
3. **可持续**:建立 RAM 预算台账与门禁,防止再次"往大了给"
1. **可测量**:建立"每任务 RAM 消耗 + 每模块静态占用 + 堆余量"的常态化观测手段(§0.1~§0.6,已落地)
2. **降消耗**:通过 LVGL 内存池、任务栈、CCM RAM、驱动缓冲等手段释放 RAM
3. **可持续**:建立 RAM 预算台账与门禁,防止再次"往大了给"(§3 长效机制,部分为 `[计划]`)
## 2. 现状测量
@@ -50,38 +54,118 @@
**静态合计 125600B(122.7KB)/ 128KB**(map:16118 `Size: 0x0001ec78`,Max `0x00020000`)。
#### 2.2.1 逐符号明细(RW_IRAM1,2026-09-21 当前)
按大小降序(完整台账:`data/ram_report_20260921.md`):
| # | 大小 (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.4 KB | 约 30 个 .o | 小型 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)。
### 2.3 任务栈(从 48KB 堆动态分配)
`Src/freertos.c` 中 `osThreadAttr_t.stack_size` 单位为**字节**(CubeMX 生成代码,`512 * 4` 表示 2048 字节):
![任务栈预算](../images/任务栈预算.svg)
| 任务 | 配置栈 (freertos.c 行号) | 字节 | 实际职责 | 问题 |
`Src/freertos.c` 中 `osThreadAttr_t.stack_size` 单位为**字节**(CubeMX 生成代码,`1024 * 4` 表示 4096 字节):
| 任务 | 配置栈 | 字节 | 实际职责 | 2026-09-21 实测峰值(§0.6) |
|---|---|---|---|---|
| uiTask | `4096*4` (:99) | 16KB | LVGL 1bpp 离屏渲染(渲染缓冲在静态区,栈只走调用链) | 明显过大,无实测依据 |
| netTask | `1024*4` (:78) | 4KB | CH395F 轮询(DMA 搬运,栈压力小) | 偏大 |
| ftpTask | `1024*4` (:84) | 4KB | lftpd(有大栈局部变量,见 2.4) | 4KB 接近其峰值,需实测 |
| testTask | `1024*4` (:90) | 4KB | 产品构建下空转 | 产品构建可减到 1KB |
| defaultTask | `512*4` (:71) | 2KB | FatFS 挂载 + LED 闪烁 | 偏大 |
| timeSyncTask | `512*4` (:63) | 2KB | TCP 时间同步 | 偏大 |
| idle | `configMINIMAL_STACK_SIZE=128` | 0.5KB | 空闲 | 正常 |
| timer | `configTIMER_TASK_STACK_DEPTH=256` | 1KB | 软定时器 | 正常 |
| 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 | 软定时器 | 未观测 |
**6 个应用任务栈合计 32KB,加 idle+timer 共 ~33.5KB,全部从 48KB 堆里扣** → 堆的真实余量 ≈ 48 − 33.5 − TCB(含 FPU 上下文 ~2.3KB) − 队列/事件组(~0.5KB) ≈ **11~12KB**。
> 任务栈全部从 FreeRTOS 堆(48KB,2026-09-21 已迁 CCM)分配。**"配置栈"列以 `Src/freertos.c` 为唯一来源;实测峰值/余量判定以 §0.6 的 `stack_report` 基线为准**(本表数值同步自该基线)。
### 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)。
2. **lftpd 大栈局部变量**:`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])。单次调用链栈峰值可达 2~3KB,是 ftpTask 需要 4KB 栈的主因。
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)。
### 2.5 结论:紧张的三个根因
### 2.5 结论:紧张的三个根因(含 2026-09-11 审计量化)
| 根因 | 占用 | 可回收性 |
|---|---|---|
| ① LVGL 内存池 40KB 对 1bpp 小屏菜单过大 | 40KB | 高(目标 8~16KB) |
| ② 任务栈"往大了给",32KB 无实测依据 | 32KB | 高(目标 ~15KB) |
| ③ 64KB CCM 未利用,UI 类大缓冲挤在主 SRAM | — | 高(可整体迁 CCM ~29KB) |
| ① LVGL 内存池 40KB 对 1bpp 小屏菜单过大 | 40KB | 高(目标 8~16KB;**开机实测 UI 仅用 ~3KB**,见 §2.6.2) |
| ② 任务栈"往大了给",无实测依据 | ~39KB | 高(§0.6 已有实测:uiTask 3.8/16KB、netTask 0.6/4KB) |
| ③ 64KB CCM 曾完全未利用,缓冲挤在主 SRAM | — | ✅ 2026-09-21 已启用(堆迁入 CCM),CCM 余量 ~16KB |
另有小项:FF_FS_TINY=0(每打开文件 512B 扇区窗)、ui_net_sink 调试缓冲常驻、lftpd 栈局部变量。
量化补充(来源:2026-09-11 审计快照,口径与方法见 §8.4):
- 两池(FreeRTOS 48KB + LVGL 40KB)曾占主 SRAM 的 **68%**,是当时"SRAM 几乎耗尽(95.4%,余 5.8KB)"的直接原因;**堆迁 CCM 后该结构性压力已解除**(现 RW_IRAM1 62.6%)
- 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 扇区窗)、ui_net_sink 调试缓冲常驻(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 元数据开销约 10~15%,净可用 ≈34~36 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 分布(2026-09-21)
| 段 | 大小 (B) | 占比 |
|---|---|---|
| Code (inc. data) | 234,976 | 22.4% |
| RO Data | 13,824 | 1.3% |
| RW 初值 | 632 | — |
| **合计 ROM** | **249,432** | **23.8%**(余 ~780 KB) |
![Flash 布局](../images/内存布局_FLASH.svg)
> 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. 网络调研摘要
@@ -208,22 +292,21 @@ void vApplicationMallocFailedHook(void)
| 任务 | 配置 (B) | 静态最坏 (B) | 实测峰值 (B) | 最坏 (B) | 余量 | 判定 |
|---|---|---|---|---|---|---|
| defaultTask | 4096 | **2776** | 2136 | 2776 | 1320 (32%) | PASS |
| **ftpTask** | 4096 | 2528 | **3480** | **3480** | **616 (15%)** | **FAIL** |
| netTask | 4096 | 440 | 600 | 600 | 3496 (85%) | PASS |
| uiTask | 16384 | 912 | 3804 | 3804 | 12580 (76%) | 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 池)。
**待办(由本次实测直接得出)**:ftpTask 余量仅 616 B(15%),低于 1024 B / 30% 底线。两条路:
① 按 §1.3 定栈规则 `峰值×1.5 + 1KB ≈ 6.2KB` → ftpTask 提到 **1600 words (6400 B)**(最省事);
② 先做 §1.4(lftpd 大局部变量移静态:`handle_control_channel` 1104 B、`send_file`/`receive_file` 1088 B、`send_response` 800 B)再复测,栈只需小步上调。建议 **②→①** 顺序:先砍宽度,避免长期背 6.4KB。
**处置结果(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 传输时暴露 → **不跑真实业务就定栈 = 拍脑袋。**
- 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。
要点与边界:
@@ -231,7 +314,7 @@ void vApplicationMallocFailedHook(void)
- 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 实测完全一致。
- 帧宽榜直接点名宽度大户(自身帧):`handle_control_channel` 1104 B、`receive_file`/`send_file` 1088 B、`send_response` 800 B、**`s_append` 704 B**(FIL 512 + 记录 64 + 时间戳)、**`StartDefaultTask` 560 B**(探针 `FIL __chk`)、`cfg_store_read`/`cfg_store_write_atomic` 576 B——与 §1.3/§1.4 的整改项一一对得上。
- 帧宽榜直接点名宽度大户(自身帧):`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),必须靠运行时水位。
- 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)。
@@ -302,11 +385,25 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
每改一批 → `@build` + 全功能回归 + 复测高水位(`free_min` 应 ≥ 新栈×30%),不满足则回调。
#### 1.4 lftpd 大栈局部变量 → 静态缓冲(ftpTask 栈峰值降 1~2KB)
#### 1.4 lftpd 大栈局部变量 → 静态缓冲(✅ 2026-09-21 完成;ftpTask 峰值 3480 → 1184 B)
`lftpd.c` 中 `buffer[1024]`×2(:156,185)、`response[512]`(:68)等改为文件级 `static`(lftpd 单客户端、由 ftpTask 串行处理,静态化安全)。
已把 `lftpd.c` / `lftpd_log.c` 中 **9 个大局部缓冲改为函数级 `static`**(lftpd 单客户端,全部调用与日志都在 ftpTask 内串行执行,静态化安全;各函数**各自独立缓冲**,无嵌套/别名冲突):
> lftpd 属第三方代码(pre-commit 排除范围),只做最小侵入改动,改动点在本台账登记。
| 位置 | 原栈占用 (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。
- **代价**:静态 BSS **+4,616 B**(RW_IRAM1 77,432 → 82,048 B / 128 KB,余量仍 ~48 KB;CCM 与 Flash 不变)。
- **验证**:`@build` 0/0 → 烧录 → `@test` PASS → `ftp_up_dl_test.py` 10/10 PASS + `ftp_speed_test.py --size 128` 逐字节一致(功能与搬迁前完全一致,反证无别名/重入问题)。
- **结论:原 ① 方案(ftpTask 涨到 1600 words)不再需要**,栈保持 1024 words(4096 B)。
> lftpd 属第三方代码(pre-commit 排除范围),只做最小侵入改动(每处仅加 `static` + 单行注释),改动点在本台账登记。
#### 1.5 其他小项
@@ -332,7 +429,7 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
### 阶段 3:长效机制(0.5 天 + 长期)
1. **RAM 预算台账**(本节模板落地为 `docs/内存管理/RAM预算台账.md`):记录每个 Region/缓冲/栈的计划值、实测值、责任人、变更历史;每次改内存相关代码必须更新
1. **RAM 预算台账 `[计划]`**(模板见 §8.2;落地文件 `docs/内存管理/RAM预算台账.md` **尚未创建**):记录每个 Region/缓冲/栈的计划值、实测值、变更历史;每次改内存相关代码必须更新
2. **AGENTS.md 增补**:新增任务/缓冲/栈前必须先查台账余量(把现有"内存预算紧张"条目升级为可执行 checklist:先跑 `ram_report.ps1`,算总账,再动手)
3. **pre-commit 门禁**(可选):钩子解析最新 map,RW_IRAM1 使用率 >92% 或 RW_IRAM2 超限 → 提交拦截并提示
4. **@test 扩展**:`verify_log.py` 增加可选 Required 标记 `"[MEM] heap free"`,把堆余量下限纳入自动化回归
@@ -415,4 +512,51 @@ 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:67-185,551,583`、`lftpd_io.c:44-54,189` |
| lftpd 缓冲/malloc | `Drivers/BSP/NET/lftpd/lftpd.c`(大缓冲已静态化,§1.4)、`lftpd_io.c:44-54,189`(`malloc` 隐患,阶段 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_cfg.h` + `test/test_config.h`(构建口径)、`Drivers/BSP/**` + `App/**`(缓冲语义/用途/DMA 归属);**权威数据 = `@build` 重建后的 `.map`**(历史 map 不得用于结论)。
### 8.5 图表与台账重生成
```powershell
& '.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-11 快照)
| 层 | 指标 | 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 |
> 差异来自 linker 的 `(incl. Padding)` 行(约 302 B code + 64 B RO + 8 B RW + 2 B ZI);`scripts/ram_report.py` 已排除该行,故 per-file 求和与 Grand Totals 对不上时先查它。
@@ -1,366 +0,0 @@
# STM32F4-Base 内存使用分析报告
> **版本**: v1.0 **日期**: 2026-09-11
> **基线**: `02a32be` (ui-research 分支)
> **Map**: `MDK-ARM/STM32F407-Demo/STM32F407-Demo.map` (2026-09-11 23:12:55)
> **构建**: 0 errors, 0 warnings
---
## 1. 摘要
STM32F407ZGTx 采用 **128KB 主 SRAM**(SRAM1+SRAM2)和 **1MB Flash**。本报告对两者进行逐层审计。
**核心指标**
| 资源 | 已用 | 总量 | 利用率 | 空闲 |
|------|------|------|--------|------|
| **SRAM** (RW+ZI) | 125,104 B (122.2 KB) | 131,072 B (128 KB) | **95.4%** | 5,968 B (5.8 KB) |
| **Flash** (Code+RO+RW) | 231,472 B (226 KB) | 1,048,576 B (1 MB) | **22.1%** | 817,104 B (798 KB) |
| **FreeRTOS 堆** (48KB 池) | ~35,416 B (34.6 KB) | 49,152 B (48 KB) | **72.1%** | ~13,736 B (13.4 KB) |
| **LVGL 池** (40KB 静态) | 40,960 B (40 KB) — 容量 | 40,960 B | **100%** (全保留) | 运行时未量化 |
> ⚠ **SRAM 利用率 95.4%,剩余仅 5.8KB。** 空闲 5,968B 无法容纳新的大型静态缓冲或任务栈。新增任何 RAM 需求前必须先释放或裁减现有分配。
---
## 2. 内存资源总览
### 2.1 硬件内存布局
```
STM32F407ZGTx 内存地图
┌─────────────────────────────────────────────────────────────┐
│ Flash 1MB (0x0800_0000) │
│ ├─ Code 214.8 KB (22.1%) │
│ ├─ RO Data 11.2 KB │
│ └─ RW Init Data 0.5 KB │
├─────────────────────────────────────────────────────────────┤
│ SRAM1+2 = 128KB (0x2000_0000) ← 主 SRAM (RW_IRAM1) │
│ ├─ RW Data 528 B (初始化) │
│ ├─ ZI Data 124,576 B (零初始化) │
│ └─ 空闲 5,968 B (4.6%) │
├─────────────────────────────────────────────────────────────┤
│ SRAM3 64KB (0x2001_C000) ← 未使用 (scatter 未映射) │
│ CCM 64KB (0x1000_0000) ← 未使用 (scatter 未映射) │
└─────────────────────────────────────────────────────────────┘
```
> SRAM3 和 CCM 在 `.sct` 分散加载文件中未映射。若需扩展 RAM,可考虑将非 DMA 缓冲(如 LVGL 池)迁移至 SRAM3。
### 2.2 全局数据量
| 指标 | 字节 | KB | 来源 |
|------|------|----|------|
| Code (含 inc. data) | 219,976 | 214.8 | Grand Totals |
| RO Data | 11,496 | 11.2 | Grand Totals |
| RW Data (初始化) | 528 | 0.5 | Grand Totals |
| ZI Data (零初始化) | 124,576 | 121.7 | Grand Totals |
| **Total RO** | **231,472** | **226.0** | Code + RO |
| **Total RW** | **125,104** | **122.2** | RW + ZI |
| **Total ROM** | **232,000** | **226.6** | Code + RO + RW |
---
## 3. 主 SRAM (128KB) 静态分布
### 3.1 功能分区总览
![SRAM 布局图](../images/内存布局_RAM.svg)
| 分类 | 大小 (B) | KB | 占比 | 内容说明 |
|------|----------|----|------|----------|
| FreeRTOS 堆池 | 49,152 | 48.0 | 37.5% | `heap_4.o` — 任务栈 + TCB + 队列 |
| LVGL 内存池 | 40,960 | 40.0 | 31.2% | `lv_mem_core_builtin.o` — UI 对象/样式 |
| UI 帧缓冲 | ~13,158 | ~12.8 | 10.1% | 帧缓冲 2×3200 + RemoDispBus 6477 + 状态 |
| FTL / 存储 | ~12,416 | ~12.1 | 9.5% | NAND FTL 页缓冲 + GD5F BBT |
| 网络 / CH395F | ~7,110 | ~6.9 | 5.5% | SPI2 DMA 缓冲 + Socket 控制块 |
| 内核 / CMSIS | ~3,024 | ~2.9 | 2.3% | tasks.o + cmsis_os2.o + timers.o |
| HAL 驱动 | ~1,400 | ~1.4 | 1.1% | SPI/USART/I2C/TIM 句柄 + DMA LG |
| 其他静态 | ~498 | ~0.5 | 0.4% | ADC 状态、RS485 缓冲等 |
| **已用合计** | **125,104** | **122.2** | **95.4%** | |
| **空闲** | **5,968** | **5.8** | **4.6%** | |
### 3.2 逐符号明细 (RW_IRAM1 Memory Map)
以下为 `.map` 文件中 RW_IRAM1 区域的逐符号条目,按大小降序。总计 125,094B(差 10B 为对齐填充)。
| # | 大小 (B) | 类型 | 对象文件 | 内容 |
|---|----------|------|----------|------|
| 1 | 49,152 | ZI | `heap_4.o` | `ucHeap[configTOTAL_HEAP_SIZE]` — FreeRTOS 堆池 |
| 2 | 40,960 | ZI | `lv_mem_core_builtin.o` | `ucMemPool[LV_MEM_SIZE]` — LVGL 内存池 |
| 3 | 6,746 | ZI | `ui_net_sink.o` | `g_tx[3209]` + `g_lcdmem_data[3204]` + `g_rx[64]` + 状态 |
| 4 | 6,400 | ZI | `ui_port.o` | `g_ui_fbuf[3200]` + `g_ui_draw_buf[3200]` — 160×160 1bpp 帧缓冲 |
| 5 | 6,200 | ZI | `nand_ftl.o` | `s_page_buf/s_cache_buf/s_copy_buf` 各 2048B + dhara 结构 |
| 6 | 4,104 | ZI | `ch395f.o` | `s_spi2_dma_tx_buf[2052]` + `s_spi2_dma_rx_buf[2052]` |
| 7 | 2,576 | ZI | `gd5f2gq5ue.o` | `s_bbt[256]` + `s_bbt_best[256]` + `s_bbt_page_buf[2048]` |
| 8 | 1,720 | ZI | `cmsis_os2.o` | CMSIS-RTOS2 封装静态区 |
| 9 | 1,220 | ZI | `tasks.o` | FreeRTOS 内核调度(就绪链表等) |
| 10 | 1,072 | ZI | `freertos.o` | `default_fatfs`(含 512B 扇区窗) + `fs_work[512]` + RTOS 句柄 |
| 11 | 1,024 | ZI | `startup_stm32f407xx.o` | C 库启动栈(1KB,仅启动期) |
| 12 | 560 | ZI | `spi.o` | `hspi1/hspi2` + `hdma_spi1/2 rx/tx` 句柄 |
| 13 | 556 | ZI | `lftpd_io.o` | `s_ftp_file` (FIL 含 512B 扇区窗) |
| 14 | 552 | ZI | `usart.o` | `huart1/uart4/uart5/uart2/uart3` + DMA 句柄 |
| 15 | 496 | ZI | `lv_init.o` | LVGL 全局状态 |
| 16 | 288 | ZI | `net_socket.o` | `s_net_socks[NET_MAX_SOCKETS]` |
| 17 | 280 | ZI | `timers.o` | FreeRTOS 软件定时器静态区 |
| 18 | 268 | ZI | `rs485.o` | RS-485 设备结构(`rx_buf[256]` + 状态) |
| 19–50 | 920 | RW/ZI | 其他 32 个 .o | 各模块初始化数据与小型 ZI |
### 3.3 关键 DMA 缓冲约束
以下缓冲被 SPI/DMA 引擎直接访问,**必须驻留在主 SRAM**(SRAM1/2,0x20000000–0x2001FFFF):
| 模块 | 缓冲 | 大小 | DMA 通道 |
|------|------|------|----------|
| CH395F | `s_spi2_dma_tx_buf` + `s_spi2_dma_rx_buf` | 2 × 2,052 = 4,104 B | SPI2 TX/RX DMA |
| NAND FTL | `s_page_buf` + `s_cache_buf` + `s_copy_buf` | 3 × 2,048 = 6,144 B | SPI1 TX/RX DMA |
| GD5F BBT | `s_bbt_page_buf` | 2,048 B | SPI1 RX DMA |
> **迁移警告**:任何尝试将上述缓冲移至 SRAM3 或 CCM 的操作都会导致 DMA 传输失败。
---
## 4. FreeRTOS 堆 48KB 内部拆解
`configTOTAL_HEAP_SIZE = 49,152` (48KB),heap_4 分配器。
![任务栈预算](../images/任务栈预算.svg)
### 4.1 任务栈分配
> 注:栈大小在 `osThreadAttr_t.stack_size` 中以**字节**为单位;CMSIS-RTOS2 在 `cmsis_os2.c:472–476` 将其除以 `sizeof(StackType_t)=4` 转为 words。
| 任务 | stack_size (B) | stack_size (words) | 优先级 | 功能 |
|------|----------------|---------------------|--------|------|
| defaultTask | 2,048 | 512 | Normal | FatFS 挂载、LED 闪烁 |
| netTask | 4,096 | 1,024 | Normal | CH395F 轮询、消息处理 |
| ftpTask | 4,096 | 1,024 | Normal | lftpd FTP 服务(含 1KB 栈局部 buffer[1024]) |
| testTask | 4,096 | 1,024 | BelowNormal | 测试套件(产品模式下空转) |
| uiTask | 16,384 | 4,096 | BelowNormal | LVGL UI 主循环 |
| timeSyncTask | 2,048 | 512 | Normal | TCP 墙钟时间同步 |
| idle (内核) | 512 | 128 | 0 (最低) | FreeRTOS 空闲任务 |
| timer (内核) | 1,024 | 256 | 31 (最高) | FreeRTOS 软件定时器 |
| **合计** | **34,304** | **8,576** | | |
### 4.2 堆内存消耗明细
| 组件 | 大小 (B) | 计算方法 |
|------|----------|----------|
| 8 × 任务栈 | 34,304 | 各任务 stack_size 求和 |
| 8 × TCB (任务控制块) | ~640 | sizeof(TCB_t) ≈ 76B(8字节对齐后 80B × 8) |
| netMsgQueue | ~448 | `osMessageQueueNew(8, 48)` = Queue_t(56B) + 8×48B |
| sys_evt 事件组 | ~24 | `osEventFlagsNew` = EventGroup_t |
| **稳态合计** | **~35,416** | |
| **堆空闲** | **~13,736** | 49,152 − 35,416 |
> **注意**:lftpd 在 FTP 服务期间通过 `pvPortMalloc` 临时分配路径缓冲和 DIR 对象(合计 <1KB),属于瞬态峰值。空闲 13.4KB 在无 FTP 活动时可用。
### 4.3 TCB 结构体核算
`sizeof(TCB_t)` 基于 `tasks.c:252–329` 逐字段统计(FreeRTOS V10.3.1,ARM Cortex-M4F 配置):
| 字段 | 类型 | 大小 | 配置条件 |
|------|------|------|----------|
| `pxTopOfStack` | StackType_t* | 4B | 必有 |
| `xStateListItem` | ListItem_t | 12B | 必有 |
| `xEventListItem` | ListItem_t | 12B | 必有 |
| `uxPriority` | UBaseType_t | 4B | 必有 |
| `pxStack` | StackType_t* | 4B | 必有 |
| `pcTaskName[16]` | char[16] | 16B | configMAX_TASK_NAME_LEN=16 |
| `uxTCBNumber` + `uxTaskNumber` | UBaseType_t × 2 | 8B | configUSE_TRACE_FACILITY=1 |
| `uxBasePriority` + `uxMutexesHeld` | UBaseType_t × 2 | 8B | configUSE_MUTEXES=1 |
| `ulNotifiedValue` + `ucNotifyState` | uint32_t + uint8_t | 5B | configUSE_TASK_NOTIFICATIONS=1 |
| `ucStaticallyAllocated` | uint8_t | 1B | tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE |
| **合计(含对齐)** | | **76B → 80B** | 8字节对齐 |
---
## 5. LVGL 40KB 内存池说明
`lv_mem_core_builtin.o` 占据 40,960B ZI — LVGL 的静态内存池。
| 参数 | 值 | 来源 |
|------|----|------|
| LV_MEM_SIZE | 40,960 (40KB) | `Lib/lv_conf.h` |
| 分配器 | TLSF (Two-Level Segregated Fit) | LVGL v9 builtin |
| LV_COLOR_DEPTH | 1 (1bpp) | 同上 |
| LV_USE_OS | 0 (无 OS 抽象) | 同上 |
**池内内容**:UI widget 对象树、style 数据、字符串资源、动画对象等,均为运行时动态分配。LVGL v9 的 TLSF 分配器有约 10–15% 的内部元数据开销(free block headers),实际可用净空间约 34–36KB。
**为何无法静态估算**:LVGL 池的运行时使用取决于当前 UI 页面、控件数量、样式复杂度。可通过 `LV_USE_MEM_MONITOR`(`lv_mem_monitor_t`)在运行时获取已用/碎片化数据。
---
## 6. C 库堆与栈
构建使用 **microlib**(ARM C library minimal),非标准 newlib。
| 组件 | 大小 (B) | 说明 |
|------|----------|------|
| 启动栈 (`.ARM.heap` + `.ARM.stack`) | 1,024 | `startup_stm32f407xx.o`,仅 HAL_Init 期间使用 |
| C 库代码 (Flash) | 4,230 | `mc_w.l` (3,162) + `mf_w.l` (1,066),含 printf、浮点、字符串函数 |
| C 库 ZI (RW_IRAM1) | ~8 | `stdout.o` (4B) + 一些静态标志 |
> microlib 不提供独立的 `malloc()` 堆。所有动态分配通过 FreeRTOS 的 `pvPortMalloc` 或 LVGL 池完成。C 库的栈仅用于启动期和 printf 格式化。
---
## 7. Flash 1MB 分布
![Flash 布局图](../images/内存布局_FLASH.svg)
### 7.1 总体使用
| 段 | 大小 (B) | KB | 占比 |
|----|----------|----|------|
| Code (含 inc. data) | 219,976 | 214.8 | 21.0% |
| RO Data | 11,496 | 11.2 | 1.1% |
| RW Init Data | 528 | 0.5 | <0.1% |
| **已用合计** | **231,472** | **226.0** | **22.1%** |
| **空闲** | **817,104** | **798.0** | **77.9%** |
### 7.2 代码段按模块分解
| 模块 | Code (B) | RO Data (B) | 合计 KB | 说明 |
|------|----------|-------------|---------|------|
| LVGL 库 | ~69,364 | ~4,200 | ~71.8 | lv_draw / lv_obj / lv_indev / lv_label 等 |
| 应用代码 + UI | ~42,540 | ~5,800 | ~47.2 | ui_menu / ui_net_sink / app_main / net_socket 等 |
| FatFS + dhara | ~8,500 | ~3,000 | ~11.2 | ff.o + dhara FTL journal |
| CH395F 网络 | ~8,800 | ~1,000 | ~9.6 | ch395f SPI 驱动 + lftpd |
| HAL 驱动 | ~9,400 | ~200 | ~9.4 | HAL_SPI / HAL_UART / HAL_DMA / HAL_RCC 等 |
| C 库 (microlib) | ~4,230 | ~200 | ~4.3 | printf / memcpy / strlen / 浮点 |
| FreeRTOS | ~4,240 | ~500 | ~4.6 | tasks / queues / event_groups / timers / cmsis_os2 |
| 其他 | ~73,902 | ~596 | ~72.8 | STM32 HAL 基础 / system / gpio / dma / i2c 等 |
---
## 8. 任务栈明细与预算
### 8.1 栈分配汇总
| 任务 | 分配栈 (B) | 优先级 | 主要消耗者 |
|------|-----------|--------|------------|
| uiTask | **16,384** | BelowNormal | LVGL draw/indev (16KB!) |
| netTask | 4,096 | Normal | CH395F SPI + socket 操作 + 消息处理 |
| ftpTask | 4,096 | Normal | lftpd 命令解析 + `buffer[1024]` 栈局部 + FatFS 调用 |
| testTask | 4,096 | BelowNormal | 测试套件(产品模式下 idle loop) |
| defaultTask | 2,048 | Normal | FatFS 挂载 + LED 闪烁 |
| timeSyncTask | 2,048 | Normal | TCP socket + 时间解析 |
### 8.2 堆 vs 任务栈的关系
```
FreeRTOS 堆 48KB 内存池
├── 任务栈 32KB (6 app tasks) ← 63% of heap
├── 内核栈 1.5KB (idle + timer) ← 3%
├── TCBs ~640B (8 × ~80B) ← 1.3%
├── netMsgQueue ~448B ← 0.9%
├── eventGroup ~24B ← 0.05%
├── 稳态合计 ~35.4KB ← 72.1% of heap
└── 空闲 ~13.4KB ← 27.9% of heap
```
> **关键观察**:uiTask 占 16KB(堆的 33%),是最大的单一消费者。其栈需求主要来自 LVGL v9 的深层 widget 树遍历和 draw 回调链。该分配是否过大需要运行时 `uxTaskGetStackHighWaterMark()` 验证。
---
## 9. 关键发现与风险
### 9.1 SRAM 几乎耗尽 (95.4%)
| 风险 | 等级 | 说明 |
|------|------|------|
| 新任务/缓冲无法容纳 | **高** | 空闲 5.8KB 不足以创建新任务(最小 2KB 栈 + 80B TCB) |
| 堆碎片化 | **中** | heap_4 不支持合并空闲块,长期运行后碎片可能阻断分配 |
| 栈溢出检测缺失 | **中** | 未启用 `configCHECK_FOR_STACK_OVERFLOW`;溢出将静默破坏相邻内存 |
### 9.2 两个大池(FreeRTOS 48KB + LVGL 40KB)占 SRAM 68%
这两个池合计 88KB(占 128KB 的 69%),是 SRAM 紧张的根本原因:
- **FreeRTOS 48KB**:由 6 个任务栈 + 内核对象消耗。uiTask 16KB 是最大的单项。
- **LVGL 40KB**:UI 对象/样式的动态池。实际使用量取决于 UI 复杂度(运行时未知)。
### 9.3 可用但未映射的 SRAM
| 区域 | 大小 | 现状 | 迁移可行性 |
|------|------|------|-----------|
| SRAM3 | 64KB | scatter 未映射 | 可映射;适合非 DMA 的大数据缓冲(LVGL 池等) |
| CCM | 64KB | scatter 未映射 | 仅 CPU 可访问;适合栈或静态缓冲(非 DMA、非外设访问) |
> 将 LVGL 池(40KB)迁移至 SRAM3 可立即释放 40KB,将 SRAM 利用率从 95% 降至 64%。
### 9.4 任务栈可能过大
根据 `待处理内容.md`:"所有任务的内存都是往大的给的,导致内存不够用,要仔细排查内存的预算情况"。当前栈分配基于最坏估计,尚未通过运行时高水位标记验证实际使用量。
**建议的高水位检查**(在各任务循环中周期调用):
```c
UBaseType_t watermark = uxTaskGetStackHighWaterMark(NULL);
// watermark × 4 = 剩余栈字节数
```
---
## 10. 优化方向(指向既有方案)
详细的优化方案见 [`RAM使用分析与优化方案.md`](./RAM使用分析与优化方案.md)。本节仅列出与内存使用直接相关的核心方向:
| 方向 | 预期收益 | 复杂度 |
|------|----------|--------|
| 运行时高水位标记审计 | 量化各任务栈实际使用 | 低 |
| LVGL 池迁移至 SRAM3 | 释放 40KB 主 SRAM | 中(需改分散加载+初始化) |
| uiTask 栈裁减(需高水位数据) | 可能节省 4–8KB | 低 |
| 测试任务栈裁减(产品模式 idle) | 可能节省 2–3KB | 低 |
| 启用 `configCHECK_FOR_STACK_OVERFLOW` | 防止静默溢出 | 低 |
| 使能 LVGL `LV_USE_MEM_MONITOR` | 运行时监控池使用率 | 低 |
| 裁减未使用 LVGL 组件(draw blend_to_rgb565 等) | 节省 Flash + 可能减少池压力 | 中 |
---
## 11. 附录
### A. 数据来源
| 数据 | 来源 | 路径 |
|------|------|------|
| Grand Totals / RW_IRAM1 明细 | .map 文件 | `MDK-ARM/STM32F407-Demo/STM32F407-Demo.map` |
| 逐模块 RW+ZI + Flash Top-30 | 自动生成台账 | `docs/内存管理/data/ram_report_20260911.md` |
| 解析脚本 | `scripts/ram_report.py` | 可复现重生成台账 |
| FreeRTOSConfig 参数 | `Inc/FreeRTOSConfig.h` | heap=48KB, 通知/互斥/跟踪等开关 |
| LVGL 配置 | `Lib/lv_conf.h` | LV_MEM_SIZE=40KB, COLOR_DEPTH=1 |
| 任务创建 | `Src/freertos.c:142–179` | 6 个应用任务的栈大小和属性 |
| TCB 结构体 | `tasks.c:252–329` | sizeof(TCB_t) ≈ 76B (本配置) |
### B. 自动生成台账
运行以下命令重新生成台账(构建后):
```powershell
& '.venv\Scripts\python.exe' scripts/ram_report.py -o "docs\内存管理\data\ram_report_20260911.md"
```
### C. SVG 图表重新生成
```powershell
& '.venv\Scripts\python.exe' tools/arch_diagram/gen_memory_svg.py
```
生成文件:
- `docs/images/内存布局_RAM.svg`
- `docs/images/内存布局_FLASH.svg`
- `docs/images/任务栈预算.svg`
### D. 自洽校验结果
| 层 | 指标 | Per-file sum | Grand Totals | 差值 | 判定 |
|----|------|-------------|-------------|------|------|
| Flash | Code | 219,976 | 219,976 | 0 | PASS |
| Flash | RO | 11,496 | 11,496 | 0 | PASS |
| SRAM | RW | 528 | 528 | 0 | PASS |
| SRAM | ZI | 124,576 | 124,576 | 0 | PASS |
> Per-file sum 与 Grand Totals 的差异来自 linker "(incl. Padding)" 行(约 302B code + 64B RO + 8B RW + 2B ZI),已在台账解析器中正确处理。
@@ -0,0 +1,177 @@
# STM32F4-Base RAM/Flash Usage Report
> Generated: 2026-09-21 04:33 Map: `STM32F407-Demo.map`
## 1. Grand Totals (全局总量)
| Metric | Value |
|---|---|
| Code (inc. data) | 234,976 B (229.5 KB) |
| RO Data | 13,824 B (13.5 KB) |
| RW Data (initialized) | 632 B (0.62 KB) |
| ZI Data (zero-init) | 130,600 B (127.5 KB) |
| **Total RO (Code + RO)** | **248,800 B (243.0 KB)** |
| **Total RW (RW + ZI) = SRAM used** | **131,232 B (128.16 KB)** |
| **Total ROM (Code + RO + RW init)** | **249,432 B (243.6 KB)** |
## 2. 执行区水位 (Execution Regions)
| Region | Base | Size | Max | Util% |
|---|---|---|---|---|
| ER_IROM1 | 0x08000000 | 248,800 B (242.97 KB) | 1,048,576 B (1024 KB) | 23.7% |
| RW_CCM | 0x10000000 | 49,184 B (48.03 KB) | 65,536 B (64 KB) | 75.0% |
| RW_IRAM1 | 0x20000000 | 82,048 B (80.12 KB) | 131,072 B (128 KB) | 62.6% |
## 3. RW_IRAM1 逐符号明细(ZI + RW,按大小降序)
| # | Address | Size (B) | Size (KB) | Type | Section | Object |
|---|---|---|---|---|---|---|
| 1 | 0x20006824 | 40,960 | 40.00 | Zero | .bss | lv_mem_core_builtin.o |
| 2 | 0x20012226 | 6,746 | 6.59 | Zero | .bss | ui_net_sink.o |
| 3 | 0x20010824 | 6,408 | 6.26 | Zero | .bss | ui_port.o |
| 4 | 0x20002CF8 | 6,200 | 6.05 | Zero | .bss | nand_ftl.o |
| 5 | 0x20004530 | 4,616 | 4.51 | Zero | .bss | lftpd.o |
| 6 | 0x20001094 | 4,104 | 4.01 | Zero | .bss | ch395f.o |
| 7 | 0x2000209C | 2,576 | 2.52 | Zero | .bss | gd5f2gq5ue.o |
| 8 | 0x20005F7C | 1,720 | 1.68 | Zero | .bss | cmsis_os2.o |
| 9 | 0x20000258 | 1,608 | 1.57 | Zero | .bss | freertos.o |
| 10 | 0x200059A0 | 1,220 | 1.19 | Zero | .bss | tasks.o |
| 11 | 0x20013C80 | 1,024 | 1.00 | Zero | | startup_stm32f407xx.o |
| 12 | 0x20000D94 | 704 | 0.69 | Zero | .bss | cfg_file.o |
| 13 | 0x200008F4 | 560 | 0.55 | Zero | .bss | spi.o |
| 14 | 0x20000B24 | 552 | 0.54 | Zero | .bss | usart.o |
| 15 | 0x20005738 | 552 | 0.54 | Zero | .bss | lftpd_io.o |
| 16 | 0x20006634 | 496 | 0.48 | Zero | .bss | lv_init.o |
| 17 | 0x20002BD8 | 288 | 0.28 | Zero | .bss | net_socket.o |
| 18 | 0x20005E64 | 280 | 0.27 | Zero | .bss | timers.o |
| 19 | 0x20002ACC | 268 | 0.26 | Zero | .bss | rs485.o |
| 20 | 0x200000BC | 152 | 0.15 | Data | .data | lftpd.o |
| 21 | 0x2001212C | 152 | 0.15 | Zero | .bss | ui_menu_view.o |
| 22 | 0x200008A0 | 84 | 0.08 | Zero | .bss | i2c.o |
| 23 | 0x20000D4C | 72 | 0.07 | Zero | .bss | stm32f4xx_hal_timebase_tim.o |
| 24 | 0x20001054 | 64 | 0.06 | Zero | .bss | sys_log_mcu.o |
| 25 | 0x20005960 | 64 | 0.06 | Zero | .bss | queue.o |
| 26 | 0x20000158 | 60 | 0.06 | Data | .data | tasks.o |
| 27 | 0x200121C4 | 49 | 0.05 | Zero | .bss | ui_page_runlog.o |
| 28 | 0x200121F5 | 49 | 0.05 | Zero | .bss | ui_page_selftest.o |
| 29 | 0x20000000 | 40 | 0.04 | Data | .data | freertos.o |
| 30 | 0x20000088 | 36 | 0.04 | Data | .data | net_cfg.o |
| 31 | 0x20000220 | 32 | 0.03 | Data | .data | ui_standby.o |
| 32 | 0x20002AAC | 32 | 0.03 | Zero | .bss | tpafe5160.o |
| 33 | 0x20000028 | 20 | 0.02 | Data | .data | cfg_file.o |
| 34 | 0x20000060 | 20 | 0.02 | Data | .data | gd5f2gq5ue.o |
| 35 | 0x20000194 | 20 | 0.02 | Data | .data | timers.o |
| 36 | 0x200001D8 | 18 | 0.02 | Data | .data | ui_menu.o |
| 37 | 0x20000078 | 16 | 0.02 | Data | .data | net_socket.o |
| 38 | 0x200000AC | 16 | 0.02 | Data | .data | nand_ftl.o |
| 39 | 0x20000240 | 16 | 0.02 | Data | .data | ui_net_sink.o |
| 40 | 0x2000004C | 12 | 0.01 | Data | .data | stm32f4xx_hal.o |
| 41 | 0x200001AC | 12 | 0.01 | Data | .data | port.o |
| 42 | 0x200001CC | 12 | 0.01 | Data | .data | ui_port.o |
| 43 | 0x200001F4 | 12 | 0.01 | Data | .data | ui_content_page.o |
| 44 | 0x20000200 | 12 | 0.01 | Data | .data | ui_page_password.o |
| 45 | 0x2000003C | 8 | 0.01 | Data | .data | sys_log_mcu.o |
| 46 | 0x20000044 | 8 | 0.01 | Data | .data | sys_clock.o |
| 47 | 0x200001B8 | 8 | 0.01 | Data | .data | ff.o |
| 48 | 0x200001C0 | 8 | 0.01 | Data | .data | ffsystem.o |
| 49 | 0x200001EC | 8 | 0.01 | Data | .data | ui_menu_view.o |
| 50 | 0x20000210 | 8 | 0.01 | Data | .data | ui_page_runlog.o |
| 51 | 0x20000218 | 8 | 0.01 | Data | .data | ui_page_selftest.o |
| 52 | 0x20000058 | 4 | 0.00 | Data | .data | system_stm32f4xx.o |
| 53 | 0x20000154 | 4 | 0.00 | Data | .data | lftpd_io.o |
| 54 | 0x200001A8 | 4 | 0.00 | Data | .data | cmsis_os2.o |
| 55 | 0x200001C8 | 4 | 0.00 | Data | .data | lv_textarea.o |
| 56 | 0x20000250 | 4 | 0.00 | Data | .data | mc_w.l(stdout.o) |
| 57 | 0x20000254 | 4 | 0.00 | Data | .data | mc_w.l(errno.o) |
| 58 | 0x20000074 | 2 | 0.00 | Data | .data | tpafe5160.o |
| 59 | 0x2000020C | 2 | 0.00 | Data | .data | ui_page_net_cfg.o |
| 60 | 0x2000005C | 1 | 0.00 | Data | .data | ch395f.o |
| | | **82,039** | **80.12** | | | **Total** |
## 4. Image Component Sizes(RW + ZI > 0,按大小降序)
| # | Object | Code | RO | RW | ZI | RW+ZI |
|---|---|---|---|---|---|---|
| 1 | heap_4.o | 508 | 0 | 32 | 49,152 | 49,184 |
| 2 | lv_mem_core_builtin.o | 410 | 0 | 0 | 40,960 | 40,960 |
| 3 | ui_net_sink.o | 882 | 0 | 16 | 6,746 | 6,762 |
| 4 | ui_port.o | 266 | 0 | 12 | 6,408 | 6,420 |
| 5 | nand_ftl.o | 2,008 | 65 | 16 | 6,200 | 6,216 |
| 6 | lftpd.o | 4,896 | 503 | 152 | 4,616 | 4,768 |
| 7 | ch395f.o | 2,738 | 262 | 1 | 4,104 | 4,105 |
| 8 | gd5f2gq5ue.o | 2,974 | 270 | 20 | 2,576 | 2,596 |
| 9 | cmsis_os2.o | 1,278 | 0 | 4 | 1,720 | 1,724 |
| 10 | freertos.o | 1,624 | 570 | 40 | 1,608 | 1,648 |
| 11 | tasks.o | 3,040 | 0 | 60 | 1,220 | 1,280 |
| 12 | startup_stm32f407xx.o | 36 | 392 | 0 | 1,024 | 1,024 |
| 13 | cfg_file.o | 5,036 | 726 | 20 | 704 | 724 |
| 14 | spi.o | 584 | 0 | 0 | 560 | 560 |
| 15 | lftpd_io.o | 1,018 | 0 | 4 | 552 | 556 |
| 16 | usart.o | 908 | 0 | 0 | 552 | 552 |
| 17 | lv_init.o | 156 | 0 | 0 | 496 | 496 |
| 18 | net_socket.o | 4,262 | 139 | 16 | 288 | 304 |
| 19 | timers.o | 1,012 | 0 | 20 | 280 | 300 |
| 20 | rs485.o | 120 | 0 | 0 | 268 | 268 |
| 21 | ui_menu_view.o | 2,010 | 108 | 8 | 152 | 160 |
| 22 | i2c.o | 168 | 0 | 0 | 84 | 84 |
| 23 | stm32f4xx_hal_timebase_tim.o | 152 | 0 | 0 | 72 | 72 |
| 24 | sys_log_mcu.o | 1,574 | 83 | 8 | 64 | 72 |
| 25 | queue.o | 2,392 | 0 | 0 | 64 | 64 |
| 26 | ui_page_runlog.o | 168 | 93 | 8 | 49 | 57 |
| 27 | ui_page_selftest.o | 168 | 93 | 8 | 49 | 57 |
| 28 | net_cfg.o | 748 | 103 | 36 | 0 | 36 |
| 29 | tpafe5160.o | 440 | 0 | 2 | 32 | 34 |
| 30 | ui_standby.o | 1,148 | 159 | 32 | 0 | 32 |
| 31 | ui_menu.o | 744 | 137 | 18 | 0 | 18 |
| 32 | port.o | 786 | 0 | 12 | 0 | 12 |
| 33 | stm32f4xx_hal.o | 116 | 0 | 12 | 0 | 12 |
| 34 | ui_content_page.o | 1,652 | 199 | 12 | 0 | 12 |
| 35 | ui_page_password.o | 686 | 167 | 12 | 0 | 12 |
| 36 | (incl. Padding) | 326 | 74 | 9 | 0 | 9 |
| 37 | ff.o | 7,942 | 282 | 8 | 0 | 8 |
| 38 | ffsystem.o | 204 | 20 | 8 | 0 | 8 |
| 39 | sys_clock.o | 1,748 | 286 | 8 | 0 | 8 |
| 40 | lv_textarea.o | 3,632 | 48 | 4 | 0 | 4 |
| 41 | system_stm32f4xx.o | 16 | 24 | 4 | 0 | 4 |
| 42 | errno.o | 8 | 0 | 4 | 0 | 4 |
| 43 | stdout.o | 0 | 0 | 4 | 0 | 4 |
| 44 | ui_page_net_cfg.o | 1,004 | 93 | 2 | 0 | 2 |
| | **All .o files** | **234,976** | **13,824** | **632** | **130,600** | **131,232** |
## 5. Flash (Code + RO Data) Top-30(按大小降序)
| # | Object | Code | RO Data | Code+RO |
|---|---|---|---|---|
| 1 | ff.o | 7,942 | 282 | 8,224 |
| 2 | lv_draw_sw_blend_to_rgb565_swapped.o | 7,648 | 0 | 7,648 |
| 3 | lv_draw_sw_blend_to_rgb565.o | 7,020 | 0 | 7,020 |
| 4 | cfg_file.o | 5,036 | 726 | 5,762 |
| 5 | lv_draw_sw_blend_to_argb8888.o | 5,412 | 0 | 5,412 |
| 6 | lftpd.o | 4,896 | 503 | 5,399 |
| 7 | lv_draw_sw_blend_to_i1.o | 5,272 | 0 | 5,272 |
| 8 | lv_indev.o | 4,958 | 0 | 4,958 |
| 9 | ui_font_cn_12.o | 0 | 4,626 | 4,626 |
| 10 | lv_draw_sw_blend_to_rgb888.o | 4,560 | 0 | 4,560 |
| 11 | lv_obj_pos.o | 4,526 | 16 | 4,542 |
| 12 | lv_refr.o | 4,486 | 0 | 4,486 |
| 13 | net_socket.o | 4,262 | 139 | 4,401 |
| 14 | lv_draw_sw_transform.o | 4,340 | 0 | 4,340 |
| 15 | lv_obj_style.o | 4,138 | 0 | 4,138 |
| 16 | lv_obj_scroll.o | 4,126 | 0 | 4,126 |
| 17 | lv_label.o | 4,042 | 45 | 4,087 |
| 18 | lv_draw_sw_mask.o | 3,862 | 0 | 3,862 |
| 19 | lv_draw_sw_blend_to_l8.o | 3,778 | 0 | 3,778 |
| 20 | lv_buttonmatrix.o | 3,586 | 121 | 3,707 |
| 21 | lv_textarea.o | 3,632 | 48 | 3,680 |
| 22 | stm32f4xx_hal_spi.o | 3,586 | 0 | 3,586 |
| 23 | lv_draw_sw_blend_to_al88.o | 3,560 | 0 | 3,560 |
| 24 | lv_draw_sw_blend_to_argb8888_premultiplied.o | 3,332 | 0 | 3,332 |
| 25 | lv_draw_sw_box_shadow.o | 3,246 | 0 | 3,246 |
| 26 | gd5f2gq5ue.o | 2,974 | 270 | 3,244 |
| 27 | lv_indev_scroll.o | 3,068 | 0 | 3,068 |
| 28 | tasks.o | 3,040 | 0 | 3,040 |
| 29 | lv_obj.o | 2,982 | 43 | 3,025 |
| 30 | ch395f.o | 2,738 | 262 | 3,000 |
| | **All .o files** | **234,976** | **13,824** | **248,800** |
> 自洽校验:per-file 234,976/13,824/632/130,600 vs Grand Totals 234,976/13,824/632/130,600(CodeΔ=+0 / ROΔ=+0 / RWΔ=+0 / ZIΔ=+0;精确一致(含 '('incl. Generated)'-'(incl. Padding)' 台账行))→ **PASS**
+10 -10
View File
@@ -1,7 +1,7 @@
# Stack budget report
- Source: `MDK-ARM\STM32F407-Demo\STM32F407-Demo.htm` (Keil static call graph)
- Linker run: Mon Sep 21 04:08:58 2026
- Linker run: Mon Sep 21 04:28:54 2026
- Global maximum stack usage: **2776 B** (any call tree)
- Runtime watermark: 6 task(s) from `tools\serial_tool\state\serial.log`
@@ -10,9 +10,9 @@ Rule: `margin = cfg - max(static_max_depth, runtime_peak)` must be >= max(1024 B
| Task | Entry | Cfg (B) | Static max (B) | Runtime peak (B) | Worst (B) | Margin (B) | Margin % | Verdict |
|---|---|---|---|---|---|---|---|---|
| defaultTask | `StartDefaultTask` | 4096 | 2776 | 2136 | 2776 | 1320 | 32% | PASS |
| netTask | `StartNetTask` | 4096 | 440 | 600 | 600 | 3496 | 85% | PASS |
| ftpTask | `StartFtpTask` | 4096 | 2528 | 3480 | 3480 | 616 | 15% | FAIL |
| uiTask | `StartUiTask` | 16384 | 912 | 3804 | 3804 | 12580 | 76% | PASS |
| netTask | `StartNetTask` | 4096 | 440 | 588 | 588 | 3508 | 85% | PASS |
| ftpTask | `StartFtpTask` | 4096 | 472 | 1184 | 1184 | 2912 | 71% | PASS |
| uiTask | `StartUiTask` | 16384 | 912 | 3824 | 3824 | 12560 | 76% | PASS |
| testTask | `start_test_task` | 8192 | 168 | 348 | 348 | 7844 | 95% | PASS |
| timeSyncTask | `time_sync_task` | 2048 | 336 | 420 | 420 | 1628 | 79% | PASS |
@@ -34,7 +34,7 @@ StartDefaultTask -> net_cfg_load -> net_cfg_legacy_migrate -> net_cfg_save -> cf
StartNetTask -> net_task_func -> net_stack_bring_up -> net_init -> ch395f_reset -> sys_clock_get_str -> unix_to_sd2506 -> __aeabi_uldivmod
```
### ftpTask: 2528 B (own frame 24 B)
### ftpTask: 472 B (own frame 24 B)
```
StartFtpTask -> lftpd_start -> handle_control_channel -> send_response -> lftpd_inet_write_string -> net_send -> osMessageQueuePut -> xQueueGenericSend -> xTaskResumeAll -> xTaskIncrementTick
@@ -62,10 +62,6 @@ time_sync_task -> time_sync_handle_conn -> sys_clock_set_unix -> sys_clock_set -
| Bytes | Function |
|---|---|
| 1104 | `handle_control_channel` |
| 1088 | `receive_file` |
| 1088 | `send_file` |
| 800 | `send_response` |
| 704 | `s_append` |
| 624 | `lv_draw_sw_arc` |
| 624 | `draw_indic` |
@@ -76,4 +72,8 @@ time_sync_task -> time_sync_handle_conn -> sys_clock_set_unix -> sys_clock_set -
| 560 | `draw_main` |
| 400 | `draw_line_skew` |
| 352 | `lv_draw_sw_triangle` |
| 336 | `lftpd_start` |
| 328 | `lv_obj_redraw` |
| 328 | `view_frame_draw_cb` |
| 312 | `nc_draw_cb` |
| 304 | `lv_draw_label_iterate_characters` |
| 304 | `lv_draw_sw_box_shadow` |
@@ -1,189 +0,0 @@
# STM32F4-Base 内存使用分析 —— 执行规划
> 版本:v1.0 日期:2026-09-11
> 目标平台:STM32F407ZGTx @168MHz(1MB Flash / 192KB SRAM + 64KB CCM)
> 软件:FreeRTOS V10.3.1(CMSIS-RTOS2)+ LVGL + FatFs/dhara FTL
> 构建口径:**产品默认构建**(`test/test_config.h` 全注释 = 无测试套件;`App/app_cfg.h` 中 `APP_NET_ENABLE`/`APP_ADC_ENABLE`/`APP_RS485_ENABLE` 全开)
---
## 1. 任务目标与交付物
### 1.1 目标
对工程做一次**严谨、可复现、可追溯**的内存使用审计,回答三个问题:
1. **主 SRAM 128KB 到底被谁、占用多少、还剩多少?**(静态区逐模块 + 逐大项)
2. **FreeRTOS 48KB 堆内部怎么分布?**(任务栈 / TCB / 队列 / 事件组 / 临时分配 / 碎片余量)
3. **LVGL 40KB 内存池、C 库堆栈、Flash 1MB 各自怎么用?**
要求:**有总览、有细节**,每个数字都能追到 `map` 文件或源码行号,做到"每一部分分配了多少 RAM"一目了然。
### 1.2 交付物
| # | 交付物 | 落位 |
|---|---|---|
| D1 | 本规划文档(执行中逐项打勾) | `docs/内存管理/内存使用分析规划.md` |
| D2 | map 解析脚本(可复用、纯标准库) | `scripts/ram_report.py` |
| D3 | 数据台账(map 原始抽取结果) | `docs/内存管理/data/ram_report_YYYYMMDD.md`(脚本生成) |
| D4 | 配图(SVG,`draw-diagram` 规范) | `docs/images/内存布局_RAM.svg`、`内存布局_FLASH.svg`、`任务栈预算.svg` |
| D5 | SVG 生成器 | `tools/arch_diagram/gen_memory_svg.py` |
| D6 | **最终报告**(图文并茂) | `docs/内存管理/STM32F4-Base内存使用分析报告.md` |
> 与既有 `docs/内存管理/RAM使用分析与优化方案.md`(2026-09-07,偏"优化方案/整改计划")互为补充:本报告聚焦**现状审计与分布**,结论指向该优化方案。报告内交叉引用,不重复展开整改动作。
---
## 2. 范围与口径(内存资源真相)
### 2.1 芯片物理内存(数据源:RM0090 §3.1、`STM32F407-Demo.sct`)
| 区域 | 起始地址 | 容量 | 本工程是否使用 | 依据 |
|---|---|---|---|---|
| Flash | `0x0800_0000` | 1024 KB | ✅ 执行区 `LR_IROM1/ER_IROM1` | `.sct:7-13` |
| SRAM1 | `0x2000_0000` | 112 KB | ✅ `RW_IRAM1` 的一部分 | `.sct:14-16` |
| SRAM2 | `0x2001_C000` | 16 KB | ✅ `RW_IRAM1` 的一部分 | 同上 |
| SRAM3 | `0x2002_0000` | 64 KB | ❌ **未映射**(注释:clone MCU 可能无此区) | `.sct:1-5` |
| CCM SRAM | `0x1000_0000` | 64 KB | ❌ **未使用**(仅 CPU 可达,DMA 不可达) | `.sct`(无 CCM 区) |
**关键结论(本报告的分析边界)**:实际可分配的 SRAM = `RW_IRAM1` 的 **128KB**。SRAM3(64KB)与 CCM(64KB)是**潜在预算**,不计入当前占用,仅在结论中作为"可挖潜空间"提及。
### 2.2 口径定义
- **静态区**:`RW_IRAM1` 内由链接器直接分配的所有 `RW + ZI`(含 `ucHeap` 堆池本体、`ucMemPool` LVGL 池本体、各驱动缓冲、C 库 `HEAP`/`STACK` 节)。
- **运行时动态区**:从 FreeRTOS 堆(`ucHeap`)内部再分配的部分 = 任务栈 + TCB + 队列/事件组 + 临时分配;从 LVGL 池(40KB)内部再分配的部分 = 对象/样式/字符串。**注意**:这两层"池"的本体在静态区已全额计入,其内部细分**不重复加总**,只做占比说明。
- **Flash**:`RO + RW 初值`(`Total ROM Size`),按模块与功能区(向量表/代码/只读数据/中文字库)拆分。
---
## 3. 数据来源(一手材料清单)
| 类别 | 文件 | 用途 |
|---|---|---|
| 链接配置 | `MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct` | 区域划分 |
| RTOS 配置 | `Inc/FreeRTOSConfig.h` | 堆大小/优先级/静态分配开关 |
| RTOS 封装语义 | `Middlewares/.../CMSIS_RTOS_V2/cmsis_os2.c:472-476` | **栈大小单位 = 字节**(`/sizeof(StackType_t)`) |
| 任务定义 | `Src/freertos.c:60-184` | 任务属性、队列、事件组 |
| LVGL 配置 | `Lib/lv_conf.h` | `LV_MEM_SIZE`、`LV_COLOR_DEPTH`、`LV_USE_OS` |
| FatFs 配置 | `Lib/FatFs/ffconf.h` | `FF_MAX_SS`/`FF_FS_TINY`/`FF_USE_LFN` |
| 模块开关 | `App/app_cfg.h`、`test/test_config.h` | 构建口径 |
| **权威数据** | `MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`(`@build` 重建) | 总量 + 逐 .o + 逐符号 |
| 源码 | `Drivers/BSP/**`、`App/**` | 缓冲语义/用途/DMA 归属 |
> **重建要求**:既有 map 为历史构建产物,分析前必须重新 `@build` 生成与当前源码一致的 map(S1)。
---
## 4. 分析方法论(四视角 + 交叉校验)
```
┌── V1 链接视图(map)─────┐
│ Grand Totals / Region │ 总量、水位
│ Image component sizes │ 逐 .o 的 RO/RW/ZI
│ Image Symbol Table │ 逐符号地址+大小
└────────────┬─────────────┘
│ 交叉校验
┌─────────────────────────┼─────────────────────────┐
▼ ▼ ▼
V2 运行时堆视图 V3 LVGL 池视图 V4 源码/配置视图
(48KB ucHeap 内部) (40KB ucMemPool 内部) (编译期常量缓冲)
任务栈+TCB+队列+临时 对象/样式/字符串 逐模块 static/global
```
- **V1 链接视图**:脚本解析 map,产出 `Region 水位` + `Image component sizes`(全部 .o 的 ZI/RW 排序)+ `Symbol Table`(大对象清单)。**这是所有静态数字的唯一权威来源。**
- **V2 运行时堆视图**:按 FreeRTOS `heap_4` 分配规则**手算** 48KB 池的占用(任务栈、TCB、队列、事件组、软定时器、idle、lftpd 临时分配)。分配点的存在性由源码遍历确认。
- **V3 LVGL 池视图**:确认 `LV_MEM_SIZE` 本体在静态区,说明其内部用途;不重复加总。
- **V4 源码/配置视图**:逐模块列出大缓冲的**用途、大小、是否 DMA 可达**,用于给 V1 的符号赋语义(map 只有符号名,没有"这是干什么的")。
- **交叉校验**:V4 逐项相加应与 V1 的 `Image component sizes` 对每个 .o 吻合;不等则以 V1 为准并记录差异原因。
---
## 5. 执行步骤(S0–S9,含验收与状态)
> 每步完成即勾选,并在第 9 节进度表登记。涉及代码/文件的改动遵守 AGENTS.md 编码铁律(只用 Edit;新文件 UTF-8 无 BOM)。
| 步骤 | 内容 | 验收标准 | 状态 |
|---|---|---|---|
| **S0** | 编写本规划文档 | 文档落位并被确认 | ✅ 完成 |
| **S1** | `@build` 重建;确认 `build_log` 0 错 0 警、map 时间戳更新 | map 存在且时间戳为本次 | ✅ 完成 |
| **S2** | 编写 `scripts/ram_report.py`:解析 map → 地表/区域水位/逐 .o/大符号;输出台账 `ram_report_YYYYMMDD.md` | 脚本可复跑;数字与 map 人工抽查一致 | ✅ 完成 |
| **S3** | 逐模块源码审计:枚举全部大缓冲(用途/大小/DMA 归属/文件行号) | 覆盖 Top-N 全部大项,无遗漏 | ✅ 完成 |
| **S4** | FreeRTOS 堆核算:任务栈/TCB/队列/事件组/定时器/idle + 临时分配上限 | 手算总和 ≤ 48KB 且给出余量 | ✅ 完成 |
| **S5** | LVGL 池核算 + C 库堆/栈 + Flash 构成核算 | 各池/各功能区数字闭合 | ✅ 完成 |
| **S6** | 汇总对账:三层总表(SRAM 静态 / 堆内部 / 池内部)+ Flash 总表 | 加总闭合;V1↔V4 差异已解释 | ✅ 完成 |
| **S7** | 生成 SVG 配图(RAM/FLASH/任务栈)并渲染目检 | 3 张 SVG 生成、像素渲染无溢出 | ✅ 完成 |
| **S8** | 撰写最终报告 `STM32F4-Base内存使用分析报告.md` | 图文并茂、结构清晰、数字可追溯 | ✅ 完成 |
| **S9** | 校验:数字自洽 / 图文一致 / U+FFFD / 引用路径 / pre-commit | 全部通过 | ✅ 完成 |
### 各步骤细化
- **S2 解析器输出**:
1. `Execution Region` 表(`RW_IRAM1` 的 `Base/Size/Max`、`ER_IROM1`)
2. `Image component sizes` 全表 → 按 `ZI+RW` 降序 Top-N(RAM);按 `Code+RO` 降序 Top-N(Flash)
3. `Image Symbol Table` 中 `ZI/RW` 大对象(≥128B)→ 符号名 + 地址 + 大小
4. 生成 Markdown 台账文件
- **S3 审计清单**(预期含):`heap_4.o:ucHeap`、`lv_mem_core_builtin.o:ucMemPool`、`ui_port.o` 帧缓冲、`ui_net_sink*.o` 远程显示缓冲、`nand_ftl.o` 页缓冲、`ch395f.o` SPI DMA 缓冲、`gd5f2gq5ue.o` BBT、`lftpd.o`、`rs485.o`、`tpafe5160.o`、`usart.o`、`sd2506.o`、`sys_clock.o`、HAL 句柄、FatFs 对象(`default_fatfs`、`fs_work`)、CMSIS/内核静态区。
- **S4 手算规则**:TSK 栈字节 + `sizeof(TCB_t)` + 队列(`sizeof(Queue_t)+8×sizeof(net_msg_t)`)+ 事件组 + 定时器任务栈/TCB + idle 栈/TCB + lftpd/FatFS 峰值临时分配。
- **S6 三张总表**:① SRAM 128KB 静态区(逐模块)② FreeRTOS 堆 48KB 内部 ③ LVGL 池 40KB 内部(说明性)+ Flash 表。
- **S7 配图**:
- `内存布局_RAM.svg`:128KB 主 SRAM 横/竖条 → 静态区(按模块堆叠)+ 堆池(内部细分)+ 空闲
- `内存布局_FLASH.svg`:1MB Flash → 向量/代码/只读数据(含中文字库)/库/空闲
- `任务栈预算.svg`:6 任务配置栈 vs 职责估算对比柱状
---
## 6. 最终报告结构(D6 目录)
1. 摘要(一页看懂:总预算 / 已用 / 余量 / 三大占用来源)
2. 内存资源总览(芯片物理图 + 本工程映射图)
3. 主 SRAM 静态分布(总表 + 逐模块表 + Top-N 大项)
4. FreeRTOS 堆 48KB 内部拆解(任务栈表 + TCB/队列表 + 余量)
5. LVGL 内存池 40KB 说明
6. C 库堆/栈
7. Flash 1MB 分布(代码/只读数据/字体)
8. 任务栈明细(配置 vs 职责,含 `freertos.c` 行号)
9. 关键发现与风险(如:静态区水位、堆余量、未用 CCM/SRAM3)
10. 优化方向(指向既有《RAM 使用分析与优化方案》,不展开)
11. 附录:数据来源、map 取数方法、脚本用法、术语与地址表
---
## 7. 产出物与落位汇总
| 类型 | 路径 | 脚本生成? |
|---|---|---|
| 规划文档 | `docs/内存管理/内存使用分析规划.md` | 手写 |
| 报告 | `docs/内存管理/STM32F4-Base内存使用分析报告.md` | 手写 |
| 台账 | `docs/内存管理/data/ram_report_YYYYMMDD.md` | `scripts/ram_report.py` |
| 配图 | `docs/images/{内存布局_RAM,内存布局_FLASH,任务栈预算}.svg` | `tools/arch_diagram/gen_memory_svg.py` |
| 脚本 | `scripts/ram_report.py`、`tools/arch_diagram/gen_memory_svg.py` | — |
---
## 8. 风险、边界与假设
| 项 | 说明 | 处置 |
|---|---|---|
| map 与源码不同步 | 历史 map 已过期 | S1 强制重建 |
| 动态分配不可静态枚举 | 任务栈实际峰值需运行时高水位 | 本报告给"配置值 + 职责估算";实测高水位列为后续(指向优化方案阶段 0) |
| 池内部占用为运行时行为 | `ucHeap`/`ucMemPool` 内部随负载变化 | 以"分配点存在性 + 理论上限"核算,不声称实测 |
| 口径 | 只算 `RW_IRAM1`(128KB) | SRAM3/CCM 单列为潜力,不计入占用 |
| 编码 | 报告含中文 | `.md` 为 UTF-8 无 BOM;只用 Write/Edit,避免整文件重写 |
| 构建一致性 | app_cfg/test_config 若变更,数字随之变 | 报告标注构建口径与 map 时间戳 |
---
## 9. 进度追踪
| 步骤 | 状态 | 备注 |
| S0 规划 | ✅ | 本文件 |
| S1 重建 | ✅ | `@build` 0 错 0 警;map 2026-09-11 23:12:55 |
| S2 解析脚本 | ✅ | `scripts/ram_report.py`;台账含自洽校验行(PASS) |
| S3 源码审计 | ✅ | 关键缓冲语义已核对(UI/FTL/BBT/CH395F/lftpd/FatFs) |
| S4 堆核算 | ✅ | 栈 34,304 + TCB 640 + 队列 448 + 事件组 24 ≈ 35,416B,余 13,736B |
| S5 池/Flash 核算 | ✅ | LVGL 40KB 静态占用;Flash 22.1% |
| S6 汇总对账 | ✅ | 台账解析器数字与 Grand Totals 完全一致(差值=incl.padding) |
| S7 配图 | ✅ | 3 张 SVG 生成 + Chrome 渲染像素验证 |
| S8 报告 | ✅ | `STM32F4-Base内存使用分析报告.md` 已落位 |
| S9 校验 | ✅ | U+FFFD 0 / 引用路径有效 / pre-commit 全过 |
@@ -3,7 +3,7 @@
> 版本:v1.0 日期:2026-09-21
> 适用工程:STM32F4-Base(STM32F407ZGTx @168MHz,FreeRTOS V10.3.1 + CMSIS-RTOS2,Keil MDK v5 / ARMCC)
> 状态:**当前有效策略**(台架验证:产品构建 gen68 干净启动 61 行;存储套件 20/20 PASSED;恢复跑 3/3 PASSED)
> 关联文档:`STM32F4-Base内存使用分析报告.md`(容量审计)/ `RAM使用分析与优化方案.md`(优化路线)
> 关联文档:`RAM使用分析与优化方案.md`(**内存管理主文档**:容量审计 + 优化方案 + 观测工具 + 基线数据)
> 布局实现:`MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct`
## 1. 本文档目的
@@ -24,7 +24,7 @@
3. CCM 存在性已用 `ST-LINK_CLI -r32 0x10000000` 本机实测可读写;**克隆片可能缺 SRAM3,换料必须重测并回退布局**。
4. 工程不进入 Stop/Standby 低功耗模式,两区域掉电保留行为无差异,不构成分配判据。
> 勘误:`STM32F4-Base内存使用分析报告.md`(2026-09-11)内存地图中 "SRAM3 64KB (0x2001_C000)" 为笔误——F407 的 SRAM3 即 CCM,位于 0x10000000(AXI);AHB2 侧只有 SRAM1(112KB)+SRAM2(16KB)。以本文为准。
> 勘误(原《STM32F4-Base 内存使用分析报告》2026-09-11 快照,随主文档合并保留于此):该报告内存地图中 "SRAM3 64KB (0x2001_C000)" 为笔误——F407 的 SRAM3 即 CCM,位于 `0x10000000`(AXI);AHB2 侧只有 SRAM1(112KB)+SRAM2(16KB)。**以本文为准**(主文档 §8.4 与 `内存布局_RAM.svg` 均按此口径)。
## 3. 全板 DMA 使用方清点(DMA 缓冲必须为 SRAM 静态)
+1 -1
View File
@@ -147,7 +147,7 @@
- 现象:串口无 `[FTP] waiting for connection...`、无 `[LFT_INET]` 报错,`[STACK]` 行里 ftpTask 峰值极低(实测 156 B,仅为任务入口),21 端口始终 `connection refused`。
- 根因(2026-09-21 定位并修复):`osEventFlagsWait` 默认**消费**所等待的位,而 `SYS_EVT_NET_READY` / `SYS_EVT_STORAGE_READY` 是**状态位**。`netTask` 等 `STORAGE_READY` 时清掉 bit1,`uiTask` / `timeSyncTask` 等 `NET_READY` 时清掉 bit0 → `ftpTask` 的 `WaitAll(bit0|bit1)` 条件永不成立,任务永久阻塞在内核等待队列(无日志、无报错、静默失效)。
- 修复:**所有就绪位等待加 `osFlagsNoClear`**(`Src/freertos.c` 的 ftpTask、`App/task/ui_task.c`、`App/time_sync.c`、`App/task/net_task.c`、`test/ch395f_test_task.c`);命令位(`SYS_EVT_CFG_SAVE` / `SYS_EVT_NET_RECONFIG`)保持消费型等待不变。
- 验证:修后串口出现 `[FTP] waiting for connection...`、21 端口可连;ftpTask 的 `[STACK]` 峰值握手后升到 ~2.5KB、128KB 传输后升到 ~3.4KB(见内存方案 §0.6,余量告警)。
- 验证:修后串口出现 `[FTP] waiting for connection...`、21 端口可连;ftpTask 的 `[STACK]` 峰值握手后升到 ~2.5KB、128KB 传输后升到 ~3.4KB(该值曾触发余量告警,已在 2026-09-21 把 lftpd 大局部变量静态化后降到 **1184 B / 4096 B,余量 71%**——见内存方案 §1.4/§0.6)。
### 8.7 单客户端限制:端口"通"但随后连接被拒(旧会话未释放)
@@ -243,6 +243,7 @@ graph TD
#### D-4(P2)孤儿文档
`docs/内存管理/内存使用分析规划.md` 无任何入链(项目自有文档中唯一孤儿)。**建议**:并入 `RAM使用分析与优化方案.md` 的"规划"章节,或在 `docs/README.md` 索引中显式列出。
> ✅ **2026-09-21 已处理**:该文档与《STM32F4-Base 内存使用分析报告》一并并入 `RAM使用分析与优化方案.md`(现为内存管理主文档),两个文件已删除;口径与方法论见其 §8.4,快照数据见 `docs/内存管理/data/`。
#### D-5(P2)缺一个文档入口/索引
+4 -3
View File
@@ -61,8 +61,8 @@
> `idle`(128 words)/ `timer`(256 words)由 FreeRTOS 内核创建。所有任务栈从 48KB heap_4 堆分配。
> **栈实测与预算**(2026-09-21,工具 `scripts/stack_report.py` = Keil 静态调用图 + 串口 `[STACK]` 水位;详见 `docs/内存管理/RAM使用分析与优化方案.md` §0.6):
> defaultTask 静态最坏 2776B/4096B(余量 32%)、**ftpTask 实测 3480B/4096B(余量仅 616B = 15%,低于 1024B 底线,待处置)**、uiTask 实测 3804B/16384B(缩栈空间大,内存方案 §1.3 目标 6KB)、netTask 600B、timeSyncTask 420B、testTask(产品构建)348B。
> ⚠ 改栈或加大栈内局部变量前后**必须**重跑该脚本——静态分析低估含递归/间接调用的链(ftpTask 静态 2528B vs 实测 3480B)。
> defaultTask 静态最坏 2776B/4096B(余量 32%)、ftpTask 实测 **1184B/4096B(余量 71%)**(lftpd 大局部缓冲静态化前为 3480B / 15%,见内存方案 §1.4)、uiTask 实测 3824B/16384B(缩栈空间大,内存方案 §1.3 目标 6KB)、netTask 588B、timeSyncTask 420B、testTask(产品构建)348B。
> ⚠ 改栈或加大栈内局部变量前后**必须**重跑该脚本——静态分析低估含递归/间接调用的链(优化前 ftpTask:静态 2528B vs 实测 3480B)。
### 2.2 就绪屏障(Event Flags,取代魔法延时)
@@ -336,4 +336,5 @@ flowchart LR
| 2026-09-13 | `sys_clock` 由 ③基础服务层改归 **④中间件层**(通用平台时间服务,日志/文件/对时共用;文件移入 `App/util/` 与 crc/ringbuf 同目录,include 路径不变):`FatFs` → `sys_clock` 成为**层内引用**,工程反向依赖消除;同日新增的 §1.2 "反向依赖例外" 条目撤销 |
| 2026-09-14 | 通用配置文件(P3):网口配置存储由 `/cfg/netcfg.bin`(NCFG 二进制)迁移至 `/config.ini`(INI `[network] 段`,`App/cfg_file` 引擎 + `cfg_file_store` 双端后端);`net_cfg` 对外 API 不变;旧档首启自动迁移;`cfg_file` 列为 MCU/模拟器共享源码 |
| 2026-09-14 | 运行/自检记录:新增 `App/log/sys_log`(定长 64B 文本记录引擎,MCU 后端 `/log/run.log`·`selftest.log`,defaultTask 唯一写者)+ UI 页面插件 `ui_page_runlog`/`ui_page_selftest`(动态列表)+ `ui_standby` 待机屏(空闲超时切入);密码页错误提示;字体 111→122 字(217 字形);详见 `docs/运行与自检记录设计说明.md` |
| 2026-09-21 | **§2.2 屏障语义修正 + FTP 永不监听缺陷修复**:`osEventFlagsWait` 默认消费所等位,就绪位(`NET_READY`/`STORAGE_READY`)被 netTask/uiTask/timeSync 先消费后,`ftpTask` 的 `WaitAll(两位)` 永不成立 → 任务永久阻塞、21 端口从未监听(静默);5 处就绪位等待统一加 `osFlagsNoClear`(命令位 `CFG_SAVE`/`NET_RECONFIG` 保持消费型)。同轮新增**栈可观测性**:`Src/freertos.c` 的 `stack_probe_poll`(串口 `[STACK]` 水位)+ `scripts/stack_report.py`(Keil 静态调用图 × 运行时水位,判定取两者更坏者);§2.1 任务表补齐实测栈数据(ftpTask 3480B/4096B 余量 15% 待处置)并修正过期的 defaultTask/testTask 栈配置,§3 时序图补 `NoClear` 与 FTP 启动日志,§5 补 FTP 启动门/单客户端实测注意 |
| 2026-09-21 | **§2.2 屏障语义修正 + FTP 永不监听缺陷修复**:`osEventFlagsWait` 默认消费所等位,就绪位(`NET_READY`/`STORAGE_READY`)被 netTask/uiTask/timeSync 先消费后,`ftpTask` 的 `WaitAll(两位)` 永不成立 → 任务永久阻塞、21 端口从未监听(静默);5 处就绪位等待统一加 `osFlagsNoClear`(命令位 `CFG_SAVE`/`NET_RECONFIG` 保持消费型)。同轮新增**栈可观测性**:`Src/freertos.c` 的 `stack_probe_poll`(串口 `[STACK]` 水位)+ `scripts/stack_report.py`(Keil 静态调用图 × 运行时水位,判定取两者更坏者);§2.1 任务表补齐实测栈数据并修正过期的 defaultTask/testTask 栈配置,§3 时序图补 `NoClear` 与 FTP 启动日志,§5 补 FTP 启动门/单客户端实测注意 |
| 2026-09-21 | **栈宽整改落地(内存方案 §1.4:lftpd 大局部缓冲静态化)**:`Drivers/BSP/NET/lftpd/lftpd.c` + `lftpd_log.c` 共 **9 处**大局部数组(`message[256]`/`response[512]`/`name[256]`×2/`buffer[1024]`×2/`read_buffer[512]`/`arg_buf[512]`/`client` 会话上下文/日志 `buffer[256]`)改为函数级 `static`(lftpd 单客户端、全部在 ftpTask 内串行);**ftpTask 实测峰值 3480 → 1184 B(余量 15% → 71%,判定 PASS)**,静态调用图 2528 → 472 B,代价静态 BSS +4,616 B(RW_IRAM1 82,048 / 131,072 B,余量仍 ~48 KB);§2.1 栈实测数据同步,原"涨栈到 1600 words"方案取消;FTP 功能回归 10/10 + 128KB 逐字节一致 |
+2 -2
View File
@@ -387,7 +387,7 @@ pre-commit run --all-files
### 3.3 资源预算(部署红线)
实测(2026-09-11,`docs/内存管理/STM32F4-Base内存使用分析报告.md`):
实测(2026-09-11 快照口径,现汇总于 `docs/内存管理/RAM使用分析与优化方案.md`):
| 资源 | 已用 | 总量 | 利用率 | 余量 | 红线说明 |
|---|---|---|---|---|---|
@@ -517,7 +517,7 @@ pre-commit run --all-files
|---|---|
| 项目全景架构 | `docs/项目架构总览.md` |
| 启动顺序 / 就绪屏障 | `docs/项目启动顺序管理.md` |
| 内存预算详析 | `docs/内存管理/STM32F4-Base内存使用分析报告.md` |
| 内存预算详析 / 优化方案 / 栈观测 | `docs/内存管理/RAM使用分析与优化方案.md`(主文档)· `docs/内存管理/内存内容分配策略.md`(区域与 DMA 规则) |
| 网口配置 / 生效链路 | `docs/网络配置说明.md` |
| 远程显示协议 | `docs/UI 设计/RemoDispBus协议与接收设计说明.md` |
| LVGL 模拟器部署 | `docs/UI 设计/LVGL模拟器部署与使用说明.md`、`simulator/README.md` |