a91640dc6e
前置来自 #57 SNTP 调研:旧 net_sendto / net_recvfrom 由调用方直调 SPI 且无限自旋收包,既违约「netTask 是芯片唯一访问者」,又把 IWDG 预算吃在自旋里。 改造面 - 新增 Drivers/BSP/NET/net_udp.c/.h(真机专属,模拟器不编译): bind/set_peer/sendto/recvfrom 四条执行体 + 挂起收包表 + tick; net_msg_type_t 加四型,公开 API 一律「入队 → 睡在通知上」。 - 源地址出参不搭 net_msg_t(按值拷贝,AGENTS 禁加回传字段):改住 net_udp.c 的 per-fd 槽,netTask 就地写回调用方内存,通知值只带 结果码;每条出口都清槽,泄漏 = 该 fd 永久 BUSY。 - 发送复用 net_send_locked,不写第二份分块逻辑;「会话是否已死」 收成一条判据 net_send_state_down()(TCP 看 ESTABLISHED、UDP 看 UDP_OPEN),原先 4 处各写一遍。 - 顺带修掉真缺陷(新条 TRAP-NET-22):旧 sendto 把 DesIP 写成具体 IP,芯片静默切 UDP Client,而旧 recvfrom 无条件剥 8 字节 → SNTP 这类 48 B 应答丢载荷前 8 字节并交出垃圾源地址。加 udp_mode(塞进 原有 padding,sizeof 不变)、bind 恒 SERVER、显式对端 sendto 切 CLIENT、set_peer(fd, NULL) 轻量回 SERVER,仅 SERVER 且 avail >= 8 才剥头。 - 截断定案:一次调用交付一个数据报,buf 装不下的尾巴用 64 B 栈上 scratch 排空(不用 clear_recv_buf——它连后面已到达的包一起丢)。 - 取消点接齐:net_init 重建 cancel_all、net_socket 分配 fd clear、 net_close settle_close + clear、重 bind 前先结算等包者, net_process_messages 第 5 个 tick 位挂 net_udp_tick。 - 收紧两处契约(原实现无生产调用者):UDP 未 bind 一律 -5; net_recv / _nb / _to 拒 DGRAM(绕过剥头就是静默吃 8 字节); NET_ERR_NOT_IN_NETTASK(-12) 退役为历史常量并写明原因。 验证 - 主机单测 test/host/net_socket 全套 616/616 绿(改造前 470):新 分片 ns_s7_udp.c 20 组,改写 440 / 555 / 560 组;覆盖率 net_udp.c 234/236(99.2%)、net_socket.c 956/969(98.7%), scripts/test_coverage_baseline.json 随本提交登记新文件。 - 测试抓到 4 个被测码真 bug:自等守卫误用调用侧谓词、芯片长度被读 两次、槽指针取在 fd 合法性检查之前、重 bind 静默作废等待者。 - Keil ARMCC 0 Error(s) 0 Warning(s);net_udp.o Code 1,358 / ZI 224 (8 fd × 28 B,其余模块零增长),全仓 RW_IRAM1 72.2%;内存台账 受管区按门禁提示用 --write / --update 机器回填,未手改数字。 - pre-commit 全量钩子(含 doc-links、coding-standard、mem-budget、 generated-fresh、bsp-err、registration、nested-comment)全绿。 限度 - 本轮无板子:真机回归 5 条(两端互通、set_peer(NULL) 能否恢复收 广播、截断排空后下一包完整性、挂起 recv 期间 IWDG 余量、bind 写 DesIP 与 open 先后的真芯片效果)已写进覆盖率口径「台架必验」。 - scripts/stack_report.py 退出码 1 是 10-26 起挂在账上的 iec104Task 栈债(margin 1,016 < 1,024,见 docs/代码规范/pre-commit使用说明.md: 122);本轮只让 net_bind 的 UDP 分支多一层静态帧(1,544 → 1,600), netTask margin 1,888 与 timeSyncTask 1,152 不受影响。抬栈要过 mem-budget 台账,不在本分支自行处理。 - 登记簿销项按仓库约定留给主工作树,本分支不动 docs/任务规划/**。
227 lines
12 KiB
C
227 lines
12 KiB
C
/*
|
||
* 模块名称:挂起请求表(阻塞 recv / send 挂起模型)
|
||
* 文件说明:阻塞 recv / send 的"挂起请求"数据结构与生命周期:登记 / 每轮完成检查 /
|
||
* 取消(close、网络重建、对账 resync)/ 幂等清理。
|
||
* recv 侧 = 数据到达即完成;send 侧 = 分块推进至全部写完(进度刷新停滞计时)。
|
||
* 架构与五路语义见 docs/网络/Net层阻塞模型架构设计.md
|
||
* 依赖:net_types.h(net_sock_t);ch395f.h(send 侧分块写 + 缓冲常量);
|
||
* 消费方 = net_socket.c(消息分派 / close / 重建 / 对账)、
|
||
* net_listener.c(挂起保险查询)
|
||
* 限定条件:除 net_pending_active / net_send_pending_active / *_clear(纯 RAM 读)
|
||
* 外,全部函数须 netTask 上下文
|
||
*/
|
||
#ifndef NET_PENDING_H
|
||
#define NET_PENDING_H
|
||
|
||
#include <stdint.h>
|
||
#include "FreeRTOS.h"
|
||
#include "task.h"
|
||
#include "net_types.h"
|
||
#include "ch395f.h"
|
||
|
||
/* 内部哨兵:pending 已登记(消息分派据此跳过即时通知;取值不与字节数空间冲突) */
|
||
#define NET_PENDING_REGISTERED 0x70000000
|
||
|
||
/* 挂起超时留痕探针:排查"调用方挂死唤不醒"时置 1——挂起超 NET_RECV_TRIPWIRE_MS
|
||
* 只留一条 [ERR](哪个 fd/多少毫秒/什么基准),绝不强制结束、绝不伪造返回值。
|
||
* 开与关语义完全一致,差别只在留痕;默认 0 = 产品构建零成本。
|
||
* 见 docs/网络/Net层阻塞模型架构设计.md §12 */
|
||
#define NET_PENDING_TRIPWIRE 0
|
||
#if NET_PENDING_TRIPWIRE
|
||
#define NET_RECV_TRIPWIRE_MS 60000U /* 挂起超时留痕阈值(ms) */
|
||
#endif
|
||
|
||
/* 单次写入 CH395F 发送 FIFO 的最大字节数:受硬件发送缓冲
|
||
* (CH395F_SEND_BUF_SIZE_BYTES = 1KB)与 SPI DMA 载荷
|
||
* (CH395F_SPI_DMA_MAX_PAYLOAD = 2KB)双重限制,以硬件发送缓冲为硬上限
|
||
* (TRAP-NET-16:单次写 2KB/4KB 会写穿 1KB 发送 FIFO)。
|
||
* ⚠ 唯一定义处:发送快速路径(net_send_locked)与挂起 send 推进
|
||
* (net_send_pending_tick)**必须**共用同一粒度,否则两条路径的分块行为漂移。 */
|
||
#define NET_SEND_CHUNK_MAX ((int)CH395F_SEND_BUF_SIZE_BYTES < (int)CH395F_SPI_DMA_MAX_PAYLOAD ? \
|
||
(int)CH395F_SEND_BUF_SIZE_BYTES : (int)CH395F_SPI_DMA_MAX_PAYLOAD)
|
||
|
||
/*
|
||
* 函数功能:捕获执行任务句柄(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)
|
||
* 函数说明:监听生命周期模块化:net_listener_tick 用它跳过"重开 Socket
|
||
* 会摧毁在途连接"的 fd(挂起 recv 保险)
|
||
*/
|
||
int net_pending_active(int sockfd);
|
||
|
||
/* ================= 挂起 send 请求表(SEND pending,二期) =================
|
||
* 与 recv 表**完全独立**(各自一张表、各占一槽):收发两个方向互不阻塞,
|
||
* 同一 fd 可同时有一个挂起 recv 与一个挂起 send(今天的分片实现本就支持该并发)。
|
||
* 语义差异:recv 是"等一次到达即完成",send 是"分块推进直到全部写完",
|
||
* 故 send 的停滞计时按**无进展**刷新(大文件传输总时长必然超过单块预算)。
|
||
*/
|
||
|
||
/*
|
||
* 函数功能:登记一个挂起 send 请求(net_send_locked 的"等发送缓冲"超预算路径)
|
||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||
* buf - 调用方发送缓冲基址(须保持有效至完成;本函数不拷贝数据) const void *
|
||
* len - 本次发送**总**长度(含登记前已写入芯片的部分) int
|
||
* sent - 登记前已写入芯片的字节数(进度起点,0 ≤ sent < len) int
|
||
* caller - 调用方任务句柄(完成通知目标) TaskHandle_t
|
||
* 出口参数:无
|
||
* 返回值:NET_PENDING_REGISTERED - 登记成功(分派跳过即时通知)
|
||
* -1 - 拒绝(参数非法 / 同 fd 已有挂起 send / 执行任务自身发起)
|
||
* 限定条件:netTask 上下文
|
||
* 函数说明:每 fd 至多一个挂起 send(禁止覆盖——覆盖丢失前调用方通知 = 永久挂死);
|
||
* 执行任务自身发起(net_send_sock 阻塞用法)无他人可完成 → 拒绝(自等死锁)。
|
||
* ⚠ **调用方铁律**:`sent < len` 是调用侧必须保证的**前置条件**,不是本函数的
|
||
* 兜底。`sent == len` 表示"全部字节已交付芯片、无剩余块可挂起"——此时超预算
|
||
* 应**直接返回 sent**,不得调用本函数;本函数会按参数非法拒绝并回 -1,
|
||
* 把一次完整发送报成失败(net_send_locked 末块等待即此情形,
|
||
* 见《Net层阻塞模型架构设计.md》 §5.5 末块边界)。
|
||
*/
|
||
int net_send_pending_register(int sockfd, const void *buf, int len, int sent,
|
||
TaskHandle_t caller);
|
||
|
||
/*
|
||
* 函数功能:挂起 send 推进与完成检查(netTask 每轮 net_process_messages 调用一次,非阻塞)
|
||
* 入口参数:p_socks - Socket 控制块表首指针(net_socket.c 的 s_net_socks) net_sock_t *
|
||
* 出口参数:无
|
||
* 返回值:无
|
||
* 限定条件:netTask 上下文
|
||
* 函数说明:逐 fd 按序判定:①会话不可继续(net_send_state_down:TCP 看
|
||
* ESTABLISHED / UDP 看 UDP_OPEN;session_dead 一并覆盖)→ 以已发
|
||
* 字节数结束;②推进——发送缓冲空闲则写下一块(每轮至多一块,写完即清
|
||
* send_ready,下一块等 SENBUF_FREE 由 net_poll 重新置位);③全部写完 →
|
||
* 以总长度结束;④无进展超时 → 以已发字节数结束。结果均为**已发字节数**
|
||
* (短写由调用方按 `ret != len` 判定,与分片实现契约一致)。
|
||
*/
|
||
void net_send_pending_tick(net_sock_t *p_socks);
|
||
|
||
/*
|
||
* 函数功能:判"本 Socket 的发送会话是否已不可继续"(TCP/UDP 统一判据)
|
||
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
|
||
* 返回值:1 - 不可继续;0 - 可继续 int
|
||
* 限定条件:p_sock 非空;纯读控制块,任意上下文
|
||
* 函数说明:TCP 门 = ESTABLISHED,UDP 门 = UDP_OPEN。快速路径(net_send_locked)
|
||
* 与挂起路径(net_send_pending_tick)共用,判据只此一处。
|
||
*/
|
||
int net_send_state_down(const net_sock_t *p_sock);
|
||
|
||
/*
|
||
* 函数功能:清除本 fd 的挂起 send(不通知;"新 fd 不得继承"与 close 后的幂等清理)
|
||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||
* 出口参数:无
|
||
* 返回值:无
|
||
* 限定条件:无
|
||
*/
|
||
void net_send_pending_clear(int sockfd);
|
||
|
||
/*
|
||
* 函数功能:取消本 fd 的挂起 send 并通知调用方(close / drop_conn / 对账 resync / 重建)
|
||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||
* err - 取消原因(记入 netTask 错误码槽位 + 日志) int
|
||
* 出口参数:无
|
||
* 返回值:无(无在位挂起时静默返回)
|
||
* 限定条件:netTask 上下文
|
||
* 函数说明:以**已发字节数**结束(已写入芯片的字节不回收——对端可能已收到;
|
||
* 未发部分放弃,由调用方按短写处理),原因记 errno 与 [ERR] 日志
|
||
*/
|
||
void net_send_pending_cancel(int sockfd, int err);
|
||
|
||
/*
|
||
* 函数功能:取消全部挂起 send 并逐一通知(网络重建路径)
|
||
* 入口参数:err - 取消原因 int
|
||
* 出口参数:无
|
||
* 返回值:无
|
||
* 限定条件:netTask 上下文;须在控制块清零与芯片复位之前调用
|
||
*/
|
||
void net_send_pending_cancel_all(int err);
|
||
|
||
/*
|
||
* 函数功能:查询本 fd 是否有在途挂起 send —— 宿主能力,供 net_listener 用
|
||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||
* 出口参数:无
|
||
* 返回值:1 - 有挂起;0 - 无(含描述符越界) int
|
||
* 限定条件:任意上下文(纯 RAM 读,不发起 RPC)
|
||
* 函数说明:与 net_pending_active 同用途——net_listener_tick 据此跳过
|
||
* "重开 Socket 会摧毁在途传输"的 fd(挂起 send 保险)
|
||
*/
|
||
int net_send_pending_active(int sockfd);
|
||
|
||
#endif /* NET_PENDING_H */
|