事件从 evlog_put*() 进一个 256 条的 RAM 环(临界区里两条指令,**不阻塞、 不碰 flash**),由最低优先级的 t_log 攒够 16 条或最老那条等满 1 s 时批量写进 新的 `evlog` 分区(17 扇区 × 4 KB = **4335 条**)。A10 的事件出口原样接上, 判据那一层一个字没动。 八条实现约定(详见需求文档 §10.7): · 落盘不用 NVS 也不用 SPIFFS,是一块裸 data 分区 —— NVS 的页式 GC 会把 「写一条事件」变成一次不可预测的搬移。HAL 因此多出第六样 hal_store; · 17 个扇区不是 16:每扇区要拿一个 16 B 的槽当扇区头,4 KB 只装 255 条, 16 扇区 = 4080,差 16 条不到 §10.7 要求的 4096; · 序号 = flash 里的位置(epoch × 255 + 槽号),不是自增计数器 —— 重启接着数 不用存计数器;代价是没落盘的事件还没有序号,A12 读之前要先 flush; · 入队走关中断的临界区,**不是第三把锁**:入队方是最高优先级的 t_safety, 停在一把 t_log 持着的锁上等于让最低优先级的任务决定巡检节拍; · 丢事件可以,悄悄丢不行 —— 缓冲满了盖最旧的,但补一条 LOG_EV_LOST 记着丢了几条; · 每条记录带 CRC-8(见下),写失败就放弃该扇区剩余部分,被放弃的那段在序号 空间里是个洞,读回时给一条空记录占位(跳过的话上位机游标会卡住); · 每次上电记一条 LOG_EV_BOOT:时间戳上电从 0 计,没有它两次上电的记录会穿插。 事件类型的数字是 flash 格式的一部分,只许往末尾加;safety_ev_t → log_ev_t 是显式 switch 不是加偏移。§10.7 里还没人产生的六类号已占住(A12/A15/A16 去填)。 27 个用例;23 条注错逐个跑过,21 条变红。第一版校验和用异或,漏掉的正是最常见 的那条:payload 全 0 的记录被掉电切在第 8 个字节上时,后 8 个字节留在 0xFF, 而 8 个 0xFF 的异或恰好是 0 —— 半条的校验和与整条一模一样,扫描把半条当成 一条真事件。换成 CRC-8 之后拦住。另外两个只在这里才踩得到的坑:「掉电重启」 不能用 sim_hal_reset()(那是换新板子,flash 一起擦,测试永远绿),为此加了 sim_hal_reboot();「读到洞就跳过」这个错被测试里 static 数组的零初始化掩盖过 一次,缓冲要先填成非零。 Co-Authored-By: Claude Opus 5 <[email protected]>
142 行
4.6 KiB
C
142 行
4.6 KiB
C
/* 两把互斥锁的假实现。宿主机测试是**单线程**的,所以这里模拟的不是并发本身,
|
||
而是「加锁纪律」—— 谁拿了、放没放、有没有嵌套、该拿的时候拿了没有。
|
||
|
||
⚠️ 单线程 sim 里「等一把别人持有的锁」永远等不到,所以:
|
||
· 有限超时 → 把虚拟时钟往前拨满 timeout_ms,计一次超时,返回 HAL_ERR_TIMEOUT
|
||
(这正是真板上等不到锁时该发生的事,调用方的处理路径因此测得到);
|
||
· HAL_LOCK_FOREVER 且被别人持有 → 直接 abort。那在真板上是死锁,
|
||
在这里悄悄返回一个错误码只会把它藏起来。
|
||
|
||
「谁」是一个假任务号(`sim_lock_set_task()`),默认 0。测试用它扮演第二个任务。 */
|
||
#include <assert.h>
|
||
#include <string.h>
|
||
|
||
#include "hal_lock.h"
|
||
#include "sim_hal.h"
|
||
|
||
#define NO_OWNER (-1)
|
||
|
||
static int s_owner[HAL_LOCK_COUNT];
|
||
static uint32_t s_takes[HAL_LOCK_COUNT];
|
||
static uint32_t s_timeouts[HAL_LOCK_COUNT];
|
||
static uint32_t s_violations[HAL_LOCK_COUNT];
|
||
static bool s_require[HAL_LOCK_COUNT];
|
||
static int s_task;
|
||
static bool s_inited;
|
||
static int s_crit_depth;
|
||
static uint32_t s_crit_enters;
|
||
static uint32_t s_crit_blocking;
|
||
|
||
void sim_lock_reset_internal(void); /* 只给 sim_hal_reset() 用 */
|
||
|
||
void sim_lock_reset_internal(void)
|
||
{
|
||
for (int i = 0; i < HAL_LOCK_COUNT; i++) {
|
||
s_owner[i] = NO_OWNER;
|
||
}
|
||
memset(s_takes, 0, sizeof(s_takes));
|
||
memset(s_timeouts, 0, sizeof(s_timeouts));
|
||
memset(s_violations, 0, sizeof(s_violations));
|
||
memset(s_require, 0, sizeof(s_require));
|
||
s_task = 0;
|
||
s_inited = false;
|
||
s_crit_depth = 0;
|
||
s_crit_enters = 0;
|
||
s_crit_blocking = 0;
|
||
}
|
||
|
||
static bool valid(hal_lock_id_t id) { return id >= 0 && id < HAL_LOCK_COUNT; }
|
||
|
||
hal_err_t hal_lock_init(void)
|
||
{
|
||
s_inited = true;
|
||
return HAL_OK;
|
||
}
|
||
|
||
bool hal_lock_held(hal_lock_id_t id)
|
||
{
|
||
return valid(id) && s_owner[id] == s_task;
|
||
}
|
||
|
||
hal_err_t hal_lock_take(hal_lock_id_t id, uint32_t timeout_ms)
|
||
{
|
||
sim_crit_blocking_note(); /* 关着中断去等一把锁:真板上这是把系统钉死 */
|
||
if (!valid(id) || !s_inited) {
|
||
return HAL_ERR_STATE;
|
||
}
|
||
/* 已经持有 = 重复加锁(这两把都不是递归锁);持有另一把 = 嵌套,见 hal_lock.h。 */
|
||
for (int i = 0; i < HAL_LOCK_COUNT; i++) {
|
||
if (s_owner[i] == s_task) {
|
||
return HAL_ERR_STATE;
|
||
}
|
||
}
|
||
if (s_owner[id] != NO_OWNER) {
|
||
assert(timeout_ms != HAL_LOCK_FOREVER); /* 单线程里死等 = 死锁 */
|
||
s_timeouts[id]++;
|
||
hal_time_delay_ms(timeout_ms); /* 真板上这段时间是等掉的,虚拟时钟也要花掉 */
|
||
return HAL_ERR_TIMEOUT;
|
||
}
|
||
s_owner[id] = s_task;
|
||
s_takes[id]++;
|
||
return HAL_OK;
|
||
}
|
||
|
||
void hal_lock_give(hal_lock_id_t id)
|
||
{
|
||
assert(valid(id));
|
||
assert(s_owner[id] == s_task); /* 不是自己拿的 / 根本没人拿 */
|
||
s_owner[id] = NO_OWNER;
|
||
}
|
||
|
||
uint32_t hal_lock_timeouts(hal_lock_id_t id) { return valid(id) ? s_timeouts[id] : 0; }
|
||
|
||
/* ---- 假板子这一侧的控制面 ------------------------------------------------ */
|
||
|
||
void sim_lock_set_task(int task) { s_task = task; }
|
||
int sim_lock_task(void) { return s_task; }
|
||
int sim_lock_owner(hal_lock_id_t id) { return valid(id) ? s_owner[id] : NO_OWNER; }
|
||
uint32_t sim_lock_takes(hal_lock_id_t id) { return valid(id) ? s_takes[id] : 0; }
|
||
|
||
void sim_lock_require(hal_lock_id_t id, bool on)
|
||
{
|
||
if (valid(id)) {
|
||
s_require[id] = on;
|
||
}
|
||
}
|
||
|
||
uint32_t sim_lock_violations(hal_lock_id_t id) { return valid(id) ? s_violations[id] : 0; }
|
||
|
||
void sim_lock_check(hal_lock_id_t id)
|
||
{
|
||
if (valid(id) && s_require[id] && !hal_lock_held(id)) {
|
||
s_violations[id]++;
|
||
}
|
||
}
|
||
|
||
/* ---- 临界区(不是第三把锁,见 hal_lock.h) -------------------------------
|
||
单线程 sim 里它没有任何互斥作用 —— 这里模拟的同样是**纪律**:
|
||
进出配不配对、里面有没有干会阻塞的事。 */
|
||
|
||
void hal_crit_enter(void)
|
||
{
|
||
s_crit_depth++;
|
||
s_crit_enters++;
|
||
}
|
||
|
||
void hal_crit_exit(void)
|
||
{
|
||
assert(s_crit_depth > 0); /* 多放一次 = 真板上中断被提前打开,谁也发现不了 */
|
||
s_crit_depth--;
|
||
}
|
||
|
||
void sim_crit_blocking_note(void)
|
||
{
|
||
if (s_crit_depth > 0) {
|
||
s_crit_blocking++;
|
||
}
|
||
}
|
||
|
||
int sim_crit_depth(void) { return s_crit_depth; }
|
||
uint32_t sim_crit_enters(void) { return s_crit_enters; }
|
||
uint32_t sim_crit_blocking(void) { return s_crit_blocking; }
|