diff --git a/Drivers/BSP/NET/net_socket.c b/Drivers/BSP/NET/net_socket.c index e111007c..c8482180 100644 --- a/Drivers/BSP/NET/net_socket.c +++ b/Drivers/BSP/NET/net_socket.c @@ -535,11 +535,12 @@ int net_poll(void) { * 函数说明:1. 芯片在快速复连场景会丢 CONNECT/DISCONNECT 中断;丢事件后本层无法靠事件自愈 * 2. 典型:对端连接占着单连接监听的"后备"(芯片 TCP 仍 ESTABLISHED),后续 SYN 全被拒 * 3. 应用因未收到 DISCONNECT 仍以为有客户端,永不调用 accept,事件驱动自愈无路可走 - * 4. 故周期回读芯片状态,修正三类失同步: - * 5. ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(应用读到 EOF 后自动重监听) + * 4. 故周期回读芯片状态,修正**两类**失同步: + * 5. ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(只置事实;重建交 pump) * 6. ② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(复用 handle_connect_event) - * 7. ③ 本层 LISTENING 而芯片监听已消失 → 重建监听(net_relisten_try) - * 7b.④ 本层 CLOSED 但 fd 在用且已绑端口(孤儿监听)→ 重建监听(B10) + * 7. ⚠ N-32 P2(2026-10-12):原③(本层 LISTENING 丢监听)与④(孤儿监听) + * 已**整体移交** `net_listener_pump` —— 本函数内不再有任何重建动作 + * (单一所有者);认领门槛相应收窄为 ESTABLISHED/LISTENING * 8. 单次状态读可能落在"刚 open/listen 尚未生效"的瞬态 * 9. 同一失配需连续 NET_RECONCILE_STABLE_N 次才动手(2026-09-23 实测:无去抖时监听被反复重建直至放弃) * 10. 同一 socket 动手后冷却 NET_RECONCILE_COOLDOWN_MS,防去抖成风暴 @@ -576,23 +577,13 @@ static void net_reconcile_tick(void) { /* 失同步类型:1=连接已失 2=握手已成丢中断 3=监听已失 */ int bad = 0; - /* 对账认领范围:ESTABLISHED / LISTENING,外加"孤儿监听"(④)。 - * 孤儿 = fd 在用、**监听型**(auto_relisten,仅 net_listen_locked:1135 置位) - * 且已绑端口的 Socket 停在 CLOSED:断开事件已被 handle_disconnect_event - * 消费(置 CLOSED、不再向应用上报 EOF),应用那边还在等 net_recv 的 0 - * 才肯重建 —— 两边都不动手,芯片也不会自己回 LISTEN,端口就此永久拒连 - * (B10,2026-10-12 实测 :7003 芯片侧 0x05/0x00 = Socket 开着但不在 - * LISTEN,30s 不自恢复)。 - * ⚠ auto_relisten 是这里的**决定性门**:少了它,"本层 CLOSED + 已绑端口" - * 的出向连接(板级 Phase 7 TC701~703 用 net_connect 绑本地口)会被误判 - * 孤儿并被 relisten 成监听器 */ + /* 对账认领范围:**仅 ESTABLISHED / LISTENING**。 + * ⚠ 原先还认领"孤儿监听"(CLOSED + auto_relisten + 已绑端口)以驱动对账④; + * N-32 P2 把③④整体移交给 net_listener_pump(唯一重建入口),本函数只剩 + * ①②——两者都以 ESTABLISHED/LISTENING 为前提,故认领门槛相应收窄。 */ if ((p_sock->in_use == 0U) || ((p_sock->state != NET_SOCK_STATE_ESTABLISHED) && - (p_sock->state != NET_SOCK_STATE_LISTENING) && - !((p_sock->state == NET_SOCK_STATE_CLOSED) && - (p_sock->auto_relisten != 0U) && - (p_sock->type == NET_SOCK_STREAM) && - (p_sock->local_port != 0U)))) { + (p_sock->state != NET_SOCK_STATE_LISTENING))) { s_bad[i] = 0U; continue; } @@ -616,17 +607,9 @@ static void net_reconcile_tick(void) { if (!((peer != 0U) && (peer == p_sock->last_peer_port))) { bad = 2; } - } else if ((p_sock->state == NET_SOCK_STATE_LISTENING) && - ((st[0] == CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_LISTEN))) { - /* ③ 本层 LISTENING 而芯片监听已消失 → 重建监听 */ - bad = 3; - } else if ((p_sock->state == NET_SOCK_STATE_CLOSED) && - ((st[0] == CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_LISTEN))) { - /* ④ 孤儿监听:本层 CLOSED + fd 在用 + **监听型** + 已绑端口,芯片侧不在 - * LISTEN(监听型这一条由上方认领门槛保证)→ 与③同一处置,但触发源不同 - * (③是监听态丢监听,④是压根没人重建) */ - bad = 4; } + /* ③④(本层 LISTENING 丢监听 / 本层 CLOSED 孤儿监听)已移交 + * net_listener_pump —— N-32 P2 单一所有者,本函数不再做重建动作 */ /* 无失同步:清零计数,转下一 socket */ if (bad == 0) { @@ -647,31 +630,134 @@ static void net_reconcile_tick(void) { s_cool[i] = now + NET_RECONCILE_COOLDOWN_MS; if (bad == 1) { - /* 类型①:按断开处理(应用读到 EOF 后自动重监听) */ + /* 类型①:按断开处理(应用读到 EOF 后由 pump 重建监听) */ DBG_INFO("sock%d reconcile: chip closed (sock=0x%02X tcp=0x%02X) -> disconnect", i, st[0], st[1]); - /* 复用断开状态机(含陈旧事件甄别 + 状态迁移 + 事件派发) */ + /* 复用断开状态机(含陈旧事件甄别 + 状态迁移 + 事件派发); + * 只置事实,重建由 net_listener_pump 统一做(N-32 P2) */ handle_disconnect_event(p_sock); - } else if (bad == 2) { + } else { /* 类型②:补做 standalone accept */ DBG_INFO("sock%d reconcile: connection completed w/o CONNECT irq -> accept", i); /* 复用 CONNECT 事件的簿记路径 */ handle_connect_event(p_sock); - } else if (bad == 3) { - /* 类型③:重建监听(单次尝试,失败进 D10 重试队列) */ - DBG_INFO("sock%d reconcile: listen lost (sock=0x%02X tcp=0x%02X) -> relisten", - i, st[0], st[1]); - (void)net_relisten_try(p_sock); - } else { - /* 类型④:孤儿监听 —— 补一次重建(B10)。net_relisten_try 自带 - * "已在重试队列则不重复登记"保护,与 D10 队列 / Trap 23 沉降隔离兼容 */ - DBG_INFO("sock%d reconcile: orphan listener (chip=0x%02X/0x%02X) -> relisten", - i, st[0], st[1]); - (void)net_relisten_try(p_sock); } } } +/* 监听器 pump 周期(对账 200ms 的 1/2:比它快、又不至于像 50ms 那样 + * 频繁 close/open 芯片 Socket 与消费者收发撞车——2026-10-12 台架实测 + * 50ms 会把 uiTask 最坏单轮从 172ms 推到 1034ms) */ +#define NET_LISTENER_PUMP_PERIOD_MS 100U + +/* + * 函数功能:监听器 pump(N-32 P2「单一所有者」)—— 本层**唯一**对监听型 + * Socket 调 net_relisten_try 的地方(应用显式 NET_MSG_RELISTEN 除外) + * 入口参数:无 + * 出口参数:无 + * 返回值:无 + * 限定条件:netTask 上下文(由 net_process_messages 每轮调用) + * 函数说明:1. 设计意图(设计文档 §4 不变量 II):其它所有位置只**置事实** + * (session_dead / 状态迁移 / 事件派发),不**做动作**——于是 + * "谁先看到 DISCONNECT 就谁重建"这类竞争在结构上消失 + * 2. 覆盖三种情形(前两种原属对账③④,第三种为 U11 缺口): + * ① 本层 LISTENING 而芯片监听已失位 → 重建(原对账③) + * ② 本层 CLOSED(会话已结束)而芯片不在 LISTEN → 重建(原对账④; + * 原 accept/recv/pending 三处散点只置事实,重建收敛到这里) + * ③ 本层 CLOSED 而芯片**正在** LISTEN → **只做状态重同步** + * (U11):芯片监听器是健康的,重下发 open+listen 会因 USE + * 错误走进关闭分支、把正听着的 Socket 关掉(U12 同源事故); + * 只把本层状态收回 LISTENING,**不发任何芯片命令** + * 3. 去抖/冷却沿用对账的实测结论(2026-09-23 实证:无去抖时监听被 + * 反复重建直至放弃) + * 4. session_dead **不在此清零**(与 net_relisten_locked 同一契约: + * 重建监听 ≠ 应用已确认会话结束;清零只发生在 handle_connect_event) + */ +static void net_listener_pump(void) { + /* 上次轮次时刻 */ + static uint32_t s_last = 0U; + /* 连续失配计数(去抖) */ + static uint8_t s_bad[NET_MAX_SOCKETS]; + /* 冷却到期时刻(防抖成风暴) */ + static uint32_t s_cool[NET_MAX_SOCKETS]; + /* 当前 tick */ + uint32_t now = HAL_GetTick(); + /* Socket 循环索引 */ + int i; + + /* 未到 pump 周期:直接返回 */ + if ((uint32_t)(now - s_last) < NET_LISTENER_PUMP_PERIOD_MS) { + return; + } + s_last = now; + + for (i = 0; i < NET_MAX_SOCKETS; i++) { + /* 取控制块 */ + net_sock_t *p_sock = &s_net_socks[i]; + /* 芯片侧状态两字节 */ + uint8_t st[2] = {0, 0}; + /* 芯片当前是否在 LISTEN */ + int chip_listening; + + /* 认领门槛:仅监听型 fd(在用 + 自动重监听 + TCP + 已绑端口)。 + * auto_relisten 是决定性门:少了它,"本层 CLOSED + 已绑端口"的出向 + * 连接(板级 Phase 7 TC701~703 用 net_connect 绑本地口)会被误判为 + * 监听器并被 relisten(同对账④的既定口径) */ + if ((p_sock->in_use == 0U) || + (p_sock->auto_relisten == 0U) || + (p_sock->type != NET_SOCK_STREAM) || + (p_sock->local_port == 0U)) { + s_bad[i] = 0U; + continue; + } + /* 只处理与监听生命周期相关的两个状态 */ + if ((p_sock->state != NET_SOCK_STATE_LISTENING) && + (p_sock->state != NET_SOCK_STATE_CLOSED)) { + s_bad[i] = 0U; + continue; + } + + /* 回读芯片状态(每监听型 fd 一条命令) */ + ch395f_get_socket_status((uint8_t)i, st); + chip_listening = ((st[0] != CH395F_SOCKET_CLOSED) && + (st[1] == CH395F_TCP_LISTEN)); + + /* 芯片在听 */ + if (chip_listening) { + s_bad[i] = 0U; + /* 本层 CLOSED 而芯片在听 = U11(本层状态滞后)→ 只重同步 */ + if (p_sock->state == NET_SOCK_STATE_CLOSED) { + DBG_INFO("sock%d pump: chip listening, resync CLOSED->LISTENING (U11)", i); + /* 只改本层状态,**不发任何芯片命令**(同 net_resync_listen 的 + * 既定纪律,理由见该函数注释)。session_dead 不清 */ + p_sock->state = NET_SOCK_STATE_LISTENING; + /* N-30(Trap 25):死连接对端端口转登幽灵甄别基准 + 去抖清零 + * —— 芯片监听器未被重开,芯片侧对端信息可能仍是上一条连接的, + * 必须转登为基准,否则对账② 的幽灵拦截会失效(同 net_resync_listen) */ + p_sock->last_peer_port = p_sock->remote_port; + p_sock->lost_conn_seen = 0U; + } + continue; + } + + /* 芯片不在听:情形①②同一处置——重建(本层唯一重建入口) */ + if (s_bad[i] < NET_RECONCILE_STABLE_N) { + s_bad[i]++; + } + /* 去抖未满或处于冷却期:本轮不动手 */ + if ((s_bad[i] < NET_RECONCILE_STABLE_N) || + ((int32_t)(now - s_cool[i]) < 0)) { + continue; + } + s_bad[i] = 0U; + s_cool[i] = now + NET_RECONCILE_COOLDOWN_MS; + DBG_INFO("sock%d pump: chip not listening (0x%02X/0x%02X, was state=%d) -> relisten", + i, st[0], st[1], (int)p_sock->state); + /* 非阻塞:失败由 D10 重试轮询接管 */ + (void)net_relisten_try(p_sock); + } +} + /* * 函数功能:netTask 主循环核心 —— 自愈 tick 宿主 + 消息队列分派器。每轮 * 先跑三个非阻塞自愈 tick(D10 重监听 / L3 挂起 recv / @@ -709,8 +795,11 @@ void net_process_messages(void) { net_relisten_retry_tick(); /* L3 挂起 recv 完成检查:数据到达 / 到点 / 断连时通知调用方 */ net_pending_tick(); - /* Trap 22 状态对账:周期回读芯片状态,自愈丢失的中断 */ + /* Trap 22 状态对账:周期回读芯片状态,自愈丢失的中断(①②;③④ 已归 pump) */ net_reconcile_tick(); + /* N-32 P2:监听器 pump —— 本层唯一重建入口(置于对账之后:对账①② + * 可能刚把会话置 CLOSED,pump 紧接着就能看到并重建) */ + net_listener_pump(); /* 队列排空:超时 0 非阻塞取,每条处理一轮,直到本轮队列为空 */ while (osMessageQueueGet(netMsgQueueHandle, &msg, NULL, 0) == osOK) { @@ -1344,8 +1433,11 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen DBG_INFO("sock%d closed-with-data accept (drain pending)", sockfd); } else { /* 连接已断开(CLOSED)且无残留数据:先把 Socket 重新置回 LISTEN, - * 下一次 accept 即可等待新连接(BSD 风格监听常驻) */ - /* 非阻塞:失败由 D10 重试轮询接管 */ + * 下一次 accept 即可等待新连接(BSD 风格监听常驻)。 + * N-32 P2 修正:**保留本处即时重建** —— 它由消费者调 accept 触发 + * (消费者正在等新连接),不是周期性抢先;删掉会把 CLOSED 窗口 + * 拉长、导致调用方高频空转 accept(实测 [WD] task stall)。 + * 非阻塞:失败由 D10 重试轮询接管 */ (void)net_relisten_try(p_listen_sock); } } @@ -1485,15 +1577,26 @@ int net_listener_armed(int sockfd) { if (p_sock == NULL) { return 0; } - /* 正常监听态 */ + /* 正常监听态:放行。net_lost_connect_probe(丢 CONNECT 补 accept)与常规 + * accept 都发生在 net_accept_locked 内部,门控挡在外面就永远到不了 */ if (p_sock->state == NET_SOCK_STATE_LISTENING) { return 1; } - /* 断连待重建态:监听器仍在服役(auto_relisten + 已绑端口), - * 芯片侧交给 relisten / 对账恢复;同 net_reconcile_tick ④ 的认领口径 */ + /* 断连待重建态:**仅当芯片 RX 可能仍有残留数据**时放行,让 + * net_accept_locked 的 "closed-but-readable" 接受(N-30,对端"发完即 FIN") + * 仍能拿到该连接。 + * ⚠ 无残留时必须返回 0 —— 否则调用方会在整个 CLOSED 窗口内每轮都发起 + * net_accept(RPC:入队 + xTaskNotifyWait 回执**无上界**)→ 队列积压 → + * 调用方任务喂不上狗。2026-10-12 P2a 台架实测:删掉即时 relisten 后 CLOSED + * 窗口被拉长到 pump 周期 + 隔离期(~100ms),:21 0ms×50 期间出现 48 条 + * `[WD] task stall`(mask=0x67/req=0x77,缺 bit4=UI)。 + * 重建本身由 net_listener_pump 负责,调用方**无需**在此催促。 + * readable 由 net_poll 在 RECV_OK 置位,"对端发完即 FIN"场景下 CLOSED 之后 + * 仍为 1(无人清除),可作"可能仍有残留"的纯 RAM 判据(不发起 SPI)。 */ if ((p_sock->state == NET_SOCK_STATE_CLOSED) && (p_sock->auto_relisten != 0U) && - (p_sock->local_port != 0U)) { + (p_sock->local_port != 0U) && + (p_sock->readable != 0U)) { return 1; } return 0; @@ -1938,7 +2041,12 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags, * `net_relisten_try` 无状态门,对已 LISTENING 的 fd 再 open+listen * 会让 ch395f_open_socket 返 USE,其失败分支会 ch395f_close_socket * —— 把正听着新 SYN 的 Socket 当场关掉(U12 同源事故)。 - * session_dead + 已回 LISTENING = 重建已由别处完成,这里只报 EOF。 */ + * session_dead + 已回 LISTENING = 重建已由别处完成,这里只报 EOF。 + * N-32 P2 修正(2026-10-12 台架实证):**保留本处即时重建**。 + * 它是"消费者动作触发"(消费者正在等这条连接的结局),不是周期性 + * "抢先";删掉它会把 CLOSED 窗口从 ~0 拉长到 pump 周期+隔离期 + * (~100ms),期间调用方高频发起 net_accept(RPC 无上界)→ + * `[WD] task stall`(实测 uis 往返 1063ms)。周期性抢先由 pump 收敛即可。 */ if ((p_sock->state == NET_SOCK_STATE_CLOSED) && (p_sock->auto_relisten != 0U)) { /* 非阻塞:失败由 D10 重试轮询接管 */ (void)net_relisten_try(p_sock); @@ -2517,7 +2625,10 @@ static void net_pending_tick(void) { /* 启用自动重监听:先把监听器重新置回 LISTEN。 * ⚠ 内层 `state == CLOSED` 门同 net_recv_locked:session_dead * 但已回 LISTENING 时重建已由别处完成,再 open+listen 会因 - * USE 错误走进关闭分支、关掉正听着的 Socket(U12 同源) */ + * USE 错误走进关闭分支、关掉正听着的 Socket(U12 同源)。 + * N-32 P2 修正:**保留本处即时重建** —— 它只在有**挂起 recv** + * 时到达(`p->active != 0`),属"消费者在等这条连接结局"的 + * 消费者驱动路径,不是周期性抢先(同 net_recv_locked 的理由)。 */ if ((p_sock->state == NET_SOCK_STATE_CLOSED) && (p_sock->auto_relisten != 0U)) { /* 非阻塞:失败由 D10 重试轮询接管 */ @@ -3724,18 +3835,19 @@ static void handle_disconnect_event(net_sock_t *p_sock) { if (p_sock->state == NET_SOCK_STATE_LISTENING) { /* Trap 22 / N-6:监听态收到 DISCONNECT——bit3=1(0x08)下芯片**不**关该 Socket(停 0x05/0x00,芯片 TCP 侧已 CLOSED);不重开则此后所有 SYN 全被拒 = 监听永久死亡; * 触发 = 断连后 <~1s 快速复连时 CONNECT 中断未上报(控制块滞留 LISTENING)后该连接断开(2026-09-23 生产固件复现); - * → 回读芯片状态:监听已失位即重建(D10 非阻塞重试),仍在监听判陈旧丢弃 */ + * N-32 P2:本分支**只回读留痕、不再 relisten** —— 重建统一交 + * net_listener_pump(每 50 ms 回读芯片状态,监听失位即重建)。原处直接 + * relisten 是 12 个所有者之一,且位于 DISCONNECT 事件路径,可能"抢先" + * 于消费者 recv(P1 已消除其后果,P2 消除其可能) */ /* 回读缓冲(芯片状态 2 字节) */ uint8_t st[2] = {0, 0}; - /* 回读芯片状态 */ + /* 回读芯片状态(仅为留痕:重建与否由 pump 裁决) */ ch395f_get_socket_status((uint8_t)sockfd, st); - /* 监听已失位(芯片已关闭):留痕并重建监听 */ + /* 监听已失位(芯片已关闭) */ if ((st[0] == CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_LISTEN)) { - DBG_INFO("sock%d listen lost (chip sock=0x%02X tcp=0x%02X), relisten now", + DBG_INFO("sock%d listen lost (chip sock=0x%02X tcp=0x%02X), pump will relisten", sockfd, st[0], st[1]); - /* 非阻塞:失败由 D10 重试轮询接管 */ - (void)net_relisten_try(p_sock); } else { /* 仍在监听:判陈旧事件丢弃 */ DBG_INFO("sock%d stale listen DISCONNECT ignored (chip tcp=0x%02X)", sockfd, st[1]); diff --git a/Drivers/BSP/NET/net_socket.h b/Drivers/BSP/NET/net_socket.h index df88d1b3..7e7360ae 100644 --- a/Drivers/BSP/NET/net_socket.h +++ b/Drivers/BSP/NET/net_socket.h @@ -183,13 +183,14 @@ int net_can_accept(int sockfd); * `net_lost_connect_probe`(丢 CONNECT 中断补 accept,N-30 实证的 * "数据被吞/425"主根因修复)对该端口等于从未运行 * 2. 放行 LISTENING:探针与 "closed-but-readable 接受" 都发生在 - * `net_accept_locked` **内部**,门控挡在外面就永远到不了;LISTENING 态 - * accept 要么被探针补中、要么返 WOULDBLOCK,两者均安全 - * 3. 放行 CLOSED + auto_relisten:单连接模型断连后 fd 停在 CLOSED 等 - * relisten(D10 非阻塞重试 / 对账④),此窗口内新 SYN 可能已被芯片 - * 接住 → 放行让 `net_accept_locked` 的状态门自行裁决 + * `net_accept_locked` **内部**,门控挡在外面就永远到不了 + * 3. 放行 CLOSED+auto_relisten **且 readable≠0**(有残留数据): + * 保住 N-30 的 "closed-but-readable" 接受;**无残留时必须返回 0**, + * 否则调用方会在整个 CLOSED 窗口内每轮发起 `net_accept`(RPC 无上界) + * → 队列积压 → 喂不上狗(2026-10-12 P2a 台架实测 48 条 task stall)。 + * 重建由 `net_listener_pump` 负责,调用方无需催促 * 4. 与 `net_can_accept` 的语义差异**有意为之**:本函数回答"这个监听器 - * 还在服役吗",而非"这一轮一定有连接" + * 这一轮**值不值得**发起一次 accept",而非"一定有连接" * 5. 跨任务读单字,同 `net_can_accept` 的最坏情形论证 */ int net_listener_armed(int sockfd); diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index b6e39fb1..b9d28990 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -36,10 +36,10 @@ | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| -| ER_IROM1 | 214,100 B (209.08 KB) | 1,048,576 B (1024 KB) | 20.4% | -| RW_IRAM1 | 94,768 B (92.55 KB) | 131,072 B (128 KB) | 72.3% | +| ER_IROM1 | 214,248 B (209.23 KB) | 1,048,576 B (1024 KB) | 20.4% | +| RW_IRAM1 | 94,808 B (92.59 KB) | 131,072 B (128 KB) | 72.3% | | RW_CCM | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% | -| **Flash (ROM 合计)** | **215,068 B (210.0 KB)** | 1,048,576 B (1024 KB) | **20.5%** | +| **Flash (ROM 合计)** | **215,228 B (210.2 KB)** | 1,048,576 B (1024 KB) | **20.5%** | 各区的容量与常量来源: