L3 pending化一期:阻塞recv改挂起请求+主循环节拍完成(netTask零占用),lftpd/timeSync分片协议退役

This commit is contained in:
2026-09-22 16:29:31 +08:00
parent 4ce966dfb8
commit a45c0cb883
3 changed files with 372 additions and 109 deletions
+26 -26
View File
@@ -6,18 +6,17 @@
#include <string.h>
/*
* E-02 头阻塞修复(2026-09-22 M3 台架实证):
* E-02 头阻塞修复 + L3 pending 化(2026-09-22,M3 台架实证):
* netTask 是全部 socket 操作的唯一执行者;lftpd 原先用默认 30s 的**阻塞**
* net_recv 等待对端下一行/下一块数据 → 只要客户端连上却不发完整行,netTask
* 就被长期占用,其余任务(含远端显示)的网络操作整体冻结(实测一次空闲 FTP
* 登录让 LCDMEM 整帧等待 17.7s)。
* 修法:把等待切成 ≤LFTPD_NET_SLICE_MS 的片(每片 netTask 占用有界),
* 调用方累计总预算 LFTPD_TOTAL_BUDGET_MS —— **总超时语义与旧实现一致**,
* 但任何时刻都不再有"秒级冻结"。单片超时由 net_recv_to 返回 -2 区分(无 errno 竞态)。
* 分片长度即"其他任务最坏等待":实测 100ms 片下受压帧仍达 ~750ms(多片排队),
* 收到 5ms 后受压帧降到 ~10ms 量级(见 m3_data 修复后数据)。
* 2026-09-22 先落地分片有界读(5ms 片;实测 100ms 片下受压帧仍达 ~750ms,
* 收到 5ms 后降到 ~10ms 量级,见 m3_data 修复后数据),同日 L3 pending 化
* 后改为"挂起请求":每次等待 = 一次挂起登记(netTask 零占用,完成按主循环
* 节拍检查),调用方单次超时 = 剩余总预算(总等待 ≤ LFTPD_TOTAL_BUDGET_MS,
* **总超时语义与旧实现一致**),旧分片协议退役。
*/
#define LFTPD_NET_SLICE_MS 5U
#define LFTPD_TOTAL_BUDGET_MS 30000U
int lftpd_inet_listen(int port)
@@ -120,25 +119,23 @@ 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) {
/* 分片有界读(E-02):单片 netTask 占用 ≤ LFTPD_NET_SLICE_MS */
/* 单次预算 = 剩余总预算(L3 pending:等待期 netTask 零占用,调用方
* 在自己上下文睡任务通知,旧片间 vTaskDelay(1) 让出退役) */
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);
if (rem == 0U) {
return -1; /* 总预算耗尽(语义同旧 30s 超时) */
}
int read_len = net_recv_to(socket,
buffer + total_read_len,
(int)(buffer_len - total_read_len - 1),
LFTPD_NET_SLICE_MS);
rem);
if (read_len == 0) {
return -1; /* 对端关闭 */
}
if (read_len == -2) {
/* 本片无数据:累计总预算,未超则继续等。
* **必须显式让出 CPU**:若某片因 net_poll 自身耗时而在首轮就返回 -2,
* 本任务(Normal)立即重排队会形成忙循环 → 饿死 BelowNormal 的 uiTask
* → 远端显示请求数秒无人拾取(2026-09-22 M3 实测 2.4~3.4s 周期尖刺)。
* vTaskDelay(1) 保证低优先级任务每毫秒都有运行机会。 */
if ((xTaskGetTickCount() - t0) >= pdMS_TO_TICKS(LFTPD_TOTAL_BUDGET_MS)) {
return -1; /* 总预算耗尽(语义同旧 30s 超时) */
}
vTaskDelay(pdMS_TO_TICKS(1));
continue;
continue; /* 本次预算等满无数据 → 下轮 rem=0 退出 */
}
if (read_len < 0) {
return read_len; /* 真错误 */
@@ -200,22 +197,25 @@ int lftpd_inet_write(int socket, const void *buf, int len)
int lftpd_inet_read(int socket, void *buf, int len)
{
/* 分片有界读(E-02):总预算 30s 不变,但每片 netTask 占用 ≤ LFTPD_NET_SLICE_MS,
* 避免空闲数据连接(客户端开了 PASV 却不发数据)冻结全网 */
/* L3 pending 读:总预算 30s 不变,等待期 netTask 零占用
* (空闲数据连接不再冻结全网;旧分片协议随 L3 pending 化退役) */
TickType_t t0 = xTaskGetTickCount();
for (;;) {
int n = net_recv_to(socket, buf, len, LFTPD_NET_SLICE_MS);
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);
if (rem == 0U) {
return -1; /* 总预算耗尽(语义同旧 30s 超时) */
}
int n = net_recv_to(socket, buf, len, rem);
if (n >= 0) {
return n; /* >0 数据 / 0 EOF */
}
if (n == -1) {
return -1; /* 真错误 */
}
if ((xTaskGetTickCount() - t0) >= pdMS_TO_TICKS(LFTPD_TOTAL_BUDGET_MS)) {
return -1; /* 总预算耗尽(语义同旧 30s 超时) */
}
vTaskDelay(pdMS_TO_TICKS(1)); /* 让出 CPU:防空闲时忙循环饿死低优先级任务 */
/* n == -2:本次预算等满无数据 → 下轮 rem=0 退出 */
}
}
+324 -59
View File
@@ -68,7 +68,7 @@ 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 int net_send_locked(net_sock_t *p_sock, const void *buf, int len);
static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
uint32_t timeout_ms);
uint32_t timeout_ms, TaskHandle_t caller);
static int net_close_locked(net_sock_t *p_sock);
static int net_relisten_locked(net_sock_t *p_sock);
/* D10 非阻塞重监听:单次尝试 + 延迟重试(实现见 net_relisten_locked 之后) */
@@ -78,6 +78,37 @@ static void net_relisten_retry_tick(void);
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 错误(错误码经通知值回传)。
*/
#define NET_PENDING_REGISTERED 0x70000000 /* 内部哨兵:pending 已登记(分派跳过即时通知;取值不与字节数空间冲突) */
#define NET_PENDING_WATCHDOG_MS 1000U /* 看门狗:到点后 1s 仍未了结 = 完成逻辑缺位 → 强制了结 + 错误日志 */
typedef struct {
uint8_t active; /* 1 = 本 fd 有挂起 recv */
void *buf; /* 调用方缓冲(仅指针,须保持有效至完成) */
int len; /* 缓冲容量 */
uint32_t deadline; /* 截止时刻(tick)= 登记时刻 + 本次预算 */
uint32_t registered; /* 登记时刻(tick;看门狗/日志) */
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 *
@@ -112,10 +143,15 @@ 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);
/* 清零 Socket 控制块 */
memset(s_net_socks, 0, sizeof(s_net_socks));
s_net_errno = 0;
s_net_initialized = 0;
s_net_task_handle = xTaskGetCurrentTaskHandle(); /* 执行任务 = net_init 调用者(NET 层 = netTask / 硬件层 = testTask) */
/* 检查 CH395F 芯片 */
if (ch395f_check_exist() != CH395F_STATUS_OK) {
@@ -356,10 +392,14 @@ void net_process_messages(void) {
}
/* D10:待重试的自动重监听(Trap 09 沉降后重开),非阻塞、每轮最多一次尝试。
* 放在消息处理之前:断连后即使没有任何应用消息,监听器也能自主恢复。 */
* 放在消息处理之前:断连后即使没有任何应用消息,监听器也能自主恢复。
* L3:挂起 recv 的完成检查同位(两 tick 共享主循环节拍,均为 O(每 fd) 非阻塞)。 */
net_relisten_retry_tick();
net_pending_tick();
while (osMessageQueueGet(netMsgQueueHandle, &msg, NULL, 0) == osOK) {
int do_notify = 1;
switch (msg.type) {
case NET_MSG_SEND: {
net_sock_t *p_sock = net_get_sock(msg.sockfd);
@@ -380,8 +420,13 @@ void net_process_messages(void) {
s_net_errno = NET_ERR_BADF;
msg.result = -1;
} else {
/* L3:无数据 + 阻塞等待 = 登记挂起请求(netTask 零占用),
* 完成由 net_pending_tick 按主循环节拍通知调用方 */
msg.result = net_recv_locked(p_sock, msg.buf, msg.len, msg.flags,
msg.timeout_ms);
msg.timeout_ms, msg.caller);
if (msg.result == NET_PENDING_REGISTERED) {
do_notify = 0; /* 挂起已登记:不发即时回执,完成时才通知 */
}
}
break;
}
@@ -426,6 +471,7 @@ void net_process_messages(void) {
* close/重监听命令——会摧毁芯片侧仍健康的监听器 → 连接永拒
* (run8 实证 170s 拒绝)。新连接的 CONNECT 中断会把两侧重新同步。 */
p_sock->state = NET_SOCK_STATE_LISTENING;
net_pending_cancel(msg.sockfd, NET_ERR_CONNRESET); /* L3:死连接上的挂起 recv 立即取消(必通知) */
msg.result = 0;
}
break;
@@ -453,8 +499,9 @@ void net_process_messages(void) {
/* 结果回传:成功=字节数/描述符;失败=具体错误码(如 NET_ERR_WOULDBLOCK)。
* 注意消息体按值拷入队列——调用方看不到 netTask 对 msg 的修改,结果只能搭
* 通知值这趟车带回;**整个 32 位都是结果**(若压入序列号等高 16 位标签,
* 单次结果上限会被压到 32767,而 TEST_BUF_SIZE=32768 等调用点会溢出为负)。 */
{
* 单次结果上限会被压到 32767,而 TEST_BUF_SIZE=32768 等调用点会溢出为负)。
* L3:挂起 recv(do_notify=0)不发即时回执,由 net_pending_tick 完成时通知。 */
if (do_notify != 0) {
uint32_t notify_val = (uint32_t)msg.result;
if (msg.result < 0) {
@@ -533,6 +580,7 @@ int net_socket(int domain, int type) {
p_sock->send_ready = 1;
p_sock->readable = 0;
net_relisten_clear(sockfd); /* 新 fd 不得继承上一次的待重试状态 */
net_pending_clear(sockfd); /* 新 fd 不得继承上一次挂起的 recv 请求(L3) */
return sockfd;
}
@@ -1074,21 +1122,26 @@ static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
/*
* 函数功能:接收数据(内部锁定版本,必须在 netTask 上下文调用)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位,NET_MSG_DONTWAIT 表示非阻塞 int
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位,NET_MSG_DONTWAIT 表示非阻塞 int
* timeout_ms - 本次等待预算(0 = 默认 NET_RECV_TIMEOUT_MS) uint32_t
* caller - 调用方任务句柄(挂起请求的完成通知目标) TaskHandle_t
* 出口参数:buf - 填充接收到的数据
* 返回值:实际接收字节数(> 0);0 表示对端关闭;-1 表示出错/超时/非阻塞无数据 int
* 返回值:实际接收字节数(> 0);0 表示对端关闭;-1 表示出错/非阻塞无数据 int
* NET_PENDING_REGISTERED 表示已登记挂起请求(分派据此跳过即时通知)
* 限定条件:p_sock 非空;netTask 上下文
* 函数说明:1. 循环调用 net_poll() 检查接收长度,有数据即读出
* 2. 非阻塞模式下无数据立即返回 -1(NET_ERR_WOULDBLOCK)
* 3. CLOSED 状态尝试排空接收缓冲区残余数据(Trap 13:FIN 可能先于数据到达)
* 函数说明:1. 数据已在(快速路径)→ 立即读出返回,行为与旧实现一致
* 2. 非阻塞模式无数据立即返回 -1(NET_ERR_WOULDBLOCK)
* 3. 无数据 + 阻塞等待 → 登记挂起请求(L3 pending 化,E-02 结构演进):
* netTask 零占用立即返回;完成条件(数据/到点/断连)由
* net_pending_tick 按主循环节拍检查并通知 caller;
* 关闭/网络重建/陈旧重同步由对应路径显式了结(取消必通知)。
* CLOSED 状态语义(排空 + EOF + 自动重监听)逐项保留在 tick 内
*/
static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
uint32_t timeout_ms) {
uint32_t tick_start;
uint32_t to_ms;
uint32_t timeout_ms, TaskHandle_t caller) {
int sockfd = (int)(p_sock - s_net_socks);
if (buf == NULL || len <= 0) {
@@ -1096,58 +1149,37 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
return -1;
}
/* 超时来源:调用方指定(分片有界读,E-02 修复)优先,否则用默认值 */
to_ms = (timeout_ms != 0U) ? timeout_ms : (uint32_t)NET_RECV_TIMEOUT_MS;
tick_start = HAL_GetTick();
/* 超时来源:调用方指定(单次预算)优先,否则用默认值(均作为挂起截止) */
uint32_t to_ms = (timeout_ms != 0U) ? timeout_ms : (uint32_t)NET_RECV_TIMEOUT_MS;
while (1) {
net_poll();
net_poll();
int recv_len = ch395f_get_recv_len(sockfd);
if (recv_len > 0) {
int read_len = (recv_len > len) ? len : recv_len;
ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len);
p_sock->recv_bytes += (uint32_t)read_len;
p_sock->readable = 0; /* 已消费数据(注:部分读受 Trap 13 限制,下一次 RECV_OK 会重新置位) */
return read_len;
}
if (flags & NET_MSG_DONTWAIT) {
/* 非阻塞且当前无数据:若连接已断开则视为 EOF(0) 并自动重监听,
* 否则返回 WOULDBLOCK(-1) 表示暂时无数据 */
if (p_sock->state == NET_SOCK_STATE_CLOSED) {
if (p_sock->auto_relisten) {
(void)net_relisten_try(p_sock); /* 非阻塞:失败由重试轮询接管 */
}
return 0;
}
s_net_errno = NET_ERR_WOULDBLOCK;
return -1;
}
if ((to_ms > 0U) &&
((HAL_GetTick() - tick_start) >= to_ms)) {
s_net_errno = NET_ERR_TIMEDOUT;
return -1;
}
int recv_len = ch395f_get_recv_len(sockfd);
if (recv_len > 0) {
int read_len = (recv_len > len) ? len : recv_len;
ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len);
p_sock->recv_bytes += (uint32_t)read_len;
p_sock->readable = 0; /* 已消费数据(注:部分读受 Trap 13 限制,下一次 RECV_OK 会重新置位) */
return read_len;
}
if (flags & NET_MSG_DONTWAIT) {
/* 非阻塞且当前无数据:若连接已断开则视为 EOF(0) 并自动重监听,
* 否则返回 WOULDBLOCK(-1) 表示暂时无数据 */
if (p_sock->state == NET_SOCK_STATE_CLOSED) {
/* 断开前尝试排空 CH395F 接收缓冲区(Trap 13:FIN 可能比数据先到) */
int rem = ch395f_get_recv_len(sockfd);
if (rem > 0) {
int rd = (rem > len) ? len : rem;
ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)rd);
p_sock->recv_bytes += (uint32_t)rd;
p_sock->readable = 0;
return rd;
}
/* EOF:单连接模式自动回到 LISTEN,应用下次 net_accept 即接到新连接 */
if (p_sock->auto_relisten) {
(void)net_relisten_try(p_sock); /* 非阻塞:失败由重试轮询接管 */
}
return 0;
}
s_net_errno = NET_ERR_WOULDBLOCK;
return -1;
}
/* 无数据 + 阻塞等待:登记挂起请求,立即返回(netTask 任何时刻都不被占用)。
* 调用方在自己上下文睡任务通知(LwIP netconn 模型);等待期间 netTask
* 继续主循环(事件泵 + 队列),其他消费者零头阻塞。 */
return net_pending_register(sockfd, buf, len, to_ms, caller);
}
/*
@@ -1303,6 +1335,9 @@ static void net_relisten_retry_tick(void) {
if (s_relisten_wait[i] == 0U) {
continue;
}
if (s_net_pending[i].active != 0U) {
continue; /* 本 fd 有在途挂起 recv:重开 Socket 会摧毁在途连接(L3 保险) */
}
if ((int32_t)(now - s_relisten_next[i]) < 0) {
continue; /* 未到点(tick 差值按有符号比较,抗回绕) */
}
@@ -1320,6 +1355,228 @@ static void net_relisten_retry_tick(void) {
}
}
/* ===== L3 挂起 recv 请求的实现(一期,2026-09-22) =====
* 完成条件按 ①数据 ②到点 ③断连 顺序检查(逐项对齐旧自旋循环语义):
* ① 数据通道(ESTABLISHED/UDP_OPEN)内 readable 门控 SPI 读——常见无数据路径
* 零额外 SPI 事务(INT# 电平触发链保证缓冲有数据时每轮重读 SINT 重位置位)
* ② 到点(与状态无关,旧自旋亦在任何状态下检查超时)→ NET_ERR_TIMEDOUT
* ③ CLOSED:先排空(Trap 13:FIN 可能先于数据)→ 有数据按数据完成,
* 否则 EOF(0) + 自动重监听
* 其他状态(CONNECTING/LISTENING/…)保持等待状态迁移(CONNECT→ESTABLISHED
* 是合法路径;陈旧 resync 由显式取消处理)——不做状态推断取消,避免误杀
* 正在建链的等待。看门狗兜底:到点后仍未了结 = 完成逻辑缺位 → 强制了结留痕。 */
/*
* 函数功能:通知调用方并清除本 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) {
net_pending_t *p;
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return;
}
p = &s_net_pending[sockfd];
if (p->active == 0U) {
return;
}
if (result < 0) {
s_net_errno = result;
}
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 - 本次等待预算(截止 = 登记时刻 + to_ms) 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;
uint32_t now;
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
s_net_errno = NET_ERR_BADF;
return -1;
}
p = &s_net_pending[sockfd];
if (p->active != 0U) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
if ((caller == NULL) || (caller == s_net_task_handle)) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
now = HAL_GetTick();
p->active = 1U;
p->buf = buf;
p->len = len;
p->deadline = now + to_ms;
p->registered = now;
p->caller = caller;
return NET_PENDING_REGISTERED;
}
/*
* 函数功能:挂起 recv 完成检查(netTask 每轮 net_process_messages 调用一次,非阻塞)
* 返回值:无
* 限定条件:netTask 上下文
*/
static void net_pending_tick(void) {
uint32_t now = HAL_GetTick();
int i;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
net_pending_t *p = &s_net_pending[i];
net_sock_t *p_sock = &s_net_socks[i];
if (p->active == 0U) {
continue;
}
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) {
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; /* 虚假标志:缓冲无数据,继续等待 */
}
}
/* ② 到点(tick 差值按有符号比较,抗回绕) */
if ((int32_t)(now - p->deadline) >= 0) {
s_net_errno = NET_ERR_TIMEDOUT;
net_pending_complete(i, NET_ERR_TIMEDOUT);
continue;
}
/* ③ 断连:排空 → 数据或 EOF(0) + 自动重监听 */
if (p_sock->state == NET_SOCK_STATE_CLOSED) {
int rem = ch395f_get_recv_len((uint8_t)i);
if (rem > 0) {
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 {
if (p_sock->auto_relisten != 0U) {
(void)net_relisten_try(p_sock); /* 非阻塞:失败由重试轮询接管 */
}
net_pending_complete(i, 0);
}
continue;
}
/* 其他状态:保持等待(见本区块头注释) */
/* ④ 看门狗:到点 + 裕量仍未了结 = 完成逻辑缺位 → 强制了结并留痕 */
if ((int32_t)(now - p->deadline) >= (int32_t)NET_PENDING_WATCHDOG_MS) {
DBG_ERROR("sock%d pending recv stuck %ums past deadline (force settle)",
i, (unsigned)((int32_t)now - (int32_t)p->deadline));
s_net_errno = NET_ERR_TIMEDOUT;
net_pending_complete(i, NET_ERR_TIMEDOUT);
}
}
}
/*
* 函数功能:清除本 fd 的挂起请求(不通知;用于"新 fd 不得继承"与 close 后的幂等清理)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 返回值:无
* 限定条件:无
*/
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 上下文
*/
static void net_pending_cancel(int sockfd, int err) {
net_pending_complete(sockfd, err); /* complete 内部校验范围与在位 */
}
/*
* 函数功能:取消全部挂起请求并逐一通知(网络重建路径)
* 入口参数:err - 通知的错误码 int
* 返回值:无
* 限定条件:netTask 上下文;须在控制块清零与芯片复位之前调用
*/
static void net_pending_cancel_all(int err) {
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;
int avail;
if (s_net_pending[sockfd].active == 0U) {
return;
}
val = NET_ERR_CONNRESET;
avail = ch395f_get_recv_len((uint8_t)sockfd);
if (avail > 0) {
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(内部版本,不经过消息队列)
@@ -1334,6 +1591,10 @@ static int net_close_locked(net_sock_t *p_sock) {
int sockfd = (int)(p_sock - s_net_socks);
uint32_t tick = HAL_GetTick();
/* L3:先了结本 fd 的挂起 recv(排空/取消 + 必通知),再关芯片 Socket
* ——关闭不得让等待中的调用方永久悬挂 */
net_pending_settle_close(p_sock);
ch395f_close_socket(sockfd);
/* 排空上一条连接残留的 DISCONNECT 中断(单连接模式仅有 Socket 0,
@@ -1357,6 +1618,7 @@ static int net_close_locked(net_sock_t *p_sock) {
memset(p_sock, 0, sizeof(net_sock_t));
net_relisten_clear(sockfd); /* 关闭即取消该 fd 的待重试(D10 重试队列) */
net_pending_clear(sockfd); /* 关闭即清除该 fd 的挂起状态(L3;幂等) */
return 0;
}
@@ -1814,13 +2076,16 @@ int net_send_sock(net_sock_t *p_sock, const void *buf, int len) {
* -1 - 接收失败 int
* 限定条件:必须在 netTask 上下文中调用;p_sock->in_use == 1
* 函数说明:直接操作 Socket 控制块,不经过消息队列。避免 netTask 向自身发送消息造成死锁
* (L3 后:netTask 上下文的阻塞等待会被挂起登记拒绝——自通知死锁,
* 本函数阻塞用法仅对 DONTWAIT 有效)
*/
int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags) {
if (p_sock == NULL) {
s_net_errno = NET_ERR_BADF;
return -1;
}
return net_recv_locked(p_sock, buf, len, flags, 0U); /* 0 = 默认 NET_RECV_TIMEOUT_MS */
return net_recv_locked(p_sock, buf, len, flags, 0U, /* 0 = 默认 NET_RECV_TIMEOUT_MS */
xTaskGetCurrentTaskHandle());
}
/*
* 函数功能:关闭 Socket(内部版本,绕过消息队列,供 netTask 直接使用)