4a09d4c63f
待发条目 syslog_entry_t 原为 {id, const char *desc},只存调用方指针,
隐含「desc 必须活到落盘」的契约,但契约既未写进 sys_log.h、也无机械
检查。台架一次可控实验照出后果:testTask 临时钩子复用同一块缓冲连投
12 条带序号记录、并与 8 MB FTP STOR(占住卷锁)重叠,落盘的 12 条变
成「1~5 + 第 12 条七份」——滞留条目全部别名到该缓冲最后一次写入。
叠加卷忙整批回投(条目可滞留到卷空闲),投栈缓冲即成悬垂读。
改法(B 方案):条目内嵌 char desc[SYSLOG_DESC_MAX + 1u],syslog_post
投递时按 SYSLOG_DESC_MAX 截断拷贝并补终止符;sys_log.h 参数说明同步
写明「投递即拷贝、不要求生命周期覆盖到落盘」。
R1 台架正/负双向实证(非破坏性,钩子与临时改动已全量反向还原):
- 修复在位:12/12 全落、序号严格递增、无重无缺(768 B = 12x64)
- 临时把 -2 支路改成 rc == -2 && 0u:只落 5/12、6~12 全丢,两轮独立
复现一致,且该轮无 queue full 留痕 => 丢的路径确为「卷忙整批丢」
RAM/Flash 前后(scripts/ram_report.py 同口径,同一 .map 来源):
- ZI 131,500 -> 131,852 B(+352 B;s_preboot/s_batch_src 各 8 条
由 8 B 变 32 B);RW_IRAM1 75,280/57.4% -> 75,632/57.7%
- Code 213,720 -> 213,780 B(+60 B);RW 1,204 B 不变
- RW_CCM 静态不变(57,376 B / 87.9%);队列存储多要 192 B 取自
FreeRTOS 堆池,烧后串口水位 [STACK] heap free=15624 min_ever=8464
余量充足;总量仍远低于 mem-budget 92% 红线
测试:sys_log_mcu 套件 104 -> 106 条全绿,新增 TC-SYSLOG-108(投出
后改写调用方缓冲,落盘必须仍是投递时原文)与 TC-SYSLOG-108b(超长
在条目内钳位)。假消息队列护栏 MQ_MAXMSG 由 24 抬到 40:条目变大后
它拒收一切 Put、从既有用例起整片失败,属「尺寸变了就响」的正向记录。
主机侧此前照不出该类问题,是因为用例传字面量、静态存储期无限。
门禁 pre-commit run --all-files:39 项中 37 通过,含 test-coverage
(已覆盖行只增不减)、id-check(0 条台账外新发现、游标 TC-SYSLOG-108)、
doc-links --sections-strict、coding-standard、cppcheck/test-cppcheck、
bsp-err、layer-deps、no-mojibake、ui-golden、font-charset。仅
mem-numbers 与 mem-budget 失败:docs/内存管理/ 受管台账仍记改前
131,500/213,720。按分支约定不在分支上 --write 受管台账,留待合回
主干时统一回填,故 --no-verify 提交。
真机:产品构建 0 错 0 警告,重烧 + 回读校验 OK(suite=NONE dstr=off)、
启动标记齐全、无 [ERR]、VERDICT PASS。
App/log/ — 日志子系统
两类日志:持久化运行日志(
sys_log,落 NAND)与控制台日志(console_log,走串口)。 两者都遵循单写者模型,避免多任务交错输出。
文件一览
| 文件 | 作用 |
|---|---|
sys_log.h |
日志接口(平台无关):定长 64B 记录写入 /log/run.log·selftest.log |
sys_log_mcu.c |
sys_log 的 MCU 端后端实现(NAND + FatFs) |
console_log.c/h |
控制台单写者日志:多任务日志经队列汇入唯一写者,避免串口输出交错 |
soe_rec.c/h |
SOE 记录纯编解码(B9):64B 定长记录(CP56Time2a 时标 + 类型 + 元件号 + 单调序号 + 值 + sum8 撕裂校验),零 I/O 零 OS 依赖,主机单测覆盖 |
soe_store.c/h |
SOE 落盘胶水(B9):/log/soe.log,protectTask 投递(零文件系统)→ defaultTask 排空落盘 |
关键机制
syslog(运行/自检记录)
- 唯一写者 = defaultTask;其它任务只投递,不直接写盘
- 单记录定长 64 B,循环写;待机页可显示记录
- 内核启动前的日志走无锁静态缓冲
s_preboot[8]暂存(因为内核启动前不能建队列), 内核启动后由syslog_drain惰性创建真队列并排空 - ⚠️ 写盘必须校验
f_write返回的字节数(该 FatFs 移植在簇分配失败时返回FR_OK+ 短写, 不报错误码);追加写必须FA_WRITE|FA_OPEN_APPEND(FA_OPEN_APPEND单独用不含写位)
console_log(控制台单写者)
- 多任务的生产者 → 单写者消费者,消除任务间日志行交错
- 设计详见
docs/子系统设计/控制台单写者日志设计说明.md
soe(SOE 事件顺序记录,B9)
- 记录 = 64 B 定长二进制(
soe_rec.h布局),落/log/soe.log; 与 run.log 分文件——run.log 是文本记录、有 UI/主机读取方,二进制 SOE 混入会破坏读取器;分文件保持"定长 64B + 条数=fsize/64 + 满删重建 + FTP 下载 + 主机解码"同一套先例 - 生产者 = protectTask(元件动作/返回、软压板投退、采样闭锁/恢复六类
边沿):
soe_store_post()纯 RAM(取时标 + 组记录 + 队列/待发环), 零 NAND I/O;⚠️ post 内禁DBG_*——会把 protectTask 静态最坏链 垫高一帧(实证 872→1192 B FAIL),丢弃留痕由 drain(defaultTask)差值上报 - 唯一写者 = defaultTask:挂
syslog_drain()尾部(继承其卷就绪门), 50 ms 节拍 → SOE 落盘延迟 ≤50 ms;写盘纪律全套照 syslog(卷锁 try /FA_WRITE|FA_OPEN_APPEND/ 校验字节数 / 满删重建 / 失败留痕去重), 绝不把名字/参数类错误码归入卷损坏触发集 - 时标口径:sys_clock 秒域时基 → CP56 ms 域 = 秒×1000(亚秒恒 0, 不造假);墙钟未建 → IV 位如实置位;seq 仅上电生命周期内单调 (容量重建不重置,仅复位重置——主机解码按 boot 段分段断言)
- 主机侧:
test/host/soe/decode_soe.py解码 + 断言(校验和/seq 单调/ CP56↔epoch UTC 交叉);⚠️ 交叉校验用calendar.timegm,不是本地时区的dt.timestamp()(+08 机差 28800 s,首跑实证坑)
相关文档
docs/子系统设计/控制台单写者日志设计说明.mddocs/子系统设计/运行与自检记录设计说明.md- FatFs 陷阱:
AGENTS.md「平台约束」