f1d0b5c2d9
最反直觉的是结果里的差值:36 处裸形态号全部清零,绝对数只从 1,162 降到 1,156(−6,余量 4 → 10 格)。 作者没把差值糊过去而是查到底:check_ids 只在字面量**落不进任何已登记族**时才计不可判定,撞进已登记族的 23 处本来就记在可判定侧,删它们分子分母同缩、比例反而从 86.74% 涨到 86.77%;真正在盲区里的只有族外 7 处,其中 1 处还被变更记录行豁免 ⇒ 账面 −6,算式对平(±1 属同窗别道在飞)。**这四条机制我写进 docs/门禁说明/id-check.md §6.10 并署名作者**——它是「怎么还这根护栏的债」的第一份操作口径。 改动面:App/fs/vol_lock.c 10 处、.h 6 处、vol_lock_test.c 14 处、卷锁单元测试说明.md 3 处 ⇒ 同口径 改后 0 处。四态语义只留 vol_lock.h 的 vol_lock_stats_t 注释一处,其余三处只描述断言并指回头文件, 不抄第二真源。除 4 行声明外全是注释文字,代码行为零改动。顺带消掉一个**同形两义**的撞号源。 实测:1,162(03:15:08)→ 1,156(03:21:56),写变更记录后复量仍 1,156(本笔没吃余量);--strict exit 0;vol_lock 套件 45/45;**上限未动、刻意不跑 --update-baseline**(作者反对成立:重锚会把只减不增的线 按这次 −6 重画,白丢一次真还债的账面记录);未改 id_registry.json / 台账,未跑 --all-files。
123 lines
6.3 KiB
C
123 lines
6.3 KiB
C
/*
|
||
* 模块名称:Volume Lock(卷锁)— FreeRTOS 实现
|
||
* 模块功能:osMutex 串行化 NAND FatFs 卷访问(设计/使用约定见 vol_lock.h)
|
||
* 适用平台:STM32F407ZGTx + FreeRTOS
|
||
* 作者:王建锋
|
||
* 创建日期:2026-10-02
|
||
* 修改记录:
|
||
* v1.0 2026-10-02 王建锋 创建
|
||
* v1.1 2026-10-26 DTU 项目组 道G task-17:① 新增 4 个观测计数(持锁 / try 超时 /
|
||
* 分片退让 / 零互斥 四态,
|
||
* 共 16 B 静态)与 vol_lock_get_stats() 只读口 —— 不取锁、
|
||
* 零外部调用、零局部变量,13 处调用点一字未动;
|
||
* ② 修 :38「返回值」注释与代码相反的错(fail-open 原被
|
||
* 归给 0,实际返 1);③ 刻意不记时刻:时刻要读 tick,
|
||
* 等于给 defaultTask 取锁链添帧,改由消费方观测并自盖
|
||
* (命名 obs_first_ms/obs_last_ms,见 vol_lock.h 告诫 ②)
|
||
*/
|
||
|
||
/* 头文件包含区 */
|
||
#include "vol_lock.h"
|
||
|
||
#include "cmsis_os.h"
|
||
|
||
/* 静态变量定义区 */
|
||
static osMutexId_t s_lock; /* 卷串行化互斥锁(vol_lock_init 创建) */
|
||
|
||
/* 观测计数(道G task-17「卷锁 5 态可观测性」)—— 本模块唯一的新增状态,共 16 B。
|
||
* 为什么计数必须在锁层、而且只有计数没有别的:
|
||
* ① 13 处调用点各自 return、历史上零痕;在每个调用点各写一份 = 13 份真源。
|
||
* ② 不能在这里打日志或投记录:唯一能落盘的通道(/log/wdev.log、run.log)自己也要
|
||
* 取这把卷锁,而卷锁**非递归** —— 被卷忙挡住时既写不出东西,还会自取死锁。
|
||
* ③ 也不读 tick 记时刻:那会给 defaultTask 的取锁链添一个 callee 与几字节帧,而
|
||
* 这条链的栈余量正是要省着花的;"什么时候开始忙"交给消费方观测(口径见 vol_lock.h
|
||
* 的告诫 ②:观测时刻不等于发生时刻,别写成首次发生)。
|
||
* 并发:多个任务可同时自增同一个计数,竞态下少计不会多计(判据是"是否 > 0 / 是否还
|
||
* 在增长",不是精确值),所以不为此上临界区 —— 上临界区反倒把观测做成了故障本身。
|
||
* volatile:跨任务读写且无锁保护,禁止把读侧重复读优化掉。 */
|
||
static volatile uint32_t s_st_acquired; /* 持锁态:真取到互斥(四态定义见 vol_lock.h) */
|
||
static volatile uint32_t s_st_try_timeout; /* try 超时态:timeout=0 没取到,卷被别人占住 */
|
||
static volatile uint32_t s_st_blk_timeout; /* 分片退让态:分片等待超时,ftpTask 正常退让 */
|
||
static volatile uint32_t s_st_failopen; /* 零互斥态:锁不存在却返 1,谎报成功那一态 */
|
||
|
||
/* 函数定义区 */
|
||
|
||
/*
|
||
* 函数功能:创建卷串行化互斥锁(幂等,重复调用安全)
|
||
* 入口参数:无 void
|
||
* 返回值:0 - 成功(已创建或已存在);-1 - osMutexNew 失败 int
|
||
* 限定条件:可在内核启动后任意时刻调用;本实现不检查是否已启动调度器
|
||
*/
|
||
int vol_lock_init(void) {
|
||
if (s_lock != NULL) {
|
||
return 0; /* 幂等 */
|
||
}
|
||
s_lock = osMutexNew(NULL);
|
||
return (s_lock != NULL) ? 0 : -1;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:获取卷锁(首次调用自动 vol_lock_init)
|
||
* 入口参数:timeout_ms - 等待上限(毫秒);0 = try 语义(不阻塞) uint32_t
|
||
* 返回值:1 - 已取得互斥,**或锁不存在的 fail-open(返 1 但此刻并未串行化)**;
|
||
* 0 - 未取得(timeout=0 的 try 失败,或 timeout>0 的分片等待超时) int
|
||
* 限定条件:返回 0 时调用方**不得** vol_lock_release;fail-open 是为保证
|
||
* 看门狗喂狗点(defaultTask)绝不能因等锁而阻塞
|
||
* 函数说明:⚠ 原 v1.0 这行「返回值」把 fail-open 归给 0,与本函数 :46 的代码和
|
||
* TC-VOLLOCK-008 都相反(道G task-17 修正)。"返 1"有两种成因,调用方
|
||
* 无需各自分辨(它们只关心能不能干活),但整机的判据必须看
|
||
* vol_lock_get_stats().failopen —— 累计 > 0 = 本 boot 至少发生过一次
|
||
* "无互斥读写卷"。本函数只自增计数:不打日志、不投记录、不读 tick、
|
||
* 不新增局部变量(理由见上面计数块)。
|
||
*/
|
||
int vol_lock_wait(uint32_t timeout_ms) {
|
||
if (s_lock == NULL) {
|
||
(void)vol_lock_init();
|
||
if (s_lock == NULL) {
|
||
s_st_failopen++; /* 零互斥态:返 1 但零互斥 —— 谎报成功那一态 */
|
||
return 1; /* 锁创建失败:fail-open(不阻塞,保持启动段可用) */
|
||
}
|
||
}
|
||
if (osMutexWait(s_lock, timeout_ms) == osOK) {
|
||
s_st_acquired++; /* 持锁态:真持锁 */
|
||
return 1;
|
||
}
|
||
if (timeout_ms == 0u) {
|
||
s_st_try_timeout++; /* try 超时态:压根没等(喂狗点/绘制路径只有这一态会发生) */
|
||
} else {
|
||
s_st_blk_timeout++; /* 分片退让态:等了一会儿没等到,与 try 超时态语义不同 */
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:释放卷锁
|
||
* 入口参数:无 void
|
||
* 返回值:无 void
|
||
* 限定条件:仅在 vol_lock_wait() 返回 1 后调用;锁未创建时静默返回
|
||
*/
|
||
void vol_lock_release(void) {
|
||
if (s_lock != NULL) {
|
||
(void)osMutexRelease(s_lock);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读卷锁观测计数(契约与消费方告诫见 vol_lock.h 的函数头)
|
||
* 入口参数:p_out - 输出缓冲;NULL 时无害返回 vol_lock_stats_t *
|
||
* 返 回 值:无 void
|
||
* 限定条件:不取卷锁、不关中断、不阻塞;**零外部调用、零局部变量**,因此不改变
|
||
* 任何调用方的栈最坏链(这条是本卡选型的结论,不要"顺手优化"掉)
|
||
* 函数说明:逐字段拷而非整体赋值 —— 每个字段都是某个真实值,不会读到半更新的字;
|
||
* 四元组之间不保证同一瞬间(诊断够用,理由见 .h 的告诫 ①)
|
||
*/
|
||
void vol_lock_get_stats(vol_lock_stats_t *p_out) {
|
||
if (p_out == NULL) {
|
||
return;
|
||
}
|
||
p_out->acquired = s_st_acquired;
|
||
p_out->try_timeout = s_st_try_timeout;
|
||
p_out->blk_timeout = s_st_blk_timeout;
|
||
p_out->failopen = s_st_failopen;
|
||
}
|