diff --git a/App/sync/time_sync.c b/App/sync/time_sync.c
index 0939f36b..36c41f5f 100644
--- a/App/sync/time_sync.c
+++ b/App/sync/time_sync.c
@@ -124,10 +124,10 @@ static int time_sync_open_listener(void) {
* 入口参数:conn_sock - 已建立的连接 Socket 描述符(单连接模式下即监听 Socket 自身)
* 返回值:无
* 函数说明:pending 读取 PC 发来的时间包("TIME"+uint32 大端 本地时间),解析后
- * 写回 RTC。**不在此 close**(自愈收敛阶段1-5:单连接模式下 conn_sock 即
+ * 写回 RTC。**不在此 close**(单连接模式下 conn_sock 即
* 监听 fd,close 连监听一起毁、重建窗 ~0.3-1s;net 层 auto_relisten
* 已把同 fd 收回监听,外层直接续 accept——lftpd 样板)。每次等待 =
- * 一次挂起请求(netTask 零占用,L3 pending 化),单次超时 = 剩余总预算
+ * 一次挂起请求(netTask 零占用),单次超时 = 剩余总预算
* (≤30s,语义与旧 30s 阻塞一致);跨调用累积,容忍时间包被 TCP 分段送达
*/
static void time_sync_handle_conn(int conn_sock) {
@@ -180,14 +180,14 @@ static void time_sync_handle_conn(int conn_sock) {
DBG_INFO("time_sync: wall clock updated (epoch=%lu)", (unsigned long)epoch);
}
} else {
- /* 自愈收敛阶段 3(E-02 收口,《网络自愈架构设计》§5.2 E13):本层放弃会话 → 主动拆除。
+ /* E-02 收口(docs/网络/网络自愈架构设计.md §5.2 E13):本层放弃会话 → 主动拆除。
* 挂起但 TCP 仍活的客户端(PC 内核自动 ACK keepalive,芯片僵尸判不死)
* 会永久占住单连接监听;EOF 路径 drop 幂等返 0(net 层已 relisten)。
* 模拟器多连接后端 conn≠ls,由 sim 实现按数据 fd 关闭。 */
(void)net_drop_conn(conn_sock);
}
- /* 自愈收敛阶段1-5:原 net_close(conn_sock) 已删——单连接模式 conn==监听 fd,
+ /* 原 net_close(conn_sock) 已删——单连接模式 conn==监听 fd,
* close 毁监听(AGENTS 模块约定:EOF 时应用层不得 close);net 层
* auto_relisten 已把同 fd 收回 LISTENING,外层循环直接续 accept。
* 模拟器多连接后端 conn≠ls,由调用方(外层)负责关数据 fd。 */
@@ -199,7 +199,7 @@ static void time_sync_handle_conn(int conn_sock) {
* 返回值:无
* 限定条件:net_init() 已由 netTask 完成;netTask 正在处理消息队列
* 函数说明:等待网络栈就绪后,循环:建监听 -> 等连接 -> 处理 -> **同 fd 续
- * accept**(自愈收敛阶段1-5:会话收尾不 close,监听跨会话常驻,仅网络
+ * accept**(会话收尾不 close,监听跨会话常驻,仅网络
* 代次变化时重建)。全部 socket 操作经消息队列由 netTask 串行执行,
* 与本任务/其它网络任务并发安全。
*/
@@ -226,7 +226,7 @@ void time_sync_task(void *argument) {
}
}
- /* 监听 fd 跨会话常驻(自愈收敛阶段1-5):仅首次/网络代次变化/被毁时重建 */
+ /* 监听 fd 跨会话常驻:仅首次/网络代次变化/被毁时重建 */
int ls = -1;
for (;;) {
@@ -266,7 +266,7 @@ void time_sync_task(void *argument) {
}
time_sync_handle_conn(conn);
- /* 自愈收敛阶段1-5:会话收尾不 close(单连接模式 conn==ls,close 连监听
+ /* 会话收尾不 close(单连接模式 conn==ls,close 连监听
* 一起毁);net 层 auto_relisten 已把同 fd 收回 LISTENING,下轮循环
* 直接续 accept。模拟器多连接后端(conn≠ls)才关数据 fd。 */
if (conn != ls) {
diff --git a/App/task/iec104_serv.c b/App/task/iec104_serv.c
index 0b9fc928..6205a0a8 100644
--- a/App/task/iec104_serv.c
+++ b/App/task/iec104_serv.c
@@ -224,11 +224,11 @@ static void s_serv_poll(void) {
if (s_client < 0) {
int c;
- /* 自愈收敛阶段1(F-NET-4):门控从 net_can_accept(只认 ESTABLISHED,已退役)
+ /* F-NET-4:门控从 net_can_accept(只认 ESTABLISHED,已退役)
* 迁 net_listener_armed——旧门控把 LISTENING 窗口全挡掉,:2404 的丢
* CONNECT 探针(net_accept_locked 内)因此从不运行(与已修的 :7003
* 同型);armed=ESTABLISHED 待 accept / LISTENING /
- * CLOSED+auto_relisten+残留(ESTABLISHED 放行为 2026-10-13 台架补齐:
+ * CLOSED+auto_relisten+残留(ESTABLISHED 放行由台架实证补齐:
* 漏它则本任务 200ms 周期抢不过对账② ~400ms 去抖、整场饿死) */
if (net_listener_armed(s_listen) == 0) {
if (net_get_sock(s_listen) == NULL) {
@@ -258,7 +258,7 @@ static void s_serv_poll(void) {
s_liveness_ctr = 0u;
}
- /* 自愈收敛阶段 1.5:DONTWAIT 转正为具名 API net_recv_nb——本任务在 IWDG
+ /* DONTWAIT 已转正为具名 API net_recv_nb——本任务在 IWDG
* required_mask 内且有 100ms 周期职责(喂狗/链路节拍/上送),禁止无限
* 阻塞 recv(文档契约见 net_socket.h net_recv 注释),非阻塞轮询是义务 */
n = net_recv_nb(s_client, s_rx, (int)sizeof(s_rx));
diff --git a/Drivers/BSP/CH395F/ch395f.c b/Drivers/BSP/CH395F/ch395f.c
index 38f2832a..133e5759 100644
--- a/Drivers/BSP/CH395F/ch395f.c
+++ b/Drivers/BSP/CH395F/ch395f.c
@@ -1410,13 +1410,12 @@ void ch395f_get_unreach_info(uint8_t *p_buf) {
}
/* ============================================================================
- * 自愈收敛阶段 2 驱动原语(设计 docs/网络/网络自愈架构设计.md §4.4;
- * 形态定案锚点 同文档 §5.2 E2/E3/E4,2026-10-13 台架实证)
- * 只加不接线:net_poll 全量刷新 / 两级预清改调用 / 链路 feed 接线 = 阶段 3。
+ * 自愈驱动原语(设计 docs/网络/网络自愈架构设计.md §4.4;
+ * 形态定案锚点 同文档 §5.2 E2/E3/E4)
* ========================================================================== */
/* 每 Socket 状态缓存(刷新点:主动回读 ch395f_get_sock_state;
- * 阶段 3 起 net_poll 每轮对在用 Socket 各刷一次——E2 实测 18.1us/次,
+ * 以及 net_poll 每轮对在用 Socket 各刷一次——E2 实测 18.1us/次,
* 4 监听 ~72us 远小于 1ms 轮询周期) */
static ch395f_sock_state_t g_sock_state[8];
@@ -1451,7 +1450,7 @@ static uint16_t g_p2_faults;
static uint8_t g_p2_fault_logged;
/*
- * 函数功能:阶段 2 原语边界异常留痕(首次 DBG_ERROR,之后只计数)
+ * 函数功能:驱动原语边界异常留痕(首次 DBG_ERROR,之后只计数)
* 入口参数:tag - 出错点短标签 const char*
*/
static void ch395f_p2_fault_note(const char *tag) {
@@ -1481,7 +1480,7 @@ static void ch395f_rst_rx_discard(uint8_t sn, uint16_t len) {
/*
* 函数功能:主动回读本 Socket 状态并刷新驱动缓存(缓存刷新点之一,另一个
- * 是阶段 3 的 net_poll 每轮全量刷新)
+ * 是 net_poll 每轮全量刷新)
* 入口参数:sn - Socket 索引 uint8_t 0 - 7
* 返回值:类型化状态;sn>7 返回 {CLOSED, 哨兵} 并计一次边界 fault
* 限定条件:芯片已初始化;netTask 上下文(与其它 CH395F 事务串行)
@@ -1728,7 +1727,7 @@ int ch395f_cmd_done(uint8_t sn, ch395f_done_what_t what) {
}
/*
- * 函数功能:喂全局中断位(阶段 3 接线:net_poll 读全局后喂 bit0~3;
+ * 函数功能:喂全局中断位(net_poll 读全局后喂 bit0~3;
* bit2 PHY_CHANGE 置位时重读 PHY 更新 link_up 缓存 + 变化计数)
* 入口参数:glob_bits - 全局中断状态低字节语义位图 uint16_t
* 返回值:无
diff --git a/Drivers/BSP/CH395F/ch395f.h b/Drivers/BSP/CH395F/ch395f.h
index 4f0977e1..06972d67 100644
--- a/Drivers/BSP/CH395F/ch395f.h
+++ b/Drivers/BSP/CH395F/ch395f.h
@@ -159,9 +159,9 @@ extern "C" {
#define CH395F_CMD_SET_KEEPALIVE_INTVL 0x57U
#define CH395F_CMD_SET_KEEPALIVE_CNT 0x58U
#define CH395F_CMD_SET_KEEPALIVE_SN 0x59U
-/* 自愈收敛阶段0-9:EEPROM(0xE9..0xEB)/GPIO(0xEC/0xED)/SLEEP(0x03) 命令宏与
+/* EEPROM(0xE9..0xEB)/GPIO(0xEC/0xED)/SLEEP(0x03) 命令宏与
* GET_GLOB_INT_STATUS(0x29,只有 2 字节版 0x19 有实现) 死声明已删除——
- * 全仓零引用核实于 2026-10-13;需要时按手册 §5.x 重新登记 */
+ * 全仓零引用已核实;需要时按手册 §5.x 重新登记 */
/*
* CH395 命令执行状态码(CMD_GET_CMD_STATUS 返回值,与芯片手册一致)
@@ -197,7 +197,7 @@ extern "C" {
/*
* 功能参数标志位(CMD_SET_FUN_PARA,§5.52 表)
- * bit3 SOCKET_CLOSE 需芯片版本 ≥0x46(版本门控见 net_init,自愈收敛阶段0-6);
+ * bit3 SOCKET_CLOSE 需芯片版本 ≥0x46(版本门控见 net_init);
* bit1 TCP_SERVER 多连接模式——本项目单连接模型不用(红线,勿置 1);
* bit0 保留必须为 0(TRAP-NET-15 定案更正:0x09→0x08)
*/
@@ -237,7 +237,7 @@ extern "C" {
/*
* 全局中断状态位低字节(bit0~3 全局事件;§5.16——2 字节版
* GET_GLOB_INT_STATUS_ALL 低字节同语义。Socket 事件位(bit4 起)由
- * net_poll 按 1U<<(i+4) 计算,旧 SOCK0..3 单列宏已删(自愈收敛阶段0-5,
+ * net_poll 按 1U<<(i+4) 计算,旧 SOCK0..3 单列宏已删(
* 全仓零引用;1 字节版命令 0x29 亦已随死声明清理)
*/
#define CH395F_GINT_STAT_UNREACH 0x01U
@@ -377,7 +377,7 @@ ch395f_status_t ch395f_init(void);
/*
* 函数功能:设置 CH395 IP 地址(点分十进制序:p_ip[0]=第一段,如 192;
- * 自愈收敛阶段0-7 更正旧注"低字节在前"——调用方 parse_ip_string 产出
+ * 字节序曾误注为"低字节在前"——调用方 parse_ip_string 产出
* 点分数组直传、芯片按首段在前接受,台架多年 IP 生效实证)
* 入口参数:p_ip - 4 字节 IP 地址指针 uint8_t*
* 限定条件:指针非空,芯片已初始化
@@ -482,7 +482,7 @@ void ch395f_set_proto_type(uint8_t sock, uint8_t proto);
* p_ip - 4 字节目标 IP 指针 uint8_t*
* 限定条件:指针非空
* 函数说明:字节序与 ch395f_get_remot_ipp 输出一致(net_connect 直传其输出,
- * 台架出站连接往返实证;自愈收敛阶段0-7 补注)
+ * 台架出站连接往返实证)
*/
void ch395f_set_des_ip(uint8_t sock, uint8_t *p_ip);
@@ -753,10 +753,9 @@ void ch395f_set_ttl(uint8_t sock, uint8_t ttl);
void ch395f_get_unreach_info(uint8_t *p_buf);
/*
- * 函数声明区 - 自愈收敛阶段 2 驱动原语(设计 docs/网络/网络自愈架构设计.md §4.4;
+ * 函数声明区 - 自愈驱动原语(设计 docs/网络/网络自愈架构设计.md §4.4;
* 形态定案锚点 同文档 §5.2 E2/E3/E4)
- * ⚠ 阶段 2 = 只加不接线:net_poll 全量刷新、三处两级预清改调用、链路 feed 接线
- * 均在阶段 3 收敛时进行。E2 实测:单次状态回读 18.1us;LISTEN 可见延迟 100% <=10ms。
+ * E2 实测:单次状态回读 18.1us;LISTEN 可见延迟 100% <=10ms。
*/
/* 类型化状态回读 + 每 Socket 缓存(F-DRV-1/3) */
@@ -769,7 +768,7 @@ typedef struct {
/*
* 函数功能:主动回读本 Socket 状态并刷新驱动缓存(缓存刷新点之一,另一个
- * 是阶段 3 的 net_poll 每轮全量刷新)
+ * 是 net_poll 每轮全量刷新)
* 入口参数:sn - Socket 索引 uint8_t 0 - 7
* 返回值:类型化状态;sn>7 返回 {CLOSED, 哨兵} 并计一次边界 fault uint8_t×2
* 限定条件:芯片已初始化;netTask 上下文(与其它 CH395F 事务串行)
@@ -838,7 +837,7 @@ typedef enum {
*/
int ch395f_cmd_done(uint8_t sn, ch395f_done_what_t what);
-/* 链路态缓存(F-DRV-13;接线在阶段 3:net_poll 读全局后喂 bit0~3) */
+/* 链路态缓存(F-DRV-13;net_poll 读全局后喂 bit0~3) */
/*
* 函数功能:喂全局中断位(net_poll 读 GET_GLOB_INT_STATUS_ALL 后调用;
diff --git a/Drivers/BSP/NET/README.md b/Drivers/BSP/NET/README.md
index 654a0936..35c87e94 100644
--- a/Drivers/BSP/NET/README.md
+++ b/Drivers/BSP/NET/README.md
@@ -18,18 +18,18 @@
### 阻塞模型(挂起 recv / send / pending)
-- 一期(RECV)已落地:`net_recv` 等支持带超时的阻塞(`pending` 挂起 + tick 唤醒)
-- 二期(SEND)已落地:`net_send` 快路径有界自旋(`NET_SEND_SPIN_MS` = 2ms),超预算登记
+- **recv 挂起(pending)已落地**:`net_recv` 阻塞不占 netTask——登记挂起请求 + 每轮 tick 唤醒
+- **send 挂起已落地**:`net_send` 快路径有界自旋(`NET_SEND_SPIN_MS` = 2ms),超预算登记
挂起 send,由 `net_send_pending_tick` 按主循环节拍分块推进;netTask 最坏停滞由 1s 压到 2ms
-- 二期(ACCEPT)待做 → `docs/任务规划/登记簿.md` N-4
+- **ACCEPT 尚未挂起化**(调用方轮询问题,不是队头阻塞)→ `docs/任务规划/登记簿.md` N-4
- 架构细节:`docs/网络/Net层阻塞模型架构设计.md`
-### 周期对账自愈(TRAP-NET-17 修复;自愈收敛阶段 3 合并 pump)
+### 周期对账自愈(TRAP-NET-17 修复;对账与监听重建合一)
-- `net_reconcile_tick()`(100ms,自愈收敛阶段 3 起判据读**驱动状态缓存**——唯一
+- `net_reconcile_tick()`(100ms,判据读**驱动状态缓存**——唯一
刷新点 `net_poll` 每轮全量刷新,E2 实测单次回读 18.1us)修正失同步:
本层 ESTABLISHED 而芯片已断 → 断开;本层 LISTENING 而芯片已握手 → 补 accept;
- 本层 LISTENING/CLOSED(监听型 fd)而芯片监听消失 → 重建(原 pump③④);
+ 本层 LISTENING/CLOSED(监听型 fd)而芯片监听消失 → 重建(形态③④);
本层 CLOSED 而芯片在听 → 只重同步(U11);芯片静默回收 → RESYNC(⑤)
- **分级去抖(②=1 轮 / ③④⑤=2 轮 / ①=4 轮)+ 冷却(1s)**——监听失位的重建
动作**必须去抖**,否则"刚 listen 未生效"瞬态会反复重建直至
@@ -39,7 +39,8 @@
### 异常复监听隔离(TRAP-NET-18 修复)
- 异常释放(TIMEOUT)的 socket 复监听前隔离:排空 RX 残渣 + 主动 close + 等 CLOSED
-- 400ms 起、800ms 上限强制放行(上限须 < 重试放弃时限 2000ms)
+- 400ms 起;**800ms 上限只留痕、不强行放行**(芯片未回 CLOSED 时 open 注定吃 USE 错,
+ 真正兜底 = D10 预算到期 + 对账形态④ 重新登记)
- 仅异常路径生效,正常 FIN 路径零改动
### 错误码(E-03 修复)
@@ -69,5 +70,5 @@
- 上层用法:`docs/网络/BSD_Socket_API_使用指南.md`
- 内部调用关系:`docs/网络/BSD_Socket_API_函数调用关系.md`
- 架构设计:`docs/网络/Net层阻塞模型架构设计.md`(阻塞等待 / pending 请求模型)
-- 二期设计基线:`docs/任务规划/基线与待确认.md` §2.1
+- ACCEPT 挂起化设计基线:`docs/任务规划/基线与待确认.md` §2.1
- 测试:`docs/测试说明/CH395F_Test_Guide.md`、`docs/测试说明/FTP_Test_Guide.md`
diff --git a/Drivers/BSP/NET/lftpd/lftpd.c b/Drivers/BSP/NET/lftpd/lftpd.c
index 964a9e1b..d3719416 100644
--- a/Drivers/BSP/NET/lftpd/lftpd.c
+++ b/Drivers/BSP/NET/lftpd/lftpd.c
@@ -467,7 +467,7 @@ static int cmd_pasv(lftpd_client_t *client, const char *arg)
client->data_socket = -1;
}
- /* 自愈收敛阶段1-6:原 3×200ms 应用层重试已删(lftpd_inet_listen 单次化)——
+ /* 原 3×200ms 应用层重试已删(lftpd_inet_listen 单次化)——
* 失败直接 425,FTP 客户端自带传输重试语义;服务端不再叠阻塞重试 */
int listener_socket = lftpd_inet_listen(0);
if (listener_socket < 0) {
diff --git a/Drivers/BSP/NET/lftpd/lftpd_inet.c b/Drivers/BSP/NET/lftpd/lftpd_inet.c
index ca0f2f03..7a073ad0 100644
--- a/Drivers/BSP/NET/lftpd/lftpd_inet.c
+++ b/Drivers/BSP/NET/lftpd/lftpd_inet.c
@@ -31,7 +31,7 @@ void lftpd_task_wd_beat(void)
int lftpd_inet_listen(int port)
{
- /* 自愈收敛阶段1-6:应用层阻塞重试退役(原 5 轮 500ms/2000ms,最坏 10s 阻塞
+ /* 应用层阻塞重试退役(原 5 轮 500ms/2000ms,最坏 10s 阻塞
* ftpTask)——TRAP-NET-09 的启动形态已由就绪屏障消除(FTP_READY 等 NET_READY,
* lftpd_start 外层本就有监听失败重建循环);芯片忙窗失败属罕见,PASV 数据
* 监听交给 FTP 客户端自身的重试语义(主流 FTP 服务器 passive listen 失败
diff --git a/Drivers/BSP/NET/net_config.h b/Drivers/BSP/NET/net_config.h
index 92302d37..7cfbbe28 100644
--- a/Drivers/BSP/NET/net_config.h
+++ b/Drivers/BSP/NET/net_config.h
@@ -11,10 +11,10 @@
* 实际生效值 60s/5s/3 并在 net_socket.c 引用(原宏为无效遗留值)
* 2026-09-22 新增 NET_RETRANS_COUNT/NET_RETRANS_PERIOD_MS(P0-a:重传周期
* 500→200ms 治 D1 症状),net_socket.c 的字面量 8/500 移入本文件
- * 2026-10-13 自愈收敛阶段 1.5:删除 NET_RECV_TIMEOUT_MS(30s 默认预算)——
+ * 2026-10-13 recv 语义主流化:删除 NET_RECV_TIMEOUT_MS(30s 默认预算)——
* recv 超时改为调用方策略(net_recv 无限阻塞 /
* net_recv_to 显式预算 / net_select 首选),API 不再强加默认值
- * 2026-10-14 二期 SEND pending:新增 NET_SEND_SPIN_MS(发送快速路径有界自旋
+ * 2026-10-14 SEND pending 化:新增 NET_SEND_SPIN_MS(发送快速路径有界自旋
* 预算,超预算转挂起登记)——netTask 发送最坏停滞由
* NET_SEND_TIMEOUT_MS(1s)压到本值,健康路径行为不变
*/
@@ -36,7 +36,7 @@ extern "C" {
/*
* 超时时间配置(毫秒)
* 0 = 无限等待(阻塞模式)
- * 注:recv 侧无编译期默认预算(自愈收敛阶段 1.5 删除 NET_RECV_TIMEOUT_MS)——
+ * 注:recv 侧无编译期默认预算(NET_RECV_TIMEOUT_MS 已删除)——
* 超时是调用方策略:net_recv() 无限阻塞、net_recv_to() 显式预算、
* net_select() 首选机制。
*/
diff --git a/Drivers/BSP/NET/net_events.c b/Drivers/BSP/NET/net_events.c
index c7c64c4f..c52d9bbe 100644
--- a/Drivers/BSP/NET/net_events.c
+++ b/Drivers/BSP/NET/net_events.c
@@ -79,7 +79,7 @@ void net_bench_cmd_log(char tag, int sock, int detail) {
#endif
-/* 对账+pump 合并周期(自愈收敛阶段 3:原 200ms 对账 / 100ms pump 合一) */
+/* 对账与监听重建合一周期(原 200ms 对账 / 100ms 重建合一) */
#define NET_RECONCILE_PERIOD_MS 100U
/* 同一 socket 两次动手的最小间隔(防抖成风暴) */
#define NET_RECONCILE_COOLDOWN_MS 1000U
@@ -242,7 +242,7 @@ void handle_disconnect_event(net_sock_t *p_sock) {
* 而 TCP 槽 0x00 且本层 ESTABLISHED = 逻辑矛盾(活连接应 0x04):陈旧 DISCONNECT
* (拆链完成滞后锁存,TRAP-NET-12)与"真断开恰被旧拆链覆写槽"在 D20 上不可分辨
* (结构性失明,F-NET-15 Q1)。处置:不吃边沿,置可疑电平返回,交对账①
- * (ESTABLISHED + 芯片 TCP==CLOSED)电平去抖再入本函数(自愈收敛阶段 3 合并
+ * (ESTABLISHED + 芯片 TCP==CLOSED)电平去抖再入本函数(对账与重建合一
* 后=4×100ms,NET_RECON_STABLE_DEATH,让位租约 400ms 不变 + 冷却):
* flag==1 旁路本门 → 正常释放;连接真活则租约内
* 会话正常完成(风暴链掐断,E9a n30 0/50 实证)。
@@ -426,7 +426,7 @@ static void fire_event(net_sock_t *p_sock, net_event_t event) {
* 返回值:无
* 限定条件:netTask 上下文(net_accept_locked 内调用,其开头已 net_poll(),
* 驱动缓存新鲜度有保证)
- * 函数说明:1. 自愈收敛阶段 3(F-MS-9):判据改读**驱动状态缓存**(零 SPI;唯一
+ * 函数说明:1. F-MS-9:判据改读**驱动状态缓存**(零 SPI;唯一
* 刷新点=net_poll),**探针去抖删除**——原去抖防的两类瞬态已
* 结构性消除:"刚 listen 尚未生效"显示 TCP=00 永不命中 ESTABLISHED
* 门(E2:LISTEN 可见 100% <=10ms 无长尾);拆链窗假 ESTABLISHED
@@ -456,7 +456,7 @@ void net_lost_connect_probe(net_sock_t *p_sock) {
p_sock->lost_conn_seen = 0U;
return;
}
- /* 自愈收敛阶段 3(F-MS-9):去抖已删(判据读缓存,两类瞬态结构性消除,
+ /* F-MS-9:去抖已删(判据读缓存,两类瞬态结构性消除,
* 见函数头注释 1);lost_conn_seen 仅作 M3 可疑电平标志用,动手前清零 */
p_sock->lost_conn_seen = 0U;
/* 留痕:事件未收到自愈命中 */
@@ -468,7 +468,7 @@ void net_lost_connect_probe(net_sock_t *p_sock) {
/*
- * 函数功能:芯片/本层 socket 状态周期对账(TRAP-NET-17 事件面未收到自愈,E14 复核:非芯片丢事件是被读清消费)+ 监听器 pump(N-32 P2)—— 自愈收敛阶段 3 合并为单一 100ms tick(docs/网络/网络自愈架构设计.md §4.1)
+ * 函数功能:芯片/本层 socket 状态周期对账(TRAP-NET-17 事件面未收到自愈,E14 复核:非芯片丢事件是被读清消费)+ 监听器重建 —— 合并为单一 100ms tick(docs/网络/网络自愈架构设计.md §4.1)
* 入口参数:无
* 出口参数:无
* 返回值:无
@@ -556,7 +556,7 @@ void net_reconcile_tick(void) {
stable_need = NET_RECON_STABLE_DEATH;
} else if ((c.open != CH395F_SOCKET_CLOSED) &&
(c.tcp_state == CH395F_TCP_LISTEN)) {
- /* ⑤(F-NET-2 缺口,自愈收敛阶段1):芯片静默回收——本层 ESTABLISHED
+ /* ⑤(F-NET-2 缺口):芯片静默回收——本层 ESTABLISHED
* 而芯片已自行把 Socket 收回 LISTEN(0x05/0x01,**不发 DISCONNECT
* 中断**,run8 台架实证)→ RESYNC 语义 */
bad = 5;
@@ -633,7 +633,7 @@ void net_reconcile_tick(void) {
handle_disconnect_event(p_sock);
} else if (bad == 5) {
/* 类型⑤:静默回收 → RESYNC 语义(原 NET_MSG_RESYNC_LISTEN 分派体
- * 原样并入,自愈收敛阶段1;纪律同 U11:**不发任何芯片命令**——
+ * 原样并入;纪律同 U11:**不发任何芯片命令**——
* 重下发 close/重监听会摧毁芯片侧健康监听器 → 端口永拒,
* run8 实证 170s 拒绝) */
DBG_INFO("sock%d reconcile: chip re-listened w/o DISCONNECT (0x%02X/0x%02X) -> resync",
diff --git a/Drivers/BSP/NET/net_listener.c b/Drivers/BSP/NET/net_listener.c
index f39bd20e..4afc48fe 100644
--- a/Drivers/BSP/NET/net_listener.c
+++ b/Drivers/BSP/NET/net_listener.c
@@ -8,8 +8,8 @@
* 作者:王建锋
* 创建日期:2026-10-12
* 修改记录:
- * 2026-10-12 王建锋 从 net_socket.c 整体抽出监听生命周期(N-32 §10 模块化)
- * 2026-10-13 王建锋 自愈收敛阶段 3:pump 并入 net_reconcile_tick
+ * 2026-10-12 王建锋 从 net_socket.c 整体抽出监听生命周期(模块化)
+ * 2026-10-13 王建锋 监听重建 pump 并入 net_reconcile_tick(单一决策者)
* 2026-10-14 王建锋 单一所有者收口(I2 加强):try 当场动作 + 四处调用点各自带门
* → request 登记意图 + tick 唯一执行;沉降留痕改"只留首末"
* 2026-10-14 王建锋 注释精简(−45%,代码零改动):陷阱详情沉淀进架构设计文档,
diff --git a/Drivers/BSP/NET/net_listener.h b/Drivers/BSP/NET/net_listener.h
index 2ec9daab..e88464e8 100644
--- a/Drivers/BSP/NET/net_listener.h
+++ b/Drivers/BSP/NET/net_listener.h
@@ -114,7 +114,7 @@ void net_listener_clear(int sockfd);
*/
void net_listener_tick(void);
-/* 自愈收敛阶段 3:原 `net_listener_pump`(周期重建决策者)已并入 net_socket.c 的
+/* 原 `net_listener_pump`(周期重建决策者)已并入 net_events.c 的
* net_reconcile_tick 单一 100ms tick(判据读驱动状态缓存,《网络自愈架构设计》§4.1)。
* 本模块的 net_listener_tick 是执行者,登记方 = reconciler 形态③④ + 各消费者路径
* (accept / recv EOF / drop_conn / 挂起 recv 断连),全部走 net_listener_request。 */
diff --git a/Drivers/BSP/NET/net_pending.h b/Drivers/BSP/NET/net_pending.h
index 96e5176c..2251eca2 100644
--- a/Drivers/BSP/NET/net_pending.h
+++ b/Drivers/BSP/NET/net_pending.h
@@ -151,8 +151,8 @@ int net_pending_active(int sockfd);
* ⚠ **调用方铁律**:`sent < len` 是调用侧必须保证的**前置条件**,不是本函数的
* 兜底。`sent == len` 表示"全部字节已交付芯片、无剩余块可挂起"——此时超预算
* 应**直接返回 sent**,不得调用本函数;本函数会按参数非法拒绝并回 -1,
- * 把一次完整发送报成失败(net_send_locked 末块等待 2026-10-14 台架实证,
- * 见《Net层阻塞模型架构设计.md》 §9.1)。
+ * 把一次完整发送报成失败(net_send_locked 末块等待即此情形,
+ * 见《Net层阻塞模型架构设计.md》 §5.5 末块边界)。
*/
int net_send_pending_register(int sockfd, const void *buf, int len, int sent,
TaskHandle_t caller);
diff --git a/Drivers/BSP/NET/net_socket.c b/Drivers/BSP/NET/net_socket.c
index 6af72cd4..07009d4d 100644
--- a/Drivers/BSP/NET/net_socket.c
+++ b/Drivers/BSP/NET/net_socket.c
@@ -45,7 +45,7 @@
* 假设=「RX 非空但 readable=0」(RECV_OK 事件未收到的数据面形态)在生产真实
* 发生。置 1 烧实验固件:net_poll 每 32 轮对在用 fd 发一条 GET_RECV_LEN,
* 命中计数 + 低频日志(每小时 ≤1 条防刷屏);跑 24h 常态 + E1 压测。
- * 判据:命中 >0 → 阶段 3 项 4(U15 内化)立项;=0(含压测)→ 上层 32 轮
+ * 判据:命中 >0 → U15(数据面失配)内化立项;=0(含压测)→ 上层 32 轮
* 盲收保留现状、U15 登记观察项。**默认 0 = 产品构建零成本(编译剔除)**。
* E6 已裁决(2026-10-13,《网络自愈架构设计》§5.2 E6):命中=0(含压测)→ 项 4 不立项,维持默认 0。
* ⚠ 必须定义在文件最前部:bring_up 的 armed 日志与 net_poll 探测环两处消费。 */
@@ -240,7 +240,7 @@ static bool s_mac_is_zero(const uint8_t *p_mac) {
/* 复位芯片 */
ch395f_reset();
- /* 芯片版本留痕 + 0-6 版本门控(自愈收敛阶段0,§5.52 表):bit3 SOCKET_CLOSE
+ /* 芯片版本留痕 + 版本门控(CH395F 手册 §5.52 表):bit3 SOCKET_CLOSE
* 需芯片 ≥0x46、TCP_SERVER 需 ≥0x44。本台架芯片实测 ≥0x46(TRAP-NET-20 定案
* SOCKET_CLOSE 实际生效);门控防批次漂移——版本不足则**拒置 bit3**
* (退回芯片自动关断语义)并 [ERR] 留痕,不静默带病运行 */
@@ -414,7 +414,7 @@ int net_poll(void) {
/* 读取全局中断状态(2 字节):读操作本身会清除 CH395F 中断并使其拉高 INT#,
* 若此后仍有新中断,CH395F 会再次拉低 INT#,由下一次 net_poll() 处理 */
int_status = ch395f_get_glob_int_status_all();
- /* 自愈收敛阶段 3(F-DRV-13 接线):把全局位喂驱动——bit2 PHY_CHANGE 时驱动
+ /* F-DRV-13 接线:把全局位喂驱动——bit2 PHY_CHANGE 时驱动
* 重读 PHY 更新 link_up 缓存 + 变化计数,net 层在 reconciler 订阅留痕
* (运行期拔网线不再静默;不自动重配,重配仍走 UI/配置引擎路径) */
ch395f_glob_event_feed(int_status);
@@ -496,7 +496,7 @@ int net_poll(void) {
/* 因此每次 net_poll() 只处理“一批”,剩余数据在后续轮询中读取 */
} while (0);
- /* 自愈收敛阶段 3(§5 项 1 基础):每轮全量刷新在用 Socket 的驱动状态缓存
+ /* 单一事实源(I1):每轮全量刷新在用 Socket 的驱动状态缓存
* (唯一刷新点;E2 实测单次回读 18.1us,在用 5~6 个 ≈100us ≪ 1ms 轮询
* 周期)。reconciler / 探针 / 镜像降级决策点一律读缓存(
* ch395f_sock_state_cached),不再各自发状态 SPI。放在中断处理之后:
@@ -504,7 +504,7 @@ int net_poll(void) {
for (int i = 0; i < NET_MAX_SOCKETS; i++) {
if (s_net_socks[i].in_use != 0U) {
(void)ch395f_get_sock_state((uint8_t)i);
- /* 自愈收敛阶段 3(E-02 收口):驱动 sock_reset 状态机在 net_poll 步进
+ /* E-02 收口:驱动 sock_reset 状态机在 net_poll 步进
* (驱动头注释契约"在 net_poll 或测试循环里周期调用")。IDLE 槽位
* 纯 RAM 判返回 0 无 SPI;仅活跃复位机做短 SPI。作用:同步包装
* (net_sock_reset_sync)600ms 放弃后复位机不搁浅,芯片条件满足
@@ -572,7 +572,7 @@ int net_poll(void) {
* 30ms 节奏 / 2s 预算;门集与"芯片在听就不 open"(U12 防护)都在这一处
* 3. net_pending_tick:按节拍结束挂起 recv(数据/到点[仅显式预算]/断连)
* 4. net_send_pending_tick:按节拍推进挂起 send(断连/推进/写完/无进展超时)
- * 5. net_reconcile_tick(TRAP-NET-17 + pump 合并,自愈收敛阶段 3):100ms 对账
+ * 5. net_reconcile_tick(TRAP-NET-17 事件面自愈 + 监听重建合一):100ms 对账
* ①②⑤ + ③④ 重建 + U11 重同步,判据读驱动状态缓存;去抖分级 + 1s 冷却
* 6. net_msg_t 按值入队 → 结果只能搭任务通知值回传(32 位整体即结果,不做位域压缩)
* 7. RECV / SEND 特例:超预算登记 pending 哨兵 NET_PENDING_REGISTERED(0x70000000),
@@ -593,7 +593,7 @@ void net_process_messages(void) {
net_pending_tick(s_net_socks);
/* 挂起 send 推进与完成检查(SEND pending):断连 / 推进写块 / 写完 / 无进展超时 */
net_send_pending_tick(s_net_socks);
- /* TRAP-NET-17 状态对账 + 监听器 pump(自愈收敛阶段 3 合并单一 100ms tick):
+ /* TRAP-NET-17 状态对账 + 监听重建合一(单一 100ms tick):
* 判据读驱动缓存(net_poll 每轮全量刷新),①②⑤ 失同步 + ③④ 重建 +
* U11 重同步全部在此(本层唯一周期重建入口,N-32 P2 单一所有者) */
net_reconcile_tick();
@@ -690,7 +690,7 @@ void net_process_messages(void) {
break;
}
case NET_MSG_DROP_CONN: {
- /* 自愈收敛阶段 3(E-02 收口):应用放弃会话的主动拆除(拆连接、保监听) */
+ /* E-02 收口:应用放弃会话的主动拆除(拆连接、保监听) */
net_sock_t *p_sock = net_get_sock(msg.sockfd);
if (p_sock == NULL) {
/* 描述符无效 → BADF */
@@ -709,7 +709,7 @@ void net_process_messages(void) {
}
break;
}
- /* 自愈收敛阶段1(F-NET-3):NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 两个
+ /* F-NET-3:NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 两个
* 手动自愈消息已删——RESYNC 语义并入对账⑤(net_reconcile_tick),
* RELISTEN 与 reconciler/D10 完全重叠且对健康监听器是 footgun;
* 生产调用者本就为零(仅 test/board Phase 13,已改由自动机制接管) */
@@ -833,7 +833,7 @@ int net_socket(int domain, int type) {
p_sock->send_ready = 1;
/* 无可读数据 */
p_sock->readable = 0;
- /* 自愈收敛阶段 1.5:阻塞模式默认 = 阻塞(BSD/Python 语义;非阻塞用
+ /* 阻塞模式默认 = 阻塞(BSD/Python 语义;非阻塞用
* net_setblocking(fd, 0) 显式设置)。close 的 memset 会清零本字段,
* 新 fd 必须在此显式置 1(同 released_abnormal 的"不得继承"纪律) */
p_sock->blocking = 1;
@@ -962,7 +962,7 @@ int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) {
}
/*
- * 函数功能:驱动 sock_reset 原语的有界确定性排渣等待(P2b 收口,自愈收敛阶段 3;
+ * 函数功能:驱动 sock_reset 原语的有界确定性排渣等待(排渣收口;
* 替代原"close + 盲等 500ms"式忙等)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* mode - CH395F_RESET_GRACEFUL(等 100ms)/ QUARANTINE(等 400ms)
@@ -1038,7 +1038,7 @@ int net_listen_locked(int sockfd) {
ch395f_set_sour_port(sockfd, p_sock->local_port);
/* CH395F 关闭 Socket 后需延时才能重新打开(TRAP-NET-09),故失败重试最多 3 次。
- * P2b 收口(自愈收敛阶段 3):原"close + 盲等 500ms"改**确定性排渣**——
+ * 排渣收口:原"close + 盲等 500ms"改**确定性排渣**——
* sock_reset(读空 RX -> CLOSE -> 排空事件 -> 等 byte0=00H,内置 LISTEN
* 态二段 CLOSE 补发;E4 定案 60 轮实证复位后 open 必成)。GRACEFUL
* 最短等 100ms ≪ 原 500ms 盲等;复位失败/超时仍走下一轮重试,TRAP-NET-09 语义保留 */
@@ -1084,7 +1084,7 @@ int net_listen_locked(int sockfd) {
status = ch395f_tcp_listen(sockfd);
/* LISTEN 失败 → BUSY */
if (status != CH395F_ERR_SUCCESS) {
- /* 0-3(自愈收敛阶段0,U12 对称):open 已成而 LISTEN 失败必须 close,
+ /* U12 对称:open 已成而 LISTEN 失败必须 close,
* 否则芯片 Socket 卡"OPEN 无监听"、后续 open 恒返 USE(同
* net_relisten_locked 对应分支) */
ch395f_close_socket(sockfd);
@@ -1326,7 +1326,7 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
return msg.result;
}
-/* 自愈收敛阶段1(F-NET-4):net_can_accept 已删除——它只认 ESTABLISHED,作为 accept
+/* F-NET-4:net_can_accept 已删除——它只认 ESTABLISHED,作为 accept
* 门控会把"监听器在服役但本轮还没 CONNECT"的窗口全挡掉(:2404 CONNECT 事件补 accept 探针
* 因此从不运行,与已修的 :7003 探针失效同型);唯一生产调用者 iec104 已迁
* net_listener_armed。net_can_read(RECV 门控)不受影响、保留。 */
@@ -1338,11 +1338,11 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
* 返回值:1 - 就绪(LISTENING,或 CLOSED + auto_relisten = 断连待重建态);
* 0 - 未就绪(fd 未分配 / 非监听型 / 正处隔离期以外的不就绪态) int
* 限定条件:任意任务上下文;net_init() 已调用
- * 函数说明:1. N-32(P1 不变量 II):取代已退役的 net_can_accept()(要求
- * ESTABLISHED,自愈收敛阶段1 删除)作为监听 accept 门控。原门控下
+ * 函数说明:1. 不变量 II(单一重建所有者):取代已退役的 net_can_accept()(只认
+ * ESTABLISHED,F-NET-4 删除)作为监听 accept 门控。原门控下
* :7003 永不进 net_accept →
* net_lost_connect_probe(CONNECT 事件未收到补 accept)对该端口从不运行
- * (设计文档 §2.3 末"探针失效")
+ * (F-NET-4)
* 2. 为什么放行 LISTENING:探针与"closed-but-readable 接受"(:1338)
* 只在 net_accept_locked 内部发生,门控挡在外面就永远到不了;
* LISTENING 态 accept 由探测补 accept 或返 WOULDBLOCK,均安全
@@ -1364,7 +1364,7 @@ int net_listener_armed(int sockfd) {
/* 连接已到、应用尚未 accept:单连接模型下 CONNECT 处理(net_poll 分派 /
* 探针补 accept / 对账② standalone accept)把监听 fd 转 ESTABLISHED,
* 这就是"待 accept"电平——**必须放行**(原 net_can_accept 唯一放行态;
- * 自愈收敛阶段1 i104 台架实证:200ms 任务周期抢不过对账②的 ~400ms 去抖,
+ * i104 台架实证:200ms 任务周期抢不过对账②的 ~400ms 去抖,
* 对账②先转 ESTABLISHED → 不放行则应用永不 accept、会话整场饿死)。
* 无 RPC 风暴风险:此态下 accept 必返 fd,一次调用即离开本分支 */
if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
@@ -1380,7 +1380,7 @@ int net_listener_armed(int sockfd) {
* 仍能拿到该连接。
* ⚠ 无残留时必须返回 0 —— 否则调用方会在整个 CLOSED 窗口内每轮都发起
* net_accept(RPC:入队 + xTaskNotifyWait 回执**无上界**)→ 队列积压 →
- * 调用方任务喂不上狗。2026-10-12 P2a 台架实测:删掉即时 relisten 后 CLOSED
+ * 调用方任务喂不上狗。台架实测:删掉即时登记重建后 CLOSED
* 窗口被拉长到 reconciler 周期 + 隔离期(~100ms),:21 0ms×50 期间出现 48 条
* `[WD] task stall`(mask=0x67/req=0x77,缺 bit4=UI)。
* 重建本身由 net_reconcile_tick 形态③④ 负责,调用方**无需**在此催促。
@@ -1501,7 +1501,7 @@ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) {
* 限定条件:net_init() 已调用;必须在 netTask 上下文中调用
* 函数说明:1. 校验控制块(in_use、TCP 类型、地址参数)并保存对端地址
* 2. 芯片侧 OPEN(失败重试 3 次,每次失败走 sock_reset 确定性排渣,
- * 见 TRAP-NET-09 / P2b)+ 配置源/目的端口与目的 IP
+ * 见 TRAP-NET-09)+ 配置源/目的端口与目的 IP
* 3. 清本 Socket 残留中断状态(丢弃上一条连接遗留的 DISCONNECT)
* 4. 发起 TCP_CONNECT(驱动内已轮询 CMD_STATUS)并置 CONNECTING;
* 命令级被拒立即关 Socket 返 -1,TCP 层成败仍由 CONNECT/TIMEOUT 裁定
@@ -1553,7 +1553,7 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a
ch395f_set_proto_type(sockfd, CH395F_PROTO_TYPE_TCP);
/* CH395F 关闭 Socket 后需延时才能重新打开(TRAP-NET-09),故失败重试最多 3 次。
- * P2b 收口(自愈收敛阶段 3):原"close + 盲等 500ms"改**确定性排渣**——
+ * 排渣收口:原"close + 盲等 500ms"改**确定性排渣**——
* sock_reset(读空 RX -> CLOSE -> 排空事件 -> 等 byte0=00H,内置 LISTEN
* 态二段 CLOSE 补发;E4 定案 60 轮实证复位后 open 必成)。GRACEFUL
* 最短等 100ms ≪ 原 500ms 盲等;复位失败/超时仍走下一轮重试,TRAP-NET-09 语义保留 */
@@ -1574,7 +1574,7 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a
}
/* 清掉本 Socket 可能残留的中断状态,丢弃上一条连接遗留的 DISCONNECT。
- * F-NET-7(自愈收敛阶段 3):只清**本 Socket** SINT(驱动护栏原语)——原
+ * F-NET-7:只清**本 Socket** SINT(驱动护栏原语)——原
* "再读清全局"会焊死其它在用 Socket 的事件位(全局寄存器唯一读者=net_poll) */
(void)ch395f_sock_events((uint8_t)sockfd);
@@ -1670,7 +1670,7 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a
* netTask 最坏停滞由 1s 压到 NET_SEND_SPIN_MS。
* ⚠ 仅**写块前**的等待可挂起:末块写完后 sent == len,此时超预算必须
* 直接返回 sent 而非登记(登记函数按 sent >= len 拒收,会把完整发送
- * 报成 -1;2026-10-14 台架实证,详见《Net层阻塞模型架构设计.md》 §9.1)
+ * 报成 -1;详见《Net层阻塞模型架构设计.md》 §5.5 末块边界)
* 4. 发送中途断连(状态非 ESTABLISHED)→ 立即失败并返回已发字节
* (errno=CONNRESET,不再空等超时)
* 5. 未写完即返回(含挂起完成)均为**短写**:调用方须按 ret != len 判定
@@ -1810,7 +1810,7 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len,
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位,NET_MSG_DONTWAIT 表示非阻塞 int
- * timeout_ms - 本次等待预算(0 = 无限等待,自愈收敛阶段 1.5 起无默认值)uint32_t
+ * timeout_ms - 本次等待预算(0 = 无限等待,无默认值)uint32_t
* caller - 调用方任务句柄(挂起请求的完成通知目标) TaskHandle_t
* 出口参数:buf - 填充接收到的数据
* 返回值:实际接收字节数(> 0);0 表示对端关闭;-1 表示出错/非阻塞无数据 int
@@ -1818,7 +1818,7 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len,
* 限定条件:p_sock 非空;netTask 上下文
* 函数说明:1. 数据已在(快速路径)→ 立即读出返回
* 2. 非阻塞(NET_MSG_DONTWAIT 单次覆盖 **或** socket blocking 属性为 0,
- * 自愈收敛阶段 1.5 模式属性)无数据立即返回 -1(NET_ERR_WOULDBLOCK)
+ * 模式属性)无数据立即返回 -1(NET_ERR_WOULDBLOCK)
* 3. 无数据 + 阻塞等待 → 登记挂起请求(pending 化,E-02 结构演进)
* 4. netTask 零占用立即返回;完成条件(数据/到点[仅显式预算]/断连)由 net_pending_tick 按主循环节拍检查
* 5. 并通知 caller;关闭/网络重建/陈旧重同步由对应路径显式结束(取消必通知)
@@ -1835,7 +1835,7 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
}
/* 超时来源:只有调用方显式预算(net_recv_to);0 = 无限等待
- * (自愈收敛阶段 1.5:NET_RECV_TIMEOUT_MS 30s 默认预算已删,API 不强加隐藏值) */
+ * (NET_RECV_TIMEOUT_MS 30s 默认预算已删,API 不强加隐藏值) */
uint32_t to_ms = timeout_ms;
/* 先轮询一次:刷新状态机与可读标志 */
@@ -1859,7 +1859,7 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
}
/* 非阻塞模式:NET_MSG_DONTWAIT 单次覆盖,或 socket 的 blocking 属性为 0
- * (net_setblocking(fd, 0),自愈收敛阶段 1.5 模式挂控制块属性) */
+ * (net_setblocking(fd, 0),模式挂控制块属性) */
if ((flags & NET_MSG_DONTWAIT) || (p_sock->blocking == 0U)) {
/* 非阻塞且当前无数据:若连接已断开则视为 EOF(0) 并自动重监听,
* 否则返回 WOULDBLOCK(-1) 表示暂时无数据 */
@@ -1905,7 +1905,7 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
* 出口参数:无
* 返回值:0 - 关闭成功 int
* 限定条件:必须在 netTask 上下文中调用;p_sock 有效
- * 函数说明:1. P2b 收口(自愈收敛阶段 3):原"close + 500ms 盲等排空循环"改
+ * 函数说明:1. 排渣收口:原"close + 500ms 盲等排空循环"改
* **确定性 sock_reset**(读空 RX -> CLOSE(幂等) -> 排空本 Socket
* 事件(quiescent) -> 等 byte0=00H,内置 LISTEN 态二段 CLOSE 补发
* ——E2 补充发现:一次 CLOSE 只拆监听 byte0 恒 05)
@@ -2077,7 +2077,7 @@ int net_send(int sockfd, const void *buf, int len, int flags) {
/*
* 函数功能:接收数据(线程安全版本,通过消息队列委托给 netTask;
- * **默认无限阻塞**——自愈收敛阶段 1.5 语义主流化,30s 默认预算已删)
+ * **默认无限阻塞**,30s 默认预算已删)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
@@ -2121,7 +2121,7 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
msg.len = len;
/* 标志:0=按 socket 阻塞属性,NET_MSG_DONTWAIT=单次非阻塞 */
msg.flags = flags;
- /* 0 = 无限等待(自愈收敛阶段 1.5:无默认预算;显式预算走 net_recv_to) */
+ /* 0 = 无限等待(无默认预算;显式预算走 net_recv_to) */
msg.timeout_ms = 0U;
/* 调用方任务句柄(netTask 凭它回传回执) */
msg.caller = xTaskGetCurrentTaskHandle();
@@ -2147,7 +2147,7 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
}
/*
- * 函数功能:接收数据(非阻塞具名入口——自愈收敛阶段 1.5 把 NET_MSG_DONTWAIT 路径转正,
+ * 函数功能:接收数据(非阻塞具名入口——原 NET_MSG_DONTWAIT 路径转正,
* 对齐 BSD recv(MSG_DONTWAIT) / Python recv 非阻塞模式的"名如其义")
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 接收缓冲区指针 void *
@@ -2167,7 +2167,7 @@ int net_recv_nb(int sockfd, void *buf, int len) {
/*
* 函数功能:接收数据(带调用方指定超时的阻塞版,用于分片有界读;
- * 自愈收敛阶段 1.5 起为**显式预算**便捷入口,等价 Python settimeout,
+ * 为**显式预算**便捷入口,等价 Python settimeout,
* 不再有隐藏默认值)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf / len - 接收缓冲与容量 void * / int > 0
@@ -2199,7 +2199,7 @@ int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms) {
}
/* ---- 3. 分片长度检查:timeout_ms = 0 在 netTask 侧意味着"**无限等待**"
- * (自愈收敛阶段 1.5 删除 30s 默认预算),恰与本接口"有界等待"的存在
+ * (已删除 30s 默认预算),恰与本接口"有界等待"的存在
* 理由相反,直接拒绝 ---- */
if (timeout_ms == 0U) {
/* 分片长度必须 > 0 */
@@ -2508,7 +2508,7 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
* 限定条件:Socket 已创建且为 UDP 类型(SOCK_DGRAM);net_init() 已调用;有数据可用
* 函数说明:1. 轮询检查芯片接收缓冲(net_poll + get_recv_len),有数据即返回
* 2. UDP 数据报前 8 字节为报文头(源端口+源 IP),据此填充 src_addr
- * 3. 无数据时(自愈收敛阶段 1.5,与 net_recv 同构):DONTWAIT / 非阻塞属性
+ * 3. 无数据时(与 net_recv 同构):DONTWAIT / 非阻塞属性
* 立即返回 -1(WOULDBLOCK);阻塞模式**无限等待**(30s 默认预算已删;
* 本接口为轮询自旋实现且无生产调用者,受 IWDG 监控的任务勿用无限形态)
*/
@@ -2535,7 +2535,7 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
- /* 轮询直到:有数据 / 非阻塞(自愈收敛阶段 1.5:阻塞 = 无限等待,无默认预算) */
+ /* 轮询直到:有数据 / 非阻塞(阻塞 = 无限等待,无默认预算) */
while (1) {
/* 轮询一次:更新状态机与可读标志 */
net_poll();
@@ -2588,13 +2588,13 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
}
/* 非阻塞模式:NET_MSG_DONTWAIT 单次覆盖,或 socket blocking 属性为 0
- * (自愈收敛阶段 1.5,与 net_recv_locked 同判据):无数据立即返回 */
+ * (与 net_recv_locked 同判据):无数据立即返回 */
if ((flags & NET_MSG_DONTWAIT) || (p_sock->blocking == 0U)) {
/* 置 WOULDBLOCK */
return NET_ERR_RETURN_1(NET_ERR_WOULDBLOCK);
}
- /* 阻塞模式:无数据继续轮询等待(自愈收敛阶段 1.5:无限等待,无默认预算——
+ /* 阻塞模式:无数据继续轮询等待(无限等待,无默认预算——
* 旧 NET_RECV_TIMEOUT_MS 30s 兜底已删;唤醒源 = 数据,与 net_recv 的
* 差异是本接口为自旋实现,勿在受 IWDG 监控任务中使用无限形态) */
}
@@ -2706,7 +2706,7 @@ int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) {
}
/*
- * 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(自愈收敛阶段 1.5,模式挂控制块属性,
+ * 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(模式挂控制块属性,
* 对齐 BSD O_NONBLOCK / Python setblocking)
* 入口参数:sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* on - 1 = 阻塞(net_socket 默认);0 = 非阻塞 int
@@ -2784,7 +2784,7 @@ int net_sock_state(int sockfd) {
return (int)s_net_socks[sockfd].state;
}
-/* 自愈收敛阶段1(F-NET-3):手动自愈 API net_resync_listen / net_relisten 已删除——
+/* F-NET-3:手动自愈 API net_resync_listen / net_relisten 已删除——
* RESYNC 语义并入对账⑤(net_reconcile_tick 自动认领"芯片静默回收回 LISTEN"形态,
* 200ms×2 去抖 + 1s 冷却沿用);RELISTEN 与 reconciler/D10 完全重叠且对健康监听器是
* footgun(无条件 open→USE→close 自毁 ~1s,原注释自认)。生产调用者本就为零
diff --git a/Drivers/BSP/NET/net_socket.h b/Drivers/BSP/NET/net_socket.h
index ec76fba0..37d38960 100644
--- a/Drivers/BSP/NET/net_socket.h
+++ b/Drivers/BSP/NET/net_socket.h
@@ -37,8 +37,8 @@ typedef enum {
NET_MSG_CONNECT,
NET_MSG_LISTEN,
NET_MSG_ACCEPT,
- NET_MSG_DROP_CONN, /* 自愈收敛阶段 3(E-02 收口):主动拆会话、保监听(见 net_drop_conn) */
- /* 自愈收敛阶段1(F-NET-2/3):NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 已删——
+ NET_MSG_DROP_CONN, /* E-02 收口:主动拆会话、保监听(见 net_drop_conn) */
+ /* F-NET-2/3:NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 已删——
* 静默回收由对账⑤自动认领(net_reconcile_tick),重建由 reconciler
* 形态③④/D10 单一
* 所有者负责;手动自愈 API 面退役(生产调用者本就为零) */
@@ -51,7 +51,7 @@ typedef struct {
int len; /* 数据长度/缓冲区大小 */
int flags; /* 接收标志 */
uint32_t timeout_ms; /* 接收超时(入队方向;0 = 无限等待,无默认预算——
- * 自愈收敛阶段 1.5 删除 NET_RECV_TIMEOUT_MS 30s 默认) */
+ * NET_RECV_TIMEOUT_MS 30s 默认已删除) */
TaskHandle_t caller; /* 调用方任务句柄 */
int result; /* 操作结果 */
/* 连接参数 */
@@ -154,7 +154,7 @@ int net_listen(int sockfd);
*/
int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen);
-/* 自愈收敛阶段1(F-NET-4):net_can_accept 已退役——只认 ESTABLISHED 的旧门控会把
+/* F-NET-4:net_can_accept 已退役——只认 ESTABLISHED 的旧门控会把
* "监听器在服役、本轮待 CONNECT"窗口全挡掉(CONNECT 事件补 accept 探针因此从不运行);
* 监听 accept 门控统一用 net_listener_armed(),recv 门控 net_can_read() 保留。 */
@@ -165,18 +165,18 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen);
* 返回值:1 - 就绪(ESTABLISHED 待 accept / LISTENING / CLOSED+auto_relisten
* +残留数据);0 - 不就绪 int
* 限定条件:net_init() 已调用;任意任务上下文
- * 函数说明:1. N-32(P1 不变量 II,自愈收敛阶段1 补齐 ESTABLISHED 放行):取代已退役的
+ * 函数说明:1. 不变量 II(单一重建所有者;补齐 ESTABLISHED 放行):取代已退役的
* `net_can_accept()`(只认 ESTABLISHED)作为监听 accept 门控——原门控使
* :7003 永不进 `net_accept()`,
* `net_lost_connect_probe`(CONNECT 事件未收到补 accept,N-30 实证的
* "数据被吞/425"主根因修复)对该端口等于从未运行
* 2. 放行 LISTENING:探针与 "closed-but-readable 接受" 都发生在
* `net_accept_locked` **内部**,门控挡在外面就永远到不了
- * 2b. 放行 ESTABLISHED(自愈收敛阶段1 台架修正):单连接模型下 CONNECT 处理
+ * 2b. 放行 ESTABLISHED(i104 台架实证):单连接模型下 CONNECT 处理
* (net_poll 分派 / 探针补 accept / 对账② standalone accept)把监听 fd
* 转 ESTABLISHED,这正是"待 accept"电平——漏放行则任务周期慢的消费者
* (i104 200ms 抢不过对账② ~400ms 去抖)永不 accept、会话整场饿死
- * (2026-10-13 实测全 18 项 FAIL)。此态 accept 必返 fd,无风暴风险
+ * (实测全 18 项 FAIL)。此态 accept 必返 fd,无风暴风险
* 3. 放行 CLOSED+auto_relisten **且 readable≠0**(有残留数据):
* 保住 N-30 的 "closed-but-readable" 接受;**无残留时必须返回 0**,
* 否则调用方会在整个 CLOSED 窗口内每轮发起 `net_accept`(RPC 无上界)
@@ -242,7 +242,7 @@ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen);
int net_send(int sockfd, const void *buf, int len, int flags);
/*
- * 函数功能:接收数据(TCP,**默认无限阻塞**——自愈收敛阶段 1.5 语义主流化)
+ * 函数功能:接收数据(TCP,**默认无限阻塞**)
* 入口参数:sockfd - socket 描述符
* buf - 接收缓冲区
* len - 缓冲区大小
@@ -263,7 +263,7 @@ int net_send(int sockfd, const void *buf, int len, int flags);
int net_recv(int sockfd, void *buf, int len, int flags);
/*
- * 函数功能:接收数据(非阻塞具名入口,原 NET_MSG_DONTWAIT 路径转正——自愈收敛阶段 1.5)
+ * 函数功能:接收数据(非阻塞具名入口,原 NET_MSG_DONTWAIT 路径转正)
* 入口参数:sockfd - socket 描述符
* buf - 接收缓冲区
* len - 缓冲区大小
@@ -277,7 +277,7 @@ int net_recv_nb(int sockfd, void *buf, int len);
/*
* 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(对齐 BSD O_NONBLOCK / Python
- * setblocking——自愈收敛阶段 1.5,模式挂 socket 控制块属性)
+ * setblocking,模式挂 socket 控制块属性)
* 入口参数:sockfd - socket 描述符
* on - 1 = 阻塞(net_socket 默认);0 = 非阻塞
* 返回值:0 成功;-1 失败(描述符无效/未占用)
@@ -298,7 +298,7 @@ int net_setblocking(int sockfd, int on);
* timeout_ms - 单次调用在 netTask 内的最长等待(毫秒,>0)
* 返回值:>0 实际字节数;0 = 对端关闭(EOF);-1 = 真错误(errno 可查);**-2 = 超时无数据**
* 限定条件:非 netTask 上下文;Socket 已连接
- * 函数说明:1. **显式预算**便捷入口(等价 Python settimeout,自愈收敛阶段 1.5 起
+ * 函数说明:1. **显式预算**便捷入口(等价 Python settimeout,
* 不再有隐藏默认值):net_recv 的无限阻塞 + 本接口的显式预算构成
* 超时语义的全部来源;超时首选仍是 net_select
* 2. E-02 修复配套:lftpd 等"等对端下一行/下一块数据"的调用方
@@ -335,7 +335,7 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
* 返回值:实际接收字节数,失败返回 -1
* 限定条件:socket 已创建,指针非空
* 函数说明:1. 读取 CH395F 接收缓冲区,获取对端地址
- * 2. 阻塞语义与 net_recv 同(自愈收敛阶段 1.5):blocking 属性下无数据
+ * 2. 阻塞语义与 net_recv 同:blocking 属性下无数据
* 无限等待,DONTWAIT / 非阻塞属性立即 NET_ERR_WOULDBLOCK;
* 本接口为轮询自旋实现(历史遗留,UDP 专用),受 IWDG 监控的
* 任务同样禁止对其无限等待
@@ -353,7 +353,7 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
int net_close(int sockfd);
/*
- * 函数功能:主动拆掉当前 TCP 会话,**保留监听**(自愈收敛阶段 3,E-02 收口)
+ * 函数功能:主动拆掉当前 TCP 会话,**保留监听**(E-02 收口)
* 入口参数:sockfd - socket 描述符
* 返回值:0 成功(含幂等:无活动会话),-1 失败
* 限定条件:非 netTask 上下文(经消息队列委托,同其它 net_* API)
@@ -409,7 +409,7 @@ int net_get_errno(void);
*/
int net_sock_state(int sockfd);
-/* 自愈收敛阶段1(F-NET-3):net_resync_listen / net_relisten 声明已删——
+/* F-NET-3:net_resync_listen / net_relisten 声明已删——
* 静默回收形态由对账⑤自动认领(2 轮去抖 + 1s 冷却,纪律"不发芯片命令"
* 原样保留在认领动作内);监听重建由 reconciler 形态③④(100ms 周期)/
* D10(30ms 重试)单一所有者。勿再引入"应用层手动自愈"入口。 */
diff --git a/Drivers/BSP/NET/net_types.h b/Drivers/BSP/NET/net_types.h
index 82312635..24c36b8f 100644
--- a/Drivers/BSP/NET/net_types.h
+++ b/Drivers/BSP/NET/net_types.h
@@ -48,8 +48,7 @@ extern "C" {
* 接收标志定义
*/
#define NET_MSG_DONTWAIT 0x01 /* 非阻塞模式(单次覆盖 socket 的 blocking 属性;
- * 具名入口见 net_recv_nb() / net_setblocking(),
- * 自愈收敛阶段 1.5) */
+ * 具名入口见 net_recv_nb() / net_setblocking()) */
/*
* 网络字节序转换宏
@@ -112,7 +111,7 @@ struct net_sockaddr_in {
/*
* Socket 状态枚举(**本层会话投影**,非芯片状态事实源)
- * 自愈收敛阶段 3(镜像降级,docs/网络/网络自愈架构设计.md §3.3):
+ * 镜像降级(docs/网络/网络自愈架构设计.md §3.3):
* 芯片侧状态唯一事实源=驱动状态缓存(ch395f_sock_state_cached,刷新点=net_poll);
* 本 8 态只表达**本层会话语义**(fd 生命周期/待 accept 电平/门控展示),
* 事件与对账把它与芯片事实对齐。新增"按芯片状态决策"的代码一律读驱动缓存,
@@ -178,7 +177,7 @@ typedef struct {
/* 单连接模式自动重监听:连接断开后由 net 层自动回到 LISTEN(BSD 风格监听常驻) */
uint8_t auto_relisten;
- /* 自愈收敛阶段 1.5(2026-10-13):阻塞/非阻塞**模式属性**(对齐 BSD O_NONBLOCK /
+ /* 阻塞/非阻塞**模式属性**(对齐 BSD O_NONBLOCK /
* Python setblocking 的"模式归使用者"模型)。
* 1 = 阻塞(net_socket() 默认);0 = 非阻塞(net_setblocking(fd, 0))。
* net_recv() 无 NET_MSG_DONTWAIT 时按本属性决定"无数据 → 登记挂起等待"
diff --git a/Drivers/BSP/UI/ui_net_sink.c b/Drivers/BSP/UI/ui_net_sink.c
index c807180b..f66a39c4 100644
--- a/Drivers/BSP/UI/ui_net_sink.c
+++ b/Drivers/BSP/UI/ui_net_sink.c
@@ -545,7 +545,7 @@ void ui_net_sink_poll(void) {
return;
}
- /* 有客户端时非阻塞收帧(I-1 门控 + I-2 兜底;自愈收敛阶段 1.5:DONTWAIT 转正
+ /* 有客户端时非阻塞收帧(I-1 门控 + I-2 兜底;DONTWAIT 已转正
* 为具名 API net_recv_nb——uiTask 在 IWDG required_mask 内且有 10ms UI 推流
* 周期职责,禁止无限阻塞 recv,非阻塞轮询是义务):
* 无数据可读 → 多数轮次直接返回,省掉一次 recv 往返(该往返内部
diff --git a/Drivers/BSP/UI/ui_net_sink.h b/Drivers/BSP/UI/ui_net_sink.h
index deabda79..51a32f76 100644
--- a/Drivers/BSP/UI/ui_net_sink.h
+++ b/Drivers/BSP/UI/ui_net_sink.h
@@ -47,7 +47,7 @@ int ui_net_sink_start(uint16_t port);
* 函数说明:1. 无客户端时先门控(net_listener_armed,纯 RAM 读)再尝试接受连接
* 2. 有客户端时先门控(net_can_read)再用 net_recv_nb 非阻塞收帧并解析
* (KEEPLIVE/INIT/KEY/LCDMEM);门控判为无数据时仍每 32 轮兜底收一次
- * (自愈收敛阶段 1.5:DONTWAIT 转正为具名 API;uiTask 受 IWDG 监控,
+ * (DONTWAIT 已转正为具名 API;uiTask 受 IWDG 监控,
* 非阻塞是契约义务——见 net_socket.h net_recv 文档契约)
* 3. 对端关闭或异常时复位客户端,等待下次重监听
* ⚠ **不是"零成本非阻塞"**:门控只消除无谓往返;真有活时仍是
diff --git a/docs/README.md b/docs/README.md
index ea575e50..b7a0f937 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -95,7 +95,7 @@ docs/
| [`网络/BSD_Socket_API_使用指南.md`](网络/BSD_Socket_API_使用指南.md) | **上层视图**:BSD Socket API 公开语义、示例、常见问题 |
| [`网络/BSD_Socket_API_函数调用关系.md`](网络/BSD_Socket_API_函数调用关系.md) | **内部视图**:netTask / 消息队列 / L3 挂起 / 重试对账的调用关系与设计意图 |
| [`网络/Net层阻塞模型架构设计.md`](网络/Net层阻塞模型架构设计.md) | **等待模型**:recv/send 挂起请求架构、数据结构与挂起槽状态机、recv 五路 + send 四路完成语义、模块接口契约(14 函数)、实现落点(20 集成点)、调用方契约、设计边界与性能特征 |
-| [`网络/网络自愈架构设计.md`](网络/网络自愈架构设计.md) | **网络自愈架构 + 监听生命周期**:现状权威描述——根因三层证据与 A′ 出局、设计原则(不变量 I1~I8)、分层职责与三 tick、reconciler 形态①~⑤ + M3 门、驱动原语、FUN_PARA bit3 契约、recv 语义、KeepAlive、参数表与 E 锚点速查、红线、代码级事实清点、决策记录 D1~D7、`net_listener.c` 模块化边界、延后项;审计与 B10 根治过程已归档 |
+| [`网络/网络自愈架构设计.md`](网络/网络自愈架构设计.md) | **网络自愈架构 + 监听生命周期**:现状权威描述——根因三层证据与 A′ 出局、ioLibrary 代码级对照、设计原则(不变量 I1~I8)、分层职责与三 tick、reconciler 形态①~⑤ + M3 门、驱动原语、读即清信源审计、FUN_PARA bit3 契约、recv 语义、KeepAlive、参数表与 E 锚点速查、红线、编号发现速查、代码级事实清点、决策记录、`net_listener.c` / `net_events.c` 模块化边界、延后项;审计与实施过程已归档 |
---
diff --git a/docs/images/网络不稳根因总览.svg b/docs/images/网络不稳根因总览.svg
index bfd714fa..83c32523 100644
--- a/docs/images/网络不稳根因总览.svg
+++ b/docs/images/网络不稳根因总览.svg
@@ -1,7 +1,7 @@
diff --git a/docs/网络/Net层阻塞模型架构设计.md b/docs/网络/Net层阻塞模型架构设计.md
index caaae7f9..64fb8560 100644
--- a/docs/网络/Net层阻塞模型架构设计.md
+++ b/docs/网络/Net层阻塞模型架构设计.md
@@ -1,45 +1,43 @@
# Net 层阻塞模型架构设计
> **模块名称**:Net 层挂起 recv / send 模型(`Drivers/BSP/NET/net_pending.c` + netTask 主循环节拍)
-> **文档定位**:**现状架构设计说明**——描述本层**当前如何组织等待与完成**、为什么这样设计、
-> 数据结构与接口契约、实现落点与集成步骤、边界条件与已验证的性能特征。**不是历史记录**:
-> 历史演进、门禁实测记录与修订日志一律不保留在文档内(需要时查 git 历史)。
-> **状态**:recv 挂起(一期)与 send 挂起(二期)设计均已落地;recv 全量台架验证,
-> send 于 2026-10-14 完成台架复测(含本次复测发现并修复的末块挂起越界缺陷,见 §9.1)。
-> **日期**:2026-10-14
+> **文档定位**:**现状架构设计说明**——本层**当前如何组织等待与完成**、为什么这样设计、数据结构与接口
+> 契约、实现落点、边界条件与性能特征。历史演进、门禁实测记录与修订日志一律不保留在文档内(需要时查
+> git 历史)。
+> **状态**:recv 与 send 两条挂起路径**均已落地**(真机台架验证);ACCEPT 尚未挂起化,判据见 §10。
> **相关文档**:
> `docs/网络/BSD_Socket_API_使用指南.md`(应用视角 API 语义)、
> `docs/网络/BSD_Socket_API_函数调用关系.md`(调用链与执行域)、
-> `docs/网络/网络自愈架构设计.md`(周期对账与监听生命周期——与本模型的分工见 §2.4;模块化边界见 §11)、
+> `docs/网络/网络自愈架构设计.md`(周期对账与监听生命周期——与本模型的分工见 §2.4;模块化边界见该文档 §11)、
> `docs/测试说明/链路延迟测试说明.md`(延迟基线与故障对照)、
> `docs/问题记录/错误码传播审计.md`(E-02 队头阻塞审计)、
-> `docs/任务规划/基线与待确认.md` §2.1(二期扩展设计:SEND 已落地、ACCEPT 待办)
+> `docs/任务规划/基线与待确认.md` §2.1(ACCEPT 挂起化设计基线)
## 目录
-- [1. 问题域](#1-问题域)
+- [1. 问题域:等待为什么必须挪出 netTask](#1-问题域等待为什么必须挪出-nettask)
- [2. 架构总览](#2-架构总览)
- [3. 一次阻塞 recv 的全过程](#3-一次阻塞-recv-的全过程)
- [4. 数据结构](#4-数据结构)
-- [5. 完成与取消语义](#5-完成与取消语义)
+- [5. 挂起的请求会被什么结束](#5-挂起的请求会被什么结束)
- [6. 模块接口契约](#6-模块接口契约)
-- [7. 实现落点(集成步骤)](#7-实现落点集成步骤)
+- [7. 实现落点(改/查本模型看哪里)](#7-实现落点改查本模型看哪里)
- [8. 调用方契约](#8-调用方契约)
-- [9. 性能特征](#9-性能特征)
+- [9. 性能与资源特征](#9-性能与资源特征)
- [10. 设计边界与已知代价](#10-设计边界与已知代价)
-- [11. 设计铁律](#11-设计铁律)
-- [12. 排查辅助:挂起超时留痕探针](#12-排查辅助挂起超时留痕探针)
+- [11. 设计铁律(为什么是这样)](#11-设计铁律为什么是这样)
+- [12. 排查辅助:挂起超时留痕探针(编译期选项,默认关)](#12-排查辅助挂起超时留痕探针编译期选项默认关)
- [13. 源码索引](#13-源码索引)
---
-## 1. 问题域
+## 1. 问题域:等待为什么必须挪出 netTask
netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线归它独占(SPI 铁律),
因此所有 socket 操作必须经消息队列串行化到 netTask 上下文完成。
这带来一个结构性约束:**消费者发起的"等待数据"会占住执行者**。若 `net_recv` 采用
-"在 netTask 内自旋轮询直到数据到达"的朴素形态,一条空闲连接即可让整个网络层停摆——
+"在 netTask 内自旋轮询直到数据到达"的形态,一条空闲连接即可让整个网络层停摆——
所有其他 socket 的操作都要排队等它。
> **约束的本质**:本项目的"数据到达"无法被免费观测。业界正统形态(LwIP 等)依赖网卡
@@ -47,31 +45,35 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
> **芯片内部**,唯一观测手段是一次 SPI 事务,而 SPI 只许 netTask 发起。
> → **观测本身占用执行者**,等待无法挪出 netTask。
-设计自由度因此只剩两条:① 每次占用执行者多久 ② 等待期间执行者还能做什么。
-三条候选路线的取舍:
+设计自由度因此只剩两条:① 每次占用执行者多久 ② 等待期间执行者还能做什么。三条候选路线:
-| 路线 | 单次占用执行者 | 等待期间执行者 | 落地结论 |
+| 路线 | 单次占用执行者 | 等待期间执行者 | 结论 |
|---|---|---|---|
-| A 执行者内自旋 | 整个等待期(可无限) | **完全停摆** | ❌ 队头阻塞(实测一条空闲 FTP 连接冻结远端显示 17.7s) |
+| A 执行者内自旋 | 整个等待期(可无限) | **完全停摆** | ❌ 队头阻塞:一条空闲连接即可冻结整个网络层 |
| B 有界分片轮询(`net_recv_to` 分片) | ≤ `timeout_ms` | 分片间隙可用 | ⚠️ 止血:仍周期性占用,调用方须自管总预算 |
| C **挂起登记(本模型)** | 微秒级(登记一次) | 照常服务整个队列 | ✅ 结构性消灭 |
-路线 A→B→C 的落地证据与量化见 `docs/问题记录/错误码传播审计.md` E-02。
-路线 B 的入口 `net_recv_to` 在路线 C 下**继续保留**,但语义已从"分期占用执行者"
-变为"调用方显式预算"(见 §6.2、§8)。
+三条路线的量化对照见 `docs/问题记录/错误码传播审计.md` E-02。本模型选 C;路线 B 的入口
+`net_recv_to` **仍在 API 面上**,定位是**调用方显式预算**(§6.2、§8),与挂起登记互补。
-> **SEND 是同一问题的第二个面**:`net_send` 写入 CH395F 发送 FIFO 后需等 `send_ready`
-> (由 `SENBUF_FREE` 中断经 `net_poll` 重新置位),旧的 `net_send_locked` 在 netTask 内
-> **忙轮询等它**,单次停滞上限 `NET_SEND_TIMEOUT_MS`(1 s)——同属执行者占用型队头阻塞,
-> 量级比 recv 小一个数量级,但同样会冻结整个消息队列并顺延挂起 recv 的唤醒。二期把
-> send 也纳入挂起模型(§4.5、§5.5),**快路径保留有界自旋**(健康链路 219 µs ≪ 2 ms 预算,
-> 行为不变),超预算才登记挂起。收益判据仍是"执行者被占多久"(§10)。
+> **send 是同一问题的第二个面**:`net_send` 写入 CH395F 发送 FIFO 后要等 `send_ready`
+> (由 `SENBUF_FREE` 中断经 `net_poll` 重新置位)。这段等待若发生在 netTask 内,就是同一种
+> **执行者占用型**队头阻塞——量级比 recv 小一个数量级,但同样冻结整个消息队列,并顺延挂起
+> recv 的唤醒。故 send 纳入同一模型:**快路径保留有界自旋**(`NET_SEND_SPIN_MS` = 2 ms,
+> 健康链路块间等待 med 219 µs,正常发送根本不触发),超预算才登记挂起(§4.5、§5.5)。
+> 判据始终只有一个:**执行者被占多久**(§10)。
---
## 2. 架构总览
-核心思想:把"等数据"从**执行者内的自旋**改为**执行者外的登记**。
+核心思想:**把"等数据"从执行者内的自旋,改为执行者外的登记**。四个支点:
+
+1. **登记而非占用**——netTask 只花微秒级把请求记进表里,随即去处理下一条消息;
+2. **等待落在调用方自己的上下文**——睡在任务通知上,与 LwIP 的消费者线程模型同构;
+3. **完成判定复用主循环既有节拍**——判据是 RAM 里的标志与到期时间,不引入额外 SPI 事务;
+4. **唯一完成出口**——所有完成/取消都汇聚到一个函数(通知 + 清槽),结构上排除"漏通知"和
+ "完成两次"。

@@ -81,7 +83,7 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
|---|---|
| **左栏 · 调用方任务** | `net_recv` → 判"数据已在?" → **是**走快速路径;**否**(`timeout > 0`)登记挂起 recv 后在自己上下文睡任务通知。`net_send` 同构:快路径短暂自旋写块,超预算则登记挂起 send 后睡 |
| **中栏 · netTask 主循环** | ~1ms 节拍:`net_poll`(刷新 `readable` + 全量刷新驱动状态缓存)→ 4 个非阻塞 tick(`net_listener_tick` D10 → `net_pending_tick` 结束挂起 recv → `net_send_pending_tick` 推进/结束挂起 send → `net_reconcile_tick` 对账 + 监听重建唯一入口)→ 排空消息队列 |
-| **下带 · 判定** | recv 每轮按序判定:数据到达 / 到点(仅显式预算)/ EOF / 同 fd 后续操作 / 网络重建(**五路**);send 每轮按序判定:断连 / 推进 / 写完 / 无进展超时(**四路**);挂起超时留痕探针(编译期选项,默认关,见 §12)只留痕 |
+| **下带 · 判定** | recv 每轮按序判定:数据到达 / 到点(仅显式预算)/ EOF / 同 fd 后续操作 / 网络重建(**五路**,§5.1 简称);send 每轮按序判定:断连 / 推进 / 写完 / 无进展超时(**四路**,§5.5 简称);挂起超时留痕探针(编译期选项,默认关,见 §12)只留痕 |
| **回环** | 判定完成经 `xTaskNotify` 唤醒调用方(结果 = 字节数 / 负错误码) |
> 图形由 `tools/arch_diagram/gen_net_pending_svg.py` 生成(改流程后重跑;见
@@ -92,13 +94,12 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
- 等待期间 netTask **零额外占用**——`net_pending_tick` 只读标志与到期时间,
数据到达判据 `readable` 由主循环既有的 `net_poll()` 每轮刷新,**不引入额外 SPI 事务**;
- **挂起 send 的占用有界且非阻塞**——`net_send_pending_tick` 每轮至多写一块(`≤ NET_SEND_CHUNK_MAX`),
- 写完即返回,绝不 `osDelay` 忙等;故 netTask 最坏停滞由 `NET_SEND_TIMEOUT_MS`(1 s)
- 压到 `NET_SEND_SPIN_MS`(2 ms);
+ 写完即返回,绝不 `osDelay` 忙等;故 netTask 最坏停滞 = `NET_SEND_SPIN_MS`(2 ms),
+ 而不是 `NET_SEND_TIMEOUT_MS`(1 s);
- 调用方**在自己的上下文睡眠**(任务通知),与 LwIP 的消费者线程模型同构;
-- 公开 API 与调用契约**稳定**——`net_recv`(默认无限阻塞)/ `net_recv_nb` +
- `net_setblocking` / `net_recv_to`(唯一显式预算入口)/ `net_send` 的签名不变;
- 既有调用方、测试与模拟器零改动;`net_send` 的返回值语义(已发字节数,短写须检查)
- 与挂起化前一致,超时归属语义详见
+- **公开 API 契约不因本模型改变**:`net_recv`(默认无限阻塞)/ `net_recv_nb` +
+ `net_setblocking` / `net_recv_to`(唯一显式预算入口)/ `net_send`(返回已写入字节数,
+ 短写须检查)。超时归属语义详见
`docs/网络/网络自愈架构设计.md` §4.5 与 `docs/网络/BSD_Socket_API_使用指南.md` §3.5。
### 2.2 模块边界
@@ -107,7 +108,7 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线
| 文件 | 持有 | 对外接口 |
|---|---|---|
-| `Drivers/BSP/NET/net_pending.c` / `.h` | 挂起 recv 表 `s_net_pending[]`、挂起 send 表 `s_net_send[]`、执行者句柄 `s_owner_task`、全部完成/取消逻辑 | 14 个函数(recv 组 9 + send 组 7,含 2 个 static,§6.1) |
+| `Drivers/BSP/NET/net_pending.c` / `.h` | 挂起 recv 表 `s_net_pending[]`、挂起 send 表 `s_net_send[]`、执行者句柄 `s_owner_task`、全部完成/取消逻辑 | 16 个函数(recv 组 9 + send 组 7,其中 2 个 static,对外 14 个,§6.1) |
| `Drivers/BSP/NET/net_socket.c` | 消息队列与分派、`net_recv_locked` / `net_send_locked` 的挂起登记、close/drop_conn/重建/对账的结算调用 | 调用方(§7) |
| `Drivers/BSP/NET/net_listener.c` | 监听器重开 | 消费 `net_pending_active()` / `net_send_pending_active()` 作"挂起保险" |
| `App/task/net_task.c` | 主循环节拍(`net_poll` → `net_process_messages` → `osDelay(1)`) | 提供 tick 节拍 |
@@ -130,10 +131,10 @@ task_wd_beat() → 处理 SYS_EVT_NET_RECONFIG → net_poll() → net_process_me
| 顺序 | tick | 职责 | 与挂起模型的关系 |
|---|---|---|---|
-| ① | `net_listener_tick()` | D10 重监听重试(30ms/2s) | 跳过有在途挂起 recv / send 的 fd(§7.2 集成点 8 / 20) |
+| ① | `net_listener_tick()` | D10 重监听重试(30ms/2s) | 跳过有在途挂起 recv / send 的 fd(§7.1 集成点 8 / 20) |
| ② | `net_pending_tick(s_net_socks)` | **结束挂起 recv**(数据/到点/断连) | recv 的主完成检查入口 |
-| ③ | `net_send_pending_tick(s_net_socks)` | **推进/结束挂起 send**(断连/推进/写完/超时) | send 的主推进入口(二期) |
-| ④ | `net_reconcile_tick()` | 100ms 对账 + 监听重建唯一入口 | 形态⑤(芯片静默回收)取消本 fd 挂起 recv / send(§7.2 集成点 7 / 17) |
+| ③ | `net_send_pending_tick(s_net_socks)` | **推进/结束挂起 send**(断连/推进/写完/超时) | send 的主推进入口 |
+| ④ | `net_reconcile_tick()` | 100ms 对账 + 监听重建唯一入口 | 形态⑤(芯片静默回收)取消本 fd 挂起 recv / send(§7.1 集成点 7 / 17) |
四个 tick 均非阻塞、**绝不 `osDelay` 忙等**;tick 先于消息分派,保证"断连后即使无应用
消息,监听器与挂起 recv / send 也能自主收敛"。②③ 相对顺序无关(两张独立表),
@@ -150,7 +151,7 @@ task_wd_beat() → 处理 SYS_EVT_NET_RECONFIG → net_poll() → net_process_me
| 监听生命周期的模块化边界(`net_listener.c` 拆分依据) | `docs/网络/网络自愈架构设计.md` §11 |
交叉点只有两处:`net_reconcile_tick` 形态⑤ 取消挂起(§5.2 第 5 路)、
-`net_listener_tick` 的挂起 recv 保险(§7.2 集成点 8)。
+`net_listener_tick` 的挂起 recv 保险(§7.1 集成点 8)。
---
@@ -186,33 +187,27 @@ flowchart TD
K["取消路径:close / drop_conn / 网络重建 / 对账 resync"] -. "先结束挂起、再动芯片" .-> I
```
-1. **入队**(调用方任务):`net_recv` 把请求打包进消息入 `netMsgQueue`,随后进入两段式
- 等待(步骤 6)。netTask 不被占用。
-2. **快速路径判定**(netTask,`net_recv_locked`):函数入口先 `net_poll()` 一次刷新状态机
- 与可读标志,再查 `ch395f_get_recv_len`。数据已在(`recv_len > 0`)→ 直接读出、经通知值
- 回传字节数——不产生挂起,常见路径到此为止。
+1. **入队**(调用方任务):`net_recv` 把请求打包进 `netMsgQueue`,随后进入两段式等待
+ (步骤 5)——netTask 不被占用。
+2. **快速路径**(netTask,`net_recv_locked`):入口先 `net_poll()` 刷新状态机与可读标志,再查
+ `ch395f_get_recv_len`;数据已在(`recv_len > 0`)→ 直接读出、经通知值回传字节数,不产生
+ 挂起。常见路径到此为止。
3. **登记**(netTask):无数据且阻塞模式 → `net_pending_register` 过两条守卫(§4.3)→ 记
`buf`/`len`/`caller` 与预算(`net_recv_to` → `deadline`;无限等待 → `infinite=1` 无
- deadline)→ 返回哨兵 `NET_PENDING_REGISTERED`,netTask 立即处理下一条消息。
-4. **每轮判定**(netTask,`net_pending_tick`):对每个挂起 fd 按序 ①数据 → ②到点 →
- ③断连(判据见 §5);命中任一条件进入公共出口。挂起超时留痕探针只留痕不完成
- (编译期选项,默认关,见 §12)。
-5. **公共完成出口**(`net_pending_complete`,所有完成/取消路径的**唯一**出口):
- `xTaskNotify` 覆盖式写入**通知值 = 结果原值**(>0 字节数 / 0 EOF / 负错误码)→ 清
- `active`;无在位挂起则静默返回(防重复通知)。失败结果同步记入 netTask 自己的 errno
- 槽位(按任务槽位,非跨任务共享全局)。
-6. **唤醒与取结果**(调用方任务):所有 `net_*` API 的回执共用同一条任务通知通道,
- **两段式等待**防"入队→等待入口"间隙内通知丢失——先零等待拾取(清除掩码全 1;
- 以 `pdTRUE`/`pdFALSE` 判定已落地,**不能判值≠0**——合法的 0 结果 = EOF 也是有效通知),
- 未拾取到再阻塞等待。通知值 32 位整体作为结果,不切分位域(大单次收发不被截断)。
-7. **取消路径**(第 4/5 路,共同规则"**先结束挂起、再动芯片**"):
- - **同 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` 指针须保持有效至调用返回(挂起表按指针写入,§8)。
+ deadline)→ 返回哨兵 `NET_PENDING_REGISTERED`(分派据此**不发即时回执**,§6.2),
+ netTask 立即处理下一条消息。
+4. **每轮判定 → 公共出口**(netTask):`net_pending_tick` 对每个挂起 fd 按序 ①数据 → ②到点 →
+ ③断连(判据见 §5.2);命中即进 `net_pending_complete`——所有完成/取消路径的**唯一**出口:
+ `xTaskNotify` 覆盖式写入**通知值 = 结果原值**(>0 字节数 / 0 EOF / 负错误码)→ 清 `active`;
+ 无在位挂起则静默返回(防重复通知)。
+5. **唤醒与取结果**(调用方任务):所有 `net_*` API 的回执共用同一条任务通知通道,**两段式
+ 等待**防"入队→等待入口"间隙内通知丢失(细则见 §7.2 集成点 11)。
+6. **取消路径**(第 4/5 路,共同规则"**先结束挂起、再动芯片**"):close / drop_conn 首行先
+ `net_pending_settle_close` 结算(芯片 RX 有残数据 → 按字节数送达;无残数据 → 取消
+ CONNRESET),对账发现芯片静默回收走 `net_pending_cancel`,网络重建在 `net_init` 顶部
+ `net_pending_cancel_all`——**取消必通知**,否则调用方永久挂死(§5.2 第 4/5 路)。
+
+> **`buf` 生命周期**:挂起期间 `buf` 指针须保持有效至调用返回(挂起表按指针写入,§8)。
---
@@ -234,7 +229,7 @@ flowchart TD
- **存储**:**独立静态数组** `s_net_pending[NET_MAX_SOCKETS]`(不置于 `net_sock_t` 内)。
- **容量**:每 fd **至多一个**挂起请求。
- **占用**:结构 24 B/fd × 8 fd = **192 B**(`tripwire_logged` 落在对齐填充里,开启探针
- 不增加占用);另 `s_owner_task` 4 B。实测 SRAM +200 B。
+ 不增加占用);另 `s_owner_task` 4 B。
- **`infinite` 与 `deadline` 正交**:`to_ms == 0` 即"0 预算 = 无限等待"(无默认预算)。
### 4.2 为什么独立成表而不是塞进 `net_sock_t`
@@ -290,7 +285,7 @@ stateDiagram-v2
- **存储**:**独立静态数组** `s_net_send[NET_MAX_SOCKETS]`,与 `s_net_pending[]` **完全独立**
(各自一张表、各占一槽)。
- **容量**:每 fd **至多一个**挂起 send。
-- **占用**:结构 28 B/fd × 8 fd = **224 B**(实测 RW_IRAM1 +224 B,与 `s_net_pending` 的 192 B 并列)。
+- **占用**:结构 28 B/fd × 8 fd = **224 B**(RW_IRAM1,与 `s_net_pending` 的 192 B 并列)。
- **与 recv 表可并存**:同一 fd 可同时有一个挂起 recv 与一个挂起 send——收发两个方向互不阻塞
(SPI 由 netTask 串行,本就支持该并发)。
- **`deadline` 按无进展刷新**:这是与 recv 表 `deadline`(一次预算、不刷新)的**关键差异**,
@@ -298,7 +293,7 @@ stateDiagram-v2
---
-## 5. 完成与取消语义
+## 5. 挂起的请求会被什么结束
recv 与 send 两张挂起表**各自独立**,完成语义分别定义:recv 是"等一次到达即完成"(五路),
send 是"分块推进直到全部写完"(四路 + 显式取消)。两表可同一 fd 并存,互不阻塞(§4.5)。
@@ -341,9 +336,9 @@ send 是"分块推进直到全部写完"(四路 + 显式取消)。两表可
- 动作:查芯片残留 → 有则按数据完成;无则 `auto_relisten` 时 `net_listener_request()`
**登记**重建(动作在下一轮 `net_listener_tick`,≤1ms),随后按 `released_abnormal` 决定
`CONNRESET`(异常释放)或 `0`(正常 FIN)。
-- ⚠ 原内层 `state == CLOSED` 门已**上收到 `net_listener_tick`**(E17 单一所有者收口):
- 它防的是对已 `LISTENING` 的 fd 再 `open+listen` → `USE` 错误走进关闭分支、关掉正听着的
- Socket(U12 同源);这是**执行侧安全不变量**,不再由每个登记点各自复述(漏一处即事故)。
+- ⚠ **执行侧安全不变量**:不得对已 `LISTENING` 的 fd 再 `open+listen`——否则 `USE` 错误会
+ 走进关闭分支、关掉正听着的 Socket(U12)。这条门**由 `net_listener_tick` 单一所有者持有**,
+ 各登记点不复述(漏一处即事故)。
- 该处**保留即时登记**(而非只交 reconciler):它只在 `p->active != 0`(有挂起 recv)时
到达,属"消费者在等这条连接结局"的**消费者驱动路径**;删掉会把 CLOSED 窗口拉长到
reconciler 周期 + 隔离期(~100ms),期间调用方高频 `net_accept` → `[WD] task stall`。
@@ -375,11 +370,11 @@ send 是"分块推进直到全部写完"(四路 + 显式取消)。两表可
`CONNECT → ESTABLISHED` 是合法迁移,推断取消会误杀正在建链的等待。陈旧 resync
(芯片静默回收)由显式 `net_pending_cancel` 处理,不靠推断。
-### 5.5 挂起 send 的完成与取消(四路 + 显式取消)
+### 5.5 挂起 send 会被什么结束(四路 + 显式取消)
send 与 recv 语义不同:recv 是"等一次到达即完成",send 是"**分块推进直到全部写完**"。
因此 `net_send_pending_tick()` 每轮对每个挂起 send 按序判定 **①断连 → ②推进 → ③写完 → ④无进展超时**,
-另有显式取消路径(close / drop_conn / resync / 重建)。
+下文简称**四路**;另有显式取消路径(close / drop_conn / resync / 重建)。
| # | 条件 | 动作 | 设计要点 |
|---|---|---|---|
@@ -399,6 +394,14 @@ send 与 recv 语义不同:recv 是"等一次到达即完成",send 是"**分
经历上百次 `send_ready` 等待,总时长远超单块预算。若按总时长判,正常大传输会被误杀成超时——
故 `deadline` 每次成功写块后重置为"当前 + `NET_SEND_TIMEOUT_MS`",只判"块间停滞"。
+**末块边界:写完末块不再等发送缓冲**(`net_send_locked` 内层等待循环里置于超时/自旋判定
+**之前**的 `sent >= len` 守卫)——"等 SENBUF_FREE"的意义是"还有下一块要写";写完末块后
+剩余已是 0,此时任何"等缓冲区"型预算分支都不该再走。若继续走并落到登记,登记函数按
+`sent >= len → INVAL` 拒收(**无剩余块可挂起**),一次已完整交付芯片的发送会被报成失败。
+该守卫同时保证后续 `net_send_pending_register` 调用必伴随 `sent < len`,登记必获受理;
+末块路径零额外延迟。**自旋预算与"末块"是两个正交维度,组合必须显式覆盖**——回归由
+`test/host/ftp_regression.py`(RETR 1 KB 单块帧最敏感)覆盖。
+
---
## 6. 模块接口契约
@@ -406,7 +409,7 @@ send 与 recv 语义不同:recv 是"等一次到达即完成",send 是"**分
### 6.1 接口总表
全部声明在 `Drivers/BSP/NET/net_pending.h`,实现见 `Drivers/BSP/NET/net_pending.c`。
-recv 与 send 各一组(send 组前缀 `net_send_pending_`),共 14 个函数。
+recv 与 send 各一组(send 组前缀 `net_send_pending_`),共 16 个函数(2 个 static,对外 14 个)。
**公共 / recv 组**:
@@ -444,18 +447,14 @@ recv 与 send 各一组(send 组前缀 `net_send_pending_`),共 14 个函
32 位整体不切分;`result < 0` 时先 `net_set_errno(result)`(记入 netTask 任务槽位)。
- **失败码回传路径(两条)**:快速路径由 `net_process_messages` 回传该条消息**自身**的
错误码;挂起路径由 `net_pending_complete` 记 errno 并把负值作为通知值——两处取的都是
- netTask 自己的 errno 槽位(E-03 后按任务隔离),不受其他任务影响。
+ netTask 自己的 errno 槽位(按任务隔离),不受其他任务影响。
- **`NET_PENDING_REGISTERED` 哨兵**:`0x70000000`,取值不与字节数空间冲突;分派器据此
置 `do_notify = 0`,把回执推迟到真正的完成时刻,避免"登记成功"被误报为处理结果。
- **`net_recv_to` 与 `timeout_ms = 0`**:`net_recv_to` 拒收 `timeout_ms == 0`(`INVAL`),
因为 0 在 netTask 侧意味着"无限等待",与该接口"有界等待"的存在理由相反。
- **`-2` 映射**:`net_recv_to` 见到 `NET_ERR_TIMEDOUT` 返回 `-2`(**先于**通用 `< 0` 分支
判断,否则 `-9` 会被吞成 `-1`);超时只凭返回码判定,不依赖 errno(无跨任务竞态)。
-- **send 短写契约**:`net_send` 返回值恒为**已写入字节数**(`≥ 0`);`= len` 为全部写入,
- `< len` 为短写,调用方**必须**检查并重试剩余(`ui_net_sink` / `iec104_serv` 按
- `ret != total` 判短写;`lftpd_inet_write` 循环 `while (n > 0)`、`n <= 0` 视为致命)。
- 断连/超时/取消均以已发字节数结束,**不伪造"全部成功"、不改写为负错误码**——原因记
- netTask 自己的 errno 槽位。
+- **send 短写契约**:见 §8 调用方契约(同一件事的两面:模块侧保证、调用方侧义务)。
- **`net_send_sock`(netTask 内部版)**:其阻塞用法会把 `caller` 置为 netTask 自身,
登记必被"拒绝执行者自等待"守卫拒(`BUSY`)→ 返回 `-1`。故该接口只适合"发送缓冲可用"
的场合;netTask 内需长时间等待的发送须自行分片(见 §7.2 集成点 13)。
@@ -466,18 +465,18 @@ recv 与 send 各一组(send 组前缀 `net_send_pending_`),共 14 个函
|---|---|---|---|
| `NET_PENDING_TRIPWIRE` | `Drivers/BSP/NET/net_pending.h` | `0`(产品构建编译剔除,零成本) | 开启挂起超时留痕探针(见 §12) |
| `NET_RECV_TRIPWIRE_MS` | 同上(仅探针开启时定义) | `60000` | 留痕阈值 |
-| `NET_SEND_SPIN_MS` | `Drivers/BSP/NET/net_config.h` | `2` | **发送快速路径**的有界自旋预算(ms):单次"等发送缓冲腾出"超过本值即登记挂起 send,把 netTask 最坏停滞由 `NET_SEND_TIMEOUT_MS`(1 s)压到本值。健康链路实测块间等待 med 219 µs / max 378 µs(≈5× 余量),故正常发送不会触发挂起 |
+| `NET_SEND_SPIN_MS` | `Drivers/BSP/NET/net_config.h` | `2` | **发送快速路径**的有界自旋预算(ms):单次"等发送缓冲腾出"超过本值即登记挂起 send,netTask 最坏停滞即本值。健康链路块间等待 med 219 µs / max 378 µs(≈5× 余量),故正常发送不会触发挂起 |
| `NET_SEND_TIMEOUT_MS` | `Drivers/BSP/NET/net_config.h` | `1000` | 发送**无进展**超时(ms):快速路径的块间停滞上限;挂起路径的 `deadline` 亦按本值刷新。`0` = 不限 |
---
-## 7. 实现落点(集成步骤)
+## 7. 实现落点(改/查本模型看哪里)
本节是"**改/查本模型时看哪里**"的落地清单:每个集成点若缺失或改动,后果都是确定的。
### 7.1 集成点总表
-**recv 组**(编号 1–12 沿用一期):
+**recv 组**(编号 1–12):
| # | 集成点 | 文件 / 函数 | 调用 | 缺失后果 |
|---|---|---|---|---|
@@ -494,7 +493,7 @@ recv 与 send 各一组(send 组前缀 `net_send_pending_`),共 14 个函
| 11 | 唤醒通道(两段式) | `net_socket.c` `net_wait_result` | `xTaskNotifyWait` 零等待 + 阻塞 | 通知丢失 → 调用方挂死 |
| 12 | 双端注册 | `MDK-ARM/STM32F407-Demo.uvprojx` + `simulator/CMakeLists.txt` | `net_pending.c` 只入真机 | 编译缺失 / 模拟器误编(§7.4) |
-**send 组**(编号 13–19,二期;与 recv 组一一对称):
+**send 组**(编号 13–20,与 recv 组一一对称):
| # | 集成点 | 文件 / 函数 | 调用 | 缺失后果 |
|---|---|---|---|---|
@@ -507,7 +506,7 @@ recv 与 send 各一组(send 组前缀 `net_send_pending_`),共 14 个函
| 19 | 新 fd / close 清槽 | `net_socket.c` `alloc_socket` / `net_close_locked` 尾部 | `net_send_pending_clear(sockfd)` | fd 复用继承旧挂起 send → 新会话误写旧缓冲 |
| 20 | send 重开保险 | `net_listener.c` `net_listener_tick` | `net_send_pending_active(i)` 命中则 `continue` | 重开 Socket **摧毁在途传输**(半写的数据流被截断) |
-### 7.2 逐集成点要点
+### 7.2 顺序敏感 / 易错集成点
**1–2. `net_init` 顶部顺序**(`Drivers/BSP/NET/net_socket.c`):
@@ -521,47 +520,40 @@ net_pending_capture_owner() ← 捕获执行者
顺序不可交换:取消必须先于清零与复位,否则挂起调用方既收不到通知、其 `caller`
也可能随控制块一起失效。
-**3–4 / 14–15. `net_process_messages` 开头与分派**:
-
-```text
-net_listener_tick() ← D10
-net_pending_tick(s_net_socks) ← 挂起 recv 完成检查
-net_send_pending_tick(s_net_socks) ← 挂起 send 推进/完成检查
-net_reconcile_tick() ← 对账 + 重建
-while (osMessageQueueGet(..., 0) == osOK) { ... }
-```
-
-`NET_MSG_RECV` 分支:`msg.result = net_recv_locked(...)`;`NET_MSG_SEND` 分支:
+**3–4 / 14–15. `net_process_messages` 开头与分派**:四个 tick 按 §2.3 的固定顺序先跑,
+再排空消息队列。`NET_MSG_RECV` 分支:`msg.result = net_recv_locked(...)`;`NET_MSG_SEND` 分支:
`msg.result = net_send_locked(..., msg.caller)`;两者若等于 `NET_PENDING_REGISTERED`
则 `do_notify = 0`(**不发即时回执**)。send 分支还须把 `msg.caller`(调用方任务句柄)
-透传给 `net_send_locked`——挂起登记的目标。四个 tick 均先于消息分派。
+透传给 `net_send_locked`——挂起登记的目标。
-**5–6. 结算先于拆链**:`net_close_locked` / `net_drop_conn_locked` 的**第一行**就是
-`net_pending_settle_close(...)`——"关闭不得让等待中的调用方永久悬挂",且"关闭不丢已到达
-数据(芯片缓冲在 close 前可读)"。
-
-**8 / 20. 挂起保险(recv 与 send)**:`net_listener_tick` 对每个待重试 fd,先做状态门
-(仅 `LISTENING`/`CLOSED` 才重试),再 `net_pending_active(i) || net_send_pending_active(i)`
-命中即 `continue`——重开 Socket 会摧毁在途连接(recv)与在途传输(send 的半写数据流)。
+**5–6 / 8 / 20. 结算与保险**:`net_close_locked` / `net_drop_conn_locked` 的**第一行**就是
+`net_pending_settle_close(...)`——关闭既不得让等待中的调用方永久悬挂,也不得丢掉已到达的数据
+(芯片缓冲在 close 前可读)。`net_listener_tick` 对每个待重试 fd 先做状态门(仅
+`LISTENING`/`CLOSED` 才重试),再 `net_pending_active(i) || net_send_pending_active(i)` 命中即
+`continue`——重开 Socket 会摧毁在途连接与在途传输(半写的数据流被截断)。
**11. 两段式等待**(`net_wait_result`,所有 `net_*` API 共用):
- 第一段零等待拾取,消费"入队 → 等待入口"间隙内已落地的通知;
- 判定必须用返回值 `pdTRUE`/`pdFALSE`,**不能判值 ≠ 0**(0 = EOF 是合法通知);
- 清除掩码必须全 1(传 0 = 不清除,会残留上次结果被误读为本次结果);
-- 禁止在本路径加逐调用日志(曾占日志 98.6%,冲掉 `[LAT]` 证据行)。
+- **禁止在本路径加逐调用日志**——该路径每条 API 调用都过一遍,会淹没排查用的证据日志。
-**13. 发送有界自旋**(`net_send_locked`):两个等待循环(首块等待 + 每块发完等待)在
-`net_poll()` 之后判两件事,**顺序不可交换**:先判 `NET_SEND_TIMEOUT_MS`(1 s 停滞 → 记
-`TIMEDOUT` 收口),再判 `NET_SEND_SPIN_MS`(超预算 → `net_send_pending_register(...)` 返回
-哨兵)。把超时判定放在自旋判定**之前**,是为了保证 `NET_SEND_SPIN_MS > NET_SEND_TIMEOUT_MS`
-的误配下仍以超时收口,不会"永远登记不出去"。健康路径块间等待 med 219 µs ≪ 2 ms,**永不触发**
-挂起——故快速路径行为与挂起化前逐字节一致。
+**13. 发送有界自旋**(`net_send_locked`):两个等待循环都在 `net_poll()` 之后判预算,
+**判定顺序不可交换**:
-**14–19. send 组生命周期**:`net_send_pending_tick` 每轮按 ①断连 → ②推进 → ③写完 → ④超时
-(§5.5)推进;`net_close_locked` / `net_drop_conn_locked` / `net_reconcile_tick` 形态⑤ /
-`net_init` 顶部四处调 `net_send_pending_cancel(_all)`(**必通知**,否则调用方永久挂死);
-`alloc_socket` / `net_close_locked` 尾部调 `net_send_pending_clear`(静默归零)。
+- **外层(写块前,等发送缓冲腾出)**:断连 → `CONNRESET` 收口 → `NET_SEND_TIMEOUT_MS`
+ (1 s 停滞)→ `TIMEDOUT` 收口 → `NET_SEND_SPIN_MS` 超预算 → `net_send_pending_register(...)`。
+ 此处位于写块之前,循环条件保证 `sent < len`,登记必获受理。
+- **内层(写块后,等 `SENBUF_FREE`)**:**先判 `sent >= len`**——末块已全部交付芯片,
+ 直接跳出返回(§5.5 末块边界),再判超时、再判自旋预算。
+
+超时判定放在自旋判定**之前**,是为了保证 `NET_SEND_SPIN_MS > NET_SEND_TIMEOUT_MS` 的误配下仍以超时收口,
+不会"永远登记不出去"。健康路径块间等待 med 219 µs ≪ 2 ms,**永不触发**挂起——
+快速路径行为与非挂起实现一致。
+
+**14–19. send 组生命周期**:与 recv 组逐点对称(§7.1 表)——四处取消点(close / drop_conn /
+对账形态⑤ / `net_init` 顶部)**必通知**,两处清槽点(`alloc_socket` / close 尾部)静默归零。
### 7.3 改动检查清单
@@ -586,7 +578,7 @@ while (osMessageQueueGet(..., 0) == osOK) { ... }
- [ ] 所有出口**必须**经 `net_send_pending_complete`(不得直接清 `active`);
- [ ] 若新条件是"取消",**必须通知**(取消不通知 = 永久挂死);
- [ ] 结果恒为**已发字节数**(短写语义),**不得**改写为负错误码——原因走 `net_set_errno`;
-- [ ] 复核"无进展计时刷新"未被新条件破坏(按总时长判会误杀大传输);
+- [ ] 复核"无进展计时刷新"与**末块边界**(§5.5)未被新条件破坏;
- [ ] 更新本文 §5.5 与 `docs/网络/BSD_Socket_API_使用指南.md` §3.5。
**新增一个 socket fd 生命周期路径**:任何 `memset(p_sock)` / 释放 fd 的路径都须
@@ -618,56 +610,24 @@ while (osMessageQueueGet(..., 0) == osOK) { ... }
---
-## 9. 性能特征
+## 9. 性能与资源特征
| 指标 | 值 | 来源 / 条件 |
|---|---|---|
| 队列响应性(挂起连接下) | **92.5 ms** | `test/host/e02_probe.py`:挂起对时客户端 + FTP banner 探测 |
| M3 静默档帧周期 | med 58.9 / p95 69.9 / max 72.1 ms | `docs/测试说明/链路延迟测试说明.md` §6.3(零尖峰) |
-| M3 压力档帧周期 | med 60.1 / p95 66.4 / max 69.3 ms | 同上(零尖峰;p99 尾部 133.5 → 69.3 ms) |
-| netTask 栈实测峰值 | **1116 B / 3840 B(余 70%)**,FTP 传输期峰 **1164 B**;静态最坏链 ≈1.9 KB | `docs/内存管理/RAM使用分析与优化方案.md` §2.3;2026-10-14 台架复测(含 `net_send_pending_tick` 新增栈帧) |
+| M3 压力档帧周期 | med 60.1 / p95 66.4 / max 69.3 ms | 同上(零尖峰) |
+| netTask 栈峰值 | **1116 B / 3840 B(余 70%)**,FTP 传输期峰 **1164 B**;静态最坏链 ≈1.9 KB | `docs/内存管理/RAM使用分析与优化方案.md` §2.3 |
+| 挂起表静态 RAM | recv `s_net_pending[]` **192 B** + send `s_net_send[]` **224 B**(RW_IRAM1) | §4.1 / §4.5 |
-**结构性收益(为什么这些数字成立)**:压力档下 MCU 侧 `prod_cycle_mcu` 6706 µs 纹丝不动
-(min 6698 / max 6720)——队头阻塞工况不再向 UI 帧周期传导;`net_pending_tick` 在无挂起
-时是纯指针读(零 SPI),故 L5 队列往返段与挂起化前逐位不变。
+**为什么这些数字成立**:
-**SEND pending 的结构性收益**(二期,`net_send_pending_tick`):发送停滞期 netTask 不再
-被冻结——netTask 最坏停滞由 `NET_SEND_TIMEOUT_MS`(1 s)压到 `NET_SEND_SPIN_MS`(2 ms),
-且停滞不再顺延 `net_pending_tick` 的挂起 recv 唤醒。健康链路块间等待 med 219 µs / max 378 µs
-(≈5× 余量),故**正常发送完全不触发挂起**,快路径逐字节不变。
-静态代价:`s_net_send[]` **+224 B**(RW_IRAM1,8×28 B,见 §4.5)。
-
-> ✅ **2026-10-14 台架复测已补测**:SEND pending 改动后 netTask 栈峰值 **1116 B / 3840 B**
-> (开机态)~ **1164 B**(FTP 传输期),较此前 920 B 增加 ≈200 B(`net_send_pending_tick`
-> 新增一层调用帧),余量 69~70%,远低于 1.9 KB 静态最坏链。复测同步发现并修复了
-> 末块挂起越界缺陷(见 §9.1)。
-
-### 9.1 末块挂起越界缺陷(2026-10-14 台架复测发现并修复)
-
-**现象**:SEND pending 二期上线后台架复测,`ftp_regression.py` 间歇性 1/11(部分步骤
-`write error, ret=-1`),偶可 11/11;基线固件同测 6/6 恒过 → 判定为**本次改动引入的回归**。
-
-**根因**:`net_send_locked` 的内层"等本块发完(SENBUF_FREE)"循环位于**写块之后**。
-写完**末块**时 `sent` 已累加到 `len`,若该次 SENBUF_FREE 等待超过 `NET_SEND_SPIN_MS`
-(2 ms),代码径自调用 `net_send_pending_register(..., sent, ...)`。而登记函数的参数
-校验含 `sent >= len → INVAL` 拒绝(本模型下 `sent == len` 意味着**无剩余块可挂起**),
-返回 -1 被 `net_send_locked` 直接透传 → 一次**实际已完整交付芯片**的发送被报成失败。
-`ftp_regression` 的 RETR 1KB 是单块帧(一次即 `sent == len`),故首当其冲。
-
-**修复**:内层等待循环**开头**判 `sent >= len` → 直接跳出等待返回 `sent`
-(== `len`,完整交付)——末块写完后已无下一块,"等 SENBUF_FREE"是纯保守自旋,
-其等待顺延为下一批的责任。该守卫**置于超时/自旋判定之前**,末块路径零额外延迟;
-同时它保证后面的 `net_send_pending_register` 调用必伴随 `sent < len`,登记必获受理。
-外层写块前的挂起判定不受影响(循环条件 `sent < len` 未被改写)。
-
-**为什么此前没暴露**:健康链路块间等待 med 219 µs ≪ 2 ms,末块等待极少越过预算;只有
-在末块 SENBUF_FREE 变慢(FTP 数据连接刚建立、客户端读速不匹配等)时才触发,故表现为
-间歇性。**教训**:自旋预算与"末块"边界是两个正交维度,二者的组合必须显式覆盖——
-写完末块后"还差多少没写"的答案已是 0,此时任何"等缓冲区"型预算分支都不该继续走。
-
-**复测结论**(修复后):`ftp_regression` 连续 **19 次 11/11**(含守卫重排版后重烧 5 次)、
-`ftp_integrity_test` 20 轮 × 64 KB STOR/RETR SHA-256 **20/20 一致**(两轮,吞吐稳定
-≈80 KB/s)、设备日志零 write error、无 forbidden 标记、ST-LINK 回读 `No difference found`。
+- 压力档下 MCU 侧 `prod_cycle_mcu` 纹丝不动(min/max 相差 <1%)——队头阻塞工况不再向 UI
+ 帧周期传导;`net_pending_tick` 在无挂起时是纯指针读(零 SPI),队列往返段不受影响。
+- **send 侧**:发送停滞期 netTask 不再被冻结——netTask 最坏停滞由 `NET_SEND_TIMEOUT_MS`
+ (1 s)压到 `NET_SEND_SPIN_MS`(2 ms),且停滞不再顺延 `net_pending_tick` 的挂起 recv
+ 唤醒。健康链路块间等待 med 219 µs / max 378 µs(≈5× 余量),故**正常发送完全不触发挂起**,
+ 快路径行为不变。
---
@@ -675,23 +635,21 @@ while (osMessageQueueGet(..., 0) == osOK) { ... }
| 项 | 现状 | 说明 |
|---|---|---|
-| **SEND 路径**(执行者占用问题)✅ **二期已覆盖** | `net_send_locked` 快路径有界自旋 ≤ `NET_SEND_SPIN_MS`(2 ms);超预算登记挂起 send,由 `net_send_pending_tick` 按主循环节拍分块推进(`NET_SEND_CHUNK_MAX` = 1 KB/块),调用方在自己上下文睡 | 停滞期 netTask **不再冻结**:挂起 send 每轮至多写一块,`net_pending_tick` 与消息队列照常推进——挂起 recv 的最坏唤醒延迟不再被 ≈1 s 顺延。健康链路块间等待 med 219 µs ≪ 2 ms,快速路径行为与挂起化前逐字节一致。`NET_SEND_TIMEOUT_MS` 降级为**无进展**超时(每次成功写块刷新,见 §5.5) |
-| **ACCEPT 路径未覆盖**(调用方轮询问题) | `net_accept_locked` 单次 `net_poll` 后**立即**返回 `ESTABLISHED` 或 `WOULDBLOCK`——**不占用 netTask**(0 停滞) | 代价不在执行者,而在**调用方轮询**:没有"阻塞到有连接"的入口,各服务(`ui_net_sink` / `iec104_serv` 100 ms / `time_sync` / `lftpd`)各自手写轮询循环,每轮一次完整 RPC 往返(入队 + 通知 + 唤醒),并带来一个轮询周期级的 accept 延迟抖动。pending 化的收益是**消除轮询 churn 与抖动**,不是解队头阻塞 |
-| **超时精度** | 随主循环节拍(~1ms),被长操作占用时顺延 | 一期文档化、不修;SEND 化后该顺延面**收窄**(发送停滞不再顺延整层) |
-| **挂起超时留痕探针** | 编译期选项 `NET_PENDING_TRIPWIRE` 默认关(排查挂死时开);未接自检 selftest | 二期补齐自检 |
+| **SEND 路径**(执行者占用问题)✅ **已纳入本模型** | `net_send_locked` 快路径有界自旋 ≤ `NET_SEND_SPIN_MS`(2 ms);超预算登记挂起 send,由 `net_send_pending_tick` 按主循环节拍分块推进(`NET_SEND_CHUNK_MAX` = 1 KB/块),调用方在自己上下文睡 | 停滞期 netTask **不再冻结**:挂起 send 每轮至多写一块,`net_pending_tick` 与消息队列照常推进。健康链路块间等待 med 219 µs ≪ 2 ms,快路径行为不变。`NET_SEND_TIMEOUT_MS` 是**无进展**超时(每次成功写块刷新,见 §5.5) |
+| **ACCEPT 路径未纳入**(调用方轮询问题) | `net_accept_locked` 单次 `net_poll` 后**立即**返回 `ESTABLISHED` 或 `WOULDBLOCK`——**不占用 netTask**(0 停滞) | 代价不在执行者,而在**调用方轮询**:没有"阻塞到有连接"的入口,各服务(`ui_net_sink` / `iec104_serv` 100 ms / `time_sync` / `lftpd`)各自手写轮询循环,每轮一次完整 RPC 往返(入队 + 通知 + 唤醒),并带来一个轮询周期级的 accept 延迟抖动。pending 化的收益是**消除轮询 churn 与抖动**,不是解队头阻塞 |
+| **超时精度** | 随主循环节拍(~1ms),被长操作占用时顺延 | 文档化、不修;发送停滞不再顺延整层(send 已在模型内),顺延面只剩单次长操作本身 |
+| **挂起超时留痕探针** | 编译期选项 `NET_PENDING_TRIPWIRE` 默认关(排查挂死时开);未接自检 selftest | 待办见 `docs/任务规划/登记簿.md` N-4 |
| **模拟器不可建模** | `net_socket_sim.c` 单线程 Winsock 无法复现**挂起模型内部机制**(外部语义已由包装层按 blocking 属性/预算模拟) | 挂起表 / tick / 通知路径在模拟器上零覆盖,只能硬件验证(§7.4) |
| **单 fd 单挂起** | 每 fd 至多一个挂起 recv,第二个被 `BUSY` 拒绝 | 由登记守卫保证;同 fd 并发 recv 本就无意义(per-fd 串行语义) |
> **SEND 与 ACCEPT 的 pending 化收益性质不同,勿混为一谈**。判据只有一个:
> **执行者被占多久**——SEND 挂起化前 ≤1 s/次(占用),ACCEPT 0(不占用)。
-> - SEND 是**执行者占用**问题,与 RECV 同源:pending 化直接解队头阻塞,收益真实,
-> **二期已落地**(§5.5、§7.1 send 组);
+> - SEND 是**执行者占用**问题,与 RECV 同源:pending 化直接解队头阻塞,收益真实,**已落地**
+> (§5.5、§7.1 send 组);
> - ACCEPT 是**调用方轮询**问题:执行者本就零占用,"accept 慢"来自调用方轮询周期,
> 不是 netTask 卡住——pending 化换的是 RPC 开销与延迟抖动,**不解决任何队头阻塞**。
-> 故 ACCEPT 优先级低于 SEND,**仍未落地**(登记项见 `docs/任务规划/登记簿.md` N-4)。
-
-**二期扩展设计**(ACCEPT 挂起化、门禁清单、改动量)见
-`docs/任务规划/基线与待确认.md` §2.1。SEND 的挂起化已按该设计落地,见本文 §4.5 / §5.5 / §7.1。
+> 故 ACCEPT 优先级低于 SEND,**尚未落地**(登记项见 `docs/任务规划/登记簿.md` N-4,
+> 设计基线见 `docs/任务规划/基线与待确认.md` §2.1)。
---
@@ -711,6 +669,8 @@ while (osMessageQueueGet(..., 0) == osOK) { ... }
7. **send 结果恒为已发字节数**——短写是一等公民;断连 / 超时 / 取消**不伪造**全部成功、
**不把结果改写为负错误码**,原因只经 `net_set_errno` 留痕。send 停滞计时按**无进展**
刷新(每次成功写块重置),绝不按总时长判(会误杀大传输)。
+8. **写完末块即结束,不再等发送缓冲**——"等缓冲区"只为下一块服务;`sent == len` 时任何
+ 预算分支都不得再走(§5.5 末块边界)。
---
@@ -723,7 +683,7 @@ while (osMessageQueueGet(..., 0) == osOK) { ... }
探针的处理:`net_pending_tick` 对这样的 fd **只打一条 `[ERR]` 日志**(每 fd 一次,
防刷屏),然后让它继续等——**绝不强制结束、绝不伪造 TIMEDOUT 返回值**。为什么不用
"强制超时"兜底:强制结束会把"唤醒路径有 bug"这个**缺陷**伪装成一次**普通超时**——
-调用方拿到假结果,缺陷永远潜伏(旧实现正是"到点 +1s 伪造 TIMEDOUT",已废除)。
+调用方拿到假结果,缺陷永远潜伏。
开关:`NET_PENDING_TRIPWIRE`(`Drivers/BSP/NET/net_pending.h`,默认 0 = 编译剔除、产品
零成本)——排查"调用方挂死唤不醒"时置 1 重烧。**开与关语义完全一致**:探针只留痕,
@@ -734,26 +694,14 @@ while (osMessageQueueGet(..., 0) == osOK) { ... }
## 13. 源码索引
-| 文件 | 符号 | 职责 |
+| 文件 | 关键符号 | 职责 |
|---|---|---|
-| `Drivers/BSP/NET/net_pending.h` | `NET_PENDING_REGISTERED` / `NET_PENDING_TRIPWIRE` / `NET_SEND_CHUNK_MAX` | 哨兵、编译期开关、分块上限(唯一定义处) |
+| `Drivers/BSP/NET/net_pending.h` | `NET_PENDING_REGISTERED` / `NET_PENDING_TRIPWIRE` / `NET_RECV_TRIPWIRE_MS` / `NET_SEND_CHUNK_MAX` | 哨兵、编译期开关、分块上限(唯一定义处);16 个函数声明(§6.1) |
| `Drivers/BSP/NET/net_config.h` | `NET_SEND_SPIN_MS` / `NET_SEND_TIMEOUT_MS` | 发送自旋预算与无进展超时 |
-| `Drivers/BSP/NET/net_pending.c` | `net_pending_capture_owner` | 捕获执行者句柄(防自等待,recv/send 共用) |
-| `Drivers/BSP/NET/net_pending.c` | `net_pending_register` | 登记挂起 recv(两守卫) |
-| `Drivers/BSP/NET/net_pending.c` | `net_pending_tick` | 每轮 recv 完成检查(①②③ + 探针) |
-| `Drivers/BSP/NET/net_pending.c` | `net_pending_complete`(static) | recv 唯一通知出口(通知 + 清槽 + 记 errno) |
-| `Drivers/BSP/NET/net_pending.c` | `net_pending_clear` | recv 幂等静默清槽 |
-| `Drivers/BSP/NET/net_pending.c` | `net_pending_cancel` / `net_pending_cancel_all` | recv 显式取消(resync / 重建) |
-| `Drivers/BSP/NET/net_pending.c` | `net_pending_settle_close` | recv 关闭前结算(排空或取消) |
-| `Drivers/BSP/NET/net_pending.c` | `net_pending_active` | recv 挂起查询(纯 RAM 读,监听器保险) |
-| `Drivers/BSP/NET/net_pending.c` | `net_send_pending_register` | 登记挂起 send(两守卫;记录进度起点 `sent`) |
-| `Drivers/BSP/NET/net_pending.c` | `net_send_pending_tick` | 每轮 send 推进/完成检查(①断连②推进③写完④超时) |
-| `Drivers/BSP/NET/net_pending.c` | `net_send_pending_complete`(static) | send 唯一通知出口(结果 = 已发字节数) |
-| `Drivers/BSP/NET/net_pending.c` | `net_send_pending_clear` / `net_send_pending_cancel` / `net_send_pending_cancel_all` | send 静默清槽 / 显式取消 / 重建取消 |
-| `Drivers/BSP/NET/net_pending.c` | `net_send_pending_active` | send 挂起查询(纯 RAM 读,监听器保险) |
-| `Drivers/BSP/NET/net_socket.c` | `net_recv_locked` / `net_recv` / `net_recv_nb` / `net_recv_to` | 快速路径与挂起 recv 登记入口 |
-| `Drivers/BSP/NET/net_socket.c` | `net_send_locked` / `net_send` / `net_send_sock` | 快速路径(有界自旋)与挂起 send 登记入口 |
-| `Drivers/BSP/NET/net_socket.c` | `net_process_messages` / `net_wait_result` | 分派(含 `NET_MSG_SEND` 透传 `caller`)、哨兵抑制、两段式唤醒 |
-| `Drivers/BSP/NET/net_socket.c` | `net_close_locked` / `net_drop_conn_locked` / `alloc_socket` / `net_init` | 结算、清槽、取消、捕获执行者(recv + send 双侧) |
+| `Drivers/BSP/NET/net_pending.c` | recv 组 / send 组全部实现(两张挂起表 + 完成出口 + 探针) | 本模型主体(§4、§5、§6) |
+| `Drivers/BSP/NET/net_socket.c` | `net_recv_locked` / `net_send_locked` | 快速路径 + 挂起登记入口(含末块守卫与有界自旋) |
+| `Drivers/BSP/NET/net_socket.c` | `net_process_messages` / `net_wait_result` | 四 tick 节拍与消息分派(含 `NET_MSG_SEND` 透传 `caller`)、哨兵抑制、两段式唤醒 |
+| `Drivers/BSP/NET/net_socket.c` | `net_init` / `net_close_locked` / `net_drop_conn_locked` / `alloc_socket` | 捕获执行者、结算、清槽、取消(recv + send 双侧) |
+| `Drivers/BSP/NET/net_events.c` | `net_reconcile_tick` | 形态⑤(芯片静默回收)取消两侧挂起 |
| `Drivers/BSP/NET/net_listener.c` | `net_listener_tick` | 消费 `net_pending_active` / `net_send_pending_active` 作挂起保险 |
| `App/task/net_task.c` | `net_task_func` | 主循环节拍(`net_poll` → `net_process_messages`) |
diff --git a/docs/网络/网络自愈架构设计.md b/docs/网络/网络自愈架构设计.md
index dca866ac..502b2857 100644
--- a/docs/网络/网络自愈架构设计.md
+++ b/docs/网络/网络自愈架构设计.md
@@ -1,36 +1,26 @@
# 网络自愈架构设计
-> **定位**:本文是 CH395F 网络自愈架构的**现状权威描述**——设计结构、原理、关键参数及其实证锚点。
-> 文中「收敛阶段 0~3」指 2026-10 自愈架构的分阶段实施(过程已归档,见 §12)。
-> **本文已吸收原《网络Socket监听生命周期设计说明》(B10 根治,2026-10-14 合并)**:
-> 其原理与规范层论证并入 §1.1~§1.4、FUN_PARA bit3 契约并入 §4.9、代码级事实并入 §9、
-> 决策记录并入 §10、模块化边界并入 §11;原文档已删除,过程全文在 git 历史
-> (`git log --oneline -- docs/子系统设计/网络Socket监听生命周期设计说明.md`)。
-> 架构审计过程(T1~T4 三路审计与 39 条编号发现、E0~E15 实验方法与原始记录、分阶段实施清单)
-> 已完成使命并删除——过程全文在 git 历史(`git show 1a35b36^:docs/网络/N34-自愈审计/`,目录合并前
-> 7 份文档)。实证结论已沉淀进:`docs/问题记录/CH395F_Trap_Records.md`(TRAP-NET-01~21 与 B 系列)、
-> `docs/芯片手册/CH395F.md`。
-> 仍被代码与文档引用的 12 条编号发现保留在 §7 速查表维持可寻址。
+> **定位**:CH395F 网络自愈架构的**现状权威描述**——架构与机制、为什么这样设计、关键参数及其
+> 实证锚点、红线与延后项。
+> **不写历史**:审计过程、分阶段实施勾选清单、台架原始记录、重构过程账目一律不入本文;
+> 需要考古见 §12。正文只描述**当前如何组织**与**为什么这样组织**。
+> **编号是冻结标识**:`I*` / `E*` / `F-*` / `形态*` / `U*` / `TRAP-NET-*` / `D*` 被代码注释、
+> `test/host/` 探针与 `docs/问题记录/` 直接引用——**看懂即可,勿重排改名**,对照见下方导读。
## 导读:本文用到的编号与术语
-本文跨越 2026-10 的多轮审计与实验,正文里混用了**多套编号**与**机制代号**。
-它们都是**冻结标识**——代码注释、`test/host/` 探针脚本、`docs/问题记录/` 都直接引用,
-**看懂即可,不要重排或改名**(改号即断链)。查下表:
-
| 记号 | 是什么 | 在哪查 |
|---|---|---|
| `I1`~`I8` | 本架构的**不变量**(Invariant)——违反即有实测后果 | §2 |
-| `E0`~`E15`、`E-02` | **台架实验**编号(E = Experiment):每个实验是「假设 → 方法 → 判据 → 门禁哪个实施决策」 | §5.2(原始记录已归档,见 §12) |
+| `E0`~`E19`、`E-02` | **台架实验**编号(E = Experiment):每个实验是「假设 → 方法 → 判据 → 门禁哪个决策」 | §5.2(原始记录已归档,见 §12) |
| `F-NET-nn` / `F-DRV-nn` / `F-MS-nn` | 审计的**编号发现**:NET 层 / 驱动层 / 状态镜像层 | §7(只保留仍被引用的 12 条) |
| `M1`~`M5` | 实验固件里的**候选修改代号**:M1 沉降无条件化 · M2 门控换 `net_listener_armed` · M3 可疑门 · M4/M4-lite 删沉降段全局读即清 · M5 `sock_reset` quiescent 排空 | §4.1、§4.3 |
| `形态①`~`形态⑤` | `net_reconcile_tick` 的五类**失同步判据** | §4.1 |
-| `U1`~`U15` | 状态迁移的**"无人负责"组合**(审计枚举出的架构欠债) | `docs/问题记录/CH395F_Trap_Records.md` B10 行 |
-| `TRAP-NET-nn`、`B3`/`B10` | **陷阱记录**条目与 B 系列(待修/已修清单) | 同上 |
-| `N-nn` | `docs/任务规划/登记簿.md` 的**任务/发现编号** | 该登记簿(⚠ `N-32` 等**已销项**——登记簿行按"规划只留待办、完成项删除"约定已删,本文引用处即其最终事实描述) |
+| `U1`~`U15` | 状态迁移的**"无人负责"组合**(架构欠债清单) | `docs/问题记录/CH395F_Trap_Records.md` B10 行 |
+| `TRAP-NET-nn`、`B3`/`B10` | **陷阱记录**条目与 B 系列 | 同上 |
+| `N-nn` | `docs/任务规划/登记簿.md` 的**任务/发现编号**(⚠ 已销项的号不再可查,本文引用处即其最终事实描述) | 该登记簿 |
| `D1`~`D7` | 本文**决策记录** | §10 |
-| `T1`~`T4` | 审计的**三个视角**(已归档,勿再引用) | git 历史(§12) |
-| `P0`~`P3`、`阶段 0`~`阶段 3` | 已完成的**实施阶段**(前者 = B10 根治,后者 = 自愈收敛),其勾选清单勿再引用 | §12 |
+| `自愈收敛阶段 N` | 2026-10 的分阶段实施标签,**只存在于历史文档与变更记录**(代码注释已全部改为平实语言);勾选清单已归档(§12)——**勿用它叙述设计,也勿再写回注释** | §12 |
| `TSL` / `TSC` / `TSD` | SPI 命令与数据之间的**时序间隔**要求 | `docs/芯片手册/CH395F.md` §7.4 |
| `n30` | 压测脚本 `n30_repro.py`(:21 背靠背 STOR 30 轮),自愈回归的**主判据** | `test/host/` |
| 沉降窗 / 隔离期 | 释放会话后、重建监听前的**最短等待**(GRACEFUL 100 ms / QUARANTINE 400 ms) | §4.3 |
@@ -38,7 +28,7 @@
| 镜像 | net 层持有的 8 态 `net_sock_state_t`——**会话投影**,不是芯片事实源 | §3.3 |
> ⚠ **字母 `E` 有两套编号,靠连字符区分**:
-> - `E0`~`E15`(**无**连字符)= 本文的**自愈实验**编号,逐条见 §5.2;
+> - `E0`~`E19`(**无**连字符)= 本文的**自愈实验**编号,逐条见 §5.2;
> - `E-02`(**带**连字符)= `docs/问题记录/错误码传播审计.md` 的**审计发现**编号——
> §5.2 末行引用它是为了记录该发现的**收口台架**,**不是**实验编号
> (证据:`test/host/e02_probe.py` 文档串自述"错误码传播审计.md E-02 的 timeSync 触发形态");
@@ -46,21 +36,23 @@
## 1. 为什么是这个架构
-三个芯片事实决定架构形态(全部台架实证,见 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-09/-17/-18/-20
-与 §5.2 锚点):
+三个芯片事实决定架构形态(全部台架实证,见 `docs/问题记录/CH395F_Trap_Records.md`
+TRAP-NET-09/-17/-18/-20 与 §5.2 锚点):
1. **芯片永不自动回 LISTEN**——单连接复用模型(监听与数据通道复用同一 Socket),
"监听常驻"只能是软件承诺(I4)。
2. **事件面不是可靠唯一信源**——事件可被其它合法读者消费,且状态面存在事件覆盖不了的幽灵
- (E7b;E15:幽灵 = 芯片如实报告旧连接拆链进行中,非谎报)→ 正确性必须靠回读芯片事实 + 有界超时兜底(I3)。
+ (E7b;E15:幽灵 = 芯片如实报告旧连接拆链进行中,非谎报)→ 正确性必须靠回读芯片事实 +
+ 有界超时兜底(I3)。
3. **close 有沉降窗、RX 残渣有害**——带非空 RX 关闭会拆链停滞、吞下一会话 SYN 且自持
(E4 变体 C 0/20);沉降窗不定长(TRAP-NET-09/-20)→ 复用前清理必须内联进事务(I5)。
-主流对照(WIZnet ioLibrary / Zephyr / FreeRTOS+TCP / mbed / lwIP 移植)印证同一判断:
-没有任何主流方案维护第二份 socket 状态镜像、也没有"周期修正镜像"的对账;芯片怪癖一律
-**内联进事务、收在驱动层、以有界超时兜底**。本仓旧"五层自愈"补丁的结构性根源是三件事的
-组合:net 层持有损 8 态镜像并作为决策依据 + 状态迁移只靠中断事件 + 重建与事务脱钩。
-现架构(§2~§4)即按此收敛的结果。
+主流 offload 驱动(WIZnet ioLibrary / Zephyr / FreeRTOS+TCP / mbed / lwIP 移植)里**没有任何
+一家维护第二份 socket 状态镜像**,也没有"周期修正镜像"的对账;芯片怪癖一律**内联进事务、
+收在驱动层、以有界超时兜底**。本架构按同一形态收敛(§2~§4),代码级对照见 §1.5。
+
+由此确定本层**不做**的三件事:不把中断事件当正确性来源(I3)、不把软件镜像当事实源(I1)、
+不把监听重建散给多个所有者(I2)。
### 1.1 芯片层事实:LISTEN 不是能力,是一个会被消耗掉的槽位状态
@@ -72,9 +64,8 @@
| 断开后芯片行为由 FUN_PARA bit3 分流:=0 芯片自动关 Socket(→`0x00/0x00`);**=1 芯片什么都不做(→`0x05/0x00`),MCU 必须自己 CLOSE** | `docs/芯片手册/CH395F.md` §5.23 L572、§5.52 L795② |
⚠ 本仓 `ch395f_set_fun_para(0x08)`(`Drivers/BSP/NET/net_socket.c`,bit3 `SOCKET_CLOSE`=1,
-TRAP-NET-15/-20 定案为"保留排空现场")⇒ **断开后芯片必然停在 `0x05/0x00`**。
-探针看到的"孤儿芯片态"不是芯片异常,而是本仓这个配置的**直接且合法的后果**;
-bit3=1 的必要性与安全边界见 §4.9。
+定案为"保留排空现场",论证见 §4.9 / D4)⇒ **断开后芯片必然停在 `0x05/0x00`**。
+探针看到的"孤儿芯片态"不是芯片异常,而是这个配置的**直接且合法的后果**。
### 1.2 规范层:本仓做法与 TCP/POSIX 的所有权契约正面冲突
@@ -94,20 +85,22 @@ EOF 只在 **recv 那一刻**本层为 CLOSED 才给出(`net_recv_locked`,`D
若该 fd 已被别的路径 relisten 回 LISTENING,同一个 recv 恒返 `WOULDBLOCK`。
这是一个**边沿**,不是一个**电平**——正是不变量 I2/I3 要消除的形状。
-由此派生出"谁重建监听"的**12 个所有者**:栈内 8 处(对账、`handle_disconnect_event` 监听分支、
-`net_accept_locked` CLOSED 分支、`net_recv_locked`、`net_pending_tick`、`NET_MSG_RELISTEN`、
-`net_resync_listen`、`net_listen_locked`)+ 应用层 4 处(lftpd、time_sync、ui_net_sink、net_task 代次);
-状态迁移点 15 个,其中 **15 个"无人负责"组合**(U1~U15,见 `docs/问题记录/CH395F_Trap_Records.md` B10 行)。
-收敛后周期性决策已唯一化为 §4.1 形态③④、消费者驱动即时登记按 §4.2 保留(3 处)——
-上段是欠债的**原始形状**,保留以说明"为什么需要 I2"。
+fd 别名让这个边沿**必须有人接**,而"谁重建监听"在类型上无法唯一化:同一 fd 既当监听器又当
+连接时,任何持有 fd 的路径都"看起来有资格"重建。本架构的解法是把两件事彻底拆开(§4.1 / §4.2):
-三个已实测的派生失效(机理保留,供判读 I2/I3 被违反时的症状):
+- **判据唯一**:周期性"该不该重建"只在 `net_reconcile_tick` 形态③④;
+- **动作唯一**:真正下发 open+listen 只在 `net_listener_tick` 一处,其余路径一律
+ `net_listener_request()` **只登记意图**;
+- **消费者驱动即时登记 3 处**(accept CLOSED 分支 / recv EOF / 挂起 recv 断连)与周期兜底
+ 构成双层,**不可删**(§4.2)。
-| 失效 | 机理 | 现状 |
+三个已实测的派生失效(保留作**症状判读**:I2/I3 被违反时就是这个样子):
+
+| 失效 | 机理 | 现状机制 |
|---|---|---|
-| 孤儿监听(B10) | 边沿没人接 → net 层等应用重建、应用等 net 层给 EOF、对账只裁 ESTABLISHED/LISTENING 直接跳过、芯片不会自己回 LISTEN → **四方互等** | 已收口(§4.1 形态④ + §4.6 `net_drop_conn`) |
-| 消费者卡死 | 抢先 relisten 把慢消费者的 EOF **永久**吞掉 → `g_client` 不清、新连接被旧会话静默吸收 | 已由 `session_dead` 粘滞电平消除(§3.3 不变量 I) |
-| 监听被误关 | `net_can_read` 在非 ESTABLISHED(**含 LISTENING**)返 1 → 消费者 recv 得 `WOULDBLOCK` → 判"真错误"后无护栏 `net_close` 等于**关掉监听 fd** | 已加 `g_client != g_listen` 护栏(`Drivers/BSP/UI/ui_net_sink.c`);落 TRAP-NET-09 沉降窗 |
+| 孤儿监听(B10) | 边沿没人接 → net 层等应用重建、应用等 net 层给 EOF、对账只裁 ESTABLISHED/LISTENING 直接跳过、芯片不会自己回 LISTEN → **四方互等** | 对账形态④ 认领孤儿(§4.1)+ 应用侧 `net_drop_conn`(§4.6) |
+| 消费者卡死 | 抢先 relisten 把慢消费者的 EOF **永久**吞掉 → `g_client` 不清、新连接被旧会话静默吸收 | `session_dead` 粘滞**电平**消除(§3.3) |
+| 监听被误关 | `net_can_read` 在非 ESTABLISHED(**含 LISTENING**)返 1 → 消费者 recv 得 `WOULDBLOCK` → 判"真错误"后无护栏 `net_close` 等于**关掉监听 fd** | 应用侧 `g_client != g_listen` 护栏(`Drivers/BSP/UI/ui_net_sink.c`)+ 红线 8(§6) |
### 1.4 为什么不走"芯片原生多连接模式"(A′ 出局)
@@ -119,7 +112,7 @@ EOF 只在 **recv 那一刻**本层为 CLOSED 才给出(`net_recv_locked`,`D
「CH395F 多连接模式不支持多监听(类型:架构)」,处置即 `fun_para=0x00`。
本产品有 21/8888/7003 **三个常驻监听** + 会话期动态 PASV 监听。
2. 池分配按**源端口匹配**(`docs/芯片手册/CH395F.md` §9.2.6 L1295),多监听器的池会互相串。
-3. 历史实测:「多连接模式下最多接受 3 个客户端(Socket 1~3),Socket 4~7 的 CONNECT 中断从不触发」、
+3. 实测:「多连接模式下最多接受 3 个客户端(Socket 1~3),Socket 4~7 的 CONNECT 中断从不触发」、
「多个 Socket 数据互相覆盖」——即 TRAP-NET-02/-11。
且 FUN_PARA **初始化后不可改**(`docs/芯片手册/CH395F.md` §5.52 L795⑥),这是开机一次性决策,
@@ -127,9 +120,6 @@ EOF 只在 **recv 那一刻**本层为 CLOSED 才给出(`net_recv_locked`,`D
### 1.5 与主流 offload 驱动的代码级对照("是不是过度设计"的正面回答)
-> 2026-10-14 补。此前本节只有"主流方案无一维护第二份 socket 状态镜像"一句**结论**,
-> 没有代码级对照——于是"沉降/重试这套机器是不是我们想多了"会被反复问。这张表就是答案。
-
**对照对象**:WIZnet **ioLibrary** `Ethernet/socket.c`(W5100~W6300 官方驱动,TCP offload
芯片的事实标准)。其关闭/重开路径原文:
@@ -157,7 +147,7 @@ int8_t listen(...) { while (getSn_SR(sn) != SOCK_LISTEN) { close(sn); return SOC
分别对应 listen 前排清 SINT(§4.10)、`s_relisten_*[]`、沉降窗(§4.3)、D10 预算(§4.2)。
**这套形状不是本仓发明,是 offload 芯片驱动的通用形态。**
-**那本仓多出来的体积从哪来——三条约束的兑换券**:
+**那本仓多出来的体积从哪来——四条约束的兑换券**:
| 约束 | ioLibrary | 本仓 | 复杂度落点 |
|---|---|---|---|
@@ -177,15 +167,12 @@ TIME_WAIT/2MSL 藏在栈内部,应用永远看不见 FIN_WAIT_2;监听器所
(§1.2 那句"需要靠铁律维持的正确性就是架构欠债的凭证"的另一面:欠债要还,但还债的货币
由芯片选型决定)。
-**本仓可安全减的部分(诚实清单)**——该文件的**内部组织度**(搬移边界 / 窄接口 / 可机械
-检验判据)见 §11,本节只管"这台机器该不该存在":`net_listener.c` 实测 **386 行 = 注释 155 +
-代码 204**(原 508 = 注释 277 + 代码 204;2026-10-14 注释精简 −44%,20 条陷阱的现场叙述迁入
-本文,**代码逐行未改**)——**7 个函数**(4 公开 + 3 内部)/ 8 个 per-socket 数组,
-**代码量本来就不大,过去大的是 20 条陷阱的现场注释**;精简后那些叙述在本文件里。
+**本仓可安全减的部分(诚实清单)**——本节只管"这台机器该不该存在",内部组织度见 §11:
+`net_listener.c` = **7 个函数**(4 公开 + 3 内部)/ 8 个 per-socket 数组。
可省:`s_relisten_settle_dead[]`(恒 = `settle_start + NET_RELISTEN_SETTLE_MS`,
可就地算)、`s_relisten_capped[]`(可压成 `quarantine` 的一个 bit)——合计 16 B。
**不可省**:沉降窗(E4 变体 C 带残渣关闭 0/20)、D10(= ioLibrary 的 TIMEOUT 等价物)、
-listen 前排清(§4.10)、登记/执行分离(E17,12 个所有者收敛成 1)。
+listen 前排清(§4.10)、登记/执行分离(§4.2,E17)。
**真要大幅减掉这套机器,唯一杠杆是芯片**——而 §1.4 已用三条硬证据否掉多连接模式,且
`FUN_PARA` 初始化后不可改。WCH 官方博客原话(E15 已引):"客户端主动断连并立即重新发起
@@ -199,20 +186,20 @@ WCH 官方博客 cnblogs sw111/18451802。
## 2. 设计原则(不变量)
-| # | 不变量 | 违反的后果(均实测) |
+| # | 不变量 | 违反的后果 |
|---|---|---|
-| I1 | **单一事实源**:芯片状态的权威副本 = 驱动每 Socket 缓存,唯一刷新点 = `net_poll` 每轮全量刷新;net 层 8 态 `net_sock_state_t` 只是"会话投影"(展示/门控),决策点读缓存或主动回读 | 有损镜像产生"镜像说 A、芯片是 B"窗口——旧五层补丁全在为这个窗口付税(F-MS-8) |
-| I2 | **单一重建所有者**:唯一周期性监听重建决策者 = `net_reconcile_tick` 形态③④;事件处理器与沉降登记**只置事实、不动作** | 多所有者竞争——"谁先看到 DISCONNECT 谁重建"→ 抢先 relisten 吞慢消费者的 EOF(N-32 根因、B10) |
+| I1 | **单一事实源**:芯片状态的权威副本 = 驱动每 Socket 缓存,唯一刷新点 = `net_poll` 每轮全量刷新;net 层 8 态 `net_sock_state_t` 只是"会话投影"(展示/门控),决策点读缓存或主动回读 | 有损镜像产生"镜像说 A、芯片是 B"的决策窗口(F-MS-8) |
+| I2 | **单一重建所有者**:唯一周期性监听重建决策者 = `net_reconcile_tick` 形态③④;事件处理器与沉降登记**只置事实、不动作** | 多所有者竞争——"谁先看到 DISCONNECT 谁重建"→ 抢先 relisten 吞掉慢消费者的 EOF(B10) |
| I3 | **事件只负责提前唤醒,正确性靠回读 + 有界超时** | 纯事件驱动 → 事件被读清消费竞态吞掉后监听静默死亡(TRAP-NET-17;E14 复核:事件非芯片侧丢失,是被消费) |
| I4 | **监听常驻 = 软件承诺**(`auto_relisten`),不是芯片属性 | 出向连接被误判孤儿 / 监听永久失效 |
| I5 | **复用前清理内联进事务**:`sock_reset` = 排空 RX → CLOSE → 排空事件(quiescent)→ 等 byte0=00H | 带非空 RX 关闭 → 拆链停滞、SYN 被吞、级联自持(E4 变体 C 0/20) |
-| I6 | **分层**:驱动持事实与机械;net 层持策略与所有权;应用层零自愈责任 | 应用层重试/手动自愈 = 第 6/7 层(F-DRV-9,已退役) |
-| I7 | **全局中断寄存器唯一读者 = `net_poll`**(F-NET-7;测试/台架代码同样禁自行读取,等事件一律直读本 Socket SINT——用户裁定 2026-10-13) | B3 级事故:吞其它 Socket 的 CONNECT/DISCONNECT/RECV_OK 边沿事件 |
+| I6 | **分层**:驱动持事实与机械;net 层持策略与所有权;应用层零自愈责任 | 应用层重试/手动自愈 = 又一层补丁(F-DRV-9) |
+| I7 | **全局中断寄存器唯一读者 = `net_poll`**(F-NET-7;测试/台架代码同样禁自行读取,等事件一律直读本 Socket SINT) | 吞掉其它 Socket 的 CONNECT/DISCONNECT/RECV_OK 边沿事件(B3 级事故) |
| I8 | **不留未证明的复杂度**(TRAP-NET-20 铁律) | 正面执行:U15 内化不立项(E6 命中=0);TSL 变体只作待用补丁(E12) |
**去抖 + 冷却**:失配连续 N 轮才动手(分级,见 §4.1)+ 动手后 1s 冷却。去抖与冷却本是
-"镜像世界"的自偿成本(F-MS-9)——驱动持事实后探针去抖已删(读缓存无瞬态噪声);
-reconciler 保留分级去抖,因为形态①要让位 M3 窗、形态②要覆盖芯片 10ms 级可见窗(E2)。
+"镜像世界"的自偿成本(F-MS-9)——判据改读驱动缓存后,探针侧去抖已无必要(缓存无瞬态噪声);
+reconciler 保留分级去抖,因为形态① 要让位 M3 窗、形态② 要覆盖芯片 10ms 级可见窗(E2)。
## 3. 架构总览
@@ -221,7 +208,7 @@ reconciler 保留分级去抖,因为形态①要让位 M3 窗、形态②要
| 层 | 持有什么 | 关键接口 |
|---|---|---|
| 驱动层 `Drivers/BSP/CH395F/ch395f.c/.h` | 每 Socket 状态缓存 + 类型化回读/谓词;每 Socket 事件读即清护栏;复合复位状态机;命令完成判据(状态回读);链路态缓存 | `ch395f_sock_state_cached` / `ch395f_sock_events` / `ch395f_sock_reset_begin` / `ch395f_sock_reset_poll` / `ch395f_cmd_done` / `ch395f_glob_event_feed` / `ch395f_link_up_cached` |
-| net 层 `net_socket.c` / `net_listener.c`(netTask 单所有者) | fd↔Socket 映射、消息队列串行化、L3 pending recv、事件分发与回调;会话投影与粘滞电平;单一 reconciler;监听生命周期(armed/D10/settle);同步复位包装 | `net_poll` / `net_reconcile_tick` / `net_listener_request` / `net_listener_settle` / `net_listener_tick` / `net_sock_reset_sync` / `net_drop_conn` |
+| net 层 `net_socket.c` / `net_events.c` / `net_listener.c` / `net_pending.c`(netTask 单所有者) | fd↔Socket 映射、消息队列串行化、挂起 recv/send;事件分发与回调;会话投影与粘滞电平;单一 reconciler;监听生命周期(armed/D10/settle);同步复位包装 | `net_poll` / `net_reconcile_tick` / `net_listener_request` / `net_listener_settle` / `net_listener_tick` / `net_sock_reset_sync` / `net_drop_conn` |
| 应用/服务层(`ui_net_sink` / `iec104_serv` / `lftpd` / `time_sync`) | 只管 fd 与数据;放弃会话走 `net_drop_conn`;EOF 不得 `net_close` 监听 fd | — |
### 3.2 主循环自愈三 tick
@@ -229,15 +216,15 @@ reconciler 保留分级去抖,因为形态①要让位 M3 窗、形态②要
| tick | 节奏 | 职责 |
|---|---|---|
| `net_poll` | 主循环每轮(≈1ms) | INT# 引脚 → 全局事件汇聚(唯一读者)→ SINT 分派 → **驱动缓存全量刷新** → 链路位喂驱动 → 步进 `ch395f_sock_reset_poll` |
-| `net_process_messages` | 每轮 | RPC 消息处理;开头每轮 `net_pending_tick`(挂起 recv 结束)+ `net_listener_tick`(D10) |
+| `net_process_messages` | 每轮 | 开头按序跑三个非阻塞 tick:`net_listener_tick`(D10 重建**唯一执行者**)→ `net_pending_tick`(结束挂起 recv)→ `net_send_pending_tick`(推进挂起 send),随后排空 RPC 消息队列 |
| `net_reconcile_tick` | 100ms | 唯一周期**对账**——判据在此,重建动作在 §4.2 唯一执行者(§4.1) |
### 3.3 状态模型(会话投影)
- `net_sock_state_t` 8 态 = **本层会话投影**,非芯片状态事实源(I1)。
-- `session_dead`:"会话已结束"的粘滞**电平**(N-32 P1 不变量)——消费者不再被抢先
- relisten 饿死;relisten / 形态④ / listen / RESYNC 刻意不清它(契约注释在位)。
-- `lost_conn_seen`:M3 可疑电平(§4.1)。
+- `session_dead`:"会话已结束"的粘滞**电平**——消费者不再被抢先 relisten 饿死;
+ relisten / 形态④ / listen / RESYNC 刻意不清它(契约注释在 `net_types.h`)。
+- `lost_conn_seen`:M3 可疑电平(§4.1.6)。
- `last_peer_port` + `released_abnormal`:幽灵甄别与异常释放簿记。
### 3.4 全景图
@@ -285,123 +272,110 @@ reconciler 保留分级去抖,因为形态①要让位 M3 窗、形态②要
| ① | ESTABLISHED ↔ 芯片 CLOSED / TCP_CLOSED | 复用 `handle_disconnect_event`(置事实 + GRACEFUL 沉降) | **4 轮**(≈400ms,M3 让位窗) | 无(置事实) |
| ② | LISTENING ↔ 芯片已握手(TCP=ESTABLISHED) | `GET_REMOT_IPP` 幽灵甄别 → 补 accept(`handle_connect_event`) | **1 轮**(E2:LISTEN 可见 ≤10ms 无长尾) | 1 次 SPI 读 |
| ③ | LISTENING ↔ 监听型 fd 而芯片不在 LISTEN | 登记重建请求(动作在 I2 唯一执行者 `net_listener_tick`) | 2 轮 | 无(转交) |
-| ④ | 本层 CLOSED(auto_relisten)而芯片**不在** LISTEN | 登记重建请求(同 ③,原 pump②) | 2 轮 | 无(转交) |
+| ④ | 本层 CLOSED(auto_relisten)而芯片**不在** LISTEN | 登记重建请求(同 ③) | 2 轮 | 无(转交) |
| ⑤ | ESTABLISHED ↔ 芯片已回 LISTEN(静默回收,F-NET-2) | RESYNC 语义只修本层(转登端口 + 取消挂起 recv/send),零命令 | 2 轮 | **零** |
| U11 | 本层 CLOSED ↔ 芯片**正在** LISTEN | 只重同步本层为 LISTENING(转登端口) | **不去抖、不占冷却** | **零** |
-⚠ **U11 不是形态④**(本表 2026-10-14 修正:旧表把 U11 误标成 ④、且**漏了真 ④ 的行**,
-还把 U11 的去抖写成 2 轮)。两者判据同族(本层 CLOSED + 监听型 fd)而结论相反:
+⚠ **U11 不是形态④**。两者判据同族(本层 CLOSED + 监听型 fd)而结论相反:
**芯片在听 = 本层滞后 → 只改状态;芯片不在听 = 监听真没了 → 登记重建**。搞混的代价就是
U12 同源事故——对正听着的 Socket 重下发 open+listen 会吃 USE 错、走进关闭分支,
把健康监听器关掉。
表注:形态② 命中时的对端端口回读是本 tick 唯一按需 SPI。
-#### 4.1.4 每个形态为什么长这样、怎么验的
+#### 4.1.4 每个形态为什么长这样
**① 连接已失**:成因 = 连接被拆而 DISCONNECT 没被 netTask 收到,本层停在 ESTABLISHED、
消费者挂在 recv 上等永远不会来的数据。动作复用断开状态机(含陈旧事件甄别 + 状态迁移 +
-事件派发),**只置事实、不做重建**(N-32,重建交 ③④)。验法 = 注入吞掉该端口的 DISCONNECT
-分派(前提:在 i104 的 200ms accept 轮询窗内由 PC 发 RST,让芯片真的拆链)→
-`reconcile: chip closed (0x05/0x00) -> disconnect`。日志里 `open` 仍是 `0x05`、
-`tcp_state=0x00` → 判据是**"或"**,故成立(E18 第 2 轮 4/4 救回)。
-⚠ 第 1 轮曾**误判命中**:拿 PC 侧"能连上"当判据,而设备日志里根本没有 `chip closed` 行
-(干活的是 ③)→ **判据是设备日志,不是端口可达性**(E18 方法论第一条)。
+事件派发),**只置事实、不做重建**(重建交 ③④)。判据是**"或"**(`open` 仍可为 `0x05`、
+`tcp_state=0x00` 即成立)。⚠ 判读只认**设备日志**的 `chip closed` 行,**端口可达性不是判据**
+(E18 方法论第一条)。
**② 握手已完成、事件没收到**:成因 = 芯片做完三次握手而 CONNECT 没送到,本层以为在听、
-应用不会 accept,数据全堆在芯片 RX,应用侧**完全看不见**这条连接。⚠ **幽灵甄别(N-30 /
-TRAP-NET-20)是本形态最凶处**:拆链收尾窗口里芯片状态会**如实**显示 ESTABLISHED(旧连接
-未拆净,E15 契约,非谎报),此时补 accept 就把新数据发给已死的旧连接 = 生产 `LIST#4`
-数据被吞的直接成因;故仅当"对端端口非零且 = `last_peer_port`"时拦截,`peer==0` 不拦截
+应用不会 accept,数据全堆在芯片 RX,应用侧**完全看不见**这条连接。⚠ **幽灵甄别
+(TRAP-NET-20)是本形态最凶处**:拆链收尾窗口里芯片状态会**如实**显示 ESTABLISHED
+(旧连接未拆净,E15 契约,非谎报),此时补 accept 就把新数据发给已死的旧连接 = 生产数据被
+吞的直接成因;故仅当"对端端口非零且 = `last_peer_port`"时拦截,`peer==0` 不拦截
(对端端口提交有滞后,不得因此挡掉救援)。去抖 1 轮 = 最激进,因为它是"白捡的救援"、代价小。
-⚠ **② 不在 E18 靶单上**(需吞 CONNECT 事件)→ 其依据是幽灵甄别 + E2/E10 台架记录,
-**勿写成"已负向注入验证"**。
+⚠ 本形态**未经负向注入验证**(需吞 CONNECT 事件),依据是幽灵甄别 + E2/E10 台架记录,
+**勿写成"已验证"**。
**③ 监听器失位**:成因 = 芯片上的监听器没了(close / 超时 / 回收)且没发事件,本层还以为
-在听 → 端口拒连。验法 = 绕过本层全部簿记,直接对**空闲**监听器(`:7003` 远显,无连接、
-无消费者 = 最干净的靶)发 `ch395f_close_socket()` → `chip not listening (0x00/0xFF,
-was state=3) -> relisten`(`was state=3` 即 LISTENING)。E18 第 1、2 轮两次独立复现。
+在听 → 端口拒连。最干净的靶 = 空闲监听器(无连接、无消费者)。
**④ 孤儿监听**:成因 = 会话结束、本层已收口 CLOSED,但监听没重建成功(或重建事件丢了)。
与 ③ 的差别**只在本层状态**:③ 是"以为还在听",④ 是"知道会话结束了、但监听器本该在"。
-**它是常态唯一在干活的形态**(归因见 §4.1.5)。④ 不是靶,却被 E18 第 1 轮的失败反复
-误打中(没吞掉芯片自发 DISCONNECT → 本层塌成 CLOSED → 活落到 ④)——这些"打歪了"的样本
-正是 §4.1.5 结论的来源。
+**它是常态唯一在干活的形态**(归因见 §4.1.5)。
**⑤ 芯片静默回收**:成因 = 芯片**自己**把 Socket 收回 LISTEN 且**不发 DISCONNECT 中断**
-(run8 台架实证),本层挂着一个已不存在的"活连接"。动作纪律 = **零芯片命令**:只改本层
+(台架实证),本层挂着一个已不存在的"活连接"。动作纪律 = **零芯片命令**:只改本层
state、转登 `last_peer_port`、清 `lost_conn_seen`、**取消挂起 recv/send**(必通知,防调用方
挂死)。为什么绝不能顺手 close + 重 open:芯片那个监听器是**健康的**,重下发会摧毁它 →
-端口永久拒绝,**run8 实测 170s 拒连**(与 U11 同一条纪律)。验法 = 四步机:开盲区吞掉芯片
-自发的一切中断 + close → **等芯片真回 CLOSED** → 绕过本层 open+listen → 确认落到
-`0x05/0x01` 而本层仍 ESTABLISHED → `chip re-listened w/o DISCONNECT -> resync`。
-两次失败各暴露一个硬事实(详见 §5.2 E18):状态机漏 `step=1` → 每 ~1ms 重复 close →
-**SPI 风暴** → `[ERR][WD] task stall`;等待预算 200 轮太短——芯片 close 后停在
-**FIN_WAIT_2(`0x05/0x06`)实测 972 轮 ≈1s**(E15 的 2MSL 长尾),靠 PC 侧 RST 才打断。
-这条实测同时是 §4.3 沉降上限"为什么必须存在"的时间尺度证据。
+端口长期拒绝连接(实测 170 s 拒连;与 U11 同一条纪律)。
+注入验证还顺带量出两个硬事实(§5.2 E18):状态机漏步进会 **SPI 风暴** → `[ERR][WD] task
+stall`;芯片 close 后停在 **FIN_WAIT_2(`0x05/0x06`)≈1 s**(E15 的 2MSL 长尾)——这条时间
+尺度就是 §4.3 沉降上限必须存在的理由。
#### 4.1.5 为什么 ①③⑤ 常态 0 命中
-消费者驱动的 pending recv / recv EOF 比 100 ms 对账**更快**,多数场景先把本层收口成
+消费者驱动的挂起 recv / recv EOF 比 100 ms 对账**更快**,多数场景先把本层收口成
CLOSED → 由形态④ 接手。关键在两条快路径的判据(`net_pending.c` 的挂起 recv 结束判定 +
`net_socket.c` 的 recv 快路径):**只看本层 `state`、从不读芯片**——所以只要"有人在等",
事件流就先赢,①③⑤ 永远轮不到。推论就是职责边界:**①③⑤ 管的是"没有人在等"的场景**
(空闲监听器被静默 close、连接被静默拆掉而无人 recv、芯片静默回收),与 ④ **互补而非
-重复**。怎么打中的见 §4.1.4、证据见 §5.2 E18。→ **三条均不可降级为只留痕**;⑤ 的
-"取消挂起 recv/send"尤其不可省,否则消费者在死连接上干等到 deadline。
+重复**。→ **三条均不可降级为只留痕**;⑤ 的"取消挂起 recv/send"尤其不可省,否则消费者在
+死连接上干等到 deadline。
-#### 4.1.6 M3 可疑门与沿革
+#### 4.1.6 M3 可疑门
-- **M3 可疑门**(`handle_disconnect_event` ESTABLISHED 分支):芯片 `0x05/0x00`
- (Socket OPEN 但 TCP 槽 0x00——活连接应为 0x04,逻辑矛盾态)且 `lost_conn_seen==0`
- 且 `recv_bytes==0` 且**芯片 RX 有在途数据**(不可毁的消费者资产,与 closed-with-data
- 救回同源)→ 不释放,置可疑电平,交形态① 电平二次确认;双锁存 = 强证据直接放行。
- 风暴态免疫实证(E9 R3:预置残渣后 n30 0/50;`:21 @0ms×50` 50/50 零误伤——健康 FIN
- 芯片为 0x07、静默回收为 0x00/0x00,均不命中判据)。
-- 沿革:形态③④ = 原 N-32 `net_listener_pump`(阶段 3 并入);形态⑤ = 原
- `net_resync_listen` API 语义(阶段 1 删 API 并入)。
+`handle_disconnect_event` ESTABLISHED 分支:芯片 `0x05/0x00`(Socket OPEN 但 TCP 槽 0x00——
+活连接应为 0x04,逻辑矛盾态)且 `lost_conn_seen==0` 且 `recv_bytes==0` 且**芯片 RX 有在途
+数据**(不可毁的消费者资产,与 closed-with-data 救回同源)→ 不释放,置可疑电平,
+交形态① 电平二次确认;双锁存 = 强证据直接放行。
+
+风暴态免疫(E9 R3:预置残渣后 `n30` 0/50;`:21 @0ms×50` 50/50 零误伤——健康 FIN 芯片为
+0x07、静默回收为 0x00/0x00,均不命中判据)。
### 4.2 监听生命周期(单一所有者:登记 / 执行分离)
- **`auto_relisten`**:`net_listen` 成功即置位;所有重建路径的门控标志(I4)。
- **`net_listener_armed`**:唯一监听门控。⚠ **必须放行 ESTABLISHED**——它是单连接模型
- 的"待 accept"电平;漏放行时 i104 的 200ms 轮询抢不过形态② 去抖而整场饿死
- (E10 实测 18/18 FAIL → 修复后 18/18 PASS,127s→8.3s)。
+ 的"待 accept"电平;漏放行时 i104 的 200ms 轮询抢不过形态② 去抖而整场饿死(E10)。
- **登记侧 `net_listener_request(fd, reason)`**:只置意图(`reason` 仅用于留痕归因),
- **不碰芯片**。登记点 5 处:reconciler 形态③④ / accept CLOSED 分支 / recv EOF /
- `net_drop_conn` / 挂起 recv 断连(pending③)。已在请求中**不重置重试时刻**(防饥饿)。
-- **执行侧 `net_listener_tick()`**(netTask 每轮;原名 `net_listener_retry_tick`,只做
- D10 到点重试):**唯一**调用 `net_relisten_locked` 的地方,门集集中一处——
- 控制块有效 + `in_use` + 状态 ∈ {LISTENING, CLOSED} + 监听型(`auto_relisten` /
- STREAM / `local_port≠0`)+ **芯片已在 LISTEN 即视为请求已达成、静默了结**
- (U12 的结构性防护,读驱动状态缓存零 SPI)+ 挂起保险(L3)+ 30ms 节奏 / 2s 预算
- (D10)。"预算耗尽"不是终态——reconciler 形态③④ 周期重新登记。
- ⚠ 收口前是 `net_listener_try`(当场动作、**无状态门**),故 4 个调用点各自补
- `state == CLOSED` 门——漏一处即 U12 事故形态;门集上收后调用点不再需要(E17)。
-- **消费者驱动即时登记 ×3**(accept CLOSED 分支 / recv EOF / pending③):与周期兜底
- 构成双层——**不可删**(P2a 实测:删消费者驱动 → CLOSED 窗口拉长 → `[WD] task stall`)。
- 收口后动作延后一轮(≤1ms)执行,恢复窗 p50 199~208ms 与收口前持平(E17)。
+ **不碰芯片**,因此**不需要状态门**。登记点 5 处:reconciler 形态③④ / accept CLOSED 分支 /
+ recv EOF / `net_drop_conn` / 挂起 recv 断连。已在请求中**不重置重试时刻**(防饥饿)。
+- **执行侧 `net_listener_tick()`**(netTask 每轮):**唯一**调用 `net_relisten_locked` 的地方,
+ 门集集中一处——控制块有效 + `in_use` + 状态 ∈ {LISTENING, CLOSED} + 监听型
+ (`auto_relisten` / STREAM / `local_port≠0`)+ **芯片已在 LISTEN 即视为请求已达成、静默了结**
+ (U12 的结构性防护,读驱动状态缓存零 SPI)+ **挂起保险**(`net_pending_active` /
+ `net_send_pending_active` 命中则跳过,防重开 Socket 摧毁在途连接与在途传输)+ 30ms 节奏 /
+ 2s 预算(D10)。"预算耗尽"不是终态——reconciler 形态③④ 周期重新登记。
+ ⚠ **门集只在这一处**:任何"当场动作 + 自带门"的写法都等于把 U12 风险复制到每个调用点
+ (漏一处即事故)。
+- **消费者驱动即时登记 ×3**(accept CLOSED 分支 / recv EOF / 挂起 recv 断连):与周期兜底
+ 构成双层——**不可删**(台架实测:删掉后 CLOSED 窗口拉长到对账周期 + 隔离期,期间调用方
+ 高频 `net_accept` → `[WD] task stall`)。动作延后一轮(≤1ms)执行,恢复窗与当场动作
+ 持平(E17)。
- `net_listener_settle(fd, mode)`:登记"会话已结束,重建前需沉降/隔离"——**只置事实**,
动作只在 `net_listener_tick` 发生(I2)。
### 4.3 沉降与排渣隔离(TRAP-NET-18)
- 两档:**GRACEFUL 100ms 底**(正常释放)/ **QUARANTINE 400ms 底**(异常释放:芯片
- TIMEOUT、FTP ABORT);**800ms 上限只留痕、不放行**(E19 反事实 A/B → 2026-10-14 降级)。
-- **800ms 上限为什么不再"强制放行"**(E19 实测):芯片未回 CLOSED 时强行 `open` 注定吃
- USE 错(TRAP-NET-09),放行与不放行的端口恢复时刻**同一分布**(放行 +512 / 不放行
- +505·533 / 降级后实测 +509 ms)——恢复由"芯片何时回 CLOSED"决定。**"监听器不会永久失效"
- 的真正兜底 = D10 预算到期 + 对账形态④ 重新登记**,不是放行;勿再把本处当兜底。
- 降级后上限到点只打**一行** `keep waiting` 且**隔离标志不清**(下一轮继续排空残渣);
- 若每轮都留痕会 30ms 刷两行(构建 B 实测踩过,故加 `s_relisten_capped` 只留首末)。
+ TIMEOUT、FTP ABORT);**800ms 上限只留痕、不放行**。
+- **800ms 上限为什么不放行**(E19 反事实 A/B):芯片未回 CLOSED 时强行 `open` 注定吃
+ USE 错(TRAP-NET-09),放行与不放行的端口恢复时刻**同一分布**——恢复由"芯片何时回
+ CLOSED"决定。**"监听器不会永久失效"的真正兜底 = D10 预算到期 + 对账形态④ 重新登记**,
+ 不是放行;勿再把本处当兜底。上限到点只打**一行** `keep waiting` 且**隔离标志不清**
+ (下一轮继续排空残渣);留痕只留首末(每轮都打会 30ms 刷两行)。
- **隔离期持续排空 RX**:中止后残渣(实测 rxdrain=2048 = 芯片单 Socket RX 缓冲满容,
E4)会被下一条连接读走 → 隔离期边等沉降窗边排空。
-- **GRACEFUL 无条件化**(M1,F-NET-15):旧 `recv_bytes≠0` 门是背靠背失效循环的放大器
- ——被陈旧 DISCONNECT 杀死的零数据会话免沉降 → 残渣杀下一条连接、自持循环;
- 无条件化后 n30 8%→96.7%(E7b)。⚠ M1 必须与 M4-lite(删沉降段全局读即清)同批,
- 否则沉降成为每次断连热路径后放大 F-NET-7 供给面(E7 实测教训)。
+- **GRACEFUL 无条件化**(M1,F-NET-15):`recv_bytes≠0` 门是背靠背失效循环的放大器
+ ——被陈旧 DISCONNECT 杀死的零数据会话免沉降 → 残渣杀下一条连接、自持循环;无条件化后
+ `n30` 8%→96.7%(E7b)。⚠ M1 必须与 M4-lite(删沉降段全局读即清)**同批**,否则沉降成为
+ 每次断连热路径后放大 F-NET-7 供给面(E7 实测教训)。
-### 4.4 驱动原语(阶段 2 落地,E2/E3/E4 定案形态)
+### 4.4 驱动原语
- **类型化回读 + 缓存**:`ch395f_sock_state_cached(sn)` / `ch395f_get_sock_state(sn)` /
`ch395f_sock_is_listening(sn)`;`open=0` 时 `tcp_state` 返回哨兵 `0xFF`
@@ -409,17 +383,16 @@ CLOSED → 由形态④ 接手。关键在两条快路径的判据(`net_pendin
每轮全量(单次回读 18.1µs,E2)。
- **事件护栏**:`ch395f_sock_events(sn)` 只读本 Socket SINT(读即清),永不触碰全局
寄存器(I7)。测试/台架代码等事件同样直读 `ch395f_get_sock_int_status(sn)`——
- 不用全局位图门控(门控观察分不清"芯片未锁存"与"全局位被读清",TRAP-NET-20
- 观察混淆源已就此消除)。**listen/relisten 的排事件读清必须在 LISTEN 命令之前**
- (E14:post-listen 读清会吞掉即时到达的新连接 CONNECT——生产与台架同源根除)。
+ 不用全局位图门控(门控观察分不清"芯片未锁存"与"全局位被读清")。
+ ⚠ **listen/relisten 的排事件读清必须在 LISTEN 命令之前**(E14:post-listen 读清会吞掉
+ 即时到达的新连接 CONNECT——生产与台架同源根除)。
- **复合复位状态机**:`ch395f_sock_reset_begin(sn, mode)` / `ch395f_sock_reset_poll(sn)`
——非阻塞、`net_poll` 每轮步进(同步包装 600ms 放弃后复位机也不搁浅)。
- 序列(E4 定案):① 读空 RX(`CLEAR_RECV_BUF` 等价可用,**取读空**——字节数可观测
- 可留痕,保 `docs/芯片手册/CH395F.md` §5.52 bit3=1"读出残留数据后再关闭"契约)
- ② CLOSE_SOCKET ③ 排空本 Socket 事件(drain-until-quiescent:连续两轮无新位)
- ④ 轮询 byte0=00H。**LISTEN 态 Socket 的 CLOSE 是二段**(E2 补充发现:一次 CLOSE
- 只拆监听,byte0 恒留 05H 不自落)——状态机内置二段补发(只补发一次)。
- GRACEFUL 100ms / QUARANTINE 400ms 底,600ms 封顶。
+ 序列(E4 定案):① 读空 RX(**取读空**——字节数可观测可留痕,保芯片"读出残留数据后再
+ 关闭"的契约,`docs/芯片手册/CH395F.md` §5.52 bit3=1)② CLOSE_SOCKET ③ 排空本 Socket 事件
+ (drain-until-quiescent:连续两轮无新位)④ 轮询 byte0=00H。**LISTEN 态 Socket 的 CLOSE 是
+ 二段**(E2:一次 CLOSE 只拆监听,byte0 恒留 05H 不自落)——状态机内置二段补发(只补发
+ 一次)。GRACEFUL 100ms / QUARANTINE 400ms 底,600ms 封顶。
- **命令完成判据**:`ch395f_cmd_done` = **状态回读**(open 完成→byte0=05H;listen
完成→TCP=01H;close 完成→byte0=00H)——GET_CMD_STATUS 对这三条命令无判别力且陈旧
错误码(0x17)跨命令持续存活(E3);GET_CMD_STATUS 仅保留内核前慢命令与"命令级被拒"
@@ -428,30 +401,31 @@ CLOSED → 由形态④ 接手。关键在两条快路径的判据(`net_pendin
→ 重读 PHY 更新 `link_up` 缓存 + 变化计数;reconciler 订阅留痕(运行期拔网线不再静默)。
- net 层同步包装 `net_sock_reset_sync`(listen/connect open 失败重试、close 快路径)
——忙等统一,close/connect 路径不再读清全局寄存器(I7)。
-- **⚠ 监听重建(relisten)尚未接进本原语(E16)**:现序列 = 一次 CLOSE + CLOSE 前排空 +
- 等 CLOSED,**不等价**于 net 层旧隔离序列的"隔离窗内持续排空/关闭"(残渣在 CLOSE 之后
- 才落进 RX),也不与"同一芯片 Socket 被上层立即复用"共存(复位机会拆掉刚建的监听)。
- 承接前置条件与 A/B 实测数据见 §8 延后项 1;在此之前 `net_listener.c` 保留自建序列。
+- ⚠ **监听重建(relisten)不接进本原语**:现序列 = 一次 CLOSE + CLOSE 前排空 + 等 CLOSED,
+ **不等价**于监听隔离段的"隔离窗内持续排空/关闭"(残渣在 CLOSE 之后才落进 RX),也不与
+ "同一芯片 Socket 被上层立即复用"共存(复位机会拆掉刚建的监听)。E16 实测回退,
+ 三条前置语义与回归数据见 §8 延后项 1;在此之前 `net_listener.c` 保留自建序列。
-### 4.5 recv 语义与 pending(阶段 1.5)
+### 4.5 recv 语义与挂起(pending)
- `net_recv()` 默认**无限阻塞**;`net_recv_nb()` + `net_setblocking(fd, on)`(模式是
socket 属性);`net_recv_to()` = 显式预算便捷入口(`timeout_ms=0` 仍拒 INVAL)。
- 唤醒源 = 数据 / EOF / close / 网络重建 CONNRESET;会话死亡兜底 = 形态①(≈400ms)。
-- **L3 pending recv**:阻塞 recv 不独占 netTask(挂起登记 + 主循环节拍完成);
+- **挂起 recv / send**:阻塞 recv/send 不独占 netTask(挂起登记 + 主循环节拍完成),
+ 架构与完成语义 → `docs/网络/Net层阻塞模型架构设计.md`。
**挂起超时留痕探针**(编译期开关 `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)`
- → 本层收口 CLOSED(不清 fd)→ `auto_relisten` 即时登记重建请求(`net_listener_tick` 执行)
- (失败交 D10 + 形态④)。
+- 语义:`net_pending_settle_close` 先结束挂起 recv(回传结果 + 唤醒调用方)→
+ `net_sock_reset_sync(QUARANTINE)` → 本层收口 CLOSED(不清 fd)→ `auto_relisten` 即时登记
+ 重建请求(`net_listener_tick` 执行;失败交 D10 + 形态④)。
- **幂等**(非 ESTABLISHED/CONNECTING 直接返 0);**EOF 路径不得调**
(`auto_relisten` 已接管,再调属重复拆)。
- 芯片物理边界(E-02 台架):`close` **无 RST 语义**——对端配合(收到 FIN 后其内核
@@ -459,7 +433,7 @@ CLOSED → 由形态④ 接手。关键在两条快路径的判据(`net_pendin
不 close:芯片停 `FIN_WAIT_2` 长尾,**监听恢复受限于对端 close**,兜底 = 芯片
TIMEOUT/KeepAlive 拆僵尸 30~90s(E13)——不是应用可加速的路径。
-### 4.7 KeepAlive(服务器侧,E11/E13 定案)
+### 4.7 KeepAlive(服务器侧)
- `handle_connect_event` LISTENING 分支开启,参数 **60s/5s/3**(规定位置见 `docs/芯片手册/CH395F.md` §5.56;
参数须 500ms 倍数且 IDLE>INTVL,TRAP-NET-06)。
@@ -475,8 +449,7 @@ CLOSED → 由形态④ 接手。关键在两条快路径的判据(`net_pendin
- `NET_U15_PROBE` 插桩留仓(产品构建编译消失):`net_poll` 每 32 轮对在用 fd 探测
`GET_RECV_LEN>0 && !readable`(两段确认),armed 日志 + 命中低频日志。
-- **E6 裁决(2026-10-13,~4h 常态 + 每小时 :7003×10 + E1 压测):命中=0 → 内化不
- 立项**,上层盲收保留现状(I8);将来出现数据面失配再武装重裁。
+- **E6 裁决:命中=0 → 内化不立项**,上层盲收保留现状(I8);将来出现数据面失配再武装重裁。
### 4.9 FUN_PARA bit3=1 的必要性与安全边界(D4)
@@ -508,30 +481,27 @@ Socket 仍 OPEN、芯片 TCP 已 CLOSED、**RX 缓冲里 STOR 载荷一个不少
严格更差。:8888 对时包、:7003 远显推流同样是「数据 + 随即关闭」形态,一并受害。
**③ bit3=0 也并不能真正消除孤儿态。** 芯片自动关以**收到** DISCONNECT/TIM_OUT 为前提,
-而 TRAP-NET-17(E14 复核)已实证快速复连场景这两个中断会被读清消费掉
-(`docs/问题记录/CH395F_Trap_Records.md` B10 行)→ 事件丢了,bit3=0 同样停在已关/半关态,
-孤儿 Socket 照样存在。它只是降低出现概率,代价却是第②条。
+而 TRAP-NET-17(E14 复核)已实证快速复连场景这两个中断会被读清消费掉 → 事件丢了,
+bit3=0 同样停在已关/半关态,孤儿 Socket 照样存在。它只是降低出现概率,代价却是第②条。
**④ 所以「这么多问题」不归 bit3=1。** 断开后停 `0x05/0x00` 是 bit3=1 的**合法后果**(D4);
-真正造成 B10 永久拒连、消费者卡死、EOF 被吞没的,是软件侧没把「该由 MCU 关 Socket」这条
-义务落到唯一所有者(§1.3 的 12 所有者 / 15 组合),以及 U12 的「relisten 的 LISTEN 失败
-不 close 芯片 Socket」与 open 失败路径不对称。bit3=1 只是把义务显式化,把原本被芯片
-掩盖的架构欠债照了出来。
+真正造成永久拒连、消费者卡死、EOF 被吞没的,是软件侧没把「该由 MCU 关 Socket」这条义务
+落到唯一所有者(§1.3),以及 U12 的「relisten 的 LISTEN 失败不 close 芯片 Socket」与 open
+失败路径不对称。bit3=1 只是把义务显式化,把原本被芯片掩盖的架构欠债照了出来。
**⑤ 安全处理 = 把 bit3=1 的契约在软件侧收口**(逐条对应上面的缺口):
| 契约要求 | 对应缺口 | 收口手段(现状) |
|---|---|---|
-| 「会话结束」必须**谁都能看见**,不能只在边沿那一刻 | EOF 边沿化:抢先 relisten → recv 永久 `WOULDBLOCK` | 不变量 I:`net_sock_t.session_dead` 粘滞电平,`net_recv_locked` 判据 `state==CLOSED \|\| session_dead`(§3.3) |
-| 关闭 Socket 必须**一定有人做**、且只做一次 | 重建所有者 12 个 + U12 的 LISTEN 失败不回 close | 不变量 II:重建**判据**唯一化 = `net_reconcile_tick` 形态③④;重建**动作**唯一化 = `net_listener_tick`(§4.1 / §4.2,E17) |
+| 「会话结束」必须**谁都能看见**,不能只在边沿那一刻 | EOF 边沿化:抢先 relisten → recv 永久 `WOULDBLOCK` | `net_sock_t.session_dead` 粘滞电平,`net_recv_locked` 判据 `state==CLOSED \|\| session_dead`(§3.3) |
+| 关闭 Socket 必须**一定有人做**、且只做一次 | 重建所有者无法唯一化 + U12 的 LISTEN 失败不回 close | 重建**判据**唯一化 = `net_reconcile_tick` 形态③④;重建**动作**唯一化 = `net_listener_tick`(§4.1 / §4.2) |
| 关闭姿势必须正确 | TRAP-NET-12:陈旧 DISCONNECT 锁存秒断下一条连接 | `sock_reset` 序列 = 排 RX → CLOSE → 排事件 → 等 `byte0=00H`(§4.4) |
| 异常释放须先排空残渣再关 | TRAP-NET-18:~2KB RX 残渣被下条连接当自己的数据读走 | 隔离期持续排空 RX + 主动 close + 等 `CLOSED/TCP_CLOSED`(§4.3) |
### 4.10 读即清信源审计(谁在读、会不会互吞)
-> 2026-10-14 全库审计(grep `get_sock_int_status` / `glob_int_status` / `int_status_sn`)。
-> 口径原本散在 **B3、F-NET-15 M4-lite、E14、TRAP-NET-12/-18** 四处代码注释里,**没有总表**——
-> 而"想再加一处读清"的人不会先去读那四处。本节就是那张挡人的表。
+> 口径散在多处代码注释里,**没有总表时"想再加一处读清"的人不会先去读那几处**。
+> 本节就是那张挡人的表:现存读清点全量清单 + 新增读清的准入判据。
#### 4.10.1 两个读即清寄存器与两级门控
@@ -543,7 +513,7 @@ Socket 仍 OPEN、芯片 TCP 已 CLOSED、**RX 缓冲里 STOR 载荷一个不少
`net_poll` 是**两级门控**:先读全局位,某 Socket 位为 0 就 `continue`、**不去读它的 SINT**。
⇒ 任何"顺手全量清一遍"的代码,等于**把别的在用 Socket 的门一并焊死**——它们的
CONNECT/DISCONNECT 边沿**永久丢失**(RECV 属条件位、下轮可补回,TRAP-NET-08)。
-这就是 B3 那类代码的真实危害,也是"全局寄存器只有一个读者"这条约定的全部理由。
+这就是"全局寄存器只有一个读者"这条约定的全部理由。
#### 4.10.2 现存读清点全量清单(产品代码 6 处)
@@ -560,13 +530,13 @@ CONNECT/DISCONNECT 边沿**永久丢失**(RECV 属条件位、下轮可补回
⇒ 当前**没有已知的"事件被读清互吞"活口**(`test/board` 台架里的读清点不算——它们剥离了
net 层,本就以"我自己是唯一读者"为前提)。
-#### 4.10.3 已根除的三条互吞实例(历史,勿复活)
+#### 4.10.3 三条已根除的互吞形态(勿复活)
-| 已删的东西 | 危害 | 定性 |
+| 形态 | 危害 | 现状 |
|---|---|---|
-| **B3**:`net_listen_locked` / `net_relisten_locked` 遍历 **8 个 Socket 全清 + 再读清全局** | 见 §4.10.1——别的在用 Socket 边沿永久丢失;而后者是**每次断连的热路径** | git 考古:多连接时代调试遗留被原样搬进单连接模型(e862009bc → 319d00e3 → db64e6acf),**不属于任何 Trap 的修复** |
-| **F-NET-15 M4-lite**:沉降段的全局读即清 | 每 30ms 一轮就搁浅其它 Socket 的 CONNECT/DISCONNECT/RECV_OK;M1 使沉降成为每次断连热路径后,放大成 **FTP 控制/数据通道互杀**(E7 实测 lftpd 5s 超时) | 已删,注释留在 `net_listener.c` 隔离段 |
-| **E14**:排事件放在 `tcp_listen` **之后** | listen 后读清把**快速复连新连接的 CONNECT** 当场吞掉(生产与台架同源根除) | **已挪到 LISTEN 之前**(`net_listener.c` / `net_listen_locked` 同改法) |
+| 遍历 **8 个 Socket 全清 + 再读清全局**(`net_listen_locked` / `net_relisten_locked`) | 见 §4.10.1——别的在用 Socket 边沿永久丢失;且它在**每次断连的热路径**上 | 已删(多连接时代的调试遗留被原样搬进单连接模型,**不属于任何 Trap 的修复**) |
+| 沉降段的全局读即清 | 每 30ms 一轮就搁浅其它 Socket 的 CONNECT/DISCONNECT/RECV_OK;沉降成为每次断连热路径后放大成 **FTP 控制/数据通道互杀**(E7 实测 lftpd 5s 超时) | 已删(M4-lite,与 M1 同批) |
+| 排事件放在 `tcp_listen` **之后** | listen 后读清把**快速复连新连接的 CONNECT** 当场吞掉 | 已挪到 LISTEN **之前**(生产与台架同源根除,E14) |
#### 4.10.4 一处「看着像缺陷、其实是配对消费」
@@ -597,57 +567,58 @@ DISCONNECT 会被 `(void)` 丢掉。但 RECV_OK 是条件位(下轮补回)
| 参数 | 值 | 依据锚点 |
|---|---|---|
-| `NET_RECONCILE_PERIOD_MS` | 100ms | E2:每轮全量刷新成本 ≈72µs;pump 时代 50ms 实测把 uiTask 最坏单轮推到 1034ms(`net_listener.c` 注释留痕) |
+| `NET_RECONCILE_PERIOD_MS` | 100ms | E2:每轮全量刷新成本 ≈72µs;更密的周期会把 uiTask 最坏单轮推高(`net_listener.c` 注释留痕) |
| 去抖(分级) | ②=1 轮 / ③④⑤=2 轮 / ①=4 轮 | E2:LISTEN 可见 ≤10ms 无长尾 → ② 可单轮;①=4 轮 ≈400ms = M3 让位窗 |
| 冷却 | 1s | 防重建风暴(E8 风暴签名) |
-| GRACEFUL / QUARANTINE / 强制放行 | 100ms / 400ms / 800ms | TRAP-NET-18 二阶段实测;强制放行必须 < D10 2s |
+| GRACEFUL / QUARANTINE / 留痕上限 | 100ms / 400ms / 800ms | TRAP-NET-18 二阶段实测;上限必须 < D10 2s(§4.3) |
| D10 | 30ms / 2s | TRAP-NET-09/-20:沉降窗不定长;2s 是预算不是终态 |
| `sock_reset_sync` 封顶 | 600ms | E2:LISTEN 态二段 CLOSE ~180ms 落定 + 余量 |
-| KeepAlive | 60s / 5s / 3 | E11:判死 ≈30.0s;T2 不误杀空闲会话 |
-| recv tripwire | 60s | 阶段 1.5(完成逻辑缺位探针,不改语义) |
+| KeepAlive | 60s / 5s / 3 | E11:判死 ≈30.0s;不误杀空闲会话 |
+| recv tripwire | 60s | §4.5 挂起留痕探针(不改语义) |
| 恢复窗(正常断连→监听可用) | 0.3~1s | E1 发现 A:释放只置事实 → 等周期 tick → SYN 静默丢 → 客户端 1s RTO → `:7003` 复连 p50≈1020ms |
### 5.2 实验锚点速查(E 编号 = 台架实验序号)
-> **`E` 是什么**:`E` + 序号 = **台架实验编号**(E = Experiment),不是错误码、也不是小节号。
-> 每个实验遵循「假设 → 方法 → 判据 → 它门禁哪个实施决策」四段式;下表给出方法与结论,
-> 原始记录(假设、原始日志、统计)随审计过程归档,取用命令见 §12。
-> ⚠ **这些编号是冻结标识**:`net_socket.c` / `net_listener.c` 注释、`test/host/e02_probe.py`、
-> `test/host/e6_soak.py`、`docs/问题记录/CH395F_Trap_Records.md` 都直接引用它们,**重排或改名会断链**。
-> ⚠ 本表**末行 `E-02` 不是实验编号**——它是 `docs/问题记录/错误码传播审计.md` 的**审计发现**编号
+> **`E` + 序号 = 台架实验编号**(E = Experiment),不是错误码、也不是小节号。每个实验遵循
+> 「假设 → 方法 → 判据 → 它门禁哪个决策」四段式;本表只给**方法与结论**,原始记录(假设、
+> 原始日志、统计)已归档,取用命令见 §12。
+> ⚠ **这些编号是冻结标识**:`net_socket.c` / `net_events.c` / `net_listener.c` 注释、
+> `test/host/e02_probe.py`、`test/host/e6_soak.py`、`docs/问题记录/CH395F_Trap_Records.md`
+> 都直接引用它们,**重排或改名会断链**。
+> ⚠ 末行 `E-02` **不是实验编号**——它是 `docs/问题记录/错误码传播审计.md` 的**审计发现**编号
> (`E-` 族,冻结于 `E-63`),列在此处只为记录该发现的**收口台架**。
> 区分规则:**带连字符 = 审计发现,无连字符 = 自愈实验**。
-| 编号 | 实验做了什么(方法) | 结论 | 落点 |
+| 编号 | 验证了什么(方法) | 结论(现状口径) | 落点 |
|---|---|---|---|
-| **E0** | 直接 grep 生产常态串口日志(gen=149 窗口,开机后 ~3.5 h,零台架成本) | 常态下对账**零命中**(冷路径);日志噪声主要来自 D10 重试的逐轮留痕 | reconciler 热路径成本可接受;重试日志只留首末 |
-| **E1** | 现网固件 + PC 侧压测:`n30_repro.py` 背靠背 STOR、`ftp_regression`、`:7003` 100 ms×50 快速复连、iec104 连断 ×10 | `:7003` 复连 50/50、p50≈1020 ms(恢复窗 0.3~1 s);背靠背 STOR 出现 4/50 失效循环 | 促成止血;`n30_repro` 纳入常备回归 |
-| **E2** | 裸机实验固件(`ch395f_reuse_bench.c` + DWT 周期计数器):测 `GET_SOCKET_STATUS` 单次 SPI 耗时;LISTEN 命令后 0/10/50/100 ms 回读,看首次观察到 TCP=01H 的延迟 | 单次回读 **18.1 µs**(每轮全量刷新成本可忽略);LISTEN 可见延迟 ≤10 ms 无长尾;**LISTEN 态 CLOSE 是二段**——一次只拆监听,byte0 恒留 05H 不自落 | 驱动缓存刷新策略;reconciler 形态② 去抖降到 1 轮;`sock_reset` 内置二段补发 |
-| **E3** | 同场固件,对 OPEN / LISTEN / CLOSE / SET_* 五条命令各 5 轮 ×2 遍 = 50 样本,看 `GET_CMD_STATUS` 有无判别力 | OPEN / LISTEN / CLOSE **无判别力**,且陈旧错误码 0x17 跨命令持续存活;SET 类**完全不更新**状态 | 命令完成判据改用**状态回读**(`ch395f_cmd_done`) |
-| **E4** | 裸机 3 变体 ×10 轮 ×2 遍 = 60 轮:先造污染会话(发 3000B 后 RST),按变体复位后**立即**重开监听并上传 4096B 图案载荷,逐字节校验 | 变体 A(排空 RX→CLOSE→排空事件→等 byte0=00H)**20/20**;变体 C(什么都不做)**0/20** 级联自持;实测 `rxdrain=2048`(芯片单 Socket RX 缓冲满容) | §4.4 复位序列定案;两档沉降**都必须排 RX** |
-| **E5** | :7003 上 connect→发 1B→对端 FIN→等 3 s→复连 ×10,查"芯片静默回收回 LISTEN 却不发 DISCONNECT"能否复现 | 20 次 FIN **全部**产生 `DISCONNECTED`;静默回收形态在 E1+E5 累计 ~110 次连接中断里**零复现** | 缺口代码上真实但触发罕见 → 顺带修(对账形态⑤),不单独立项 |
-| **E6** | 生产路径插桩固件(`NET_U15_PROBE=1` + `test/host/e6_soak.py`):常态运行 + 每小时 :7003×10 + 压测,探"`GET_RECV_LEN>0` 但 `!readable`"失配 | 命中 **0**(~4 h,用户裁决提前收线,原规格 24 h) | U15 登记**观察项**、不立项(I8);上层盲收保留现状 |
-| **E7 / E7b** | 实验固件加候选修改 M1(GRACEFUL 沉降无条件化)+ M2(门控换 `net_listener_armed`),再叠加/去掉 M4-lite(删沉降段全局读即清),跑 `n30` 压测 | M1+M2+M4-lite 把 `n30` 成功率从 **8% → 96.7%**(116/120);**M1 单独上会放大 F-NET-7** | 转正(`9ac9879`);立下「M1 与 M4-lite 必须同批」纪律 |
-| **E8** | 同固件、同负载,**只变设备状态**跑 `n30` 矩阵(全新启动 / 探针+压测累积后 / 仅 ST-Link 复位) | 故障率在 **2%~54%** 间随状态摆动 → 故障率 ∝ 芯片侧残留存量;抓到"风暴签名"三机制(F-NET-17) | M3 可疑门 + `sock_reset` quiescent 排空;"沉降让位救回"红线 |
-| **E9** | M3 原型三轮迭代(可疑门判据:芯片 `0x05/0x00` + `lost_conn_seen==0` + `recv_bytes==0` + 芯片 RX 有在途数据) | 判据定案为**资产门版**;风暴态免疫 + 零误伤 | §4.1 的 M3 门(`ae17d36`) |
-| **E10** | 阶段 0 九项 + 阶段 1 六项转正后的全量台架验证 | 全绿;暴露 **`net_listener_armed` 必须放行 ESTABLISHED**——漏放行时 i104 的 200 ms 轮询抢不过形态② 去抖而整场饿死(18/18 FAIL,127 s→8.3 s) | §4.2 门控定案 |
-| **E11** | 临时固件开启服务器侧 KeepAlive(`CMD_SET_KEEPALIVE_SN`,60s/5s/3),用"禁用 PC 网卡"造"链路在、对端静默" | 真实可用;判死 **≈30.0 s**(早于手册公式 75 s,以实测为准);静默但**存活**的对端不误杀 | §4.7 转正 |
+| **E0** | 直接读生产常态串口日志(开机后 ~3.5 h,零台架成本) | 常态下对账**零命中**(冷路径);日志噪声主要来自 D10 逐轮留痕 | reconciler 热路径成本可接受;重试日志只留首末 |
+| **E1** | 现网固件 + PC 压测:`n30_repro.py` 背靠背 STOR、`ftp_regression`、`:7003` 快速复连、iec104 连断 | 恢复窗 0.3~1 s(`:7003` 复连 p50≈1020 ms);背靠背 STOR 存在失效循环 | `n30_repro` 纳入常备回归 |
+| **E2** | 裸机 bench(`ch395f_reuse_bench.c` + DWT 计时):`GET_SOCKET_STATUS` 单次耗时;LISTEN 命令后回读看首次可见 TCP=01H 的延迟 | 单次回读 **18.1 µs**(全量刷新成本可忽略);LISTEN 可见延迟 ≤10 ms 无长尾;**LISTEN 态 CLOSE 是二段**(一次只拆监听,byte0 恒留 05H) | 驱动缓存刷新策略;形态② 去抖 1 轮;`sock_reset` 内置二段补发 |
+| **E3** | 同场固件,OPEN / LISTEN / CLOSE / SET_* 五条命令各 50 样本,测 `GET_CMD_STATUS` 判别力 | OPEN / LISTEN / CLOSE **无判别力**,陈旧错误码 0x17 跨命令持续存活;SET 类**完全不更新** | 命令完成判据改用**状态回读**(`ch395f_cmd_done`) |
+| **E4** | 裸机 3 复位变体 ×20 轮:先造污染会话(发 3000B 后 RST),复位后**立即**重开监听并上传 4096B 图案载荷,逐字节校验 | 变体 A(排空 RX→CLOSE→排空事件→等 byte0=00H)**20/20**;变体 C(什么都不做)**0/20** 级联自持;实测 `rxdrain=2048`(芯片单 Socket RX 缓冲满容) | §4.4 复位序列定案;两档沉降**都必须排 RX** |
+| **E5** | `:7003` 上 connect→发 1B→对端 FIN→等 3 s→复连 ×10,找"芯片静默回收回 LISTEN 却不发 DISCONNECT" | 20 次 FIN **全部**正常产生 `DISCONNECTED`;静默回收在累计 ~110 次连接中断里**零复现** | 缺口真实但触发罕见 → 形态⑤ 顺带修,不单独立项 |
+| **E6** | 插桩固件(`NET_U15_PROBE=1` + `test/host/e6_soak.py`):常态 + 每小时 `:7003`×10 + 压测,探"`GET_RECV_LEN>0` 但 `!readable`"失配 | 命中 **0**(~4 h 常态) | U15 观察项**不立项**(I8);上层盲收保留现状(§4.8) |
+| **E7 / E7b** | 候选修改 M1(GRACEFUL 沉降无条件化)+ M2(门控换 `net_listener_armed`),叠加/去掉 M4-lite(删沉降段全局读即清),跑 `n30` | M1+M2+M4-lite 把 `n30` 成功率 **8% → 96.7%**;**M1 单独上会放大 F-NET-7** | 三者即现状(§4.2 门控 / §4.3 沉降);纪律:**M1 与 M4-lite 必须同批** |
+| **E8** | 同固件同负载,**只变设备状态**跑 `n30` 矩阵(全新启动 / 累积后 / 仅 ST-Link 复位) | 故障率在 **2%~54%** 间随状态摆动 → 故障率 ∝ 芯片侧残留存量;抓到"风暴签名"三机制(F-NET-17) | M3 可疑门 + `sock_reset` quiescent 排空;"沉降让位救回"红线 |
+| **E9** | M3 可疑门判据三轮迭代(`0x05/0x00` + `lost_conn_seen==0` + `recv_bytes==0` + 芯片 RX 有在途数据) | 判据定案为**资产门版**;风暴态免疫 + 零误伤 | §4.1.6 M3 门 |
+| **E10** | 不变量与机制定案后的全量台架回归 | 全绿;暴露 **`net_listener_armed` 必须放行 ESTABLISHED**——漏放行时 i104 的 200 ms 轮询抢不过形态② 去抖而整场饿死 | §4.2 门控定案 |
+| **E11** | 临时固件开启服务器侧 KeepAlive(`CMD_SET_KEEPALIVE_SN`,60s/5s/3),用"禁用 PC 网卡"造"链路在、对端静默" | 真实可用;判死 **≈30.0 s**(早于手册公式 75 s,以实测为准);静默但**存活**的对端不误杀 | §4.7 KeepAlive 定案 |
| **E12** | SPI 缓存区 TSL 时序 A/B:手册要求"长度与数据之间"留 TSL(读 0.4 µs / 写 0.2 µs),本仓把命令头与数据打包进同一 DMA 突发(间隔=0),对照两臂 | 460 轮 / ≈58 MB 双向 SHA-256 **无可复现危害**、两臂无差异 | §6 红线 6;"两段+TSL"变体留作**待用补丁** |
-| **E13** | 产品固件 + PC 网卡抖动注入,尝试确定性复现 E8/E12 记录的"残渣风暴态" | 根因 = **僵尸会话**(对端静默死亡、芯片侧 ESTABLISHED 未清、单连接监听被占),而非跨烧录残渣;`RSTI←NRST` 推翻"跨烧录存活";自愈链端到端 30~90 s 有效 | §4.7;PASV 预清缺口由 `sock_reset` 收口 |
-| **E14** | 48 轮直读 SINT 矩阵复核"丢中断"说法(drain-int 现场抓取 + SINT 补读 ×3 + 超时归类) | **芯片侧事件丢失 = 0**;"丢失"实为 drain 读清消费 3/48 + 门控竞态 2/48 + 主机同步 1/48;幽灵态本轮 0/48 未复现 | §1 事实 2 改写;生产 listen/relisten 排事件挪到 LISTEN 命令**之前**(根除自伤窗);TRAP-NET-17/-20 归因更正(机制保留不变) |
-| **E15** | 幽灵态归因复核(WCH 官网 + 手册出处) | `0x04` + 旧端口 = 芯片**如实报告**旧连接拆链进行中,不是谎报:bit3=1 是自家配置(手册 §5.52②);拆链走 RFC 9293 §3.3.2 状态机(FIN_WAIT_1→FIN_WAIT_2→TIME_WAIT,2MSL 驻留);拆链窗实测 >2 s 与手册 §5.17 默认重传 12 次耗尽 ≥3 s 同量级;WCH 官方博客确认"断连即重连被拒"、官方方案 = 关闭完成后重开 | TRAP-NET-20 口径"谎报"→"如实报告";settle 等待 = 官方方案;幽灵甄别 = 强制放行路径的护栏;I3 就此加强 |
-| **E16** | 把 relisten 隔离段收口到驱动 `sock_reset` 原语(删 net 层自建的排空/关闭/等待序列与 100/400 ms 参数副本),同一块板 A/B 跑 `ftp_abort_probe`(RST 10 + FIN 5)+ `relisten_stress`(:7003 300ms×10、:21 100ms×50)+ `n30_repro` 30 轮;失败后两轮补救:① 原语加"沉降窗后二次排空 RX" ② `net_sock_reset_sync` 改"已在运行=加入等待" | **收口失败、已回退**:原语版 RST **0/10**、FIN **0/5**(`followup` SHA 不匹配 = 首笔复连仍被截断),并新增 `sock_reset_sync timeout (600ms)` 与 `close: sock_reset not done` 两条 `[ERR]`;同场原序列 **10/10 + 5/5**、恢复窗 p50 **208 ms**(n=30,min 177 / max 280)。快复连与背靠背腿原语版也全过(50/50、10/10、30/30)→ **失效面精确落在"中止后首笔复连"**。根因两条:残渣在 CLOSE 之后才落进 RX(一次排空接不住、二次排空仍不等价于"每 30 ms 持续 CLOSE+排空");复位机"逼回 CLOSED"的目标与上层立即复用同一 Socket 冲突 | §8 延后项 1 改写为受阻 + 三条前置语义;§4.4 加承接警告;登记 **N-30** |
-| **E17** | **单一所有者收口**(与 E16 相反方向的简化):`net_listener_try`(当场动作、**无状态门**,4 个调用点各自补 `state == CLOSED`)改为 `net_listener_request`(只登记意图)+ `net_listener_tick`(唯一执行者,门集含"芯片已在 LISTEN 即了结");顺带把沉降/重试留痕改为只留首末。同一块板全量回归 | `ftp_abort_probe` RST **10/10** + FIN **5/5**;`relisten_stress` :7003 10/10(p50 25 ms)、:21 50/50(p50 509 ms);`n30_repro` **30/30**;`e02_probe` queue/listener 双 PASS(恢复 513 ms ≈ 基线 500 ms);恢复窗 p50 **199~208 ms**(n=108,与 E16 基线 208 ms 持平)→ **延后一轮(≤1 ms)执行不伤即时性**;relisten 留痕 **5.4 → 2.5 行/次恢复**;`Code` +8 B | §4.2 改为"登记 / 执行分离";§9 事实表与 §11 API 表同步;`net_listener_try` 一名退役 |
-| **E18** | **负向注入打靶 · 三轮**(任务分支 `task/N31-neg-inject`,注入代码 +232 行、`NET_FAULT_INJECT` 编译开关、**不入库**):① 吞掉 DISCONNECT(:2404,在 i104 的 200 ms accept 轮询窗内 RST → "芯片 CLOSED / 本层 ESTABLISHED / 无人在 recv");③ 绕过本层簿记直接 close 芯片监听(:7003 空闲监听器);⑤ 开盲区吞掉芯片自发的中断 + 等芯片真 CLOSED 再 open+listen + 确认落到 `0x05/0x01`(:8888,PC 侧 RST 打断 `FIN_WAIT_2`) | **三条全部命中**:① 吞事件后 `reconcile: chip closed -> disconnect` → `auto-relisten ok`,**4/4 救回**、端口 5/5 复验;③ 两轮独立复现 `was state=3` → `auto-relisten ok (recon)`;⑤ `reclaim DONE chip=0x05/0x01` → `chip re-listened w/o DISCONNECT -> resync` → 本层回 LISTENING,零芯片命令、端口立刻可复连、无 `task stall` | **①③⑤ 均不可降级为只留痕**:它们只在"没有人在等"时才有活干(pending/EOF 比 100 ms 对账快,常态先把状态收口成 ④ → **互补而非重复**);⑤ 的**取消挂起 recv/send** 尤其不可省,否则消费者在死连接上干等到 deadline。顺带量出"为什么常态打不中":①⑤ 的前提会被更快的消费者路径消解,⑤ 还要先越过芯片 `FIN_WAIT_2`(E15 的 2MSL 长尾,实测 972 轮 ≈1 s) |
-| **E19** | **`force open` 反事实 A/B**(同一注入场景:本层已收口 CLOSED + 芯片停 `FIN_WAIT_2` `0x05/0x06` + **对端挂住不关** = E-02 场景;构建 A = 现状放行,构建 B = `NET_FORCE_OPEN_COUNTERFACTUAL` 只留痕不放行) | **800 ms 上限首次被打中**:`relisten quarantine timeout, force open (sock=0x05 tcp=0x06)`。A:对端挂住期间端口 **0/4** 可连、对端关闭后 **+512 ms** 恢复;B:同样 **0/4**、**+505 / +533 ms** 恢复 → **放行与不放行同一分布**;B 另暴露"抑制后每 30 ms 刷两行"的日志风暴(放行顺带关掉了沉降窗) | **`force open` 的动作在全部可产现场景内无可测收益**:端口恢复时刻由"芯片何时回 CLOSED"决定,真正兜住"监听器永久失效"的是 **D10 预算 + 形态④ 重新登记**(B 实测:抑制后仍持续重试并恢复)→ "防永久失效"的归属应改正。两条实测坑:① 本层直接改 `state` **不会**进沉降窗,窗只由 `net_listener_settle` 开启(注入踩过一次,导致连打三轮 0 命中);② `[ERR] relisten retry budget exhausted` 在此**被稳定复现** = 对端不配合拆链的签名,非回归。**决策已落地**:`force open` 降级为只留痕(`keep waiting` + `s_relisten_capped` 只留首末、隔离标志不清→继续排渣);真机注入复验 **+509 ms** 与 A/B 同分布,回归 RST **10/10**、FIN **5/5**、`relisten_stress` :21 **50/50**(p50 507)、`n30_repro` **30/30**,netTask 静态最坏链 1920→**1912 B** |
-| **E-02**(审计编号) | `test/host/e02_probe.py` 台架定案 `net_drop_conn` 收口后的时序(阶段 3 产品固件) | 对端配合(收到 FIN 后其内核 close):`TIME_WAIT` ~150 ms → 形态④ relisten,恢复全程 ~500 ms;对端内核存活但不 close:芯片停 `FIN_WAIT_2` 长尾,**监听恢复受限于对端 close** | §4.6 |
+| **E13** | 产品固件 + PC 网卡抖动注入,追"残渣风暴态"根因 | 根因 = **僵尸会话**(对端静默死亡、芯片侧 ESTABLISHED 未清、单连接监听被占),而非跨烧录残渣;`RSTI←NRST` 推翻"跨烧录存活";自愈链端到端 30~90 s 有效 | §4.7;PASV 预清缺口由 `sock_reset` 收口 |
+| **E14** | 48 轮直读 SINT 矩阵复核"丢中断"说法(drain-int 现场抓取 + SINT 补读 ×3 + 超时归类) | **芯片侧事件丢失 = 0**;"丢失"实为 drain 读清消费 + 门控竞态 + 主机同步;幽灵态本轮 0/48 未复现 | §1 事实 2;生产 listen/relisten 排事件挪到 LISTEN 命令**之前**;TRAP-NET-17/-20 归因更正(机制保留不变) |
+| **E15** | 幽灵态归因复核(WCH 官网 + 手册出处) | `0x04` + 旧端口 = 芯片**如实报告**旧连接拆链进行中,不是谎报:bit3=1 是自家配置(手册 §5.52②);拆链走 RFC 9293 §3.3.2 状态机(FIN_WAIT_1→FIN_WAIT_2→TIME_WAIT,2MSL 驻留);WCH 官方博客确认"断连即重连被拒"、官方方案 = 关闭完成后重开 | TRAP-NET-20 口径"谎报"→"如实报告";settle 等待 = 官方方案;幽灵甄别 = 护栏;I3 加强 |
+| **E16** | 把 relisten 隔离段收口到驱动 `sock_reset` 原语(删 net 层自建序列与参数副本),同板 A/B 跑 `ftp_abort_probe` + `relisten_stress` + `n30_repro` | **收口失败、已回退**:失效面精确落在"中止后首笔复连";根因两条——残渣在 CLOSE **之后**才落进 RX(一次排空接不住);复位机"逼回 CLOSED"的目标与上层立即复用同一 Socket 冲突 | §8 延后项 1(三条前置语义);§4.4 警告;登记 **N-30** |
+| **E17** | **单一所有者收口**:`net_listener_try`(当场动作、**无状态门**,各调用点自带门)改为 `net_listener_request`(只登记意图)+ `net_listener_tick`(唯一执行者,门集含"芯片已在 LISTEN 即了结"),同板全量回归 | 全绿;**延后一轮(≤1 ms)执行不伤即时性**(恢复窗 p50 与当场动作持平);relisten 留痕减半 | §4.2 登记/执行分离;§9.2 门集;§11.1 判据 |
+| **E18** | **负向注入打靶 · 三轮**(注入代码不入库):① 吞掉 DISCONNECT(在 i104 的 200 ms accept 轮询窗内 RST);③ 绕过本层簿记直接 close 芯片空闲监听;⑤ 开盲区吞掉芯片自发的中断 + 等芯片真 CLOSED 再 open+listen | **①③⑤ 全部命中并救回**;①⑤ 的前提会被更快的消费者路径消解 → 常态 0 命中;⑤ 还要先越过芯片 `FIN_WAIT_2`(E15 的 2MSL 长尾,实测 ≈1 s) | §4.1.4 / §4.1.5:**三条均不可降级为只留痕**;⑤ 的**取消挂起 recv/send** 不可省 |
+| **E19** | **`force open` 反事实 A/B**(本层已收口 CLOSED + 芯片停 `FIN_WAIT_2` `0x05/0x06` + **对端挂住不关** = E-02 场景;A = 现状放行,B = 只留痕不放行) | **放行与不放行的端口恢复时刻同一分布** → 动作在全部可产现场景内无可测收益;真正兜住"监听器永久失效"的是 **D10 预算 + 形态④ 重新登记**。两条实测坑:① 本层直接改 `state` **不会**进沉降窗(窗只由 `net_listener_settle` 开启);② `relisten retry budget exhausted` 在此被稳定复现 = 对端不配合拆链的签名,**非回归** | §4.3:800 ms 上限降级为**只留痕**(`keep waiting` 只留首末、隔离标志不清 → 继续排渣) |
+| **E-02**(审计编号) | `test/host/e02_probe.py` 台架定案 `net_drop_conn` 收口后的时序 | 对端配合(收到 FIN 后其内核 close):`TIME_WAIT` ~150 ms → 形态④ relisten,恢复全程 ~500 ms;对端内核存活但不 close:芯片停 `FIN_WAIT_2` 长尾,**监听恢复受限于对端 close** | §4.6 |
## 6. 红线(明确不做的事)
1. **不改 EXTI/ISR**:全局事件寄存器整体读即清,ISR 读寄存器会把事件从 netTask 抢走
——正是 B3 事故形状。
2. **不启用多连接模式(FUN_PARA bit1)**:不支持多监听(A′ 出局);监听常驻只能软件承诺。
-3. **不删消费者驱动即时登记 ×3**(P2a 实测删除 → `[WD] task stall`)。
+3. **不删消费者驱动即时登记 ×3**(§4.2;删除 → CLOSED 窗口拉长 → `[WD] task stall`)。
4. **不改单连接复用模型**:硬 TCP 栈原生主流形态(ioLibrary 无 accept、FreeRTOS reuse
同形),不是对 BSD 的偏离。
5. **SPI 分频 8、中断经 `net_poll` 集中读清**:PCB 约束 + I7(护栏是收窄读法,不是改路径)。
@@ -655,43 +626,41 @@ DISCONNECT 会被 `(void)` 丢掉。但 RECV_OK 是条件位(下轮补回)
(已证明无吞吐/正确性回退)。
7. **不做盲收内化 / 不加 U15 对账**:E6 命中=0(I8)。
8. **EOF 时不得 `net_close` 监听 fd**:模块约定铁律(会连监听一起毁掉)。
-9. **不把 DONE_CLOSE 当单次 CLOSE 的充分条件**:LISTEN 态 Socket 需二段(E2 补充发现)
+9. **不把 DONE_CLOSE 当单次 CLOSE 的充分条件**:LISTEN 态 Socket 需二段(E2)
——走 `sock_reset`(内置补发)或保留 TRAP-NET-09 式重试。
+10. **不把监听重建接进 `sock_reset` 原语**:三条前置语义未补齐(§8 延后项 1,E16)。
## 7. 编号发现速查(仍被代码/文档引用的 12 条)
| 编号 | 一句话结论 | 收口落点 |
|---|---|---|
-| F-NET-2 | "本层 ESTABLISHED 而芯片已回 LISTEN"无任何机制认领 | 对账形态⑤(阶段 1,`net_resync_listen` 已删) |
-| F-NET-3 | `net_relisten` / `net_resync_listen` / `net_can_accept` 是冗余或过期 API 面 | 全部删除(阶段 1) |
-| F-NET-4 | iec104 过期门控 → 丢 CONNECT 探针对 :2404 从不运行 | 门控统一 `net_listener_armed`(阶段 1) |
-| F-NET-7 | 全局寄存器读即清残留会搁浅其它 Socket 事件 | I7 + `sock_events` 护栏(阶段 2/3) |
-| F-NET-15 | GRACEFUL 沉降门 `recv_bytes≠0` = 背靠背失效循环放大器 | M1 无条件化(M2/M4-lite 同批,`9ac9879`) |
-| F-NET-17 | 风暴态三机制(沉降自再生锁存 / 泵与救回互抢 RX / 芯片 OPEN 卡死放大) | M3 可疑门 + `sock_reset` quiescent + 让位红线 |
-| F-DRV-1 | 同一芯片事实 7 处独立回读、三套去抖参数——单一事实源缺失是五层膨胀的地基 | 驱动缓存(I1,阶段 2) |
-| F-DRV-4 | "close+排空+等 CLOSED"两套同构变体参数漂移 | `ch395f_sock_reset` 统一 |
-| F-DRV-8 | 命令完成等待是阻塞原语(`HAL_Delay(20)` 忙等、4s 上限) | `cmd_done` 状态回读判据 + 非阻塞状态机 |
-| F-DRV-13 | 链路事件四个全局位读清后无人消费,运行期拔网线完全静默 | `glob_event_feed` + `link_up` 缓存(阶段 2/3) |
+| F-NET-2 | "本层 ESTABLISHED 而芯片已回 LISTEN"无机制认领 | 对账形态⑤(§4.1) |
+| F-NET-3 | `net_relisten` / `net_resync_listen` / `net_can_accept` 是冗余或过期 API 面 | 全部删除;监听门控统一 `net_listener_armed`(§4.2) |
+| F-NET-4 | iec104 过期门控 → 丢 CONNECT 探针对 :2404 从不运行 | 门控统一 `net_listener_armed`(§4.2) |
+| F-NET-7 | 全局寄存器读即清残留会搁浅其它 Socket 事件 | I7 + `ch395f_sock_events` 护栏(§4.10) |
+| F-NET-15 | GRACEFUL 沉降门 `recv_bytes≠0` = 背靠背失效循环放大器 | 沉降无条件化(与删沉降段全局读即清同批,§4.3) |
+| F-NET-17 | 风暴态三机制(沉降自再生锁存 / 泵与救回互抢 RX / 芯片 OPEN 卡死放大) | M3 可疑门 + `sock_reset` quiescent + 让位红线(§4.1.6) |
+| F-DRV-1 | 同一芯片事实 7 处独立回读、三套去抖参数——单一事实源缺失是补丁膨胀的地基 | 驱动状态缓存 = 单一事实源(I1,§4.4) |
+| F-DRV-4 | "close+排空+等 CLOSED"两套同构变体参数漂移 | `ch395f_sock_reset` 统一(§4.4) |
+| F-DRV-8 | 命令完成等待是阻塞原语(`HAL_Delay` 忙等、4s 上限) | `cmd_done` 状态回读判据 + 非阻塞状态机(§4.4) |
+| F-DRV-13 | 链路事件四个全局位读清后无人消费,运行期拔网线完全静默 | `glob_event_feed` + `link_up` 缓存(§4.4) |
| F-MS-8 | 芯片 11 态 → net 层 8 态有损投影,有损镜像必生决策窗口 | 镜像降级为会话投影(I1、§3.3) |
-| F-MS-9 | 去抖/冷却是镜像世界的自偿成本 | 探针去抖已删(读缓存);reconciler 保留分级去抖(让位窗需求) |
+| F-MS-9 | 去抖/冷却是镜像世界的自偿成本 | 探针去抖已删(读缓存无瞬态);reconciler 保留分级去抖(让位窗需求) |
## 8. 延后项
-1. **relisten 沉降段统一(受阻,2026-10-14 实测,见 §5.2 E16)**:曾按本项把
- `net_relisten_locked` 的隔离段改走 `ch395f_sock_reset_begin/poll`(删 net 层自建的
- 排空+关闭+等待序列与 100/400 ms 参数副本),**已回退**——`ftp_abort_probe` RST **0/10**、
- FIN **0/5**(首笔复连仍被截断),并新增两条 `[ERR]`;同场原序列 **10/10 + 5/5**。
- 原语要能承接,先补三条语义(缺一即回归):
- ① **窗口内持续清**:残渣在 CLOSE **之后**才落进 RX,只在 CLOSE 前排一次接不住;
- 补"窗后二次排空"仍 0/10——旧序列每 30 ms 重试轮都 CLOSE + 排空 + 排事件,
- 整个隔离窗持续清,原语的"一次 CLOSE + 一次排空"与之不等价;
- ② **复用让位**:复位机的目标是把本 Socket 逼回 CLOSED,而 FTP 数据通道每笔传输都在
- 同一芯片 Socket 上重建监听;复位机未了结时它会去拆刚建的监听(WAIT_CLOSE 的
- 二段 CLOSE 补发),并让 `net_sock_reset_sync` 600 ms 超时 → `close` 跳过排渣、
- `listen` 的 open 重试环退化成无等待空转;
- ③ **槽位可加入**:每 Socket 只有一个复位槽位,`ch395f_sock_reset_begin` 返 -1
- (已在运行)不等于失败——同步包装必须**加入**已在跑的复位机,否则 `net_close`
- 走 "proceed anyway"、排渣被跳过。
+1. **relisten 沉降段统一到驱动 `sock_reset` 原语(受阻)**:原语要能承接监听重建,先补三条
+ 语义(缺一即回归,E16 实测数据见 §5.2):
+ ① **窗口内持续清**:残渣在 CLOSE **之后**才落进 RX,只在 CLOSE 前排一次接不住——旧序列
+ 每个 30 ms 重试轮都 CLOSE + 排空 + 排事件,整个隔离窗持续清,原语的"一次 CLOSE +
+ 一次排空"与之不等价;
+ ② **复用让位**:复位机的目标是把本 Socket 逼回 CLOSED,而 FTP 数据通道每笔传输都在同一
+ 芯片 Socket 上重建监听;复位机未了结时它会去拆刚建的监听(WAIT_CLOSE 的二段 CLOSE
+ 补发),并让 `net_sock_reset_sync` 600 ms 超时 → `close` 跳过排渣、`listen` 的 open
+ 重试环退化成无等待空转;
+ ③ **槽位可加入**:每 Socket 只有一个复位槽位,`ch395f_sock_reset_begin` 返 -1(已在运行)
+ 不等于失败——同步包装必须**加入**已在跑的复位机,否则 `net_close` 走 "proceed
+ anyway"、排渣被跳过。
补齐 ①②③ 并各自回归后再接;登记 **N-30**(`docs/任务规划/登记簿.md`)。
2. **部分读→再查 recv_len 定向 TC**:旧"部分读归零"机理未证实(与手册 §5.34 及
TC-NET-201 相反),注释约束已降级为历史观察——以定向用例结案(陷阱记录「台架纪律」
@@ -701,11 +670,8 @@ DISCONNECT 会被 `(void)` 丢掉。但 RECV_OK 是条件位(下轮补回)
## 9. 代码级事实清点
-> 判读本架构或做决策时需要的**源码事实**。⚠ 只给符号名、不固化行号——行号随重构漂移
-> (本仓曾出现文档内两处行数快照自相矛盾)。行数快照(2026-10-14,两次模块化之后):
-> `net_socket.c` 3119 行、`net_events.c` 663 行、`net_listener.c` 386 行、`net_listener.h` 126 行。
-> ⚠ **这类快照当天就会漂移**(本节上一版写的 3702/467/97 已被同日的 §11.5 作废)——
-> 它是"读代码前的地图",**不是判据**;要判据请数符号,别数行。
+> 判读本架构或做决策时需要的**源码事实**。⚠ 只给符号名、不固化行号与行数——它们随重构
+> 漂移,是"读代码前的地图"而**不是判据**;要判据请数符号,别数行。
### 9.1 会话投影的状态写入面
@@ -721,76 +687,79 @@ DISCONNECT 会被 `(void)` 丢掉。但 RECV_OK 是条件位(下轮补回)
| `net_connect_locked` | **无状态门**(可在 LISTENING 上改成 CONNECTING) |
| `net_recv_locked` 快路径 | **无状态门** |
| `net_listener_armed` | 唯一监听门控;**必须放行 ESTABLISHED**(§4.2) |
-| `net_listener_tick`(唯一执行者) | 门集**只在这一处**:状态 ∈ {LISTENING, CLOSED} + 监听型 + **芯片已在 LISTEN 即视为达成、绝不 open**(U12 结构性防护)。登记侧 `net_listener_request` 因此**不需要**状态门——旧 `net_listener_try` 当场动作且**无状态门**,对已 LISTENING 的 fd 再 open+listen 会因 USE 错误走进关闭分支、**关掉正听着新 SYN 的 Socket**(U12 同源事故),故当时 4 个调用点须各自补内层 `state == CLOSED` 门(漏一处即事故);E17 收口后该门上收,调用点不再复述 |
+| `net_listener_tick`(唯一执行者) | 门集**只在这一处**:状态 ∈ {LISTENING, CLOSED} + 监听型 + **芯片已在 LISTEN 即视为达成、绝不 open**(U12 结构性防护)+ 挂起保险。登记侧 `net_listener_request` 因此**不需要**状态门——任何"当场动作 + 调用点各自补门"的写法都会把 U12 风险复制 N 份(对已 LISTENING 的 fd 再 open+listen 会因 USE 错误走进关闭分支、**关掉正听着新 SYN 的 Socket**) |
-### 9.3 三个服务的契约差异(历史成因,现状已对齐)
+### 9.3 三个服务的连接结束契约(现状)
-| 服务 | 曾认为的连接结束信号 | net 层实际给什么 | 监听重建者 | 是否留无人重建组合 |
-|---|---|---|---|---|
-| `lftpd` 控制 :21 | `read_line!=0`(EOF→-1) | 阻塞 pending 保证 CLOSED 时挂起 recv 必被结束(FIN→0 / 异常→CONNRESET) | net 层;应用**永不** close 控制 fd | 无(契约一致,**正面样板**) |
-| `time_sync` :8888 | EOF / 预算耗尽 / 错误,随后**无条件** `net_close(conn==ls)` | 同上 | 应用自己每会话全量重建 | 不留孤儿,但违反红线 8 → close→重建沉降窗(TRAP-NET-09)+ 最坏 ~10 s 端口下线;**已改走 `net_drop_conn`**(§4.6) |
-| `ui_net_sink` :7003 | `net_recv` 返 0 或真错误;重建条件 `net_get_sock(g_listen)==NULL`(fd 不释放 → **恒假**) | 该 0 只在 recv 时刻 `state==CLOSED` 才给 | 名义 net 层,实际自身条件在 MCU 恒假 | **有**(B10 与"消费者卡死"都出自这里);**已收口**(§1.3 / §4.1) |
-| 模拟器后端 | `recv` 0 恒可得、fd 分离 | Winsock 原生 | 无需 | 无——且对上述全部失效模式**零覆盖**(D6:双模型分裂暂不消除) |
+| 服务 | 连接结束信号 | 监听重建者 | 备注 |
+|---|---|---|---|
+| `lftpd` 控制 :21 | `read_line != 0`;阻塞挂起 recv 保证 CLOSED 时必被结束(FIN→0 / 异常→CONNRESET) | net 层;应用**永不** close 控制 fd | 契约一致,**正面样板** |
+| `time_sync` :8888 | EOF / 预算耗尽 → `net_drop_conn` 拆会话保监听 | net 层(形态④) | 同 fd 即监听 fd,应用侧**不得** `net_close`(红线 8)→ 走 §4.6 |
+| `ui_net_sink` :7003 | `net_recv` 返 0 或真错误;非阻塞收帧 | net 层 | 重建条件**不得**写成 `net_get_sock(g_listen)==NULL`(fd 不释放 → 恒假);靠 `session_dead` 电平 + 形态④ |
+| 模拟器后端 | `recv` 0 恒可得、fd 分离 | 无需 | Winsock 原生;对上述全部失效模式**零覆盖**(D6:双模型分裂暂不消除) |
## 10. 决策记录
| # | 决定 | 依据 |
|---|---|---|
-| D1 | **A′(启用芯片多连接模式)出局**,根治必须在单连接复用模型内做 | §1.4:多监听不支持 / 池按源端口匹配会互串 / 历史实测 TRAP-NET-02、-11 / FUN_PARA 初始化后不可改 |
-| D2 | 采用**形态 B 深化**(电平化 + 单一所有者),**不做 P3**(listener 一等对象 + `accept` 返回虚拟句柄) | P3 需控制面重写 15~25 函数、RAM/Flash 上升,只有"同端口并发多连接"需求才值得 |
-| D3 | 周期对账**保留**,其副作用由不变量 I 消除 | TRAP-NET-17 实证事件面不是可靠唯一信源(E14 复核:非芯片侧丢失,是读清消费)→ 周期对账不可删 |
-| D4 | `FUN_PARA` **保持 `0x08`**,不因孤儿态改配置 | bit3=1 是 TRAP-NET-15/-20 定案的"保留排空现场"契约;bit3=0 会令芯片在 DISCONNECT/TIM_OUT 时**当场清缓冲** →「发完即 FIN」的尾部数据必被硬件擦除、**不可补救**。完整论证见 §4.9 |
-| D5 | 顺序 **P0 → P1 → P2**,每阶段独立提交、独立回归、可单独回滚 | P1 不动 fd 模型却同时消掉 B10 与"消费者卡死"两类事故,性价比最高 |
-| D6 | 真机/模拟器**双模型分裂暂不消除** | 消除即等于做 P3;先靠不变量 I/II 把应用侧契约对齐 |
+| D1 | **A′(启用芯片多连接模式)出局**,根治必须在单连接复用模型内做 | §1.4:多监听不支持 / 池按源端口匹配会互串 / 实测 TRAP-NET-02、-11 / FUN_PARA 初始化后不可改 |
+| D2 | 采用**电平化 + 单一所有者**路线,**不做监听器一等对象化**(listener 独立对象 + `accept` 返回虚拟句柄) | 后者需控制面重写 15~25 函数、RAM/Flash 上升,只有"同端口并发多连接"需求才值得 |
+| D3 | 周期对账**保留**,其副作用由不变量消除 | TRAP-NET-17 实证事件面不是可靠唯一信源(E14 复核:非芯片侧丢失,是读清消费)→ 周期对账不可删 |
+| D4 | `FUN_PARA` **保持 `0x08`**,不因孤儿态改配置 | bit3=1 是"保留排空现场"契约;bit3=0 会令芯片在 DISCONNECT/TIM_OUT 时**当场清缓冲** →「发完即 FIN」的尾部数据必被硬件擦除、**不可补救**。完整论证见 §4.9 |
+| D6 | 真机/模拟器**双模型分裂暂不消除** | 消除即等于做监听器一等对象化;先靠不变量把应用侧契约对齐 |
| D7 | 同族残留**不在监听生命周期根治范围**,各自单列:U1(CONNECTING 滞留)、U11、U12、U14、U15,以及审计项 B4~B9 | 与监听生命周期不同因,混做会让回归面失控 |
-## 11. 监听生命周期的模块化边界(`net_listener.c`)
+> `D5` 是"分阶段实施顺序"的决策,实施完成后不再构成任何设计含义 → 已删除;**编号不回收**
+> (沿用 `docs/编号规范.md` §5.5 的游标不回退约定)。
-> **动机**:真机 net 层 `net_socket.c` 一度 4126 行,而模拟器做同样的事只有 807 行(5 倍);
-> `relisten` 一词在文件里出现 110 次。诉求 = **高内聚低耦合**。
-> 先由 §4.1 把"周期性重建"收敛到唯一决策者,搬移边界才第一次清晰——**先收敛,再搬移**。
->
-> ⚠ **本节只回答"组织度"**(搬什么、边界在哪、接口多窄)。若问的是"这套沉降 / 重试机器
-> 本身是不是我们想多了"——那是**合法性**问题,见 §1.5 与 ioLibrary 的代码级对照。
+## 11. 模块化边界(`net_listener.c` / `net_events.c`)
-### 11.1 可机械检验的判据
+> **本节只回答"组织度"**:搬什么、边界在哪、接口多窄。若问的是"这套沉降 / 重试机器本身
+> 是不是我们想多了"——那是**合法性**问题,见 §1.5 与 ioLibrary 的代码级对照。
+> 前提:**先收敛,再搬移**——§4.1 先把周期性重建收敛到唯一决策者,搬移边界才清晰。
-| # | 判据 | 收敛前 | 目标 | 结果 |
-|---|---|---|---|---|
-| 1 | 模块外调用「重建监听」的**入口** | 8 处散点直调 + 2 处直写状态数组 | **单一入口 `net_listener_request`**(登记)+ **单一执行者 `net_listener_tick`**(动作) | 5 个登记点(形态③④ / accept CLOSED 分支 / recv EOF / `net_drop_conn` / 挂起 recv 断连),**0 处模块外执行**(E17) |
-| 2 | `s_relisten_*` 静态状态被**模块外直接读写** | 2 处外部写 | **0** | 0(外部写改为 `net_listener_settle`) |
-| 3 | 依赖方向 | 无 | 新模块 → `net_socket.c` **单向** | 达成(不反向 include) |
+### 11.1 可机械检验的判据(现状值)
-⚠ **明确不承诺**:本重构**不消除 fd 别名**(同一 Socket 既当监听器又当连接)。
+| # | 判据 | 现状 |
+|---|---|---|
+| 1 | 模块外调用「重建监听」的**入口** | **单一登记入口 `net_listener_request` + 单一执行者 `net_listener_tick`**;5 个登记点(形态③④ / accept CLOSED 分支 / recv EOF / `net_drop_conn` / 挂起 recv 断连),**0 处模块外执行** |
+| 2 | `s_relisten_*` 静态状态被**模块外直接读写** | **0**(外部经 `net_listener_settle`) |
+| 3 | 依赖方向 | 新模块 → `net_socket.c` **单向**(不反向 include) |
+
+⚠ **明确不承诺**:模块化**不消除 fd 别名**(同一 Socket 既当监听器又当连接)。
那是 CH395F 单连接模型 + bit1 多连接模式**不支持多监听**(§1.4)锁死的,属硬件上限。
重构只提升**组织度**,不改变架构上限——**别指望补丁消失,只指望补丁不再重复**。
### 11.2 边界划分
-**搬入 `Drivers/BSP/NET/net_listener.c`**(**真机专属**,sim 不编):
+**在 `Drivers/BSP/NET/net_listener.c`**(**真机专属**,sim 不编):
-| 搬入 | 理由 |
+| 内容 | 理由 |
|---|---|
| `net_relisten_locked` / `net_listener_request` / `net_listener_tick` / `net_listener_clear` / `net_relisten_done` | 「维持 fd 处于 LISTEN 态」的全部机制 |
| `net_drain_chip_rx` | 重建前的排空(只被 relisten 调用) |
-| 8 个 `s_relisten_*` 数组 + 7 个 `NET_RELISTEN_*` 常量 | 状态与策略私有化(第 8 个 `s_relisten_capped` 是 E19 降级时新增,属 §1.5「可省清单」) |
-| ~~`net_listener_pump`~~ | 原唯一的周期性重建决策者——**阶段 3 已删除**,决策并入 `net_reconcile_tick` 形态③④(§4.1) |
+| 8 个 `s_relisten_*` 数组 + 7 个 `NET_RELISTEN_*` 常量 | 状态与策略私有化(`s_relisten_capped` 属 §1.5「可省清单」) |
+
+**在 `Drivers/BSP/NET/net_events.c`**(**真机专属**,sim 不编):
+
+| 内容 | 理由 |
+|---|---|
+| `net_reconcile_tick`(形态①②③④⑤/U11) | 周期面的失同步判据 |
+| 5 个 `handle_*_event` | 边沿面的事件处理 |
+| `net_lost_connect_probe` | 形态② 的补 accept 动作(与对账同职责) |
**留在 `net_socket.c`**(并说明理由,避免"看着像就搬"):
-| 留下 | 理由 |
+| 内容 | 理由 |
|---|---|
| `net_listen_locked` | 是**应用 API 路径**(首次监听,含 BOUND/CREATED 状态门与缓冲)——属"连接层建监听",不是"维持监听" |
| `net_accept_locked` | 产出的是**连接**(LISTENING→ESTABLISHED 迁移),属连接层;它只**调用**新模块 |
-| `net_lost_connect_probe` | 做的是"补 accept"(连接建立),不是重建监听 |
| `net_listener_armed` | **双端公开 API**(sim 也实现);纯 RAM 查询、很薄,留在原处避免 sim 缺符号 |
-| ~~`net_resync_listen`~~ | **阶段 1 已删除**:其"只改本层状态"语义并入对账⑤(§4.1),手动 API 面退役 |
-| `net_reconcile_tick` | ①②⑤ 是**连接**失同步自愈;③④/U11 亦已回归本 tick(阶段 3,§4.1) |
| `net_sock_t` 全部字段 | 数据归 `net_types.h`(双端共享核心),**不随模块走** |
> ⚠ `net_sock_t` 的 `last_peer_port` / `lost_conn_seen` 是**跨层字段**:`net_relisten_locked`
> 要写它们(转登幽灵甄别基准),`net_lost_connect_probe` 与对账②也要读写。
-> **这是本次重构无法消除的残余耦合**(单连接模型的固有结果),明确登记而非假装不存在。
+> **这是无法消除的残余耦合**(单连接模型的固有结果),明确登记而非假装不存在。
### 11.3 对外接口(4 函数 + 2 枚举,全部窄接口)
@@ -816,77 +785,54 @@ void net_listener_clear(int sockfd); /* 清本 fd 的重建/沉降状态(
void net_listener_tick(void); /* 唯一执行者(netTask 每轮,非阻塞;30ms/2s) */
```
-**反向依赖(新模块需要宿主提供的)**:`net_sock_t` 定义(`net_types.h`,共享头,不算反向依赖)、
-`net_set_errno` / `NET_ERR_RETURN_1`、`net_pending_active`(§11.4)、`ch395f_*` 驱动、`HAL_GetTick`。
-`net_listener.c` **不访问** `s_net_socks`——一律用 `p_sock->ch395_sock` 或调用方传入的 `sockfd`。
+**反向依赖(新模块需要宿主提供的)**:`net_sock_t` 定义(`net_types.h`,共享头,不算反向
+依赖)、`net_set_errno` / `NET_ERR_RETURN_1`、`net_pending_active`、`net_sock_raw`、
+`ch395f_*` 驱动、`HAL_GetTick`。`net_listener.c` **不访问** `s_net_socks`——一律用
+`p_sock->ch395_sock` 或调用方传入的 `sockfd`。
-### 11.4 落地结果与三处「非纯搬移」
+### 11.4 当前代码形状的由来(四条非搬移约束)
-`net_socket.c` 4126 → 3656 行(-471);新增 `net_listener.c` / `net_listener.h`。
-`RW-data` 980 → 980、`ZI-data` 143060 → 143060 **完全不变**(数据总量不变);
-`Code` / `RO-data` 各 -8 / -12 字节(相对 471 行搬移可忽略)。
-接口由「8 处散点直调 + 2 处直接写数组」收敛为 **4 个窄接口**;`s_net_socks` 依赖**已消除**。
-`@test` PASS + 回读 `No difference found`。
-
-执行中暴露三处**必要**的"非纯搬移"(均已在代码注释留痕,是当前代码形状的成因):
-
-1. **`net_set_errno` 由 `static` 提升为模块间可见** —— 否则新模块链接期报 `L6218E: Undefined symbol`;
- `NET_ERR_RETURN_1` 宏随之迁到 `net_socket.h`(它依赖前者,不能只移宏)。
-2. **新增 `net_pending_active(fd)`** —— `net_listener_tick` 原有「本 fd 有在途挂起 recv 则跳过」
- (L3 保险,防重开 Socket 摧毁在途连接)直接读 `s_net_pending[i].active`;新模块不能碰该 static
- 数组,故加薄访问器。**语义原样保留**。
-3. **`net_listener.c` 必须真机专属** —— 它依赖 `ch395f_*` SPI 驱动,而 sim 是 Winsock 后端
- (无 `ch395f_*`、无孤儿态、无重监听概念)→ **无法**在 sim 编译。故只注册 Keil 端,
- 并在 `scripts/check_registration.py` 的 `SIM_EXEMPT` **显式点名**(遵"显式清单 + 每行理由"原则)。
-
-> **判据修订留痕**:原定「纯搬移可证 = Code / RO-data / RW-data / ZI-data 完全不变」**过严**——
-> 接口改造必然改变代码生成。修订为:**RW-data / ZI-data 必须完全不变**(数据总量不变),
-> `Code` / `RO-data` 允许极小变化。
-
-### 11.5 同族第二模块:`net_events.c`(2026-10-14,事件处理 + 周期对账)
-
-§11 把"动作"搬进 `net_listener.c` 后,`net_socket.c` 里剩下的**判据**(`net_reconcile_tick`
-形态①②③④⑤/U11)与**边沿事件处理**(5 个 `handle_*_event`)仍是同一职责的两半——
-一个周期面、一个事件面,都在做"芯片状态面 ↔ 本层镜像"的收敛。本次把它们合成**一个**模块。
-
-**结果**:`net_socket.c` 3720 → 3119 行(−601);新增 `net_events.c` 663 / `net_events.h` 46。
-`RW-data` 1112 → 1112、`ZI-data` 154456 → 154456 **完全不变**;`Code` 212316 → 212296(−20 B,
-拆 TU 的链接器布局差)。`@test` PASS:635ms 到 `net init: OK`,`:8888`/`:2404`/FTP 均建立,
-`[ERR]`/Fault/relisten/reconcile 命中 **0** 条。
-
-**为什么是一个模块而不是两个**(与原计划不同,值得记):剥掉注释后实测**对账区真实调用
-事件处理器 2 处**(形态② 补 accept 走 `handle_connect_event`),且 `NET_RECON_STABLE_DEATH`
-两边共用。拆成 `net_events.c` + `net_reconcile.c` 会造出跨模块调用;合起来这 2 处变模块内
-直调,**跨模块导出为零**。⚠ 依赖扫描**必须先剥注释**——未剥时 `alloc_socket` /
-`net_close_locked` / `net_relisten_locked` 全被误报成依赖(其实只是注释里的提及),
-足以把人引向错误的边界。
-
-**三处「非纯搬移」**(沿用 §11.4 的口径,均已留痕):
-
-1. **fd 取法替换**:`p_sock - s_net_socks` → `p_sock->ch395_sock`(`net_socket()` 里
- `ch395_sock = sockfd`,恒等;模拟器同构造)。⚠ 随之**收窄**两处沉降登记的范围门
- `(qfd >= 0) && (qfd < MAX)` → `qfd < MAX`:`ch395_sock` 是 `uint8_t`,`(int)` 后恒非负,
- 留着被 cppcheck 判 `knownConditionTrueFalse` 拦提交。**勿加回**。
-2. **新增 `net_sock_raw()`**:对账必须看到 `in_use==0` / `state==CLOSED` 的槽位(形态④
- 认领孤儿监听正依赖 CLOSED 态),而 `net_get_sock()` 对未在用槽位返回 NULL 会漏掉这一类。
+1. **`net_set_errno` 由 `static` 提升为模块间可见** —— 否则新模块链接期报 `L6218E:
+ Undefined symbol`;`NET_ERR_RETURN_1` 宏随之迁到 `net_socket.h`(它依赖前者,不能只移宏)。
+2. **`net_pending_active(fd)` / `net_send_pending_active(fd)` 薄访问器** —— 监听器的
+ 「本 fd 有在途挂起则跳过」保险(防重开 Socket 摧毁在途连接/在途传输)不得直读挂起 static
+ 表,**语义原样保留**。
+3. **`net_sock_raw()`** —— 对账必须看到 `in_use==0` / `state==CLOSED` 的槽位(形态④ 认领
+ 孤儿监听正依赖 CLOSED 态),而 `net_get_sock()` 对未在用槽位返回 NULL 会漏掉这一类。
声明放**配对头** `net_socket.h` 而非内部头 `net_events.h`——规范 **S17 2.2** 要求非 static
- 函数声明在配对头文件(同 §11.4 第 1 条 `net_set_errno` 先例)。
-3. **真机专属 + `SIM_EXEMPT` 显式点名**:同 §11.4 第 3 条(依赖 `ch395f_*` SPI 与驱动状态
- 缓存,Winsock 后端无"事件未收到/静默回收"概念,无失同步可收敛)。
+ 函数声明在配对头文件。
+4. **两个新模块都真机专属** —— 依赖 `ch395f_*` SPI 驱动与驱动状态缓存,而 sim 是 Winsock
+ 后端(无 `ch395f_*`、无孤儿态、无"事件未收到/静默回收"概念)→ **无法**在 sim 编译。
+ 故只注册 Keil 端,并在 `scripts/check_registration.py` 的 `SIM_EXEMPT` **显式点名**
+ (遵"显式清单 + 每行理由"原则)。
-**搬移完整性的机械证明**:剥注释后取**代码行多重集**,`net_socket.c` + `net_events.c` 之和
-与原 `net_socket.c` 相比,差异**恰好等于**上面声明的改动集(3 处 fd 取法 + 1 处数组访问 +
-8 处 `static` 去除 + 6 行前向声明 + 新文件自身 15 行头 + `net_sock_raw` 三行),**无一处意外**。
-搬移类改动只有配上这种证明才敢合入——否则"注释和代码混在一起改"无法区分意外丢失。
+### 11.5 为什么对账与事件处理是**一个**模块(`net_events.c`)
+
+判据(`net_reconcile_tick` 形态①②③④⑤/U11)与边沿事件处理(5 个 `handle_*_event`)是同一
+职责的两半——一个周期面、一个事件面,都在做"芯片状态面 ↔ 本层镜像"的收敛。
+
+**为什么不是一个模块拆成两个**:剥掉注释后实测**对账区真实调用事件处理器 2 处**(形态② 补
+accept 走 `handle_connect_event`),且 `NET_RECON_STABLE_DEATH` 两边共用。拆成
+`net_events.c` + `net_reconcile.c` 会造出跨模块调用;合起来这 2 处变模块内直调,
+**跨模块导出为零**。
+
+**两条可复用的方法论**:
+
+- ⚠ **依赖扫描必须先剥注释**——未剥时 `alloc_socket` / `net_close_locked` /
+ `net_relisten_locked` 全被误报成依赖(其实只是注释里的提及),足以把人引向错误的边界。
+- **搬移类改动要配机械证明**:剥注释后取**代码行多重集**,新旧之和比对,差异必须**恰好等于**
+ 声明的改动集(fd 取法替换、`static` 去除、前向声明、新文件自身头部、新增访问器),
+ **无一处意外**——否则"注释和代码混在一起改"无法区分意外丢失。
## 12. 引用与归档约定
- 本文引用格式:`docs/网络/网络自愈架构设计.md` §N.M(紧邻写法);E 编号引 §5.2
(如"§5.2 E12"),F 编号引 §7。
-- **勿引用已删除的过程章节**:N34 审计表(T1~T4、实验方法与原始记录、分阶段勾选清单),
- 以及原《网络Socket监听生命周期设计说明》的 P0/P1/P2 勾选清单与台架验收记录。
- 过程考古:
- - N34 自愈审计:`git show 1a35b36^:docs/网络/N34-自愈审计/`(目录合并前 7 份文档)或
+- **勿引用已删除的过程章节**。过程考古:
+ - N34 自愈审计(T1~T4 三路审计、39 条编号发现全文、E0~E15 原始记录、分阶段实施清单):
+ `git show 1a35b36^:docs/网络/N34-自愈审计/`(目录合并前 7 份文档)或
`git log --oneline -- docs/网络/N34-自愈审计/`;
- - 监听生命周期设计说明(本文 §1.1~§1.4 / §4.9 / §9~§11 的来源):
- `git log --oneline -- docs/子系统设计/网络Socket监听生命周期设计说明.md`。
+ - 监听生命周期(本文 §1.1~§1.4 / §4.9 / §9~§11 的来源,含 P0~P3 勾选清单与台架验收记录):
+ `git log --oneline -- docs/子系统设计/网络Socket监听生命周期设计说明.md`;
+ - `自愈收敛阶段 N` 只出现在历史文档与变更记录里(代码注释已清理为平实语言)——
+ **勿再引入**,设计含义一律以本文 §2~§4 为准。
diff --git a/simulator/sim/net_socket_sim.c b/simulator/sim/net_socket_sim.c
index ebe12428..b92b2eca 100644
--- a/simulator/sim/net_socket_sim.c
+++ b/simulator/sim/net_socket_sim.c
@@ -11,7 +11,7 @@
* 设计要点:
* 1. fd 采用与 MCU 端一致的"控制块索引"模型(0 ~ NET_MAX_SOCKETS-1)
* 2. Winsock 句柄创建即置非阻塞(ioctlsocket FIONBIO),阻塞语义在包装层
- * 按控制块 blocking 属性模拟(自愈收敛阶段 1.5:模式归使用者——blocking=1
+ * 按控制块 blocking 属性模拟(模式归使用者——blocking=1
* 时 recv 轮询等待(Sleep(1)),=0 或 NET_MSG_DONTWAIT 立即 WOULDBLOCK),
* 使 net_recv / net_recv_nb / net_recv_to / net_accept 的语义与 MCU 端一致
* 3. 代次号在模拟器上恒为 0(无"网络重建"概念),net_get_generation 恒返回 0
@@ -215,7 +215,7 @@ int net_socket(int domain, int type) {
s_socks[idx].type = (uint8_t)type;
s_socks[idx].state = NET_SOCK_STATE_CREATED;
s_socks[idx].ch395_sock = (uint8_t)idx;
- /* 自愈收敛阶段 1.5:阻塞模式默认 = 阻塞(与 MCU 端 net_socket 一致;
+ /* 阻塞模式默认 = 阻塞(与 MCU 端 net_socket 一致;
* Winsock 句柄仍恒非阻塞,阻塞语义由包装层按本属性模拟,见文件头要点 2) */
s_socks[idx].blocking = 1;
s_ws[idx] = ws;
@@ -334,7 +334,7 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
s_socks[nidx].type = (uint8_t)NET_SOCK_STREAM;
s_socks[nidx].state = NET_SOCK_STATE_ESTABLISHED;
s_socks[nidx].ch395_sock = (uint8_t)nidx;
- /* 自愈收敛阶段 1.5:accept 出的新连接同样默认阻塞模式(与 MCU 端一致) */
+ /* accept 出的新连接同样默认阻塞模式(与 MCU 端一致) */
s_socks[nidx].blocking = 1;
s_ws[nidx] = cws;
@@ -342,7 +342,7 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
return nidx;
}
-/* 自愈收敛阶段1(F-NET-4):net_can_accept 双端退役——监听 accept 门控统一用
+/* F-NET-4:net_can_accept 双端退役——监听 accept 门控统一用
* net_listener_armed()(本文件同名实现保留恒 1 语义),recv 门控 net_can_read() 保留。 */
/*
@@ -517,7 +517,7 @@ static int recv_wait(int sockfd, void *buf, int len, int has_budget, unsigned lo
}
/*
- * 函数功能:接收数据(TCP,**默认无限阻塞**——自愈收敛阶段 1.5 与 MCU 端语义对齐)
+ * 函数功能:接收数据(TCP,**默认无限阻塞**——与 MCU 端语义对齐)
* 入口参数:sockfd - socket 描述符
* buf - 接收缓冲区
* len - 缓冲区大小
@@ -542,12 +542,12 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
if ((flags & NET_MSG_DONTWAIT) || s_socks[sockfd].blocking == 0U) {
return recv_once(sockfd, buf, len);
}
- /* 阻塞:无限等待(无默认预算,自愈收敛阶段 1.5) */
+ /* 阻塞:无限等待(无默认预算) */
return recv_wait(sockfd, buf, len, 0, 0ULL);
}
/*
- * 函数功能:接收数据(非阻塞具名入口,与 MCU 端同名 API 对齐——自愈收敛阶段 1.5)
+ * 函数功能:接收数据(非阻塞具名入口,与 MCU 端同名 API 对齐)
* 入口参数:sockfd / buf / len - 同 net_recv
* 返回值:>0 字节数;0 = 对端关闭;-1 = 无数据(WOULDBLOCK) 或错误
* 限定条件:socket 已建立
@@ -566,7 +566,7 @@ int net_recv_nb(int sockfd, void *buf, int len) {
}
/*
- * 函数功能:接收数据(**显式预算**便捷入口,与 MCU 端同名 API 对齐——自愈收敛阶段 1.5)
+ * 函数功能:接收数据(**显式预算**便捷入口,与 MCU 端同名 API 对齐)
* 入口参数:sockfd / buf / len - 同 net_recv
* timeout_ms - 本次等待预算(ms,必须 >0;0 = 无限等待语义
* 属 net_recv 的职责,此处拒 INVAL,与 MCU 端一致)
@@ -709,7 +709,7 @@ int net_close(int sockfd) {
}
/*
- * 函数功能:主动拆掉当前 TCP 会话、保留监听(与 MCU 端同名 API——自愈收敛阶段 3,E-02 收口)
+ * 函数功能:主动拆掉当前 TCP 会话、保留监听(与 MCU 端同名 API——E-02 收口)
* 入口参数:sockfd - socket 描述符
* 出口参数:无
* 返回值:0 成功(含幂等:无活动会话),-1 失败
@@ -766,7 +766,7 @@ int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) {
}
/*
- * 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(与 MCU 端同名 API 对齐——自愈收敛阶段 1.5)
+ * 函数功能:设置 Socket 阻塞/非阻塞**模式属性**(与 MCU 端同名 API 对齐)
* 入口参数:sockfd - socket 描述符
* on - 1 = 阻塞(net_socket/net_accept 默认);0 = 非阻塞
* 返回值:0 成功;-1 失败(描述符无效/未占用)
diff --git a/test/board/ch395f_reuse_bench.c b/test/board/ch395f_reuse_bench.c
index c05417d2..0ca418ec 100644
--- a/test/board/ch395f_reuse_bench.c
+++ b/test/board/ch395f_reuse_bench.c
@@ -13,7 +13,7 @@
* 旧 N-30 定案矩阵(对照 vs "CONNECT 事件未收到捕获自 accept"修复组)保留在 s_vars[]/
* bench_round(),由 s_bench_legacy=0 停泊不运行。
*
- * 2026-10-13 自愈收敛阶段 2 前置实验(task-9):同一固件合并跑 E2/E3/E4——
+ * 驱动原语前置实验:同一固件合并跑 E2/E3/E4——
* E2 = GET_SOCKET_STATUS 单次 SPI 成本(DWT 周期计数)+ TCP_LISTEN 可见延迟矩阵;
* E3 = GET_CMD_STATUS 判别力矩阵(OPEN/LISTEN/CLOSE/SET_SOUR_PORT/SET_PROTO_TYPE,
* 含"命令未发时"对照读,复核 Trap 记录的 SET 类无判别力 + 陈旧 BUSY 残留);
@@ -392,7 +392,7 @@ static void bench_round(unsigned idx) {
}
/* =====================================================================
- * 自愈收敛阶段 2 前置实验区(2026-10-13):E2 / E3 / E4 一次固件合并跑
+ * 驱动原语前置实验区:E2 / E3 / E4 一次固件合并跑
* ===================================================================== */
/* 旧 N-30 定案矩阵停泊开关:0 = 不运行(保留代码备查,勿删) */
diff --git a/test/board/ch395f_test.h b/test/board/ch395f_test.h
index 1fbc9b45..89bceab1 100644
--- a/test/board/ch395f_test.h
+++ b/test/board/ch395f_test.h
@@ -67,7 +67,7 @@ extern "C" {
* E11 实证);勿与 Phase 6/9/10/13 混选(recv 互抢数据) */
/* ---- 链路时延基线(M1;分段/阈值见 docs/测试说明/链路延迟测试说明.md)---- */
//#define ENABLE_PHASE12_TESTS /* [硬] SPI 字节命令 / DMA 时延基线(L1/L2,无需 PC)—— M1 已完成,基线见说明文档 §6.1 */
-//#define ENABLE_PHASE15_TESTS /* [硬] 自愈收敛阶段 2 驱动原语:状态回读一致性/cmd_done/sock_reset 状态机跑通/链路缓存(无需 PC)——TC 详见 docs/测试说明/CH395F_Test_Guide.md 阶段 15;2026-10-13 台架 20/20 PASS */
+//#define ENABLE_PHASE15_TESTS /* [硬] 自愈驱动原语:状态回读一致性/cmd_done/sock_reset 状态机跑通/链路缓存(无需 PC)——TC 详见 docs/测试说明/CH395F_Test_Guide.md 阶段 15;台架 20/20 PASS */
/* ---- 链路时延(M2/M3;分段/阈值见 docs/测试说明/链路延迟测试说明.md)---- */
//#define ENABLE_PHASE13_TESTS /* [网] 芯片 + 队列时延(L3~L7/L10,需 PC peer)—— M2 已完成,基线见说明文档 §6.2 */
#define ENABLE_PHASE14_TESTS /* [网] 生产路径时延(L8~L11 + 模拟器 Δ)—— M3,需 peer --mode remo(:7003);
diff --git a/test/board/ch395f_test_task.c b/test/board/ch395f_test_task.c
index a6840778..ec0371dd 100644
--- a/test/board/ch395f_test_task.c
+++ b/test/board/ch395f_test_task.c
@@ -1580,7 +1580,7 @@ static void phase5_run(void) {
#endif
/* ===================================================================
- * Phase 15 — 自愈收敛阶段 2 驱动原语(无需 PC)
+ * Phase 15 — 自愈驱动原语(无需 PC)
*
* 测试目的:验证 ch395f 新增原语(设计 docs/网络/网络自愈架构设计.md §4.4)
* 语义正确:类型化状态回读一致性 / cmd_done(E3 定案=状态回读)/
@@ -1734,7 +1734,7 @@ void ch395f_test_run(void)
net_latency_phase12_run();
#endif
#ifdef ENABLE_PHASE15_TESTS
- /* Phase 15(自愈收敛阶段 2 驱动原语):独占态下直调驱动,放 HW 层末尾 */
+ /* Phase 15(自愈驱动原语):独占态下直调驱动,放 HW 层末尾 */
phase15_run();
#endif
}
diff --git a/test/board/net_latency_test.c b/test/board/net_latency_test.c
index fea00399..7bc730bd 100644
--- a/test/board/net_latency_test.c
+++ b/test/board/net_latency_test.c
@@ -760,10 +760,10 @@ void net_latency_phase13_run(int32_t sockfd)
if (s_p13_state != P13_ST_ABORT) {
return; /* 正常完成/DONE:会话终态,不再处理 */
}
- /* 自愈收敛阶段1(F-NET-3):原"进 abort 分状态手动重同步 + 10s 周期 relisten"
- * 已删——三种去向全部由 net 层自动机制认领(run8 静默回收滞留 ESTABLISHED
- * → 对账⑤ RESYNC;CLOSED 失位 → pump 100ms 认领重建;LISTENING 静默丢
- * 监听 → pump"芯片不在听"判据),手动 API net_resync_listen/net_relisten
+ /* F-NET-3:原"进 abort 分状态手动重同步 + 10s 周期 relisten"
+ * 已删——三种去向全部由 net 层自动机制认领(静默回收滞留 ESTABLISHED
+ * → 对账⑤ RESYNC;CLOSED 失位 → 对账③④ 认领重建;LISTENING 静默丢
+ * 监听 → 形态③"芯片不在听"判据),手动 API net_resync_listen/net_relisten
* 已退役。此处只保留收包与 peer re-HELLO 探测。 */
n = net_recv(sockfd, s_p13_rx, (int)sizeof(s_p13_rx), NET_MSG_DONTWAIT);
if (n > 0) {
diff --git a/test/host/e02_probe.py b/test/host/e02_probe.py
index 81c0de5d..66d98220 100644
--- a/test/host/e02_probe.py
+++ b/test/host/e02_probe.py
@@ -7,7 +7,7 @@
全体消费者的队列消息停摆最长 30s;
2. 2s 后从 PC 发起 FTP(:21) 连接,测 banner 到达时延 = 队列响应性:
修复前 ≈ 25~30s(队头冻结),修复后 ≈ 数百 ms 内;
- 3. 34s 时 30s 总预算应已耗尽:设备拆掉该会话(自愈收敛阶段 3 net_drop_conn:
+ 3. 34s 时 30s 总预算应已耗尽:设备拆掉该会话(net_drop_conn:
FIN 关闭 + 本层收口 + 重监听),随后**本探针模拟守规矩对端**:
close 自己的 socket;
4. 芯片回 CLOSED(TC-NET-204 模式 A:TIME_WAIT 过渡 ~150ms)→ relisten,
diff --git a/test/host/time_sync/time_sync_test.c b/test/host/time_sync/time_sync_test.c
index 53347ea7..48d7747f 100644
--- a/test/host/time_sync/time_sync_test.c
+++ b/test/host/time_sync/time_sync_test.c
@@ -235,7 +235,7 @@ int net_close(int sockfd) {
return 0;
}
-/* ---- net_drop_conn(自愈收敛阶段 3 E-02 收口):计数 + 记录 fd ---- */
+/* ---- net_drop_conn(E-02 收口):计数 + 记录 fd ---- */
static int s_drop_calls;
static int s_drop_socks[32];
@@ -511,7 +511,7 @@ static void tc_open_listener(void) {
}
/* ===================== TC-TS-020..029:handle_conn =====================
- * ⚠ 自愈收敛阶段1(2026-10-13)契约变更:handle_conn **不再** net_close
+ * ⚠ 契约变更:handle_conn **不再** net_close
* (单连接模式下 conn==listen,close 会连监听一起毁);会话收尾只返回外层,
* 由 time_sync_task 在 `conn != ls`(模拟器多连接后端)时才关数据 fd。
* 故本组断言一律为 s_close_calls == 0;任务级关闭见 TC-TS-040e。 */
@@ -530,7 +530,7 @@ static void tc_handle_conn(void) {
CHECK(s_set_unix_last == 0x12345678u, "TC-TS-020b epoch 按大端解析");
CHECK((s_recv_calls == 1) && (s_recv_len_log[0] == 16), "TC-TS-020c 首次请求 16 字节");
CHECK(s_recv_to_log[0] == 50U, "TC-TS-020d 单次等待片长 = 50ms");
- CHECK(s_close_calls == 0, "TC-TS-020e 处理完**不**关闭连接(自愈收敛阶段1:close 交外层 conn!=ls 分支)");
+ CHECK(s_close_calls == 0, "TC-TS-020e 处理完**不**关闭连接(close 交外层 conn!=ls 分支)");
CHECK(s_drop_calls == 0, "TC-TS-020g 成功会话不拆(drop 只用于放弃路径)");
CHECK(g_tstest_info_count == 1, "TC-TS-020f 留痕 wall clock updated");
@@ -605,7 +605,7 @@ static void tc_handle_conn(void) {
CHECK(s_recv_calls == 1, "TC-TS-027 预算耗尽后不再读(只读 1 次)");
CHECK(g_tstest_info_count == 1, "TC-TS-027b 留痕 no TIME packet in 30s, drop conn");
CHECK(s_close_calls == 0, "TC-TS-027c 预算耗尽也不关闭连接");
- CHECK(s_drop_calls == 1, "TC-TS-027d 预算耗尽主动拆会话(自愈收敛阶段 3 E-02 收口:net_drop_conn 拆连接保监听)");
+ CHECK(s_drop_calls == 1, "TC-TS-027d 预算耗尽主动拆会话(E-02 收口:net_drop_conn 拆连接保监听)");
/* 9) 剩余预算不足一片 */
fakes_reset();
diff --git a/test/host/time_sync_server.py b/test/host/time_sync_server.py
index 8b7d9efc..e3af15a9 100644
--- a/test/host/time_sync_server.py
+++ b/test/host/time_sync_server.py
@@ -57,7 +57,7 @@ def main():
# 关键:发送后保持连接打开,给板子留出读完 8 字节的窗口。
# 若立即 close,CH395F 收到 FIN 会丢弃接收缓冲里尚未被板子读出的
# 8 字节时间包,导致板子 net_recv 看到 CLOSED 而无数据、时间同步失败。
- # 自愈收敛阶段1-5 起板子会话收尾**不再 close**(单连接监听跨会话常驻,
+ # 板子会话收尾**不再 close**(单连接监听跨会话常驻,
# EOF 时 net_close 会连监听一起毁掉)——这里 recv 等 2s 宽限后由
# 本端关闭;板子 net 层 auto_relisten 随即恢复监听,下一轮照常连接。
sock.settimeout(2.0)
diff --git a/tools/arch_diagram/gen_net_instability_svg.py b/tools/arch_diagram/gen_net_instability_svg.py
index 34084a05..6b0b04a4 100644
--- a/tools/arch_diagram/gen_net_instability_svg.py
+++ b/tools/arch_diagram/gen_net_instability_svg.py
@@ -72,13 +72,13 @@ RINGS = [
"机制:accept/recv/pending 三处都重建监听,谁先看到断连谁动手互相踩脚"],
"修电梯的只剩一个人",
["重建收敛到唯一所有者(net_listener + reconciler),其余位置只置事实不动手",
- "不变量 II:唯一周期重建决策者(消费者即时重建三处保留,P2a 实测)"]),
+ "不变量 II:唯一周期重建决策者(消费者即时重建三处保留,台架实测)"]),
("⑤ 传达员被按在椅子上", "队头阻塞",
["症状:FTP banner 迟到 25~30 s(E-02 实测),netTask 频繁冻结半秒",
"机制:30 s 阻塞收 + 500 ms 盲等忙等,而 netTask 是全楼唯一传达员"],
"传达员永不离开传达室",
["忙等统一为 net_sock_reset_sync 有界排渣(600 ms 硬上限),阻塞消息化",
- "阶段 1.5:net_recv 默认非阻塞 + net_recv_nb + 60 s tripwire 日志"]),
+ "recv 语义主流化:net_recv 默认无限阻塞 + net_recv_nb + 60 s tripwire 日志"]),
("⑥ 僵尸客人赖着不走", "E13:网卡闪断",
["症状:设备网络「莫名死了」、新连接被拒——在和幽灵做生意",
"机制:PC 网卡闪断,内核悄悄撕掉 socket 不发 FIN,芯片侧仍 ESTABLISHED 占死柜台"],
@@ -101,11 +101,11 @@ RINGS = [
SUM_LEFT = ["七环叠加 =「玄学不稳」",
"随机 · 无错误日志 · 时好时坏——每个环节单看都「正常工作」"]
-SUM_RIGHT = ["逐环拆掉 = 稳(自愈收敛阶段 0~3 已实施)",
+SUM_RIGHT = ["逐环拆掉 = 稳(现状机制已全部落地)",
"不依赖门铃 · 唯一主人 · 永不冻结 · 100ms 对账 · 开张前排渣——台架回归全绿"]
FOOT = ("证据链:docs/问题记录/CH395F_Trap_Records.md(TRAP-NET-07/-09/-12/-17/-18/-20) · "
- "架构现状 docs/网络/网络自愈架构设计.md §2~§4 · 实证锚点同文档 §5.2(E1~E13)")
+ "架构现状 docs/网络/网络自愈架构设计.md §2~§4 · 实证锚点同文档 §5.2(E0~E19)")
# ----------------------------- 工具函数 -----------------------------
@@ -195,7 +195,7 @@ def build():
% (PAD, 40, TITLE_FONT, esc("为什么网络「一直不稳」——七个误会与逐个拆掉(自愈架构复盘)")))
add('%s'
% (PAD, 62, SUB_FONT,
- esc("左列 = 根因(比喻 + 机制);右列 = 自愈收敛阶段 0~3 的对应拆法;环与环叠加才构成「玄学」")))
+ esc("左列 = 根因(比喻 + 机制);右列 = 现状机制的对应拆法;环与环叠加才构成「玄学」")))
for (pt, ps, lrows, ft, rrows, by, h) in placed:
draw_band(out, pt, ps, lrows, PAD, by, lw, h, C_PROB, C_PROB_BG)