0f7b5a87fb
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号) 一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、 采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。 门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏 undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8 reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF; id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。 test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字, 是本批唯一改变构建产物的文件集合。 为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged 部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、 不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。 实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01% (基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 / 告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。 基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处 引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使 识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。 ⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二 根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有), 今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。 固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0; App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889 断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿; check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带 BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。 .cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法: Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。 同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%, RW_CCM 57,376 B / 87.9%,阈值 92%。
338 lines
12 KiB
C
338 lines
12 KiB
C
/*
|
||
* 模块名称:Network Select Implementation
|
||
* 模块功能:I/O 多路复用实现,包括 select 和 poll 两种模式,用于同时监控多个 Socket 的读写事件
|
||
* 适用平台:STM32F407ZGTx + CH395F 以太网芯片
|
||
* 作者:王建锋
|
||
* 创建日期:2026-07-18
|
||
* 修改记录:
|
||
* 2026-07-18 王建锋 创建初始版本
|
||
*/
|
||
|
||
#include <string.h>
|
||
#include <stdio.h>
|
||
|
||
#include "cmsis_os.h" /* CMSIS-RTOS2 API: osDelay() */
|
||
#include "FreeRTOS.h"
|
||
#include "task.h"
|
||
|
||
#include "net_select.h"
|
||
#include "net_socket.h"
|
||
#include "ch395f.h"
|
||
#include "main.h"
|
||
|
||
/*
|
||
* 私有函数声明区
|
||
*/
|
||
|
||
static int check_socket_readable(int sockfd);
|
||
static int check_socket_writable(int sockfd);
|
||
static int check_socket_error(int sockfd);
|
||
|
||
/*
|
||
* 公共函数实现区
|
||
*/
|
||
|
||
/*
|
||
* 函数功能:I/O 多路复用(select 模式),等待多个 Socket 的读写事件就绪
|
||
* 入口参数:nfds - 最大文件描述符数 +1 int > 0
|
||
* readfds - 读事件集合指针 net_fd_set_t *(可为 NULL)
|
||
* writefds - 写事件集合指针 net_fd_set_t *(可为 NULL)
|
||
* exceptfds- 异常事件集合指针(表项不存在的 fd 计入异常并计入返回计数;
|
||
* timeout 传 {0,0} 表示"只查一轮立即返回") net_fd_set_t *(可为 NULL)
|
||
* timeout - 超时时间结构体指针 net_timeval_t *(NULL=无限等待)
|
||
* 出口参数:readfds/writefds/exceptfds - 仅保留就绪的 Socket 位 net_fd_set_t *
|
||
* 返回值:就绪的 Socket 数量(>= 0);-1 表示参数无效 int
|
||
* 限定条件:net_init() + MX_FREERTOS_Init() 已执行;可在任意任务上下文调用
|
||
* 函数说明:1. 轮询 Socket 控制块(RAM)中的读/写/异常状态,判断事件是否就绪
|
||
* 2. 不在此直接访问 CH395F SPI——所有 ch395f 事务统一由 netTask 的 net_poll()
|
||
* 每 10ms 串行执行并刷新控制块状态(send_ready / readable),本函数仅做
|
||
* RAM 读取,避免与应用任务发起的 SPI 访问竞争(见 TRAP-NET-14)
|
||
* 3. 阻塞等待直到有事件就绪或超时
|
||
* 4. 通过 osDelay(1) 让出 CPU,平衡实时性与 CPU 利用率(状态刷新延迟 <=10ms)
|
||
*/
|
||
int net_select(int nfds, net_fd_set_t *readfds, net_fd_set_t *writefds,
|
||
net_fd_set_t *exceptfds, net_timeval_t *timeout) {
|
||
uint32_t start_tick;
|
||
uint32_t timeout_ms = 0;
|
||
net_fd_set_t result_read = {0};
|
||
net_fd_set_t result_write = {0};
|
||
net_fd_set_t result_except = {0};
|
||
int ready_count = 0;
|
||
int i;
|
||
|
||
/* 检查输入参数合法性 */
|
||
if ((nfds <= 0) || (nfds > NET_MAX_SOCKETS)) {
|
||
return -1;
|
||
}
|
||
|
||
/* 计算超时时间(毫秒) */
|
||
if (timeout != NULL) {
|
||
timeout_ms = (uint32_t)(timeout->tv_sec * 1000) +
|
||
(uint32_t)(timeout->tv_usec / 1000);
|
||
}
|
||
|
||
start_tick = HAL_GetTick();
|
||
|
||
while (1) {
|
||
/* 仅读取 netTask 的 net_poll() 已刷新的控制块状态(无 CH395F SPI 访问) */
|
||
ready_count = 0;
|
||
|
||
/* 检查哪个 Socket 有事件 */
|
||
for (i = 0; i < nfds && i < NET_MAX_SOCKETS; i++) {
|
||
const net_sock_t *p_sock = net_get_sock(i);
|
||
|
||
if (p_sock == NULL) {
|
||
/* 表项不存在就是异常,必须在 continue **之前**判:原实现先跳过,而
|
||
* check_socket_error() 只有"表项为空 / fd 越界"才返回 1,越界的 i
|
||
* 又不在本循环范围内 → exceptfds 永远是空的死参数(调用方按 POSIX
|
||
* 直觉用它,就永远什么都收不到)。2026-10-23 修掉。 */
|
||
if ((exceptfds != NULL) && NET_FD_ISSET(i, exceptfds)) {
|
||
NET_FD_SET(i, &result_except);
|
||
ready_count++;
|
||
}
|
||
continue;
|
||
}
|
||
|
||
/* 检查可读事件 */
|
||
if ((readfds != NULL) && NET_FD_ISSET(i, readfds)) {
|
||
if (check_socket_readable(i)) {
|
||
NET_FD_SET(i, &result_read);
|
||
ready_count++;
|
||
}
|
||
}
|
||
|
||
/* 检查可写事件 */
|
||
if ((writefds != NULL) && NET_FD_ISSET(i, writefds)) {
|
||
if (check_socket_writable(i)) {
|
||
NET_FD_SET(i, &result_write);
|
||
ready_count++;
|
||
}
|
||
}
|
||
|
||
/* 检查异常事件 */
|
||
if ((exceptfds != NULL) && NET_FD_ISSET(i, exceptfds)) {
|
||
if (check_socket_error(i)) {
|
||
NET_FD_SET(i, &result_except);
|
||
ready_count++;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 有事件就绪,返回 */
|
||
if (ready_count > 0) {
|
||
break;
|
||
}
|
||
|
||
/* 超时检测 */
|
||
if (timeout != NULL) {
|
||
/* 不能再加 (timeout_ms > 0) 前置:timeout = {0, 0} 换算出 0,那是
|
||
* POSIX 的"轮询一次立即返回";加上它 = 超时永不生效,调用方(netTask
|
||
* 在 IWDG required_mask 里)再也回不到主循环。就绪检查在本判断之前,
|
||
* 所以有就绪时依旧立刻带计数返回,2026-10-23 由主机单测暴露并修掉。 */
|
||
if ((HAL_GetTick() - start_tick) >= timeout_ms) {
|
||
break; /* 超时返回 0(timeout_ms == 0 即"只查一轮") */
|
||
}
|
||
}
|
||
|
||
/* 让出 CPU:FreeRTOS osDelay() 会正确进入休眠,允许其他任务运行 */
|
||
osDelay(1); /* 1ms 周期轮询,平衡实时性和 CPU 利用率 */
|
||
}
|
||
|
||
/* 复制结果集合 */
|
||
if (readfds != NULL) {
|
||
readfds->fd_bits = result_read.fd_bits;
|
||
}
|
||
if (writefds != NULL) {
|
||
writefds->fd_bits = result_write.fd_bits;
|
||
}
|
||
if (exceptfds != NULL) {
|
||
exceptfds->fd_bits = result_except.fd_bits;
|
||
}
|
||
|
||
return ready_count;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:I/O 多路复用(poll 模式),等待多个 Socket 的读写事件就绪
|
||
* 入口参数:fds - pollfd 数组指针 net_pollfd_t *
|
||
* nfds - 数组元素数量 int > 0
|
||
* timeout - 超时时间(毫秒) int >= 0(-1=无限等待,但本实现按 >=0 处理)
|
||
* 出口参数:fds[i].revents - 填充实际发生的事件掩码 short
|
||
* 返回值:就绪的 fd 数量(>= 0);-1 表示参数无效 int
|
||
* 限定条件:net_init() + MX_FREERTOS_Init() 已执行;可在任意任务上下文调用
|
||
* 函数说明:1. 轮询 Socket 控制块(RAM)中的读/写/错误/挂起状态,判断事件是否就绪
|
||
* 2. 不在此直接访问 CH395F SPI——状态由 netTask 的 net_poll() 每 10ms 刷新
|
||
* 3. 阻塞等待直到有事件就绪或超时
|
||
* 4. 通过 osDelay(1) 让出 CPU,平衡实时性与 CPU 利用率(状态刷新延迟 <=10ms)
|
||
*/
|
||
int net_poll_events(net_pollfd_t *fds, int nfds, int timeout) {
|
||
uint32_t start_tick;
|
||
int ready = 0;
|
||
int i;
|
||
|
||
if ((fds == NULL) || (nfds <= 0)) {
|
||
return -1;
|
||
}
|
||
|
||
start_tick = HAL_GetTick();
|
||
|
||
while (1) {
|
||
/* 仅读取 netTask 的 net_poll() 已刷新的控制块状态(无 CH395F SPI 访问) */
|
||
ready = 0;
|
||
|
||
for (i = 0; i < nfds; i++) {
|
||
fds[i].revents = 0;
|
||
|
||
/* 检查 fd 有效性 */
|
||
if ((fds[i].fd < 0) || (fds[i].fd >= NET_MAX_SOCKETS)) {
|
||
fds[i].revents |= NET_POLLNVAL;
|
||
ready++;
|
||
continue;
|
||
}
|
||
|
||
const net_sock_t *p_sock = net_get_sock(fds[i].fd);
|
||
|
||
if (p_sock == NULL) {
|
||
fds[i].revents |= NET_POLLNVAL;
|
||
ready++;
|
||
continue;
|
||
}
|
||
|
||
/* 检查可读 */
|
||
if (fds[i].events & NET_POLLIN) {
|
||
if (check_socket_readable(fds[i].fd)) {
|
||
fds[i].revents |= NET_POLLIN;
|
||
ready++;
|
||
}
|
||
}
|
||
|
||
/* 检查可写 */
|
||
if (fds[i].events & NET_POLLOUT) {
|
||
if (check_socket_writable(fds[i].fd)) {
|
||
fds[i].revents |= NET_POLLOUT;
|
||
ready++;
|
||
}
|
||
}
|
||
|
||
/* 检查错误 */
|
||
if (check_socket_error(fds[i].fd)) {
|
||
fds[i].revents |= NET_POLLERR;
|
||
ready++;
|
||
}
|
||
|
||
/* 检查挂起(连接断开) */
|
||
if (NET_SOCK_STATE_CLOSED == p_sock->state) {
|
||
fds[i].revents |= NET_POLLHUP;
|
||
ready++;
|
||
}
|
||
}
|
||
|
||
if (ready > 0) {
|
||
break;
|
||
}
|
||
|
||
/* 超时检测 */
|
||
if (timeout >= 0) {
|
||
if ((HAL_GetTick() - start_tick) >= (uint32_t)timeout) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* 让出 CPU:FreeRTOS osDelay() 会正确进入休眠,允许其他任务运行 */
|
||
osDelay(1); /* 1ms 周期轮询,平衡实时性和 CPU 利用率 */
|
||
}
|
||
|
||
return ready;
|
||
}
|
||
|
||
/*
|
||
* 私有函数实现区
|
||
*/
|
||
|
||
/*
|
||
* 函数功能:检查指定 Socket 当前是否可读
|
||
* 入口参数:sockfd - 待检查的 Socket 描述符 int 0 - NET_MAX_SOCKETS-1
|
||
* 出口参数:无
|
||
* 返回值:1 - 可读(接收缓冲非空);0 - 不可读或描述符无效 int
|
||
* 限定条件:net_init() 已调用
|
||
* 函数说明:1.(2026-10-04):LISTENING 态**不再**恒判可读。本网络层无
|
||
* "待 accept 的挂起连接队列"——新连接到达时 CONNECT 事件把该 socket
|
||
* 自身直接转为 ESTABLISHED(handle_connect_event:"监听自身转数据通道"),
|
||
* 故监听态恒无数据可读。原实现对 LISTENING 恒返回 1,会使
|
||
* select()/poll() 在监听 socket 上永久立即就绪(忙轮询地雷;当前无
|
||
* 调用方,属潜在地雷)。连接到达经 NET_EVENT_CONNECTED 回调通知。
|
||
* 2. 其余态:readable 由 net_poll 在 RECV_OK 时置位、net_recv_locked
|
||
* 读空后清零。避免应用任务直接访问 CH395F SPI(见 TRAP-NET-14)
|
||
*/
|
||
static int check_socket_readable(int sockfd) {
|
||
const net_sock_t *p_sock;
|
||
|
||
p_sock = net_get_sock(sockfd);
|
||
if (p_sock == NULL) {
|
||
return 0;
|
||
}
|
||
|
||
/* 监听态恒不可读(见上方说明) */
|
||
if (NET_SOCK_STATE_LISTENING == p_sock->state) {
|
||
return 0;
|
||
}
|
||
|
||
/* 有数据可读 */
|
||
if (p_sock->readable) {
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:检查指定 Socket 当前是否可写
|
||
* 入口参数:sockfd - 待检查的 Socket 描述符 int 0 - NET_MAX_SOCKETS-1
|
||
* 出口参数:无
|
||
* 返回值:1 - 可写(已连接且发送缓冲空闲);0 - 不可写或描述符无效 int
|
||
* 限定条件:net_init() 已调用
|
||
* 函数说明:1. 仅已建立连接且 send_ready 标志置位的 Socket 视为可写
|
||
*/
|
||
static int check_socket_writable(int sockfd) {
|
||
const net_sock_t *p_sock;
|
||
|
||
p_sock = net_get_sock(sockfd);
|
||
if (p_sock == NULL) {
|
||
return 0;
|
||
}
|
||
|
||
/* 已建立连接的 Socket 发送缓冲区空闲时可写 */
|
||
if (NET_SOCK_STATE_ESTABLISHED == p_sock->state) {
|
||
if (p_sock->send_ready) {
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:检查指定 Socket 是否存在错误状态
|
||
* 入口参数:sockfd - 待检查的 Socket 描述符 int 0 - NET_MAX_SOCKETS-1
|
||
* 出口参数:无
|
||
* 返回值:1 - 有错误(含描述符越界或 Socket 未分配);0 - 无错误 int
|
||
* 限定条件:无
|
||
* 函数说明:1. 描述符越界视为错误
|
||
* 2. 未分配(net_get_sock 返回 NULL)的 Socket 视为错误
|
||
*/
|
||
static int check_socket_error(int sockfd) {
|
||
const net_sock_t *p_sock;
|
||
|
||
/* 无效 fd 视为错误 */
|
||
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
|
||
return 1;
|
||
}
|
||
|
||
p_sock = net_get_sock(sockfd);
|
||
if (p_sock == NULL) {
|
||
return 1; /* 未使用的 Socket 视为错误 */
|
||
}
|
||
|
||
return 0;
|
||
}
|