From 0ddf8c9aca12c167735e1b6df12fc20710a8c194 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Tue, 29 Sep 2026 06:00:48 +0800 Subject: [PATCH] =?UTF-8?q?Net=20=E5=B1=82=E9=98=BB=E5=A1=9E=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E6=9E=B6=E6=9E=84=E8=AE=BE=E8=AE=A1=20=C2=A72=20?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E6=80=BB=E8=A7=88=E6=94=B9=E7=94=A8=20SVG=20?= =?UTF-8?q?=E7=9F=A2=E9=87=8F=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/images/Net层阻塞模型.svg | 86 +++++++ docs/架构设计/图形绘制流程.md | 4 + docs/网络/Net层阻塞模型架构设计.md | 43 +--- tools/arch_diagram/gen_net_pending_svg.py | 299 ++++++++++++++++++++++ 4 files changed, 402 insertions(+), 30 deletions(-) create mode 100644 docs/images/Net层阻塞模型.svg create mode 100644 tools/arch_diagram/gen_net_pending_svg.py diff --git a/docs/images/Net层阻塞模型.svg b/docs/images/Net层阻塞模型.svg new file mode 100644 index 00000000..fc2df1d5 --- /dev/null +++ b/docs/images/Net层阻塞模型.svg @@ -0,0 +1,86 @@ + + +Net 层阻塞模型 · pending 请求架构 +把「等数据」从执行者内的自旋,改为执行者外的登记 —— 调用方在自己上下文睡眠,netTask 零占用地服务整个队列 + +调用方任务 + +netTask(唯一执行者) + +CH395F 芯片 + +主循环节拍 ~1ms + +调用方任务(消费者) + +net_recv(fd, buf, len, timeout) +公开 API 与调用契约不变(签名 / 返回值 / 超时语义) + + + +数据已在? +readable 标志由 net_poll 每轮刷新 + + + +登记 pending 到 s_net_pending[fd] +占用执行者到此结束(微秒级)· 每 fd 至多一个,不覆盖 + + + +调用方在自己上下文睡任务通知 +与 LwIP 消费者线程模型同构 · 取消必通知,永不挂死 +是 +否,timeout > 0 + +立即 SPI 读出 +GET_REMV_LEN / READ → 通知调用方(结果 = 字节数) + + + + +登记即返回 +(微秒级) + +netTask 主循环(~1ms 节拍)—— 挂起期间照常服务整个队列 + +net_poll() +刷新 readable + +net_pending_tick() +了结挂起 + +net_relisten_retry_tick() +重试对账 + +排空消息队列 +服务所有消费者 + +net_pending_tick():五路完成 / 取消判定(每轮对每个挂起 fd 按序) + + +① +数据到达 +→ SPI 读出,通知调用方(结果 = 字节数) +② +到点 deadline +→ 通知 NET_ERR_TIMEDOUT(对外 -2 映射保持) +③ +EOF / DISCONNECT +→ 按 0(EOF) 完成 + auto-relisten + 通知 +④ +同 fd 后续操作 +→ 先行结算该 fd 挂起,再执行新操作 +⑤ +网络重建(芯片复位) +→ 取消 + 必须通知(否则调用方永久挂死) +看门狗:到点后 1s 仍未了结 = 完成逻辑缺位 → 强制了结 + DBG_ERROR + +xTaskNotify +唤醒调用方(结果 = 字节数 / 负错误码) + + + + +关键性质:挂起期间 netTask 零额外占用(判据复用主循环既有 net_poll 的 readable,不引入额外 SPI 事务);公开 API 不变,既有调用方 / 测试 / 模拟器零改动。 + diff --git a/docs/架构设计/图形绘制流程.md b/docs/架构设计/图形绘制流程.md index e7f51ce7..2bdebdb7 100644 --- a/docs/架构设计/图形绘制流程.md +++ b/docs/架构设计/图形绘制流程.md @@ -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 布局 | --- diff --git a/docs/网络/Net层阻塞模型架构设计.md b/docs/网络/Net层阻塞模型架构设计.md index b251488b..21b3f01c 100644 --- a/docs/网络/Net层阻塞模型架构设计.md +++ b/docs/网络/Net层阻塞模型架构设计.md @@ -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 层阻塞模型 · pending 请求架构](../images/Net层阻塞模型.svg) + +上图四层结构: + +| 区域 | 内容 | +|---|---| +| **左栏 · 调用方任务** | `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. 数据结构 diff --git a/tools/arch_diagram/gen_net_pending_svg.py b/tools/arch_diagram/gen_net_pending_svg.py new file mode 100644 index 00000000..6bff92bf --- /dev/null +++ b/tools/arch_diagram/gen_net_pending_svg.py @@ -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('' % (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('' + % (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('' + % (x, y, w, h, bg, color)) + add('%s' + % (x + 16, y + 32, tfs, esc(title))) + if note: + add('%s' + % (x + 16, y + 56, STEP_NOTE_FONT, esc(note))) + + # ---- 画布 ---- + add('' % (W, H)) + + # ---- 标题 ---- + add('%s' + % (PAD, TITLE_FONT, esc("Net 层阻塞模型 · pending 请求架构"))) + add('%s' + % (PAD, SUB_FONT, + esc("把「等数据」从执行者内的自旋,改为执行者外的登记 —— 调用方在自己上下文睡眠," + "netTask 零占用地服务整个队列"))) + + # ---- 图例 ---- + lx = PAD + for color, label in [(C_CALLER, "调用方任务"), + (C_ACTOR, "netTask(唯一执行者)"), + (C_CHIP, "CH395F 芯片"), + (C_TICK, "主循环节拍 ~1ms")]: + add('' % (lx, color)) + add('%s' + % (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('' % (LB_X, LANE_Y, LB_W, LANE_H)) + add('%s' + % (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('%s' + % (sx + sw + 12, step_y[1] + 30, C_OK, esc("是"))) + add('%s' + % (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('%s' + % ((sx + sw + lw_x) / 2, reg_y + 14, esc("登记即返回"))) + add('%s' + % ((sx + sw + lw_x) / 2, reg_y + 28, esc("(微秒级)"))) + + # ---- 主循环横带 ---- + add('' + % (lw_x, lw_y, lw_w, lw_h, BG_TICK, C_TICK)) + add('%s' + % (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('' % (cx, cy, cw, color)) + add('%s' + % (cx + 10, cy + 20, color, esc(name))) + add('%s' + % (cx + 10, cy + 38, esc(note))) + + # ---- 五路判定带 ---- + ph_y = LANE_Y + LANE_H + 26 + ph_h = 234 + add('' + % (PAD, ph_y, W - 2 * PAD, ph_h)) + add('%s' + % (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('%s' + % (PAD + 26, ry + 16, color, esc(no))) + add('%s' + % (PAD + 56, ry + 16, esc(cond))) + add('%s' + % (PAD + 230, ry + 16, esc("→ " + act))) + + wd_y = rows_y + len(PHASES) * rh + 10 + add('%s' + % (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('' + % (nb_x, nb_y, nb_w, nb_h, BG_CALLER, C_CALLER)) + add('%s' + % (nb_x + 14, nb_y + 26, C_CALLER, esc("xTaskNotify"))) + add('%s' + % (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('%s' + % (PAD, H - 24, FOOT_FONT, esc(FOOT))) + + svg = ['' % (W, H, W, H, FONT)] + svg.extend(out) + svg.append('') + 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()