Files
STM32F4-Base/App/util/task_prof.h
T
wandering1 54d6d2d5d5 docs: 删除 Task #32 停摆分析(已结案),可复用结论并入看门狗设计说明 §11
按登记簿"完成即销项"规则删除 docs/问题记录/UI任务看门狗停摆分析.md
(560 行)。结案依据:累计 38 次上板启动 0 复现(原 L2 基线 3/10),
P0/P1-4a/P1-5/P2/方案 A 全部落地,方案 B 已评估决定不做。

并入 docs/子系统设计/看门狗设计说明.md 新增 §11(只留可复用部分):
- §11.1 三条易误读的判读口径:启动期 MISS start 0x67 是良性 episode;
  miss= 是累计不是连续(连续数饱和在 WD_MISS_TOLERANCE=8);
  reset cause 不能区分 IWDG 溢出与台架软件复位
- §11.2 诊断四步:先看掩码缺几位(只缺 1 位 = 定点打击,多位 = 全局
  饥饿)→ 逐项量化占空比(实测 10%~13%,否掉"CPU 饱和"直觉)→
  用计数速率反证空转(ring 恰好 1000/s)→ 用 [PROF] 指认占用者
- §11.3 三条结构性脆弱点(uiTask 唯一 BelowNormal/心跳曾依赖 netTask
  往返/自定截止期不可恢复)+ 两个顺带修掉的正确性缺陷(无新帧仍压环
  ≈3% 频率失配、失败拍静默沿用旧值)
- §11.4 [PROF]/[PROF2] 全字段语义 + v2.8 实测基线(netp=0 是真实值、
  adcm 峰值来自心跳日志、uip 地板 = LVGL 帧周期)与三机理判读口诀
- §11.5 三条教训:门控类优化必须保留低频兜底;门控会顺手消灭暴露路径;
  事件驱动要核对节拍源启动时刻 vs 消费者创建时刻
- §11.6 明确不做的四条及理由(提 uiTask 优先级会掩盖真缺陷、net_* 有界
  等待版会串回执、推流搬出 uiTask、推流塞进 netTask)

UI 推流门控与方案 B 否决理由收进 RemoDispBus 协议说明 §3(该处原本已有
摘要,改为自洽不再回指);就绪门控 API 的约束已在 BSD Socket 指南 §10.1。

引用改指 12 处:ui_task.c、task_prof.c/.h、ui_net_sink.c/.h、docs/README、
RemoDispBus、看门狗设计说明 R7 行、ADC 采样节拍设计说明、BSD Socket 指南
×2、项目审查报告 N-10 ×2。

⚠ ui_task.c / ui_net_sink.c / ui_net_sink.h 是带 BOM 文件,Edit 剥掉了
BOM,已按原样补回 3 字节 EF BB BF(未整文件重写)。

验证:check_doc_links --all 通过(127 个文档,无悬空引用);
@build 0 错 0 警,Code=257660 与删除前一字不差(纯注释改动)。
2026-10-11 00:38:50 +08:00

141 lines
7.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.
/*
* 模块名称:Task Profiler(任务耗时最坏值探针)
* 模块功能:用内核 DWT 周期计数器(CYCCNT,168 MHz 主频下 168 周期 = 1 us)为
* 各任务主循环的**最坏耗时 / 执行次数 / "此刻是否仍在某段内"** 提供
* 常开、低开销的观测面,经控制台单写者输出 `[PROF]` 行。
* 用途:把"某任务心跳缺席"这一结果拆成三种互斥机理,供归因定位。
* 适用平台:STM32F407ZGTx(仅 MCU 端:依赖 DWT 与 HAL 时基;模拟器/主机单测
* 不编译本模块)
* 作者:王建锋
* 创建日期:2026-10-10
* 修改记录:
* v1.0 2026-10-10 王建锋 创建(Task #32 的 P2 测量埋点)
*
* 设计背景(docs/子系统设计/看门狗设计说明.md §11.4):
* uiTask 偶发心跳缺席导致 IWDG 复位,但"占用者"无法从既有日志判定。
* 同一现象至少对应三种互斥机理,观测上必须能区分:
* ① 被调度饿死 → 循环周期长,而段内耗时短;
* ② 阻塞在网络往返 → `ui_net_sink_poll()` 段内耗时长;若永久阻塞,
* 该段样本**永不结算**,只有"此刻仍在段内已 X us"能证明;
* ③ 自身渲染拖长 → 单轮工作耗时长。
* 故每个探针记录 max / count / last,并对可疑段记录 enter/exit。
*
* ⚠ 开销:一次采样 = 1 次 DWT 读 + 数次比较/存储(< 20 周期);对 1 ms 节拍
* (168000 周期)可忽略。无动态分配、无内核对象、无临界区、无阻塞。
* ⚠ 单写者假设:同一探针 ID 只由**一个任务**写入(见下表),报告由
* defaultTask 读取。32 位对齐读写在 Cortex-M4 上原子,读侧不会撕裂。
*
* 探针 ID 写入者 含义(enter/exit = 可读出"此刻仍在段内已多久")
* UI_PERIOD uiTask 上轮循环起点 → 本轮循环起点(enter/exit)
* UI_WORK uiTask 本轮起点 → 心跳前(含 LVGL 渲染 + 推流轮询)
* UI_SINK uiTask ui_net_sink_poll() 单次往返(enter/exit)
* ADC_BEAT adcTask 单拍起点 → 心跳后(含取数 + 压环 + 相量 + 心跳日志)
* ADC_POLL adcTask adc_input_poll() 单次(enter/exit)——隔离 ADC 超时成本
* DEF_DRAIN defaultTask dbg_console_drain()(enter/exit)——控制台忙等发送
* DEF_SYSLOG defaultTask syslog_drain()(enter/exit)——NAND/FTL 写
* NET_POLL netTask net_poll()(enter/exit)——CH395F 驱动轮询
*
* 后四个探针用于**指认占用者**:UI 心跳缺席时,谁的最坏耗时在同一窗口抬起来,
* 谁就是挤占 CPU 的 Normal 任务(见 docs/子系统设计/看门狗设计说明.md §11.2 第 4 步)。
*/
#ifndef TASK_PROF_H
#define TASK_PROF_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* 探针 ID(顺序即内部槽位下标,新增只能追加) */
typedef enum {
TASK_PROF_UI_PERIOD = 0, /* uiTask 循环周期 */
TASK_PROF_UI_WORK = 1, /* uiTask 单轮工作耗时(不含 osDelay) */
TASK_PROF_UI_SINK = 2, /* ui_net_sink_poll() 往返耗时 */
TASK_PROF_ADC_BEAT = 3, /* adcTask 单拍耗时 */
TASK_PROF_ADC_POLL = 4, /* adc_input_poll() 耗时(隔离 ADC 超时成本) */
TASK_PROF_DEF_DRAIN = 5, /* defaultTask 控制台排空耗时 */
TASK_PROF_DEF_SYSLOG = 6, /* defaultTask syslog 落盘耗时 */
TASK_PROF_NET_POLL = 7, /* netTask net_poll() 耗时(CH395F 驱动) */
TASK_PROF_ID_COUNT
} task_prof_id_t;
/*
* 函数功能:读 DWT 周期计数器(懒使能;基准为"任意",仅供取差值)
* 入口参数:无
* 返回值:当前 CYCCNT 值(周期数)
* 限定条件:任意上下文可调(幂等;纯寄存器读,不创建任何内核对象)
* 函数说明:首次调用置 CoreDebug->DEMCR.TRCENA 与 DWT->CTRL.CYCCNTENA。
* 刻意**不清零** CYCCNT:本模块只用差值,清零无意义,且会与
* console_log.c 的同名惰性使能互相干扰。DWT 使能失败时恒返回 0,
* 采样侧把 0 判为"不可用"(见 task_prof_sample)。
*/
uint32_t task_prof_cyc(void);
/*
* 函数功能:记录一次耗时样本(周期数)
* 入口参数:id - 探针 ID
* cyc - 耗时(周期数;0 或超出合理上界视为不可用/计数器被重置)
* 返回值:无
* 限定条件:单写者(每个 ID 只由一个任务调用)
* 函数说明:更新 count / last / max。`cyc == 0`(DWT 未使能)或
* `cyc >= TASK_PROF_GLITCH_CYC`(计数器中途被重置造成的无符号回绕)
* 只累加 glitch 计数、不污染 max——否则一次计数器重置就会把
* 最坏值抬到 ~2^32 周期,掩盖真实数据。
*/
void task_prof_sample(task_prof_id_t id, uint32_t cyc);
/*
* 函数功能:标记"进入某段"(用于观测"此刻仍在段内已多久")
* 入口参数:id - 探针 ID
* 返回值:无
* 限定条件:单写者;须与 task_prof_exit 成对
* 函数说明:记录进入时刻并置"在段内"标志。若该任务此后永久阻塞在该段,
* 样本永不结算,只有报告侧的"仍在段内"字段能证明它卡在哪里。
*/
void task_prof_enter(task_prof_id_t id);
/*
* 函数功能:结算一段耗时(与 task_prof_enter 成对)
* 入口参数:id - 探针 ID
* 返回值:无
* 限定条件:单写者;须与 task_prof_enter 成对
* 函数说明:清"在段内"标志 → 以 enter 时刻为起点记一次样本。
* **未 enter 过(in_seg == 0)时直接返回**——这使"先 exit 再 enter"的
* 周期测量写法(如 uiTask 的循环周期)在首轮不会拿 0 当起点,
* 从而不产生一次虚假的超大样本。
*/
void task_prof_exit(task_prof_id_t id);
/*
* 函数功能:周期性打印 `[PROF]` / `[PROF2]` 行(内部节流:首次 5 s 后,此后每 2 s)
* 入口参数:无
* 返回值:无
* 限定条件:仅 defaultTask 主循环调用(唯一串口写者;静态行缓冲复用)
* 函数说明:整行一次 `dbg_console_write_line` 入环形缓冲(行级原子)。
* `[PROF]` = uiTask/adcTask 任务级(周期 / 工作 / 往返 / 单拍);
* `[PROF2]` = 占用者段级(defaultTask 排空与落盘、netTask 驱动轮询、
* adcTask ADC 单次),用于指认"谁把 CPU 占住了"。
* 用**静态**行缓冲而非栈缓冲:本函数挂在 defaultTask 的最坏调用链上,
* 栈上再放一个 192 B 行缓冲会挤占该任务的栈预算。
*/
void task_prof_report(void);
/*
* 函数功能:停摆现场快照——同 `[PROF]` + `[PROF2]` 两行,走**裸 UART 直发**
* 入口参数:无
* 返回值:无
* 限定条件:仅 defaultTask 调用(此时它就是唯一串口写者,无并发)
* 函数说明:用于"某任务心跳刚由'到过'变为'缺席'"的瞬间。缺席窗口内环形缓冲
* 可能积压,裸直发保证这两行能出去(与 task_wd_poll 的 `MISS start`
* 标记同款理由)。
*/
void task_prof_report_raw(void);
#ifdef __cplusplus
}
#endif
#endif /* TASK_PROF_H */