From 04a7677bf2b997de8d998638489526f0f1e7fbff Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Mon, 21 Sep 2026 04:46:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=90=8C=E6=AD=A5net?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=A0=81=E5=9B=9E=E4=BC=A0=E7=BA=A6=E6=9D=9F?= =?UTF-8?q?=E4=B8=8E=E5=8D=95=E8=BF=9E=E6=8E=A5=E6=96=AD=E5=BC=80=E8=AF=AD?= =?UTF-8?q?=E4=B9=89,=E5=85=B3=E9=97=AD=E5=B7=B2=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=9A=84FTP=2021=E5=BE=85=E5=8A=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 ++ docs/BSD_Socket_API_使用指南.md | 29 +++++++++++++++++++++++++++++ 待处理内容.md | 2 +- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 3f296e7c..8b1fd6cc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,6 +83,8 @@ STM32F407ZGTx @168MHz(HSE 8MHz → PLL M=4/N=168/P=2);**仅 Keil MDK-ARM v - **CH395F 单连接 socket**:监听与数据通道复用同一 Socket(CONNECT 后转数据通道,DISCONNECT/TIMEOUT 后重开 → LISTEN);所有 CH395F 中断在 `net_poll()` 内经 `GET_GLOB_INT_STATUS` 集中读取并清除,**不得**在 ISR 或其他地方单独处理 - 所有 `net_*` socket 操作经 `netMsgQueue` 消息队列在 netTask 串行执行(线程安全);非 netTask 任务直接调 `net_*` API,勿直接碰 CH395F 驱动 +- **`net_*` 公开 API 的错误码经"任务通知值"回传**(2026-09-21 实证):`net_msg_t` 是**按值拷入队列**的,netTask 改的是自己那份副本,调用方永远看不到 → 错误码搭 `xTaskNotify()` 通知值回来(失败=负的具体错误码如 `NET_ERR_WOULDBLOCK`、成功=字节数/描述符),公开 API 恢复 errno 后对外统一返回 -1。**勿给 `net_msg_t` 加"回传用"字段**(调用方只会读到栈垃圾;曾据此"修"错误码,导致 `ui_net_sink` 每次空轮询都误判为真错误 → 连上即 `net_close`) +- **单连接 socket 的断开语义**:监听 Socket 在 CONNECT 后自身即数据通道(`g_client == g_listen`),EOF 时 net 层已 `auto_relisten`;应用层**不得**在 EOF 时 `net_close` 该 fd(会连监听一起毁掉 → 端口"连接被拒"),仅当数据 fd ≠ 监听 fd(模拟器多连接后端)时才关 - 网口配置:`/config.ini` [network] 段(工厂默认 192.168.1.100/24、gw .1;MAC 全零 = 芯片内置;UI 保存立即生效;旧档 `/cfg/netcfg.bin` 首启自动迁移)→ `docs/网络配置说明.md` - **新 .c 文件必须手动注册到两处**才能双端编译:`MDK-ARM/STM32F407-Demo.uvprojx` + `simulator/CMakeLists.txt`(用 `new-source-file` skill) - 测试构建:`test/test_config.h` 的 `TEST_SUITE_CH395F/STORAGE/GD5F/RTC` **互斥**(一次只能启用一个,`#error`);全部注释 = 产品构建;**破坏性用例默认关闭**:同文件 `ENABLE_DESTRUCTIVE_TESTS`(默认注释)控制写/擦 NAND、格式化、坏块注入与 FatFS 写类用例——关闭时套件为**只读模式**(开机横幅 `mode: READ-ONLY`,可随时在在用设备上跑、不动卷内容与容量),只在**产品首次上电/产线首启**或明确需要时打开(`mode: DESTRUCTIVE`);2026-09-21 实测一轮破坏性 GD5F 运行即让 dhara 多记一个坏块(容量 95248 → 95200;该计数持久化在 dhara journal checkpoint 头里,普通恢复模式清不掉,需全片擦除——见 `docs/测试说明/存储维护与容量恢复.md`);CH395F 套件 `test/ch395f_test.h`:硬件层 Phase 1~5 与 NET 层 Phase 6~11 互斥;CH395F 硬件层模式下 netTask(基础线程:无条件创建、全构建唯一实例)入口自挂起(不运行),testTask 独占 CH395F diff --git a/docs/BSD_Socket_API_使用指南.md b/docs/BSD_Socket_API_使用指南.md index 897c65ce..0f9b794a 100644 --- a/docs/BSD_Socket_API_使用指南.md +++ b/docs/BSD_Socket_API_使用指南.md @@ -1134,6 +1134,23 @@ if (len < 0 && net_get_errno() == NET_ERR_WOULDBLOCK) { } ``` +**⚠ 单连接模型(MCU 端 CH395F)的断开处理**:监听 Socket 在 CONNECT 后**自身即数据通道** +(`g_client == g_listen`);`net_recv()` 返回 0(EOF)时 net 层已自动把该 Socket 放回 LISTEN +(`auto_relisten`,BSD 风格"监听常驻")。此时**不得**再对它调 `net_close()`——那会把监听一起关掉, +后续连接直接被拒(连接与监听同一 fd,重建还受 Trap 09 时序限制)。正确写法: + +```c +} else if (len == 0) { + if (client_fd != listen_fd) { + net_close(client_fd); /* 多连接后端(PC 模拟器):数据通道独立,需显式关闭 */ + } + client_fd = -1; /* 单连接后端:监听已由 net 层恢复,直接等下一次 accept */ +} +``` + +(2026-09-21 台架实证:`ui_net_sink` 曾在 EOF 时无条件 `net_close`,表现为 7003 答完一帧即断连、 +端口随后"连接被拒"。) + **方式三:事件回调** ```c void callback(int sockfd, net_event_t event, void *arg) @@ -1224,12 +1241,24 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po 由 CubeMX 在 `MX_FREERTOS_Init()` 中创建(`osMessageQueueNew(8, sizeof(net_msg_t), NULL)`),在 `osKernelInitialize()` 之后执行,确保 FreeRTOS 内核已就绪。 +**问题:`net_recv()` 返回 -1 后,`net_get_errno()` 的 `NET_ERR_WOULDBLOCK` 是谁设置的?** + +消息体是**按值拷入队列**的:`osMessageQueuePut()` 复制整个 `net_msg_t`,`netTask` 改的是它自己那份副本, +**调用方看不到**(极易踩坑)。因此实现上不靠消息字段回传错误码,而是把它搭在 `xTaskNotify()` 的通知值上: +失败时通知值 = 具体错误码(负值,如 `NET_ERR_WOULDBLOCK`),成功时 = 实际字节数/描述符; +公开 API 再把它恢复为 `s_net_errno`,并按 BSD 语义对外统一返回 -1。 + +推论:**不要给 `net_msg_t` 加"回传用"字段**——调用方读到的只会是自己的初值/栈垃圾; +需要回传请用调用方提供的指针,或复用通知值。(2026-09-21 实证:曾按"加 `msg.err` 字段"方式修错误码, +结果 `ui_net_sink` 每次空轮询都拿到栈垃圾 → 误判为"真错误" → 连上即 `net_close`。) + --- ## 变更记录 | 日期 | 变更 | |------|------| +| 2026-09-21 | **修正公开 API 的错误码回传**:`net_msg_t` 按值拷入队列 → netTask 对消息的写入调用方不可见,改用 `xTaskNotify()` 通知值回传具体错误码(负值),公开 API 恢复 errno 后统一返回 -1(此前被覆盖成 `NET_ERR`,使 `net_get_errno() != NET_ERR_WOULDBLOCK` 的"空轮询"判断恒成立 → `ui_net_sink` 连上即 `net_close`);§12.4 补单连接模型断开处理(EOF 时不得 `net_close` 监听 socket);§12.9 补错误码回传约束 | | 2026-09-15 | 同步配置引擎属主变更(P1-3):`net_init` 调用方说明改为"netTask 只读 RAM 副本"(网口配置由 defaultTask 在就绪屏障前加载);模拟器端启动序列补 `net_cfg_save_run()`(主循环 = 模拟器侧配置引擎属主,执行 UI 保存请求) | | 2026-09-13 | 从 git 历史恢复(`4e9ac0d` 2026-08-28 误删);同步当前代码:`net_init` 4 参数(新增 `mac`)、`net_socket` 2 参数(移除 `protocol`)、新增 `net_get_generation` / `net_generation_inc` / `net_listen_locked`、`NET_NO_OS` 双端构建、netTask 周期 10ms → 1ms | | 2026-09-13 | 配图:§2.1 分层架构改 SVG 生成器产物(`tools/arch_diagram/gen_net_stack_svg.py` → `docs/images/BSD网络栈分层.svg`);§2.4 状态机、§3.2 消息队列时序改 Mermaid(状态名对齐代码 `net_sock_state_t`,补 `UDP_OPEN` / `auto_relisten` 迁移,修正旧 ASCII 图不准处) | diff --git a/待处理内容.md b/待处理内容.md index 62e68496..5b9bcfd9 100644 --- a/待处理内容.md +++ b/待处理内容.md @@ -1,3 +1,3 @@ * 严格的内存使用情况排查,现在所有任务的内存都是往大的给的,导致内存不够用,后面要仔细排查内存的预算情况。 * [2026-09-15 实测] 静态 RAM 预算:scatter 仅映射 **128KB**(`0x20000000`,芯片 192KB 的上部 64KB 未映射——克隆芯片兼容性决策,勿擅自扩);产品构建静态镜像 **123.6KB**(RW 632B + ZI 125952B,含 FreeRTOS 堆),余量仅 **4.4KB** → 测试固件溢出:CH395F HW 全阶段固件超 8.7KB(PHASE5 三缓冲 12.3KB)、NET 层固件超 ~28KB(`s_rx_buf` 32KB),`L6406E` 构建实测(审计清单 P1-10)。跑 CH395F 套件前须先解决 RAM 预算(全量实测新基线 + 测试缓冲瘦身 + 产品镜像压缩)。**[2026-09-18 P0-1 后新基线]** 产品镜像 RW 624B + ZI 125440B = **126064B(123.1KB)**,余量 **5008B(4.9KB)**——比 09-15 基线多 512B,来源是配置引擎弃用 C 库 `malloc` 后 MicroLIB 的 512B `HEAP` 段被链接器整段移除(map:`Removing startup_stm32f407xx.o(HEAP), (512 bytes)`);两处测试固件溢出量随之各减约 0.5KB,结论不变。 -* [2026-09-21 实测] FTP 服务(端口 21)从未监听:V0.1 板新旧固件开机后 TCP 连 21 均超时(7003/8888 监听正常),开机日志无任何 FTP 相关行;需排查 ftpTask/lftpd_inet 启动路径(FTP_READY 屏障或 lftpd listen 未触发)。 +* [2026-09-21 已解决] FTP 服务(端口 21)不监听:根因是就绪屏障位被消费(ftpTask 的 WaitAll 永不成立),已由 `1abb3d2` 修复;复测端口 21 返回 `220 Service ready`,上传/下载正常。