Files
STM32F4-Base/tools/arch_diagram/gen_net_pending_svg.py
T

300 lines
13 KiB
Python
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.
#!/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()