Files
STM32F4-Base/App/fs/vol_lock.c
T
wandering1 f1d0b5c2d9 docs(规范,门禁): 卷锁五态去裸形——本仓第一次真正还第二根护栏的债,并写清还债的算法
最反直觉的是结果里的差值: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。
2026-10-27 05:14:11 +08:00

123 lines
6.3 KiB
C
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.
/*
* 模块名称: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;
}