docs(内存): 阶段子项改层级编号 4.阶段.项,消除与顶层章节同号撞面
CI / L0 静态门禁 (pre-commit) (push) Failing after 2m1s
CI / L0.5 主机侧单测 (MSVC) (push) Successful in 21s

根因:`RAM使用分析与优化方案.md` 顶层有 §1 背景 / §2 现状测量(2.1~2.6),而 §4
「方案」下"阶段 N"的子项也用裸号 0.x / 1.x / 2.x——阶段 2 的 2.1/2.2/2.3 与顶层
2.x **完全同号**,阶段 1 的 1.x 又长得像"第 1 章第 x 节"。`doc-links` 对本文自
引用按设计直接放过,于是门禁和人一起看错:AGENTS.md 把"栈余量台账"写成 §1.4,
而 §1.4 其实是阶段 1 的 lftpd 静态化项(上一笔提交的信息里我把它误判成断链,
已 amend 更正)。

改法:阶段子项一律改层级编号,阶段号不变、只加 `4.` 前缀 —— 0.x→4.0.x、
1.x→4.1.x、2.x→4.2.x。共改 14 个标题;文内自引用与实施序列表(S4~S9 那套)、
暂缓清单、§5 预期效果列里的裸号一并补成 §4.x.y。

外部引用同步 15 处:AGENTS.md、lftpd/README ×2、菜单页面设计、RAM预算台账 ×2、
项目架构总览 ×3、FTP_Test_Guide ×3、boot_log_data/README、部署环境要求、审查
报告、ram_baseline.py ×3。最有说服力的两处歧义:审查报告与 RAM预算台账都写
"缩堆见 …§2.2"——既可读成顶层 §2.2(静态大项排行)也可读成阶段 2 第 2 项,
现均为 §4.2.2(阶段 2 第 2 项"缩堆")。

另修 4 处把 §1.4 当"栈台账"的错指:采样有效性校验设计说明 :441/:593/:650 改
指 §2.3(任务栈配置与实测峰值)与 §4.0.6(静态最坏链与判定);id_aliases.json
 的 note 改指 §4.0.6 并标注"原 §1.4,2026-10-22 层级编号"。

冻结与示例:其它文档**变更记录行**里的旧编号按项目纪律不改写(项目架构总览
:367、RAM预算台账 :98),改在被引文档 §4 开头留一张换算表 + 约定说明;把旧号
当语法示例写的 7 处一并换掉(文档规范 ×3、check_doc_links ×3、rename_ref ×1),
否则示例本身指向已改名的锚点。主文档版本行 bump 到 v1.3 / 2026-10-22。

门禁:check_doc_links.py --all --sections-strict → 146 个文档 0 问题(章节引用
2513 处、机器可判定 1491);check_ids.py --strict 增量拦截通过;mem-numbers、
ram_baseline --gate(SRAM1 57.4% / CCM 87.9%)、task-table 全绿;本轮未改任何
.c/.h。16 个文件字节卫生自查 BOM/CR/U+FFFD/行尾空格全清。已提交本地,未推送。
This commit is contained in:
2026-10-22 15:38:14 +08:00
parent f4b9bd57d5
commit 504db26698
16 changed files with 84 additions and 76 deletions
@@ -1,6 +1,8 @@
# STM32F4-Base RAM 使用分析与优化方案(内存管理主文档)
> 版本:v1.2 日期:2026-09-21
> 版本:v1.3 日期:2026-10-22(v1.2 2026-09-21)
> 变更:v1.3 §4 的阶段子项改为**层级编号**(见下"编号约定");同步 2026-10-22 现状数字与
> `data/ram_report_20261022.md` / `data/stack_report_20261022.md` 两份新快照。
> 变更: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`(静态调用图 × 运行时栈水位)+ 源码静态分析 + 网络调研
@@ -15,7 +17,7 @@
- 主 SRAM 128KB 中静态区曾占 **122.7KB**,余量仅 5.3KB
- FreeRTOS 堆 48KB 里装下 ~33.5KB 任务栈,可分配余量只有 ~12KB
- F407 另有 **64KB CCM RAM**:**已于 2026-09-21 启用**并把 FreeRTOS 堆迁入(§1.2)
- F407 另有 **64KB CCM RAM**:**已于 2026-09-21 启用**并把 FreeRTOS 堆迁入(§4.1.2)
- 起步时没有任何运行时测量手段:不知道每个任务实际用了多少栈,改栈全靠拍脑袋
当前状态(**以 §2.1 受管快照为准**,由 `.map` 自动生成;2026-10-22 `@build`):RW_IRAM1 **75,264 / 131,072 B(57.4%)**、RW_CCM **57,376 / 65,280 B(87.9%)**。✅ **任务栈:`stack_report.py` 9/9 PASS**(2026-10-22 静态最坏链 + 同日真机 `[STACK]` 水位,`defaultTask` 最坏 3024B/5120(余 40%)、`protectTask` 872B/2048(余 57%),见 `data/stack_report_20261022.md`);**卡点已从 SRAM1 移到 CCM**(余 7,904 B)。历史:`defaultTask` 曾于 2026-10-04 达 3664B/4096 ≈10%,登记为 S-1,2026-10-10 削链后销项(详见"2026-10-10 复测"小节)。
@@ -47,7 +49,7 @@
| 项 | 说明 | 来源 |
|---|---|---|
| 主 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 |
| CCM SRAM (0x10000000) | 64 KB;FreeRTOS 堆于 2026-09-21 迁入(§4.1.2) | scatter `STM32F407-Demo.sct:25`;RM0090 §3.1.1 |
| FreeRTOS 堆 (heap_4) | **56 KB(57,344 B)**,**位于 RW_CCM**;2026-10-12(B5)由 48 KB 扩容(protectTask/iec104Task 栈入堆,CCM 水位 87.9% < 92% 门禁);旧 48KB 池实测 `free=8,784 / min_ever=4,848` B(2026-10-03),新池 min_ever 待上板复测 | `Inc/FreeRTOSConfig.h:71` `configTOTAL_HEAP_SIZE`(L1:`.ioc` `FREERTOS.configTOTAL_HEAP_SIZE`) |
| C 库堆/栈 (startup) | 512 B / 1 KB,启动期 main() 用 | map `startup_stm32f407xx.o` HEAP/STACK 节 |
@@ -61,7 +63,7 @@
| 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) |
| 5 | 4,616 | `lftpd.o` | FTP 会话静态缓冲(2026-09-21 由栈迁入,§4.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 封装静态区 |
@@ -77,7 +79,7 @@
> (卷探针 `FIL` 528 + `default_fatfs`/`fs_work`)、`cfg_file_store_mcu.o` 1,104 B(2 处 `FIL` = 1,056)。
> 同期 Flash **−600 B**(删旧档迁移)。台账:`data/ram_report_20261010.md`。
**RW_CCM(CCM)**:`heap_4.o` 49,184 B(FreeRTOS 堆 49,152 B + RW 32 B)——2026-09-21 由主 SRAM 迁入(§1.2),不再计入主 SRAM。
**RW_CCM(CCM)**:`heap_4.o` 49,184 B(FreeRTOS 堆 49,152 B + RW 32 B)——2026-09-21 由主 SRAM 迁入(§4.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。
@@ -121,12 +123,12 @@
| idle(内核) | `configMINIMAL_STACK_SIZE=128` | 0.5KB | 空闲 | 未观测(未取句柄) |
| timer(内核) | `configTIMER_TASK_STACK_DEPTH=256` | 1KB | 软定时器 | 未观测 |
> 任务栈全部从 FreeRTOS 堆(56KB = `configTOTAL_HEAP_SIZE` 57,344 B;2026-09-21 迁 CCM、2026-10-12 扩容)分配。**"配置栈"列以 `Src/freertos.c` 为唯一来源;实测峰值/余量判定以 §0.6 的 `stack_report` 基线为准**(本表数值同步自该基线)。
> 任务栈全部从 FreeRTOS 堆(56KB = `configTOTAL_HEAP_SIZE` 57,344 B;2026-09-21 迁 CCM、2026-10-12 扩容)分配。**"配置栈"列以 `Src/freertos.c` 为唯一来源;实测峰值/余量判定以 §4.0.6 的 `stack_report` 基线为准**(本表数值同步自该基线)。
### 2.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+ 栈。
1. **lftpd 的 C 库 `malloc` 隐患(✅ 2026-09-10 已修,见 §4.2.1)**:`lftpd_io.c` 的 3 处 `malloc`/`free`(路径规范化/`DIR`)与 `lftpd.c` 的 7 处 `free` 已全部改用 `pvPortMalloc`/`vPortFree`(C 库堆仅 startup 的 512B,原写法长路径必炸);当前源码无裸 `malloc` 残留。
2. **lftpd 大栈局部变量(✅ 2026-09-21 已整改,见 §4.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 结论:紧张的三个根因(含 2026-09-11 审计量化)
@@ -134,7 +136,7 @@
| 根因 | 占用 | 可回收性 |
|---|---|---|
| ① LVGL 内存池 40KB 对 1bpp 小屏菜单过大 | 40KB → **16KB ✅ 2026-10-15** | 高(目标 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 缩减) |
| ② 任务栈"往大了给",无实测依据 | 38,912 B(38 KB 现值,§2.6.1) | 高(§4.0.6 已有实测:uiTask 3.8/16KB、netTask 0.6/4KB → §4.1.3 缩减) |
| ③ 64KB CCM 曾完全未利用,缓冲挤在主 SRAM | — | ✅ 2026-09-21 已启用(堆迁入 CCM);2026-10-22 CCM 余量 7,904 B(**全局卡点**)|
量化补充(来源:2026-09-11 审计快照,口径与方法见 §8.4):
@@ -142,9 +144,9 @@
- 两池(FreeRTOS 48KB + LVGL 40KB)曾占主 SRAM 的 **68%**,是当时"SRAM 几乎耗尽(95.4%,余 5.8KB)"的直接原因;**堆迁 CCM 后该结构性压力已解除**(2026-10-22 RW_IRAM1 57.4%,见 §2.1)
- heap_4 **不合并相邻空闲块** → 长期运行需盯碎片,`min_ever`(2026-10-22 实测 8,464 B,56KB 池)是硬指标而非 `free`
- 未映射资源:SRAM3 64KB(scatter 未映射;clone 芯片可能无此区);CCM 已用
- 曾经的三条中风险项:新任务无处安置(已缓解)、堆碎片(仍存)、栈溢出检测缺失 → **已于阶段 0 开启 `configCHECK_FOR_STACK_OVERFLOW=2` 并实测抓到过溢出**(§0.1)
- 曾经的三条中风险项:新任务无处安置(已缓解)、堆碎片(仍存)、栈溢出检测缺失 → **已于阶段 0 开启 `configCHECK_FOR_STACK_OVERFLOW=2` 并实测抓到过溢出**(§4.0.1)
小项:FF_FS_TINY=0(每打开文件 512B 扇区窗,阶段 1.5 可改 1)、`ui_net_sink` PC 远程显示缓冲常驻 6.4KB(产品构建可加开关关闭,阶段 1.5);lftpd 栈局部变量 ✅ 2026-09-21 已静态化(§1.4)。
小项:FF_FS_TINY=0(每打开文件 512B 扇区窗,§4.1.5 可改 1)、`ui_net_sink` PC 远程显示缓冲常驻 6.4KB(产品构建可加开关关闭,§4.1.5);lftpd 栈局部变量 ✅ 2026-09-21 已静态化(§4.1.4)。
### 2.6 堆 / LVGL 池 / C 库 / Flash 明细
@@ -189,7 +191,7 @@
| 组件 | 大小 | 说明 |
|---|---|---|
| 启动栈 | 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 库堆 | 512 B | `Heap_Size EQU 0x200` → **自研代码禁用 C 库 `malloc`**(AGENTS.md 铁律;lftpd 的 `malloc` 隐患见 §2.4-1 与 §4.2.1) |
| C 库代码(Flash) | ~4.2 KB | printf / 浮点 / 字符串 |
#### 2.6.4 Flash 1 MB 分布
@@ -207,7 +209,7 @@
> **2026-10-11 裁剪 LVGL 死代码(原审查报告 F-1/F-2/F-3,已销项)**:`Lib/lv_conf.h` 显式关掉 10 个 `LV_DRAW_SW_SUPPORT_*`(只留 I1)、5 个未用控件类(label / button / buttonmatrix / bar / textarea)、`LV_USE_THEME_SIMPLE`、`LV_USE_FLEX` / `LV_USE_GRID` → **`ER_IROM1` 274,724 → 210,148 B(省 64,576 B = 旧镜像的 23.5%)**,Flash 占比 26.3% → **20.1%**。
> 对象级对照(`.map` 组件表,Code+RO+RW):`blend_to_*` 40,582 → 1,338(其中 `blend_to_i1` 自身 5,272 → 1,338——它按**源**格式的分支也一并消失);控件类 13,397 → 0;`lv_theme_simple` 866 → 0;`lv_draw_sw_transform` 4,340 → 598;`lv_theme_mono` 1,400 → 1,036;镜像内对象数 227 → 212。实测比原报告估算(54,630 B)多省约 10 KB,差额正是这些"锚点消失后连带消失的分支"。
> ⚠ **配套本地补丁**:`lv_draw_sw_blend_to_i1.c` 的 `lv_color16_from_u16` 上游 9.3.0 漏了定义处守卫(唯一使用点全在 `#if LV_DRAW_SW_SUPPORT_RGB565_SWAPPED` 内)→ 关掉该格式后 ARMCC 报 `#177-D declared but never referenced`(破坏"0 警告"门禁)。已按使用点补同一守卫,升级 LVGL 时若上游已修可去掉。
> 验证:ui-verify 五步全过——**11 页内容黄金参考在裁剪前生成、裁剪后逐字节一致**,菜单 8 状态像素一致,LVGL 池水位 5,440 → 5,368 B(−72 B,样式表更小,一次性残留仍 80 B);`@build` 0 错 0 警;`@test` 真机 PASS。详见 `../UI设计/UI_设计架构.md` §1.4 第 10 条。
> 验证:ui-verify 五步全过——**11 页内容黄金参考在裁剪前生成、裁剪后逐字节一致**,菜单 8 状态像素一致,LVGL 池水位 5,440 → 5,368 B(−72 B,样式表更小,一次性残留仍 80 B);`@build` 0 错 0 警;`@test` 真机 PASS。详见 `../UI设计/UI_设计架构.md` §4.1.4 第 10 条。
> **长效防复发**:已建门禁 `scripts/check_lv_conf_sync.py`(pre-commit 钩子 `lv-conf-sync`)——比对两份 `lv_conf.h` 的**生效值**(未定义则取 `lv_conf_internal.h` 兜底默认),并抓无效宏名与重复定义;首次跑揪出两端共 26 个 9.3 不存在的选项、3 处重复定义、10 项靠默认值隐式生效的开关,已清理(本次清理对镜像体积中性)。详解见 `../代码规范/pre-commit使用说明.md` §2.15。
![Flash 布局](../images/内存布局_FLASH.svg)
@@ -231,11 +233,17 @@
## 4. 方案
> **编号约定(2026-10-22 起)**:本节的子项按层级写成 **`4.<阶段>.<项>`**(如 `§4.1.4`),
> 不再用裸 `0.x / 1.x / 2.x`——旧写法与本文顶层章节 `§1 背景`、`§2 现状测量` **同号撞面**,
> 人和 `doc-links`(自引用直接放过)都会读错;实测最坑的是"缩堆"那条既像 §2.2(静态大项排行)
> 又像阶段 2 第 2 项。对照:`0.x→4.0.x`、`1.x→4.1.x`、`2.x→4.2.x`(阶段号不变、只加 `4.` 前缀)。
> **其它文档的变更记录行里的旧编号按项目纪律冻结不改写**,需要追溯时按本表换算。
> ⏸ **实施状态(用户裁定 2026-10-15)**——本节的"阶段/天数"是当初的排期设想,现在按下面口径执行:
>
> - **保持常开**:阶段 0 的测量能力 + 阶段 3 的长效机制(`mem-budget` 92% 门禁、`RAM预算台账.md` 受管区、`ram_baseline.tsv` 趋势、`[STACK]` 水位与 `stack_report.py` / `ram_report.py`)。它们已落地、零维护成本,且正是"何时该重开优化"的传感器。
> - **已落地的优化不回退**:1.1 LVGL 池 40→16 KB(✅ 2026-10-15,SRAM1 74.9% → **56.1%**)、1.4 lftpd 大栈局部变量改静态(✅ 2026-09-21)。
> - **整簇暂缓、不再排期**:1.2 启用 CCM 承载 UI 侧缓冲、1.3 任务栈按实测缩减、2.2 缩减 `configTOTAL_HEAP_SIZE`、2.3 任务/队列全静态化,以及把池进一步试到 8 KB。
> - **已落地的优化不回退**:§4.1.1 LVGL 池 40→16 KB(✅ 2026-10-15,SRAM1 74.9% → **56.1%**)、§4.1.4 lftpd 大栈局部变量改静态(✅ 2026-09-21)。
> - **整簇暂缓、不再排期**:§4.1.2 启用 CCM 承载 UI 侧缓冲、§4.1.3 任务栈按实测缩减、§4.2.2 缩减 `configTOTAL_HEAP_SIZE`、§4.2.3 任务/队列全静态化,以及把池进一步试到 8 KB。
>
> **暂缓理由与重开条件** → `../问题记录/项目审查与优化建议报告.md` §5.3。一句话:**当前没有功能在等这块预算**,而搬迁要动 scatter、要逐条过 DMA 可达性清单(违例是静默传错不是报错)、缩堆还得上板复测 `min_ever`——收益不抵风险与工时。下面各节**保留作重开时的实施依据,不代表现在要做**。
@@ -243,7 +251,7 @@
> 原则:没有数据不动刀。本阶段全部是"只增不减"的改动,零功能风险。
#### 0.1 栈溢出检测(防身)
#### 4.0.1 栈溢出检测(防身)
`Inc/FreeRTOSConfig.h` 增加:
@@ -270,7 +278,7 @@ void vApplicationMallocFailedHook(void)
成本:每次上下文切换几十周期,不占 RAM。
#### 0.2 运行时栈水位(已落地:`stack_probe_poll`,2026-09-21)
#### 4.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()`(剩余字节,内部走
@@ -291,9 +299,9 @@ void vApplicationMallocFailedHook(void)
- `uxTaskGetStackHighWaterMark` 返回**任务创建以来**的最小剩余栈(单位 word=4B);`used = 配置 − 剩余` 即该任务历史峰值栈占用。
- **测量必须覆盖全功能路径**(高水位是历史最小值,只反映跑过的路径):全菜单遍历、FTP 上传/下载大文件、CH395F 断链重连、NAND 格式化、时间同步。建议 `@test` 全回归 + 1 小时浸泡。
- 未覆盖 idle/timer(内核静态小栈、纯循环,未取句柄);构建期静态侧见 §0.6。
- 未覆盖 idle/timer(内核静态小栈、纯循环,未取句柄);构建期静态侧见 §4.0.6。
#### 0.3 map 静态占用报告脚本
#### 4.0.3 map 静态占用报告脚本
脚本 `scripts/ram_report.py`(Python,已随仓库提供):
@@ -304,15 +312,15 @@ void vApplicationMallocFailedHook(void)
- ✅ **2026-10-12 落地**:`scripts/ram_baseline.py --trend` 与基线 TSV
(`scripts/ram_baseline.tsv`)做 diff,任何对象 RW+ZI 增量 >1KB 即高亮
#### 0.4 (可选)运行时间统计
#### 4.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() 启动期栈峰值
#### 4.0.5 (可选)main() 启动期栈峰值
启动期跑在 startup 的 1KB C 栈上(CubeMX 初始化 + 各驱动 init,含 BBT 全扫描)。ARMCC 可用 `__current_stack_usage()` 在 `app_main_init` 末尾打点,确认 1KB 够用;不够则调 scatter 的 `STACK` 节(与任务栈无关,单独预算)。
#### 0.6 静态栈分析 + 汇总工具(构建期,零目标依赖;2026-09-21 落地)
#### 4.0.6 静态栈分析 + 汇总工具(构建期,零目标依赖;2026-09-21 落地)
**UV4 每次构建已自动生成静态调用图** `MDK-ARM/STM32F407-Demo/STM32F407-Demo.htm`
(标题 "Static Call Graph",随 `@build` 刷新,**无需**给 armlink 加 `--callgraph`)。每个函数
@@ -340,7 +348,7 @@ void vApplicationMallocFailedHook(void)
| `--min-free-pct` | `30` | 要求的最小余量百分比 |
- **判定规则**:`余量 = 配置 − max(静态最坏深度, 实测峰值)`,须 `≥ max(1024 B, 配置×30%)`;任一任务不达标 → 脚本退出码 **1**(可挂门禁),全达标 → 0,HTM 缺失 → 2。
- **何时跑**:① 改栈或加大局部变量前后;② 改 FatFs/lftpd/LVGL 等第三方配置后;③ 任务职责变化时。串口日志覆盖的路径越全越准(路径覆盖要求见 §0.2)。
- **何时跑**:① 改栈或加大局部变量前后;② 改 FatFs/lftpd/LVGL 等第三方配置后;③ 任务职责变化时。串口日志覆盖的路径越全越准(路径覆盖要求见 §4.0.2)。
- **报告内容**:每任务预算表(配置/静态/实测/最坏/余量/判定)、各任务最坏调用链、帧宽 Top15(找"宽度大户")、堆 free/min_ever。
首份基线(2026-09-21,产品构建 + FTP 会话实测;产物 `docs/内存管理/data/stack_report_20260921.md`):
@@ -349,7 +357,7 @@ void vApplicationMallocFailedHook(void)
|---|---|---|---|---|---|---|
| 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** |
| **ftpTask**(§4.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 |
@@ -357,12 +365,12 @@ void vApplicationMallocFailedHook(void)
实测堆:`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)。
**处置结果(2026-09-21 当日闭环,走 ②→①)**:先做 §4.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/上下文量级)。
- netTask / uiTask / testTask / timeSyncTask / **ftpTask**:实测 > 静态——静态是**下界**(互递归、间接调用、ISR 上下文保存 ~100B 未计入)。ftpTask 最典型:优化前静态 2528 B(控制通道握手链)**低估了 950 B**,真实峰值 3480 B 只在 128KB STOR 传输时暴露 → **不跑真实业务就定栈 = 拍脑袋。** §4.1.4 静态化后静态 472 B / 实测 1184 B,两者重新收敛(差距回到 ISR/上下文量级)。
- 本次可观测性的第一个产出不是数字而是**缺陷**:ftpTask 因就绪位被其它等待者消费而**从未启动**(21 端口一直无监听),修好后才有了上面这组数据——详见 `docs/测试说明/FTP_Test_Guide.md` §8.8。
要点与边界:
@@ -370,7 +378,7 @@ void vApplicationMallocFailedHook(void)
- defaultTask 的静态最坏链是**配置保存/旧档迁移**路径,不是首启探针路径(**2026-09-21 当时的链;旧档迁移已于 2026-10-10 删除、日志行缓冲已按模块收紧,现链见下方"2026-10-10 复测"**):
`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)。
- 帧宽榜直接点名宽度大户(自身帧):`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——与 §4.1.4 的整改项对得上。**lftpd 三项已于 2026-09-21 静态化(§4.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 是**下限**(实测 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 单客户端复用,探测会占掉唯一会话(见 `docs/测试说明/FTP_Test_Guide.md` §8.9)。
@@ -433,14 +441,14 @@ FreeRTOS 堆不变(`free=17248 / min_ever=9312`)。验证链:`@build` 0/0
### 阶段 1:静态大项削减(收益最大,3~5 天)
#### 1.1 LVGL 内存池 40KB → 16KB(✅ 已执行 2026-10-15,省 24,576 B)→ 8KB(待评估)
#### 4.1.1 LVGL 内存池 40KB → 16KB(✅ 已执行 2026-10-15,省 24,576 B)→ 8KB(待评估)
- 依据:屏幕仅 160×160×1bpp,菜单为轻量控件树;LVGL 内存池只装对象/样式/字符串缓冲,与像素缓冲无关。40KB 来自模板默认值,无适配依据。
- **已落地**:`Lib/lv_conf.h` 与 `simulator/lv_conf.h` 同步改 `(16 * 1024)`——两端必须同值,`lv-conf-sync` 门禁按**生效值**比对。实测水位见 §2.6.2(最重页面峰值 6,568 B = 池的 40%);`@build` 0 错 0 警,ZI 精确 −24,576 B → SRAM1 由 74.9% 降到 **56.1%**(余 57,504 B)。
- 验证口径(本次即用它):`simulator/tests/check_menu_rows.py`(菜单 8 状态像素 + 池基线 5,368 B)+ `simulator/tests/check_content_pages.py`(11 页黄金参考逐字节 + 往返池水位 5,448 B)+ 真机 `menu build done: used=` 日志。
- **再降 8KB 的前置**:全量 UI 回归(PC 模拟器 + 真机)无 `LV_ASSERT_MALLOC`/分配失败;若 8KB 不够就停在 16KB,不勉强。
#### 1.2 启用 CCM RAM(+64KB 预算空间)
#### 4.1.2 启用 CCM RAM(+64KB 预算空间)
依据 RM0090 §3.1.1:F407 的 64KB CCM @0x10000000 仅 CPU 可访问(DMA 不可达、无 cache)。
@@ -469,7 +477,7 @@ FreeRTOS 堆不变(`free=17248 / min_ever=9312`)。验证链:`@build` 0/0
验证:map 中出现 `RW_CCM` 且 `*(CCM_DATA)` 各对象地址落在 0x10000000~0x100FFFF;`ram_report.py` 同时输出两个 Region 水位。
#### 1.3 任务栈按实测数据缩减(省 ~17KB;目标值已按 2026-09-21 实测修正)
#### 4.1.3 任务栈按实测数据缩减(省 ~17KB;目标值已按 2026-09-21 实测修正)
定栈规则(写入台账,长期执行):
@@ -485,7 +493,7 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
|---|---|---|---|
| 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 余量并复测 |
| ftpTask | 4KB | **3KB** | 实测 1184B(§4.1.4 静态化后)→ 1776+1024=2800B → 3KB;再降须放宽 ISR 余量并复测 |
| testTask | 8KB | **2KB** | 产品构建空转(实测 348B)→ 1024+1024;存储套件构建按该构建静态值另放大 |
| defaultTask | 4KB | **保持 4KB** | 2026-10-10 削链后静态最坏 2776B / 实测 2152B(余 32%);曾达 5312B 溢出判据,见"2026-10-10 复测" |
| timeSyncTask | 2KB | **保持 2KB** | 实测 420B → 1024+1024=2048B,无缩减空间 |
@@ -494,7 +502,7 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
每改一批 → `@build` + 全功能回归 + 复测高水位(`free_min` 应 ≥ 新栈×30%),不满足则回调。
#### 1.4 lftpd 大栈局部变量 → 静态缓冲(✅ 2026-09-21 完成;ftpTask 峰值 3480 → 1184 B)
#### 4.1.4 lftpd 大栈局部变量 → 静态缓冲(✅ 2026-09-21 完成;ftpTask 峰值 3480 → 1184 B)
已把 `lftpd.c` / `lftpd_log.c` 中 **9 个大局部缓冲改为函数级 `static`**(lftpd 单客户端,全部调用与日志都在 ftpTask 内串行执行,静态化安全;各函数**各自独立缓冲**,无嵌套/别名冲突):
@@ -514,25 +522,25 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
> lftpd 属第三方代码(pre-commit 排除范围),只做最小侵入改动(每处仅加 `static` + 单行注释),改动点在本台账登记。
#### 1.5 其他小项
#### 4.1.5 其他小项
- `Lib/FatFs/ffconf.h:32` `FF_FS_TINY` 0→1(每打开文件省 512B 扇区窗;FAT32 适用;`FF_FS_REENTRANT=0` 现状下安全)
- `ui_net_sink`(PC 远程显示,6.4KB):加 `APP_UI_NET_SINK` 编译开关,产品固件可关;保留时缓冲随 1.2 迁 CCM(⏸ **1.2 已暂缓,实际仍在 SRAM1**)
- `ui_net_sink`(PC 远程显示,6.4KB):加 `APP_UI_NET_SINK` 编译开关,产品固件可关;保留时缓冲随 §4.1.2 迁 CCM(⏸ **§4.1.2 已暂缓,实际仍在 SRAM1**)
- `CH395F_TCP_MSS=1024` 已是压缩值,DMA 缓冲 `MSS×2+4` 为协议决定,不动
### 阶段 2:堆与动态分配治理(1~2 天)
#### 2.1 修复 lftpd 的 malloc 隐患(必修)
#### 4.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
#### 4.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 (可选,进阶)任务/队列全静态化
#### 4.2.3 (可选,进阶)任务/队列全静态化
`configSUPPORT_STATIC_ALLOCATION=1` 已开。用 `osThreadNew` 的 `attr.cb_mem`(`StaticTask_t` + 静态栈数组)可把栈完全移出堆,预算 100% 透明、堆只剩小对象分配。改动面涉及 CubeMX 生成的 `freertos.c` 属性块,收益(~15KB 确定性)与改动成本权衡后决定,**不作为必做项**。
@@ -546,16 +554,16 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
## 5. 预期效果
| 项目 | 现状(2026-09-21 实测,**主 SRAM 数字已随代码更新,以 §2.1 为准**) | 剩余项全部落地后(1.1/1.3/1.5/2.2) |
| 项目 | 现状(2026-09-21 实测,**主 SRAM 数字已随代码更新,以 §2.1 为准**) | 剩余项全部落地后(§4.1.1/§4.1.3/§4.1.5/§4.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 堆,历史当时值。2026-10-12 B5 后堆 56 KB / 87.9%) | ~56KB / 64KB(堆 32K + 池 16K + UI 缓冲 + BBT) |
| 任务栈合计 | 38,912 B(38.0KB) | ~21.5KB(§1.3 修正目标) |
| 任务栈合计 | 38,912 B(38.0KB) | ~21.5KB(§4.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)。
> 已落地项(不再计入预期):§4.1.2 堆迁 CCM(2026-09-21)、§4.1.4 lftpd 静态化、§4.2.1 lftpd `malloc`→`pvPortMalloc`(2026-09-10)。
> 估算假设:LVGL 池缩到 16KB(8KB 未验证)且迁 CCM;`ui_net_sink` 产品构建关闭;`ui_port` 帧缓冲 + BBT 位图迁 CCM;堆缩 48K→32K。落地顺序与每步验证见 §6。
>
> ⏸ **2026-10-15 用户裁定:本表"目标"列所依赖的搬迁与缩堆整簇暂缓**(理由与重开条件 → `../问题记录/项目审查与优化建议报告.md` §5.3)。已落地部分只有 **1.1 池 40→16 KB**(SRAM1 实测 **73,568 B / 56.1%**,余 57,504 B)与 1.4;本表保留作重开时的收益测算依据,**当前实际水位以 `RAM预算台账.md` 受管区为准**,不要拿本表"目标"列当现状。
@@ -567,12 +575,12 @@ ISR 余量:该任务运行期间可能被中断打断的最深中断链(SPI/
| 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 全量回归(模拟器 + 实机),无断言失败 | ✅ **2026-10-15**(11 页黄金参考 + 池水位 5,448 B + `@test` 真机 `used=3192`;实测峰值 6,568 B = 池的 40%,§2.6.2) |
| S6 | 1.2 CCM(scatter + section + 门禁宏) | map 显示 `RW_CCM`;关 `APP_CCM_ENABLE` 也能编译通过 | ⏸ **暂缓(2026-10-15 裁定)**;已完成部分仅 `RW_CCM` + 堆迁入(09-21),缓冲 section 迁移与门禁宏不再排期 |
| S7 | 1.3 栈缩减(按 S3 数据)+ 复测 | 每任务 `free_min ≥ 新栈×30%`,无溢出钩子触发 | ⏸ **暂缓(2026-10-15 裁定)**(目标值已按 09-21 实测修正,重开时可直接用) |
| S8 | 2.2 堆 48K→32K + 复测 | 全负载 `heap free ≥ 8KB` | ⏸ **暂缓(2026-10-15 裁定)**;⚠ 2026-10-12 B5 反向扩容至 56 KB,重开时须重跑 `stack_report.py` + 上板 `min_ever` |
| S9 | 1.5 小项 + 台账/门禁/AGENTS.md | `@build` + `@test` 全绿 | ◐ AGENTS.md 内存条目已增补(09-21);台账 / 趋势 / `mem-budget` 三件套 ✅ 2026-10-12(**常开,不受暂缓影响**) |
| S4 | §4.1.4 lftpd 静态化 + §4.2.1 malloc 修复 | FTP STOR/RETR 大文件 + 长路径回归 | ✅(§4.2.1 于 2026-09-10、§4.1.4 于 2026-09-21 完成) |
| S5 | §4.1.1 LVGL 池 40K→16K | UI 全量回归(模拟器 + 实机),无断言失败 | ✅ **2026-10-15**(11 页黄金参考 + 池水位 5,448 B + `@test` 真机 `used=3192`;实测峰值 6,568 B = 池的 40%,§2.6.2) |
| S6 | §4.1.2 CCM(scatter + section + 门禁宏) | map 显示 `RW_CCM`;关 `APP_CCM_ENABLE` 也能编译通过 | ⏸ **暂缓(2026-10-15 裁定)**;已完成部分仅 `RW_CCM` + 堆迁入(09-21),缓冲 section 迁移与门禁宏不再排期 |
| S7 | §4.1.3 栈缩减(按 S3 数据)+ 复测 | 每任务 `free_min ≥ 新栈×30%`,无溢出钩子触发 | ⏸ **暂缓(2026-10-15 裁定)**(目标值已按 09-21 实测修正,重开时可直接用) |
| S8 | §4.2.2 堆 48K→32K + 复测 | 全负载 `heap free ≥ 8KB` | ⏸ **暂缓(2026-10-15 裁定)**;⚠ 2026-10-12 B5 反向扩容至 56 KB,重开时须重跑 `stack_report.py` + 上板 `min_ever` |
| S9 | §4.1.5 小项 + 台账/门禁/AGENTS.md | `@build` + `@test` 全绿 | ◐ AGENTS.md 内存条目已增补(09-21);台账 / 趋势 / `mem-budget` 三件套 ✅ 2026-10-12(**常开,不受暂缓影响**) |
每步都必须 `@build` 通过(0 错误 0 警告)后再进入下一步;涉及 UI 的步骤用 `simulator` 先过一遍再上实机。
@@ -629,7 +637,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`(`malloc`→`pvPortMalloc` 已修,阶段 2.1) |
| lftpd 缓冲/malloc | `Drivers/BSP/NET/lftpd/lftpd.c`(大缓冲已静态化,§4.1.4)、`lftpd_io.c`(`malloc`→`pvPortMalloc` 已修,阶段 2.1) |
### 8.4 分析口径与数据来源(原《内存使用分析规划》并入)