Net 层阻塞模型架构设计 §2 架构总览改用 SVG 矢量图
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="1030" viewBox="0 0 1440 1030" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
|
||||
<rect x="0" y="0" width="1440" height="1030" fill="#ffffff"/>
|
||||
<text x="40" y="44" font-size="21" font-weight="700" fill="#0f172a">Net 层阻塞模型 · pending 请求架构</text>
|
||||
<text x="40" y="70" font-size="13" fill="#64748b">把「等数据」从执行者内的自旋,改为执行者外的登记 —— 调用方在自己上下文睡眠,netTask 零占用地服务整个队列</text>
|
||||
<rect x="40.0" y="100" width="16" height="16" rx="4" fill="#ffffff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="64.0" y="112" font-size="12.5" fill="#475569">调用方任务</text>
|
||||
<rect x="150.5" y="100" width="16" height="16" rx="4" fill="#ffffff" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="174.5" y="112" font-size="12.5" fill="#475569">netTask(唯一执行者)</text>
|
||||
<rect x="336.8" y="100" width="16" height="16" rx="4" fill="#ffffff" stroke="#0d9488" stroke-width="2"/>
|
||||
<text x="360.8" y="112" font-size="12.5" fill="#475569">CH395F 芯片</text>
|
||||
<rect x="460.5" y="100" width="16" height="16" rx="4" fill="#ffffff" stroke="#d97706" stroke-width="2"/>
|
||||
<text x="484.5" y="112" font-size="12.5" fill="#475569">主循环节拍 ~1ms</text>
|
||||
<rect x="40" y="140" width="440" height="600" rx="14" fill="#f8fafc" stroke="#64748b" stroke-opacity="0.28"/>
|
||||
<text x="58" y="168" font-size="15.0" font-weight="700" fill="#334155">调用方任务(消费者)</text>
|
||||
<rect x="64.0" y="196.0" width="392.0" height="74.0" rx="10" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.55" stroke-width="1.6"/>
|
||||
<text x="80.0" y="228.0" font-size="14.0" font-weight="700" fill="#0f172a">net_recv(fd, buf, len, timeout)</text>
|
||||
<text x="80.0" y="252.0" font-size="12.0" fill="#64748b">公开 API 与调用契约不变(签名 / 返回值 / 超时语义)</text>
|
||||
<polyline points="260.0,270.0 260.0,300.0" fill="none" stroke="#2563eb" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 260.0 300.0 L 254.5 289.0 L 265.5 289.0 z" fill="#2563eb"/>
|
||||
<rect x="64.0" y="300.0" width="392.0" height="74.0" rx="10" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.55" stroke-width="1.6"/>
|
||||
<text x="80.0" y="332.0" font-size="14.0" font-weight="700" fill="#0f172a">数据已在?</text>
|
||||
<text x="80.0" y="356.0" font-size="12.0" fill="#64748b">readable 标志由 net_poll 每轮刷新</text>
|
||||
<polyline points="260.0,374.0 260.0,404.0" fill="none" stroke="#2563eb" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 260.0 404.0 L 254.5 393.0 L 265.5 393.0 z" fill="#2563eb"/>
|
||||
<rect x="64.0" y="404.0" width="392.0" height="74.0" rx="10" fill="#f5f3ff" stroke="#7c3aed" stroke-opacity="0.55" stroke-width="1.6"/>
|
||||
<text x="80.0" y="436.0" font-size="14.0" font-weight="700" fill="#0f172a">登记 pending 到 s_net_pending[fd]</text>
|
||||
<text x="80.0" y="460.0" font-size="12.0" fill="#64748b">占用执行者到此结束(微秒级)· 每 fd 至多一个,不覆盖</text>
|
||||
<polyline points="260.0,478.0 260.0,508.0" fill="none" stroke="#7c3aed" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 260.0 508.0 L 254.5 497.0 L 265.5 497.0 z" fill="#7c3aed"/>
|
||||
<rect x="64.0" y="508.0" width="392.0" height="74.0" rx="10" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.55" stroke-width="1.6"/>
|
||||
<text x="80.0" y="540.0" font-size="14.0" font-weight="700" fill="#0f172a">调用方在自己上下文睡任务通知</text>
|
||||
<text x="80.0" y="564.0" font-size="12.0" fill="#64748b">与 LwIP 消费者线程模型同构 · 取消必通知,永不挂死</text>
|
||||
<text x="468.0" y="330.0" font-size="12" font-weight="700" fill="#16a34a">是</text>
|
||||
<text x="78.0" y="396.0" font-size="12" font-weight="700" fill="#dc2626">否,timeout > 0</text>
|
||||
<rect x="540.0" y="300.0" width="830.0" height="74.0" rx="10" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.55" stroke-width="1.6"/>
|
||||
<text x="556.0" y="332.0" font-size="14.0" font-weight="700" fill="#0f172a">立即 SPI 读出</text>
|
||||
<text x="556.0" y="356.0" font-size="12.0" fill="#64748b">GET_REMV_LEN / READ → 通知调用方(结果 = 字节数)</text>
|
||||
<polyline points="456.0,337.0 538.0,337.0" fill="none" stroke="#16a34a" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 538.0 337.0 L 527.0 342.5 L 527.0 331.5 z" fill="#16a34a"/>
|
||||
<polyline points="456.0,441.0 538.0,441.0" fill="none" stroke="#7c3aed" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 538.0 441.0 L 527.0 446.5 L 527.0 435.5 z" fill="#7c3aed"/>
|
||||
<text x="498.0" y="418.0" font-size="11.5" fill="#64748b" text-anchor="middle">登记即返回</text>
|
||||
<text x="498.0" y="432.0" font-size="11.5" fill="#64748b" text-anchor="middle">(微秒级)</text>
|
||||
<rect x="540" y="392" width="830" height="100" rx="12" fill="#fffbeb" stroke="#d97706" stroke-opacity="0.5" stroke-width="1.6"/>
|
||||
<text x="556" y="418" font-size="14.0" font-weight="700" fill="#b45309">netTask 主循环(~1ms 节拍)—— 挂起期间照常服务整个队列</text>
|
||||
<rect x="556.0" y="434.0" width="190.5" height="46" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.6"/>
|
||||
<text x="566.0" y="454.0" font-size="12.5" font-weight="700" fill="#0d9488">net_poll()</text>
|
||||
<text x="566.0" y="472.0" font-size="11" fill="#64748b">刷新 readable</text>
|
||||
<rect x="758.5" y="434.0" width="190.5" height="46" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.6"/>
|
||||
<text x="768.5" y="454.0" font-size="12.5" font-weight="700" fill="#d97706">net_pending_tick()</text>
|
||||
<text x="768.5" y="472.0" font-size="11" fill="#64748b">了结挂起</text>
|
||||
<rect x="961.0" y="434.0" width="190.5" height="46" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.6"/>
|
||||
<text x="971.0" y="454.0" font-size="12.5" font-weight="700" fill="#7c3aed">net_relisten_retry_tick()</text>
|
||||
<text x="971.0" y="472.0" font-size="11" fill="#64748b">重试对账</text>
|
||||
<rect x="1163.5" y="434.0" width="190.5" height="46" rx="8" fill="#ffffff" stroke="#64748b" stroke-opacity="0.6"/>
|
||||
<text x="1173.5" y="454.0" font-size="12.5" font-weight="700" fill="#64748b">排空消息队列</text>
|
||||
<text x="1173.5" y="472.0" font-size="11" fill="#64748b">服务所有消费者</text>
|
||||
<rect x="40" y="766" width="1360" height="234" rx="14" fill="#f8fafc" stroke="#64748b" stroke-opacity="0.28"/>
|
||||
<text x="58" y="794" font-size="15.0" font-weight="700" fill="#334155">net_pending_tick():五路完成 / 取消判定(每轮对每个挂起 fd 按序)</text>
|
||||
<polyline points="955.0,492.0 955.0,764.0" fill="none" stroke="#d97706" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 955.0 764.0 L 949.5 753.0 L 960.5 753.0 z" fill="#d97706"/>
|
||||
<text x="66" y="832.0" font-size="14" font-weight="700" fill="#0d9488">①</text>
|
||||
<text x="96" y="832.0" font-size="12.5" font-weight="700" fill="#0f172a">数据到达</text>
|
||||
<text x="270" y="832.0" font-size="12.5" fill="#475569">→ SPI 读出,通知调用方(结果 = 字节数)</text>
|
||||
<text x="66" y="859.0" font-size="14" font-weight="700" fill="#d97706">②</text>
|
||||
<text x="96" y="859.0" font-size="12.5" font-weight="700" fill="#0f172a">到点 deadline</text>
|
||||
<text x="270" y="859.0" font-size="12.5" fill="#475569">→ 通知 NET_ERR_TIMEDOUT(对外 -2 映射保持)</text>
|
||||
<text x="66" y="886.0" font-size="14" font-weight="700" fill="#7c3aed">③</text>
|
||||
<text x="96" y="886.0" font-size="12.5" font-weight="700" fill="#0f172a">EOF / DISCONNECT</text>
|
||||
<text x="270" y="886.0" font-size="12.5" fill="#475569">→ 按 0(EOF) 完成 + auto-relisten + 通知</text>
|
||||
<text x="66" y="913.0" font-size="14" font-weight="700" fill="#2563eb">④</text>
|
||||
<text x="96" y="913.0" font-size="12.5" font-weight="700" fill="#0f172a">同 fd 后续操作</text>
|
||||
<text x="270" y="913.0" font-size="12.5" fill="#475569">→ 先行结算该 fd 挂起,再执行新操作</text>
|
||||
<text x="66" y="940.0" font-size="14" font-weight="700" fill="#dc2626">⑤</text>
|
||||
<text x="96" y="940.0" font-size="12.5" font-weight="700" fill="#0f172a">网络重建(芯片复位)</text>
|
||||
<text x="270" y="940.0" font-size="12.5" fill="#475569">→ 取消 + 必须通知(否则调用方永久挂死)</text>
|
||||
<text x="96" y="969" font-size="12" font-weight="700" fill="#dc2626">看门狗:到点后 1s 仍未了结 = 完成逻辑缺位 → 强制了结 + DBG_ERROR</text>
|
||||
<rect x="1120" y="812" width="250" height="62" rx="10" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.55" stroke-width="1.6"/>
|
||||
<text x="1134" y="838" font-size="13.5" font-weight="700" fill="#2563eb">xTaskNotify</text>
|
||||
<text x="1134" y="858" font-size="11.5" fill="#64748b">唤醒调用方(结果 = 字节数 / 负错误码)</text>
|
||||
<polyline points="1080.0,843.0 1118.0,843.0" fill="none" stroke="#2563eb" stroke-width="2.2" stroke-linejoin="round"/>
|
||||
<path d="M 1118.0 843.0 L 1107.0 848.5 L 1107.0 837.5 z" fill="#2563eb"/>
|
||||
<polyline points="1245.0,874.0 1245.0,988.0 50.0,988.0 50.0,545.0 56.0,545.0" fill="none" stroke="#2563eb" stroke-width="2.2" stroke-linejoin="round" stroke-dasharray="7 5"/>
|
||||
<path d="M 56.0 545.0 L 45.0 550.5 L 45.0 539.5 z" fill="#2563eb"/>
|
||||
<text x="40" y="1006" font-size="12" fill="#64748b">关键性质:挂起期间 netTask 零额外占用(判据复用主循环既有 net_poll 的 readable,不引入额外 SPI 事务);公开 API 不变,既有调用方 / 测试 / 模拟器零改动。</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.2 KiB |
@@ -44,6 +44,10 @@
|
||||
| `gen_frame_svg.py` | `docs/images/RemoDispBus帧格式.svg` | `FIELDS` / `TITLE` / `SUB` / `FOOT` |
|
||||
| `gen_storage_stack_svg.py` | `docs/images/存储栈分层.svg` | `L_*` / `ROWS` / `TITLE` / `SUB` / `FOOT` |
|
||||
| `gen_partition_svg.py` | `docs/images/物理分区布局.svg` | `FIELDS` / `TITLE` / `SUB` / `FOOT`(MB 计量) |
|
||||
| `gen_net_pending_svg.py` | `docs/images/Net层阻塞模型.svg` | `CALLER_STEPS` / `LOOP_STEPS` / `PHASES` |
|
||||
| `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 布局 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+13
-30
@@ -36,36 +36,16 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
|
||||
|
||||
核心思想:把"等数据"从**执行者内的自旋**改为**执行者外的登记**。
|
||||
|
||||
```
|
||||
调用方任务 netTask(执行者) CH395F 芯片
|
||||
────────── ───────────────── ────────────
|
||||
net_recv(fd, buf, len, timeout)
|
||||
│
|
||||
├─ 数据已在?── 是 ──→ 立即 SPI 读出 ────────────────────→ GET_REMV_LEN/READ
|
||||
│ 通知调用方(结果 = 字节数)
|
||||
│
|
||||
└─ 否,且 timeout>0
|
||||
│
|
||||
├─ 登记 pending 到 s_net_pending[fd] ← 占用执行者到此结束(微秒级)
|
||||
└─ 在自己上下文睡任务通知 ⟵──────────────┐
|
||||
│
|
||||
┌────────────────┴─────────────────┐
|
||||
│ netTask 主循环(~1ms 节拍) │
|
||||
│ net_poll() ← 刷新 readable
|
||||
│ net_pending_tick() ← 了结挂起
|
||||
│ net_relisten_retry_tick() │
|
||||
│ 排空消息队列 │
|
||||
└──────────────────────────────────┘
|
||||
│
|
||||
┌──────────────────────────────────────────────────┘
|
||||
│ 五路完成/取消判定(每轮对每个挂起 fd)
|
||||
│ ① 数据到达 → SPI 读出,通知调用方(结果 = 字节数)
|
||||
│ ② 到点 deadline → 通知 NET_ERR_TIMEDOUT
|
||||
│ ③ EOF/DISCONNECT → 按 0(EOF) 完成 + auto-relisten + 通知
|
||||
│ ④ 同 fd 后续操作 → 先行结算该 fd 的挂起,再执行新操作
|
||||
│ ⑤ 网络重建 → 取消 + **必须通知**(否则调用方永久挂死)
|
||||
└─→ xTaskNotify → 唤醒调用方
|
||||
```
|
||||

|
||||
|
||||
上图四层结构:
|
||||
|
||||
| 区域 | 内容 |
|
||||
|---|---|
|
||||
| **左栏 · 调用方任务** | `net_recv` → 判"数据已在?" → **是**走快速路径;**否**(`timeout > 0`)登记 pending 后在自己上下文睡任务通知 |
|
||||
| **中栏 · netTask 主循环** | ~1ms 节拍:`net_poll`(刷新 readable)→ `net_pending_tick`(了结挂起)→ `net_relisten_retry_tick` → 排空消息队列 |
|
||||
| **下带 · 五路判定** | 每轮对每个挂起 fd 按序判定:数据到达 / 到点 / EOF / 同 fd 后续操作 / 网络重建;含 1s 看门狗 |
|
||||
| **回环** | 判定完成经 `xTaskNotify` 唤醒调用方(结果 = 字节数 / 负错误码) |
|
||||
|
||||
**关键性质**:
|
||||
|
||||
@@ -75,6 +55,9 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
|
||||
- 公开 API 与调用契约**不变**——`net_recv` / `net_recv_to` 的签名、返回值、
|
||||
超时语义均与改造前一致,既有调用方、测试与模拟器零改动。
|
||||
|
||||
> 图形由 `tools/arch_diagram/gen_net_pending_svg.py` 生成(改流程后重跑;见
|
||||
> `docs/架构设计/图形绘制流程.md`)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据结构
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""生成 docs/images/Net层阻塞模型.svg —— pending 请求模型架构图(纯标准库)。
|
||||
|
||||
结构:左右两栏 + 主循环横带 + 五路判定带
|
||||
左 = 调用方任务(net_recv → 数据已在? → 登记挂起 / 睡任务通知)
|
||||
右 = netTask 立即 SPI 读出(快速路径)+ CH395F 芯片
|
||||
中 = netTask 主循环(~1ms 节拍:net_poll / net_pending_tick / ...)
|
||||
下 = 五路完成/取消判定 → xTaskNotify 唤醒调用方
|
||||
|
||||
目的:把"等数据"从执行者内的自旋改为执行者外的登记——登记后调用方在自己上下文
|
||||
睡眠,netTask 零占用地继续服务整个队列。
|
||||
|
||||
用法(仓库根目录,任意 python3):
|
||||
python tools/arch_diagram/gen_net_pending_svg.py
|
||||
输出:
|
||||
docs/images/Net层阻塞模型.svg
|
||||
改流程时:编辑本文件顶部的 CALLER_STEPS / LOOP_STEPS / PHASES 数据后重跑。
|
||||
"""
|
||||
import html
|
||||
import math
|
||||
import os
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
OUT = os.path.join(ROOT, "docs", "images", "Net层阻塞模型.svg")
|
||||
|
||||
# ----------------------------- 画布与样式 -----------------------------
|
||||
W = 1440
|
||||
H = 1030
|
||||
PAD = 40
|
||||
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
|
||||
|
||||
TITLE_FONT = 21
|
||||
SUB_FONT = 13
|
||||
PANEL_TITLE_FONT = 15
|
||||
STEP_TITLE_FONT = 14
|
||||
STEP_NOTE_FONT = 12
|
||||
LANE_FONT = 12.5
|
||||
FOOT_FONT = 12
|
||||
|
||||
C_CALLER, BG_CALLER = "#2563eb", "#eff6ff"
|
||||
C_ACTOR, BG_ACTOR = "#7c3aed", "#f5f3ff"
|
||||
C_CHIP, BG_CHIP = "#0d9488", "#f0fdfa"
|
||||
C_TICK, BG_TICK = "#d97706", "#fffbeb"
|
||||
C_CANCEL = "#dc2626"
|
||||
C_OK = "#16a34a"
|
||||
|
||||
ARROW_W = 2.2
|
||||
|
||||
|
||||
def esc(s):
|
||||
return html.escape(s, quote=True)
|
||||
|
||||
|
||||
def text_w(s, fs):
|
||||
"""粗略估算文本像素宽:CJK/全角 ≈ 1.0em,其余 ≈ 0.58em。"""
|
||||
w = 0.0
|
||||
for ch in s:
|
||||
w += fs * (1.0 if ord(ch) >= 0x2E80 else 0.58)
|
||||
return w
|
||||
|
||||
|
||||
# ----------------------------- 数据(改这里) -----------------------------
|
||||
# 左栏:调用方流程(自上而下)
|
||||
CALLER_STEPS = [
|
||||
("net_recv(fd, buf, len, timeout)", "公开 API 与调用契约不变(签名 / 返回值 / 超时语义)"),
|
||||
("数据已在?", "readable 标志由 net_poll 每轮刷新"),
|
||||
("登记 pending 到 s_net_pending[fd]", "占用执行者到此结束(微秒级)· 每 fd 至多一个,不覆盖"),
|
||||
("调用方在自己上下文睡任务通知", "与 LwIP 消费者线程模型同构 · 取消必通知,永不挂死"),
|
||||
]
|
||||
|
||||
FAST_PATH = ("立即 SPI 读出",
|
||||
"GET_REMV_LEN / READ → 通知调用方(结果 = 字节数)")
|
||||
|
||||
LOOP_STEPS = [
|
||||
("net_poll()", "刷新 readable", C_CHIP),
|
||||
("net_pending_tick()", "了结挂起", C_TICK),
|
||||
("net_relisten_retry_tick()", "重试对账", C_ACTOR),
|
||||
("排空消息队列", "服务所有消费者", "#64748b"),
|
||||
]
|
||||
|
||||
PHASES = [
|
||||
("①", "数据到达", "SPI 读出,通知调用方(结果 = 字节数)", C_CHIP),
|
||||
("②", "到点 deadline", "通知 NET_ERR_TIMEDOUT(对外 -2 映射保持)", C_TICK),
|
||||
("③", "EOF / DISCONNECT", "按 0(EOF) 完成 + auto-relisten + 通知", C_ACTOR),
|
||||
("④", "同 fd 后续操作", "先行结算该 fd 挂起,再执行新操作", C_CALLER),
|
||||
("⑤", "网络重建(芯片复位)", "取消 + 必须通知(否则调用方永久挂死)", C_CANCEL),
|
||||
]
|
||||
|
||||
WATCHDOG = "看门狗:到点后 1s 仍未了结 = 完成逻辑缺位 → 强制了结 + DBG_ERROR"
|
||||
FOOT = ("关键性质:挂起期间 netTask 零额外占用(判据复用主循环既有 net_poll 的 readable,"
|
||||
"不引入额外 SPI 事务);公开 API 不变,既有调用方 / 测试 / 模拟器零改动。")
|
||||
|
||||
|
||||
# ----------------------------- 生成 -----------------------------
|
||||
def build():
|
||||
out = []
|
||||
add = out.append
|
||||
|
||||
def poly(points, color, dash=False, head=True, width=ARROW_W):
|
||||
pts = " ".join("%.1f,%.1f" % p for p in points)
|
||||
d = ' stroke-dasharray="7 5"' if dash else ''
|
||||
add('<polyline points="%s" fill="none" stroke="%s" stroke-width="%.1f" '
|
||||
'stroke-linejoin="round"%s/>' % (pts, color, width, d))
|
||||
if head:
|
||||
(x1, y1), (x2, y2) = points[-2], points[-1]
|
||||
ang = math.atan2(y2 - y1, x2 - x1)
|
||||
hl, hw = 11.0, 5.5
|
||||
bx, by = x2 - hl * math.cos(ang), y2 - hl * math.sin(ang)
|
||||
px, py = -math.sin(ang) * hw, math.cos(ang) * hw
|
||||
add('<path d="M %.1f %.1f L %.1f %.1f L %.1f %.1f z" fill="%s"/>'
|
||||
% (x2, y2, bx + px, by + py, bx - px, by - py, color))
|
||||
|
||||
def line(x1, y1, x2, y2, color, dash=False, head=False, width=ARROW_W):
|
||||
poly([(x1, y1), (x2, y2)], color, dash=dash, head=head, width=width)
|
||||
|
||||
def box(x, y, w, h, color, bg, title, note=None, tfs=STEP_TITLE_FONT):
|
||||
add('<rect x="%.1f" y="%.1f" width="%.1f" height="%.1f" rx="10" fill="%s" '
|
||||
'stroke="%s" stroke-opacity="0.55" stroke-width="1.6"/>'
|
||||
% (x, y, w, h, bg, color))
|
||||
add('<text x="%.1f" y="%.1f" font-size="%.1f" font-weight="700" fill="#0f172a">%s</text>'
|
||||
% (x + 16, y + 32, tfs, esc(title)))
|
||||
if note:
|
||||
add('<text x="%.1f" y="%.1f" font-size="%.1f" fill="#64748b">%s</text>'
|
||||
% (x + 16, y + 56, STEP_NOTE_FONT, esc(note)))
|
||||
|
||||
# ---- 画布 ----
|
||||
add('<rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, H))
|
||||
|
||||
# ---- 标题 ----
|
||||
add('<text x="%d" y="44" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
|
||||
% (PAD, TITLE_FONT, esc("Net 层阻塞模型 · pending 请求架构")))
|
||||
add('<text x="%d" y="70" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (PAD, SUB_FONT,
|
||||
esc("把「等数据」从执行者内的自旋,改为执行者外的登记 —— 调用方在自己上下文睡眠,"
|
||||
"netTask 零占用地服务整个队列")))
|
||||
|
||||
# ---- 图例 ----
|
||||
lx = PAD
|
||||
for color, label in [(C_CALLER, "调用方任务"),
|
||||
(C_ACTOR, "netTask(唯一执行者)"),
|
||||
(C_CHIP, "CH395F 芯片"),
|
||||
(C_TICK, "主循环节拍 ~1ms")]:
|
||||
add('<rect x="%.1f" y="100" width="16" height="16" rx="4" fill="#ffffff" '
|
||||
'stroke="%s" stroke-width="2"/>' % (lx, color))
|
||||
add('<text x="%.1f" y="112" font-size="%.1f" fill="#475569">%s</text>'
|
||||
% (lx + 24, LANE_FONT, esc(label)))
|
||||
lx += 30 + text_w(label, LANE_FONT) + 18
|
||||
|
||||
# ---- 布局参数 ----
|
||||
LANE_Y, LANE_H = 140, 600
|
||||
|
||||
LB_X, LB_W = PAD, 440
|
||||
MB_X, MB_W = LB_X + LB_W + 30, W - PAD - (LB_X + LB_W + 30)
|
||||
|
||||
STEP_H = 74
|
||||
STEP_GAP = 30
|
||||
sx, sw = LB_X + 24, LB_W - 48
|
||||
|
||||
# 左栏背景
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="14" fill="#f8fafc" '
|
||||
'stroke="#64748b" stroke-opacity="0.28"/>' % (LB_X, LANE_Y, LB_W, LANE_H))
|
||||
add('<text x="%d" y="%d" font-size="%.1f" font-weight="700" fill="#334155">%s</text>'
|
||||
% (LB_X + 18, LANE_Y + 28, PANEL_TITLE_FONT, esc("调用方任务(消费者)")))
|
||||
|
||||
# 左栏四个步骤
|
||||
step_y = []
|
||||
y = LANE_Y + 56
|
||||
for i, (title, note) in enumerate(CALLER_STEPS):
|
||||
color = C_ACTOR if i == 2 else C_CALLER
|
||||
bg = BG_ACTOR if i == 2 else BG_CALLER
|
||||
box(sx, y, sw, STEP_H, color, bg, title, note)
|
||||
step_y.append(y)
|
||||
if i < len(CALLER_STEPS) - 1:
|
||||
line(sx + sw / 2, y + STEP_H, sx + sw / 2, y + STEP_H + STEP_GAP,
|
||||
color, head=True)
|
||||
y += STEP_H + STEP_GAP
|
||||
|
||||
# 分支标签
|
||||
add('<text x="%.1f" y="%.1f" font-size="12" font-weight="700" fill="%s">%s</text>'
|
||||
% (sx + sw + 12, step_y[1] + 30, C_OK, esc("是")))
|
||||
add('<text x="%.1f" y="%.1f" font-size="12" font-weight="700" fill="%s">%s</text>'
|
||||
% (sx + 14, step_y[1] + STEP_H + 22, C_CANCEL, esc("否,timeout > 0")))
|
||||
|
||||
# ---- 快速路径(中栏右上,与"数据已在?"对齐) ----
|
||||
fp_x = MB_X + 30
|
||||
fp_w = MB_W - 60
|
||||
fp_y = step_y[1]
|
||||
box(fp_x, fp_y, fp_w, STEP_H, C_CHIP, BG_CHIP, FAST_PATH[0], FAST_PATH[1])
|
||||
line(sx + sw, fp_y + STEP_H / 2, fp_x - 2, fp_y + STEP_H / 2, C_OK, head=True)
|
||||
|
||||
# ---- 登记 → 主循环(箭头说明) ----
|
||||
reg_y = step_y[2]
|
||||
lw_x, lw_w = MB_X + 30, MB_W - 60
|
||||
lw_h = 100
|
||||
lw_y = reg_y - 12
|
||||
line(sx + sw, reg_y + STEP_H / 2, lw_x - 2, reg_y + STEP_H / 2, C_ACTOR, head=True)
|
||||
add('<text x="%.1f" y="%.1f" font-size="11.5" fill="#64748b" text-anchor="middle">%s</text>'
|
||||
% ((sx + sw + lw_x) / 2, reg_y + 14, esc("登记即返回")))
|
||||
add('<text x="%.1f" y="%.1f" font-size="11.5" fill="#64748b" text-anchor="middle">%s</text>'
|
||||
% ((sx + sw + lw_x) / 2, reg_y + 28, esc("(微秒级)")))
|
||||
|
||||
# ---- 主循环横带 ----
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="12" fill="%s" '
|
||||
'stroke="%s" stroke-opacity="0.5" stroke-width="1.6"/>'
|
||||
% (lw_x, lw_y, lw_w, lw_h, BG_TICK, C_TICK))
|
||||
add('<text x="%d" y="%d" font-size="%.1f" font-weight="700" fill="#b45309">%s</text>'
|
||||
% (lw_x + 16, lw_y + 26, STEP_TITLE_FONT,
|
||||
esc("netTask 主循环(~1ms 节拍)—— 挂起期间照常服务整个队列")))
|
||||
|
||||
n = len(LOOP_STEPS)
|
||||
gap = 12
|
||||
cw = (lw_w - 32 - gap * (n - 1)) / n
|
||||
for i, (name, note, color) in enumerate(LOOP_STEPS):
|
||||
cx = lw_x + 16 + i * (cw + gap)
|
||||
cy = lw_y + 42
|
||||
add('<rect x="%.1f" y="%.1f" width="%.1f" height="46" rx="8" fill="#ffffff" '
|
||||
'stroke="%s" stroke-opacity="0.6"/>' % (cx, cy, cw, color))
|
||||
add('<text x="%.1f" y="%.1f" font-size="12.5" font-weight="700" fill="%s">%s</text>'
|
||||
% (cx + 10, cy + 20, color, esc(name)))
|
||||
add('<text x="%.1f" y="%.1f" font-size="11" fill="#64748b">%s</text>'
|
||||
% (cx + 10, cy + 38, esc(note)))
|
||||
|
||||
# ---- 五路判定带 ----
|
||||
ph_y = LANE_Y + LANE_H + 26
|
||||
ph_h = 234
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="14" fill="#f8fafc" '
|
||||
'stroke="#64748b" stroke-opacity="0.28"/>'
|
||||
% (PAD, ph_y, W - 2 * PAD, ph_h))
|
||||
add('<text x="%d" y="%d" font-size="%.1f" font-weight="700" fill="#334155">%s</text>'
|
||||
% (PAD + 18, ph_y + 28, PANEL_TITLE_FONT,
|
||||
esc("net_pending_tick():五路完成 / 取消判定(每轮对每个挂起 fd 按序)")))
|
||||
|
||||
# 主循环 → 五路
|
||||
line(lw_x + lw_w / 2, lw_y + lw_h, lw_x + lw_w / 2, ph_y - 2, C_TICK, head=True)
|
||||
|
||||
rows_y = ph_y + 50
|
||||
rh = 27
|
||||
for i, (no, cond, act, color) in enumerate(PHASES):
|
||||
ry = rows_y + i * rh
|
||||
add('<text x="%d" y="%.1f" font-size="14" font-weight="700" fill="%s">%s</text>'
|
||||
% (PAD + 26, ry + 16, color, esc(no)))
|
||||
add('<text x="%d" y="%.1f" font-size="12.5" font-weight="700" fill="#0f172a">%s</text>'
|
||||
% (PAD + 56, ry + 16, esc(cond)))
|
||||
add('<text x="%d" y="%.1f" font-size="12.5" fill="#475569">%s</text>'
|
||||
% (PAD + 230, ry + 16, esc("→ " + act)))
|
||||
|
||||
wd_y = rows_y + len(PHASES) * rh + 10
|
||||
add('<text x="%d" y="%d" font-size="12" font-weight="700" fill="%s">%s</text>'
|
||||
% (PAD + 56, wd_y + 8, C_CANCEL, esc(WATCHDOG)))
|
||||
|
||||
# xTaskNotify 回环 → 调用方睡眠盒
|
||||
nb_w, nb_h = 250, 62
|
||||
nb_x = W - PAD - 30 - nb_w
|
||||
nb_y = ph_y + 46
|
||||
add('<rect x="%d" y="%d" width="%d" height="%d" rx="10" fill="%s" '
|
||||
'stroke="%s" stroke-opacity="0.55" stroke-width="1.6"/>'
|
||||
% (nb_x, nb_y, nb_w, nb_h, BG_CALLER, C_CALLER))
|
||||
add('<text x="%d" y="%d" font-size="13.5" font-weight="700" fill="%s">%s</text>'
|
||||
% (nb_x + 14, nb_y + 26, C_CALLER, esc("xTaskNotify")))
|
||||
add('<text x="%d" y="%d" font-size="11.5" fill="#64748b">%s</text>'
|
||||
% (nb_x + 14, nb_y + 46, esc("唤醒调用方(结果 = 字节数 / 负错误码)")))
|
||||
|
||||
# 五路 → notify(左向短箭头)
|
||||
line(W - PAD - 30 - nb_w - 40, nb_y + nb_h / 2, nb_x - 2, nb_y + nb_h / 2,
|
||||
C_CALLER, head=True)
|
||||
|
||||
# notify → 调用方睡眠盒(虚线长回环:沿判定带下缘走左外侧)
|
||||
slp_y = step_y[3]
|
||||
back_x = PAD + 10
|
||||
bus_y = ph_y + ph_h - 12
|
||||
poly([(nb_x + nb_w / 2, nb_y + nb_h),
|
||||
(nb_x + nb_w / 2, bus_y),
|
||||
(back_x, bus_y),
|
||||
(back_x, slp_y + STEP_H / 2),
|
||||
(sx - 8, slp_y + STEP_H / 2)],
|
||||
C_CALLER, dash=True, head=True)
|
||||
|
||||
# ---- 页脚 ----
|
||||
add('<text x="%d" y="%d" font-size="%d" fill="#64748b">%s</text>'
|
||||
% (PAD, H - 24, FOOT_FONT, esc(FOOT)))
|
||||
|
||||
svg = ['<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" '
|
||||
'viewBox="0 0 %d %d" font-family="%s">' % (W, H, W, H, FONT)]
|
||||
svg.extend(out)
|
||||
svg.append('</svg>')
|
||||
return "\n".join(svg)
|
||||
|
||||
|
||||
def main():
|
||||
svg = build()
|
||||
os.makedirs(os.path.dirname(OUT), exist_ok=True)
|
||||
with open(OUT, "w", encoding="utf-8") as f:
|
||||
f.write(svg)
|
||||
print("已生成: %s (%d bytes)" % (OUT, len(svg.encode("utf-8"))))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user