refactor(net): 挂起 recv 模型拆出 net_pending.c/h + 留痕探针编译门控 + E15 归因修正
- E15 归因修正:幽灵状态 0x04+旧端口 = 芯片如实报告拆除中的旧连接 (文档化行为,非芯片故障;bit3=1 为我方设置(ver>=0x46)、 RFC 9293 §3.3.2 Fig5 拆除顺序、WCH 官方博客方案 = close 完成后重开监听) - 术语统一:全库"了结"→"结束"(约 45 处);阻塞模型 §4 首次出现处定义"五路" - 注释清理:删孤儿函数头注释、考古叙事改现在时、去不可检索内部编码"L3" (33 处 6 文件)、banner 去重(唯一理由移到分支现场) - 挂起超时留痕探针:编译期开关 NET_PENDING_TRIPWIRE(默认 0 = 编译剔除, 省约 150B Flash;排查调用方挂死时置 1,只留痕绝不强制结束) - 拆模块:net_pending.c/h(363/94 行)承载挂起 recv 模型, net_socket.c 4059→3661 行;tick 收控制块表参数、settle_close 双参显式传; net_pending_active 声明移配对头(S17 2.2);仅注册 uvprojx + check_registration MCU-only 白名单(同 net_listener.c 机制, 模拟器走 net_socket_sim.c 按外部语义模拟) - 文档:阻塞模型新增全过程小节(单主干流程图)+ §9 排查辅助; NET README / 函数调用关系 / 自愈架构 / BSD 指南同步;引用关系图重生成 - 台架回归:启动闭环 PASS([ERR]/Fault 零命中、回读一致)、 :7003 重连探针 10/10、FTP 50 连接 STOR 50/50、回归窗口串口日志无异常 - RAM:ER_IROM1 230004B(探针门控后约 -150B),台账已回填
This commit is contained in:
+1
-1
@@ -272,7 +272,7 @@ docs/
|
||||
|
||||
| 日期 | 变更 | 说明 |
|
||||
|---|---|---|
|
||||
| 2026-10-14 | E14"丢中断"复核与归因更正 | 48 轮直读 SINT 矩阵复核历史"丢中断"说法:**芯片侧事件丢失 = 0**("丢失"= drain 读清消费 3/48 + 门控竞态 2/48 + 主机同步 1/48,幽灵态 0/48 未复现);根除性修复 = listen/relisten 路径排事件挪至 LISTEN 命令**之前**(吞新会话 CONNECT 的生产侧自伤窗);全局读收口(全局寄存器唯一读者=`net_poll`,I7 护栏);全库 17 文件约 40 处"丢中断"归因更正为"事件未收到/读清消费竞态",`docs/网络/网络自愈架构设计.md` §5.2 新增 E14 锚点;台架保留复核探针(drain-int/SINT 补读×3/超时归类) |
|
||||
| 2026-10-14 | E14"丢中断"复核与归因更正 | 48 轮直读 SINT 矩阵复核历史"丢中断"说法:**芯片侧事件丢失 = 0**("丢失"= drain 读清消费 3/48 + 门控竞态 2/48 + 主机同步 1/48,幽灵态 0/48 未复现);根除性修复 = listen/relisten 路径排事件挪至 LISTEN 命令**之前**(吞新会话 CONNECT 的生产侧自伤窗);全局读收口(全局寄存器唯一读者=`net_poll`,I7 护栏);全库 17 文件约 40 处"丢中断"归因更正为"事件未收到/读清消费竞态",`docs/网络/网络自愈架构设计.md` §5.2 新增 E14 锚点;台架保留复核探针(drain-int/SINT 补读×3/超时归类);同日 E15 幽灵态归因复核(官网+手册实锤:0x04+旧端口 = 芯片**如实报告**旧连接拆链进行中——bit3=1 保留现场为自家配置、拆链窗与 §5.17 重传默认值吻合、WCH 官方博客确认断连即重连被拒且官方方案 = 关闭完成后重开,即 settle 等待) |
|
||||
| 2026-10-13 | 网络自愈架构文档瘦身与改名 | 合并文档 `网络/N34-自愈审计.md`(2062 行审计全记录)瘦身为**终版设计描述**(281 行:不变量 I1~I8 / 架构与机制详解 / 参数与 E 锚点速查 / 红线 / 延后项),并经 `rename_ref.py` 改名 [`网络/网络自愈架构设计.md`](网络/网络自愈架构设计.md)(全库 16 文件 30 处引用同步);审计过程(T1~T4、E0~E13)归档 git 历史,考古命令见该文档「引用与归档约定」 |
|
||||
| 2026-10-13 | 网络自愈审计归档 | 原 `docs/网络/N34-自愈审计/` 目录 7 份文档(根因总览 / net 层盘点 / 驱动层审计 / 主流对照 / 汇总裁决 / 实施方案 / 实验方案)**合并为单文档 [`网络/网络自愈架构设计.md`](网络/网络自愈架构设计.md)**,原目录删除;全库引用同步(`docs/` 4 篇 + 驱动/网络代码注释 + 配图生成器与 SVG)。本索引 §5.1 补登记 |
|
||||
| 2026-10-12 | 新增环境与部署 | 新建 `docs/环境与部署/并行任务工作树方案.md`(**多任务并行开发机制规格**:`git worktree` 隔离、目录与共享/独占分配、`task-worktree` / `@task` skill 设计、单写者文档与合并残留审计、台架锁;**已落地并端到端实测**);本索引 §9.2 补登记 |
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<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>
|
||||
<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_listener_retry_tick()</text>
|
||||
<text x="971.0" y="472.0" font-size="11" fill="#64748b">重试对账</text>
|
||||
@@ -74,7 +74,7 @@
|
||||
<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>
|
||||
<text x="96" y="969" font-size="12" font-weight="700" fill="#dc2626">挂起超时留痕探针(编译期选项,默认关):挂起超阈值仍未结束 → 只 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>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
@@ -77,6 +77,8 @@ digraph refgraph_full {
|
||||
"Drivers/BSP/NET/net_config.h" [fillcolor="#dbeafe", color="#1d4ed8"];
|
||||
"Drivers/BSP/NET/net_listener.c" [fillcolor="#dbeafe", color="#1d4ed8"];
|
||||
"Drivers/BSP/NET/net_listener.h" [fillcolor="#dbeafe", color="#1d4ed8"];
|
||||
"Drivers/BSP/NET/net_pending.c" [fillcolor="#dbeafe", color="#1d4ed8"];
|
||||
"Drivers/BSP/NET/net_pending.h" [fillcolor="#dbeafe", color="#1d4ed8"];
|
||||
"Drivers/BSP/NET/net_select.c" [fillcolor="#dbeafe", color="#1d4ed8"];
|
||||
"Drivers/BSP/NET/net_select.h" [fillcolor="#dbeafe", color="#1d4ed8"];
|
||||
"Drivers/BSP/NET/net_socket.c" [fillcolor="#dbeafe", color="#1d4ed8"];
|
||||
@@ -505,6 +507,20 @@ digraph refgraph_full {
|
||||
"Drivers/BSP/NET/net_listener.c" -> "test/board/test_config.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_listener.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
|
||||
"Drivers/BSP/NET/net_listener.h" -> "Drivers/BSP/NET/net_types.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_pending.c" -> "Drivers/BSP/CH395F/ch395f.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_pending.c" -> "Drivers/BSP/NET/net_listener.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_pending.c" -> "Drivers/BSP/NET/net_pending.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_pending.c" -> "Drivers/BSP/NET/net_socket.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_pending.c" -> "Drivers/BSP/NET/net_types.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_pending.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_pending.c" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_pending.c" -> "Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h" [color="#94a3b8", penwidth=0.7];
|
||||
"Drivers/BSP/NET/net_pending.c" -> "Middlewares/Third_Party/FreeRTOS/Source/include/task.h" [color="#94a3b8", penwidth=0.7];
|
||||
"Drivers/BSP/NET/net_pending.c" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
|
||||
"Drivers/BSP/NET/net_pending.h" -> "Drivers/BSP/NET/net_types.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_pending.h" -> "Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h" [color="#94a3b8", penwidth=0.7];
|
||||
"Drivers/BSP/NET/net_pending.h" -> "Middlewares/Third_Party/FreeRTOS/Source/include/task.h" [color="#94a3b8", penwidth=0.7];
|
||||
"Drivers/BSP/NET/net_pending.h" -> "外部/系统头文件" [color="#f59e0b", penwidth=0.6, style=dashed];
|
||||
"Drivers/BSP/NET/net_select.c" -> "Drivers/BSP/CH395F/ch395f.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_select.c" -> "Drivers/BSP/NET/net_select.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_select.c" -> "Drivers/BSP/NET/net_socket.h" [color="#2563eb", penwidth=1.0];
|
||||
@@ -517,6 +533,7 @@ digraph refgraph_full {
|
||||
"Drivers/BSP/NET/net_select.h" -> "Drivers/BSP/NET/net_types.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_socket.c" -> "Drivers/BSP/CH395F/ch395f.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_socket.c" -> "Drivers/BSP/NET/net_listener.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_socket.c" -> "Drivers/BSP/NET/net_pending.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_socket.c" -> "Drivers/BSP/NET/net_socket.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_socket.c" -> "Drivers/BSP/dbg_log.h" [color="#2563eb", penwidth=1.0];
|
||||
"Drivers/BSP/NET/net_socket.c" -> "Inc/main.h" [color="#2563eb", penwidth=1.0];
|
||||
|
||||
+109
-5
@@ -341,6 +341,14 @@
|
||||
"path": "Drivers/BSP/NET/net_listener.h",
|
||||
"class": "fp"
|
||||
},
|
||||
{
|
||||
"path": "Drivers/BSP/NET/net_pending.c",
|
||||
"class": "fp"
|
||||
},
|
||||
{
|
||||
"path": "Drivers/BSP/NET/net_pending.h",
|
||||
"class": "fp"
|
||||
},
|
||||
{
|
||||
"path": "Drivers/BSP/NET/net_select.c",
|
||||
"class": "fp"
|
||||
@@ -7619,6 +7627,90 @@
|
||||
"kind": "fp-fp",
|
||||
"include": "net_types.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_pending.c",
|
||||
"to": "外部/系统头文件",
|
||||
"kind": "fp-ext",
|
||||
"include": "stddef.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_pending.c",
|
||||
"to": "Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h",
|
||||
"kind": "fp-tp",
|
||||
"include": "FreeRTOS.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_pending.c",
|
||||
"to": "Middlewares/Third_Party/FreeRTOS/Source/include/task.h",
|
||||
"kind": "fp-tp",
|
||||
"include": "task.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_pending.c",
|
||||
"to": "Drivers/BSP/NET/net_types.h",
|
||||
"kind": "fp-fp",
|
||||
"include": "net_types.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_pending.c",
|
||||
"to": "Drivers/BSP/NET/net_socket.h",
|
||||
"kind": "fp-fp",
|
||||
"include": "net_socket.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_pending.c",
|
||||
"to": "Drivers/BSP/NET/net_pending.h",
|
||||
"kind": "fp-fp",
|
||||
"include": "net_pending.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_pending.c",
|
||||
"to": "Drivers/BSP/NET/net_listener.h",
|
||||
"kind": "fp-fp",
|
||||
"include": "net_listener.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_pending.c",
|
||||
"to": "Drivers/BSP/CH395F/ch395f.h",
|
||||
"kind": "fp-fp",
|
||||
"include": "ch395f.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_pending.c",
|
||||
"to": "Drivers/BSP/dbg_log.h",
|
||||
"kind": "fp-fp",
|
||||
"include": "dbg_log.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_pending.c",
|
||||
"to": "Inc/main.h",
|
||||
"kind": "fp-fp",
|
||||
"include": "main.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_pending.h",
|
||||
"to": "外部/系统头文件",
|
||||
"kind": "fp-ext",
|
||||
"include": "stdint.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_pending.h",
|
||||
"to": "Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h",
|
||||
"kind": "fp-tp",
|
||||
"include": "FreeRTOS.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_pending.h",
|
||||
"to": "Middlewares/Third_Party/FreeRTOS/Source/include/task.h",
|
||||
"kind": "fp-tp",
|
||||
"include": "task.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_pending.h",
|
||||
"to": "Drivers/BSP/NET/net_types.h",
|
||||
"kind": "fp-fp",
|
||||
"include": "net_types.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_select.c",
|
||||
"to": "外部/系统头文件",
|
||||
@@ -7733,6 +7825,12 @@
|
||||
"kind": "fp-fp",
|
||||
"include": "net_listener.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_socket.c",
|
||||
"to": "Drivers/BSP/NET/net_pending.h",
|
||||
"kind": "fp-fp",
|
||||
"include": "net_pending.h"
|
||||
},
|
||||
{
|
||||
"from": "Drivers/BSP/NET/net_socket.c",
|
||||
"to": "Drivers/BSP/CH395F/ch395f.h",
|
||||
@@ -41763,6 +41861,12 @@
|
||||
"stdbool.h",
|
||||
"string.h"
|
||||
],
|
||||
"Drivers/BSP/NET/net_pending.c": [
|
||||
"stddef.h"
|
||||
],
|
||||
"Drivers/BSP/NET/net_pending.h": [
|
||||
"stdint.h"
|
||||
],
|
||||
"Drivers/BSP/NET/net_select.c": [
|
||||
"stdio.h",
|
||||
"string.h"
|
||||
@@ -43501,11 +43605,11 @@
|
||||
]
|
||||
},
|
||||
"stats": {
|
||||
"files_indexed": 1491,
|
||||
"includes_parsed": 5939,
|
||||
"resolved": 4792,
|
||||
"unresolved": 1147,
|
||||
"nodes_fp": 211,
|
||||
"files_indexed": 1493,
|
||||
"includes_parsed": 5954,
|
||||
"resolved": 4805,
|
||||
"unresolved": 1149,
|
||||
"nodes_fp": 213,
|
||||
"nodes_tp": 1266,
|
||||
"ext_names": 351
|
||||
},
|
||||
|
||||
+2543
-2441
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 506 KiB After Width: | Height: | Size: 512 KiB |
@@ -82,12 +82,12 @@ digraph refgraph_module {
|
||||
"BSP/GD5F2GQ5UE" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×1"];
|
||||
"BSP/GD5F2GQ5UE" -> "dhara" [color="#2563eb", penwidth=1.2, label="×2"];
|
||||
"BSP/GD5F2GQ5UE" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×4"];
|
||||
"BSP/NET" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×2"];
|
||||
"BSP/NET" -> "BSP/CH395F" [color="#2563eb", penwidth=1.2, label="×3"];
|
||||
"BSP/NET" -> "FreeRTOS" [color="#2563eb", penwidth=1.2, label="×11"];
|
||||
"BSP/NET" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×3"];
|
||||
"BSP/NET" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×3"];
|
||||
"BSP/NET" -> "BSP/CH395F" [color="#2563eb", penwidth=1.2, label="×4"];
|
||||
"BSP/NET" -> "FreeRTOS" [color="#2563eb", penwidth=1.2, label="×15"];
|
||||
"BSP/NET" -> "Src/Inc (CubeMX)" [color="#2563eb", penwidth=1.2, label="×4"];
|
||||
"BSP/NET" -> "test" [color="#2563eb", penwidth=1.2, label="×4"];
|
||||
"BSP/NET" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×4"];
|
||||
"BSP/NET" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×6"];
|
||||
"BSP/NETCFG" -> "App" [color="#2563eb", penwidth=1.2, label="×2"];
|
||||
"BSP/NETCFG" -> "BSP(公共)" [color="#2563eb", penwidth=1.2, label="×1"];
|
||||
"BSP/NETCFG" -> "外部/系统头文件" [color="#94a3b8", penwidth=0.7, label="×4"];
|
||||
|
||||
@@ -513,28 +513,28 @@
|
||||
<title>BSP/NET->BSP(公共)</title>
|
||||
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M1366.53,-1486.55C1371.67,-1482.5 1376.67,-1477.93 1380.75,-1473 1392.66,-1458.6 1385.78,-1448.45 1398.75,-1435 1417.52,-1415.54 1429.05,-1420 1452.75,-1407 1500.15,-1380.99 1523.25,-1387.42 1558,-1346 1607.98,-1286.42 1639.96,-1044.77 1649.85,-959.76"/>
|
||||
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="1653.31,-960.31 1650.97,-949.97 1646.36,-959.51 1653.31,-960.31"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="1514.38" y="-1402.27" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×2</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="1514.38" y="-1402.27" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×3</text>
|
||||
</g>
|
||||
<!-- BSP/NET->BSP/CH395F -->
|
||||
<g id="edge56" class="edge">
|
||||
<title>BSP/NET->BSP/CH395F</title>
|
||||
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M1368.34,-1486.5C1373.07,-1482.56 1377.45,-1478.04 1380.75,-1473 1400.78,-1442.42 1375.3,-1420.79 1398.75,-1392.75 1415.45,-1372.77 1430.53,-1383.59 1452.75,-1370 1464.34,-1362.91 1476.01,-1353.7 1485.97,-1345.06"/>
|
||||
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="1488.11,-1347.85 1493.24,-1338.57 1483.45,-1342.62 1488.11,-1347.85"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="1425.75" y="-1397.5" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×3</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="1425.75" y="-1397.5" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×4</text>
|
||||
</g>
|
||||
<!-- BSP/NET->FreeRTOS -->
|
||||
<g id="edge57" class="edge">
|
||||
<title>BSP/NET->FreeRTOS</title>
|
||||
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M1349.64,-1523.41C1371.83,-1556.12 1418.69,-1626.84 1452.75,-1689.75 1471.36,-1724.13 1489.77,-1765.26 1501.33,-1792.2"/>
|
||||
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="1498,-1793.31 1505.13,-1801.14 1504.44,-1790.57 1498,-1793.31"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="1425.75" y="-1694.5" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×11</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="1425.75" y="-1694.5" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×15</text>
|
||||
</g>
|
||||
<!-- BSP/NET->Src/Inc (CubeMX) -->
|
||||
<g id="edge58" class="edge">
|
||||
<title>BSP/NET->Src/Inc (CubeMX)</title>
|
||||
<path fill="none" stroke="#2563eb" stroke-width="1.2" d="M1329.66,-1523.2C1315.28,-1572.05 1276.62,-1702.66 1273.25,-1706 1264.72,-1714.47 870.58,-1827.86 858.75,-1830 813.65,-1838.17 762.2,-1841.67 722,-1843.13"/>
|
||||
<polygon fill="#2563eb" stroke="#2563eb" stroke-width="1.2" points="722.14,-1839.63 712.26,-1843.45 722.37,-1846.62 722.14,-1839.63"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="982" y="-1802.03" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×3</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="982" y="-1802.03" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×4</text>
|
||||
</g>
|
||||
<!-- BSP/NET->test -->
|
||||
<g id="edge59" class="edge">
|
||||
@@ -548,7 +548,7 @@
|
||||
<title>BSP/NET->外部/系统头文件</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-width="0.7" d="M1369.51,-1508.86C1459.7,-1519.56 1714.78,-1549.82 1843.01,-1565.03"/>
|
||||
<polygon fill="#94a3b8" stroke="#94a3b8" stroke-width="0.7" points="1842.5,-1568.5 1852.84,-1566.2 1843.32,-1561.55 1842.5,-1568.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="1654.12" y="-1550.91" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×4</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="1654.12" y="-1550.91" font-family="Microsoft YaHei" font-size="10.00" fill="#334155">×6</text>
|
||||
</g>
|
||||
<!-- BSP/NETCFG->App -->
|
||||
<g id="edge61" class="edge">
|
||||
|
||||
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
+1
-1
@@ -17,7 +17,7 @@
|
||||
> 本节仅保留**二期仍要用的设计基线**。
|
||||
|
||||
**一期已验证的机制骨架**(二期沿用,勿重新发明):独立静态 pending 表 + `net_pending_tick`
|
||||
主循环节拍了结(五路:数据/到点/断连/同 fd 串行/重建取消必通知)+ 登记守卫(每 fd 至多一个、
|
||||
主循环按节拍结束挂起(五路:数据/到点/断连/同 fd 串行/重建取消必通知)+ 登记守卫(每 fd 至多一个、
|
||||
不覆盖、拒绝 netTask 自等待)+ 1s 看门狗强结算。
|
||||
|
||||
**二期扩展点**:
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 |
|
||||
|---|---|---|---|
|
||||
| ER_IROM1 | 230,208 B (224.81 KB) | 1,048,576 B (1024 KB) | 22.0% |
|
||||
| ER_IROM1 | 230,048 B (224.66 KB) | 1,048,576 B (1024 KB) | 21.9% |
|
||||
| RW_IRAM1 | 97,912 B (95.62 KB) | 131,072 B (128 KB) | 74.7% |
|
||||
| RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% |
|
||||
| **Flash (ROM 合计)** | **231,312 B (225.9 KB)** | 1,048,576 B (1024 KB) | **22.1%** |
|
||||
| **Flash (ROM 合计)** | **231,152 B (225.7 KB)** | 1,048,576 B (1024 KB) | **22.0%** |
|
||||
<!-- MEM_SNAPSHOT:END -->
|
||||
|
||||
各区的容量与常量来源:
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<!-- MEM_BUDGET:BEGIN generated by scripts/ram_baseline.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) |
|
||||
|---|---|---|---|---|
|
||||
| ER_IROM1 (Flash) | 230,208 B | 1,048,576 B | 22.0% | 参考 |
|
||||
| ER_IROM1 (Flash) | 230,048 B | 1,048,576 B | 21.9% | 参考 |
|
||||
| RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS |
|
||||
| UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 |
|
||||
| RW_IRAM1 | 97,912 B | 131,072 B | 74.7% | PASS |
|
||||
|
||||
@@ -241,7 +241,7 @@ P2 之后,对账④保留但只作事件面未收到的补漏(TRAP-NET-17
|
||||
|
||||
| 服务 | 认为的连接结束信号 | net 层实际给什么 | 监听重建者 | 是否留无人重建组合 |
|
||||
|---|---|---|---|---|
|
||||
| `lftpd` 控制 :21 | `read_line!=0`(EOF→-1,`lftpd_inet.c:154-156`) | 阻塞 pending 保证 CLOSED 必了结(FIN→0 / 异常→CONNRESET,`net_socket.c:2457-2462`) | net 层(`:2449-2451`),应用**永不** close 控制 fd(`lftpd.c:746-750`) | 无(契约一致,是正面样板) |
|
||||
| `lftpd` 控制 :21 | `read_line!=0`(EOF→-1,`lftpd_inet.c:154-156`) | 阻塞 pending 保证 CLOSED 时挂起 recv 必被结束(FIN→0 / 异常→CONNRESET,`net_socket.c:2457-2462`) | net 层(`:2449-2451`),应用**永不** close 控制 fd(`lftpd.c:746-750`) | 无(契约一致,是正面样板) |
|
||||
| `time_sync` :8888 | EOF/30 s 预算/错误,随后**无条件** `net_close(conn==ls)`(`time_sync.c:182`) | 同上 | 应用自己每会话全量重建(`:224`) | 不留孤儿,但违反「EOF 不得 close 监听 fd」→ close→重建沉降窗(TRAP-NET-09)+ 最坏 ~10 s 端口下线 |
|
||||
| `ui_net_sink` :7003 | `net_recv` 返 0 或真错误;重建条件 `net_get_sock(g_listen)==NULL`(`:557`/`:566`) | 该 0 只在 recv 时刻 `state==CLOSED` 才给 | 名义 net 层,实际自身条件在 MCU **恒假**(fd 不释放) | **有**(B10 与"消费者卡死"都出自这里) |
|
||||
| 模拟器后端 | `recv` 0 恒可得、fd 分离(`net_socket_sim.c:293-336`) | Winsock 原生 | 无需 | 无——且对上述全部失效模式**零覆盖** |
|
||||
|
||||
@@ -1012,7 +1012,7 @@ int net_drop_conn(int sockfd);
|
||||
|
||||
应用**放弃会话**(对时 30s 预算耗尽、协议超时)时主动拆掉当前 TCP 会话并**保留监听**——单连接模型下挂起但 TCP 仍活的客户端会永久占住监听(PC 内核自动 ACK keepalive,芯片僵尸判不死,定案见 `docs/网络/网络自愈架构设计.md` §5.2 E13),而应用又不得 `net_close` 监听 fd(毁监听);本 API 补上"拆连接不拆监听"的应用侧形态。
|
||||
|
||||
- **netTask 侧**:先了结本 fd 挂起 recv(排空/取消 + 必通知),再 `sock_reset` QUARANTINE(FIN + 排渣 + 排空事件 + 等 byte0=00H,TRAP-NET-18 异常释放语义,400ms 底);本层收口 CLOSED;`auto_relisten` fd 随即 `net_listener_try` 即时重监听(失败交 D10 重试 + reconciler 形态④)
|
||||
- **netTask 侧**:先结束本 fd 挂起 recv(排空/取消 + 必通知),再 `sock_reset` QUARANTINE(FIN + 排渣 + 排空事件 + 等 byte0=00H,TRAP-NET-18 异常释放语义,400ms 底);本层收口 CLOSED;`auto_relisten` fd 随即 `net_listener_try` 即时重监听(失败交 D10 重试 + reconciler 形态④)
|
||||
- **幂等**:无活动会话(非 ESTABLISHED/CONNECTING)直接返 0,不动监听
|
||||
- **EOF 路径不要调**:EOF 时 net 层 `auto_relisten` 已接管(§12.4),无需也不应再拆
|
||||
- 经消息队列委托 netTask(同其它 `net_*` API,非 netTask 上下文调用);等待期 netTask 有界占用 ~400-600ms
|
||||
@@ -1057,7 +1057,7 @@ void net_process_messages(void);
|
||||
处理其他任务投递的请求(send/recv/connect/accept/close/drop_conn)。每次调用先跑 3 个**非阻塞** tick,再取队列消息(0 超时出队):
|
||||
|
||||
1. **D10 重监听重试**(`net_listener_retry_tick`):断连后监听器恢复的到点重试(30ms 间隔 / 2s 时限,§3.6)
|
||||
2. **L3 挂起 recv 完成检查**(`net_pending_tick`):按 ①数据 ②到点(仅显式预算) ③断连 检查每个挂起请求并通知调用方;④ tripwire:挂起超 60s 只 `[ERR]` 留痕一次,不强制了结(自愈收敛阶段 1.5)
|
||||
2. **L3 挂起 recv 完成检查**(`net_pending_tick`):按 ①数据 ②到点(仅显式预算) ③断连 检查每个挂起请求并通知调用方;④ 挂起超时留痕探针(编译期选项,默认关):挂起超阈值只 `[ERR]` 留痕一次,不强制结束
|
||||
3. **TRAP-NET-17 芯片/本层对账 + 监听重建(合一 `net_reconcile_tick`)**:100ms 周期,判据读驱动状态缓存(`ch395f_sock_state_cached`),修正**五类**失同步(①②③④⑤)+ U11 被动重同步——原 200ms 对账与 100ms 监听器 pump 已合并为单一 tick,**唯一重建入口**=形态③④(§3.6)
|
||||
|
||||
> `net_msg_t.timeout_ms`:L3 显式预算(`net_recv_to()`),0=**无限等待**(自愈收敛阶段 1.5 删除 `NET_RECV_TIMEOUT_MS` 30s 默认预算——超时归调用方)。消息类型 7 种(send/recv/close/connect/listen/accept/drop_conn)——自愈类消息 `NET_MSG_RESYNC_LISTEN` / `NET_MSG_RELISTEN` 已随 API 退役删除(自愈收敛阶段1)。
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
> **定位**:本文档是 `docs/网络/BSD_Socket_API_使用指南.md`(下称"使用指南")的**内部视图**伴读文档。
|
||||
> 使用指南回答"**上层该怎么调**"(公开语义 / 示例 / 常见问题);本文回答"**内部函数各自做什么、互相怎么调用、为什么这么设计**"(调用关系 / 设计意图 / 踩坑)。
|
||||
>
|
||||
> **范围**:MCU 端 `Drivers/BSP/NET/net_socket.c`(约 3670 行)+ 其拆出的
|
||||
> `Drivers/BSP/NET/net_listener.c`(约 550 行,**监听生命周期**,N-32 §10 模块化)。
|
||||
> **范围**:MCU 端 `Drivers/BSP/NET/net_socket.c`(约 3660 行)+ 其拆出的
|
||||
> `Drivers/BSP/NET/net_listener.c`(约 550 行,**监听生命周期**,N-32 §10 模块化)+
|
||||
> `Drivers/BSP/NET/net_pending.c`(约 390 行,**挂起 recv 请求表**,2026-10-14 拆出)。
|
||||
> 模拟器端后端 `simulator/sim/net_socket_sim.c`(Winsock)是另一套实现,见 `docs/网络/BSD_Socket_API_使用指南.md` §2.3;`net_select.c` 不在此展开,见 `docs/网络/BSD_Socket_API_使用指南.md` §7 / §10.5。
|
||||
> `net_listener.c` **只被 `net_socket.c` include**(内部头,不进公开 `net_socket.h`),模拟器零依赖、不编译;其设计依据见 `docs/子系统设计/网络Socket监听生命周期设计说明.md` §10。
|
||||
> `net_listener.c` / `net_pending.c` 真机专属(模拟器不编译);设计依据分别见
|
||||
> `docs/子系统设计/网络Socket监听生命周期设计说明.md` §10 与
|
||||
> `docs/网络/Net层阻塞模型架构设计.md`。
|
||||
>
|
||||
> **交叉引用约定**:机制标 Trap 号 → `docs/问题记录/CH395F_Trap_Records.md`;用法语义引用使用指南 §;关键参数值与 `net_config.h` / `net_socket.c` 内宏一致。
|
||||
|
||||
@@ -57,7 +60,7 @@ flowchart TB
|
||||
LS["netSendLocked<br/>分块 ≤1KB + sendReady 门控"]
|
||||
LR["netRecvLocked<br/>快路径 / DONTWAIT / 登记挂起"]
|
||||
LA["netAcceptLocked<br/>先 netPoll + 状态判定"]
|
||||
LC["netCloseLocked<br/>了结挂起 + cmd_done 快路径 / sock_reset(GRACEFUL)"]
|
||||
LC["netCloseLocked<br/>结束挂起 + cmd_done 快路径 / sock_reset(GRACEFUL)"]
|
||||
DC["netDropConnLocked<br/>拆会话保监听:sock_reset(QUARANTINE) + 即时重监听"]
|
||||
RLS["netListenerTry → netRelistenLocked<br/>单次尝试 + TRAP-NET-18 排渣隔离"]
|
||||
end
|
||||
@@ -99,7 +102,7 @@ flowchart TB
|
||||
2. `net_process_messages()` 开头的 **3 个 tick 是网络层自愈主干**:没有任何应用消息时,重试 / 挂起完成 / 状态对账 + 监听重建照常运转——"断连后即使没人发消息,监听器也能自己活过来"靠的就是它们。
|
||||
3. 事件处理器**只由 `net_poll` 驱动**,例外是 `net_reconcile_tick`(分级去抖 + 1s 冷却)可再入 `handle_disconnect_event` / `handle_connect_event`——对账是事件未收到的自愈通道(TRAP-NET-17)。**注意职责边界(N-32 P2,自愈收敛阶段 3 合并后)**:对账与事件处理器**只置事实**(置 CLOSED / 登记沉降),**重建监听的动作统一由 `net_reconcile_tick` 形态③④ 做**(本层唯一重建入口)——"谁先看到 DISCONNECT 就谁重建"的竞争在结构上消失。
|
||||
4. 回调 `event_cb` 在 **netTask 上下文**执行:回调内不得阻塞、不得发网络操作。
|
||||
5. **模块归属**:`T1`(D10 重试)/ `RLS` 的 `net_listener_try` / `ST`(settle)在 `net_listener.c`(真机专属、模拟器不编译);其余 tick(含合并后的 `T3` reconciler)与 `_locked` 在 `net_socket.c`。
|
||||
5. **模块归属**:`T1`(D10 重试)/ `RLS` 的 `net_listener_try` / `ST`(settle)在 `net_listener.c`(真机专属、模拟器不编译);挂起 recv 完成检查(`net_pending_tick`)与挂起表在 `net_pending.c`(真机专属);其余 tick(含合并后的 `T3` reconciler)与 `_locked` 在 `net_socket.c`。
|
||||
|
||||
---
|
||||
|
||||
@@ -133,7 +136,7 @@ flowchart TB
|
||||
| `net_recv` | `NET_MSG_RECV`(timeout=0→**无限等待**,自愈收敛阶段 1.5 删 30s 默认) | `net_recv_locked` | `net_poll` + `get_recv_len` / `read_recv_buf` | >0 / 0(EOF) / -1(errno = WOULDBLOCK / CONNRESET;阻塞形态无 TIMEDOUT 一档) | 快路径:数据已在 → 立即读出返回(允许部分读);无数据 + 阻塞(`blocking` 属性=1 且未 DONTWAIT)→ **登记挂起后立即返回**(L3,netTask 零占用),唤醒源=数据/EOF/close/网络重建;DONTWAIT / `blocking=0` + CLOSED/session_dead → EOF + 触发重监听;⚠ IWDG 受监控任务禁用无限形态(`net_socket.h` 文档契约) |
|
||||
| `net_recv_nb` | `NET_MSG_RECV`(flags=DONTWAIT) | `net_recv_locked` | 同上 | >0 / 0 / -1(WOULDBLOCK 等) | 原 `NET_MSG_DONTWAIT` 路径转正的具名入口(自愈收敛阶段 1.5);不改变 `blocking` 属性;受 IWDG 监控 / 有周期职责任务的推荐默认(ui_net_sink / iec104_serv 已迁) |
|
||||
| `net_recv_to` | `NET_MSG_RECV`(timeout>0) | `net_recv_locked` | 同上 | >0 / 0 / -1 / **-2(本片超时无数据)** | E-02 头阻塞修复配套 + **显式预算**入口(自愈收敛阶段 1.5,等价 Python settimeout,无隐藏默认值):调用方把长等待切成有界片(如剩余预算),**总超时语义不变、单次占用有界**;`timeout_ms=0` 直接拒(0 = 无限等待,与本接口"有界"存在理由相反);超时**只凭 -2 判定、不得依赖 errno**(`s_net_errno` 是跨任务共享全局量,随时被别的任务覆盖) |
|
||||
| `net_close` | `NET_MSG_CLOSE` | `net_close_locked` | `ch395f_cmd_done(CLOSE)` 快路径(芯片已 CLOSED → 只清本 Socket 残留事件)/ `net_sock_reset_sync(GRACEFUL)` 确定性排渣(自愈收敛阶段 3) | 0 / -1 | **先**了结本 fd 挂起 recv(排空/取消 + 必通知,关不得让等待者悬挂);排空只碰本 Socket SINT,**不再读清全局中断寄存器**(F-NET-7:全局唯一读者 = `net_poll`);原"close + 500ms 盲等排空循环"已换 sock_reset(内置 LISTEN 态二段 CLOSE 补发,E2 补充发现);控制块整体清零(`last_peer_port` 幽灵基准跨 fd 保留,N-30) |
|
||||
| `net_close` | `NET_MSG_CLOSE` | `net_close_locked` | `ch395f_cmd_done(CLOSE)` 快路径(芯片已 CLOSED → 只清本 Socket 残留事件)/ `net_sock_reset_sync(GRACEFUL)` 确定性排渣(自愈收敛阶段 3) | 0 / -1 | **先**结束本 fd 挂起 recv(排空/取消 + 必通知,关不得让等待者悬挂);排空只碰本 Socket SINT,**不再读清全局中断寄存器**(F-NET-7:全局唯一读者 = `net_poll`);原"close + 500ms 盲等排空循环"已换 sock_reset(内置 LISTEN 态二段 CLOSE 补发,E2 补充发现);控制块整体清零(`last_peer_port` 幽灵基准跨 fd 保留,N-30) |
|
||||
| `net_drop_conn` | `NET_MSG_DROP_CONN`(自愈收敛阶段 3 新增) | `net_drop_conn_locked` | `net_pending_settle_close` + `net_sock_reset_sync(QUARANTINE)` +(auto_relisten)`net_listener_try` | 0(含幂等)/ -1 | **拆会话保监听**(E-02 收口):应用放弃会话(对时 30s 预算耗尽 / 协议超时)而单连接监听被挂起对端永久占住时的应用侧出口;EOF 路径**不得**调(`auto_relisten` 已接管);非 ESTABLISHED/CONNECTING 直接返 0 不动监听;本层收口 CLOSED 不清 fd(保留 local_port/auto_relisten),监听型 fd 即时重监听、失败交 D10 + reconciler 形态④ |
|
||||
| ~~`net_relisten`~~ | ~~`NET_MSG_RELISTEN`~~ | **自愈收敛阶段1 已删除**(消息类型同删) | — | — | 原"无条件芯片侧重建"与 pump/D10 完全重叠,且对健康监听器是 footgun(open→USE→close 自毁 ~1s);生产零调用。历史实现见 `net_socket.c` git 历史 |
|
||||
| ~~`net_resync_listen`~~ | ~~`NET_MSG_RESYNC_LISTEN`~~ | **自愈收敛阶段1 已删除**(语义并入对账⑤) | — | — | D14 静默回收(芯片自行回监听、不发 DISCONNECT)现由 `net_reconcile_tick` 判据⑤自动认领:本层 ESTABLISHED 且芯片 0x05/0x01 → 只修本层(state→LISTENING + 转登 `last_peer_port` + 清 `lost_conn_seen` + 取消挂起 recv),**不发芯片命令**(纪律原样保留,run8 实证 170s 拒绝);去抖/冷却沿用对账框架 |
|
||||
@@ -161,9 +164,9 @@ flowchart TB
|
||||
| 函数 | 设计作用 | 主要调用 | 意图 / 注意 |
|
||||
|---|---|---|---|
|
||||
| `net_pending_register` | 登记挂起(`net_recv_locked` 的"无数据 + 阻塞"路径) | 写 `s_net_pending[fd]` | 每 fd 至多一个:**覆盖会丢失前调用方通知 = 永久挂死**;执行任务自身(netTask)发起的等待直接拒绝(自等自通知 = 死锁,netTask 内须用 DONTWAIT) |
|
||||
| `net_pending_tick` | 完成检查(`net_process_messages` 每轮调用,非阻塞) | ① `get_recv_len` + `read_recv_buf` ② 到点(仅显式预算) ③ `get_recv_len` 排空 + `net_listener_try` ④ `net_pending_complete` | 按 **①数据 ②到点(仅 `net_recv_to` 显式预算;无限登记无 deadline)③断连** 顺序检查(逐项对齐旧自旋语义);CONNECTING/LISTENING 等状态**保持等待**(CONNECT→ESTABLISHED 是合法迁移,不做状态推断取消,防误杀正在建链的等待);**tripwire**(自愈收敛阶段 1.5,原看门狗降级):挂起超 60s = 完成逻辑缺位 → 只 `[ERR]` 留痕一次,**绝不强制了结、绝不伪造 TIMEDOUT**——③ 与显式 cancel 是无限 recv 的全部唤醒责任方 |
|
||||
| `net_pending_tick` | 完成检查(`net_process_messages` 每轮调用,非阻塞) | ① `get_recv_len` + `read_recv_buf` ② 到点(仅显式预算) ③ `get_recv_len` 排空 + `net_listener_try` ④ `net_pending_complete` | 按 **①数据 ②到点(仅 `net_recv_to` 显式预算;无限登记无 deadline)③断连** 顺序检查(逐项对齐旧自旋语义);CONNECTING/LISTENING 等状态**保持等待**(CONNECT→ESTABLISHED 是合法迁移,不做状态推断取消,防误杀正在建链的等待);**挂起超时留痕探针**(编译期选项 `NET_PENDING_TRIPWIRE`,默认关):挂起超阈值 = 完成逻辑缺位 → 只 `[ERR]` 留痕一次,**绝不强制结束、绝不伪造 TIMEDOUT**——③ 与显式 cancel 是无限 recv 的全部唤醒责任方 |
|
||||
| `net_pending_complete` | 所有完成/取消路径的公共出口 | `xTaskNotify(caller, result)` + 清 active | 通知值 = 结果原值(>0 字节 / 0 EOF / 负错误码,与消息分派同一回传通道);失败结果同步 `s_net_errno`(netTask 上下文一致性);无在位挂起时静默返回(防重复通知) |
|
||||
| `net_pending_settle_close` | close 前**了结**本 fd 挂起 | `get_recv_len` + `read_recv_buf` + `net_pending_complete` | 关闭不得丢已到达数据(芯片缓冲 close 前可读):有数据 → 按字节数通知;无数据 → 取消(CONNRESET);先于 `ch395f_close_socket` 执行 |
|
||||
| `net_pending_settle_close` | close 前**结束**本 fd 挂起 | `get_recv_len` + `read_recv_buf` + `net_pending_complete` | 关闭不得丢已到达数据(芯片缓冲 close 前可读):有数据 → 按字节数通知;无数据 → 取消(CONNRESET);先于 `ch395f_close_socket` 执行 |
|
||||
| `net_pending_cancel` / `cancel_all` | 显式取消(**必通知**) | `net_pending_complete` | 取消路径:陈旧 resync(单 fd)/ 网络重建(全部,`net_init` 开头,早于控制块清零)——"取消不通知 = 调用方永久挂死" |
|
||||
| `net_pending_clear` | 清除**不通知** | 清 `active` | 仅用于"新 fd 不得继承"(`net_socket`)与 close 后幂等清理(`net_close_locked`)——这两处前驱通知已由 settle 路径发出 |
|
||||
|
||||
@@ -374,7 +377,7 @@ sequenceDiagram
|
||||
| 5 | **基于状态读的重建动作必须去抖 + 冷却**:分级门槛 ②=1 轮 / ③④⑤=2 轮 / ①=4 轮(100ms/轮)+ 冷却 1s | 2026-09-23:无去抖 → 监听反复重建直至 `relisten give up`,三服务口全死 |
|
||||
| 6 | **异常释放(TIMEOUT)后复监听前必须隔离**:排空 RX 残渣 + 等 CLOSED/TCP_CLOSED;隔离上限 < 重试放弃时限 | TRAP-NET-18:~2KB 残渣被下条连接当自己的数据 → 首笔上传截断 2048B 并误报 226 成功;上限=2s 时隔离耗尽重试预算 → 监听被弃 |
|
||||
| 7 | **RECV 电平触发中断每轮只处理一批** | TRAP-NET-08:连续循环 → 主循环饿死 |
|
||||
| 8 | **close 前先了结挂起 recv + 排空残留 DISCONNECT** | TRAP-NET-12:锁存的陈旧 DISCONNECT 秒断下一连接 |
|
||||
| 8 | **close 前先结束挂起 recv + 排空残留 DISCONNECT** | TRAP-NET-12:锁存的陈旧 DISCONNECT 秒断下一连接 |
|
||||
| 9 | **两段式等待防丢失通知**:零等待拾取 + 阻塞;清除掩码全 1;以 pdTRUE 判定 | run3~11:丢失通知 → 挂死 / 结果错位;0 结果误判"无通知" → 永久挂死 |
|
||||
| 10 | **网络重建先取消并通知全部挂起,再复位芯片** | 取消不通知 = 调用方永久挂死 |
|
||||
| 11 | **监听重建只有一个所有者**(`net_reconcile_tick` 形态③④,自愈收敛阶段 3 合并后;原 `net_listener_pump` 已删);其它位置**只置事实**(置 CLOSED / 登记沉降),不做重建动作 | N-32 P2 / B10:多所有者导致"谁先看到 DISCONNECT 就谁重建"的竞争与重建风暴 |
|
||||
|
||||
+68
-16
@@ -1,6 +1,6 @@
|
||||
# Net 层阻塞模型架构设计
|
||||
|
||||
> **模块名称**:Net 层(`net_socket.c` / netTask)阻塞等待模型
|
||||
> **模块名称**:Net 层挂起 recv 模型(`net_pending.c` / netTask 主循环节拍)
|
||||
> **文档定位**:**现状架构设计说明**——描述本层**当前如何组织等待与完成**、为什么这样设计、
|
||||
> 边界条件与已验证的性能特征。**不是历史记录**:历史演进、门禁实测记录与修订日志
|
||||
> 一律不保留在文档内(需要时查 git 历史)。
|
||||
@@ -44,8 +44,8 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
|
||||
| 区域 | 内容 |
|
||||
|---|---|
|
||||
| **左栏 · 调用方任务** | `net_recv` → 判"数据已在?" → **是**走快速路径;**否**(`timeout > 0`)登记 pending 后在自己上下文睡任务通知 |
|
||||
| **中栏 · netTask 主循环** | ~1ms 节拍:`net_poll`(刷新 readable + 全量刷新驱动状态缓存)→ 3 个非阻塞 tick(`net_listener_retry_tick` D10 → `net_pending_tick` 了结挂起 → `net_reconcile_tick` TRAP-NET-17 对账 + 监听重建唯一入口)→ 排空消息队列 |
|
||||
| **下带 · 五路判定** | 每轮对每个挂起 fd 按序判定:数据到达 / 到点(仅显式预算)/ EOF / 同 fd 后续操作 / 网络重建;tripwire 60s 只留痕 |
|
||||
| **中栏 · netTask 主循环** | ~1ms 节拍:`net_poll`(刷新 readable + 全量刷新驱动状态缓存)→ 3 个非阻塞 tick(`net_listener_retry_tick` D10 → `net_pending_tick` 结束挂起 → `net_reconcile_tick` TRAP-NET-17 对账 + 监听重建唯一入口)→ 排空消息队列 |
|
||||
| **下带 · 五路判定** | 每轮对每个挂起 fd 按序判定:数据到达 / 到点(仅显式预算)/ EOF / 同 fd 后续操作 / 网络重建;挂起超时留痕探针(编译期选项,默认关,见 §9)只留痕 |
|
||||
| **回环** | 判定完成经 `xTaskNotify` 唤醒调用方(结果 = 字节数 / 负错误码) |
|
||||
|
||||
**关键性质**:
|
||||
@@ -70,7 +70,7 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
|
||||
| 项 | 设计 |
|
||||
|---|---|
|
||||
| 存储 | **独立静态数组** `s_net_pending[NET_MAX_SOCKETS]`(不置于 `net_sock_t` 内) |
|
||||
| 字段 | `active` / `infinite` / `tripwire_logged` / `buf` / `len` / `deadline` / `registered` / `caller`(`infinite`:0 预算 = 无限等待;`tripwire_logged`:tripwire 只留痕一次) |
|
||||
| 字段 | `active` / `infinite` / `buf` / `len` / `deadline` / `registered` / `caller`(`infinite`:0 预算 = 无限等待;留痕开启时另有 `tripwire_logged`:保证只留痕一次) |
|
||||
| 容量 | 每 fd **至多一个**挂起请求 |
|
||||
| 占用 | ≈24B/fd × 8 fd = **192B**(+4B 任务句柄),实测 SRAM +200B |
|
||||
|
||||
@@ -89,28 +89,63 @@ socket 生命周期解耦,与 `net_listener_retry_tick` 的重试表同构。
|
||||
|
||||
---
|
||||
|
||||
## 4. 完成与取消语义(五路)
|
||||
## 4. 挂起的 recv 会被什么结束(5 种触发条件)
|
||||
|
||||
`net_pending_tick()` 在主循环每轮运行,对每个挂起 fd 按序判定:
|
||||
挂起 recv 被结束的方式只有 5 种,下文简称**五路**——①②③ 由 `net_pending_tick()`
|
||||
每轮对每个挂起 fd 按序判定,④⑤ 由显式操作触发:
|
||||
|
||||
| # | 条件 | 动作 | 设计要点 |
|
||||
|---|---|---|---|
|
||||
| 1 | **数据到达** | SPI 读出 + 通知(结果 = 字节数) | 由 `net_poll` 维护的 `readable` 门控,**零额外 SPI**;仅 ESTABLISHED / UDP_OPEN 态可读;spurious readable 需清除 |
|
||||
| 2 | **到点** `now >= deadline`(仅显式预算登记;无限等待登记无 deadline) | 通知 `NET_ERR_TIMEDOUT`(对外 -2 映射保持) | 状态无关;用有符号回绕安全差值,防 tick 回绕;这是 `net_recv_to` 调用方策略的正常兑现,不是伪造 |
|
||||
| 3 | **CLOSED**(EOF/DISCONNECT) | 排空:有数据则读出;否则按 0(EOF) 完成 + `auto_relisten` + 通知 | CLOSE 先行完成挂起 = `net_close` 的 settle 钩子(排空→数据否则 CONNRESET) |
|
||||
| 4 | **同 fd 后续操作** | 先结算挂起,再执行新操作 | per-fd 串行——冻结面从"全网"缩到"本 fd" |
|
||||
| 4 | **同 fd 后续操作**:close / drop_conn / 对账 resync(芯片静默回收) | 先结束挂起 recv(排空 → 有数据按字节数完成;否则取消 CONNRESET),再执行新操作 | per-fd 串行:pending 模型唯一留下的次序面就在本 fd 内,其他 fd 不受影响。同 fd 第二个 recv 被登记守卫拒绝(BUSY);send 无需结算(收发不冲突,SPI 由 netTask 串行) |
|
||||
| 5 | **网络重建**(`net_stack_bring_up`,芯片复位) | **取消 + 必须通知** | `net_init` 顶部 `net_pending_cancel_all(CONNRESET)`,先于芯片复位;**取消不通知 = 调用方永久挂死** |
|
||||
|
||||
**状态推断的克制**:CONNECTING / LISTENING 等中间态**保持等待**,**不做**状态推断式取消——
|
||||
CONNECT → ESTABLISHED 是合法迁移,推断取消会误杀正在建链的等待。
|
||||
|
||||
### 4.1 tripwire
|
||||
### 4.1 一次阻塞 recv 从调用到返回的全过程
|
||||
|
||||
挂起超 60s(`NET_RECV_TRIPWIRE_MS`)= "完成逻辑缺位"探针 → **只 `DBG_ERROR` 留痕一次,
|
||||
绝不强制了结、绝不伪造 TIMEDOUT 返回值**——强制了结会掩盖完成路径缺陷,伪造返回值
|
||||
违背调用方意图。
|
||||
定位:留痕成本极低的开发期探针;唤醒责任在 ①数据 ②到点(仅显式预算)③断连 与
|
||||
显式 cancel 路径(close / 网络重建 / 陈旧重同步),见 §4 五路。
|
||||
一句话链:**登记(μs 级)→ 调用方自己睡 → 每轮判定(只读标志与到期时间)→ 公共出口(通知+清槽)→ 两段式等待取结果**。以 `net_recv(sockfd, buf, len)` 阻塞路径为例:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["调用方任务:net_recv(sockfd, buf, len)"] --> B["消息入队,随后在<br/>自己上下文睡(两段式等待)"]
|
||||
B -.-> C
|
||||
|
||||
subgraph NT["netTask —— 唯一执行者(~1ms 节拍)"]
|
||||
C{"数据已在?<br/>readable / recv_len > 0"}
|
||||
C -- "是(常见)" --> D["直接读出"]
|
||||
C -- "否 + 阻塞模式" --> E["登记挂起请求(μs 级)<br/>守卫:每 fd 至多一个、拒绝 netTask 自等待"]
|
||||
E --> F["随即返回,去处理下一条消息<br/>—— 等待不占用执行者"]
|
||||
F --> G["每轮检查每个挂起 fd"]
|
||||
G -- "未命中 → 等下一轮" --> G
|
||||
G -- "数据到达" --> H1["SPI 读出"]
|
||||
G -- "到点(仅显式预算)" --> H2["结果 = TIMEDOUT"]
|
||||
G -- "断连(CLOSED / session_dead)" --> H3["排空:有残留 → 读出<br/>无残留 → EOF(0) / 异常 CONNRESET"]
|
||||
H1 --> I
|
||||
H2 --> I
|
||||
H3 --> I
|
||||
end
|
||||
|
||||
D --> I
|
||||
I["通知调用方(同一回传通道):通知值 = 结果原值<br/>挂起路径经公共出口 net_pending_complete:清槽、防重复通知"]
|
||||
I --> J["调用方醒来取结果<br/>0 = EOF 也是有效通知(以 pdTRUE 判定)"]
|
||||
|
||||
K["取消路径:close / drop_conn / 网络重建"] -. "先结束挂起、再动芯片" .-> I
|
||||
```
|
||||
|
||||
1. **入队**(调用方任务):`net_recv` 把请求打包进消息入 `netMsgQueue`,随后进入两段式等待(步骤 6)。netTask 不被占用。
|
||||
2. **快速路径判定**(netTask,`net_recv_locked`):数据已在(`readable` / recv_len>0)→ 直接读出、经通知值回传字节数——不产生挂起,常见路径到此为止。
|
||||
3. **登记**(netTask):无数据且阻塞模式 → `net_pending_register` 过两条守卫(§3.2:每 fd 至多一个不覆盖;拒绝 netTask 自等待)→ 记 `buf`/`len`/`caller` 与预算(`net_recv_to` → `deadline`;无限等待 → `infinite=1` 无 deadline)→ 返回"已登记"哨兵,netTask 立即处理下一条消息。
|
||||
4. **每轮判定**(netTask,`net_pending_tick`):对每个挂起 fd 按序 ①数据 → ②到点 → ③断连(判据见上表;③ 的判据是 `state==CLOSED || session_dead`,漏 ③ = 无限 recv 永久挂死,勿动)。命中任一条件进入公共出口;挂起超时留痕探针只留痕不完成(编译期选项,默认关,见 §9)。
|
||||
5. **公共完成出口**(`net_pending_complete`,所有完成/取消路径的唯一出口):`xTaskNotify` 覆盖式写入**通知值 = 结果原值**(>0 字节数 / 0 EOF / 负错误码)→ 清 `active`;无在位挂起则静默返回(防重复通知)。失败结果同步记入 netTask 自己的 errno 槽位(按任务槽位,非跨任务共享全局)。
|
||||
6. **唤醒与取结果**(调用方任务):所有 `net_*` API 的回执共用同一条任务通知通道,**两段式等待**防"入队→等待入口"间隙内通知丢失——先零等待拾取(清除掩码全 1;以 pdTRUE/pdFALSE 判定已落地,不能判值≠0——合法的 0 结果=EOF 也是有效通知),未拾取到再阻塞等待。通知值 32 位整体作为结果,不切分位域(大单次收发不被截断)。
|
||||
7. **取消路径**(五路中的 ④⑤,共同规则"**先结束挂起、再动芯片**"):
|
||||
- **同 fd 后续操作**(close / drop_conn):`net_pending_settle_close` 先结算——芯片 RX 有数据(bit3=1 契约:断连后残数据留缓冲待 MCU 读)→ 按字节数完成;无数据 → 取消(CONNRESET)——然后才执行 close/拆会话。对账发现芯片静默回收(resync)时走 `net_pending_cancel`(连接已被芯片收走,无排空窗口,直接取消必通知)。
|
||||
- **网络重建**:`net_init` 顶部 `net_pending_cancel_all(CONNRESET)`,先于芯片复位;取消必通知,否则调用方永久挂死。
|
||||
8. **buf 生命周期**:挂起期间 `buf` 指针须保持有效至调用返回(挂起表按指针写入,§5)。
|
||||
|
||||
---
|
||||
|
||||
@@ -144,8 +179,8 @@ CONNECT → ESTABLISHED 是合法迁移,推断取消会误杀正在建链的
|
||||
| **SEND 路径未覆盖** | 发送仍为分块阻塞(1s/chunk stall) | 该期间队列仍被冻结;二期 pending 化解决 |
|
||||
| **ACCEPT 路径未覆盖** | accept 仍用轮询计时 | 需 pending 化(二期) |
|
||||
| **超时精度** | 随主循环节拍(~1ms),被长操作占用时顺延 | 一期文档化、不修;二期 SEND 化后该顺延面**收窄** |
|
||||
| **tripwire 未接自检** | 只 `DBG_ERROR` 留痕(不再强制了结),未写 selftest | 二期补齐 |
|
||||
| **模拟器不可建模** | `net_socket_sim.c` 单线程 Winsock 无法复现**挂起模型内部机制**(外部语义已由包装层按 blocking 属性/预算模拟) | 挂起表/tick/通知路径在模拟器上零覆盖,只能硬件验证;**监听生命周期 `net_listener.c` 亦为真机专属**(内部头只被 `net_socket.c` include,CMakeLists 不编译) |
|
||||
| **挂起超时留痕探针** | 编译期选项 `NET_PENDING_TRIPWIRE` 默认关(排查挂死时开);未接自检 selftest | 二期补齐自检 |
|
||||
| **模拟器不可建模** | `net_socket_sim.c` 单线程 Winsock 无法复现**挂起模型内部机制**(外部语义已由包装层按 blocking 属性/预算模拟) | 挂起表/tick/通知路径在模拟器上零覆盖,只能硬件验证;挂起模型实现独立在 `net_pending.c`(真机专属,CMakeLists 不编译;**监听生命周期 `net_listener.c` 同此**) |
|
||||
|
||||
**二期扩展设计**(ACCEPT / SEND 的挂起化、门禁清单、改动量)见
|
||||
`docs/任务规划/基线与待确认.md` §2.1。
|
||||
@@ -154,10 +189,27 @@ CONNECT → ESTABLISHED 是合法迁移,推断取消会误杀正在建链的
|
||||
|
||||
## 8. 设计铁律(为什么是这样)
|
||||
|
||||
1. **执行者永不在等待中自旋**——等待必须在调用方上下文完成,netTask 只负责"登记"与"了结"。
|
||||
1. **执行者永不在等待中自旋**——等待必须在调用方上下文完成,netTask 只负责"登记"与"结束"。
|
||||
2. **取消必通知**——任何取消路径(close / 重建 / 陈旧重同步)都必须唤醒调用方,
|
||||
否则是永久挂死而非错误返回。
|
||||
3. **挂起状态与 socket 表解耦**——`net_sock_t` 会被整块 memset,跨时间状态不能寄居其中。
|
||||
4. **不做状态推断式取消**——中间态(CONNECTING/LISTENING)合法迁移存在,
|
||||
推断取消会误杀正常建链等待。
|
||||
5. **数据到达不引入额外 SPI**——判据复用主循环既有 `net_poll` 维护的 `readable` 标志。
|
||||
|
||||
---
|
||||
|
||||
## 9. 排查辅助:挂起超时留痕探针(编译期选项,默认关)
|
||||
|
||||
五路判定 + 显式 cancel 是挂起 recv 的**全部唤醒责任**(§4)。一个挂起请求若超过阈值
|
||||
(60s,`NET_RECV_TRIPWIRE_MS`)还没被任何一路结束,只有一种解释:**完成路径漏了它**
|
||||
(代码缺陷)——而不是"数据还没到":无限等待本来就是调用方的合法策略,不该被强行打断。
|
||||
|
||||
探针的处理:`net_pending_tick` 对这样的 fd **只打一条 `[ERR]` 日志**(每 fd 一次,
|
||||
防刷屏),然后让它继续等——**绝不强制结束、绝不伪造 TIMEDOUT 返回值**。为什么不用
|
||||
"强制超时"兜底:强制结束会把"唤醒路径有 bug"这个**缺陷**伪装成一次**普通超时**——
|
||||
调用方拿到假结果,缺陷永远潜伏(旧实现正是"到点 +1s 伪造 TIMEDOUT",已废除)。
|
||||
|
||||
开关:`NET_PENDING_TRIPWIRE`(`net_socket.c`,默认 0 = 编译剔除、产品零成本)——
|
||||
排查"调用方挂死唤不醒"时置 1 重烧。**开与关语义完全一致**:探针只留痕,不参与完成。
|
||||
代码里该机制名为 tripwire(绊线:挂住 = 踩到缺陷 wiring)。
|
||||
|
||||
+10
-10
@@ -2,7 +2,7 @@
|
||||
|
||||
> **定位**:本文是 CH395F 网络自愈架构的**现状权威描述**——设计结构、原理、关键参数及其实证锚点。
|
||||
> 文中「收敛阶段 0~3」指 2026-10 自愈架构的分阶段实施(过程已归档,见 §9)。
|
||||
> 架构审计过程(T1~T4 三路审计与 39 条编号发现、E0~E14 实验方法与原始记录、分阶段实施清单)
|
||||
> 架构审计过程(T1~T4 三路审计与 39 条编号发现、E0~E15 实验方法与原始记录、分阶段实施清单)
|
||||
> 已完成使命并删除——过程全文在 git 历史(`git show 1a35b36^:docs/网络/N34-自愈审计/`,目录合并前
|
||||
> 7 份文档)。实证结论已沉淀进:`docs/问题记录/CH395F_Trap_Records.md`(TRAP-NET-01~21 与 B 系列)、
|
||||
> `docs/子系统设计/网络Socket监听生命周期设计说明.md`(D1~D10 与模块化边界)、`docs/芯片手册/CH395F.md`。
|
||||
@@ -15,12 +15,8 @@
|
||||
|
||||
1. **芯片永不自动回 LISTEN**——单连接复用模型(监听与数据通道复用同一 Socket),
|
||||
"监听常驻"只能是软件承诺(I4)。
|
||||
2. **事件面不是可靠唯一信源**——旧"芯片丢中断"说法已被 E14 复核**推翻**(2026-10-14,
|
||||
48 轮直读 SINT 矩阵:芯片侧事件丢失 = 0,SINT 锁存可靠)。历史"丢失"全部是
|
||||
**读清消费竞态**(台架 post-listen drain 吞掉即时到达的 CONNECT,drain-int 探针
|
||||
现场抓获 3/48;生产 B3 类游离全局读已清除)+ **门控观察伪象**(2/48,SINT 30ms 内
|
||||
即捕获)。但结论不变:事件可被其它合法读者消费,且状态面存在事件覆盖不了的幽灵
|
||||
(E7b)→ 正确性必须靠回读芯片事实 + 有界超时兜底(I3)。
|
||||
2. **事件面不是可靠唯一信源**——事件可被其它合法读者消费,且状态面存在事件覆盖不了的幽灵
|
||||
(E7b;E15:幽灵 = 芯片如实报告旧连接拆链进行中,非谎报)→ 正确性必须靠回读芯片事实 + 有界超时兜底(I3)。
|
||||
3. **close 有沉降窗、RX 残渣有害**——带非空 RX 关闭会拆链停滞、吞下一会话 SYN 且自持
|
||||
(E4 变体 C 0/20);沉降窗不定长(TRAP-NET-09/-20)→ 复用前清理必须内联进事务(I5)。
|
||||
|
||||
@@ -62,7 +58,7 @@ reconciler 保留分级去抖,因为形态①要让位 M3 窗、形态②要
|
||||
| tick | 节奏 | 职责 |
|
||||
|---|---|---|
|
||||
| `net_poll` | 主循环每轮(≈1ms) | INT# 引脚 → 全局事件汇聚(唯一读者)→ SINT 分派 → **驱动缓存全量刷新** → 链路位喂驱动 → 步进 `ch395f_sock_reset_poll` |
|
||||
| `net_process_messages` | 每轮 | RPC 消息处理;开头每轮 `net_pending_tick`(挂起 recv 了结)+ `net_listener_retry_tick`(D10) |
|
||||
| `net_process_messages` | 每轮 | RPC 消息处理;开头每轮 `net_pending_tick`(挂起 recv 结束)+ `net_listener_retry_tick`(D10) |
|
||||
| `net_reconcile_tick` | 100ms | 唯一周期对账 + 重建(§4.1) |
|
||||
|
||||
### 3.3 状态模型(会话投影)
|
||||
@@ -161,14 +157,17 @@ reconciler 保留分级去抖,因为形态①要让位 M3 窗、形态②要
|
||||
socket 属性);`net_recv_to()` = 显式预算便捷入口(`timeout_ms=0` 仍拒 INVAL)。
|
||||
- 唤醒源 = 数据 / EOF / close / 网络重建 CONNRESET;会话死亡兜底 = 形态①(≈400ms)。
|
||||
- **L3 pending recv**:阻塞 recv 不独占 netTask(挂起登记 + 主循环节拍完成);
|
||||
**tripwire**:挂起超 60s 只 `[ERR]` 留痕一次(完成逻辑缺位探针),不伪造 TIMEDOUT。
|
||||
**挂起超时留痕探针**(编译期开关 `NET_PENDING_TRIPWIRE`,默认关):挂起超阈值只
|
||||
`[ERR]` 留痕一次(完成逻辑缺位),不伪造 TIMEDOUT。
|
||||
- **术语**:"结束挂起 recv" = `net_pending_complete` 把结果(字节数 / EOF(0) / 错误码)
|
||||
回传给调用任务并唤醒它,挂起槽随之释放(旧称"了结",2026-10-14 更名)。
|
||||
- **文档契约**(嵌入式特有):IWDG `required_mask` 内任务**禁止无限阻塞 recv**——
|
||||
必须 `net_select` 超时或 `net_recv_to` 显式预算。
|
||||
- 上层 32 轮盲收(`ui_net_sink` / `iec104_serv`)保留现状(E6 裁决,§4.8)。
|
||||
|
||||
### 4.6 `net_drop_conn`——应用主动拆会话保监听(E-02 收口)
|
||||
|
||||
- 语义:`net_pending_settle_close` 先了结挂起 recv → `net_sock_reset_sync(QUARANTINE)`
|
||||
- 语义:`net_pending_settle_close` 先结束挂起 recv(回传结果 + 唤醒调用方)→ `net_sock_reset_sync(QUARANTINE)`
|
||||
→ 本层收口 CLOSED(不清 fd)→ `auto_relisten` 即时 `net_listener_try`
|
||||
(失败交 D10 + 形态④)。
|
||||
- **幂等**(非 ESTABLISHED/CONNECTING 直接返 0);**EOF 路径不得调**
|
||||
@@ -233,6 +232,7 @@ reconciler 保留分级去抖,因为形态①要让位 M3 窗、形态②要
|
||||
| E6 | U15 命中=0(~4h 常态 + 压测;用户裁决提前收线) | §4.8 观察项 |
|
||||
| E-02 | `drop_conn` 时序定案:配合对端 TIME_WAIT ~150ms→形态④;不配合 FIN_WAIT_2 长尾 | §4.6 |
|
||||
| E14 | "丢中断"复核(2026-10-14,48 轮直读 SINT):芯片侧事件丢失 = 0;"丢失"全部 = drain 读清消费(3/48,drain-int 现场抓获)+ 门控竞态(2/48)+ 主机同步(1/48);幽灵态本轮 0/48 未复现 | §1 事实 2 改写;TRAP-NET-17/-20 归因更正(机制保留不变);生产 listen/relisten 排事件挪至 LISTEN 命令**前**(根除自伤窗) |
|
||||
| E15 | 幽灵态归因复核(2026-10-14,官方出处):0x04+旧端口 = 芯片**如实报告**旧连接拆链进行中——① bit3 `SOCKET_CLOSE=1` 为我方设置(版本门控,§5.52②:DISCONNECT/TIM_OUT 后芯片按设计不动状态与缓冲);② 拆链走 RFC 9293 §3.3.2 状态机:主动关闭 FIN_WAIT_1→FIN_WAIT_2→TIME_WAIT(2MSL 驻留;TIME_WAIT 协议目的=防旧连接迟到报文伤新连接——"断连即重连被拒"的协议根源),芯片自动走完(手册 §5.22);§5.17 默认重传 12 次、实测耗尽 ≥3s,与拆链窗实测 >2s 同量级;③ WCH 官方博客确认"断连后立即重连被拒、需程序侧特殊处理" | TRAP-NET-20"谎报"→"如实报告";settle 等待 = 官方方案;幽灵甄别 = 强制放行路径的护栏;I3 就此加强 |
|
||||
|
||||
## 6. 红线(明确不做的事)
|
||||
|
||||
|
||||
@@ -124,7 +124,8 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修
|
||||
- **状态**:✅(回归 4 轮 11/11;全局寄存器读即清见第六章 B3)
|
||||
- **现象**:同一会话内**第 2 次及以后**的数据连接(LIST/STOR/RETR)两类失败,概率随上一条数据连接的负载与间隔变化:① **吞数据**——新连接建立成功、服务端 `net_send` 返回成功,客户端 20s 收不到任何字节;② **425**——新连接被接受后 ~10-20ms 出现 DISCONNECT(`chip tcp=0x00`),accept 失败。
|
||||
- **定案实验(裸机芯片台架,剥离 OS/net/FTP)**:`test/board/ch395f_reuse_bench.c` 内核启动前直接调 `ch395f_*` 受控矩阵 + 主机侧 `test/host/ch395f_reuse_host.py`。**剥离后 bug 依旧复现** → 纯芯片操作时序问题。关键现场:`post-listen st=0x05/0x04`(listen 成功但 TCP 态停在旧连接 ESTABLISHED=幽灵态)、`unexpected int=0x10`(只等来旧连接滞后 DISCONNECT)、`no CONNECT within 10s`(芯片状态已 0x04 但事件始终未到等待环——E14 复核:事件已被 drain 读清消费,非芯片丢失)。
|
||||
- **芯片级规律**:CH395F 拆链收尾窗口**变长且不可观测**(状态显示 CLOSED 持续 1s+ 也不代表拆完,实测可 >2s 且时长不定)。窗口内重新 `open+listen` 三种形态:① 握手完成但 **CONNECT 事件未收到**(E14 复核:事件被台架 post-listen drain 或读清竞态吞掉,非芯片侧丢失)→ 吞数据;② 状态直接假报 0x04 幽灵态(回读对端端口=旧连接端口);③ 旧连接滞后 DISCONNECT 打到新监听 → relisten 毁掉新连接 → 425。
|
||||
- **芯片级规律**:CH395F 拆链收尾窗口**变长且不可观测**(状态显示 CLOSED 持续 1s+ 也不代表拆完,实测可 >2s 且时长不定)。窗口内重新 `open+listen` 三种形态:① 握手完成但 **CONNECT 事件未收到**(E14 复核:事件被台架 post-listen drain 或读清竞态吞掉,非芯片侧丢失)→ 吞数据;② 状态显示 0x04 幽灵态——旧连接拆链进行中,状态如实如此(E15 见下条;回读对端端口=旧连接端口);③ 旧连接滞后 DISCONNECT 打到新监听 → relisten 毁掉新连接 → 425。
|
||||
- **归因更正(E15,2026-10-14,官方出处)**:旧"芯片谎报 0x04"措辞推翻——芯片**如实报告旧连接拆链进行中**:① `FUN_PARA` bit3 `SOCKET_CLOSE=1` 为我方设置(`net_init` 版本门控 ≥0x46;§5.52②/§5.23:DISCONNECT/TIM_OUT 后芯片**按设计不动 Socket 状态与缓冲**,等 MCU 关闭——"保留现场"是自家配置的文档化行为);② 拆链走 TCP/IP 协议状态机(RFC 9293 §3.3.2 图 5;芯片状态码表见手册 §5.22,明言"其他状态芯片会自动处理并更新"):主动关闭 FIN_WAIT_1→FIN_WAIT_2→TIME_WAIT(对端同时关经 CLOSING、同时关闭直达),TIME_WAIT 驻留 2MSL 才落 CLOSED——这就是拆链窗;TIME_WAIT 的协议目的即"防旧连接迟到报文影响新连接"(RFC 9293 §3.3.2),正是"断连即重连被拒"的协议层根源。重传默认 12 次(§5.17),实测重传耗尽 ≥3s(`test/board/ch395f_test_task.c` 超时分类实测)——与拆链窗实测 >2s 同量级;③ WCH 官方博客(《【WCH以太网接口系列芯片---CH395】CH395 TCP Server模式下客户端重连设置》,cnblogs sw111/18451802)明言"客户端主动断连并立即重新发起连接请求,此时 CH395 会拒绝该连接请求,因此需要程序中做一些特殊处理",官方方案 = 关闭完成后再 open+listen——即我们的 settle/quarantine 等待;幽灵甄别是"强制放行"路径的护栏(防把拆链中的连接当新连接 accept)。状态面从来是真——I3 就此加强。
|
||||
- **已证伪的修复**(受控实验全部无效,列出防重踩):close 后固定沉降 100~3000ms / Socket 轮换(跨 socket 毒化)/ listen 后 close+重试 / 缓冲重分配 / close 前 TX 排空延时 / 禁用对账 tick(对账不是元凶)。
|
||||
- **定案一:双信源原则——中断只是快路径,状态寄存器才是真相**:
|
||||
1. **accept 路径内联探针**(`net_lost_connect_probe`,`net_accept_locked` 每 100ms 挂探针):芯片已 ESTABLISHED 而 CONNECT 事件未到 → 连续 2 轮去抖后补 accept;
|
||||
@@ -187,7 +188,7 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修
|
||||
- **修复(三阶段,`net_socket.c` + `lftpd.c`)**:
|
||||
1. **异常释放(TIMEOUT/RST)隔离**:`handle_timeout_event` 置 per-fd 隔离标记;`net_relisten_locked` 隔离期内持续排空芯片 RX(`net_drain_chip_rx()`)+ 主动 close + 排空该 socket 中断 + 等 `CLOSED/TCP_CLOSED`,满 `NET_RELISTEN_QUARANTINE_MS`(400ms) 才 open+listen;上限 `NET_RELISTEN_SETTLE_MS`(800ms) 强制放行——**必须小于** `NET_RELISTEN_DEADLINE_MS`(2000ms):取 2000ms 会耗尽重试预算 → give up → **监听器死亡**;
|
||||
2. **正常释放(FIN)沉降**:`handle_disconnect_event` 登记(仅 `recv_bytes != 0` 时——纯连接即关与只发不收免沉降保复连时序),relisten 前最短等 `NET_RELISTEN_GRACEFUL_MS`(**100ms**,实测悬崖:0/30ms 无效、100ms 稳定通过);日志区分 `relisten settle`/`relisten quarantine`;
|
||||
3. **应用侧硬化**:`net_sock_t.released_abnormal` 标记(TIMEOUT 置 1/DISCONNECT 置 0,新 fd 与新监听清零——新连接不得继承上一条的释放原因);`net_pending_tick` 断连了结按标记以 `NET_ERR_CONNRESET` 或 EOF(0) 回传;`lftpd receive_file` 错误路径 **unlink 截断文件** + STOR 回 **450**。
|
||||
3. **应用侧硬化**:`net_sock_t.released_abnormal` 标记(TIMEOUT 置 1/DISCONNECT 置 0,新 fd 与新监听清零——新连接不得继承上一条的释放原因);`net_pending_tick` 断连结束时按标记以 `NET_ERR_CONNRESET` 或 EOF(0) 回传;`lftpd receive_file` 错误路径 **unlink 截断文件** + STOR 回 **450**。
|
||||
- **协议边界(不可判定,留痕)**:FTP STOR 无长度声明,"在途 FIN 关闭"与"正常完成"**逐字节不可区分** → 优雅中止仍回 226;仅 RST/掉链(芯片 TIMEOUT)可识别 → 450。FIN 边界由客户端长度校验(SIZE/RETR 往返)兜底。
|
||||
- **回归基线**:`--check-abort` RST **5/5**(450)+ FIN **5/5**(226 边界不变);followup 全过;正常往返 freeze SHA-256 全 MATCH;`:7003` sink 复用 RST×4/FIN×4 全过;`:8888` 真实收包写 RTC 成功。
|
||||
- **与 TC-NET-604 的关系**:本条是独立、已可信复现并已修复的真实缺陷;TC-NET-604 已结案为非缺陷(判读教训见第六章台架纪律)。现场"上传失败/文件不完整/像卡死"类报告优先用本条判据复核。
|
||||
|
||||
Reference in New Issue
Block a user