Files
STM32F4-Base/docs/内存管理/存储栈RAM优化调研.md
wandering1 4a4ad6bc5f chore(全仓): @build/@test 旧文档别名清退为技能调用名
现状口径:/keil-build 与 /serial-boot-test 是 DSH 会话的技能调用名,
不是 shell 命令;「名 + 命令行参数」的命令形态改为可执行体
run_boot_test.py 的真实调用。

- AGENTS.md:别名说明重写为「技能调用名不是 shell 命令」,两条目改
  「编译(/keil-build)」「串口回归(/serial-boot-test)」
- keil-build / serial-boot-test 两 skill 调用名与示例同步;task-worktree
  方案文档去掉 @build/@test 惯例从句;@task 保留(不在点名范围)
- 123 文件现状陈述全改;台架捕获、变更记录/时间线、rename 映射输入
  等 41 行历史证据保留旧名
- gen_memory_svg.py 6 处措辞随改,3 张 SVG 产物同批重跑
- 顺手补并行方案同行漏网计数 51→52 钩子

验证:提交期快钩子对 123 文件实跑全 Passed;check_doc_links --all
rc=0(3038 处、无法判定 0);check_mermaid --all rc=0;YAML 解析 52
钩子不变;skill-size rc=0;py_compile 全过;字节卫生零漂移。
2026-10-30 13:03:12 +08:00

15 KiB
Raw Permalink Blame History

存储栈 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 客户端实测行长远低于新值) /keil-build + FTP 上传/下载/LIST 闭环 + /serial-boot-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 + /serial-boot-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.10.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 复现

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 ②的人,已按实况改写。