现状口径:/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 全过;字节卫生零漂移。
15 KiB
存储栈 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,commitbe1a30a)与编程回读校验整体移除的实施记录(详见 GD5F TRAP-GD5F-05),以及过期逐模块基线表;仅保留未实施项与回归要求 ⏸ 实施状态(用户裁定 2026-10-15):本文全部未实施项整簇暂缓、不再排期——s_copy_buf分块(原报告的同一评估项)、APP_CCM_ENABLE的 ①② 迁 CCM、以及依赖"堆 48K→32K"的第三步。当前没有功能在等这块预算,而搬迁要逐条过 DMA 可达性清单(违例是静默传错不是报错)。保持常开的是测量与门禁面(ram_report.py/stack_report.py/mem-budget92% 门禁 / 台账 / 趋势)。理由与重开条件 →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 内(靶点见 §2FF_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,稳态写入下是常态而非边角:
disk_write()把 512 B 扇区拼进s_cache_buf并置脏(nand_ftl.c:565);- 整页凑满或
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),坏块重试时还要再读一次; dhara_map_write()先调prepare_write()(map.c:356),而后者第一件事就是auto_gc()(map.c:327)——回收发生在数据落盘之前;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;- 与此同时
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%,即每页多 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 ②的人,已按实况改写。