refactor(NET): 事件处理与周期对账抽出 net_events.c,net_socket.c 3720→3119

动机:net_socket.c 3720 行(注释 1878 / 代码 1518 / 54 函数),BSD 连接层
与"状态面收敛"两套职责混在一个文件里。16 个消费者只依赖 net_socket.h,
故搬移对应用层零波及;模拟器另有 net_socket_sim.c,新模块走 SIM_EXEMPT。

为什么是一个模块而不是两个:实测对账区真实调用事件处理器 2 处(形态② 补
accept 走 handle_connect_event),且 NET_RECON_STABLE_DEATH 两边共用——
拆成 net_events.c + net_reconcile.c 会造出跨模块调用,合起来则跨模块导出
为零。二者同属"芯片状态面与本层镜像的收敛"一个职责。

搬出:5 个 handle_*_event + fire_event + net_reconcile_tick +
net_lost_connect_probe + NET_RECONCILE_*/NET_RECON_STABLE_* 常量 +
net_bench_cmd_log(含其 2 个 static)。

搬移时 4 处 fd 取法替换(其余代码逐行未改,已用剥注释后的代码行多重集
证明差异恰好等于声明的改动集):
- p_sock - s_net_socks → p_sock->ch395_sock(net_socket() 里
  ch395_sock = sockfd,恒等;模拟器同构造)
- 对账循环 &s_net_socks[i] → net_sock_raw(i)

⚠ 随之收窄两处沉降登记的范围门 (qfd >= 0) && (qfd < MAX) → qfd < MAX:
ch395_sock 是 uint8_t,(int) 后恒非负,下界在类型上不可能越界;留着会被
cppcheck 判 knownConditionTrueFalse 拦提交。已在文件头写"勿加回"。

新增 net_sock_raw():对账必须看到 in_use==0 / state==CLOSED 的槽位
(形态④ 认领孤儿监听正依赖 CLOSED 态),而 net_get_sock() 对未在用槽位返
回 NULL 会漏掉这一类。声明放 net_socket.h 而非内部头 net_events.h——规范
S17 2.2 要求非 static 函数声明在配对头文件(同 net_set_errno 先例)。

验证:
- 代码行多重集差异 = 声明的改动集,无一处意外
- @build BUILD_OK 0 错 0 警;Code 212316→212296(−20 B,拆 TU 布局差),
  RO/RW/ZI 完全不变
- cppcheck / coding-standard / registration-check 全 PASS
- @test 硬件闭环 PASS:635ms 到 net init: OK,:8888/:2404/FTP 均建立,
  [ERR]/Fault/relisten/reconcile 命中 0 条
- 台账随 Code 变化同步(ram_baseline --write / check_mem_numbers --write)

遗留(与仍未提交的文档工作同文件,另提):BSD_Socket_API 使用指南的文件
职责表、net_listener.c/.h 文件头"见 net_socket.c"、自愈架构设计 §11 仍把
net_reconcile_tick 记在 net_socket.c——doc-links 只校验 § 编号存在性,
照不出"函数在哪个文件",需在文档提交中一并修正。
This commit is contained in:
2026-10-14 18:04:57 +08:00
parent 0edbfaf08b
commit 63cf73b4d0
9 changed files with 745 additions and 619 deletions
+663
View File
@@ -0,0 +1,663 @@
/*
* 模块名称:Network Events & Reconcile
* 模块功能:芯片边沿事件处理 + 100ms 周期对账(真机专属)—— 本层状态镜像与芯片状态面的
* **唯一收敛实现**:5 个 handle_*_event(net_poll 的分派目标)、
* net_reconcile_tick(形态①②③④⑤/U11 判据)、net_lost_connect_probe
* (accept 路径补 accept)。**只置事实与登记意图**:重建监听的**动作**在
* net_listener_tick(net_listener.c),本模块只调 net_listener_request。
* 适用平台:STM32F407ZGTx + CH395F 以太网芯片
* 作者:王建锋
* 创建日期:2026-10-14
* 修改记录:
* 2026-10-14 王建锋 从 net_socket.c 抽出(与 net_listener.c 同族模块化):事件处理与
* 周期对账同属"状态面收敛"一个职责,且对账直接调用事件处理器 2 处
* → 合为一个模块,跨模块导出为零。搬移时 4 处 fd 取法替换(其余
* 代码逐行未改):fd 反推 `p_sock - s_net_socks` → `p_sock->ch395_sock`
* (net_socket() 里 `ch395_sock = sockfd`,恒等,模拟器同构造);
* 对账循环 `&s_net_socks[i]` → `net_sock_raw(i)`。
* ⚠ 随之**收窄**两处沉降登记的范围门 `(qfd >= 0) && (qfd < MAX)` →
* `qfd < MAX`:`ch395_sock` 是 uint8_t,下界在类型上不可能越界,
* 留着会被 cppcheck 判 `knownConditionTrueFalse` 拦提交。**勿加回**。
*
* 设计依据 → docs/网络/网络自愈架构设计.md §4(形态表 §4.1 / 单一所有者 §4.2 / 沉降 §4.3)
* 依赖(单向,无反向 include):net_socket.h(net_sock_raw / net_set_errno / NET_ERR_*)
* net_events.h(本模块声明)/ net_listener.h(settle / request)/ net_pending.h(形态⑤
* 取消挂起)/ ch395f.h(驱动)/ dbg_log.h / main.h(HAL_GetTick)
*/
#include <string.h>
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
#include "cmsis_os.h"
#include "net_socket.h"
#include "net_events.h"
#include "net_listener.h"
#include "net_pending.h"
#include "ch395f.h"
#include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */
#include "net_latency_test.h"
#include "main.h"
/* 深调用链模块:与 net_socket.c 同口径收 128 B(最长行渲染后 ≈100 B < 128 → 不截断) */
#define DBG_LINE_BUF_MAX 128U
#define DBG_TAG "[NET]"
#include "dbg_log.h"
/* 模块内前向声明 */
static void fire_event(net_sock_t *p_sock, net_event_t event);
#if defined(NET_BENCH_CMD_LOG)
/*
* 函数功能:台架 Bench 芯片命令序列日志(每条真实 SPI 命令记一行,排障用)
* 入口参数:tag - 命令类别 (P=poll全局中断 R=recv S=send C=close
* A=accept L=listen N=connect U=relisten M=其他) char
* sock - CH395F socket 号 int
* detail - 结果/中断状态 int
* 出口参数:无
* 返回值:无
* 限定条件:NET_BENCH_CMD_LOG 启用(2026-09-22 排障台架工具,默认关);netTask 上下文
* 函数说明:格式 netcmd: <tag> fd=<sock> t=<FreeRTOS tick> d=<距上条命令 tick 间隔>
* x=<detail>;台架专用,实验后回退
*/
static uint32_t s_bench_prev_tick = 0U;
static uint8_t s_bench_prev_valid = 0U;
void net_bench_cmd_log(char tag, int sock, int detail) {
/* 当前 tick(命令时间戳) */
uint32_t now = (uint32_t)xTaskGetTickCount();
/* 距上一条命令的 tick 间隔(首条命令记 0) */
uint32_t delta = (s_bench_prev_valid != 0U) ? (now - s_bench_prev_tick) : 0U;
/* 更新"上一条命令"记录,供下次计算间隔 */
s_bench_prev_tick = now;
s_bench_prev_valid = 1U;
/* 台架日志:命令类别 + fd + tick + 间隔 + 结果/中断状态 */
DBG_INFO("netcmd: %c fd=%d t=%u d=%u x=%d", tag, sock, now, delta, detail);
}
#endif
/* 对账+pump 合并周期(自愈收敛阶段 3:原 200ms 对账 / 100ms pump 合一) */
#define NET_RECONCILE_PERIOD_MS 100U
/* 同一 socket 两次动手的最小间隔(防抖成风暴) */
#define NET_RECONCILE_COOLDOWN_MS 1000U
/* 分级去抖门槛(单位=轮) */
#define NET_RECON_STABLE_ACCEPT 1U /* 形态② CONNECT 事件未收到补 accept(幽灵甄别兜底假阳性) */
#define NET_RECON_STABLE_RELISTEN 2U /* 形态③④ 监听失位重建 / ⑤ 静默回收 RESYNC */
#define NET_RECON_STABLE_DEATH 4U /* 形态① 连接已失(M3 让位窗 400ms,勿改小) */
/*
* 函数功能:处理 CH395F CONNECT 中断事件(单连接到达 / Client 连接成功)
* 入口参数:p_sock - 发生连接事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 CONNECT 中断后调用;netTask 上下文
* 函数说明:1. 单连接:自身转 ESTABLISHED 并读取对端地址
* 2. TCP Client(CONNECTING):连接成功,读取对端地址并置 ESTABLISHED
* 3. 连接成功后使能 KeepAlive 并触发 NET_EVENT_CONNECTED 回调
*/
void handle_connect_event(net_sock_t *p_sock) {
#if defined(NET_BENCH_CMD_LOG)
/* Bench:记 'a'(accept)命令 */
net_bench_cmd_log('a', (int)p_sock->ch395_sock, 0);
#endif
/* 监听态:有连接到达 */
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
/* 单连接:收到 CONNECT 中断 = 自身有连接到达,转为 ESTABLISHED */
DBG_INFO("sock%d standalone accept", p_sock->ch395_sock);
/* 对端 IP/端口(6 字节:4B IP + 2B 端口,小端) */
uint8_t remote[6];
/* 回读对端 IP + 端口 */
ch395f_get_remot_ipp(p_sock->ch395_sock, remote);
/* 对端 IP 数组(前 4 字节,大端) */
memcpy(p_sock->remote_ip_arr, remote, 4);
/* 小端 IP 组装为 32 位值 */
p_sock->remote_ip = ((uint32_t)remote[0]) |
((uint32_t)remote[1] << 8) |
((uint32_t)remote[2] << 16) |
((uint32_t)remote[3] << 24);
/* 对端端口(小端 2 字节) */
p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8));
/* 状态 → ESTABLISHED(监听自身转数据通道) */
p_sock->state = NET_SOCK_STATE_ESTABLISHED;
/* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平。必须在置
* ESTABLISHED 之后立刻做:否则抢先 relisten 后到新连接的窗口内
* recv 仍会误得 EOF(把新客户端踢开) */
p_sock->session_dead = 0U;
/* F-NET-17 M3 前置:清可疑电平——探针(LISTENING 期)残留计数会带进新会话,
* 不补此清零则 M3 矛盾判据首轮即被旁路(T6 实测 grep:connect 原不清此字段) */
p_sock->lost_conn_seen = 0U;
/* 新开 Socket 发送缓冲按空闲计 */
p_sock->send_ready = 1;
/* 清除残留的 TIMEOUT 中断标志 */
/* 读即清:本 Socket 中断状态寄存器 */
(void)ch395f_get_sock_int_status(p_sock->ch395_sock);
/* §5.56:TCP 服务器须在 CONNECT 后开启 KeepAlive。2026-10-12 曾加后
* 撤回(当时仅证明"物理断链"已被 +2s TIM_OUT 快速覆盖,其余场景
* 无法注入、未证明);E11(2026-10-13,《网络自愈架构设计》)用"禁用 PC 网卡"
* 注入"链路在、对端静默"(NAT/黑洞)补上证据链:生产参数 60/5/3
* 实测 ≈30 s 判死 → TIM_OUT → QUARANTINE → reconciler 重建;对端存活时
* 探测被 ACK、空闲会话不误杀。消费链路现成,按手册位置转正。 */
ch395f_set_keepalive_enable(p_sock->ch395_sock, 1);
/* 触发连接建立事件回调 */
fire_event(p_sock, NET_EVENT_CONNECTED);
} else if (p_sock->state == NET_SOCK_STATE_CONNECTING) {
/* TCP Client 连接成功 */
/* 对端 IP/端口(6 字节:4B IP + 2B 端口,小端) */
uint8_t remote[6];
/* 回读对端 IP + 端口 */
ch395f_get_remot_ipp(p_sock->ch395_sock, remote);
/* 对端 IP 数组(前 4 字节) */
memcpy(p_sock->remote_ip_arr, remote, 4);
/* 小端 IP 组装为 32 位值 */
p_sock->remote_ip = ((uint32_t)remote[0]) |
((uint32_t)remote[1] << 8) |
((uint32_t)remote[2] << 16) |
((uint32_t)remote[3] << 24);
/* 对端端口(小端 2 字节) */
p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8));
/* 状态 → ESTABLISHED */
p_sock->state = NET_SOCK_STATE_ESTABLISHED;
/* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平(同 LISTENING
* 分支:必须在转 ESTABLISHED 后立刻做,防抢先 relisten 窗口误 EOF) */
p_sock->session_dead = 0U;
/* F-NET-17 M3 前置:清可疑电平(同 LISTENING 分支) */
p_sock->lost_conn_seen = 0U;
/* 使能 KeepAlive(客户端连接保活) */
ch395f_set_keepalive_enable(p_sock->ch395_sock, 1);
/* 触发连接建立事件回调 */
fire_event(p_sock, NET_EVENT_CONNECTED);
}
}
/*
* 函数功能:处理 Socket 断开(DISCONNECT)事件,更新 Socket 状态机
* 入口参数:p_sock - 发生断开事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 DISCONNECT 中断后调用;netTask 上下文
* 函数说明:1. ⚠ FUN_PARA bit3=1(本仓 net_init 置 0x08):DISCONNECT/TIM_OUT 后芯片
* **不**关 Socket、**不清**缓冲(停 0x05/0x00)——残留数据须由本层读出后,再由
* 本层 ch395f_close_socket 关闭;芯片不代劳(§5.52② / §5.23),不得跳过
* 显式 close(U12 成因之一)。之后需重新 OPEN + LISTEN
* 2. 单连接:置 CLOSED,交由 net_recv_locked 排空剩余数据后再关
* 3. 触发 NET_EVENT_DISCONNECTED 回调
*/
void handle_disconnect_event(net_sock_t *p_sock) {
#if defined(NET_BENCH_CMD_LOG)
/* Bench:记 'd'(disconnect)命令 */
net_bench_cmd_log('d', (int)p_sock->ch395_sock, 0);
#endif
/* 由控制块指针反推描述符 */
int sockfd = (int)p_sock->ch395_sock;
/* 监听态:DISCONNECT 落在监听器上 */
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
/* TRAP-NET-17 / N-6:监听态收到 DISCONNECT——bit3=1(0x08)下芯片**不**关该 Socket(停 0x05/0x00,芯片 TCP 侧已 CLOSED);不重开则此后所有 SYN 全被拒 = 监听永久死亡;
* 触发 = 断连后 <~1s 快速复连时 CONNECT 中断未上报(控制块滞留 LISTENING)后该连接断开(2026-09-23 生产固件复现);
* N-32 P2:本分支**只回读留痕、不再 relisten** —— 重建统一交
* net_reconcile_tick 形态③(每 100 ms 读驱动缓存,监听失位即重建)。原处直接
* relisten 是 12 个所有者之一,且位于 DISCONNECT 事件路径,可能"抢先"
* 于消费者 recv(P1 已消除其后果,P2 消除其可能)。
* ⚠ 本留痕回读保持**主动 SPI**(不用驱动缓存):事件路径上缓存必然滞后
* 于刚到达的中断(刷新在 net_poll 末尾、事件处理之后),仅周期对账可用缓存 */
/* 回读缓冲(芯片状态 2 字节) */
uint8_t st[2] = {0, 0};
/* 回读芯片状态(仅为留痕:重建与否由 reconciler 裁决) */
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), reconciler will relisten",
sockfd, st[0], st[1]);
} else {
/* 仍在监听:判陈旧事件丢弃 */
DBG_INFO("sock%d stale listen DISCONNECT ignored (chip tcp=0x%02X)", sockfd, st[1]);
}
} else if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
/* 陈旧 DISCONNECT 甄别(D20 修复,2026-09-22 台架实证):同一 fd 被"关闭 → 重新监听 → 再连接"循环复用,上一条连接的 FIN/ACK
* 可能在 fd 已复用为新连接**之后**才上报(net_close_locked 排空的中断里还没有它)→ 新连接被误判为断开(实测:FTP 同一会话第二个数据连接必失败 425);
* 处置:回读芯片状态,TCP 仍 ESTABLISHED 判陈旧丢弃(已读清不重复上报);真断开时芯片处于 CLOSE_WAIT(0x07)/CLOSED(0x00) 等状态照常处理;超时事件走 handle_timeout_event,不受影响 */
/* 芯片状态回读缓冲(2 字节) */
uint8_t st[2];
/* 回读芯片状态(Socket + TCP) */
ch395f_get_socket_status((uint8_t)sockfd, st);
/* 芯片仍 ESTABLISHED:陈旧事件 */
if (st[1] == CH395F_TCP_ESTABLISHED) {
/* 留痕:陈旧事件丢弃 */
DBG_ERROR("sock%d stale DISCONNECT ignored (chip TCP still ESTABLISHED)", sockfd);
/* 直接返回(中断已读清,不会重复上报) */
return;
}
/* F-NET-17 M3(自愈专项实验 2026-10-13):矛盾态可疑门——芯片 Socket 仍 OPEN(0x05)
* 而 TCP 槽 0x00 且本层 ESTABLISHED = 逻辑矛盾(活连接应 0x04):陈旧 DISCONNECT
* (拆链完成滞后锁存,TRAP-NET-12)与"真断开恰被旧拆链覆写槽"在 D20 上不可分辨
* (结构性失明,F-NET-15 Q1)。处置:不吃边沿,置可疑电平返回,交对账①
* (ESTABLISHED + 芯片 TCP==CLOSED)电平去抖再入本函数(自愈收敛阶段 3 合并
* 后=4×100ms,NET_RECON_STABLE_DEATH,让位租约 400ms 不变 + 冷却):
* flag==1 旁路本门 → 正常释放;连接真活则租约内
* 会话正常完成(风暴链掐断,E9a n30 0/50 实证)。
* 零额外 SPI 于常规路径(st[] 已读;recv_len 仅可疑分支多读一条)。
* ⚠ E9a/E9b 实测两轮收窄:①必须含 recv_bytes==0——已读数据的会话遇
* 0x05/0x00 多为真死(探针控制通道教训);②必须含芯片 RX>0——延迟保护的
* 本质是"在途投递不可毁"(closed-with-data 救回同源红线:RX 有数据=消费者
* 资产)。@0ms 快复连(连上即关、零数据,E9b :21 46/50 教训)RX 空 → 立即
* 释放,误杀也无数据损失,后到数据由救回路径接住。边界:滞后锁存杀
* "已读数据或 RX 空"会话的形态不在本门保护域(E8 窗口未出现该受害形态)。
* 零误伤:健康 FIN=0x07、静默回收=0x00/0x00 均不命中(E5 20/20 样本) */
if ((st[0] == CH395F_SOCKET_OPEN) && (st[1] == CH395F_TCP_CLOSED) &&
(p_sock->lost_conn_seen == 0U) && (p_sock->recv_bytes == 0U) &&
(ch395f_get_recv_len((uint8_t)sockfd) > 0U)) {
/* 置可疑电平(复用探针字段,零 RAM;connect/relisten 清零) */
p_sock->lost_conn_seen = 1U;
/* 留痕(INFO 级:这是预期自愈路径,非错误,勿污染 [ERR] 台架判据) */
DBG_INFO("sock%d suspect DISCONNECT on live conn (chip 0x%02X/0x%02X), defer to reconcile",
sockfd, st[0], st[1]);
/* 不释放、不登记沉降、不动 session_dead——保留 400ms 租约窗口 */
return;
}
/* 单连接:先不关 CH395F Socket,
* 留给 net_recv_locked 把剩余数据读出来再关,置 CLOSED 等待应用重新监听 */
DBG_INFO("sock%d DISCONNECTED, releasing (chip st=0x%02X/0x%02X)",
p_sock->ch395_sock, st[0], st[1]);
/* TRAP-NET-18 推广(2026-09-24 台架实证):正常 DISCONNECT 释放留下的芯片收尾状态
* 会在复用同一 fd 的下一条连接上以"伪 DISCONNECT"形式投递(实测 accept 后约 10~20ms)→ 该连接被提前释放,应用把已到的 2048B 当整份文件并回报 226;
* 仅**本连接收过数据**时登记沉降(实测触发源 = 收过数据的数据通道被关闭),纯连接即关/只发不收的连接免沉降,保住 TRAP-NET-17 的快速复连时序(:21 @0ms ×50 全通过) */
/* F-NET-15 M1(自愈专项实验 2026-10-12):沉降**无条件**登记。原 recv_bytes!=0 门的
* 受害者由事件时序定义(accept 后 ~20ms 被陈旧 DISCONNECT 杀 → recv_bytes==0 是
* 构造性必然),门对"残渣循环自持"形态失效(n30_repro 背靠背 9/10 失败,E1 实测);
* 门原保护目标(:21 @0ms 快复连)已被 N-32 单一重建入口延迟化(恢复窗实测 0.3~1s)架空,
* +100ms 沉降落在窗内不可见。沉降窗口同时让残渣 DISCONNECT 在 state==CLOSED
* (无害、仅留痕)时被消费,断开"杀下一条 → 新拆链 → 再杀"的繁殖链 */
{
/* fd 取自 uint8_t ch395_sock → 恒非负,只需上界门 */
int qfd = (int)p_sock->ch395_sock;
if (qfd < NET_MAX_SOCKETS) {
/* 登记优雅沉降(NET_RELISTEN_SETTLE_GRACEFUL) */
net_listener_settle(qfd, NET_LISTENER_SETTLE_GRACEFUL);
}
}
/* 正常释放(对端 FIN):断连时挂起项以 EOF(0) 结束 */
p_sock->released_abnormal = 0U;
/* N-32(P1 不变量 I):置"本会话已结束"电平。须在置 CLOSED **之前**,
* 使 session_dead 覆盖"CLOSED 等待 recv 排空"整个窗口——排空期间
* recv 的 DONTWAIT 快路径先返数据(recv_len>0 优先),数据尽后判据
* 落到本标志 → 仍得 EOF,不因排空慢而漏报 */
p_sock->session_dead = 1U;
/* N-30(TRAP-NET-20):登记本连接对端端口(幽灵态甄别基准,须在状态
* 迁移前记录——remote_port 此刻仍有效,relisten 清零) */
p_sock->last_peer_port = p_sock->remote_port;
/* 状态 → CLOSED */
p_sock->state = NET_SOCK_STATE_CLOSED;
/* 触发断连事件回调 */
/* 触发断连事件回调 */
fire_event(p_sock, NET_EVENT_DISCONNECTED);
/* 其他状态:未预期的断开 */
} else if (p_sock->state != NET_SOCK_STATE_CLOSED &&
p_sock->state != NET_SOCK_STATE_BOUND &&
p_sock->state != NET_SOCK_STATE_CREATED) {
/* 未预期的断开:记错误日志 */
DBG_ERROR("sock%d disconnect in state %d", p_sock->ch395_sock, p_sock->state);
/* 触发断连事件回调(统一) */
fire_event(p_sock, NET_EVENT_DISCONNECTED);
}
}
/*
* 函数功能:处理 Socket 数据接收(RECV_OK)事件
* 入口参数:p_sock - 发生接收事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 RECV_OK 中断后调用;netTask 上下文
* 函数说明:1. 仅触发 NET_EVENT_DATA_RECEIVED 回调通知上层
* 2. 不在此读取 recv_len:单一读取点归 net_recv_locked(省一次 SPI 查询)
*/
void handle_recv_event(net_sock_t *p_sock) {
/* RECV_OK 中断已触发,直接通知上层。不在此读 recv_len,
* 单一读取点在 net_recv_locked(省 SPI;原"部分读归零"顾虑已降级) */
/* 置可读标志,供 net_select 在无 SPI 访问下判读 */
p_sock->readable = 1;
/* 触发数据到达事件回调 */
fire_event(p_sock, NET_EVENT_DATA_RECEIVED);
}
/*
* 函数功能:处理 Socket 发送完成(SEND_OK)事件
* 入口参数:p_sock - 发生发送完成事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 SEND_OK 中断后调用;netTask 上下文
* 函数说明:触发 NET_EVENT_SEND_COMPLETE 回调通知上层
*/
void handle_send_event(net_sock_t *p_sock) {
/* 触发发送完成事件回调 */
fire_event(p_sock, NET_EVENT_SEND_COMPLETE);
}
/*
* 函数功能:处理 Socket 超时(SOCK_TIMEOUT)事件,更新 Socket 状态机
* 入口参数:p_sock - 发生超时事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 SOCK_TIMEOUT 中断后调用;netTask 上下文
* 函数说明:1. TCP Client(CONNECTING):连接超时,回到 CREATED 状态
* 2. 已建立连接:回到 CLOSED(单连接)
* 3. 监听态:异常超时,记录错误日志
* 4. 触发 NET_EVENT_TIMEOUT 回调
*/
void handle_timeout_event(net_sock_t *p_sock) {
#if defined(NET_BENCH_CMD_LOG)
/* Bench:记 'x'(timeout)命令 */
net_bench_cmd_log('x', (int)p_sock->ch395_sock, 0);
#endif
/* 连接中状态:连接超时 */
if (p_sock->state == NET_SOCK_STATE_CONNECTING) {
/* TCP Client 连接超时 */
DBG_ERROR("sock%d CONNECT TIMEOUT", p_sock->ch395_sock);
/* 状态 → CREATED(客户端槽位回可复用) */
p_sock->state = NET_SOCK_STATE_CREATED;
} else if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
/* 已建立连接超时 */
DBG_ERROR("sock%d TIMEOUT", p_sock->ch395_sock);
/* TRAP-NET-18:异常释放(TIMEOUT,例如客户端中止上传)→ 复监听前隔离,
* 等对端在途数据落尽并排空 RX 残渣(详见 net_relisten_locked 区块注释) */
{
/* fd 取自 uint8_t ch395_sock → 恒非负,只需上界门 */
int qfd = (int)p_sock->ch395_sock;
if (qfd < NET_MAX_SOCKETS) {
/* 登记异常释放隔离(重监听前等待在途数据落尽 + RX 排空) */
net_listener_settle(qfd, NET_LISTENER_SETTLE_QUARANTINE);
}
}
/* TRAP-NET-18 待办 #3:标记异常释放——挂起 recv 排空断连后以
* NET_ERR_CONNRESET 结束(而非 EOF),应用(lftpd STOR)回报 5xx
* 而非误报 226(见 net_pending_tick 断连分支) */
p_sock->released_abnormal = 1U;
/* N-30(TRAP-NET-20):异常释放同样登记对端端口(幽灵甄别基准) */
p_sock->last_peer_port = p_sock->remote_port;
/* N-32(P1 不变量 I):异常结束同样是"本会话已结束"电平(TIMEOUT/RST
* 已在下方 requarantine,本标志只表达"会话结束了"这一事实,与
* released_abnormal(结束得好不好看)正交) */
p_sock->session_dead = 1U;
/* 状态 → CLOSED */
p_sock->state = NET_SOCK_STATE_CLOSED;
} else if (p_sock->state == NET_SOCK_STATE_LISTENING) {
/* 监听超时(异常情况) */
DBG_ERROR("sock%d LISTEN TIMEOUT", p_sock->ch395_sock);
}
/* 触发超时事件回调(统一) */
fire_event(p_sock, NET_EVENT_TIMEOUT);
}
/*
* 函数功能:触发 Socket 事件回调(若已注册)
* 入口参数:p_sock - 触发事件的 Socket 控制块指针 net_sock_t *
* event - 事件类型 net_event_t
* 出口参数:无
* 返回值:无
* 限定条件:net_init() 已调用
* 函数说明:1. 仅当 p_sock->event_cb 非空时调用回调
* 2. 回调参数为 ch395_sock、event、event_arg
*/
static void fire_event(net_sock_t *p_sock, net_event_t event) {
/* 已注册回调 */
if (p_sock->event_cb != NULL) {
/* 调用回调(ch395_sock、event、用户参数) */
p_sock->event_cb(p_sock->ch395_sock, event, p_sock->event_arg);
}
}
/*
* 函数功能:监听等待期的芯片状态轮询探针——芯片已 ESTABLISHED 而 CONNECT 事件
* 未收到时补做 accept(复用 CONNECT 事件簿记路径)。
* 入口参数:p_sock - 处于 LISTENING 的监听控制块 net_sock_t *
* 出口参数:无(命中时 p_sock->state 经 handle_connect_event 转 ESTABLISHED)
* 返回值:无
* 限定条件:netTask 上下文(net_accept_locked 内调用,其开头已 net_poll(),
* 驱动缓存新鲜度有保证)
* 函数说明:1. 自愈收敛阶段 3(F-MS-9):判据改读**驱动状态缓存**(零 SPI;唯一
* 刷新点=net_poll),**探针去抖删除**——原去抖防的两类瞬态已
* 结构性消除:"刚 listen 尚未生效"显示 TCP=00 永不命中 ESTABLISHED
* 门(E2:LISTEN 可见 100% <=10ms 无长尾);拆链窗假 ESTABLISHED
* 由幽灵甄别拦截。NET_LOST_CONN_STABLE_N 随之下线
* 2. 幽灵态(对端端口 = last_peer_port)不动手,计数清零
*/
void net_lost_connect_probe(net_sock_t *p_sock) {
/* 对端 IP+端口回读缓冲(4B IP + 2B 端口小端) */
uint8_t remote[6] = {0, 0, 0, 0, 0, 0};
/* 驱动状态缓存(net_poll 每轮刷新;本探针入口前 accept 已 net_poll()) */
ch395f_sock_state_t c = ch395f_sock_state_cached(p_sock->ch395_sock);
/* 芯片尚未完成握手:可疑电平计数清零,本轮无动作 */
if (c.tcp_state != CH395F_TCP_ESTABLISHED) {
p_sock->lost_conn_seen = 0U;
return;
}
/* 已 ESTABLISHED:回读对端端口做幽灵甄别(按需 SPI,仅命中态发) */
ch395f_get_remot_ipp(p_sock->ch395_sock, remote);
/* 对端端口(小端 2 字节) */
uint16_t peer = (uint16_t)(remote[4] | (remote[5] << 8));
/* 幽灵甄别:仅"端口非零且 = 上一条连接端口"才判幽灵拦截;
* peer==0(芯片对端端口提交滞后,TRAP-NET-13 同类)不拦截——宁可补 accept
* 也不退回事件未收口的死等(2026-10-11 验证轮实测:peer==0 拦截把首连接
* 事件未收到的原有对账救援也挡死,LIST#1 卡满 20s,比修复前更糟) */
if ((peer != 0U) && (peer == p_sock->last_peer_port)) {
p_sock->lost_conn_seen = 0U;
return;
}
/* 自愈收敛阶段 3(F-MS-9):去抖已删(判据读缓存,两类瞬态结构性消除,
* 见函数头注释 1);lost_conn_seen 仅作 M3 可疑电平标志用,动手前清零 */
p_sock->lost_conn_seen = 0U;
/* 留痕:事件未收到自愈命中 */
DBG_INFO("sock%d lost-CONNECT self-accept (peer=%u)",
(int)p_sock->ch395_sock, peer);
/* 复用 CONNECT 事件的簿记路径(转 ESTABLISHED + 记对端 + 事件回调) */
handle_connect_event(p_sock);
}
/*
* 函数功能:芯片/本层 socket 状态周期对账(TRAP-NET-17 事件面未收到自愈,E14 复核:非芯片丢事件是被读清消费)+ 监听器 pump(N-32 P2)—— 自愈收敛阶段 3 合并为单一 100ms tick(docs/网络/网络自愈架构设计.md §4.1)
* 入口参数:无
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文(由 net_process_messages 每轮调用)
* 函数说明:1. 芯片快速复连会收不到 CONNECT/DISCONNECT 事件(读清消费竞态,E14 复核:非芯片丢事件),本层无法靠事件自愈
* 2. 判据读**驱动状态缓存**(唯一刷新点=net_poll 每轮全量刷新,E2 实测单次回读 18.1us)
* 3. 覆盖形态(原对账①②⑤ + 原 pump③④/U11):
* ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(只置事实)
* ② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(命中时
* 才回读对端端口做幽灵甄别——本 tick 唯一按需 SPI)
* ⑤(F-NET-2)本层 ESTABLISHED 而芯片静默回收回 LISTEN(0x05/0x01 无
* DISCONNECT)→ RESYNC 只修本层
* ③ 本层 LISTENING(监听型 fd)而芯片监听失位 → 重建
* ④ 本层 CLOSED(监听型 fd)而芯片不在 LISTEN → 重建
* U11 本层 CLOSED(监听型 fd)而芯片在听 → 只重同步,**不发芯片命令**
* (重下发因 USE 错误关掉正听着的 Socket,U12 同源事故)
* 4. 去抖分级(100ms/轮;E2:LISTEN 可见 100% <=10ms 无长尾):②=1 轮
* (幽灵甄别挡拆链窗的旧连接 ESTABLISHED);③④⑤=2 轮(2026-09-23 实证:
* 无去抖时监听被反复重建直至放弃);①=4 轮(**勿改小**:F-NET-17 M3
* 可疑电平让位再入,400ms 让位窗=在途投递不可毁的最后租约,E9a 实证)
* 5. 动手后冷却 NET_RECONCILE_COOLDOWN_MS 防风暴(U11 被动重同步不占)
* 6. 链路态订阅(F-DRV-13):驱动 link 变化计数变化 → 留痕(不自动重配)
*/
void net_reconcile_tick(void) {
/* 上次对账轮的时刻(tick) */
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;
/* 未到对账周期(100ms):直接返回(缓存刷新在 net_poll,与本函数无关) */
if ((uint32_t)(now - s_last) < NET_RECONCILE_PERIOD_MS) {
return;
}
/* 更新本轮起始时刻 */
s_last = now;
/* 链路态订阅(F-DRV-13):net_poll 已喂驱动,这里只看计数变化 */
{
/* 链路变化计数订阅基线(0xFFFF=未建档) */
static uint16_t s_link_last = 0xFFFFU;
uint16_t lc = ch395f_link_change_count();
if (s_link_last != lc) {
if (s_link_last != 0xFFFFU) {
DBG_INFO("net: PHY link %s (changes=%u)",
ch395f_link_up_cached() ? "up" : "down", (unsigned)lc);
}
s_link_last = lc;
}
}
for (i = 0; i < NET_MAX_SOCKETS; i++) {
/* 取控制块(直接数组下标,无需越界判断) */
net_sock_t *p_sock = net_sock_raw(i);
/* 驱动状态缓存(net_poll 每轮刷新,本 tick 读到的新鲜度 <=1ms) */
ch395f_sock_state_t c = ch395f_sock_state_cached((uint8_t)i);
/* 芯片当前是否在 LISTEN(二段关闭中间态 05/00 不算在听) */
int chip_listening = ((c.open != CH395F_SOCKET_CLOSED) &&
(c.tcp_state == CH395F_TCP_LISTEN));
/* 监听型 fd 门(原 pump 认领口径:auto_relisten 是决定性门——少了它,
* "本层 CLOSED + 已绑端口"的出向连接会被误判为监听器并被 relisten) */
int listener_fd = ((p_sock->auto_relisten != 0U) &&
(p_sock->type == NET_SOCK_STREAM) &&
(p_sock->local_port != 0U));
/* 失同步类型:1=连接已失 2=握手已成事件未收到 3=监听失位 4=孤儿监听
* 5=芯片静默回收回监听(编号沿用历史口径,U11 重同步不设编号——被动零动作) */
int bad = 0;
/* 本形态去抖门槛(默认按重建档) */
uint8_t stable_need = NET_RECON_STABLE_RELISTEN;
if (p_sock->in_use == 0U) {
s_bad[i] = 0U;
continue;
}
if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
/* ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理 */
if ((c.open == CH395F_SOCKET_CLOSED) || (c.tcp_state == CH395F_TCP_CLOSED)) {
bad = 1;
stable_need = NET_RECON_STABLE_DEATH;
} else if ((c.open != CH395F_SOCKET_CLOSED) &&
(c.tcp_state == CH395F_TCP_LISTEN)) {
/* ⑤(F-NET-2 缺口,自愈收敛阶段1):芯片静默回收——本层 ESTABLISHED
* 而芯片已自行把 Socket 收回 LISTEN(0x05/0x01,**不发 DISCONNECT
* 中断**,run8 台架实证)→ RESYNC 语义 */
bad = 5;
}
} else if (p_sock->state == NET_SOCK_STATE_LISTENING) {
if (c.tcp_state == CH395F_TCP_ESTABLISHED) {
/* ② 本层 LISTENING 而芯片已完成握手(CONNECT 事件未收到)→ 补做 accept
* N-30(TRAP-NET-20)幽灵态甄别:拆链收尾窗口内旧连接未拆净,状态如实
* 显示 ESTABLISHED(E15:bit3=1 保留现场契约 + TCP 拆链状态机,非谎报),
* 回读对端端口 = 上一条连接端口 → 不补 accept(补了会把数据发给
* 已死的旧连接 = 生产 LIST#4 数据被吞的直接成因),等拆净或新 SYN。
* 这是本 tick 唯一的按需 SPI(仅形态②命中时发) */
uint8_t remote[6] = {0, 0, 0, 0, 0, 0};
ch395f_get_remot_ipp((uint8_t)i, remote);
uint16_t peer = (uint16_t)(remote[4] | (remote[5] << 8));
/* 仅"端口非零且 = 上一条连接端口"判幽灵拦截;peer==0 不拦截
* (对端端口提交滞后时不得挡掉事件未收口的救援,见 net_lost_connect_probe 注释) */
if (!((peer != 0U) && (peer == p_sock->last_peer_port))) {
bad = 2;
stable_need = NET_RECON_STABLE_ACCEPT;
}
} else if (!chip_listening && listener_fd) {
/* ③ 本层 LISTENING 而芯片监听失位 → 重建(原 pump①) */
bad = 3;
}
} else if ((p_sock->state == NET_SOCK_STATE_CLOSED) && listener_fd) {
if (chip_listening) {
/* U11:本层 CLOSED 而芯片在听 = 本层状态滞后 → 只重同步。
* **不发任何芯片命令**(重下发 open+listen 会因 USE 错误走进
* 关闭分支、把正听着的 Socket 关掉,U12 同源事故)。
* 被动修正零风险:不去抖、不占冷却(原 pump 同口径) */
DBG_INFO("sock%d reconcile: chip listening, resync CLOSED->LISTENING (U11)", i);
p_sock->state = NET_SOCK_STATE_LISTENING;
/* N-30(TRAP-NET-20):死连接对端端口转登幽灵甄别基准 + 探针计数清零
* —— 芯片监听器未被重开,芯片侧对端信息可能仍是上一条连接的,
* 必须转登为基准,否则形态② 的幽灵拦截会失效 */
p_sock->last_peer_port = p_sock->remote_port;
p_sock->lost_conn_seen = 0U;
s_bad[i] = 0U;
continue;
}
/* ④ 本层 CLOSED(会话已结束)而芯片不在 LISTEN → 重建(原 pump②) */
bad = 4;
} else {
/* 其余状态(CREATED/BOUND/UDP_OPEN 等)不归本 tick 认领 */
s_bad[i] = 0U;
continue;
}
/* 无失同步:清零计数,转下一 socket */
if (bad == 0) {
s_bad[i] = 0U;
continue;
}
/* 累加连续失配计数(封顶至本形态门槛) */
if (s_bad[i] < stable_need) {
s_bad[i]++;
}
/* 去抖未满或处于冷却期:本轮不动手 */
if ((s_bad[i] < stable_need) ||
((int32_t)(now - s_cool[i]) < 0)) {
continue;
}
/* 动手前:清失配计数并置冷却到期时刻(1s) */
s_bad[i] = 0U;
s_cool[i] = now + NET_RECONCILE_COOLDOWN_MS;
if (bad == 1) {
/* 类型①:按断开处理(应用读到 EOF 后由形态③④重建监听) */
DBG_INFO("sock%d reconcile: chip closed (0x%02X/0x%02X) -> disconnect",
i, c.open, c.tcp_state);
/* 复用断开状态机(含陈旧事件甄别 + 状态迁移 + 事件派发);
* 只置事实,重建由本 tick 形态③④统一做(N-32 P2 单一所有者) */
handle_disconnect_event(p_sock);
} else if (bad == 5) {
/* 类型⑤:静默回收 → RESYNC 语义(原 NET_MSG_RESYNC_LISTEN 分派体
* 原样并入,自愈收敛阶段1;纪律同 U11:**不发任何芯片命令**——
* 重下发 close/重监听会摧毁芯片侧健康监听器 → 端口永拒,
* run8 实证 170s 拒绝) */
DBG_INFO("sock%d reconcile: chip re-listened w/o DISCONNECT (0x%02X/0x%02X) -> resync",
i, c.open, c.tcp_state);
/* 仅修 MCU 侧;新连接的 CONNECT 中断会把两侧重新同步 */
p_sock->state = NET_SOCK_STATE_LISTENING;
/* N-30(TRAP-NET-20):死连接对端端口转登幽灵甄别基准 + 探针计数清零 */
p_sock->last_peer_port = p_sock->remote_port;
p_sock->lost_conn_seen = 0U;
/* 取消本 fd 在死连接上的挂起 recv(必通知,防调用方挂死) */
net_pending_cancel(i, NET_ERR_CONNRESET);
/* 挂起 send 同样取消(会话已被芯片收走,剩余字节不可能送达) */
net_send_pending_cancel(i, NET_ERR_CONNRESET);
} else if ((bad == 3) || (bad == 4)) {
/* 类型③④:监听失位/孤儿监听 → **登记**重建请求(判据在此,动作在
* net_listener_tick 一处;非阻塞,30ms 节奏重试) */
DBG_INFO("sock%d reconcile: chip not listening (0x%02X/0x%02X, was state=%d) -> relisten",
i, c.open, c.tcp_state, (int)p_sock->state);
net_listener_request(i, NET_LISTENER_REQ_RECON);
} else {
/* 类型②:补做 standalone accept */
DBG_INFO("sock%d reconcile: connection completed w/o CONNECT irq -> accept", i);
/* 复用 CONNECT 事件的簿记路径 */
handle_connect_event(p_sock);
}
}
}
+46
View File
@@ -0,0 +1,46 @@
/*
* 模块名称:Network Events & Reconcile —— 内部头
* 模块功能:声明「芯片边沿事件处理 + 100ms 周期对账」模块对 net 层宿主提供的接口。
* 适用平台:STM32F407ZGTx + CH395F 以太网芯片
* 作者:王建锋
* 创建日期:2026-10-14
*
* ⚠ **内部头**:只被 `net_socket.c`(宿主)与 `net_events.c`(实现)include,
* **不进** 公开的 `net_socket.h`,也**不被应用层引用**——与 `net_listener.h` 同族约定。
* 本模块依赖 `ch395f_*` SPI 驱动,属**真机专属**:模拟器(Winsock 后端)不编译,
* 故已在 `scripts/check_registration.py` 的 SIM_EXEMPT 显式点名(每行附理由)。
*
* 设计依据 → docs/网络/网络自愈架构设计.md §4
* 形态表 §4.1 / 单一重建所有者 §4.2 / 沉降与隔离 §4.3 / 读即清信源纪律 §4.10
*/
#ifndef NET_EVENTS_H
#define NET_EVENTS_H
#include "net_types.h" /* net_sock_t / net_event_t / NET_MAX_SOCKETS */
/* ===== 宿主 net_socket.c 提供 ===== */
/* `net_sock_raw()`(对账循环需要的"不判 in_use"数组访问器)声明在**配对头** net_socket.h
* —— 规范 S17 2.2 要求非 static 函数声明在配对头文件,内部头不算(同 net_set_errno 先例)。 */
/* ===== 本模块 net_events.c 提供 ===== */
/* 芯片边沿事件处理器:由 net_poll() 在全局中断位命中后分派(netTask 上下文) */
void handle_connect_event(net_sock_t *p_sock); /* CONNECT:转 ESTABLISHED + 记对端 + KeepAlive */
void handle_disconnect_event(net_sock_t *p_sock); /* DISCONNECT:陈旧甄别 + 登记沉降事实 */
void handle_recv_event(net_sock_t *p_sock); /* RECV_OK:置 readable 电平 */
void handle_send_event(net_sock_t *p_sock); /* SEND_OK:清发送忙 + 回调 */
void handle_timeout_event(net_sock_t *p_sock); /* TIM_OUT:按异常释放处理(隔离重建) */
/* 100ms 周期对账:形态①②③④⑤ + U11 的唯一判据所在;由 net_process_messages 每轮调用 */
void net_reconcile_tick(void);
/* accept 路径探针:芯片已 ESTABLISHED 而 CONNECT 事件未到时补做 accept */
void net_lost_connect_probe(net_sock_t *p_sock);
#if defined(NET_BENCH_CMD_LOG)
/* 台架芯片命令序列日志(编译开关默认关;net_poll / 消息分派 / 事件处理三处消费) */
void net_bench_cmd_log(char tag, int sock, int detail);
#endif
#endif /* NET_EVENTS_H */
+14 -615
View File
@@ -19,6 +19,7 @@
#include "net_socket.h" #include "net_socket.h"
#include "net_listener.h" #include "net_listener.h"
#include "net_events.h" /* 事件处理 + 周期对账(真机专属,同目录内部头) */
#include "net_pending.h" #include "net_pending.h"
#include "ch395f.h" #include "ch395f.h"
#include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */ #include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */
@@ -170,18 +171,11 @@ int net_set_errno(int err) {
} }
/* /*
* 私有函数声明区 * 私有函数声明区
*/ */
static int alloc_socket(void); static int alloc_socket(void);
static void handle_connect_event(net_sock_t *p_sock);
static void handle_disconnect_event(net_sock_t *p_sock);
static void handle_recv_event(net_sock_t *p_sock);
static void handle_send_event(net_sock_t *p_sock);
static void handle_timeout_event(net_sock_t *p_sock);
static void fire_event(net_sock_t *p_sock, net_event_t event);
static void parse_ip_string(const char *str, uint8_t *ip_arr); static void parse_ip_string(const char *str, uint8_t *ip_arr);
static int net_send_locked(net_sock_t *p_sock, const void *buf, int len, static int net_send_locked(net_sock_t *p_sock, const void *buf, int len,
TaskHandle_t caller); TaskHandle_t caller);
@@ -382,34 +376,6 @@ void net_generation_inc(void) {
s_net_generation++; s_net_generation++;
} }
#if defined(NET_BENCH_CMD_LOG)
/*
* 函数功能:台架 Bench 芯片命令序列日志(每条真实 SPI 命令记一行,排障用)
* 入口参数:tag - 命令类别 (P=poll全局中断 R=recv S=send C=close
* A=accept L=listen N=connect U=relisten M=其他) char
* sock - CH395F socket 号 int
* detail - 结果/中断状态 int
* 出口参数:无
* 返回值:无
* 限定条件:NET_BENCH_CMD_LOG 启用(2026-09-22 排障台架工具,默认关);netTask 上下文
* 函数说明:格式 netcmd: <tag> fd=<sock> t=<FreeRTOS tick> d=<距上条命令 tick 间隔>
* x=<detail>;台架专用,实验后回退
*/
static uint32_t s_bench_prev_tick = 0U;
static uint8_t s_bench_prev_valid = 0U;
static void net_bench_cmd_log(char tag, int sock, int detail) {
/* 当前 tick(命令时间戳) */
uint32_t now = (uint32_t)xTaskGetTickCount();
/* 距上一条命令的 tick 间隔(首条命令记 0) */
uint32_t delta = (s_bench_prev_valid != 0U) ? (now - s_bench_prev_tick) : 0U;
/* 更新"上一条命令"记录,供下次计算间隔 */
s_bench_prev_tick = now;
s_bench_prev_valid = 1U;
/* 台架日志:命令类别 + fd + tick + 间隔 + 结果/中断状态 */
DBG_INFO("netcmd: %c fd=%d t=%u d=%u x=%d", tag, sock, now, delta, detail);
}
#endif
/* /*
* 函数功能:轮询 CH395F 中断状态,更新 Socket 连接状态并处理事件 * 函数功能:轮询 CH395F 中断状态,更新 Socket 连接状态并处理事件
@@ -590,210 +556,6 @@ int net_poll(void) {
return event_count; return event_count;
} }
/* 对账+pump 合并周期(自愈收敛阶段 3:原 200ms 对账 / 100ms pump 合一) */
#define NET_RECONCILE_PERIOD_MS 100U
/* 同一 socket 两次动手的最小间隔(防抖成风暴) */
#define NET_RECONCILE_COOLDOWN_MS 1000U
/* 分级去抖门槛(单位=轮) */
#define NET_RECON_STABLE_ACCEPT 1U /* 形态② CONNECT 事件未收到补 accept(幽灵甄别兜底假阳性) */
#define NET_RECON_STABLE_RELISTEN 2U /* 形态③④ 监听失位重建 / ⑤ 静默回收 RESYNC */
#define NET_RECON_STABLE_DEATH 4U /* 形态① 连接已失(M3 让位窗 400ms,勿改小) */
/*
* 函数功能:芯片/本层 socket 状态周期对账(TRAP-NET-17 事件面未收到自愈,E14 复核:非芯片丢事件是被读清消费)+ 监听器 pump(N-32 P2)—— 自愈收敛阶段 3 合并为单一 100ms tick(docs/网络/网络自愈架构设计.md §4.1)
* 入口参数:无
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文(由 net_process_messages 每轮调用)
* 函数说明:1. 芯片快速复连会收不到 CONNECT/DISCONNECT 事件(读清消费竞态,E14 复核:非芯片丢事件),本层无法靠事件自愈
* 2. 判据读**驱动状态缓存**(唯一刷新点=net_poll 每轮全量刷新,E2 实测单次回读 18.1us)
* 3. 覆盖形态(原对账①②⑤ + 原 pump③④/U11):
* ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(只置事实)
* ② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(命中时
* 才回读对端端口做幽灵甄别——本 tick 唯一按需 SPI)
* ⑤(F-NET-2)本层 ESTABLISHED 而芯片静默回收回 LISTEN(0x05/0x01 无
* DISCONNECT)→ RESYNC 只修本层
* ③ 本层 LISTENING(监听型 fd)而芯片监听失位 → 重建
* ④ 本层 CLOSED(监听型 fd)而芯片不在 LISTEN → 重建
* U11 本层 CLOSED(监听型 fd)而芯片在听 → 只重同步,**不发芯片命令**
* (重下发因 USE 错误关掉正听着的 Socket,U12 同源事故)
* 4. 去抖分级(100ms/轮;E2:LISTEN 可见 100% <=10ms 无长尾):②=1 轮
* (幽灵甄别挡拆链窗的旧连接 ESTABLISHED);③④⑤=2 轮(2026-09-23 实证:
* 无去抖时监听被反复重建直至放弃);①=4 轮(**勿改小**:F-NET-17 M3
* 可疑电平让位再入,400ms 让位窗=在途投递不可毁的最后租约,E9a 实证)
* 5. 动手后冷却 NET_RECONCILE_COOLDOWN_MS 防风暴(U11 被动重同步不占)
* 6. 链路态订阅(F-DRV-13):驱动 link 变化计数变化 → 留痕(不自动重配)
*/
static void net_reconcile_tick(void) {
/* 上次对账轮的时刻(tick) */
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;
/* 未到对账周期(100ms):直接返回(缓存刷新在 net_poll,与本函数无关) */
if ((uint32_t)(now - s_last) < NET_RECONCILE_PERIOD_MS) {
return;
}
/* 更新本轮起始时刻 */
s_last = now;
/* 链路态订阅(F-DRV-13):net_poll 已喂驱动,这里只看计数变化 */
{
/* 链路变化计数订阅基线(0xFFFF=未建档) */
static uint16_t s_link_last = 0xFFFFU;
uint16_t lc = ch395f_link_change_count();
if (s_link_last != lc) {
if (s_link_last != 0xFFFFU) {
DBG_INFO("net: PHY link %s (changes=%u)",
ch395f_link_up_cached() ? "up" : "down", (unsigned)lc);
}
s_link_last = lc;
}
}
for (i = 0; i < NET_MAX_SOCKETS; i++) {
/* 取控制块(直接数组下标,无需越界判断) */
net_sock_t *p_sock = &s_net_socks[i];
/* 驱动状态缓存(net_poll 每轮刷新,本 tick 读到的新鲜度 <=1ms) */
ch395f_sock_state_t c = ch395f_sock_state_cached((uint8_t)i);
/* 芯片当前是否在 LISTEN(二段关闭中间态 05/00 不算在听) */
int chip_listening = ((c.open != CH395F_SOCKET_CLOSED) &&
(c.tcp_state == CH395F_TCP_LISTEN));
/* 监听型 fd 门(原 pump 认领口径:auto_relisten 是决定性门——少了它,
* "本层 CLOSED + 已绑端口"的出向连接会被误判为监听器并被 relisten) */
int listener_fd = ((p_sock->auto_relisten != 0U) &&
(p_sock->type == NET_SOCK_STREAM) &&
(p_sock->local_port != 0U));
/* 失同步类型:1=连接已失 2=握手已成事件未收到 3=监听失位 4=孤儿监听
* 5=芯片静默回收回监听(编号沿用历史口径,U11 重同步不设编号——被动零动作) */
int bad = 0;
/* 本形态去抖门槛(默认按重建档) */
uint8_t stable_need = NET_RECON_STABLE_RELISTEN;
if (p_sock->in_use == 0U) {
s_bad[i] = 0U;
continue;
}
if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
/* ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理 */
if ((c.open == CH395F_SOCKET_CLOSED) || (c.tcp_state == CH395F_TCP_CLOSED)) {
bad = 1;
stable_need = NET_RECON_STABLE_DEATH;
} else if ((c.open != CH395F_SOCKET_CLOSED) &&
(c.tcp_state == CH395F_TCP_LISTEN)) {
/* ⑤(F-NET-2 缺口,自愈收敛阶段1):芯片静默回收——本层 ESTABLISHED
* 而芯片已自行把 Socket 收回 LISTEN(0x05/0x01,**不发 DISCONNECT
* 中断**,run8 台架实证)→ RESYNC 语义 */
bad = 5;
}
} else if (p_sock->state == NET_SOCK_STATE_LISTENING) {
if (c.tcp_state == CH395F_TCP_ESTABLISHED) {
/* ② 本层 LISTENING 而芯片已完成握手(CONNECT 事件未收到)→ 补做 accept
* N-30(TRAP-NET-20)幽灵态甄别:拆链收尾窗口内旧连接未拆净,状态如实
* 显示 ESTABLISHED(E15:bit3=1 保留现场契约 + TCP 拆链状态机,非谎报),
* 回读对端端口 = 上一条连接端口 → 不补 accept(补了会把数据发给
* 已死的旧连接 = 生产 LIST#4 数据被吞的直接成因),等拆净或新 SYN。
* 这是本 tick 唯一的按需 SPI(仅形态②命中时发) */
uint8_t remote[6] = {0, 0, 0, 0, 0, 0};
ch395f_get_remot_ipp((uint8_t)i, remote);
uint16_t peer = (uint16_t)(remote[4] | (remote[5] << 8));
/* 仅"端口非零且 = 上一条连接端口"判幽灵拦截;peer==0 不拦截
* (对端端口提交滞后时不得挡掉事件未收口的救援,见 net_lost_connect_probe 注释) */
if (!((peer != 0U) && (peer == p_sock->last_peer_port))) {
bad = 2;
stable_need = NET_RECON_STABLE_ACCEPT;
}
} else if (!chip_listening && listener_fd) {
/* ③ 本层 LISTENING 而芯片监听失位 → 重建(原 pump①) */
bad = 3;
}
} else if ((p_sock->state == NET_SOCK_STATE_CLOSED) && listener_fd) {
if (chip_listening) {
/* U11:本层 CLOSED 而芯片在听 = 本层状态滞后 → 只重同步。
* **不发任何芯片命令**(重下发 open+listen 会因 USE 错误走进
* 关闭分支、把正听着的 Socket 关掉,U12 同源事故)。
* 被动修正零风险:不去抖、不占冷却(原 pump 同口径) */
DBG_INFO("sock%d reconcile: chip listening, resync CLOSED->LISTENING (U11)", i);
p_sock->state = NET_SOCK_STATE_LISTENING;
/* N-30(TRAP-NET-20):死连接对端端口转登幽灵甄别基准 + 探针计数清零
* —— 芯片监听器未被重开,芯片侧对端信息可能仍是上一条连接的,
* 必须转登为基准,否则形态② 的幽灵拦截会失效 */
p_sock->last_peer_port = p_sock->remote_port;
p_sock->lost_conn_seen = 0U;
s_bad[i] = 0U;
continue;
}
/* ④ 本层 CLOSED(会话已结束)而芯片不在 LISTEN → 重建(原 pump②) */
bad = 4;
} else {
/* 其余状态(CREATED/BOUND/UDP_OPEN 等)不归本 tick 认领 */
s_bad[i] = 0U;
continue;
}
/* 无失同步:清零计数,转下一 socket */
if (bad == 0) {
s_bad[i] = 0U;
continue;
}
/* 累加连续失配计数(封顶至本形态门槛) */
if (s_bad[i] < stable_need) {
s_bad[i]++;
}
/* 去抖未满或处于冷却期:本轮不动手 */
if ((s_bad[i] < stable_need) ||
((int32_t)(now - s_cool[i]) < 0)) {
continue;
}
/* 动手前:清失配计数并置冷却到期时刻(1s) */
s_bad[i] = 0U;
s_cool[i] = now + NET_RECONCILE_COOLDOWN_MS;
if (bad == 1) {
/* 类型①:按断开处理(应用读到 EOF 后由形态③④重建监听) */
DBG_INFO("sock%d reconcile: chip closed (0x%02X/0x%02X) -> disconnect",
i, c.open, c.tcp_state);
/* 复用断开状态机(含陈旧事件甄别 + 状态迁移 + 事件派发);
* 只置事实,重建由本 tick 形态③④统一做(N-32 P2 单一所有者) */
handle_disconnect_event(p_sock);
} else if (bad == 5) {
/* 类型⑤:静默回收 → RESYNC 语义(原 NET_MSG_RESYNC_LISTEN 分派体
* 原样并入,自愈收敛阶段1;纪律同 U11:**不发任何芯片命令**——
* 重下发 close/重监听会摧毁芯片侧健康监听器 → 端口永拒,
* run8 实证 170s 拒绝) */
DBG_INFO("sock%d reconcile: chip re-listened w/o DISCONNECT (0x%02X/0x%02X) -> resync",
i, c.open, c.tcp_state);
/* 仅修 MCU 侧;新连接的 CONNECT 中断会把两侧重新同步 */
p_sock->state = NET_SOCK_STATE_LISTENING;
/* N-30(TRAP-NET-20):死连接对端端口转登幽灵甄别基准 + 探针计数清零 */
p_sock->last_peer_port = p_sock->remote_port;
p_sock->lost_conn_seen = 0U;
/* 取消本 fd 在死连接上的挂起 recv(必通知,防调用方挂死) */
net_pending_cancel(i, NET_ERR_CONNRESET);
/* 挂起 send 同样取消(会话已被芯片收走,剩余字节不可能送达) */
net_send_pending_cancel(i, NET_ERR_CONNRESET);
} else if ((bad == 3) || (bad == 4)) {
/* 类型③④:监听失位/孤儿监听 → **登记**重建请求(判据在此,动作在
* net_listener_tick 一处;非阻塞,30ms 节奏重试) */
DBG_INFO("sock%d reconcile: chip not listening (0x%02X/0x%02X, was state=%d) -> relisten",
i, c.open, c.tcp_state, (int)p_sock->state);
net_listener_request(i, NET_LISTENER_REQ_RECON);
} else {
/* 类型②:补做 standalone accept */
DBG_INFO("sock%d reconcile: connection completed w/o CONNECT irq -> accept", i);
/* 复用 CONNECT 事件的簿记路径 */
handle_connect_event(p_sock);
}
}
}
/* /*
* 函数功能:netTask 主循环核心 —— 自愈 tick 宿主 + 消息队列分派器。每轮先跑四个非阻塞 * 函数功能:netTask 主循环核心 —— 自愈 tick 宿主 + 消息队列分派器。每轮先跑四个非阻塞
@@ -1415,53 +1177,6 @@ int net_listen(int sockfd) {
* 对端端口 = 上一条连接端口;新连接临时端口几乎必然不同 → 端口相同不 accept, * 对端端口 = 上一条连接端口;新连接临时端口几乎必然不同 → 端口相同不 accept,
* 等旧连接拆净 / 新 SYN 到达(新连接若事件也未收到,后续轮询以新端口命中)。 */ * 等旧连接拆净 / 新 SYN 到达(新连接若事件也未收到,后续轮询以新端口命中)。 */
/*
* 函数功能:监听等待期的芯片状态轮询探针——芯片已 ESTABLISHED 而 CONNECT 事件
* 未收到时补做 accept(复用 CONNECT 事件簿记路径)。
* 入口参数:p_sock - 处于 LISTENING 的监听控制块 net_sock_t *
* 出口参数:无(命中时 p_sock->state 经 handle_connect_event 转 ESTABLISHED)
* 返回值:无
* 限定条件:netTask 上下文(net_accept_locked 内调用,其开头已 net_poll(),
* 驱动缓存新鲜度有保证)
* 函数说明:1. 自愈收敛阶段 3(F-MS-9):判据改读**驱动状态缓存**(零 SPI;唯一
* 刷新点=net_poll),**探针去抖删除**——原去抖防的两类瞬态已
* 结构性消除:"刚 listen 尚未生效"显示 TCP=00 永不命中 ESTABLISHED
* 门(E2:LISTEN 可见 100% <=10ms 无长尾);拆链窗假 ESTABLISHED
* 由幽灵甄别拦截。NET_LOST_CONN_STABLE_N 随之下线
* 2. 幽灵态(对端端口 = last_peer_port)不动手,计数清零
*/
static void net_lost_connect_probe(net_sock_t *p_sock) {
/* 对端 IP+端口回读缓冲(4B IP + 2B 端口小端) */
uint8_t remote[6] = {0, 0, 0, 0, 0, 0};
/* 驱动状态缓存(net_poll 每轮刷新;本探针入口前 accept 已 net_poll()) */
ch395f_sock_state_t c = ch395f_sock_state_cached(p_sock->ch395_sock);
/* 芯片尚未完成握手:可疑电平计数清零,本轮无动作 */
if (c.tcp_state != CH395F_TCP_ESTABLISHED) {
p_sock->lost_conn_seen = 0U;
return;
}
/* 已 ESTABLISHED:回读对端端口做幽灵甄别(按需 SPI,仅命中态发) */
ch395f_get_remot_ipp(p_sock->ch395_sock, remote);
/* 对端端口(小端 2 字节) */
uint16_t peer = (uint16_t)(remote[4] | (remote[5] << 8));
/* 幽灵甄别:仅"端口非零且 = 上一条连接端口"才判幽灵拦截;
* peer==0(芯片对端端口提交滞后,TRAP-NET-13 同类)不拦截——宁可补 accept
* 也不退回事件未收口的死等(2026-10-11 验证轮实测:peer==0 拦截把首连接
* 事件未收到的原有对账救援也挡死,LIST#1 卡满 20s,比修复前更糟) */
if ((peer != 0U) && (peer == p_sock->last_peer_port)) {
p_sock->lost_conn_seen = 0U;
return;
}
/* 自愈收敛阶段 3(F-MS-9):去抖已删(判据读缓存,两类瞬态结构性消除,
* 见函数头注释 1);lost_conn_seen 仅作 M3 可疑电平标志用,动手前清零 */
p_sock->lost_conn_seen = 0U;
/* 留痕:事件未收到自愈命中 */
DBG_INFO("sock%d lost-CONNECT self-accept (peer=%u)",
(int)p_sock->ch395_sock, peer);
/* 复用 CONNECT 事件的簿记路径(转 ESTABLISHED + 记对端 + 事件回调) */
handle_connect_event(p_sock);
}
/* /*
* 函数功能:TCP Server 接受新连接(内部版本,必须在 netTask 上下文调用) * 函数功能:TCP Server 接受新连接(内部版本,必须在 netTask 上下文调用)
@@ -3229,335 +2944,6 @@ static int alloc_socket(void) {
return -1; return -1;
} }
/*
* 函数功能:处理 CH395F CONNECT 中断事件(单连接到达 / Client 连接成功)
* 入口参数:p_sock - 发生连接事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 CONNECT 中断后调用;netTask 上下文
* 函数说明:1. 单连接:自身转 ESTABLISHED 并读取对端地址
* 2. TCP Client(CONNECTING):连接成功,读取对端地址并置 ESTABLISHED
* 3. 连接成功后使能 KeepAlive 并触发 NET_EVENT_CONNECTED 回调
*/
static void handle_connect_event(net_sock_t *p_sock) {
#if defined(NET_BENCH_CMD_LOG)
/* Bench:记 'a'(accept)命令 */
net_bench_cmd_log('a', (int)p_sock->ch395_sock, 0);
#endif
/* 监听态:有连接到达 */
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
/* 单连接:收到 CONNECT 中断 = 自身有连接到达,转为 ESTABLISHED */
DBG_INFO("sock%d standalone accept", p_sock->ch395_sock);
/* 对端 IP/端口(6 字节:4B IP + 2B 端口,小端) */
uint8_t remote[6];
/* 回读对端 IP + 端口 */
ch395f_get_remot_ipp(p_sock->ch395_sock, remote);
/* 对端 IP 数组(前 4 字节,大端) */
memcpy(p_sock->remote_ip_arr, remote, 4);
/* 小端 IP 组装为 32 位值 */
p_sock->remote_ip = ((uint32_t)remote[0]) |
((uint32_t)remote[1] << 8) |
((uint32_t)remote[2] << 16) |
((uint32_t)remote[3] << 24);
/* 对端端口(小端 2 字节) */
p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8));
/* 状态 → ESTABLISHED(监听自身转数据通道) */
p_sock->state = NET_SOCK_STATE_ESTABLISHED;
/* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平。必须在置
* ESTABLISHED 之后立刻做:否则抢先 relisten 后到新连接的窗口内
* recv 仍会误得 EOF(把新客户端踢开) */
p_sock->session_dead = 0U;
/* F-NET-17 M3 前置:清可疑电平——探针(LISTENING 期)残留计数会带进新会话,
* 不补此清零则 M3 矛盾判据首轮即被旁路(T6 实测 grep:connect 原不清此字段) */
p_sock->lost_conn_seen = 0U;
/* 新开 Socket 发送缓冲按空闲计 */
p_sock->send_ready = 1;
/* 清除残留的 TIMEOUT 中断标志 */
/* 读即清:本 Socket 中断状态寄存器 */
(void)ch395f_get_sock_int_status(p_sock->ch395_sock);
/* §5.56:TCP 服务器须在 CONNECT 后开启 KeepAlive。2026-10-12 曾加后
* 撤回(当时仅证明"物理断链"已被 +2s TIM_OUT 快速覆盖,其余场景
* 无法注入、未证明);E11(2026-10-13,《网络自愈架构设计》)用"禁用 PC 网卡"
* 注入"链路在、对端静默"(NAT/黑洞)补上证据链:生产参数 60/5/3
* 实测 ≈30 s 判死 → TIM_OUT → QUARANTINE → reconciler 重建;对端存活时
* 探测被 ACK、空闲会话不误杀。消费链路现成,按手册位置转正。 */
ch395f_set_keepalive_enable(p_sock->ch395_sock, 1);
/* 触发连接建立事件回调 */
fire_event(p_sock, NET_EVENT_CONNECTED);
} else if (p_sock->state == NET_SOCK_STATE_CONNECTING) {
/* TCP Client 连接成功 */
/* 对端 IP/端口(6 字节:4B IP + 2B 端口,小端) */
uint8_t remote[6];
/* 回读对端 IP + 端口 */
ch395f_get_remot_ipp(p_sock->ch395_sock, remote);
/* 对端 IP 数组(前 4 字节) */
memcpy(p_sock->remote_ip_arr, remote, 4);
/* 小端 IP 组装为 32 位值 */
p_sock->remote_ip = ((uint32_t)remote[0]) |
((uint32_t)remote[1] << 8) |
((uint32_t)remote[2] << 16) |
((uint32_t)remote[3] << 24);
/* 对端端口(小端 2 字节) */
p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8));
/* 状态 → ESTABLISHED */
p_sock->state = NET_SOCK_STATE_ESTABLISHED;
/* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平(同 LISTENING
* 分支:必须在转 ESTABLISHED 后立刻做,防抢先 relisten 窗口误 EOF) */
p_sock->session_dead = 0U;
/* F-NET-17 M3 前置:清可疑电平(同 LISTENING 分支) */
p_sock->lost_conn_seen = 0U;
/* 使能 KeepAlive(客户端连接保活) */
ch395f_set_keepalive_enable(p_sock->ch395_sock, 1);
/* 触发连接建立事件回调 */
fire_event(p_sock, NET_EVENT_CONNECTED);
}
}
/*
* 函数功能:处理 Socket 断开(DISCONNECT)事件,更新 Socket 状态机
* 入口参数:p_sock - 发生断开事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 DISCONNECT 中断后调用;netTask 上下文
* 函数说明:1. ⚠ FUN_PARA bit3=1(本仓 net_init 置 0x08):DISCONNECT/TIM_OUT 后芯片
* **不**关 Socket、**不清**缓冲(停 0x05/0x00)——残留数据须由本层读出后,再由
* 本层 ch395f_close_socket 关闭;芯片不代劳(§5.52② / §5.23),不得跳过
* 显式 close(U12 成因之一)。之后需重新 OPEN + LISTEN
* 2. 单连接:置 CLOSED,交由 net_recv_locked 排空剩余数据后再关
* 3. 触发 NET_EVENT_DISCONNECTED 回调
*/
static void handle_disconnect_event(net_sock_t *p_sock) {
#if defined(NET_BENCH_CMD_LOG)
/* Bench:记 'd'(disconnect)命令 */
net_bench_cmd_log('d', (int)p_sock->ch395_sock, 0);
#endif
/* 由控制块指针反推描述符 */
int sockfd = (int)(p_sock - s_net_socks);
/* 监听态:DISCONNECT 落在监听器上 */
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
/* TRAP-NET-17 / N-6:监听态收到 DISCONNECT——bit3=1(0x08)下芯片**不**关该 Socket(停 0x05/0x00,芯片 TCP 侧已 CLOSED);不重开则此后所有 SYN 全被拒 = 监听永久死亡;
* 触发 = 断连后 <~1s 快速复连时 CONNECT 中断未上报(控制块滞留 LISTENING)后该连接断开(2026-09-23 生产固件复现);
* N-32 P2:本分支**只回读留痕、不再 relisten** —— 重建统一交
* net_reconcile_tick 形态③(每 100 ms 读驱动缓存,监听失位即重建)。原处直接
* relisten 是 12 个所有者之一,且位于 DISCONNECT 事件路径,可能"抢先"
* 于消费者 recv(P1 已消除其后果,P2 消除其可能)。
* ⚠ 本留痕回读保持**主动 SPI**(不用驱动缓存):事件路径上缓存必然滞后
* 于刚到达的中断(刷新在 net_poll 末尾、事件处理之后),仅周期对账可用缓存 */
/* 回读缓冲(芯片状态 2 字节) */
uint8_t st[2] = {0, 0};
/* 回读芯片状态(仅为留痕:重建与否由 reconciler 裁决) */
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), reconciler will relisten",
sockfd, st[0], st[1]);
} else {
/* 仍在监听:判陈旧事件丢弃 */
DBG_INFO("sock%d stale listen DISCONNECT ignored (chip tcp=0x%02X)", sockfd, st[1]);
}
} else if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
/* 陈旧 DISCONNECT 甄别(D20 修复,2026-09-22 台架实证):同一 fd 被"关闭 → 重新监听 → 再连接"循环复用,上一条连接的 FIN/ACK
* 可能在 fd 已复用为新连接**之后**才上报(net_close_locked 排空的中断里还没有它)→ 新连接被误判为断开(实测:FTP 同一会话第二个数据连接必失败 425);
* 处置:回读芯片状态,TCP 仍 ESTABLISHED 判陈旧丢弃(已读清不重复上报);真断开时芯片处于 CLOSE_WAIT(0x07)/CLOSED(0x00) 等状态照常处理;超时事件走 handle_timeout_event,不受影响 */
/* 芯片状态回读缓冲(2 字节) */
uint8_t st[2];
/* 回读芯片状态(Socket + TCP) */
ch395f_get_socket_status((uint8_t)sockfd, st);
/* 芯片仍 ESTABLISHED:陈旧事件 */
if (st[1] == CH395F_TCP_ESTABLISHED) {
/* 留痕:陈旧事件丢弃 */
DBG_ERROR("sock%d stale DISCONNECT ignored (chip TCP still ESTABLISHED)", sockfd);
/* 直接返回(中断已读清,不会重复上报) */
return;
}
/* F-NET-17 M3(自愈专项实验 2026-10-13):矛盾态可疑门——芯片 Socket 仍 OPEN(0x05)
* 而 TCP 槽 0x00 且本层 ESTABLISHED = 逻辑矛盾(活连接应 0x04):陈旧 DISCONNECT
* (拆链完成滞后锁存,TRAP-NET-12)与"真断开恰被旧拆链覆写槽"在 D20 上不可分辨
* (结构性失明,F-NET-15 Q1)。处置:不吃边沿,置可疑电平返回,交对账①
* (ESTABLISHED + 芯片 TCP==CLOSED)电平去抖再入本函数(自愈收敛阶段 3 合并
* 后=4×100ms,NET_RECON_STABLE_DEATH,让位租约 400ms 不变 + 冷却):
* flag==1 旁路本门 → 正常释放;连接真活则租约内
* 会话正常完成(风暴链掐断,E9a n30 0/50 实证)。
* 零额外 SPI 于常规路径(st[] 已读;recv_len 仅可疑分支多读一条)。
* ⚠ E9a/E9b 实测两轮收窄:①必须含 recv_bytes==0——已读数据的会话遇
* 0x05/0x00 多为真死(探针控制通道教训);②必须含芯片 RX>0——延迟保护的
* 本质是"在途投递不可毁"(closed-with-data 救回同源红线:RX 有数据=消费者
* 资产)。@0ms 快复连(连上即关、零数据,E9b :21 46/50 教训)RX 空 → 立即
* 释放,误杀也无数据损失,后到数据由救回路径接住。边界:滞后锁存杀
* "已读数据或 RX 空"会话的形态不在本门保护域(E8 窗口未出现该受害形态)。
* 零误伤:健康 FIN=0x07、静默回收=0x00/0x00 均不命中(E5 20/20 样本) */
if ((st[0] == CH395F_SOCKET_OPEN) && (st[1] == CH395F_TCP_CLOSED) &&
(p_sock->lost_conn_seen == 0U) && (p_sock->recv_bytes == 0U) &&
(ch395f_get_recv_len((uint8_t)sockfd) > 0U)) {
/* 置可疑电平(复用探针字段,零 RAM;connect/relisten 清零) */
p_sock->lost_conn_seen = 1U;
/* 留痕(INFO 级:这是预期自愈路径,非错误,勿污染 [ERR] 台架判据) */
DBG_INFO("sock%d suspect DISCONNECT on live conn (chip 0x%02X/0x%02X), defer to reconcile",
sockfd, st[0], st[1]);
/* 不释放、不登记沉降、不动 session_dead——保留 400ms 租约窗口 */
return;
}
/* 单连接:先不关 CH395F Socket,
* 留给 net_recv_locked 把剩余数据读出来再关,置 CLOSED 等待应用重新监听 */
DBG_INFO("sock%d DISCONNECTED, releasing (chip st=0x%02X/0x%02X)",
p_sock->ch395_sock, st[0], st[1]);
/* TRAP-NET-18 推广(2026-09-24 台架实证):正常 DISCONNECT 释放留下的芯片收尾状态
* 会在复用同一 fd 的下一条连接上以"伪 DISCONNECT"形式投递(实测 accept 后约 10~20ms)→ 该连接被提前释放,应用把已到的 2048B 当整份文件并回报 226;
* 仅**本连接收过数据**时登记沉降(实测触发源 = 收过数据的数据通道被关闭),纯连接即关/只发不收的连接免沉降,保住 TRAP-NET-17 的快速复连时序(:21 @0ms ×50 全通过) */
/* F-NET-15 M1(自愈专项实验 2026-10-12):沉降**无条件**登记。原 recv_bytes!=0 门的
* 受害者由事件时序定义(accept 后 ~20ms 被陈旧 DISCONNECT 杀 → recv_bytes==0 是
* 构造性必然),门对"残渣循环自持"形态失效(n30_repro 背靠背 9/10 失败,E1 实测);
* 门原保护目标(:21 @0ms 快复连)已被 N-32 单一重建入口延迟化(恢复窗实测 0.3~1s)架空,
* +100ms 沉降落在窗内不可见。沉降窗口同时让残渣 DISCONNECT 在 state==CLOSED
* (无害、仅留痕)时被消费,断开"杀下一条 → 新拆链 → 再杀"的繁殖链 */
{
/* 反推描述符(沉降按 fd 登记) */
int qfd = (int)(p_sock - s_net_socks);
/* 描述符范围保护 */
if ((qfd >= 0) && (qfd < NET_MAX_SOCKETS)) {
/* 登记优雅沉降(NET_RELISTEN_SETTLE_GRACEFUL) */
net_listener_settle(qfd, NET_LISTENER_SETTLE_GRACEFUL);
}
}
/* 正常释放(对端 FIN):断连时挂起项以 EOF(0) 结束 */
p_sock->released_abnormal = 0U;
/* N-32(P1 不变量 I):置"本会话已结束"电平。须在置 CLOSED **之前**,
* 使 session_dead 覆盖"CLOSED 等待 recv 排空"整个窗口——排空期间
* recv 的 DONTWAIT 快路径先返数据(recv_len>0 优先),数据尽后判据
* 落到本标志 → 仍得 EOF,不因排空慢而漏报 */
p_sock->session_dead = 1U;
/* N-30(TRAP-NET-20):登记本连接对端端口(幽灵态甄别基准,须在状态
* 迁移前记录——remote_port 此刻仍有效,relisten 清零) */
p_sock->last_peer_port = p_sock->remote_port;
/* 状态 → CLOSED */
p_sock->state = NET_SOCK_STATE_CLOSED;
/* 触发断连事件回调 */
/* 触发断连事件回调 */
fire_event(p_sock, NET_EVENT_DISCONNECTED);
/* 其他状态:未预期的断开 */
} else if (p_sock->state != NET_SOCK_STATE_CLOSED &&
p_sock->state != NET_SOCK_STATE_BOUND &&
p_sock->state != NET_SOCK_STATE_CREATED) {
/* 未预期的断开:记错误日志 */
DBG_ERROR("sock%d disconnect in state %d", p_sock->ch395_sock, p_sock->state);
/* 触发断连事件回调(统一) */
fire_event(p_sock, NET_EVENT_DISCONNECTED);
}
}
/*
* 函数功能:处理 Socket 数据接收(RECV_OK)事件
* 入口参数:p_sock - 发生接收事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 RECV_OK 中断后调用;netTask 上下文
* 函数说明:1. 仅触发 NET_EVENT_DATA_RECEIVED 回调通知上层
* 2. 不在此读取 recv_len:单一读取点归 net_recv_locked(省一次 SPI 查询)
*/
static void handle_recv_event(net_sock_t *p_sock) {
/* RECV_OK 中断已触发,直接通知上层。不在此读 recv_len,
* 单一读取点在 net_recv_locked(省 SPI;原"部分读归零"顾虑已降级) */
/* 置可读标志,供 net_select 在无 SPI 访问下判读 */
p_sock->readable = 1;
/* 触发数据到达事件回调 */
fire_event(p_sock, NET_EVENT_DATA_RECEIVED);
}
/*
* 函数功能:处理 Socket 发送完成(SEND_OK)事件
* 入口参数:p_sock - 发生发送完成事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 SEND_OK 中断后调用;netTask 上下文
* 函数说明:触发 NET_EVENT_SEND_COMPLETE 回调通知上层
*/
static void handle_send_event(net_sock_t *p_sock) {
/* 触发发送完成事件回调 */
fire_event(p_sock, NET_EVENT_SEND_COMPLETE);
}
/*
* 函数功能:处理 Socket 超时(SOCK_TIMEOUT)事件,更新 Socket 状态机
* 入口参数:p_sock - 发生超时事件的 Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:net_poll() 检测到 SOCK_TIMEOUT 中断后调用;netTask 上下文
* 函数说明:1. TCP Client(CONNECTING):连接超时,回到 CREATED 状态
* 2. 已建立连接:回到 CLOSED(单连接)
* 3. 监听态:异常超时,记录错误日志
* 4. 触发 NET_EVENT_TIMEOUT 回调
*/
static void handle_timeout_event(net_sock_t *p_sock) {
#if defined(NET_BENCH_CMD_LOG)
/* Bench:记 'x'(timeout)命令 */
net_bench_cmd_log('x', (int)p_sock->ch395_sock, 0);
#endif
/* 连接中状态:连接超时 */
if (p_sock->state == NET_SOCK_STATE_CONNECTING) {
/* TCP Client 连接超时 */
DBG_ERROR("sock%d CONNECT TIMEOUT", p_sock->ch395_sock);
/* 状态 → CREATED(客户端槽位回可复用) */
p_sock->state = NET_SOCK_STATE_CREATED;
} else if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
/* 已建立连接超时 */
DBG_ERROR("sock%d TIMEOUT", p_sock->ch395_sock);
/* TRAP-NET-18:异常释放(TIMEOUT,例如客户端中止上传)→ 复监听前隔离,
* 等对端在途数据落尽并排空 RX 残渣(详见 net_relisten_locked 区块注释) */
{
/* 反推描述符 */
int qfd = (int)(p_sock - s_net_socks);
/* 描述符范围保护 */
if ((qfd >= 0) && (qfd < NET_MAX_SOCKETS)) {
/* 登记异常释放隔离(重监听前等待在途数据落尽 + RX 排空) */
net_listener_settle(qfd, NET_LISTENER_SETTLE_QUARANTINE);
}
}
/* TRAP-NET-18 待办 #3:标记异常释放——挂起 recv 排空断连后以
* NET_ERR_CONNRESET 结束(而非 EOF),应用(lftpd STOR)回报 5xx
* 而非误报 226(见 net_pending_tick 断连分支) */
p_sock->released_abnormal = 1U;
/* N-30(TRAP-NET-20):异常释放同样登记对端端口(幽灵甄别基准) */
p_sock->last_peer_port = p_sock->remote_port;
/* N-32(P1 不变量 I):异常结束同样是"本会话已结束"电平(TIMEOUT/RST
* 已在下方 requarantine,本标志只表达"会话结束了"这一事实,与
* released_abnormal(结束得好不好看)正交) */
p_sock->session_dead = 1U;
/* 状态 → CLOSED */
p_sock->state = NET_SOCK_STATE_CLOSED;
} else if (p_sock->state == NET_SOCK_STATE_LISTENING) {
/* 监听超时(异常情况) */
DBG_ERROR("sock%d LISTEN TIMEOUT", p_sock->ch395_sock);
}
/* 触发超时事件回调(统一) */
fire_event(p_sock, NET_EVENT_TIMEOUT);
}
/*
* 函数功能:触发 Socket 事件回调(若已注册)
* 入口参数:p_sock - 触发事件的 Socket 控制块指针 net_sock_t *
* event - 事件类型 net_event_t
* 出口参数:无
* 返回值:无
* 限定条件:net_init() 已调用
* 函数说明:1. 仅当 p_sock->event_cb 非空时调用回调
* 2. 回调参数为 ch395_sock、event、event_arg
*/
static void fire_event(net_sock_t *p_sock, net_event_t event) {
/* 已注册回调 */
if (p_sock->event_cb != NULL) {
/* 调用回调(ch395_sock、event、用户参数) */
p_sock->event_cb(p_sock->ch395_sock, event, p_sock->event_arg);
}
}
/* /*
* 函数功能:将点分十进制 IP 地址字符串解析为 4 字节数组 * 函数功能:将点分十进制 IP 地址字符串解析为 4 字节数组
@@ -3620,6 +3006,19 @@ net_sock_t *net_get_sock(int sockfd) {
return &s_net_socks[sockfd]; return &s_net_socks[sockfd];
} }
/*
* 函数功能:直取 Socket 控制块数组元素,**不判 in_use**(仅供 net 层内部周期对账使用)
* 入口参数:sockfd - Socket 描述符 (0~7),调用方保证在范围内
* 返回值:控制块指针(永不为 NULL)
* 限定条件:netTask 上下文
* 函数说明:net_get_sock() 对未在用槽位返回 NULL,而周期对账必须看到 in_use==0 /
* state==CLOSED 的槽位(形态④ 认领孤儿监听正依赖 CLOSED 态)→ 单独提供。
* 声明在内部头 net_events.h(不进公开 net_socket.h);应用层用 net_get_sock()。
*/
net_sock_t *net_sock_raw(int sockfd) {
return &s_net_socks[sockfd];
}
/* /*
* 函数功能:获取 Socket 本地地址(IP + Port) * 函数功能:获取 Socket 本地地址(IP + Port)
* 入口参数:sockfd - socket 描述符 int * 入口参数:sockfd - socket 描述符 int
+12
View File
@@ -485,6 +485,18 @@ void net_process_messages(void);
*/ */
net_sock_t *net_get_sock(int sockfd); net_sock_t *net_get_sock(int sockfd);
/*
* 函数功能:直取 Socket 控制块数组元素,**不判 in_use**(net 层内部周期对账专用)
* 入口参数:sockfd - Socket 描述符 (0~7),调用方保证在范围内
* 返回值:控制块指针(永不为 NULL)
* 限定条件:netTask 上下文
* 函数说明:`net_get_sock()` 对未在用槽位返回 NULL,而周期对账必须看到 in_use==0 /
* state==CLOSED 的槽位(对账形态④ 认领孤儿监听正依赖 CLOSED 态)→ 单独提供。
* ⚠ 应用层请一律用 `net_get_sock()`;本函数仅供 net_events.c 的对账循环使用。
* 模拟器不实现(无调用者,链接期不需要)。
*/
net_sock_t *net_sock_raw(int sockfd);
/* /*
* 函数功能:记录**本任务**最后一次失败的精确错误码(net 层内部通用工具) * 函数功能:记录**本任务**最后一次失败的精确错误码(net 层内部通用工具)
* 入口参数:err - 错误码(NET_ERR_xxx,负值) * 入口参数:err - 错误码(NET_ERR_xxx,负值)
+5
View File
@@ -1603,6 +1603,11 @@
<FileType>1</FileType> <FileType>1</FileType>
<FilePath>..\Drivers\BSP\NET\net_listener.c</FilePath> <FilePath>..\Drivers\BSP\NET\net_listener.c</FilePath>
</File> </File>
<File>
<FileName>net_events.c</FileName>
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\NET\net_events.c</FilePath>
</File>
<File> <File>
<FileName>net_pending.c</FileName> <FileName>net_pending.c</FileName>
<FileType>1</FileType> <FileType>1</FileType>
@@ -36,10 +36,10 @@
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py --> <!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 | | 区域 | 用量 | 容量 | 占比 |
|---|---|---|---| |---|---|---|---|
| ER_IROM1 | 231,056 B (225.64 KB) | 1,048,576 B (1024 KB) | 22.0% | | ER_IROM1 | 231,036 B (225.62 KB) | 1,048,576 B (1024 KB) | 22.0% |
| RW_IRAM1 | 98,144 B (95.84 KB) | 131,072 B (128 KB) | 74.9% | | RW_IRAM1 | 98,144 B (95.84 KB) | 131,072 B (128 KB) | 74.9% |
| RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% | | RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% |
| **Flash (ROM 合计)** | **232,168 B (226.7 KB)** | 1,048,576 B (1024 KB) | **22.1%** | | **Flash (ROM 合计)** | **232,148 B (226.7 KB)** | 1,048,576 B (1024 KB) | **22.1%** |
<!-- MEM_SNAPSHOT:END --> <!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源: 各区的容量与常量来源:
+1 -1
View File
@@ -15,7 +15,7 @@
<!-- MEM_BUDGET:BEGIN generated by scripts/ram_baseline.py --> <!-- MEM_BUDGET:BEGIN generated by scripts/ram_baseline.py -->
| 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) | | 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) |
|---|---|---|---|---| |---|---|---|---|---|
| ER_IROM1 (Flash) | 231,056 B | 1,048,576 B | 22.0% | 参考 | | ER_IROM1 (Flash) | 231,036 B | 1,048,576 B | 22.0% | 参考 |
| RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS | | RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS |
| UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 | | UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 |
| RW_IRAM1 | 98,144 B | 131,072 B | 74.9% | PASS | | RW_IRAM1 | 98,144 B | 131,072 B | 74.9% | PASS |
+1
View File
@@ -117,6 +117,7 @@ SIM_EXEMPT = [
("Drivers/BSP/NET/net_select.c", "MCU socket 选择层"), ("Drivers/BSP/NET/net_select.c", "MCU socket 选择层"),
("Drivers/BSP/NET/net_socket.c", "MCU socket 后端,模拟器另有 net_socket_sim.c"), ("Drivers/BSP/NET/net_socket.c", "MCU socket 后端,模拟器另有 net_socket_sim.c"),
("Drivers/BSP/NET/net_listener.c", "MCU 监听器生命周期(依赖 ch395f_* SPI 驱动与 net_socket.c 静态状态),模拟器无对应(Winsock 后端无孤儿态/重监听概念)"), ("Drivers/BSP/NET/net_listener.c", "MCU 监听器生命周期(依赖 ch395f_* SPI 驱动与 net_socket.c 静态状态),模拟器无对应(Winsock 后端无孤儿态/重监听概念)"),
("Drivers/BSP/NET/net_events.c", "MCU 芯片事件处理 + 100ms 周期对账(依赖 ch395f_* SPI 驱动与驱动状态缓存),模拟器无对应(Winsock 后端无\"事件未收到/静默回收\"概念,无失同步可收敛)"),
("Drivers/BSP/NET/net_pending.c", "MCU 挂起 recv 模型(依赖 ch395f_* SPI 读出与 FreeRTOS 通知),模拟器另有 net_socket_sim.c 按外部语义模拟"), ("Drivers/BSP/NET/net_pending.c", "MCU 挂起 recv 模型(依赖 ch395f_* SPI 读出与 FreeRTOS 通知),模拟器另有 net_socket_sim.c 按外部语义模拟"),
("Drivers/BSP/RS485/rs485.c", "MCU RS-485 驱动"), ("Drivers/BSP/RS485/rs485.c", "MCU RS-485 驱动"),
("Drivers/BSP/SD2506/sd2506.c", "MCU RTC 驱动"), ("Drivers/BSP/SD2506/sd2506.c", "MCU RTC 驱动"),
+1 -1
View File
@@ -1,4 +1,4 @@
date code ro rw zi er_irom1 er_pct iram1 iram1_pct ccm ccm_pct top_rwzi date code ro rw zi er_irom1 er_pct iram1 iram1_pct ccm ccm_pct top_rwzi
2026-10-12 209492 18332 1104 154184 227824 21.7 97864 74.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;console_log.o=4120;ch395f.o=4106;default_task.o=2736;sys_log_mcu.o=1924 2026-10-12 209492 18332 1104 154184 227824 21.7 97864 74.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;console_log.o=4120;ch395f.o=4106;default_task.o=2736;sys_log_mcu.o=1924
2026-10-13 211456 18760 1104 154232 230216 22.0 97912 74.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;default_task.o=2736;sys_log_mcu.o=1924 2026-10-13 211456 18760 1104 154232 230216 22.0 97912 74.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;default_task.o=2736;sys_log_mcu.o=1924
2026-10-14 212316 18740 1112 154456 231056 22.0 98144 74.9 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;default_task.o=2736;sys_log_mcu.o=1924 2026-10-14 212296 18740 1112 154456 231036 22.0 98144 74.9 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;default_task.o=2736;sys_log_mcu.o=1924
1 date code ro rw zi er_irom1 er_pct iram1 iram1_pct ccm ccm_pct top_rwzi
2 2026-10-12 209492 18332 1104 154184 227824 21.7 97864 74.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;console_log.o=4120;ch395f.o=4106;default_task.o=2736;sys_log_mcu.o=1924
3 2026-10-13 211456 18760 1104 154232 230216 22.0 97912 74.7 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;default_task.o=2736;sys_log_mcu.o=1924
4 2026-10-14 212316 212296 18740 1112 154456 231056 231036 22.0 98144 74.9 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=40960;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;default_task.o=2736;sys_log_mcu.o=1924