Files
STM32F4-Base/docs/内存管理/存储栈RAM优化调研.md
T
wandering1 0f7b5a87fb chore(编号): 存量编号全量转语义短语,门禁识别面与语料同批重打
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号)
一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、
采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。

门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏
undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8
reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF;
id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。

test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字,
是本批唯一改变构建产物的文件集合。

为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged
部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、
不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。

实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01%
(基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 /
告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。

基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处
引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使
识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。

⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二
根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有),
今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。

固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0;
App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889
断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿;
check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带
BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。

.cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法:
Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。

同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%,
RW_CCM 57,376 B / 87.9%,阈值 92%。
2026-10-26 12:28:14 +08:00

141 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 存储栈 RAM 优化调研
> 版本 v1.2 日期 2026-10-23(v1.2:新增 §5 页缓冲可合并性论证,承接原报告那条待评估项) 作者 AI 辅助调研
> 数据来源:`data/ram_report_20260921.md`(.map 静态占用)+ 逐行代码核验(`nand_ftl.c` / `gd5f2gq5ue.c` / `Lib/dhara` / `Lib/FatFs/ffconf.h` / `lftpd*`)
> 定位:`RAM使用分析与优化方案.md` 的存储专项补充;优化项编号 P0/P1/P2,与 `docs/内存管理/RAM使用分析与优化方案.md` §6 的 S 序号在"规划"一节对齐
> v1.1 清理(2026-09-21 同日):移除已落地的 BBT 页缓冲收缩(−1,776B,commit `be1a30a`)与编程回读校验整体移除的实施记录(详见 GD5F TRAP-GD5F-05),以及过期逐模块基线表;仅保留未实施项与回归要求
> ⏸ **实施状态(用户裁定 2026-10-15)**:本文**全部未实施项整簇暂缓、不再排期**——`s_copy_buf` 分块(原报告的同一评估项)、`APP_CCM_ENABLE` 的 ①② 迁 CCM、以及依赖"堆 48K→32K"的第三步。当前没有功能在等这块预算,而搬迁要逐条过 DMA 可达性清单(违例是静默传错不是报错)。**保持常开**的是测量与门禁面(`ram_report.py` / `stack_report.py` / `mem-budget` 92% 门禁 / 台账 / 趋势)。理由与重开条件 → `RAM使用分析与优化方案.md` §4;本文保留作重开时的实施依据,**不代表现在要做**。
## 1. 现状约束(不变,是下述全部方案的依据)
- 存储栈静态 RAM 全在 **RW_IRAM1(主 SRAM 128KB)**;页缓冲(nand_ftl 3×2KB + gd5f 页缓冲/位图)走 SPI1 DMA(阈值 >1024B),**DMA1/DMA2 物理不可达 CCM** → 不能直迁 CCM(唯一途径:迁移 + `gd5f_dma_buf_ok()` 运行时守卫自动回落 CPU 轮询(2026-09-21 加固),不会静默失败,代价仅每页零点几 ms CPU)
- 3×2KB 页缓冲是结构成本不可直缩:dhara journal 要求整页 checkpoint(`journal.c:196` 整页 memset);512B 扇区落 2KB 页必须整页 RMW;可动的只有 `s_copy_buf`(见 §2 分块方案)
- CPU-only 缓冲(lftpd static ~3.6K / cfg_file 0.7K / FATFS win 0.6K / lftpd_io 0.6K)迁 CCM 零代价(见 §2 迁 CCM 方案 ①)
- 栈上 FIL 重复占深链:syslog 两处、cfg 存储两处、开机探针各一个局部 `FIL`(每个含 512B 私有 buf,≈552B),全压在 defaultTask 最坏链 2,776B 内(靶点见 §2 `FF_FS_TINY` 行)
- FatFs 配置已低 RAM 取向:`FF_MAX_SS=512`、`FF_USE_LFN=0`、`FF_VOLUMES=1`、`FF_FS_LOCK=0`;`FF_FS_REENTRANT=1` 为 defaultTask/ftpTask 并发访问必需,勿动
- 教训(2026-09-21):任何比对式校验必须在**两份独立副本**间进行(U-Boot `sf test` 双缓冲形态)——旧 GC 拷贝自比对缺陷已随回读校验整体移除而消失;若未来测试构建恢复回读诊断,勿重蹈此坑
## 2. 方案清单(未实施)
| # | 方案 | 省 RAM | 区域 | 风险 | 验证 |
|---|---|---:|---|---|---|
| `s_copy_buf` 分块 | `s_copy_buf` 2048→512:GC 拷贝改分段 read→program_load(`program_load`/`read_from_cache` 支持列地址);纯 RAM 优化(GC 拷贝暂存减半) | **−1,536B(可选,不计入当前预期)** | SRAM1 | 低(仅动拷贝机制) | 待 RAM 余量需要时再议 |
| lftpd 缓冲收紧 | lftpd 缓冲收紧:response 512→256、read_buffer 512→256、arg_buf 512→256、name 256→128 ×2(本卷仅 8.3 短名,路径深度浅);数据块 2×1024 与 message 不动 | **−1,024B** | SRAM1 | 低(FTP 客户端实测行长远低于新值) | `@build` + FTP 上传/下载/LIST 闭环 + `@test` |
| `FF_FS_TINY` 共享窗 | `FF_FS_TINY=1`:删每 FIL 私有的 512B buf,文件数据走 `FatFs->win` 共享窗 | 静态 **−528B**;且栈上每处 FIL 峰值 **−528B**(defaultTask 最坏链 2,776→~2,250,实测峰 2,136→~1,600) | SRAM1 + CCM 栈 | 中(所有文件 I/O 经 512B 窗 bounce,FTP 吞吐 −20~40%;LIST 与文件读交错只影响速度) | 存储套件 + FTP 吞吐实测 + 日志/配置闭环;**与 RAM方案 实施步骤 7 的缩栈捆绑**(直接支撑 defaultTask 降档) |
| `APP_CCM_ENABLE` 迁 CCM | `APP_CCM_ENABLE` 门(与 RAM方案 实施步骤 6 同宏):①CPU-only 缓冲迁 CCM——lftpd static 3.6K + cfg_file 0.7K + FATFS win 0.6K + lftpd_io ≈ **−4.9K** 零代价;②DMA 页缓冲(nand_ftl 4.6K + gd5f 位图/页 0.8K)迁 CCM 走轮询 ≈ **−5.4K**,需实测吞吐 | **−10.3K** | SRAM1→CCM | 低①/中② | 分段实施;每步跑 `ram_report.py` + `@test` + FTP 吞吐 |
| NAND copy-back | NAND copy-back 命令(0x15→0x10)替代主机搬页:省 `s_copy_buf` 的拷贝用途 + 减半 SPI 流量 | −512B(`s_copy_buf` 分块后) | SRAM1 | 高(片上 ECC 在 copy-back 下的语义须台架实证) | 仅评估,暂缓 |
| lftpd 数据块减半 | lftpd 数据块 2×1024→512 | −1,024B | SRAM1 | 吞吐 −~30% | RAM 仍紧时才做 |
| 共享静态 FIL | syslog/cfg 共享一个静态 FIL(同任务串行) | 净≈0(静态+528/栈−528) | — | 不推荐:`FF_FS_TINY` 那条已覆盖同一收益面 | — |
**明确不做**:移除 dhara(丢掉电一致性与坏块重映射,代价不对称);journal checkpoint 分片(违反 dhara 协议);调 `gc_ratio`(无 RAM 收益);`FF_FS_REENTRANT=0`(defaultTask+ftpTask 并发访问 FatFs 必需)。
## 3. 分阶段规划(与 `RAM使用分析与优化方案.md` §6 S 序号对齐)
| 阶段 | 内容 | 收益 | 前置 |
|---|---|---:|---|
| **第一步**(独立小批) | lftpd 缓冲收紧(`s_copy_buf` 分块搁置,待 RAM 余量需要时再议) | **−1,024B** | 无 |
| **第二步**(并入 RAM方案 步骤 7 的缩栈批) | `FF_FS_TINY=1` → 复跑 `stack_report.py` → defaultTask 降档(最坏链 ~2,250 支撑 3,584B 配置,余量 37%)+ 其余任务按该步骤 7 目标 | −528B 静态 + 栈档下调释放 CCM 堆 | 第一步合入 |
| **第三步**(与 RAM方案 步骤 6/8 同批) | 先做步骤 8 的堆 48K→32K(CCM 释放 ~16K),再按迁 CCM 方案 ①→② 分批 | SRAM1 再 **−10.3K**;CCM 占用 +10.3K(由该步腾挪覆盖) | 与步骤 8 同批或同批评估 CCM 水位 |
**全部落地后存储栈预期**:SRAM1 常驻降至 **~1.9KB**(现 13.7KB,其中 BBT 页缓冲收缩已省 1,776B 不重复计;余下仅小状态量),页缓冲驻 CCM(轮询),未实施项合计换回 SRAM1 ≈ **−11.9K**(`s_copy_buf` 分块搁置,其 −1,536B 不计);叠加 RAM方案 步骤 5(LVGL 40K→16K)后 SRAM1 余量 >65KB。
## 4. 回归与基线
- **FTP 吞吐基线(2026-09-21 实测,4MB 文件,端到端含网络;瓶颈在存储侧,100M 网络非瓶颈)**:STOR **126.9 KB/s**(32.3s)/ RETR **321.4 KB/s**(12.7s),回传逐字节 MATCH(测试文件已自清理)→ 两条 FTP 相关方案前后各留一轮实测对比(`FF_FS_TINY` 那条预期吞吐降幅 20~40%)
- `FF_FS_TINY` 前后各跑一次 `stack_report.py` + `ram_report.py`(AGENTS.md 门禁)
- 迁 CCM 方案的 ②(页缓冲入 CCM 走轮询)须以实测吞吐定去留:若 NAND 读吞吐降幅 >30%,则只迁 CPU-only 侧(①)
- `s_copy_buf` 分块与 copy-back、数据块减半等小项:待 RAM 余量需要时再议
- 下次存储改动照常执行台架破坏性套件 + 掉电 resume
- 基线数字可用 `data/ram_report_20260921.md`(BBT 页缓冲收缩前快照)复核;实施后重生成基线报告并回填总方案
## 5. 页缓冲可合并性论证(2026-10-23)
> 本节的来源:`Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` 原注释声称"页缓冲三块不可合并的评估见报告",
> 而原评审报告(2026-10-25 已删除)里只有一行"中 / 暂缓",**论证从来没写过**——本节补上
> (该注释已在本轮改为指向本节)。
> 结论:**三块不能合并**(最坏路径上同时存活,合并后的失败形态是**静默数据损坏**,不是报错);
> 可削减的只有 `s_copy_buf` 一块,上限 −2,048 B,而**收益落点不是红线区** →
> 原报告的该项由"待评估"改为"**已评估、判定不做**"(报告已删除,结论以本节为准),
> 与本文件 §2 那两条残余方案一起留在暂缓簇里。
### 5.1 6,216 B 的构成(`.map` 实证)
| 符号 | 尺寸 | 链接地址(2026-10-23 构建) | 角色 | 谁在写它 |
|---|---:|---|---|---|
| `s_page_buf` | 2,048 | 0x200066B4 | dhara journal 的 checkpoint 页缓冲(`dhara_map_init` 传入,`nand_ftl.c:426`) | `journal.c`:逐页累加 132 B 元数据,攒满 16 页后整页编程 |
| `s_cache_buf` | 2,048 | 0x20006EB4 | FatFS 页缓存(512 B 扇区 → 2 KB 页的 RMW) | `disk_read` / `disk_write` / `ftl_flush_cache` |
| `s_copy_buf` | 2,048 | 0x200076B4 | `dhara_nand_copy()` 的搬页暂存(GC) | `dhara_nand_copy`(`nand_ftl.c:379`/`:383`) |
| `s_map` / `s_nand` / 3 个标量 | 56 + 8 + 8 | 0x2000667C 起 | dhara 状态与缓存标志 | — |
`nand_ftl.o` 合计 **6,216 B = RW 16 + ZI 6,200**,其中三块页缓冲 6,144 B 占 **98.8%**。
原报告 §5.2 的 Top-5 表用 ZI 口径写 6,200,与本节的 6,216 差的就是那 16 B 的 RW,两处不矛盾。
### 5.2 三块同时存活的证据链(最坏路径)
触发条件是**一次脏页回写正好撞上 journal 满 → 自动 GC**,稳态写入下是常态而非边角:
1. `disk_write()` 把 512 B 扇区拼进 `s_cache_buf` 并置脏(`nand_ftl.c:565`);
2. 整页凑满或 `CTRL_SYNC` → `ftl_flush_cache()` → `dhara_map_write(&s_map, lpn, s_cache_buf, …)`
(`nand_ftl.c:195`)——**此刻 `s_cache_buf` 就是被写的数据本身**:dhara 把它当 `data` 直接交给
`dhara_nand_prog()`(`journal.c:1021`),坏块重试时还要再读一次;
3. `dhara_map_write()` 先调 `prepare_write()`(`map.c:356`),而后者**第一件事**就是 `auto_gc()`
(`map.c:327`)——回收发生在数据落盘**之前**;
4. `auto_gc → dhara_map_gc → raw_gc → dhara_journal_copy → dhara_nand_copy(src, head)`
(`map.c:247`、`journal.c:1042`),搬页需要一块 2 KB 暂存 = `s_copy_buf`;
5. 与此同时 `s_page_buf` 里攒着**当前 checkpoint 周期内已入队页的元数据**:`push_meta()` 每入队一页
就 `memcpy` 一段 132 B 进去(`journal.c:972`),攒满 16 页才整页编程(`journal.c:992`)。
这是**跨调用存活的状态**,不是暂存。
于是第 4 步执行时三块同时持有活数据:待写的缓存页、已攒的 checkpoint 元数据、搬页暂存。
任取两块合并,被覆盖的一方都会变成垃圾,而**没有一处会报错**——这与越界校验、挂载失败处置两次
"宁可报错,不可静默"的教训同族,属于必须回避的失败形态。
### 5.3 逐对否证
| 合并方案 | 看似可行的理由 | 实际后果 |
|---|---|---|
| `s_copy_buf` ⊕ `s_cache_buf` | "搬页只是中转,用完即弃" | 覆盖的正是**还没写下去的那一页**;`journal.c:1019` 的重试循环会拿被污染的数据再编程一次 → 该页永久错数据 |
| `s_copy_buf` ⊕ `s_page_buf` | "元数据才 132 B,占不了多少" | checkpoint 元数据在页内**跨 0..1,999 偏移**分布(`hdr_user_offset()`,`journal.c:89`;本卷 `log2_ppc=4` → 15 段),被覆盖 → checkpoint 内容错 → 复位后整段回退或 `E_CORRUPT_MAP` |
| `s_page_buf` ⊕ `s_cache_buf` | "都是页缓冲,都是 2 KB" | 同第二行;且 `s_page_buf` 的存活期以**16 页**为单位,远长于缓存页 |
| 条件别名(脏时用 A,否则用 B) | "只有一条路径同时用" | 需要"缓存是否脏"这个跨模块标志;将来任何从 dhara 侧新进的调用点都会静默踩中。为省 2 KB 引入一条**不可静态验证**的隐式契约,收益与风险不成比例 |
`s_page_buf` 也不能缩:虽然每页只用到 132 B 元数据,但**编程的是整页**(`journal.c:196` 整页
`memset` 0xFF + `journal.c:992` 整页 `prog`),且元数据段一直排到偏移 1,999 → 必须 2,048 B。
### 5.4 真正可动的只有 `s_copy_buf`
| 方案 | 收益 | 做法 | 代价 / 风险 |
|---|---:|---|---|
| `s_copy_buf` 分块(本文件 §2 方案清单) | **−1,536 B** | 512 B 分块:`page_read` 一次 → 4×(read_from_cache → program_load) → **一次** `program_exec` | 正确性风险低(仍是一次编程,ECC 一次算完,不是"部分页编程");但 512 B 不大于 `GD5F_DMA_THRESHOLD`(1024) → 4 次全走 CPU 轮询:SPI1 42 MHz 下 2 KB 线上时间 0.39 ms,轮询比 DMA 多约 25~50%,即每页多 ~0.1~0.2 ms 且全程占住 CPU(不 yield)。要保住 DMA 就得下调阈值,而那会破坏"阈值 ≥ 存储路径最大栈缓冲"这条编译期契约(`gd5f2gq5ue.h:105`),不建议 |
| 内部搬页(copy-back,本文件 §2 方案清单) | **−2,048 B** | `PAGE READ(13H)` src → `WRITE ENABLE` → `PROGRAM EXECUTE(10H)` dst,**主机侧零缓冲**(`../芯片手册/GD5F2GQ5UExxG.md` §9.6) | ① 手册限定"只能在**奇偶属性相同**的块之间搬",跨属性行为须台架实证;② 读侧 ECC 校正后的数据才进 cache register,语义上等价于"搬页 + 纠错",但同样要实测;③ 主机侧假芯片把 `program_exec` 建模成"必须 `program_load` 过"(`test/host/nand_port/nand_port_test.c:187`),要先补 internal-data-move 语义,否则正确实现会被判失败 |
两条都**替代不了**"三块合一":做满也只能把 6,216 B 压到 4,168 B,压不回 2 KB 级。
### 5.5 为什么省下来也没用(落点分析)
- 三块页缓冲全在 `RW_IRAM1`(0x2000_0000 窗口内,`gd5f_dma_buf_ok()` 白名单覆盖),该区
2026-10-23 实测 **75,264 / 57.4%,余 55,808 B**;
- 红线在 **`RW_CCM`:57,376 / 87.9%,余 7,904 B**(FreeRTOS 堆 56 KB 吃掉了它);
- 所以这条线即使做满(−2,048 B)也**一分不减红线**;反过来把页缓冲搬进 CCM(本文件 §2 的迁 CCM 方案 ②)
才是"对红线有害"的方向——6,144 B 会吃掉 CCM 余量的 78%;
- 把 `s_copy_buf` 挪到栈上同理:defaultTask 静态最坏链 3,024 B / 5,120 B,再塞 2 KB 直接顶到上限,
而任务栈本身就在 CCM。
结论:**页缓冲合并这条线的收益没有消费者**。这正是 2026-10-15 整簇暂缓的机制原因,本节只是把它从"判断"
补成了"论证"——所以暂缓不变,重开仍按 `RAM使用分析与优化方案.md` §4 的三条条件。
### 5.6 复现
```bash
grep -n " nand_ftl.o" MDK-ARM/STM32F407-Demo/STM32F407-Demo.map # 6,216 = RW 16 + ZI 6,200
grep -n "s_page_buf\|s_cache_buf\|s_copy_buf" MDK-ARM/STM32F407-Demo/STM32F407-Demo.map
```
### 5.7 顺带订正的两处注释
- `nand_ftl.c:300` 原写"页缓冲三块不可合并的评估见报告",而报告里此前**没有**这份评估
→ 改为指向本节。
- `nand_ftl.c:405` 原写"`s_page_buf` 现仅装 dhara meta 132 B 走轮询",**不成立**:`s_page_buf` 是
按整页编程的(`journal.c:992` → `dhara_nand_prog` → `gd5f_program_load(0, data, 2048)`),
与另两块一样走 DMA。原文这句话会误导后来做迁 CCM ②的人,已按实况改写。