feat(net/udp): UDP 数据面并入 netMsgQueue 串行执行(#73) #74
Reference in New Issue
Block a user
Delete Branch "udp-queue-impl-ddb7c192c16e"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This is by an automated agent, not a human.
背景
#57SNTP 调研把「UDP 先串行化」列为 SNTP 落地的第一步,本 PR 就是那一步(#73派工)。改造前net_sendto/net_recvfrom由调用方直调 SPI 并且是无限自旋收包:既违反"netTask 是芯片唯一访问者",又把 IWDG 预算吃在自旋里。改了什么
Drivers/BSP/NET/net_udp.c/.h(真机专属,模拟器不编译):bind / set_peer / sendto / recvfrom四条执行体 + 挂起收包表 +net_udp_tick。net_msg_type_t加四个消息型,公开 API 一律「入队 → 睡在任务通知上」;netTask 自己不占用——等数据、等发送缓冲都登记挂起、由 tick 结算。net_msg_t是按值拷入队列、AGENTS 明令禁止加"回传用"字段,所以出参指针不进消息,改住net_udp.c的 per-fd 槽——调用方arm,netTask 执行体经该指针就地写回调用方内存,通知值只带结果码。槽互斥,且每条出口都必须清(入队失败、队列未绑定、参数被拒、挂起被结算),泄漏就等于这条 fd 永久BUSY。net_send_locked(1KB 分块 + 挂起 send 都在里面)。为此把"会话是否已死"收成一条判据net_send_state_down()(TCP 看ESTABLISHED、UDP 看UDP_OPEN)——原先 4 处各写一遍。TRAP-NET-22):旧net_sendto为了发一个包把 DesIP 从0xFFFFFFFF改写成具体 IP,芯片就此静默切到 UDP Client 形态(收包不带 8 字节信息头),而旧recvfrom对DGRAM无条件剥 8 字节 → 请求-响应式协议(SNTP 的 48 B 应答)会丢掉载荷前 8 字节并交出垃圾源地址。现在net_sock_t带udp_mode(塞进原有 2 字节 padding,sizeof(net_sock_t)不变),bind恒 SERVER、带显式对端的sendto切 CLIENT、net_udp_set_peer(fd, NULL)轻量回 SERVER(不重开 Socket,因此不清芯片缓冲),只有 SERVER 且avail >= 8才剥头。buf装不下的尾巴必须排空(ch395f_read_recv_buf是消费式读,不排空下一次就交出上一个包的残料)。排空用 64 B 栈上 scratch 循环读,不用ch395f_clear_recv_buf——它清整个接收缓冲,会把后面已到达的包一起丢。net_init重建cancel_all、net_socket分配 fdclear、net_closesettle_close+clear、重 bind 前把还在等包的调用方按-8结算;net_process_messages第 5 个 tick 位挂net_udp_tick。契约变化(评审重点)
bind才能sendto/recvfrom/net_udp_set_peer,否则-5 NET_ERR_NOTCONN(芯片侧 Socket 还没开,此时改 DesIP/DesPort 没有落点)。net_recv/net_recv_nb/net_recv_to对DGRAM一律-3:它们读的是裸字节流,绕过剥头就是静默吃 8 字节,宁可拒掉。收 UDP 请走net_recvfrom/ 新增的net_recvfrom_to。NET_ERR_NOT_IN_NETTASK(-12) 不再有运行时返回点(串行化后"不在 netTask 里"不再是错误),保留为历史常量并在头文件写明退役原因。net_recvfrom_to(fd, buf, len, to_ms, src_addr, addrlen)、net_udp_set_peer(fd, peer|NULL)、net_udp_get_mode(fd)。验证
test/host/net_socket全套 647/647 绿(改造前 470)。新分片ns_s7_udp.c共 23 组(bind 的 5 条芯片命令顺序、形态剥头逐字节、挂起收包在后续 poll 轮到达、预算超时按调用方 deadline、close/重建/重 bind 结算不许静默丢通知、出参槽互斥与泄漏、截断排空、netTask 自等守卫、参数守卫且拒时零芯片事务、「队列未绑定」与「队列满」两条失败出口不漏槽、芯片open回ERR_BUSY时精确码要沿通知值回来、队列里「别人的失败」排在我这条之前不许污染我的回执、轻/重回 SERVER、挂起 send 结算、tick 兜底);改写TC-NETSOCK-440(补 bind 前置 + 440p~440u)、555、560 组。假件按「只加不改」扩了三处:捕获ch395f_write_send_buf的真实字节、SENBUF_FREE 写延迟注入、read_calls计数。net_udp.c237/239 行(99.2%,函数 14/14)、net_socket.c966/969(99.7%,函数 59/59)、全仓 line 90.1%、47/47 套绿;scripts/test_coverage_baseline.json已在本提交内登记新文件。未覆盖两处是有意的:net_udp.c槽越界兜底(调用方已挡)、net_socket.c错误码槽重查竞态(改造前后同为那 2 行)。net_udp.oCode 1,370 / RO 71 / RW 0 / ZI 224(8 fd × 28 B,其余模块零增长);全仓 Flash 281,012 B(ER_IROM126.6%)、RAMRW_IRAM194,624 B(72.2%),92% 红线余量充足。内存台账受管区按门禁提示用ram_baseline.py --write / --update与check_mem_numbers.py --write机器回填(docs/内存管理/两份 +scripts/ram_baseline.tsv一行),没有手改数字。台账里「网络」子系统的软配额(12 KB)本轮从超 1.4 KB 变成超 1.6 KB(+256 B),是否抬配额归台账所有者。pre-commit run(52 根钩子,含 doc-links / coding-standard / mem-budget / generated-fresh / bsp-err / registration / nested-comment / mojibake)全绿;三根 manual 阶段钩子也在本机跑过:cppcheckPassed(它抓到net_udp.c两处variableScope,已修)、test-cppcheckPassed、test-coverage就是上面的覆盖率闸门。主机单测覆盖率口径.md第 4 轮):自等守卫误用调用侧谓词(导致每条正常 recvfrom 都被拒)、芯片长度被读两次、槽指针取在 fd 合法性检查之前、重 bind 静默作废等待者。限度 / 未做
set_peer(NULL)能否真恢复收广播、截断排空后下一包完整性、挂起 recv 期间的 IWDG 余量、bind写 DesIP 与 open 先后的真芯片效果)已写进覆盖率口径的「台架必验」,主机测试替代不了它们。scripts/stack_report.py退出码 1 是 2026-10-26 起就挂在账上的iec104Task既有栈债(margin 1,016 < 1,024,见docs/代码规范/pre-commit使用说明.md:122)。本轮只让net_bind的 UDP 分支多一层静态帧(1,544 → 1,600);netTask(margin 1,904,静态最坏 1,936 / 3,840)与timeSyncTask(1,152)不受影响。抬栈要过 mem-budget 台账,不在本分支自行处理。docs/任务规划/**。simulator/sim/net_socket_sim.c是 Winsock 实现,不编译net_socket.c)本轮没补net_recvfrom_to/net_udp_set_peer/net_udp_get_mode三条对应实现(当前模拟侧无调用者,补了就是死代码);只验证了NET_NO_OS口径下改过的net_socket.h能用 MSVC/W4单独编过。日后 SNTP 若要在模拟器里跑,得先在 sim 侧补这三条。a4b8bee209toa91640dc6ea91640dc6etofff961d803@super_agent_bot 真机回归做完,再并入 master 分支
收到:PR #74 在真机回归做完、拿到证据之前不并 master,我这边不提合并。现在先确认这台机器有没有可烧录的 CH395F 板子——有就把「台架必验」5 条跑成证据,没有我把烧录/对端收发步骤和判据整理成一份可执行的台架清单交给你。
This is by an automated agent, not a human.
真机回归台架已就位(阶段 16),但板子没电,实测还做不了
板子在 19:19:22 断电,三处独立证据:
ping 192.168.31.100不通;串口守护日志冻结在[PROF] t=197121(14,965,244 B,复查 4 s 未增长);ST-LINK_CLI -c SWD UR报No target connected(stlink_flash.py list仍能看到探针 SN 0B250D12A218303030303032,所以是板子没电,不是工具坏)。只有人能恢复供电/排线,所以先停在这里。commit
8a81fa9e把本轮登记的 5 条「台架必验」全部做成了可复现台架,电一来就能跑完:net_layer_udp_bench_test_main,TC-NET-1600…1609)test/host/udp_queue_bench.py(板端先 bind 当服务端,对端地址从收到的包里学——本机是 DHCP192.168.31.161,TEST_PC_PEER_IP那个固定地址不在本机,所以只能板端先听)set_peer(fd, NULL)能否真恢复收其它源[ERR][WD] task stall零条 +[PROF]心跳连续 +net_get_generation()不变OPEN_SOCKET跑法三步:
test/board/test_config.h开TEST_SUITE_CH395F;test/board/ch395f_test.h只开ENABLE_PHASE16_TESTS,把默认的 11/14 注掉(它们会带进TEST_LATENCY_PROBE,污染要证的 UDP 面)。python test\host\udp_queue_bench.py --board 192.168.31.100(首步每 4 s 重发,能盖住板子启动那段时间)。=== Phase 16: N/N PASSED (failed=0) ===且 peer 端=== UDP Bench PC Peer: N/N PASSED (failed=0) ===;跑完恢复那四行宏重建产品镜像。不需要板子的部分已经验完:阶段 16 台架镜像
0 Error(s), 0 Warning(s)(Code 257,176,RW_IRAM196,232 B = 73.4%,距 92% 红线余 24,354 B,阶段自身代价就是net_test_task.o的 .bss 1,600 B);四行宏恢复默认后产品镜像同样 0/0,RW+ZI152,048 B 与改造前逐字节相同(阶段 16 被完整预处理掉);主机单测 647/647 仍绿;本提交 pre-commit 全档 Passed/Skipped(含 92% RAM 门禁、双端注册反向校验)。顺带量出来一条(写进 §12.6 限度,不是本次改出来的):
NET_SEND_CHUNK_MAX = 1024是 TCP 流的分块口径,UDP 上超过 1024 B 的sendto会被拆成多个数据报——改造前后是同一条路径,#57 的 SNTP 只有 48 B 碰不到。阶段 16 的 1025 / 1472 两条会在真芯片上把它量出来:peer 收到第二片就 FAIL 并打印各片长度。真机跑完之前不建议并入 master。跑完我把两侧汇总行与 stall 判据回填到
docs/测试说明/单元测试/主机单测覆盖率口径.md的「台架必验」条与本 PR。PR #74 代码侧已完成,现在卡在真机回归:开发板 19:19 起掉电(串口日志冻结、ping 192.168.31.100 不通、SWD No target connected)。方便的话请把板子上电、SWD 和 COM3 接回来,我继续跑阶段 16 取证;如果这台板子暂不可用,告诉我是否把台架步骤交给别人在别处跑。
@super_agent_bot 板子已经上电了
收到:板子已上电。这就烧阶段 16 台架镜像、起 PC 对端跑真机回归,取到两端汇总行后把结果回填 PR #74。
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.