refactor(net): 挂起 recv 模型拆出 net_pending.c/h + 留痕探针编译门控 + E15 归因修正
- E15 归因修正:幽灵状态 0x04+旧端口 = 芯片如实报告拆除中的旧连接 (文档化行为,非芯片故障;bit3=1 为我方设置(ver>=0x46)、 RFC 9293 §3.3.2 Fig5 拆除顺序、WCH 官方博客方案 = close 完成后重开监听) - 术语统一:全库"了结"→"结束"(约 45 处);阻塞模型 §4 首次出现处定义"五路" - 注释清理:删孤儿函数头注释、考古叙事改现在时、去不可检索内部编码"L3" (33 处 6 文件)、banner 去重(唯一理由移到分支现场) - 挂起超时留痕探针:编译期开关 NET_PENDING_TRIPWIRE(默认 0 = 编译剔除, 省约 150B Flash;排查调用方挂死时置 1,只留痕绝不强制结束) - 拆模块:net_pending.c/h(363/94 行)承载挂起 recv 模型, net_socket.c 4059→3661 行;tick 收控制块表参数、settle_close 双参显式传; net_pending_active 声明移配对头(S17 2.2);仅注册 uvprojx + check_registration MCU-only 白名单(同 net_listener.c 机制, 模拟器走 net_socket_sim.c 按外部语义模拟) - 文档:阻塞模型新增全过程小节(单主干流程图)+ §9 排查辅助; NET README / 函数调用关系 / 自愈架构 / BSD 指南同步;引用关系图重生成 - 台架回归:启动闭环 PASS([ERR]/Fault 零命中、回读一致)、 :7003 重连探针 10/10、FTP 50 连接 STOR 50/50、回归窗口串口日志无异常 - RAM:ER_IROM1 230004B(探针门控后约 -150B),台账已回填
This commit is contained in:
@@ -143,9 +143,8 @@ extern "C" {
|
||||
* CH395F_SPI_DMA_MAX_PAYLOAD: 单次 DMA 事务最大有效载荷(总缓冲 - 4 字节命令头)
|
||||
* 缓冲按"一条命令读空整个 Socket 接收缓冲(2KB)"定容——排空契约(TRAP-NET-20,
|
||||
* bit3=1 残数据由 MCU 读出)的形态本身就是整缓冲单读。
|
||||
* (历史注记:旧注释把定容理由归给"部分读致 recv_len 归零、剩余数据不可见",该机理
|
||||
* 2026-10-11 复核**未证实**且与手册 §5.34 相左,已降级为未证实历史观察,结案见
|
||||
* `CH395F_Trap_Records.md` 台架纪律;定容不依赖该观察。)
|
||||
* (旧解释"部分读致 recv_len 归零、剩余数据不可见"未证实且与手册 §5.34 相左,
|
||||
* 定容不依赖该解释;结案见 `CH395F_Trap_Records.md` 台架纪律。)
|
||||
* 2026-09-22:与 MSS 解耦(此前 = 2×MSS+4,MSS 下调会把 DMA 缓冲缩到装不下整缓冲
|
||||
* → 整缓冲读会被截断丢弃)。
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
|
||||
| 文件 | 作用 |
|
||||
|---|---|
|
||||
| `net_socket.c/h` | **BSD Socket API 实现**:`net_socket` / `net_connect` / `net_send` / `net_recv` / `net_close` / `net_listen` / `net_accept`,含 L3 挂起(pending)与错误码 |
|
||||
| `net_socket.c/h` | **BSD Socket API 实现**:`net_socket` / `net_connect` / `net_send` / `net_recv` / `net_close` / `net_listen` / `net_accept`,含挂起 recv(pending)与错误码 |
|
||||
| `net_pending.c/h` | **挂起 recv 请求表**(阻塞 recv 挂起模型:登记 / 每轮完成检查 / 取消 / close 结算;真机专属,模拟器走 `net_socket_sim.c`) |
|
||||
| `net_select.c/h` | `select`/`poll` 多路复用(基于芯片中断事件 + 周期轮询) |
|
||||
| `net_types.h` | 网络层公共类型定义 |
|
||||
| `net_config.h` | 网络层编译期配置(缓冲区大小、超时、对账周期等) |
|
||||
@@ -15,7 +16,7 @@
|
||||
|
||||
## 核心机制
|
||||
|
||||
### 阻塞模型(L3 pending)
|
||||
### 阻塞模型(挂起 recv / pending)
|
||||
|
||||
- 一期(RECV)已落地:`net_recv` 等支持带超时的阻塞(`pending` 挂起 + tick 唤醒)
|
||||
- 二期(ACCEPT + SEND)待做 → `docs/任务规划/登记簿.md` N-4
|
||||
|
||||
@@ -6,16 +6,13 @@
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* E-02 头阻塞修复 + L3 pending 化(2026-09-22,M3 台架实证):
|
||||
* netTask 是全部 socket 操作的唯一执行者;lftpd 原先用默认 30s 的**阻塞**
|
||||
* net_recv 等待对端下一行/下一块数据 → 只要客户端连上却不发完整行,netTask
|
||||
* 就被长期占用,其余任务(含远端显示)的网络操作整体冻结(实测一次空闲 FTP
|
||||
* 登录让 LCDMEM 整帧等待 17.7s)。
|
||||
* 2026-09-22 先落地分片有界读(5ms 片;实测 100ms 片下受压帧仍达 ~750ms,
|
||||
* 收到 5ms 后降到 ~10ms 量级,见 m3_data 修复后数据),同日 L3 pending 化
|
||||
* 后改为"挂起请求":每次等待 = 一次挂起登记(netTask 零占用,完成按主循环
|
||||
* 节拍检查),调用方单次超时 = 剩余总预算(总等待 ≤ LFTPD_TOTAL_BUDGET_MS,
|
||||
* **总超时语义与旧实现一致**),旧分片协议退役。
|
||||
* E-02 头阻塞修复 + recv pending 化(M3 台架实证):
|
||||
* netTask 是全部 socket 操作的唯一执行者;lftpd 若用**阻塞** net_recv 等待对端
|
||||
* 下一行/下一块数据 → 只要客户端连上却不发完整行,netTask 就被长期占用,
|
||||
* 其余任务(含远端显示)的网络操作整体冻结(实测一次空闲 FTP 登录让 LCDMEM
|
||||
* 整帧等待 17.7s)。
|
||||
* 现形态:每次等待 = 一次挂起登记(netTask 零占用,完成按主循环节拍检查),
|
||||
* 调用方单次超时 = 剩余总预算(总等待 ≤ LFTPD_TOTAL_BUDGET_MS)。
|
||||
*/
|
||||
#define LFTPD_TOTAL_BUDGET_MS 30000U
|
||||
|
||||
@@ -133,8 +130,8 @@ int lftpd_inet_read_line(int socket, char *buffer, size_t buffer_len)
|
||||
int total_read_len = 0;
|
||||
|
||||
while (total_read_len < (int)buffer_len) {
|
||||
/* 单次预算 = 剩余总预算(L3 pending:等待期 netTask 零占用,调用方
|
||||
* 在自己上下文睡任务通知,旧片间 vTaskDelay(1) 让出退役) */
|
||||
/* 单次预算 = 剩余总预算(pending:等待期 netTask 零占用,调用方
|
||||
* 在自己上下文睡任务通知) */
|
||||
TickType_t elapsed = xTaskGetTickCount() - t0;
|
||||
uint32_t rem = (elapsed >= (TickType_t)pdMS_TO_TICKS(LFTPD_TOTAL_BUDGET_MS)) ? 0U
|
||||
: (uint32_t)((TickType_t)LFTPD_TOTAL_BUDGET_MS - elapsed);
|
||||
@@ -215,8 +212,8 @@ int lftpd_inet_write(int socket, const void *buf, int len)
|
||||
|
||||
int lftpd_inet_read(int socket, void *buf, int len)
|
||||
{
|
||||
/* L3 pending 读:总预算 30s 不变,等待期 netTask 零占用
|
||||
* (空闲数据连接不再冻结全网;旧分片协议随 L3 pending 化退役) */
|
||||
/* pending 读:总预算 30s 不变,等待期 netTask 零占用
|
||||
* (空闲数据连接不再冻结全网) */
|
||||
TickType_t t0 = xTaskGetTickCount();
|
||||
|
||||
for (;;) {
|
||||
|
||||
@@ -33,9 +33,10 @@
|
||||
|
||||
#include "net_socket.h" /* net_sock_t / net_get_sock / NET_MAX_SOCKETS / net_set_errno */
|
||||
#include "net_listener.h"
|
||||
#include "net_pending.h" /* net_pending_active(挂起 recv 保险查询) */
|
||||
#include "ch395f.h"
|
||||
#include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */
|
||||
#include "net_latency_test.h" /* L3 探针接口(relisten 恢复计时采样) */
|
||||
#include "net_latency_test.h" /* 延迟探针接口(relisten 恢复计时采样) */
|
||||
#include "main.h"
|
||||
|
||||
#include "dbg_log.h"
|
||||
@@ -182,7 +183,7 @@ static int net_relisten_locked(int sockfd) {
|
||||
* 其它在用 Socket 的 CONNECT/DISCONNECT/RECV_OK(net_poll 门控焊死);
|
||||
* ③ M1 使沉降成为每次断连热路径后,该搁浅被放大为 FTP 控制/数据通道
|
||||
* 互杀(E7 实测:lftpd 5s 超时、closed-with-data 救回全部迟到)。
|
||||
* 本 Socket 残留全局位交 net_poll 正常读到时 SINT 已为 0,无害了结 */
|
||||
* 本 Socket 残留全局位交 net_poll 正常读到时 SINT 已为 0,自然收尾 */
|
||||
/* 读芯片当前状态(两字节) */
|
||||
ch395f_get_socket_status((uint8_t)sockfd, st);
|
||||
|
||||
@@ -289,10 +290,10 @@ static int net_relisten_locked(int sockfd) {
|
||||
* 限定条件:netTask 上下文;由 handle_disconnect_event / handle_timeout_event 调用
|
||||
* 函数说明:1. 只置事实,不做动作 —— 重建由 net_reconcile_tick(形态③④)/
|
||||
* net_listener_try 负责
|
||||
* 2. 越界 sockfd 静默忽略(与旧直接写 s_relisten_quarantine 行为一致)
|
||||
* 2. 越界 sockfd 静默忽略
|
||||
*/
|
||||
void net_listener_settle(int sockfd, net_listener_settle_t level) {
|
||||
/* 描述符越界:静默忽略(与旧直接写数组行为一致,不设错误码) */
|
||||
/* 描述符越界:静默忽略,不设错误码 */
|
||||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||||
return;
|
||||
}
|
||||
@@ -399,7 +400,7 @@ int net_listener_try(int sockfd) {
|
||||
* 返回值:无
|
||||
* 限定条件:netTask 上下文(由 net_process_messages 每轮调用)
|
||||
* 函数说明:1. 到点重试待办的重监听;成功即报告探针样本
|
||||
* 2. 本 fd 有在途挂起 recv 时跳过(net_pending_active,L3 保险:
|
||||
* 2. 本 fd 有在途挂起 recv 时跳过(net_pending_active,挂起 recv 保险:
|
||||
* 重开 Socket 会摧毁在途连接)
|
||||
* 3. 超过 NET_RELISTEN_DEADLINE_MS 停止 D10 快速重试并留痕
|
||||
* (监听不永久失效——reconciler 100ms 周期仍按"芯片不在听"认领重建)
|
||||
@@ -429,7 +430,7 @@ void net_listener_retry_tick(void) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
/* 本 fd 有在途挂起 recv:重开 Socket 会摧毁在途连接(L3 保险) */
|
||||
/* 本 fd 有在途挂起 recv:重开 Socket 会摧毁在途连接(挂起 recv 保险) */
|
||||
if (net_pending_active(i) != 0U) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ typedef enum {
|
||||
* 限定条件:netTask 上下文;由 handle_disconnect_event / handle_timeout_event 调用
|
||||
* 函数说明:1. 只置事实,不做动作 —— 重建由 net_reconcile_tick(形态③④)/
|
||||
* net_listener_try 负责
|
||||
* 2. 越界 sockfd 静默忽略(不设错误码,与旧直接写数组行为一致)
|
||||
* 2. 越界 sockfd 静默忽略(不设错误码)
|
||||
*/
|
||||
void net_listener_settle(int sockfd, net_listener_settle_t level);
|
||||
|
||||
@@ -90,7 +90,7 @@ void net_listener_retry_tick(void);
|
||||
* net_reconcile_tick 单一 100ms tick(判据读驱动状态缓存,《网络自愈架构设计》§4.1)。
|
||||
* 本模块的 net_listener_try 由 reconciler 形态③④ 与 D10 重试轮询调用。 */
|
||||
|
||||
/* 注:本模块消费的宿主能力 `net_pending_active()` 声明在 net_socket.h
|
||||
* (它是 net_socket.c 的函数,须与配对头文件同处,见规范 S17 2.2)。 */
|
||||
/* 注:本模块消费的宿主能力 `net_pending_active()` 声明在 net_pending.h
|
||||
* (它是 net_pending.c 的函数,须与配对头文件同处,见规范 S17 2.2)。 */
|
||||
|
||||
#endif /* NET_LISTENER_H */
|
||||
|
||||
@@ -0,0 +1,416 @@
|
||||
/*
|
||||
* 模块名称:挂起 recv 请求表(阻塞 recv 挂起模型)—— 实现
|
||||
* 文件说明:见 net_pending.h;架构与五路语义见
|
||||
* docs/网络/Net层阻塞模型架构设计.md
|
||||
* 依赖(全部单向,无反向 include):
|
||||
* net_types.h(net_sock_t / NET_ERR_* / NET_SOCK_STATE_*)/
|
||||
* net_socket.h(NET_MAX_SOCKETS / net_set_errno / NET_ERR_RETURN_1 /
|
||||
* net_pending_active 声明)/ net_pending.h(本模块接口)/
|
||||
* net_listener.h(net_listener_try)/ ch395f.h(RX 读出)/ main.h(HAL_GetTick)
|
||||
* 限定条件:本文件真机专属——模拟器走 sim/net_socket_sim.c,
|
||||
* simulator/CMakeLists.txt 不编译本文件(同 net_listener.c 先例)
|
||||
*
|
||||
* 阻塞 recv 挂起模型(E-02 结构演进):阻塞等待 = 挂起请求 + 主循环节拍完成
|
||||
* "无数据 + 阻塞等待"不再自旋占用 netTask:登记 pending 后立即返回
|
||||
* net_pending_tick(与 D10 重监听重试同节奏,net_process_messages 开头每轮调用)
|
||||
* 按 ①数据 ②到点(仅显式预算) ③断连 顺序检查完成条件并通知调用方
|
||||
* 关闭/网络重建/陈旧重同步走显式 settle/cancel 路径(取消必通知,调用方永不挂死)
|
||||
* 公开 API 返回契约:>0 字节数 / 0 EOF / -1 错误(错误码经通知值回传)
|
||||
* 预算归调用方:timeout_ms=0 即无限等待(无默认预算);挂起超时留痕探针为
|
||||
* 编译期选项(NET_PENDING_TRIPWIRE,默认 0 = 编译剔除)
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
|
||||
#include "net_types.h"
|
||||
#include "net_socket.h"
|
||||
#include "net_pending.h"
|
||||
#include "net_listener.h"
|
||||
#include "ch395f.h"
|
||||
#include "dbg_log.h"
|
||||
#include "main.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t active; /* 1 = 本 fd 有挂起 recv */
|
||||
uint8_t infinite; /* 1 = 无限等待(无 deadline;0 预算 = 永等) */
|
||||
#if NET_PENDING_TRIPWIRE
|
||||
uint8_t tripwire_logged; /* 1 = 本轮挂起已留痕(保证只留一条) */
|
||||
#endif
|
||||
void *buf; /* 调用方缓冲(仅指针,须保持有效至完成) */
|
||||
int len; /* 缓冲容量 */
|
||||
uint32_t deadline; /* 截止时刻(tick)= 登记时刻 + 本次预算(infinite=1 时无意义) */
|
||||
uint32_t registered; /* 登记时刻(tick;留痕探针/日志基准) */
|
||||
TaskHandle_t caller; /* 调用方任务(完成通知目标) */
|
||||
} net_pending_t;
|
||||
|
||||
/* 每 fd 一个挂起(禁止覆盖:覆盖丢失前调用方通知 → 挂死) */
|
||||
static net_pending_t s_net_pending[NET_MAX_SOCKETS];
|
||||
/* 执行任务(net_init 捕获;防自通知死锁) */
|
||||
static TaskHandle_t s_owner_task;
|
||||
|
||||
/*
|
||||
* 函数功能:捕获执行任务句柄(登记守卫据此拒绝自等待死锁)
|
||||
* 入口参数:无
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:执行任务上下文(NET 层 = netTask / 硬件层 = testTask),net_init 顶部
|
||||
*/
|
||||
void net_pending_capture_owner(void) {
|
||||
s_owner_task = xTaskGetCurrentTaskHandle();
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:通知调用方并清除本 fd 的挂起请求(所有完成/取消路径的公共出口)
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* result - 32 位结果原值(>0 字节数 / 0 EOF / 负错误码) int
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:netTask 上下文;sockfd 合法且挂起在位(否则静默返回,防重复通知)
|
||||
* 函数说明:通知值 = 结果本身(与消息分派同一回传通道,调用方直接按通知值
|
||||
* 取结果);失败记入 netTask 自己的 errno 槽位(E-03)
|
||||
*/
|
||||
static void net_pending_complete(int sockfd, int result) {
|
||||
/* 本 fd 挂起请求指针 */
|
||||
net_pending_t *p;
|
||||
|
||||
/* 描述符越界:静默返回(防重复通知) */
|
||||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||||
return;
|
||||
}
|
||||
/* 取挂起请求 */
|
||||
p = &s_net_pending[sockfd];
|
||||
/* 无在位挂起(已结束/已取消):静默返回 */
|
||||
if (p->active == 0U) {
|
||||
return;
|
||||
}
|
||||
/* 失败结果:记入 netTask 自己的错误码槽位(E-03;不再写入跨任务共享全局) */
|
||||
if (result < 0) {
|
||||
(void)net_set_errno(result);
|
||||
}
|
||||
/* 覆盖式写入通知值 = 结果本身(32 位原值) */
|
||||
xTaskNotify(p->caller, (uint32_t)result, eSetValueWithOverwrite);
|
||||
/* 清除挂起状态 */
|
||||
p->active = 0U;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:登记一个挂起 recv 请求(net_recv_locked 的阻塞无数据路径)
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* buf - 调用方接收缓冲(须保持有效至完成) void *
|
||||
* len - 缓冲容量 int
|
||||
* to_ms - 本次等待预算(0 = 无限等待) uint32_t
|
||||
* caller - 调用方任务句柄(完成通知目标) TaskHandle_t
|
||||
* 出口参数:无
|
||||
* 返回值:NET_PENDING_REGISTERED - 登记成功(分派跳过即时通知)
|
||||
* -1 - 拒绝(同 fd 已有挂起 / 执行任务自身发起)
|
||||
* 限定条件:netTask 上下文
|
||||
* 函数说明:每 fd 至多一个挂起(禁止覆盖——覆盖丢失前调用方通知 = 该任务永久挂死);
|
||||
* 执行任务自身发起的阻塞等待无他人可完成(tick 跑在执行任务内)→ 自等自
|
||||
* 通知 = 死锁,必须拒绝(netTask 上下文调用方须用 DONTWAIT)
|
||||
*/
|
||||
int net_pending_register(int sockfd, void *buf, int len,
|
||||
uint32_t to_ms, TaskHandle_t caller) {
|
||||
/* 挂起请求指针 */
|
||||
net_pending_t *p;
|
||||
/* 登记时刻(tick) */
|
||||
uint32_t now;
|
||||
|
||||
/* 描述符越界 → BADF */
|
||||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||||
return NET_ERR_RETURN_1(NET_ERR_BADF);
|
||||
}
|
||||
/* 取挂起请求 */
|
||||
p = &s_net_pending[sockfd];
|
||||
|
||||
/* 同 fd 已有挂起:拒绝(覆盖将丢失前通知 = 该任务永久挂死) */
|
||||
if (p->active != 0U) {
|
||||
return NET_ERR_RETURN_1(NET_ERR_BUSY);
|
||||
}
|
||||
/* netTask 自身发起:无他人可完成(tick 跑在 netTask 内)→ 拒绝(自等死锁) */
|
||||
if ((caller == NULL) || (caller == s_owner_task)) {
|
||||
return NET_ERR_RETURN_1(NET_ERR_BUSY);
|
||||
}
|
||||
|
||||
/* 当前 tick(截止/留痕探针基准) */
|
||||
now = HAL_GetTick();
|
||||
/* 标记挂起在位 */
|
||||
p->active = 1U;
|
||||
/* 预算形态:0 = 无限等待(无 deadline,tick ② 跳过;留痕探针以登记时刻计) */
|
||||
p->infinite = (to_ms == 0U) ? 1U : 0U;
|
||||
#if NET_PENDING_TRIPWIRE
|
||||
/* 留痕标记清零(每轮挂起只留一条) */
|
||||
p->tripwire_logged = 0U;
|
||||
#endif
|
||||
/* 调用方接收缓冲(须保持有效至完成) */
|
||||
p->buf = buf;
|
||||
/* 缓冲容量 */
|
||||
p->len = len;
|
||||
/* 截止时刻 = 登记时刻 + 预算(无限等待时不启用,置 0 仅为可观测性) */
|
||||
p->deadline = (to_ms == 0U) ? 0U : (now + to_ms);
|
||||
/* 登记时刻(留痕探针基准) */
|
||||
p->registered = now;
|
||||
/* 完成通知目标 */
|
||||
p->caller = caller;
|
||||
/* 登记成功:分派跳过即时通知 */
|
||||
return NET_PENDING_REGISTERED;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:挂起 recv 完成检查(netTask 每轮 net_process_messages 调用一次,非阻塞)
|
||||
* 入口参数:p_socks - Socket 控制块表首指针(net_socket.c 的 s_net_socks) net_sock_t *
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:netTask 上下文
|
||||
* 函数说明:1. 逐 fd 检查完成条件:数据到达 / 到点(仅显式预算)/ 断连
|
||||
* 2. 完成即经 net_pending_complete 通知并清除
|
||||
* 3. 挂起超时留痕探针(编译期选项 NET_PENDING_TRIPWIRE,默认关):挂起超阈值
|
||||
* 只 [ERR] 留痕一次("完成逻辑缺位"探针),绝不强制结束、绝不伪造返回值
|
||||
*/
|
||||
void net_pending_tick(net_sock_t *p_socks) {
|
||||
/* 当前 tick */
|
||||
uint32_t now = HAL_GetTick();
|
||||
/* Socket 循环索引 */
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NET_MAX_SOCKETS; i++) {
|
||||
/* 本 fd 挂起请求 */
|
||||
net_pending_t *p = &s_net_pending[i];
|
||||
/* 本 fd 控制块 */
|
||||
net_sock_t *p_sock = &p_socks[i];
|
||||
|
||||
/* 无挂起请求:跳过 */
|
||||
if (p->active == 0U) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* fd 已关闭 */
|
||||
if (p_sock->in_use == 0U) {
|
||||
/* fd 已关闭(正常路径在 close 内先结束;此处兜底,防任何路径漏掉通知) */
|
||||
net_pending_complete(i, NET_ERR_CONNRESET);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ① 数据到达(数据优先)——readable 门控:常见无数据路径零额外 SPI 事务
|
||||
* (INT# 电平触发链保证缓冲有数据时每轮重读 SINT 重位置位) */
|
||||
if ((p_sock->state == NET_SOCK_STATE_ESTABLISHED) ||
|
||||
(p_sock->state == NET_SOCK_STATE_UDP_OPEN)) {
|
||||
/* 可读标志置位 */
|
||||
if (p_sock->readable != 0U) {
|
||||
/* 查询芯片侧可用字节数 */
|
||||
int avail = ch395f_get_recv_len((uint8_t)i);
|
||||
/* 有数据 */
|
||||
if (avail > 0) {
|
||||
/* 本次读取量 = min(可用, 缓冲容量) */
|
||||
int rd = (avail > p->len) ? p->len : avail;
|
||||
/* 读入调用方缓冲区 */
|
||||
ch395f_read_recv_buf((uint8_t)i, (uint8_t *)p->buf, (uint16_t)rd);
|
||||
/* 累计接收字节统计 */
|
||||
p_sock->recv_bytes += (uint32_t)rd;
|
||||
/* 数据已消费,清可读标志 */
|
||||
p_sock->readable = 0U;
|
||||
/* 结束:以字节数通知 */
|
||||
net_pending_complete(i, rd);
|
||||
continue;
|
||||
}
|
||||
/* 虚假标志:缓冲无数据,继续等待 */
|
||||
p_sock->readable = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
/* ② 到点(仅显式预算登记;无限等待登记无 deadline):
|
||||
* 超时是调用方策略,net_recv_to 的预算在此兑现,不是伪造。
|
||||
* tick 差值按有符号比较,抗回绕) */
|
||||
if ((p->infinite == 0U) && ((int32_t)(now - p->deadline) >= 0)) {
|
||||
/* 结束:超时(错误码由 net_pending_complete 记入本任务槽位) */
|
||||
net_pending_complete(i, NET_ERR_TIMEDOUT);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ③ 断连:排空 → 数据或 EOF(0) + 自动重监听。
|
||||
* 判据同 net_recv_locked(N-32 P1 不变量 I):`state == CLOSED ||
|
||||
* session_dead`。只判 CLOSED 时,抢先 relisten 后 ①(要求 ESTABLISHED)
|
||||
* 与 ③(要求 CLOSED) 两头落空 → 挂起的 recv 只能干等 ② deadline 超时
|
||||
* (lftpd 阻塞 recv 的"消费者卡死"原型)。无限等待登记没有 ②,
|
||||
* 漏 ③ = 永久挂死(留痕探针只留痕、不唤醒)——
|
||||
* ③ 与显式 cancel 是无限 recv 的全部唤醒责任方,勿动判据。 */
|
||||
if ((p_sock->state == NET_SOCK_STATE_CLOSED) || (p_sock->session_dead != 0U)) {
|
||||
/* 查询残留字节数 */
|
||||
int rem = ch395f_get_recv_len((uint8_t)i);
|
||||
/* 有残留数据:按数据完成 */
|
||||
if (rem > 0) {
|
||||
/* 本次读取量 = min(残留, 缓冲容量) */
|
||||
int rd = (rem > p->len) ? p->len : rem;
|
||||
/* 读入调用方缓冲区 */
|
||||
ch395f_read_recv_buf((uint8_t)i, (uint8_t *)p->buf, (uint16_t)rd);
|
||||
/* 累计接收字节统计 */
|
||||
p_sock->recv_bytes += (uint32_t)rd;
|
||||
/* 数据已消费,清可读标志 */
|
||||
p_sock->readable = 0U;
|
||||
/* 结束:以字节数通知 */
|
||||
net_pending_complete(i, rd);
|
||||
} else {
|
||||
/* 启用自动重监听:先把监听器重新置回 LISTEN。
|
||||
* ⚠ 内层 `state == CLOSED` 门同 net_recv_locked:session_dead
|
||||
* 但已回 LISTENING 时重建已由别处完成,再 open+listen 会因
|
||||
* 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 重试轮询接管(N-32 §10:入口收敛到 net_listener_try) */
|
||||
(void)net_listener_try(i);
|
||||
}
|
||||
/* TRAP-NET-18 待办 #3:异常释放(TIMEOUT/RST)不是正常 EOF(对端未发正常 FIN)
|
||||
* → 按连接重置结束:应用(lftpd STOR)对异常结束回 5xx 而非误报 226;
|
||||
* 正常 FIN(DISCONNECT)仍回 0(在途 FIN 与正常完成不可区分——协议边界,见 TRAP-NET-18 记录) */
|
||||
/* 异常释放:按连接重置结束(回 5xx,不误报 226) */
|
||||
if (p_sock->released_abnormal != 0U) {
|
||||
/* 异常释放(错误码由 net_pending_complete 记入本任务槽位) */
|
||||
net_pending_complete(i, NET_ERR_CONNRESET);
|
||||
} else {
|
||||
/* 正常 EOF:以 0 结束 */
|
||||
net_pending_complete(i, 0);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 其他状态(CONNECTING/LISTENING/…):保持等待状态迁移——CONNECT→ESTABLISHED
|
||||
* 是合法迁移,不做状态推断取消,避免误杀正在建链的等待;陈旧 resync 由显式 cancel 处理 */
|
||||
|
||||
/* ④ 挂起超时留痕探针(编译期选项 NET_PENDING_TRIPWIRE,默认关):挂起超
|
||||
* 阈值 = "完成逻辑缺位"探针 → 只留一条 [ERR],**绝不强制结束、绝不伪造
|
||||
* TIMEDOUT 返回值**(强制结束会把完成路径缺陷伪装成普通超时,调用方
|
||||
* 拿到假结果、缺陷永远潜伏)。判据:无限登记按登记时刻计;显式登记按
|
||||
* deadline 计(② 正常不会让它越过阈值,能到这里本身就是完成逻辑缺位)。 */
|
||||
#if NET_PENDING_TRIPWIRE
|
||||
if (p->tripwire_logged == 0U) {
|
||||
/* 探针基准时刻:无限 = 登记时刻;显式 = 截止时刻 */
|
||||
uint32_t base = (p->infinite != 0U) ? p->registered : p->deadline;
|
||||
if ((int32_t)(now - base) >= (int32_t)NET_RECV_TRIPWIRE_MS) {
|
||||
/* 留痕(挂起已持续毫秒数,自基准起算) */
|
||||
DBG_ERROR("sock%d recv pending %ums (base=%s): completion source missing",
|
||||
i, (unsigned)((int32_t)now - (int32_t)base),
|
||||
(p->infinite != 0U) ? "infinite" : "deadline");
|
||||
/* 只留痕一次(每轮挂起一条,防日志风暴) */
|
||||
p->tripwire_logged = 1U;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:查询本 fd 是否有在途挂起 recv —— 宿主能力,供 net_listener 用
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* 出口参数:无
|
||||
* 返回值:1 - 有挂起;0 - 无(含描述符越界) int
|
||||
* 限定条件:任意上下文(纯 RAM 读,不发起 RPC)
|
||||
* 函数说明:1. N-32 §10 模块化:net_listener_retry_tick 用它跳过"重开 Socket
|
||||
* 会摧毁在途连接"的 fd(挂起 recv 保险,原为直接读 s_net_pending[i].active)
|
||||
* 2. 声明在 net_pending.h(配对头,规范 S17 2.2)
|
||||
*/
|
||||
int net_pending_active(int sockfd) {
|
||||
/* 描述符越界:视为无挂起 */
|
||||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||||
return 0;
|
||||
}
|
||||
return (s_net_pending[sockfd].active != 0U) ? 1 : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:清除本 fd 的挂起请求(不通知;用于"新 fd 不得继承"与 close 后的幂等清理)
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:无
|
||||
* 函数说明:1. 静默清理不发通知:close 路径先经 settle 通知,本调用仅保证状态归零(幂等)
|
||||
* 2. 新建 fd 时调用,防复用槽位继承上一条连接的挂起请求
|
||||
*/
|
||||
void net_pending_clear(int sockfd) {
|
||||
/* 描述符越界:直接返回 */
|
||||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||||
return;
|
||||
}
|
||||
/* 挂起状态清零(静默,不发通知) */
|
||||
s_net_pending[sockfd].active = 0U;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取消本 fd 的挂起请求并通知调用方(显式路径:陈旧 resync 等)
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* err - 通知的错误码(如 NET_ERR_CONNRESET) int
|
||||
* 出口参数:无
|
||||
* 返回值:无(无在位挂起时静默返回)
|
||||
* 限定条件:netTask 上下文
|
||||
* 函数说明:经 net_pending_complete 以指定错误码通知调用方(取消必通知,防挂死)
|
||||
*/
|
||||
void net_pending_cancel(int sockfd, int err) {
|
||||
/* complete 内部校验范围与在位(未在位则静默返回) */
|
||||
net_pending_complete(sockfd, err);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取消全部挂起请求并逐一通知(网络重建路径)
|
||||
* 入口参数:err - 通知的错误码 int
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:netTask 上下文;须在控制块清零与芯片复位之前调用
|
||||
* 函数说明:网络重建(net_stack_bring_up)前调用,保证所有在位调用方均被唤醒
|
||||
*/
|
||||
void net_pending_cancel_all(int err) {
|
||||
/* 逐 fd 遍历 */
|
||||
for (int i = 0; i < NET_MAX_SOCKETS; i++) {
|
||||
/* 取消并通知(未在位则静默跳过) */
|
||||
net_pending_complete(i, err);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:关闭前先结束本 fd 的挂起 recv——把结果回传并唤醒调用方(有数据先
|
||||
* 送达,无数据则取消),再关闭 Socket(per-fd 串行:先完成、再关闭)
|
||||
* 入口参数:sockfd - 将被关闭的描述符 (0~7) int
|
||||
* p_sock - 将被关闭的 Socket 控制块指针(与 sockfd 对应) net_sock_t *
|
||||
* 出口参数:无
|
||||
* 返回值:无(无在位挂起时静默返回)
|
||||
* 限定条件:netTask 上下文;须在 ch395f_close_socket 之前调用
|
||||
* 函数说明:先排空尝试——关闭不丢已到达数据(芯片缓冲在 close 前可读);
|
||||
* 有数据按字节数通知,否则取消(NET_ERR_CONNRESET)
|
||||
*/
|
||||
void net_pending_settle_close(int sockfd, net_sock_t *p_sock) {
|
||||
/* 通知用结果值(默认为取消) */
|
||||
int val;
|
||||
/* 芯片 RX 可用字节数 */
|
||||
int avail;
|
||||
|
||||
/* 描述符越界或无在位挂起:静默返回 */
|
||||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||||
return;
|
||||
}
|
||||
if (s_net_pending[sockfd].active == 0U) {
|
||||
return;
|
||||
}
|
||||
/* 默认结果 = 取消(CONNRESET) */
|
||||
val = NET_ERR_CONNRESET;
|
||||
/* 查询芯片 RX 可用字节(缓冲内数据在 close 前可读) */
|
||||
avail = ch395f_get_recv_len((uint8_t)sockfd);
|
||||
/* 数据已到达:排空,关闭不丢数据 */
|
||||
if (avail > 0) {
|
||||
/* 本次读取量 = min(可用, 缓冲容量) */
|
||||
int rd = (avail > s_net_pending[sockfd].len) ? s_net_pending[sockfd].len : avail;
|
||||
/* 读入调用方缓冲区 */
|
||||
ch395f_read_recv_buf((uint8_t)sockfd, (uint8_t *)s_net_pending[sockfd].buf, (uint16_t)rd);
|
||||
/* 累计接收字节统计 */
|
||||
p_sock->recv_bytes += (uint32_t)rd;
|
||||
/* 数据已消费,清可读标志 */
|
||||
p_sock->readable = 0U;
|
||||
/* 有数据:以字节数通知 */
|
||||
val = rd;
|
||||
}
|
||||
/* 结束(必通知) */
|
||||
net_pending_complete(sockfd, val);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 模块名称:挂起 recv 请求表(阻塞 recv 挂起模型)
|
||||
* 文件说明:阻塞 recv 的"挂起请求"数据结构与生命周期:登记 / 每轮完成检查 /
|
||||
* 取消(close、网络重建、对账 resync)/ 幂等清理。
|
||||
* 架构与五路语义见 docs/网络/Net层阻塞模型架构设计.md
|
||||
* 依赖:net_types.h(net_sock_t);消费方 = net_socket.c(消息分派 / close /
|
||||
* 重建 / 对账)
|
||||
* 限定条件:除 net_pending_active / net_pending_clear(纯 RAM 读)外,
|
||||
* 全部函数须 netTask 上下文
|
||||
*/
|
||||
#ifndef NET_PENDING_H
|
||||
#define NET_PENDING_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "net_types.h"
|
||||
|
||||
/* 内部哨兵:pending 已登记(消息分派据此跳过即时通知;取值不与字节数空间冲突) */
|
||||
#define NET_PENDING_REGISTERED 0x70000000
|
||||
|
||||
/* 挂起超时留痕探针:排查"调用方挂死唤不醒"时置 1——挂起超 NET_RECV_TRIPWIRE_MS
|
||||
* 只留一条 [ERR](哪个 fd/多少毫秒/什么基准),绝不强制结束、绝不伪造返回值。
|
||||
* 开与关语义完全一致,差别只在留痕;默认 0 = 产品构建零成本。
|
||||
* 见 docs/网络/Net层阻塞模型架构设计.md §9 */
|
||||
#define NET_PENDING_TRIPWIRE 0
|
||||
#if NET_PENDING_TRIPWIRE
|
||||
#define NET_RECV_TRIPWIRE_MS 60000U /* 挂起超时留痕阈值(ms) */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 函数功能:捕获执行任务句柄(net_init 顶部调用一次;登记守卫据此拒绝自等待死锁)
|
||||
* 入口参数:无
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:执行任务(netTask / 硬件层 = testTask)上下文,创建消息队列前
|
||||
*/
|
||||
void net_pending_capture_owner(void);
|
||||
|
||||
/*
|
||||
* 函数功能:登记一个挂起 recv 请求(net_recv_locked 的阻塞无数据路径)
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* buf - 调用方接收缓冲(须保持有效至完成) void *
|
||||
* len - 缓冲容量 int
|
||||
* to_ms - 本次等待预算(0 = 无限等待) uint32_t
|
||||
* caller - 调用方任务句柄(完成通知目标) TaskHandle_t
|
||||
* 出口参数:无
|
||||
* 返回值:NET_PENDING_REGISTERED - 登记成功(分派跳过即时通知)
|
||||
* -1 - 拒绝(同 fd 已有挂起 / 执行任务自身发起;错误码经 NET_ERR_RETURN_1)
|
||||
* 限定条件:netTask 上下文
|
||||
* 函数说明:每 fd 至多一个挂起(禁止覆盖——覆盖丢失前调用方通知 = 该任务永久挂死);
|
||||
* 执行任务自身发起的阻塞等待无他人可完成 → 自等自通知 = 死锁,必须拒绝
|
||||
*/
|
||||
int net_pending_register(int sockfd, void *buf, int len,
|
||||
uint32_t to_ms, TaskHandle_t caller);
|
||||
|
||||
/*
|
||||
* 函数功能:挂起 recv 完成检查(netTask 每轮 net_process_messages 调用一次,非阻塞)
|
||||
* 入口参数:p_socks - Socket 控制块表首指针(net_socket.c 的 s_net_socks) net_sock_t *
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:netTask 上下文
|
||||
* 函数说明:逐 fd 按序判定:①数据到达 ②到点(仅显式预算) ③断连(排空→数据或
|
||||
* EOF/CONNRESET);命中即经公共出口通知并清槽;另有编译期留痕探针
|
||||
* (NET_PENDING_TRIPWIRE,默认关)
|
||||
*/
|
||||
void net_pending_tick(net_sock_t *p_socks);
|
||||
|
||||
/*
|
||||
* 函数功能:清除本 fd 的挂起请求(不通知;"新 fd 不得继承"与 close 后的幂等清理)
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:无
|
||||
*/
|
||||
void net_pending_clear(int sockfd);
|
||||
|
||||
/*
|
||||
* 函数功能:取消本 fd 的挂起请求并通知调用方(显式路径:对账 resync 等)
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* err - 通知的错误码(如 NET_ERR_CONNRESET) int
|
||||
* 出口参数:无
|
||||
* 返回值:无(无在位挂起时静默返回)
|
||||
* 限定条件:netTask 上下文
|
||||
*/
|
||||
void net_pending_cancel(int sockfd, int err);
|
||||
|
||||
/*
|
||||
* 函数功能:取消全部挂起请求并逐一通知(网络重建路径)
|
||||
* 入口参数:err - 通知的错误码 int
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:netTask 上下文;须在控制块清零与芯片复位之前调用
|
||||
*/
|
||||
void net_pending_cancel_all(int err);
|
||||
|
||||
/*
|
||||
* 函数功能:关闭前先结束本 fd 的挂起 recv——有数据先送达(按字节数),无数据则
|
||||
* 取消(CONNRESET),然后调用方才执行 close/拆会话(per-fd 串行)
|
||||
* 入口参数:sockfd - 将被关闭的描述符 (0~7) int
|
||||
* p_sock - 将被关闭的 Socket 控制块指针(与 sockfd 对应) net_sock_t *
|
||||
* 出口参数:无
|
||||
* 返回值:无(无在位挂起时静默返回)
|
||||
* 限定条件:netTask 上下文;须在 ch395f_close_socket 之前调用
|
||||
*/
|
||||
void net_pending_settle_close(int sockfd, net_sock_t *p_sock);
|
||||
|
||||
/*
|
||||
* 函数功能:查询本 fd 是否有在途挂起 recv —— 宿主能力,供 net_listener 消费
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* 出口参数:无
|
||||
* 返回值:1 - 有挂起;0 - 无(含描述符越界) int
|
||||
* 限定条件:任意上下文(纯 RAM 读,不发起 RPC)
|
||||
* 函数说明:N-32 §10 模块化:net_listener_retry_tick 用它跳过"重开 Socket
|
||||
* 会摧毁在途连接"的 fd(挂起 recv 保险)
|
||||
*/
|
||||
int net_pending_active(int sockfd);
|
||||
|
||||
#endif /* NET_PENDING_H */
|
||||
+40
-444
@@ -19,9 +19,10 @@
|
||||
|
||||
#include "net_socket.h"
|
||||
#include "net_listener.h"
|
||||
#include "net_pending.h"
|
||||
#include "ch395f.h"
|
||||
#include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */
|
||||
#include "net_latency_test.h" /* L3 探针接口(chunk_wait 采样) */
|
||||
#include "net_latency_test.h" /* 延迟探针接口(chunk_wait 采样) */
|
||||
#include "main.h"
|
||||
|
||||
/*
|
||||
@@ -104,7 +105,7 @@ static struct {
|
||||
* 函数说明:1. 已存在的槽位(owner 命中)直接复用;空槽(owner == NULL)首次占用
|
||||
* 2. 快路径**只读不写**:并发读者绝不会看到"owner 已写、err 未写"的中间态
|
||||
* 3. 槽位与任务不强绑定释放:任务句柄被回收复用时,新任务沿用该槽位的
|
||||
* err 初值无碍(语义本就允许残留码,与旧全局变量行为一致)
|
||||
* err 初值无碍(语义本就允许残留码)
|
||||
*/
|
||||
static uint32_t net_errno_slot(void) {
|
||||
/* 任务句柄(未启动内核/中断上下文时为 NULL) */
|
||||
@@ -190,41 +191,6 @@ static int net_drop_conn_locked(net_sock_t *p_sock);
|
||||
static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen);
|
||||
static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int addrlen);
|
||||
|
||||
/* L3 pending 化(一期,E-02 结构演进):阻塞等待 = 挂起请求 + 主循环节拍完成
|
||||
* "无数据 + 阻塞等待"不再自旋占用 netTask:登记 pending 后立即返回
|
||||
* net_pending_tick(与 D10 重监听重试同节奏,net_process_messages 开头每轮调用)
|
||||
* 按 ①数据 ②到点(仅显式预算) ③断连 顺序检查完成条件并通知调用方(语义逐项对齐旧自旋循环)
|
||||
* 关闭/网络重建/陈旧重同步走显式 settle/cancel 路径(取消必通知,调用方永不挂死)
|
||||
* 公开 API 返回契约不变:>0 字节数 / 0 EOF / -1 错误(错误码经通知值回传)
|
||||
* 自愈收敛阶段 1.5:预算归调用方——timeout_ms=0 即无限等待(30s 默认预算已删),
|
||||
* 原"到点 +1s 强制了结"的看门狗降级为 tripwire(只留痕、绝不伪造返回值)
|
||||
*/
|
||||
#define NET_PENDING_REGISTERED 0x70000000 /* 内部哨兵:pending 已登记(分派跳过即时通知;取值不与字节数空间冲突) */
|
||||
#define NET_RECV_TRIPWIRE_MS 60000U /* tripwire:挂起超 60s = 完成逻辑缺位探针 → 只 [ERR] 留痕一次,绝不强制了结 */
|
||||
|
||||
typedef struct {
|
||||
uint8_t active; /* 1 = 本 fd 有挂起 recv */
|
||||
uint8_t infinite; /* 1 = 无限等待(无 deadline,自愈收敛阶段 1.5:0 预算 = 永等) */
|
||||
uint8_t tripwire_logged; /* 1 = 本轮挂起已打过 tripwire(保证只留痕一条) */
|
||||
void *buf; /* 调用方缓冲(仅指针,须保持有效至完成) */
|
||||
int len; /* 缓冲容量 */
|
||||
uint32_t deadline; /* 截止时刻(tick)= 登记时刻 + 本次预算(infinite=1 时无意义) */
|
||||
uint32_t registered; /* 登记时刻(tick;tripwire/日志基准) */
|
||||
TaskHandle_t caller; /* 调用方任务(完成通知目标) */
|
||||
} net_pending_t;
|
||||
|
||||
static net_pending_t s_net_pending[NET_MAX_SOCKETS]; /* 每 fd 一个挂起(禁止覆盖:覆盖丢失前调用方通知 → 挂死) */
|
||||
static TaskHandle_t s_net_task_handle; /* 执行任务(net_init 捕获;防自通知死锁) */
|
||||
|
||||
static void net_pending_tick(void);
|
||||
static void net_pending_complete(int sockfd, int result);
|
||||
static int net_pending_register(int sockfd, void *buf, int len,
|
||||
uint32_t to_ms, TaskHandle_t caller);
|
||||
static void net_pending_clear(int sockfd);
|
||||
static void net_pending_cancel(int sockfd, int err);
|
||||
static void net_pending_cancel_all(int err);
|
||||
static void net_pending_settle_close(net_sock_t *p_sock);
|
||||
|
||||
/*
|
||||
* 函数功能:判断 6 字节 MAC 是否全零
|
||||
* 入口参数:p_mac - MAC 指针(不可为 NULL) const uint8_t *
|
||||
@@ -259,7 +225,7 @@ static bool s_mac_is_zero(const uint8_t *p_mac) {
|
||||
int net_init(const char *ip, const char *mask, const char *gateway, const uint8_t *mac) {
|
||||
ch395f_status_t ret;
|
||||
|
||||
/* L3:网络重建前取消全部挂起请求并通知调用方(调用方阻塞等回执,
|
||||
/* 网络重建前取消全部挂起请求并通知调用方(调用方阻塞等回执,
|
||||
* 取消不通知 = 永久挂死);必须早于控制块清零与芯片复位 */
|
||||
net_pending_cancel_all(NET_ERR_CONNRESET);
|
||||
|
||||
@@ -267,7 +233,7 @@ static bool s_mac_is_zero(const uint8_t *p_mac) {
|
||||
memset(s_net_socks, 0, sizeof(s_net_socks));
|
||||
net_set_errno(NET_OK);
|
||||
s_net_initialized = 0;
|
||||
s_net_task_handle = xTaskGetCurrentTaskHandle(); /* 执行任务 = net_init 调用者(NET 层 = netTask / 硬件层 = testTask) */
|
||||
net_pending_capture_owner(); /* 执行任务 = net_init 调用者(NET 层 = netTask / 硬件层 = testTask) */
|
||||
|
||||
/* 检查 CH395F 芯片 */
|
||||
if (ch395f_check_exist() != CH395F_STATUS_OK) {
|
||||
@@ -649,7 +615,7 @@ int net_poll(void) {
|
||||
* U11 本层 CLOSED(监听型 fd)而芯片在听 → 只重同步,**不发芯片命令**
|
||||
* (重下发因 USE 错误关掉正听着的 Socket,U12 同源事故)
|
||||
* 4. 去抖分级(100ms/轮;E2:LISTEN 可见 100% <=10ms 无长尾):②=1 轮
|
||||
* (幽灵甄别挡拆链窗假 ESTABLISHED);③④⑤=2 轮(2026-09-23 实证:
|
||||
* (幽灵甄别挡拆链窗的旧连接 ESTABLISHED);③④⑤=2 轮(2026-09-23 实证:
|
||||
* 无去抖时监听被反复重建直至放弃);①=4 轮(**勿改小**:F-NET-17 M3
|
||||
* 可疑电平让位再入,400ms 让位窗=在途投递不可毁的最后租约,E9a 实证)
|
||||
* 5. 动手后冷却 NET_RECONCILE_COOLDOWN_MS 防风暴(U11 被动重同步不占)
|
||||
@@ -727,7 +693,8 @@ static void net_reconcile_tick(void) {
|
||||
} 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,
|
||||
* N-30(TRAP-NET-20)幽灵态甄别:拆链收尾窗口内旧连接未拆净,状态如实
|
||||
* 显示 ESTABLISHED(E15:bit3=1 保留现场契约 + TCP 拆链状态机,非谎报),
|
||||
* 回读对端端口 = 上一条连接端口 → 不补 accept(补了会把数据发给
|
||||
* 已死的旧连接 = 生产 LIST#4 数据被吞的直接成因),等拆净或新 SYN。
|
||||
* 这是本 tick 唯一的按需 SPI(仅形态②命中时发) */
|
||||
@@ -825,7 +792,7 @@ static void net_reconcile_tick(void) {
|
||||
|
||||
/*
|
||||
* 函数功能:netTask 主循环核心 —— 自愈 tick 宿主 + 消息队列分派器。每轮
|
||||
* 先跑三个非阻塞自愈 tick(D10 重监听 / L3 挂起 recv /
|
||||
* 先跑三个非阻塞自愈 tick(D10 重监听 / 挂起 recv /
|
||||
* TRAP-NET-17 状态对账),再非阻塞排空队列按 msg.type 分派到 _locked 实现。
|
||||
* 入口参数:无
|
||||
* 出口参数:无
|
||||
@@ -835,9 +802,9 @@ static void net_reconcile_tick(void) {
|
||||
* 由 net_task.c 在 net_stack_bring_up 之后每次 1ms 循环调用。
|
||||
* 函数说明:1. 四个自愈 tick 均非阻塞(绝不 osDelay 忙等,netTask 永不冻结),先于消息分派执行
|
||||
* 2. net_listener_retry_tick(D10):断连后按 30ms(NET_RELISTEN_RETRY_MS)
|
||||
* 节拍重试 open+listen,2s 总时限到期放弃留痕;本 fd 有在途挂起 recv 时跳过(L3 保险)
|
||||
* 3. net_pending_tick(L3):按节拍了结挂起 recv(数据/到点[仅显式预算]/断连);
|
||||
* L3 tripwire(自愈收敛阶段 1.5):挂起超 60s 只 DBG_ERROR 留痕一次,不再强制了结/伪造返回值
|
||||
* 节拍重试 open+listen,2s 总时限到期放弃留痕;本 fd 有在途挂起 recv 时跳过(挂起 recv 保险)
|
||||
* 3. net_pending_tick:按节拍结束挂起 recv(数据/到点[仅显式预算]/断连);
|
||||
* 挂起超时留痕探针(编译期选项,默认关):挂起超阈值只 DBG_ERROR 留痕一次,绝不强制结束/伪造返回值
|
||||
* 4. net_reconcile_tick(TRAP-NET-17 + pump 合并,自愈收敛阶段 3):每 100ms 对账
|
||||
* ①②⑤ + ③④ 重建 + U11 重同步,判据读驱动状态缓存(net_poll 每轮
|
||||
* 全量刷新,E2 背书);去抖分级(②=1 轮/③④⑤=2 轮/①=4 轮,M3 让位窗)
|
||||
@@ -858,8 +825,8 @@ void net_process_messages(void) {
|
||||
/* 三个非阻塞自愈 tick 置于消息处理之前:断连后即使无应用消息,监听器也能自主恢复 */
|
||||
/* D10 重监听重试(TRAP-NET-09):沉降后重开芯片监听器 */
|
||||
net_listener_retry_tick();
|
||||
/* L3 挂起 recv 完成检查:数据到达 / 到点 / 断连时通知调用方 */
|
||||
net_pending_tick();
|
||||
/* 挂起 recv 完成检查:数据到达 / 到点 / 断连时通知调用方 */
|
||||
net_pending_tick(s_net_socks);
|
||||
/* TRAP-NET-17 状态对账 + 监听器 pump(自愈收敛阶段 3 合并单一 100ms tick):
|
||||
* 判据读驱动缓存(net_poll 每轮全量刷新),①②⑤ 失同步 + ③④ 重建 +
|
||||
* U11 重同步全部在此(本层唯一周期重建入口,N-32 P2 单一所有者) */
|
||||
@@ -917,7 +884,7 @@ void net_process_messages(void) {
|
||||
net_set_errno(NET_ERR_BADF);
|
||||
msg.result = -1;
|
||||
} else {
|
||||
/* 先了结本 fd 挂起 recv(排空或取消,必通知),再关芯片侧 Socket;
|
||||
/* 先结束本 fd 挂起 recv(排空或取消,必通知),再关芯片侧 Socket;
|
||||
* 单连接模型监听 fd 断开后自动恢复监听,应用不得对它在 EOF 时 close */
|
||||
msg.result = net_close_locked(p_sock);
|
||||
}
|
||||
@@ -1105,7 +1072,7 @@ int net_socket(int domain, int type) {
|
||||
p_sock->session_dead = 0;
|
||||
/* 新 fd 不得继承上一次的待重试状态(D10) */
|
||||
net_listener_clear(sockfd);
|
||||
/* 新 fd 不得继承上一次挂起的 recv 请求(L3) */
|
||||
/* 新 fd 不得继承上一次挂起的 recv 请求 */
|
||||
net_pending_clear(sockfd);
|
||||
|
||||
/* 返回描述符(控制块已就绪) */
|
||||
@@ -1334,7 +1301,7 @@ int net_listen_locked(int sockfd) {
|
||||
* 可补回,见 TRAP-NET-08)。
|
||||
* 全局寄存器交 net_poll 集中读清(AGENTS 模块约定:所有 CH395F 中断经
|
||||
* net_poll 集中读取并清除);本 Socket 残留的全局位被 net_poll 读到时
|
||||
* SINT 已为 0,无害了结。TRAP-NET-12/-18 指定的两级预清落在
|
||||
* SINT 已为 0,自然收尾。TRAP-NET-12/-18 指定的两级预清落在
|
||||
* net_connect_locked / net_close_locked / relisten 隔离段,均为单 Socket,
|
||||
* 本次未动。 */
|
||||
(void)ch395f_get_sock_int_status((uint8_t)sockfd);
|
||||
@@ -1431,7 +1398,8 @@ int net_listen(int sockfd) {
|
||||
* 永远等不到 → 数据被吞 / 425。固定沉降延时、Socket 轮换、listen 后 close 重试、
|
||||
* 缓冲重分配均已被实验证伪;唯一有效 = "中断 + 状态轮询"双信源(修复组 15/15
|
||||
* 全过,机制触发 5 次全部救回)。→ CH395F_Trap_Records.md TRAP-NET-20
|
||||
* 幽灵态甄别:拆链窗口内芯片也会直接假报 ESTABLISHED(旧连接未拆净),此时回读
|
||||
* 幽灵态甄别:拆链窗口内旧连接未拆净,状态如实为 ESTABLISHED(E15:bit3=1 保留现场
|
||||
* 契约,非谎报),此时回读
|
||||
* 对端端口 = 上一条连接端口;新连接临时端口几乎必然不同 → 端口相同不 accept,
|
||||
* 等旧连接拆净 / 新 SYN 到达(新连接若事件也未收到,后续轮询以新端口命中)。 */
|
||||
|
||||
@@ -1549,7 +1517,7 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen
|
||||
/* 当前无连接 → WOULDBLOCK */
|
||||
return NET_ERR_RETURN_1(NET_ERR_WOULDBLOCK);
|
||||
}
|
||||
/* drain_pending:落入下方 accept 返回(对端簿记仍在控制块,排空后 EOF 了结) */
|
||||
/* drain_pending:落入下方 accept 返回(对端簿记仍在控制块,排空后以 EOF 收尾) */
|
||||
}
|
||||
|
||||
/* 填充对端地址(若调用方需要) */
|
||||
@@ -1720,7 +1688,7 @@ int net_can_read(int sockfd) {
|
||||
if (p_sock == NULL) {
|
||||
return 1;
|
||||
}
|
||||
/* 连接已非 ESTABLISHED(CLOSED/LISTENING/…):需要一次 recv 了结 EOF/错误 */
|
||||
/* 连接已非 ESTABLISHED(CLOSED/LISTENING/…):需要发一次 recv 取回 EOF/错误 */
|
||||
if (p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
|
||||
return 1;
|
||||
}
|
||||
@@ -2033,8 +2001,8 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
|
||||
p_sock->send_bytes += (uint32_t)chunk;
|
||||
|
||||
#if defined(TEST_LATENCY_PROBE)
|
||||
/* L3 探针起点:发送缓冲写入刚完成
|
||||
* (样本 = 写缓冲 → SENBUF_FREE 等待,见 链路延迟测试说明 §3 L3) */
|
||||
/* 延迟探针起点(chunk_wait):发送缓冲写入刚完成
|
||||
* (样本 = 写缓冲 → SENBUF_FREE 等待,见 链路延迟测试说明 §3) */
|
||||
uint32_t c_l3 = lat_cycles_now();
|
||||
#endif
|
||||
|
||||
@@ -2086,12 +2054,12 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
|
||||
* 返回值:实际接收字节数(> 0);0 表示对端关闭;-1 表示出错/非阻塞无数据 int
|
||||
* NET_PENDING_REGISTERED 表示已登记挂起请求(分派据此跳过即时通知)
|
||||
* 限定条件:p_sock 非空;netTask 上下文
|
||||
* 函数说明:1. 数据已在(快速路径)→ 立即读出返回,行为与旧实现一致
|
||||
* 函数说明:1. 数据已在(快速路径)→ 立即读出返回
|
||||
* 2. 非阻塞(NET_MSG_DONTWAIT 单次覆盖 **或** socket blocking 属性为 0,
|
||||
* 自愈收敛阶段 1.5 模式属性)无数据立即返回 -1(NET_ERR_WOULDBLOCK)
|
||||
* 3. 无数据 + 阻塞等待 → 登记挂起请求(L3 pending 化,E-02 结构演进)
|
||||
* 3. 无数据 + 阻塞等待 → 登记挂起请求(pending 化,E-02 结构演进)
|
||||
* 4. netTask 零占用立即返回;完成条件(数据/到点[仅显式预算]/断连)由 net_pending_tick 按主循环节拍检查
|
||||
* 5. 并通知 caller;关闭/网络重建/陈旧重同步由对应路径显式了结(取消必通知)
|
||||
* 5. 并通知 caller;关闭/网络重建/陈旧重同步由对应路径显式结束(取消必通知)
|
||||
* 6. CLOSED 状态语义(排空 + EOF + 自动重监听)逐项保留在 tick 内
|
||||
*/
|
||||
static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
|
||||
@@ -2165,380 +2133,10 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
|
||||
/* 无数据 + 阻塞等待:登记挂起请求,立即返回(netTask 任何时刻都不被占用)。
|
||||
* 调用方在自己上下文睡任务通知(LwIP netconn 模型);等待期间 netTask
|
||||
* 继续主循环(事件泵 + 队列),其他消费者零头阻塞。 */
|
||||
/* 登记挂起请求(L3):返回值即登记结果 */
|
||||
/* 登记挂起请求:返回值即登记结果 */
|
||||
return net_pending_register(sockfd, buf, len, to_ms, caller);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:关闭 Socket(内部锁定版本,必须在 netTask 上下文调用)
|
||||
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
|
||||
* 出口参数:无
|
||||
* 返回值:0 - 关闭成功 int
|
||||
* 限定条件:p_sock 非空;netTask 上下文
|
||||
* 函数说明:1. 调用 ch395f_close_socket 关闭 CH395F 硬件 Socket
|
||||
* 2. 清空整个控制块(memset 0),释放该 Socket 资源
|
||||
*/
|
||||
|
||||
/* ===== L3 挂起 recv 请求的实现(一期,2026-09-22;自愈收敛阶段 1.5 语义主流化) =====
|
||||
* 完成条件按 ①数据 ②到点(仅显式预算) ③断连 顺序检查(逐项对齐旧自旋循环语义)
|
||||
* ① 数据通道(ESTABLISHED/UDP_OPEN)内 readable 门控 SPI 读——常见无数据路径零额外 SPI 事务
|
||||
* (INT# 电平触发链保证缓冲有数据时每轮重读 SINT 重位置位)
|
||||
* ② 到点(仅当登记时带显式预算 net_recv_to;无限等待登记无 deadline)→ NET_ERR_TIMEDOUT
|
||||
* ——这是调用方策略的正常兑现,不是伪造
|
||||
* ③ CLOSED:先排空(bit3=1 芯片断连后残数据留缓冲待 MCU 读——TRAP-NET-20 排空契约;
|
||||
* 旧注归"部分读归零"机理、已随复核注降级)→ 有数据按数据完成,否则 EOF(0) + 自动重监听
|
||||
* 其他状态(CONNECTING/LISTENING/…)保持等待状态迁移(CONNECT→ESTABLISHED 是合法路径;
|
||||
* 陈旧 resync 由显式取消处理)——不做状态推断取消,避免误杀正在建链的等待
|
||||
* tripwire(自愈收敛阶段 1.5 降级):挂起超 60s = "完成逻辑缺位"探针 → 只 [ERR] 留痕一次,
|
||||
* **绝不强制了结、绝不伪造 TIMEDOUT 返回值**(唤醒责任在 ①②③ 与显式 cancel 路径)
|
||||
*/
|
||||
|
||||
/*
|
||||
* 函数功能:通知调用方并清除本 fd 的挂起请求(所有完成/取消路径的公共出口)
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* result - 32 位结果原值(>0 字节数 / 0 EOF / 负错误码) int
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:netTask 上下文;sockfd 合法且挂起在位(否则静默返回,防重复通知)
|
||||
* 函数说明:通知值 = 结果本身(与消息分派同一回传通道,调用方 net_recv/net_recv_to
|
||||
* 的映射逻辑不变);失败结果同步 errno 保持 netTask 上下文一致性
|
||||
*/
|
||||
static void net_pending_complete(int sockfd, int result) {
|
||||
/* 本 fd 挂起请求指针 */
|
||||
net_pending_t *p;
|
||||
|
||||
/* 描述符越界:静默返回(防重复通知) */
|
||||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||||
return;
|
||||
}
|
||||
/* 取挂起请求 */
|
||||
p = &s_net_pending[sockfd];
|
||||
/* 无在位挂起(已了结/已取消):静默返回 */
|
||||
if (p->active == 0U) {
|
||||
return;
|
||||
}
|
||||
/* 失败结果:记入 netTask 自己的错误码槽位(E-03;不再写入跨任务共享全局) */
|
||||
if (result < 0) {
|
||||
(void)net_set_errno(result);
|
||||
}
|
||||
/* 覆盖式写入通知值 = 结果本身(32 位原值) */
|
||||
xTaskNotify(p->caller, (uint32_t)result, eSetValueWithOverwrite);
|
||||
/* 清除挂起状态 */
|
||||
p->active = 0U;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:登记一个挂起 recv 请求(net_recv_locked 的阻塞无数据路径)
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* buf - 调用方接收缓冲(须保持有效至完成) void *
|
||||
* len - 缓冲容量 int
|
||||
* to_ms - 本次等待预算(0 = 无限等待,自愈收敛阶段 1.5) uint32_t
|
||||
* caller - 调用方任务句柄(完成通知目标) TaskHandle_t
|
||||
* 出口参数:无
|
||||
* 返回值:NET_PENDING_REGISTERED - 登记成功(分派跳过即时通知)
|
||||
* -1 - 拒绝(同 fd 已有挂起 / 执行任务自身发起)
|
||||
* 限定条件:netTask 上下文
|
||||
* 函数说明:每 fd 至多一个挂起(禁止覆盖——覆盖丢失前调用方通知 = 该任务永久挂死);
|
||||
* 执行任务自身发起的阻塞等待无他人可完成(tick 跑在执行任务内)→ 自等自
|
||||
* 通知 = 死锁,必须拒绝(netTask 上下文调用方须用 DONTWAIT)
|
||||
*/
|
||||
static int net_pending_register(int sockfd, void *buf, int len,
|
||||
uint32_t to_ms, TaskHandle_t caller) {
|
||||
/* 挂起请求指针 */
|
||||
net_pending_t *p;
|
||||
/* 登记时刻(tick) */
|
||||
uint32_t now;
|
||||
|
||||
/* 描述符越界 → BADF */
|
||||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||||
return NET_ERR_RETURN_1(NET_ERR_BADF);
|
||||
}
|
||||
/* 取挂起请求 */
|
||||
p = &s_net_pending[sockfd];
|
||||
|
||||
/* 同 fd 已有挂起:拒绝(覆盖将丢失前通知 = 该任务永久挂死) */
|
||||
if (p->active != 0U) {
|
||||
return NET_ERR_RETURN_1(NET_ERR_BUSY);
|
||||
}
|
||||
/* netTask 自身发起:无他人可完成(tick 跑在 netTask 内)→ 拒绝(自等死锁) */
|
||||
if ((caller == NULL) || (caller == s_net_task_handle)) {
|
||||
return NET_ERR_RETURN_1(NET_ERR_BUSY);
|
||||
}
|
||||
|
||||
/* 当前 tick(截止/tripwire 基准) */
|
||||
now = HAL_GetTick();
|
||||
/* 标记挂起在位 */
|
||||
p->active = 1U;
|
||||
/* 预算形态:0 = 无限等待(无 deadline,tick ② 跳过;tripwire 以登记时刻计) */
|
||||
p->infinite = (to_ms == 0U) ? 1U : 0U;
|
||||
/* tripwire 留痕标记清零(每轮挂起只留痕一条) */
|
||||
p->tripwire_logged = 0U;
|
||||
/* 调用方接收缓冲(须保持有效至完成) */
|
||||
p->buf = buf;
|
||||
/* 缓冲容量 */
|
||||
p->len = len;
|
||||
/* 截止时刻 = 登记时刻 + 预算(无限等待时不启用,置 0 仅为可观测性) */
|
||||
p->deadline = (to_ms == 0U) ? 0U : (now + to_ms);
|
||||
/* 登记时刻(tripwire 基准) */
|
||||
p->registered = now;
|
||||
/* 完成通知目标 */
|
||||
p->caller = caller;
|
||||
/* 登记成功:分派跳过即时通知 */
|
||||
return NET_PENDING_REGISTERED;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:挂起 recv 完成检查(netTask 每轮 net_process_messages 调用一次,非阻塞)
|
||||
* 入口参数:无
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:netTask 上下文
|
||||
* 函数说明:1. 逐 fd 检查完成条件:数据到达 / 到点(仅显式预算)/ 断连
|
||||
* 2. 完成即经 net_pending_complete 通知并清除
|
||||
* 3. tripwire(自愈收敛阶段 1.5):挂起超 NET_RECV_TRIPWIRE_MS 只 [ERR] 留痕
|
||||
* 一次("完成逻辑缺位"探针),绝不强制了结、绝不伪造返回值
|
||||
*/
|
||||
static void net_pending_tick(void) {
|
||||
/* 当前 tick */
|
||||
uint32_t now = HAL_GetTick();
|
||||
/* Socket 循环索引 */
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NET_MAX_SOCKETS; i++) {
|
||||
/* 本 fd 挂起请求 */
|
||||
net_pending_t *p = &s_net_pending[i];
|
||||
/* 本 fd 控制块 */
|
||||
net_sock_t *p_sock = &s_net_socks[i];
|
||||
|
||||
/* 无挂起请求:跳过 */
|
||||
if (p->active == 0U) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* fd 已关闭 */
|
||||
if (p_sock->in_use == 0U) {
|
||||
/* fd 已关闭(正常路径在 close 内先了结;此处兜底,防任何路径漏结) */
|
||||
net_pending_complete(i, NET_ERR_CONNRESET);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ① 数据到达(数据优先,与旧自旋一致) */
|
||||
if ((p_sock->state == NET_SOCK_STATE_ESTABLISHED) ||
|
||||
(p_sock->state == NET_SOCK_STATE_UDP_OPEN)) {
|
||||
/* 可读标志置位 */
|
||||
if (p_sock->readable != 0U) {
|
||||
/* 查询芯片侧可用字节数 */
|
||||
int avail = ch395f_get_recv_len((uint8_t)i);
|
||||
/* 有数据 */
|
||||
if (avail > 0) {
|
||||
/* 本次读取量 = min(可用, 缓冲容量) */
|
||||
int rd = (avail > p->len) ? p->len : avail;
|
||||
/* 读入调用方缓冲区 */
|
||||
ch395f_read_recv_buf((uint8_t)i, (uint8_t *)p->buf, (uint16_t)rd);
|
||||
/* 累计接收字节统计 */
|
||||
p_sock->recv_bytes += (uint32_t)rd;
|
||||
/* 数据已消费,清可读标志 */
|
||||
p_sock->readable = 0U;
|
||||
/* 了结:以字节数通知 */
|
||||
net_pending_complete(i, rd);
|
||||
continue;
|
||||
}
|
||||
/* 虚假标志:缓冲无数据,继续等待 */
|
||||
p_sock->readable = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
/* ② 到点(仅显式预算登记;无限等待登记无 deadline——自愈收敛阶段 1.5:
|
||||
* 超时是调用方策略,net_recv_to 的预算在此兑现,不是伪造。
|
||||
* tick 差值按有符号比较,抗回绕) */
|
||||
if ((p->infinite == 0U) && ((int32_t)(now - p->deadline) >= 0)) {
|
||||
/* 了结:超时(错误码由 net_pending_complete 记入本任务槽位) */
|
||||
net_pending_complete(i, NET_ERR_TIMEDOUT);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ③ 断连:排空 → 数据或 EOF(0) + 自动重监听。
|
||||
* 判据同 net_recv_locked(N-32 P1 不变量 I):`state == CLOSED ||
|
||||
* session_dead`。只判 CLOSED 时,抢先 relisten 后 ①(要求 ESTABLISHED)
|
||||
* 与 ③(要求 CLOSED) 两头落空 → 挂起的 recv 只能干等 ② deadline 超时
|
||||
* (lftpd 阻塞 recv 的"消费者卡死"原型)。自愈收敛阶段 1.5 后该形态更致命:
|
||||
* 无限等待登记没有 ②,漏 ③ = 永久挂死(只剩 tripwire 留痕)——
|
||||
* ③ 与显式 cancel 是无限 recv 的全部唤醒责任方,勿动判据。 */
|
||||
if ((p_sock->state == NET_SOCK_STATE_CLOSED) || (p_sock->session_dead != 0U)) {
|
||||
/* 查询残留字节数 */
|
||||
int rem = ch395f_get_recv_len((uint8_t)i);
|
||||
/* 有残留数据:按数据完成 */
|
||||
if (rem > 0) {
|
||||
/* 本次读取量 = min(残留, 缓冲容量) */
|
||||
int rd = (rem > p->len) ? p->len : rem;
|
||||
/* 读入调用方缓冲区 */
|
||||
ch395f_read_recv_buf((uint8_t)i, (uint8_t *)p->buf, (uint16_t)rd);
|
||||
/* 累计接收字节统计 */
|
||||
p_sock->recv_bytes += (uint32_t)rd;
|
||||
/* 数据已消费,清可读标志 */
|
||||
p_sock->readable = 0U;
|
||||
/* 了结:以字节数通知 */
|
||||
net_pending_complete(i, rd);
|
||||
} else {
|
||||
/* 启用自动重监听:先把监听器重新置回 LISTEN。
|
||||
* ⚠ 内层 `state == CLOSED` 门同 net_recv_locked:session_dead
|
||||
* 但已回 LISTENING 时重建已由别处完成,再 open+listen 会因
|
||||
* 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 重试轮询接管(N-32 §10:入口收敛到 net_listener_try) */
|
||||
(void)net_listener_try(i);
|
||||
}
|
||||
/* TRAP-NET-18 待办 #3:异常释放(TIMEOUT/RST)不是正常 EOF(对端未发正常 FIN)
|
||||
* → 以连接重置了结:应用(lftpd STOR)对异常结束回 5xx 而非误报 226;
|
||||
* 正常 FIN(DISCONNECT)仍回 0(在途 FIN 与正常完成不可区分——协议边界,见 TRAP-NET-18 记录) */
|
||||
/* 异常释放:以连接重置了结(回 5xx,不误报 226) */
|
||||
if (p_sock->released_abnormal != 0U) {
|
||||
/* 异常释放(错误码由 net_pending_complete 记入本任务槽位) */
|
||||
net_pending_complete(i, NET_ERR_CONNRESET);
|
||||
} else {
|
||||
/* 正常 EOF:以 0 了结 */
|
||||
net_pending_complete(i, 0);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 其他状态:保持等待(见本区块头注释) */
|
||||
|
||||
/* ④ tripwire(自愈收敛阶段 1.5 降级,原"看门狗强制了结"):挂起超 60s =
|
||||
* "完成逻辑缺位"探针 → 只 [ERR] 留痕一次,**绝不强制了结、绝不伪造
|
||||
* TIMEDOUT 返回值**(旧实现到点 +1s 兜底伪造 TIMEDOUT,掩盖完成路径
|
||||
* 缺陷;新语义下无限等待本就无预算,伪造返回值反而违背调用方意图)。
|
||||
* 判据:无限登记按登记时刻计;显式登记按 deadline 计(② 正常不会让它
|
||||
* 越过 60s,能到这里本身就是完成逻辑缺位)。 */
|
||||
if (p->tripwire_logged == 0U) {
|
||||
/* 探针基准时刻:无限 = 登记时刻;显式 = 截止时刻 */
|
||||
uint32_t base = (p->infinite != 0U) ? p->registered : p->deadline;
|
||||
if ((int32_t)(now - base) >= (int32_t)NET_RECV_TRIPWIRE_MS) {
|
||||
/* 留痕(挂起已持续毫秒数,自基准起算) */
|
||||
DBG_ERROR("sock%d recv pending %ums (base=%s): completion source missing",
|
||||
i, (unsigned)((int32_t)now - (int32_t)base),
|
||||
(p->infinite != 0U) ? "infinite" : "deadline");
|
||||
/* 只留痕一次(每轮挂起一条,防日志风暴) */
|
||||
p->tripwire_logged = 1U;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:查询本 fd 是否有在途挂起 recv(L3 pending)—— 宿主能力,供 net_listener 用
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* 出口参数:无
|
||||
* 返回值:1 - 有挂起;0 - 无(含描述符越界) int
|
||||
* 限定条件:任意上下文(纯 RAM 读,不发起 RPC)
|
||||
* 函数说明:1. N-32 §10 模块化:net_listener_retry_tick 用它跳过"重开 Socket
|
||||
* 会摧毁在途连接"的 fd(L3 保险,原为直接读 s_net_pending[i].active)
|
||||
* 2. 声明在 net_listener.h(本模块提供、监听器模块消费)
|
||||
*/
|
||||
int net_pending_active(int sockfd) {
|
||||
/* 描述符越界:视为无挂起 */
|
||||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||||
return 0;
|
||||
}
|
||||
return (s_net_pending[sockfd].active != 0U) ? 1 : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:清除本 fd 的挂起请求(不通知;用于"新 fd 不得继承"与 close 后的幂等清理)
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:无
|
||||
* 函数说明:1. 静默清理不发通知:close 路径先经 settle 通知,本调用仅保证状态归零(幂等)
|
||||
* 2. 新建 fd 时调用,防复用槽位继承上一条连接的挂起请求
|
||||
*/
|
||||
static void net_pending_clear(int sockfd) {
|
||||
/* 描述符越界:直接返回 */
|
||||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||||
return;
|
||||
}
|
||||
/* 挂起状态清零(静默,不发通知) */
|
||||
s_net_pending[sockfd].active = 0U;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取消本 fd 的挂起请求并通知调用方(显式路径:陈旧 resync 等)
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* err - 通知的错误码(如 NET_ERR_CONNRESET) int
|
||||
* 出口参数:无
|
||||
* 返回值:无(无在位挂起时静默返回)
|
||||
* 限定条件:netTask 上下文
|
||||
* 函数说明:经 net_pending_complete 以指定错误码通知调用方(取消必通知,防挂死)
|
||||
*/
|
||||
static void net_pending_cancel(int sockfd, int err) {
|
||||
/* complete 内部校验范围与在位(未在位则静默返回) */
|
||||
net_pending_complete(sockfd, err);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:取消全部挂起请求并逐一通知(网络重建路径)
|
||||
* 入口参数:err - 通知的错误码 int
|
||||
* 出口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:netTask 上下文;须在控制块清零与芯片复位之前调用
|
||||
* 函数说明:网络重建(net_stack_bring_up)前调用,保证所有在位调用方均被唤醒
|
||||
*/
|
||||
static void net_pending_cancel_all(int err) {
|
||||
/* 逐 fd 遍历 */
|
||||
for (int i = 0; i < NET_MAX_SOCKETS; i++) {
|
||||
/* 取消并通知(未在位则静默跳过) */
|
||||
net_pending_complete(i, err);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:关闭前了结本 fd 的挂起 recv(per-fd 串行:先完成、再关闭)
|
||||
* 入口参数:p_sock - 将被关闭的 Socket 控制块指针 net_sock_t *
|
||||
* 出口参数:无
|
||||
* 返回值:无(无在位挂起时静默返回)
|
||||
* 限定条件:netTask 上下文;须在 ch395f_close_socket 之前调用
|
||||
* 函数说明:先排空尝试——关闭不丢已到达数据(芯片缓冲在 close 前可读);
|
||||
* 有数据按字节数通知,否则取消(NET_ERR_CONNRESET)
|
||||
*/
|
||||
static void net_pending_settle_close(net_sock_t *p_sock) {
|
||||
/* 由控制块指针反推描述符 */
|
||||
int sockfd = (int)(p_sock - s_net_socks);
|
||||
/* 通知用结果值(默认为取消) */
|
||||
int val;
|
||||
/* 芯片 RX 可用字节数 */
|
||||
int avail;
|
||||
|
||||
/* 无在位挂起:静默返回 */
|
||||
if (s_net_pending[sockfd].active == 0U) {
|
||||
return;
|
||||
}
|
||||
/* 默认结果 = 取消(CONNRESET) */
|
||||
val = NET_ERR_CONNRESET;
|
||||
/* 查询芯片 RX 可用字节(缓冲内数据在 close 前可读) */
|
||||
avail = ch395f_get_recv_len((uint8_t)sockfd);
|
||||
/* 数据已到达:排空,关闭不丢数据 */
|
||||
if (avail > 0) {
|
||||
/* 本次读取量 = min(可用, 缓冲容量) */
|
||||
int rd = (avail > s_net_pending[sockfd].len) ? s_net_pending[sockfd].len : avail;
|
||||
/* 读入调用方缓冲区 */
|
||||
ch395f_read_recv_buf((uint8_t)sockfd, (uint8_t *)s_net_pending[sockfd].buf, (uint16_t)rd);
|
||||
/* 累计接收字节统计 */
|
||||
p_sock->recv_bytes += (uint32_t)rd;
|
||||
/* 数据已消费,清可读标志 */
|
||||
p_sock->readable = 0U;
|
||||
/* 有数据:以字节数通知 */
|
||||
val = rd;
|
||||
}
|
||||
/* 了结(必通知) */
|
||||
net_pending_complete(sockfd, val);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 函数功能:关闭 Socket(内部版本,不经过消息队列)
|
||||
* 入口参数:p_sock - 要关闭的 Socket 控制块指针 net_sock_t *
|
||||
@@ -2559,9 +2157,9 @@ static int net_close_locked(net_sock_t *p_sock) {
|
||||
/* 由控制块指针反推描述符 */
|
||||
int sockfd = (int)(p_sock - s_net_socks);
|
||||
|
||||
/* L3:先了结本 fd 的挂起 recv(排空/取消 + 必通知),再关芯片 Socket
|
||||
/* 先结束本 fd 的挂起 recv(排空/取消 + 必通知),再关芯片 Socket
|
||||
* ——关闭不得让等待中的调用方永久悬挂 */
|
||||
net_pending_settle_close(p_sock);
|
||||
net_pending_settle_close((int)(p_sock - s_net_socks), p_sock);
|
||||
|
||||
/* 排渣 + 关闭 + 等 CLOSED 一体(见函数头注释 1);
|
||||
* 已 CLOSED 的 fd 只清本 Socket 残留事件(防下一连接误判"刚建立就断开") */
|
||||
@@ -2579,7 +2177,7 @@ static int net_close_locked(net_sock_t *p_sock) {
|
||||
p_sock->last_peer_port = keep_peer;
|
||||
/* 关闭即取消该 fd 的待重试(D10 重试队列) */
|
||||
net_listener_clear(sockfd);
|
||||
/* 关闭即清除该 fd 的挂起状态(L3;幂等) */
|
||||
/* 关闭即清除该 fd 的挂起状态(幂等) */
|
||||
net_pending_clear(sockfd);
|
||||
|
||||
/* 关闭成功 */
|
||||
@@ -2602,14 +2200,14 @@ static int net_close_locked(net_sock_t *p_sock) {
|
||||
* (芯片已 CLOSED 直接 open 必成,E4 定案;失败交 D10 重试 +
|
||||
* reconciler 形态④);非 auto_relisten(client fd):收口 CLOSED,
|
||||
* 是否重连由调用方决定
|
||||
* 4. 挂起 recv 先了结(settle_close:排空尝试或取消,必通知)
|
||||
* 4. 挂起 recv 先结束(settle_close:排空尝试或取消,必通知)
|
||||
*/
|
||||
static int net_drop_conn_locked(net_sock_t *p_sock) {
|
||||
/* 由控制块指针反推描述符 */
|
||||
int sockfd = (int)(p_sock - s_net_socks);
|
||||
|
||||
/* L3:先了结本 fd 挂起 recv(排空/取消 + 必通知),再拆芯片会话 */
|
||||
net_pending_settle_close(p_sock);
|
||||
/* 先结束本 fd 挂起 recv(排空/取消 + 必通知),再拆芯片会话 */
|
||||
net_pending_settle_close((int)(p_sock - s_net_socks), p_sock);
|
||||
|
||||
/* 确定性拆除(异常释放 → QUARANTINE 400ms 底,TRAP-NET-18 语义) */
|
||||
if (net_sock_reset_sync(sockfd, CH395F_RESET_QUARANTINE) != 0) {
|
||||
@@ -3263,7 +2861,7 @@ int net_send_sock(net_sock_t *p_sock, const void *buf, int len) {
|
||||
* -1 - 接收失败 int
|
||||
* 限定条件:必须在 netTask 上下文中调用;p_sock->in_use == 1
|
||||
* 函数说明:1. 直接操作 Socket 控制块,不经过消息队列。避免 netTask 向自身发送消息造成死锁
|
||||
* 2. L3 后:netTask 上下文的阻塞等待会被挂起登记拒绝(自通知死锁)
|
||||
* 2. pending 化后:netTask 上下文的阻塞等待会被挂起登记拒绝(自通知死锁)
|
||||
* 3. 本函数阻塞用法仅对 DONTWAIT 有效
|
||||
*/
|
||||
int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags) {
|
||||
@@ -3669,9 +3267,8 @@ static void handle_connect_event(net_sock_t *p_sock) {
|
||||
* 限定条件: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)。旧注释写"CH395F 在
|
||||
* DISCONNECT 后自动关闭 Socket(FUN_PARA 默认)"与实际 0x08 相反,2026-10-12 修正:
|
||||
* 它会误导维护者跳过显式 close(= U12 的成因之一)。之后需重新 OPEN + LISTEN
|
||||
* 本层 ch395f_close_socket 关闭;芯片不代劳(§5.52② / §5.23),不得跳过
|
||||
* 显式 close(U12 成因之一)。之后需重新 OPEN + LISTEN
|
||||
* 2. 单连接:置 CLOSED,交由 net_recv_locked 排空剩余数据后再关
|
||||
* 3. 触发 NET_EVENT_DISCONNECTED 回调
|
||||
*/
|
||||
@@ -3771,7 +3368,7 @@ static void handle_disconnect_event(net_sock_t *p_sock) {
|
||||
net_listener_settle(qfd, NET_LISTENER_SETTLE_GRACEFUL);
|
||||
}
|
||||
}
|
||||
/* 正常释放(对端 FIN):断连了结回 EOF(0) */
|
||||
/* 正常释放(对端 FIN):断连时挂起项以 EOF(0) 结束 */
|
||||
p_sock->released_abnormal = 0U;
|
||||
/* N-32(P1 不变量 I):置"本会话已结束"电平。须在置 CLOSED **之前**,
|
||||
* 使 session_dead 覆盖"CLOSED 等待 recv 排空"整个窗口——排空期间
|
||||
@@ -3804,8 +3401,7 @@ static void handle_disconnect_event(net_sock_t *p_sock) {
|
||||
* 返回值:无
|
||||
* 限定条件:net_poll() 检测到 RECV_OK 中断后调用;netTask 上下文
|
||||
* 函数说明:1. 仅触发 NET_EVENT_DATA_RECEIVED 回调通知上层
|
||||
* 2. 不在此读取 recv_len:单一读取点归 net_recv_locked(省一次 SPI
|
||||
* 查询;旧"部分读归零"时代的"读取冲突"顾虑已随复核注降级,结构保留)
|
||||
* 2. 不在此读取 recv_len:单一读取点归 net_recv_locked(省一次 SPI 查询)
|
||||
*/
|
||||
static void handle_recv_event(net_sock_t *p_sock) {
|
||||
/* RECV_OK 中断已触发,直接通知上层。不在此读 recv_len,
|
||||
@@ -3865,8 +3461,8 @@ static void handle_timeout_event(net_sock_t *p_sock) {
|
||||
net_listener_settle(qfd, NET_LISTENER_SETTLE_QUARANTINE);
|
||||
}
|
||||
}
|
||||
/* TRAP-NET-18 待办 #3:标记异常释放——L3 挂起 recv 排空断连后以
|
||||
* NET_ERR_CONNRESET 了结(而非 EOF),应用(lftpd STOR)回报 5xx
|
||||
/* 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):异常释放同样登记对端端口(幽灵甄别基准) */
|
||||
|
||||
@@ -193,7 +193,7 @@ int net_listener_armed(int sockfd);
|
||||
* 函数功能:查询 Socket 是否"值得发一次 recv"(**纯 RAM 读,不发起 RPC**)
|
||||
* 入口参数:sockfd - Socket 描述符 int
|
||||
* 出口参数:无
|
||||
* 返回值:1 - 应发 recv(有数据可读,或连接已非 ESTABLISHED 需了结);0 - 不必发 int
|
||||
* 返回值:1 - 应发 recv(有数据可读,或连接已非 ESTABLISHED 需发 recv 取回 EOF/错误);0 - 不必发 int
|
||||
* 限定条件:net_init() 已调用;任意任务上下文
|
||||
* 函数说明:1. ESTABLISHED → 看 readable(net_poll 在 RECV_OK 置位、读空清零)
|
||||
* 2. **非 ESTABLISHED 一律返回 1**:连接已消失时必须让上层真的 recv 一次
|
||||
@@ -492,18 +492,6 @@ int net_set_errno(int err);
|
||||
* N-32 §10:原定义在 net_socket.c,随 net_set_errno 一并提升到本头文件 */
|
||||
#define NET_ERR_RETURN_1(err) (net_set_errno(err), -1)
|
||||
|
||||
/*
|
||||
* 函数功能:查询本 fd 是否有在途挂起 recv(L3 pending)—— 供 net_listener.c 消费
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* 出口参数:无
|
||||
* 返回值:1 - 有挂起;0 - 无(含描述符越界) int
|
||||
* 限定条件:任意上下文(纯 RAM 读,不发起 RPC)
|
||||
* 函数说明:1. N-32 §10 模块化:net_listener_retry_tick 用它跳过"重开 Socket
|
||||
* 会摧毁在途连接"的 fd(L3 保险,原为直接读 s_net_pending[i].active)
|
||||
* 2. 定义在 net_socket.c(配对头即本文件)
|
||||
*/
|
||||
int net_pending_active(int sockfd);
|
||||
|
||||
/*
|
||||
* 函数功能:获取 Socket 本地地址(IP + Port)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
|
||||
@@ -188,7 +188,7 @@ typedef struct {
|
||||
uint8_t blocking;
|
||||
|
||||
/* 释放来源标记(TRAP-NET-18 待办 #3):1 = 异常释放(芯片 TIMEOUT,对端 RST/掉链),
|
||||
* 0 = 正常释放(芯片 DISCONNECT,对端 FIN)或新连接。L3 挂起 recv 的了结断连
|
||||
* 0 = 正常释放(芯片 DISCONNECT,对端 FIN)或新连接。挂起 recv 结束时的断连
|
||||
* 分支据此决定回 EOF(0) 还是 NET_ERR_CONNRESET:异常结束上报应用(lftpd STOR)
|
||||
* 为错误 → 回 5xx 而非误报 226。由 handle_timeout_event / handle_disconnect_event
|
||||
* 置位,新连接建立时(net_socket / relisten 成功)清零——新连接不得继承上一条
|
||||
@@ -196,7 +196,8 @@ typedef struct {
|
||||
uint8_t released_abnormal;
|
||||
|
||||
/* N-30(2026-10-11 裸机芯片台架实证):本 Socket 上一条被释放连接的**对端端口**。
|
||||
* 芯片拆链收尾窗口内 Socket 会假报 ESTABLISHED(幽灵态),此时回读的对端端口
|
||||
* 芯片拆链收尾窗口内旧连接未拆净,Socket 如实为 ESTABLISHED(幽灵态,E15:
|
||||
* bit3=1 保留现场契约,非谎报),此时回读的对端端口
|
||||
* = 旧连接端口;新连接的临时端口几乎必然不同。accept 路径补 accept 与对账②
|
||||
* 均以此甄别幽灵态(见 net_socket.c TRAP-NET-20)。跨 fd 释放保留(幽灵属芯片
|
||||
* Socket 级,与 fd 复用无关),新连接断开时刷新。 */
|
||||
|
||||
Reference in New Issue
Block a user