看门狗 §3.2 流程图重绘为 SVG:任务置位→校验者判定→喂狗 + 时间轴

This commit is contained in:
2026-10-01 06:35:57 +08:00
parent 2d414f8aa9
commit 0ac68605dd
5 changed files with 498 additions and 20 deletions
+126
View File
@@ -0,0 +1,126 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="950" viewBox="0 0 1440 950" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
<text x="40" y="44" font-size="21" font-weight="700" fill="#0f172a">看门狗心跳模型 · 任务置位 → 校验者判定 → 校验者喂狗</text>
<text x="40" y="70" font-size="13" fill="#64748b">IWDG + 单字心跳位图 · 由 defaultTask 独占喂狗权(唯一喂狗者)</text>
<rect x="40" y="98" width="16" height="16" rx="4" fill="#ffffff" stroke="#2563eb" stroke-width="2"/>
<text x="62" y="110" font-size="12" fill="#475569">生产者(任务置位)</text>
<rect x="200" y="98" width="16" height="16" rx="4" fill="#ffffff" stroke="#0d9488" stroke-width="2"/>
<text x="222" y="110" font-size="12" fill="#475569">心跳位图</text>
<rect x="300" y="98" width="16" height="16" rx="4" fill="#ffffff" stroke="#7c3aed" stroke-width="2"/>
<text x="322" y="110" font-size="12" fill="#475569">校验者(喂狗者)</text>
<rect x="448" y="98" width="16" height="16" rx="4" fill="#ffffff" stroke="#16a34a" stroke-width="2"/>
<text x="470" y="110" font-size="12" fill="#475569">喂狗</text>
<rect x="524" y="98" width="16" height="16" rx="4" fill="#ffffff" stroke="#e11d48" stroke-width="2"/>
<text x="546" y="110" font-size="12" fill="#475569">停喂 / 复位</text>
<rect x="40" y="148" width="470" height="584" rx="14" fill="#f8fafc" stroke="#64748b" stroke-opacity="0.28"/>
<text x="60" y="178" font-size="15.0" font-weight="700" fill="#334155">各任务主循环(生产者)—— 每轮只置位,不清零</text>
<text x="60" y="202" font-size="13" font-weight="700" fill="#2563eb">s_hb_bits |= (1u &lt;&lt; MY_ID)</text>
<rect x="62.0" y="220.0" width="426.0" height="62" rx="9" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.5"/>
<rect x="74.0" y="230.0" width="34" height="24" rx="6" fill="#2563eb"/>
<text x="91.0" y="246.5" font-size="12" font-weight="700" fill="#ffffff" text-anchor="middle">b0</text>
<text x="118.0" y="246.0" font-size="14" font-weight="700" fill="#0f172a">defaultTask</text>
<text x="118.0" y="266.0" font-size="11.5" fill="#64748b">配置属主 / syslog 唯一写者 · 50ms 基准</text>
<rect x="62.0" y="300.0" width="426.0" height="62" rx="9" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.5"/>
<rect x="74.0" y="310.0" width="34" height="24" rx="6" fill="#2563eb"/>
<text x="91.0" y="326.5" font-size="12" font-weight="700" fill="#ffffff" text-anchor="middle">b1</text>
<text x="118.0" y="326.0" font-size="14" font-weight="700" fill="#0f172a">netTask</text>
<text x="118.0" y="346.0" font-size="11.5" fill="#64748b">net_poll 循环 · 1ms 节拍</text>
<rect x="62.0" y="380.0" width="426.0" height="62" rx="9" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.5"/>
<rect x="74.0" y="390.0" width="34" height="24" rx="6" fill="#2563eb"/>
<text x="91.0" y="406.5" font-size="12" font-weight="700" fill="#ffffff" text-anchor="middle">b2</text>
<text x="118.0" y="406.0" font-size="14" font-weight="700" fill="#0f172a">ftpTask</text>
<text x="118.0" y="426.0" font-size="11.5" fill="#64748b">lftpd 事件循环(需在 lftpd 侧埋点)</text>
<rect x="62.0" y="460.0" width="426.0" height="62" rx="9" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.5"/>
<rect x="74.0" y="470.0" width="34" height="24" rx="6" fill="#2563eb"/>
<text x="91.0" y="486.5" font-size="12" font-weight="700" fill="#ffffff" text-anchor="middle">b4</text>
<text x="118.0" y="486.0" font-size="14" font-weight="700" fill="#0f172a">uiTask</text>
<text x="118.0" y="506.0" font-size="11.5" fill="#64748b">LVGL 主循环 · 10ms 节拍</text>
<rect x="62.0" y="540.0" width="426.0" height="62" rx="9" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.5"/>
<rect x="74.0" y="550.0" width="34" height="24" rx="6" fill="#2563eb"/>
<text x="91.0" y="566.5" font-size="12" font-weight="700" fill="#ffffff" text-anchor="middle">b5</text>
<text x="118.0" y="566.0" font-size="14" font-weight="700" fill="#0f172a">timeSyncTask</text>
<text x="118.0" y="586.0" font-size="11.5" fill="#64748b">NTP 轮询 · ≤50ms</text>
<rect x="62.0" y="626.0" width="426.0" height="48" rx="9" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.6"/>
<text x="76.0" y="646.0" font-size="12" font-weight="700" fill="#0d9488">心跳位图 s_hb_bits(32 位单字)</text>
<text x="76.0" y="664.0" font-size="11" fill="#64748b">就绪掩码 0x37 = bit0|1|2|4|5(bit3=testTask 默认不在内)</text>
<rect x="76.0" y="682.0" width="34" height="22" rx="4" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.70"/>
<text x="93.0" y="697.0" font-size="11" font-weight="700" fill="#0d9488" text-anchor="middle">0</text>
<rect x="116.0" y="682.0" width="34" height="22" rx="4" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.70"/>
<text x="133.0" y="697.0" font-size="11" font-weight="700" fill="#0d9488" text-anchor="middle">1</text>
<rect x="156.0" y="682.0" width="34" height="22" rx="4" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.70"/>
<text x="173.0" y="697.0" font-size="11" font-weight="700" fill="#0d9488" text-anchor="middle">2</text>
<rect x="196.0" y="682.0" width="34" height="22" rx="4" fill="#f1f5f9" stroke="#94a3b8" stroke-opacity="0.40"/>
<text x="213.0" y="697.0" font-size="11" font-weight="700" fill="#94a3b8" text-anchor="middle">3</text>
<rect x="236.0" y="682.0" width="34" height="22" rx="4" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.70"/>
<text x="253.0" y="697.0" font-size="11" font-weight="700" fill="#0d9488" text-anchor="middle">4</text>
<rect x="276.0" y="682.0" width="34" height="22" rx="4" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.70"/>
<text x="293.0" y="697.0" font-size="11" font-weight="700" fill="#0d9488" text-anchor="middle">5</text>
<line x1="488.0" y1="251.0" x2="484.0" y2="251.0" stroke="#2563eb" stroke-width="2.2"/>
<line x1="488.0" y1="331.0" x2="484.0" y2="331.0" stroke="#2563eb" stroke-width="2.2"/>
<line x1="488.0" y1="411.0" x2="484.0" y2="411.0" stroke="#2563eb" stroke-width="2.2"/>
<line x1="488.0" y1="491.0" x2="484.0" y2="491.0" stroke="#2563eb" stroke-width="2.2"/>
<line x1="488.0" y1="571.0" x2="484.0" y2="571.0" stroke="#2563eb" stroke-width="2.2"/>
<line x1="484.0" y1="251.0" x2="484.0" y2="650.0" stroke="#2563eb" stroke-width="2.2"/>
<line x1="484.0" y1="650.0" x2="488.0" y2="650.0" stroke="#2563eb" stroke-width="2.2"/>
<path d="M 488.0 650.0 L 477.0 655.5 L 477.0 644.5 z" fill="#2563eb"/>
<rect x="544" y="148" width="856" height="584" rx="14" fill="#f8fafc" stroke="#64748b" stroke-opacity="0.28"/>
<text x="564" y="178" font-size="15.0" font-weight="700" fill="#334155">defaultTask 主循环(唯一喂狗者 · 消费者)</text>
<rect x="566.0" y="204.0" width="812.0" height="62.0" rx="10" fill="#f5f3ff" stroke="#7c3aed" stroke-opacity="0.55"/>
<text x="580.0" y="228.0" font-size="14" font-weight="700" fill="#0f172a">① 节流:每 100ms 抽样一次</text>
<text x="580.0" y="246.5" font-size="11.5" fill="#64748b">now - last_tick &lt; 100ms 则直接返回(defaultTask 50ms 循环的整数倍)</text>
<line x1="972.0" y1="266.0" x2="972.0" y2="306.0" stroke="#7c3aed" stroke-width="2.2"/>
<path d="M 972.0 306.0 L 966.5 295.0 L 977.5 295.0 z" fill="#7c3aed"/>
<rect x="566.0" y="306.0" width="812.0" height="62.0" rx="10" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="580.0" y="330.0" font-size="14" font-weight="700" fill="#0f172a">② 取走并清零(单字原子)</text>
<text x="580.0" y="348.5" font-size="11.5" fill="#64748b">snapshot = s_hb_bits; s_hb_bits = 0; ← 读-改-写天然原子,免临界区</text>
<line x1="972.0" y1="368.0" x2="972.0" y2="408.0" stroke="#0d9488" stroke-width="2.2"/>
<path d="M 972.0 408.0 L 966.5 397.0 L 977.5 397.0 z" fill="#0d9488"/>
<rect x="566.0" y="408.0" width="812.0" height="62.0" rx="10" fill="#f5f3ff" stroke="#7c3aed" stroke-opacity="0.55"/>
<text x="580.0" y="432.0" font-size="14" font-weight="700" fill="#0f172a">③ 掩码判定</text>
<text x="580.0" y="450.5" font-size="11.5" fill="#64748b">snapshot == 0x37(bit0|1|2|4|5 全中)?</text>
<line x1="488.0" y1="650.0" x2="544.0" y2="650.0" stroke="#0d9488" stroke-width="2.2"/>
<line x1="544.0" y1="650.0" x2="544.0" y2="337.0" stroke="#0d9488" stroke-width="2.2"/>
<line x1="544.0" y1="337.0" x2="566.0" y2="337.0" stroke="#0d9488" stroke-width="2.2"/>
<path d="M 566.0 337.0 L 555.0 342.5 L 555.0 331.5 z" fill="#0d9488"/>
<text x="494.0" y="642.0" font-size="11.5" fill="#0d9488" text-anchor="middle">快照</text>
<line x1="972.0" y1="470.0" x2="972.0" y2="484.0" stroke="#7c3aed" stroke-width="2.2"/>
<line x1="763.0" y1="484.0" x2="763.0" y2="484.0" stroke="#16a34a" stroke-width="2.2"/>
<line x1="972.0" y1="484.0" x2="1181.0" y2="484.0" stroke="#d97706" stroke-width="2.2"/>
<line x1="763.0" y1="484.0" x2="763.0" y2="486.0" stroke="#16a34a" stroke-width="2.2"/>
<path d="M 763.0 486.0 L 757.5 475.0 L 768.5 475.0 z" fill="#16a34a"/>
<line x1="1181.0" y1="484.0" x2="1181.0" y2="486.0" stroke="#d97706" stroke-width="2.2"/>
<path d="M 1181.0 486.0 L 1175.5 475.0 L 1186.5 475.0 z" fill="#d97706"/>
<text x="763.0" y="480.0" font-size="11.5" font-weight="700" fill="#16a34a" text-anchor="middle">是</text>
<text x="1181.0" y="480.0" font-size="11.5" font-weight="700" fill="#d97706" text-anchor="middle">否</text>
<rect x="566.0" y="486.0" width="394.0" height="84.0" rx="10" fill="#f0fdf4" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="580.0" y="510.0" font-size="13" font-weight="700" fill="#0f172a">全员到位 → 喂狗</text>
<text x="580.0" y="527.5" font-size="10.5" fill="#64748b">s_miss_streak = 0</text>
<text x="580.0" y="545.0" font-size="10.5" fill="#64748b">IWDG-&gt;KR = 0xAAAA;</text>
<rect x="984.0" y="486.0" width="394.0" height="84.0" rx="10" fill="#fffbeb" stroke="#d97706" stroke-opacity="0.55"/>
<text x="998.0" y="510.0" font-size="13" font-weight="700" fill="#0f172a">有缺席 → 累计,不喂狗</text>
<text x="998.0" y="527.5" font-size="10.5" fill="#64748b">s_miss_total++ / streak++</text>
<text x="998.0" y="545.0" font-size="10.5" fill="#64748b">连续 8 次 → 打 [WD] task stall 后停喂</text>
<rect x="40" y="766" width="1360" height="150" rx="14" fill="#f8fafc" stroke="#64748b" stroke-opacity="0.28"/>
<text x="60" y="794" font-size="15.0" font-weight="700" fill="#334155">停喂之后:时间轴上的三个时刻(0.8s 停喂 → 1.7s 最坏溢出 → 2.5s 标称)</text>
<rect x="100.0" y="828.0" width="396.8" height="14" rx="3" fill="#16a34a" opacity="0.75"/>
<text x="298.4" y="862.0" font-size="11.5" fill="#16a34a" text-anchor="middle">仍照常喂狗</text>
<text x="298.4" y="878.0" font-size="10.5" fill="#94a3b8" text-anchor="middle">miss_streak 1 → 8</text>
<rect x="496.8" y="828.0" width="446.4" height="14" rx="3" fill="#e11d48" opacity="0.75"/>
<text x="720.0" y="862.0" font-size="11.5" fill="#e11d48" text-anchor="middle">已停喂 → 等 IWDG 自然溢出</text>
<text x="720.0" y="878.0" font-size="10.5" fill="#94a3b8" text-anchor="middle">worst case(LSI 47kHz)</text>
<rect x="943.2" y="828.0" width="396.8" height="14" rx="3" fill="#d97706" opacity="0.75"/>
<text x="1141.6" y="862.0" font-size="11.5" fill="#d97706" text-anchor="middle">标称溢出窗口</text>
<text x="1141.6" y="878.0" font-size="10.5" fill="#94a3b8" text-anchor="middle">LSI 32kHz</text>
<line x1="100.0" y1="822.0" x2="100.0" y2="842.0" stroke="#475569" stroke-width="1.6"/>
<text x="100.0" y="816.0" font-size="11" font-weight="700" fill="#475569" text-anchor="middle">0s</text>
<line x1="496.8" y1="822.0" x2="496.8" y2="842.0" stroke="#475569" stroke-width="1.6"/>
<text x="496.8" y="816.0" font-size="11" font-weight="700" fill="#475569" text-anchor="middle">0.8s</text>
<line x1="943.2" y1="822.0" x2="943.2" y2="842.0" stroke="#475569" stroke-width="1.6"/>
<text x="943.2" y="816.0" font-size="11" font-weight="700" fill="#475569" text-anchor="middle">1.7s</text>
<line x1="1340.0" y1="822.0" x2="1340.0" y2="842.0" stroke="#475569" stroke-width="1.6"/>
<text x="1340.0" y="816.0" font-size="11" font-weight="700" fill="#475569" text-anchor="middle">2.5s</text>
<line x1="496.8" y1="824.0" x2="496.8" y2="848.0" stroke="#e11d48" stroke-width="1.6" stroke-dasharray="5 4"/>
<line x1="943.2" y1="824.0" x2="943.2" y2="848.0" stroke="#d97706" stroke-width="1.6" stroke-dasharray="5 4"/>
<line x1="496.8" y1="896.0" x2="943.2" y2="896.0" stroke="#16a34a" stroke-width="1.6" stroke-dasharray="3 3"/>
<text x="720.0" y="912.0" font-size="11" fill="#16a34a" text-anchor="middle">裕度 ≈ 900ms</text>
<text x="40" y="936" font-size="12" fill="#64748b">喂狗 = &quot;所有被监视任务都在 100ms 窗口内动过&quot;;任一任务卡死 → 位图长期残缺 → 停喂 → IWDG 复位(保留 IWDGRSTF 权威归因)。对应 task_watchdog.c / 设计说明 §3.2。</text>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

+16 -20
View File
@@ -204,30 +204,26 @@ IWDG 是**"写键值"式外设**:写 `0x5555` 解锁 → 配 `PR`/`RLR` →
### 3.2 采用方案:任务置位 → 校验者判定 → 校验者喂狗
```
┌──────────────── 各自任务循环 ────────────────┐
│ defaultTask / netTask / ftpTask / uiTask │
│ / timeSyncTask / testTask │
│ 每轮循环:s_hb_bits |= (1u << MY_ID) │ ← 无锁,单字原子写
└───────────────────────┬──────────────────────┘
│ (位图,仅置位,不清零)
▼
┌──────────── defaultTask(唯一喂狗者)─────────┐
│ 每 100 ms 抽样: │
│ snapshot = s_hb_bits; s_hb_bits = 0; │ ← 取走并清零
│ if (snapshot != ALL_REQUIRED_MASK) │
│ → 有任务在 100 ms 内没动过 │
│ → 累计未达标次数 / 记录缺失任务名 │
│ if (连续 N 次未达标) HAL_IWDG_Refresh(); │ ← 只在全员健康时喂
└───────────────────────────────────────────────┘
│
▼
IWDG(超时 2.5 s,PR=4 / RLR=1249)
```
![看门狗心跳模型:任务置位 → 校验者判定 → 校验者喂狗](../images/看门狗心跳模型.svg)
> 图源:`tools/arch_diagram/gen_watchdog_svg.py`(改图改脚本重跑,见
> [`../架构设计/图形绘制流程.md`](../架构设计/图形绘制流程.md))。
**语义**:喂狗 = "**所有被监视的任务都在窗口内动过**"。
任何单个任务卡死 → 位图长期残缺 → 喂狗停止 → 2.5 s 后 IWDG 复位。
具体到代码(`App/util/task_watchdog.c`):
| 阶段 | 代码 |
|---|---|
| ① 任务置位(生产者) | `s_hb_bits \|= (1u << id)`(`task_wd_beat`,仅置位不清零) |
| ② 校验者取走清零 | `snapshot = s_hb_bits; s_hb_bits = 0u;`(`task_wd_poll`,单字原子) |
| ③ 掩码判定 | `snapshot == wd_required_mask()`(默认 `0x37` = bit0\|1\|2\|4\|5) |
| ④ 判定为真 → 喂狗 | `s_miss_streak = 0u; IWDG->KR = 0x0000AAAAu;` |
| ④ 判定为假 → 累计 | `s_miss_total++`;连续 8 次(800 ms)后打 `[WD] task stall` 并**停喂** |
> 图中时间轴的四个时刻(0 / 0.8 s / 1.7 s / 2.5 s)与约 900 ms 裕度的推导见 §4.3。
### 3.3 为什么心跳位图用"取走并清零"
```
+1
View File
@@ -48,6 +48,7 @@
| `gen_cfg_arch_svg.py` | `docs/images/配置文件分层架构.svg` | 配置引擎分层 |
| `gen_net_stack_svg.py` | `docs/images/BSD网络栈分层.svg` | BSD 网络栈分层 |
| `gen_memory_svg.py` | `docs/images/内存布局_*.svg` | RAM / FLASH 布局 |
| `gen_watchdog_svg.py` | `docs/images/看门狗心跳模型.svg` | `TASKS` / `REQ_BITS` / `POLL_STEPS` / `TIMELINE` |
---