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。
This is by an automated agent, not a human.
真机回归第一轮:UDP 数据面已在真芯片上跑通前半段
台架(阶段 16)第一次上板实测,板端镜像
fw build: Nov 3 2026 20:53:35 suite=CH395F dstr=off,串口取证
TC-NET-1601a/b/c+TC-NET-1602全绿:net_socket(NET_SOCK_DGRAM)拿到 fd=3,net_bind到:6001返回 0,net_udp_get_mode回 SERVER(0);net_recvfrom_to返回 20 B,*addrlen == sizeof(net_sockaddr_in)、sin_family == NET_AF_INET、源端口回读正确 —— 即「DesIP=0xFFFFFFFF 时前置 8 字节信息头」这条私有约定在真芯片上被正确剥掉、源地址被正确还原(TRAP-NET-22 的正身);
p[i]=(i*7+0x11)&0xFF),回显之后net_udp_get_mode翻成 CLIENT(1)。①的上半段(服务端收包 + 剥头 + 源地址回填 + 回显)到此有证据。②③④⑤与 1603 之后仍缺:板子在
21:0x 再次失电(
ping不通、SWDNo target connected、ST-LINK 探针本身仍在、COM3 仍枚举但 3 s 内零字节),这是今晚第二次(前一次 19:19)。
第一轮顺带抓到两处台架缺陷(不是被测代码),fc0d3e4 已修
下一次
recvfrom才抛出来,于是对端在开机第一秒就退出,板子白等 25 s。本机 CPython 3.14.7既没有
socket.SIO_UDP_CONNRESET也不接受它的数值形式,所以主力防线改成把 10054 当「这一轮没收到」继续重试;并在不用板子的回路自检里加了冷端口探针(走脚本真正在用的
_recv_maybe,另用裸套接句证明这台机器确实会回 ICMP,否则那条判定就没有牙)。日志看着像"全灭"。现在 peer 每步都重发、单步预算
--step-budget默认 35 s(小于 26 s 打警告);板端
udp_bench_expect按载荷第 0 字节的步号自愈:比本步小的旧包丢掉继续等(上限UDP_BENCH_STALE_MAX = 8),比本步大的直接报出来。顺带记两条纪律:跑台架期间别用 serial-assistant 守护进程取证(它重启后不重开 COM3,整段板端日志
会丢);
mem-budget门禁读的是磁盘上现成的.map,跑完台架忘了切回产品态重建就会报假漂移。现在的状态与下一步
加固后台架镜像本机构建
Code=257,388 / RO=34,136 / RW=2,060 / ZI=151,596,0 Error 0 Warning;测试宏已恢复默认并重建产品态
Code=249,124 / RW=2,052 / ZI=149,996,与本阶段之前逐字节一致(阶段 16 在产品里被预处理干净)。主机单测 647/647、门禁 sweep 全绿。台架锁仍持有,上电后约 3 分钟
可跑完 1603–1609(形态恢复、截断排空、30 s 挂起 park 期间的 IWDG/心跳、重建 socket 后首包、
1/20/1024/1025/1472 分片形状),跑完再回填
主机单测覆盖率口径.md与 §12.6 的实测结论。在此之前不建议并入 master。
板子今晚第二次掉电了(探针和 COM3 都在,是板子本身没电)。能否再上电并保持在电?回一句就立刻跑完 1603–1609 并回填真机结论;如果想自己跑,命令在 CH395F_Test_Guide §12.6。另外建议查下供电/电源线,一晚两次掉电不太正常。
板子又掉电了(这已是第二次在你 21:09 上电之后)。上电后撑不过几分钟就反复失联,更像供电接触不良/适配器带不动,建议先换根电源线或查插座。任一方式都行:把板子保持在电十几分钟我立刻跑完 1603–1609 并刷回产品镜像回填结论;或你自己按 CH395F_Test_Guide §12.6 的 6 步跑。
This is by an automated agent, not a human.
(本条替换之前那条乱码评论:坏在 PowerShell 5.1 用 ANSI 码页读了 UTF-8 文件,再转 JSON 存进 Gitea;已改成直接用 UTF-8 组装请求体,并把仓里所有我自己发的评论逐条核过一遍——只有这一条被写坏,其余中文正常。)
按你的指示,已停掉「等到上电就自动开跑」的守护任务,先把状态恢复正常:仓库工作树干净、测试宏都是提交态默认(
TEST_SUITE_*全部注释、ENABLE_PHASE16_TESTS注释、PHASE11/14 维持原样)、没有后台任务在抢 SWD 和串口。等你上电并说一声,我再开始台架试验。还需要你知道的两件事:
fw build: Nov 3 2026 20:53:35 suite=CH395F),不是产品镜像——板子断电期间没法刷回去。你上电之后我先跑完回归,第一件事就是把产品镜像刷回去并核对启动判据。bench_evidence\phase16c.hex(821,046 B,0 error 0 warning),跑一轮只要约 4 分钟,不用再等编译。本轮相对第一次跑(20:53,只到 TC-NET-1602)已加固三处:
WinError 10054(ICMP 端口不可达)打死——板子还没 bind 时芯片会回 ICMP,旧脚本第一次 recv 就退出,板子只能干等 25 秒;现在改成吃掉这个异常并按步重发。无回显就是这个连锁)。抓取串口的方式也换了:直接用 pyserial 读 COM3,不再走 serial-assistant 守护进程(它中途重启过,
port.ready: false且不会自己重开口,上一轮的板端日志就是这么丢的)。跑完的产物会落在
bench_evidence/(board_serial_<时间戳>.log+peer_<时间戳>.log两份),我把 ①–⑤ 的实测结果回填到docs/测试说明/单元测试/主机单测覆盖率口径.md第 4 轮和docs/测试说明/板级测试/CH395F_Test_Guide.md§12.6,再推提交。在那之前不建议并入 master。已换成自动台架:板子一上电就自动烧录并跑完整回归(约 4 分钟出结果),不用再回消息。你只要把板子重新上电并保持通电 5 分钟。75 分钟内没等到上电的话,worker 先交付现有证据、等你另安排时间。
@super_agent_bot 给我改成正常的语言,这是乱码,现在断电了,等我上电,你再进行台架试验,先恢复为正常状态
收到:先把乱码评论改成正常中文,并停掉「自动等上电就开跑」的台架任务、恢复默认状态,等你上电后再开始真机台架。
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.