1d4b6a17ce
台架一手证据(boot_log_data/20261026_prod_console_interleave_evidence.txt):两次 「仅复位不重烧」启动里,[PROT] 未标定 INFO 行内分别多出 3 个和 1 个字节,而紧邻其后 的 [NAND_FTL] 行正好缺自己行首同样数量的字节("[20" 与 "[")——串口 framing 错不会 让行变长,字节按原序出现在邻居里 ⇒ 两个写者并发逐字节写 USART1->DR。写者对: protectTask 的未标定闭锁边沿 × defaultTask 的 storage_bring_up(),两者都落在 dbg_console_writer_attach() 之前(attach 排在存储 bring-up 之后),该窗口 s_con_writer_up == 0 ⇒ 任何任务的整行都走零互斥裸发。这是控制台单写者设计说明在册 O1 的**第二个实例**;2026-10-12 那次只补了"自愿门控",而自愿门控挡不住业务边沿留痕 (业务无法预知某条边沿落在哪个窗口),所以这次被同一窗口第二次咬到。 修法是**非阻塞整行独占**:con_tx_try_claim/release 只在几条指令的临界区里 test-and-set,绝不把 7.8 ms 的逐字节发送包进关中断区、绝不等待;IPSR 上下文与调度 器未启动两种情况显式放行(崩溃遗言必须能出去;早期本就单执行流,一条不丢);同任务 按深度嵌套。抢不到独占的那条行**原文入环**延后发——前缀与时间戳在 DBG_* 调用点就 格式化进调用方缓冲,本模块与 drain 都不补前缀、不重打时间戳,所以延后发出的行与当场 直发逐字节相同;[ERR] 只在「抢不到独占且环也放不下」时才红,按 3 次启动命中 2 次的 频率,让路若计 [ERR] 就会把静默缺陷换成常红门禁。顺带堵掉第二处洞:drain 每轮先取 凭证,拿不到就让位且 tail 不推进(旧写法 attach 可以撞上别人已发出的半行)。 新导出 dbg_console_tx_begin()/tx_end() 供 task-26 收口故障钩子的多段裸发。 否决过的两条路写进文档 §4.4.1:提前 attach 会把等串口塞进 mount/mkfs 时序敏感段 或让生产者在整个 bring-up 里堵在满环上;整段进环会让「无看门狗窗口」死机时一个字都 不出(2026-10-02 那几轮正是靠 bring-up 期流式日志取证的)。 判据:test/host/console_log 127 → 152 全绿(M 族 TC-CONLOG-170..179 共 +25,既有 127 条零删除)。唯一换钉法的是 TC-CONLOG-015——旧断言钉的正是缺陷机理本身(裸发 0 次临界区 = 零互斥),现改钉「恰 2 次几条指令的临界区 + 每个上线字节都在临界区之外 + 零等待」;硬红线由假 USART 在每个上线字节处记录临界区深度水位恒 0 来机器守住。四处 注入反证各自转红 11 / 5 / 1 / 3 条并逐字节还原复跑;覆盖率 console_log.c 199/200 行 → 256/257 行、16/16 → 21/21 函数。邻域复跑:task_prof 27/27、sys_log_mcu 137/137、 iec104_serv 37/37。CONLOG 游标抬到 179。 台架复验判据(**本笔尚未上机**,下一轮请台架跑):连续 3~5 次「仅复位不重烧」启动 日志 [ERR] 零命中,且 [PROT] 未标定与 [NAND_FTL] Initializing 两行都完整。 成本:.bss +5 B、Flash 估 +100~150 B,零新增 DBG_* / 零新增缓冲 / 零堆 / 零内核对象。 文档:控制台单写者日志设计说明.md 升 v1.6(O1 改「已处置」含两实例与字节账、新增 §4.4.1、§3.4 并发区间、§4.4 取消「不加锁」口径、§7 失效表、§11 变更记录、API 表)。
91 lines
5.6 KiB
C
91 lines
5.6 KiB
C
/*
|
||
* 模块名称:Console Single-Writer Logging — 控制台单写者日志(USART1 调试串口)
|
||
* 模块功能:调试控制台单写者环形缓冲的公共 API:所有任务/上下文(含 printf)只把
|
||
* 已格式化整行文本入环形缓冲,写者任务(defaultTask)为唯一
|
||
* HAL_UART_Transmit 发送点(行级原子 + 指针可控)
|
||
* 使用方法:
|
||
* 1. 生产者:DBG_ERROR/DBG_INFO/DBG_DEBUG 宏(dbg_log.h,内部用库 snprintf
|
||
* 有界格式化整行后调 dbg_console_write_line);或直接调 dbg_console_write_line
|
||
* 2. 写者任务:进入主循环前调 dbg_console_writer_attach() 注册为唯一发送者,
|
||
* 循环中周期调 dbg_console_drain() 排空(defaultTask 已接线,见 Src/freertos.c)
|
||
* 适用平台:STM32F407ZGTx(仅 MCU 端;模拟器不编译本模块——模拟器 DBG_*
|
||
* 直接映射 printf,见 simulator/dbg_log.h)
|
||
* 作者:王建锋
|
||
* 创建日期:2026-09-24
|
||
* 修改记录:
|
||
* 2026-09-24 王建锋 创建(自 Src/usart.c USER CODE 区迁出:
|
||
* HAL 库生成文件不得包含应用代码)
|
||
* 2026-10-26 王建锋 道G task-23:裸发路径加「行级单一所有者」(O1 第二实例),
|
||
* 新增 dbg_console_tx_begin()/dbg_console_tx_end() 供"一条行分多段裸发"
|
||
* 的调用方收口;dbg_console_write_raw() 的"不加锁"三个字从接口上取消
|
||
*/
|
||
|
||
#ifndef CONSOLE_LOG_H
|
||
#define CONSOLE_LOG_H
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
#include <stdio.h>
|
||
#include <stdint.h>
|
||
|
||
/*
|
||
* 单写者控制台 API(与 DBG_ENABLE 无关,任何构建都必须可见):
|
||
* dbg_console_write_line - 生产者入口:调用方已格式化好的整行文本一次入环形缓冲
|
||
* (刻意**不用**自研可变参数函数——规范 S12 7.6 禁止;格式化在
|
||
* DBG_PRINT 宏内用库 snprintf 完成)
|
||
* dbg_console_write_raw - 异常/早期路径:有界单次直发;**整行独占发送器**
|
||
* (道G task-23:拿不到独占就把整行原文转入环形缓冲延后发,
|
||
* 与当场直发逐字节相同——不补前缀、不重打时间戳;
|
||
* 连环都放不下才计入 dbg_console_dropped())
|
||
* dbg_console_writer_attach- 写者任务(defaultTask)注册自己为唯一发送者
|
||
* dbg_console_writer_ready - 写者是否已注册(1=已注册)。供**周期性任务**在写者就绪前
|
||
* 抑制日志用:写者未注册时 con_append 走无锁直发路径
|
||
* (仅假设 defaultTask 在日志),其余任务此时落日志会与
|
||
* defaultTask 的存储初始化日志字节级交错(2026-10-10 台架实测)
|
||
* dbg_console_drain - 写者任务排空缓冲并发送(单次有界;TX 失败自动软复位自愈)
|
||
* dbg_console_dropped - 真没落地的丢弃数:① 缓冲满丢的一行;② 一个没能确认
|
||
* 送达而出环的块;③ 抢不到整行所有权**且**环也放不下的裸发
|
||
* (道G task-23)。诊断;[ERR] CONSOLE dropped 行报的就是它。
|
||
* ⚠ 抢不到所有权但成功让路入环的那条行**不入本账**(它没丢,
|
||
* 只是晚一点发)——这本账必须保持"红得有意义",因为
|
||
* [ERR] 是 @test 的 Forbidden 标记)
|
||
* dbg_console_tx_fault - 因僵尸通道软复位自愈的次数(诊断,语义不变)
|
||
* dbg_console_tx_timeout - 写者排空时观察到的块超时次数(TXE / TC 两条硬上界都算)。
|
||
* 三本账分开:看日志有没有在丢 -> dropped;看外设自愈了几次
|
||
* -> tx_fault;看 UART 物理失效(含"字节已发出但 TC 不置位")
|
||
* -> tx_timeout。别把三本账混成一个数(docs §13-O2 )
|
||
* dbg_console_tx_fault_log_take
|
||
* - 取走"TX 故障待上报"标志(读后清)。**必须**由写者任务在
|
||
* dbg_console_drain() 之外调用后自行 syslog_post——本模块
|
||
* 刻意不调日志子系统,否则形成 write_line→con_append→drain
|
||
* →syslog_post→write_line 调用图成环,静态栈深全线虚高
|
||
*/
|
||
#define DBG_CON_LINE_MAX 192U /* 单行文本上限(含结尾 '\0',超出截断) */
|
||
int dbg_console_write_line(const char *line, unsigned len);
|
||
void dbg_console_write_raw(const char *buf, unsigned len);
|
||
uint8_t dbg_console_tx_begin(void); /* 1=已独占一条行;0=别人持有(调用方照发即可) */
|
||
void dbg_console_tx_end(void); /* 与 tx_begin 成对;未持有时空操作(幂等) */
|
||
void dbg_console_writer_attach(void);
|
||
uint8_t dbg_console_writer_ready(void);
|
||
void dbg_console_drain(void);
|
||
uint32_t dbg_console_dropped(void);
|
||
uint32_t dbg_console_tx_fault(void);
|
||
uint32_t dbg_console_tx_timeout(void);
|
||
uint8_t dbg_console_tx_fault_log_take(void);
|
||
|
||
/*
|
||
* 函数功能:MicroLIB printf 底层重定向(C 库 retarget 钩子,须为全局符号)
|
||
* 入口参数:ch - 字符;f - 文件流(忽略)
|
||
* 返回值:原样返回 ch
|
||
* 限定条件:printf 调用点执行(任意任务上下文);字节交单写者路径
|
||
*/
|
||
int fputc(int ch, FILE *f);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* CONSOLE_LOG_H */
|