Files
wandering1 484d068250 feat(存储): 写回缓存空闲 flush 兜底——卷空闲周期 CTRL_SYNC(登记簿 N-22 收口)
道F 批 2 / task-16。FTL 单页写回缓存(nand_ftl.c s_cache_dirty)与 dhara
journal 只在换页/整页写/f_close 时刷出,漏网脏页的掉电驻留窗无上限。本
提交以最小面收口:不碰 GD5F/dhara 驱动,全部走既有 FatFs CTRL_SYNC 路径。

实现 App/fs/vol_idle_sync.[ch](新增,仅 MCU 端,uvprojx App 组注册):
- 四级门:STORAGE_READY 就绪门 → 5 s 节流(无符号差回绕安全判据)→
  vol_lock_wait(0) try(锁被持有=文件序列在做,跳过,不改 STOR 持有期
  语义)→ disk_ioctl(0, CTRL_SYNC);
- clean 态脏标志+journal 双短路、零 NAND 触点;脏态最坏 = 一次 f_close
  的同步段同型成本,不引入新磨损来源;
- 失败按边沿 latch 一条 DBG_ERROR、成功重新武装;失败路径也成对放锁;
- TEST_SUITE_STORAGE 构建整体 no-op(卷由 testTask 独占)。
接线:defaultTask 主循环 syslog_drain 之后(default_task.c v1.3)。

文档:docs/架构设计/STM32F4-Base存储架构说明.md §5.5 补空闲兜底与边界;
本板无掉电检测硬件、不做掉电前强制 flush、勿当掉电保护——口径同步落
注释与文档(sys_log_mcu.c 两处旧「暂不做」注释随实现改口)。

测试:新主机单测 test/host/vol_idle_sync/(TC-VOLSYNC-001..012,19 断言
全过);scripts/id_registry.json 建 TC-HOST-VOLSYNC 族(仿 ADCCAL 先例);
.pre-commit-config.yaml 加 vol-idle-sync-unit-test hook(只加自己条目)。

越界申报(①②③ 均 lead 批准,④ 为门禁连带必要):
① App/task/default_task.c 单点接线(include + 1 调用 + 头修改记录);
② docs/架构设计/STM32F4-Base存储架构说明.md §5.5;
③ .pre-commit-config.yaml 单 hook + scripts/id_registry.json 单族;
④ scripts/check_registration.py SIM_EXEMPT 加一行(vol_idle_sync.c 模拟器
   侧豁免,registration-check GAP 所需;与批准③同类别但未列名,特此申报)。

体积增量(vs scripts/ram_baseline.tsv 2026-10-25 行):
Flash(Code+RO) 237140→237328(+188 B)、RW 1224→1232(+8 B)、ZI 0;
增量 = 3 个文件级 static + 一处 DBG_ERROR 调用点;栈/堆无变化。

gate: only mem-budget/mem-numbers pending lead ledger backfill
全量 pre-commit --all-files:40 项 Passed,仅 mem-numbers(D-7 体积档)与
mem-budget 两条红 = 台账数字待回填(增量如上,属 lead 单写者禁区);
其余含 doc-links/id-check/registration/test-coverage/全部主机单测皆绿。
@build 0 错误 0 警告(Code=215932 RO=21396 RW=1232 ZI=131984)。

台架:空闲 sync 实机节奏(clean 零触点、脏态时长、无心跳扰动)属实际
测量采样类——本批跳过,留「待台架实测」。
2026-10-25 09:23:20 +08:00

126 lines
5.7 KiB
C
Raw Permalink 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.
/*
* 模块名称:Volume Idle Sync(写回缓存空闲 flush)— MCU 实现
* 模块功能:就绪门控 + 周期节流 + 卷锁 try + disk_ioctl(CTRL_SYNC) 的兜底
* 状态机(设计/使用约定/边界见 vol_idle_sync.h)
* 适用平台:STM32F407ZGTx + FreeRTOS
* 作者:DTU 项目组
* 创建日期:2026-10-25
* 修改记录:
* v1.0 2026-10-25 DTU 项目组 创建(道F 批 2:写回缓存空闲 flush 收口)
*/
/* 头文件包含区 */
#include "vol_idle_sync.h"
#include "test_config.h"
#if !defined(TEST_SUITE_STORAGE)
#include "cmsis_os.h"
#include "FreeRTOS.h"
#include "task.h"
#include "ff.h" /* FatFs 基础类型(diskio.h 依赖 BYTE/LBA_t 等,须先纳) */
#include "diskio.h" /* disk_ioctl / CTRL_SYNC / DRESULT */
#include "vol_lock.h"
#include "sys_events.h"
/* 本模块日志仅一条失败留痕(≤60 字符),默认行缓冲即可,不调 DBG_LINE_BUF_MAX */
#include "dbg_log.h"
/* Private defines -----------------------------------------------------------*/
/* 空闲同步检查周期(= 漏网脏页的最大掉电驻留窗,另加一个主循环拍 ≈50ms)。
* 取值依据:clean 态为"脏标志 + journal"双短路(零 NAND 触点),高频不伤介质
* 只是白跑;脏态最多一次页编程 + journal 收账,与 f_close 同型,但落在卷
* 已空闲的窗口、远离活跃写会话。5 s 与主循环其余慢观测(栈/堆水位 2 s)同
* 量级而更稀疏——本模块是兜底网,不是持久性主路径(主路径 = close 内同步)。
* 锁忙(卷不空闲)同样按本周期重试:不做欠账补偿,节奏恒定好推理。 */
#define VOL_IDLE_SYNC_PERIOD_MS 5000u
/* 静态变量定义区 */
/* 状态量放文件级 static(非函数内):主机侧白盒单测(test/host/vol_idle_sync/)
* 以 #include 纳入本文件后需要复位它们;命名带 vol 前缀避免与测试桩撞名 */
static uint32_t s_vol_sync_last_ms; /* 上次尝试时刻(tick ms,uint32 回绕) */
static uint8_t s_vol_sync_armed; /* 0 = 从未尝试(首拍直接放行) */
static uint8_t s_vol_sync_fail_latch; /* 失败留痕边沿 latch:一个 episode 一条 */
/* 函数定义区 */
/*
* 函数功能:存储空闲周期同步轮询(就绪门控 → 节流 → try 卷锁 → CTRL_SYNC)
* 入口参数:无 void
* 返回值:无 void
* 限定条件:仅 defaultTask 主循环调用;内部绝不阻塞(try 锁 + 常数级耗时,
* 最坏成本 = 一次 f_close 的同步段)
* 函数说明:四级门,逐级变贵:
* ① 就绪门:sys_evtHandle 未建或 STORAGE_READY 未置 → 静默
* (挂载前 disk_ioctl 只会 RES_NOTRDY,且 bring-up 失败重试期
* 卷状态未定,一律不碰);
* ② 节流门:距上次尝试不足 VOL_IDLE_SYNC_PERIOD_MS → 返回。
* 无符号差值判据((uint32)(now - last) < 周期)天然跨 32 位回绕
* 正确,与 stack_probe_poll 同一 idiom;勿"优化"成大小比较;
* ③ 空闲门:vol_lock_wait(0) try——锁被持有 = 有文件操作序列在做
* (syslog 落盘 / 配置保存 / lftpd open...close),本轮跳过,
* 绝不改变"文件持有期内不同步"的既有语义;
* ④ 动作:disk_ioctl(0, CTRL_SYNC)——FatFs 官方幂等落盘原语
* (f_close 尾部同一条路径):FTL 脏页刷入 + dhara_map_sync。
* clean 时两级短路立即返回,零 NAND 操作。
* 失败留痕:RES_ERROR 只按"边沿"打一条 DBG_ERROR(latch),持续失败
* 不刷屏;下一次成功后 latch 复位,新 episode 再报。底层失败形态
* (Cache flush FAIL / Map sync failed)已由 nand_ftl 就地打点,
* 本条只补"空闲路径也救不回"的上下文,不重复堆日志锚点。
*/
void vol_idle_sync_poll(void) {
const uint32_t now = (uint32_t)xTaskGetTickCount();
DRESULT dr;
/* ① 就绪门 */
if (sys_evtHandle == NULL) {
return;
}
if ((osEventFlagsGet(sys_evtHandle) & SYS_EVT_STORAGE_READY) == 0u) {
return;
}
/* ② 节流门(回绕安全判据,见函数说明②) */
if ((s_vol_sync_armed != 0u)
&& ((uint32_t)(now - s_vol_sync_last_ms)
< (uint32_t)pdMS_TO_TICKS(VOL_IDLE_SYNC_PERIOD_MS))) {
return;
}
s_vol_sync_last_ms = now;
s_vol_sync_armed = 1u;
/* ③ 空闲门:try 锁,拿不到 = 卷不空闲,本轮跳过(下周期再试) */
if (vol_lock_wait(0u) == 0u) {
return;
}
/* ④ 幂等落盘:脏页刷入 + journal 收账;clean 时零 NAND 触点 */
dr = disk_ioctl(0, CTRL_SYNC, NULL);
vol_lock_release(); /* 成败都放锁:同步失败不产生锁泄漏 */
if (dr != RES_OK) {
if (s_vol_sync_fail_latch == 0u) {
s_vol_sync_fail_latch = 1u;
DBG_ERROR("[FS] idle sync FAIL (dr=%d)", (int)dr);
}
} else {
s_vol_sync_fail_latch = 0u; /* 成功:重新武装,下个 episode 可再报 */
}
}
#else /* TEST_SUITE_STORAGE */
/*
* 函数功能:STORAGE 套件构建下的空实现(编译期裁剪落点)
* 入口参数:无 void
* 返回值:无 void
* 限定条件:仅当 test_config.h 选中 TEST_SUITE_STORAGE 时编入本分支
* 函数说明:该构建里挂载/盘写由 testTask 的 storage_test_run 独占(测试含
* nand_ftl_deinit 掉电模拟),后台同步与它并发会互相踩坏 FTL/FatFs
* 状态——必须彻底静默,而非仅靠运行时门控。
*/
void vol_idle_sync_poll(void) {
}
#endif /* TEST_SUITE_STORAGE */