Files
STM32F4-Base/App/log/console_log.h
wandering1 1d4b6a17ce fix(控制台): 未注册期裸发加"整行独占",让路整行入环不丢,修掉 O1 第二实例
台架一手证据(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 表)。
2026-10-26 19:34:29 +08:00

91 lines
5.6 KiB
C
Raw Permalink 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.
/*
* 模块名称: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 */