diff --git a/docs/问题记录/Net层阻塞模型业界对照与演进方案.md b/docs/问题记录/Net层阻塞模型业界对照与演进方案.md new file mode 100644 index 00000000..bf293d5d --- /dev/null +++ b/docs/问题记录/Net层阻塞模型业界对照与演进方案.md @@ -0,0 +1,260 @@ +# Net 层阻塞模型:业界对照与演进方案 + +> **模块名称**:Net 层(net_socket / netTask)阻塞等待模型 +> **文档目的**:E-02 修复(2026-09-22)的配套调研留档——(1) 本项目"分片有界等待"方案与主流开源/ +> 成熟项目模式的对照;(2) 演进方向"RECV 消息 → pending 请求"(正统事件驱动形态)的利弊与改动量估算。 +> **留作备用**:第二档 #1(E-02+E-06+E-20+E-21 阻塞模型改造)排期实施时,以 §6 为设计基线、 +> §6.5 为验证门禁。 +> **日期**:2026-09-22 +> **配套文档**:`错误码传播审计.md`(E-02 明细 / 修复计划 / 已闭环记录)、`docs/测试说明/链路延迟测试说明.md` +> (M3 基线 / §8.3 实现要点 / §9 故障对照)、`AGENTS.md`(SPI 铁律 / 就绪屏障 / 内存预算) + +--- + +## 1. 结论速览 + +1. **已落地的修复(分片有界等待)在本项目约束类里是标准做法,且有官方认可**——不是野路子, + 无需推翻;已经台架 A/B 实证(挂起对时客户端下 FTP banner 28554ms → 109ms/75ms)与 + M3 压力档复验(帧周期 17.7s 尖峰 → 零尖峰,med 65.0ms)。 +2. **业界最正统形态("执行者永不被占")在本项目不可达**——其前提是"数据到达可被执行者免费 + 观察到"(LwIP:网卡 DMA/IRQ 递 pbuf → tcpip 线程 O(1) signal 消费者)。本项目的"数据到达" + 观察手段是一次 SPI 事务,而 SPI 总线由 netTask 独占(铁律)→ 等待只能在 netTask 内执行, + 自由度只剩"每次占多久"与"等待期间还做什么"。 +3. **演进方向 = pending 请求**(控制块登记 + 主循环节拍完成 + 取消必通知):消灭头阻塞缺陷类 + (不再依赖调用方纪律)、调用方从分片协议解放、成为 E-06/E-20/E-21 的公共地基。库内已有 + 生产验证先例——D10 的 `net_relisten_retry_tick`(登记→到点完成→全程不阻塞,恢复 588ms→89ms) + 就是该模式的缩小版。 +4. **这是结构演进,不是紧急修复**:现状(分片)已验证、残余最坏可控(队列滞后 ≤5ms、发送 + stall ≤1s/分片,M3 基线已按此锁定)。实施时机 = 第二档 #1 排期窗口;路线 RECV 先行 → + ACCEPT/SEND 跟进。代码改动不大(一期 ~200-400 行、公开 API 不变),**大的是验证**(台架 + 天量级)。 + +--- + +## 2. 背景:E-02 缺陷与已落地修复 + +**缺陷**(P1,`错误码传播审计.md` E-02):netTask 是全部 socket 操作的唯一执行者,`net_process_messages` +每次取一条消息处理到完成才取下一条。任一消费者发起的 30s 阻塞 `net_recv`(`net_recv_locked` 自旋 +`net_poll`+`ch395f_get_recv_len`)期间,队列整体停摆最长 30s——FTP 客户端空闲 >30s、对时客户端 +连上不发数据,均为常见触发工况。09-21 事故(errno 覆盖误断连)的姊妹缺陷。 + +**已落地修复**(2026-09-22,两提交 + 闭环留档): + +| 提交 | 内容 | +|---|---| +| `f8d6e90` | lftpd 侧:新增 `net_recv_to` 接口(片长经 `msg.timeout_ms` 入队、片超时独立返回码 -2);`lftpd_inet_read_line`/`lftpd_inet_read` 改 5ms 分片 + 30s 总预算 + 片间 `vTaskDelay(1)` 让出;M3 压力档复验(17.7s 尖峰 → med 65.0ms/max 133.5ms/零尖峰) | +| `4a1605b` | timeSync 侧:`time_sync_handle_conn` 同模式改造(5ms 片/30s 总预算/跨片累积容忍 TCP 分段/超时留痕 `time_sync: no TIME packet in 30s, drop conn`);产品构建 `@test` 闭环 PASS | +| `3b28c56` | 审计闭环留档 + 专项回归探针 `test/e02_probe.py` | + +**A/B 台架实证**(`test/e02_probe.py`,2026-09-22,设备 192.168.1.100):挂起对时客户端连 :8888 +不发数据,2s 后 PC 测 FTP(:21) banner 时延 = 队列响应性——旧固件 **28554ms**(设备日志实证芯片侧 +握手与 MCU 侧处理相隔 27s;旧代码 30s 后静默拆连零日志)vs 修复后 **109ms**(产品构建)/ **75ms** +(测试构建)。30s 预算拆连与监听恢复行为两侧同构(~8s,2 次 Trap 09 重试)。 + +**当前架构**(netTask 主循环,`App/task/net_task.c`): + +``` +for (;;) { + [reconfig 检查 → net_stack_bring_up 重建] + net_poll(); // 读芯片中断状态、分发事件、维护 readable 标志 + net_process_messages(); // ① net_relisten_retry_tick(D10:到点重试) + // ② while(队列非空) 取一条消息处理到完成 → 通知调用方 + osDelay(1); // 主循环节拍 ≈1ms +} +``` + +--- + +## 3. 业界调研:"带超时的等数据"的五类成熟模式 + +| # | 模式 | 代表项目 | 处理方式 | 执行者是否被占用 | +|---|---|---|---|---| +| A | 宿主事件循环 | Node.js/libuv、glib、Qt、POSIX select/poll | 非阻塞 I/O + 就绪通知(epoll/kqueue/IOCP)+ 定时器;等待 = 登记请求,由 OS 观察就绪,"就绪或超时"唤醒后处理就绪集 | **永不允许**——Node 官方文档明文 "never block the Event Loop";长任务的两条官方解法 = **分片(partitioning,定期让出)** 或 offload 到 worker 池 | +| B | RTOS 网络栈 | LwIP(ESP32/STM32 生态事实标准,FreeRTOS 任务模型与本项目同款) | 消费者线程在**自己上下文**带超时睡眠(信号量/mailbox);tcpip 线程只在包事件时 signal;超时用 poll timer 回调 | **永不**——官方 raw API 文档:"Raw API applications may **never block** since all packet processing… is done in a single execution context";多线程章节:"Application threads… communicate with this main thread through **message passing**" | +| C | 嵌入式事件循环 | Zephyr `net_context`、ESP-IDF `esp_event` | 应用线程阻塞在自己的 k_sem/事件上;net 线程只做完成回调("callback is called by RX thread so **its** stack and execution context is used here") | 永不 | +| D | 后台 I/O 线程 + 有界 poll | librdkafka(Kafka 官方客户端,生产级) | 后台线程循环**短超时** `poll()` + 每轮间隙排空内部工作队列("calls rd_kafka_poll(timeout) in a loop") | **有界**(短 poll 量子,队列不饿死) | +| E | 独占硬件属主(offload 芯片 / 总线属主) | W5500/W6500 类芯片驱动(CH395F 同类)、共享 SPI 的"专用任务 + 作业队列"模式 | 非阻塞原语 + **调用方预算循环**;或专用任务 + 作业队列、每个作业每轮推进一步(状态机步进) | 有界(每步/每片) | +| F | 每连接一线程 | 传统 threaded server(Apache worker、Java thread-per-connection) | 每连接独占线程,阻塞 recv 只冻结自己 | N/A(隔离) | + +**本项目归属**:E 类。CH395F 与 W5500 同属"SPI 总线上的硬连线 TCP/IP 协议栈"芯片(W5500 产品 +定义:"Hardwired Internet controller with an integrated full TCP/IP stack… over SPI");"专用任务 ++ 消息队列"是该类的标准串行化形态之一(另一形态"每调用加锁"在社区中常见,但会把"等待"也锁进 +临界区——正是 E-02 的形态)。F 类不可行:SPI 铁律决定所有 socket 操作必须经 netTask 串行, +不存在"每连接一线程"的空间。 + +**各模式关键证据**(2026-09-22 访问): + +- A:[Node.js《Don't Block the Event Loop (or the Worker Pool)》](https://nodejs.org/learn/asynchronous-work/dont-block-the-event-loop) + (partitioning 与 offloading 并列两条官方解法;"If a thread is blocked working on behalf of a + specific client request, pending requests for other clients may not get a turn") +- B:[LwIP raw API 文档](https://www.nongnu.org/lwip/2_0_x/raw_api.html)("must reside in different + execution contexts"、tcpip_thread、message passing、never block、`tcp_poll` 定时器回调); + [EEVblog LwIP 讨论](https://www.eevblog.com/forum/microcontrollers/anyone-here-familiar-with-lwip/75/) + ("the LwIP code is designed to never block when the core lock is held"); + [LwIP+RTOS netconn 阻塞问题](https://stackoverflow.com/questions/10848851/lwip-rtos-how-to-avoid-netconn-block-the-thread-forever); + [mbed-os LwIPSocket 非阻塞实现](https://github.com/ARMmbed/mbed-os/issues/13197) +- C:[Zephyr net_context 文档](https://docs.zephyrproject.org/latest/doxygen/html/net__context_8h.html)、 + [ESP-IDF esp_event](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/esp_event.html) +- D:[librdkafka INTRODUCTION](https://github.com/confluentinc/librdkafka/blob/master/INTRODUCTION.md)、 + [WaterDrop 对 librdkafka 后台线程的剖析](https://mensfeld.pl/2026/02/waterdrop-fd-polling-50-percent-faster/) +- E:[W5500 产品页](https://wiznet.io/products/ethernet-chips/w5500)、 + [FreeRTOS 论坛 W5500 多任务共享](https://forums.freertos.org/t/handle-several-sockets-w5500-tcp-stack/14524) + +--- + +## 4. 约束分析:为什么最正统形态(B 类)在本项目不可达 + +B 类(LwIP netconn)的三个构件在本项目的对应物: + +| LwIP 构件 | LwIP 里的形态 | 本项目对应物 | 差距 | +|---|---|---|---| +| 数据到达观测 | 网卡 DMA/IRQ → pbuf 递到 tcpip 线程(免费,O(1)) | 数据到达在 CH395F **芯片内部**;唯一观测手段 = SPI 事务(GET_REMV_LEN / socket 中断状态),而 SPI 只许 netTask 发起 | **观测本身占用执行者** → 等待无法挪出 netTask | +| 消费者侧睡眠 | 应用线程睡在自己的 sem/mailbox 上(带超时) | 调用方睡在任务通知上(`net_wait_result` 两段式等待,带全掩码清除) | **已一致** ✓ | +| 完成通知 | tcpip 线程事件驱动 signal | 当前 = 等待自旋结束才通知;pending 方案 = 主循环检测到完成条件即通知 | 差"事件完成"一步 | + +补充两条不可行路径: + +1. **消费者自行轮询就绪**(如读 INT# GPIO 电平):INT# 只是"某 socket 有事件"的全局电平, + 非 per-connection;且 AGENTS.md 明令"所有 CH395F 中断在 `net_poll()` 内集中读取并清除, + **不得**在 ISR 或其他地方单独处理"——纪律上禁止。 +2. **ISR 里读 per-socket 状态**:需要 SPI 事务,CH395F 驱动非 ISR 安全(设计即"中断集中读取")。 + +**结论**:本项目的等待只能在 netTask 内执行,设计自由度 = ①每次占用时长(现状:分片封顶 5ms) +②等待期间是否/如何做其他事(现状:仅片间隙排空队列)。pending 方案 = 把自由度 ② 用满。 + +--- + +## 5. 现状方案(分片有界等待)的正当性 + +1. **官方认可**:Node 文档把"分片 + 定期让出"(partitioning)列为不阻塞事件循环的**一等官方解法** + (与 offload 并列;offload 在本项目不可行——SPI 独占)。5ms 片 + `vTaskDelay(1)` 让出即逐字 + 对应。 +2. **生产级先例**:librdkafka 后台 I/O 线程 = 短超时 poll + 间隙排空队列,"有界执行者等待" + 在生产级客户端里被接受。 +3. **E 类标准实践**:offload 芯片驱动族的通行形态 = 非阻塞原语 + 调用方预算循环 + (`net_recv_to` + 5ms 片 + 30s 总预算与之同构)。 +4. **变体选择有据**:审计方向 ① 原文为 "DONTWAIT 分片 + 调用方重投",实际落地选**有界阻塞片**。 + 理由:DONTWAIT 每片一次完整队列往返(入队+调度+取消息+通知,L5 实测 ~1.2ms),30s 等待 = + ~2.5 万次往返;有界片让 netTask 片内连续多次 `net_poll`,片内到达的数据当片即收。M3 实测 + 支持:100ms 片受压帧 ~750ms → 5ms 片 ~10ms。 +5. **已知残余**(如实留档): + - 队列最坏滞后一个片(5ms)+ 往返——pending 方案可压到 ~1ms(主循环节拍); + - SEND 的 1s/分片 stall 仍冻结队列(审计 E-02 位置明列)——pending 方案二期覆盖; + - **依赖调用方纪律**:新消费者若随手调 `net_recv(fd, buf, len, 0)`(默认 30s、无分片)即复发 + 头阻塞——pending 方案结构性免疫。 + +--- + +## 6. 演进方案:RECV 消息 → pending 请求 + +### 6.1 机制 + +- `net_sock_t` 增加 pending 字段:`buf 指针 / len / flags / deadline(tick) / caller 句柄 / active` + (≈20B/fd × 8 fd ≈ 160B 静态增长); +- `net_process_messages` 的 RECV 分支:数据已在 → 立即读出(**现快速路径不变**);数据未至且 + `timeout_ms > 0` → **登记 pending、跳过本次通知、继续循环取下一条消息**; +- 新增 `net_pending_tick()`(与 `net_relisten_retry_tick` 同位——主循环每轮调用),对每个带 + pending 的 fd 走五路完成/取消: + + | # | 条件 | 动作 | + |---|---|---| + | 1 | 数据到达(`readable` 标志已由 net_poll 每轮更新,零额外 SPI) | SPI 读出 + 通知调用方(结果 = 字节数) | + | 2 | `HAL_GetTick() >= deadline` | 通知(`NET_ERR_TIMEDOUT` → 对外 -2 映射保持) | + | 3 | EOF/DISCONNECT 事件(`handle_disconnect_event` 路径发现 pending) | 按 0(EOF) 完成 + auto_relisten + 通知 | + | 4 | 同 fd 的 CLOSE 等后续操作 | per-fd 串行:先完成 pending 再执行(冻结面从"全网"缩到"本 fd") | + | 5 | 网络重建(`net_stack_bring_up`,芯片复位) | **取消 + 必须通知**(调用方阻塞等回执,取消不通知 = 永久挂死) | + +- **调用方与公开 API 不变**:仍 `net_wait_result` 阻塞等通知(30s 睡眠发生在调用方自己上下文 + ——与 LwIP 模型一致);`net_recv`/`net_recv_to` 返回契约不变,测试/模拟器/既有调用方零改动。 + +### 6.2 好处 + +1. **头阻塞缺陷类消灭**(而非封顶):等待期间 netTask 零额外占用,队列延迟 = 一个主循环周期 + (~1ms); +2. **不再依赖调用方纪律**:任意新消费者写裸 `net_recv(..., 0)` 也不会冻结队列; +3. **调用方简化**:lftpd/timeSync 的分片循环 + `vTaskDelay(1)` 让出协议(各 ~30 行)删除, + 还原为一次 `net_recv_to(fd, ..., 30000)`; +4. **E-06/E-20/E-21 共用机制**:accept 轮询循环(100ms/200ms)、SEND 1s/分片 stall 同型转化 + (ACCEPT pending = CONNECT 事件完成;SEND 续传 = send_ready 完成)——第二档 #1 四条目收敛 + 为一个机制; +5. **长等待 SPI/CPU 成本归零**:30s 等待从 ~6000 次专属轮询降为搭主循环既有 `net_poll` 的便车。 + +### 6.3 坏处 / 代价 + +1. **执行者获得跨时间状态 → 边界交互面变大**:§6.1 五路语义每条都是新的时序边界,须逐一定义 + 并测试——本库 D 系列(D10~D20)历史即此类边界的坟场; +2. **只改 RECV 则收益对折**:SEND 1s/分片 stall 仍在(需二期); +3. **超时精度退化到主循环节拍**:主循环若正被长操作(如 SEND stall)占用,RECV 超时最多顺延 + 相应时长(现状是反方向,打平,需文档化); +4. **验证成本 >> 代码量**:net 层是回归最敏感区(09-21 事故、D 系列),执行器核心改动 = 全 + 套回归 + 基线重锁,台架投入天量级; +5. **新故障类**:"pending 登记后永不完成"(完成条件 bug / fd 状态与芯片失步)→ 调用方无限期 + 睡眠——需配套看门狗/自检(pending 最大龄告警); +6. **模拟器差距 +1**:`net_socket_sim.c`(单线程直连 Winsock)无法建模 pending,附录 D 结构性 + 差距清单加一条; +7. **排障文档需更新**:"结果缺失"类问题新增成因(pending 滞留),`net_wait_result` 两段式 + 等待与 [LAT] 探针的假设需补充说明。 + +### 6.4 改动量 + +| 阶段 | 内容 | 规模 | +|---|---|---| +| 一期(RECV) | `net_sock_t` pending 字段;RECV 分支"有数据即读 / 无数据登记";`net_pending_tick()` 五路完成/取消;close/rebuild 取消钩子 | **~200-400 行,集中在 `net_socket.c`;公开 API 零改动** | +| 调用方简化(可随一期) | lftpd `read_line`/`read`、timeSync `handle_conn` 分片循环 → 单次调用 | 各删 ~30 行 | +| 二期(可选:ACCEPT + SEND) | ACCEPT pending(E-06 accept 计时 / E-20 / E-21 轮询循环退役);SEND 续传 pending(1s stall 非占用化) | 再 ~300-500 行;L3/L5 基线再变 | + +风险评级:**中**(执行器核心 + D 系列前科)。代码量不是主要成本,验证才是。 + +### 6.5 验证门禁(实施时逐项过) + +1. CH395F NET 层全套件(Phase 6-14); +2. M3 基线重锁:L5 队列往返(预期 ~1ms 量级)、L3/L7 生产路径 Δ; +3. `test/e02_probe.py` A/B 回归(队列响应性应优于 5ms 片方案); +4. `test/ftp_regression.py`(D20)+ E-06 假 425 场景; +5. UI 远端推流压力(ui_net_sink 100Hz); +6. timeSync 三场景(挂起 / 部分包 / 正常包); +7. 产品构建 `@test` 闭环 + ST-LINK 全片回读; +8. `scripts/ram_report.py` 前后对比(铁律);`scripts/stack_report.py` 栈水位。 + +### 6.6 风险缓解 + +- 一期只做 RECV;**保留分片路径作影子对照/回退开关**(双路径并行验证后再摘除); +- 复用 D10 `net_relisten_retry_tick` 的"登记→到点完成"骨架(已在生产验证); +- 探针族扩充:close-during-pending / reconfig-during-pending / EOF-during-pending / + disconnect-during-pending(扩展 `e02_probe.py`); +- pending 最大龄自检(selftest 留痕,防"永不完成"); +- 排障文档同步(`错误码传播审计.md` / `链路延迟测试说明.md` §9 故障对照表)。 + +--- + +## 7. 结论与实施建议 + +1. **现状(分片)安全有效,不必急着演进**——残余风险可控且已基线锁定;pending 是结构演进, + 不是缺陷修复,无时间压力。 +2. **实施时机 = 第二档 #1 排期窗口**:E-06/E-20/E-21 迟早要做,pending 是四条的公共地基, + 一次做齐比四个独立补丁便宜。 +3. **路线**:一期 RECV(影子验证)→ 二期 ACCEPT → SEND(最复杂:多分片/短发送/L3 探针钩子)。 +4. **启动动作**:以 §6.1 为设计基线评审 → 按 §6.5 门禁执行 → 完成后按 E-01/E-31/E-32/E-02 + 的留档惯例更新 `错误码传播审计.md`(明细"已修复"+ 页脚闭环注记 + 提交记录)。 + +**一句话**:好处 = 缺陷类消灭 + 调用方解放 + E-06/20/21 共用机制;坏处 = 执行器状态机复杂化 ++ 天量级台架验证;改动本身不大(一期数百行、API 不变),大的是验证。 + +--- + +## 8. 参考链接(2026-09-22 访问) + +| 来源 | 用途 | +|---|---| +| [Node.js:Don't Block the Event Loop (or the Worker Pool)](https://nodejs.org/learn/asynchronous-work/dont-block-the-event-loop) | A 类:never block 规则 + partitioning 官方解法 | +| [LwIP raw API 文档(2.0.x)](https://www.nongnu.org/lwip/2_0_x/raw_api.html) | B 类:tcpip_thread / message passing / never block / tcp_poll | +| [EEVblog:Anyone here familiar with LWIP?](https://www.eevblog.com/forum/microcontrollers/anyone-here-familiar-with-lwip/75/) | B 类:tcpip 线程不阻塞的设计陈述与社区讨论 | +| [SO:LWIP + RTOS how to avoid netconn block the thread forever](https://stackoverflow.com/questions/10848851/lwip-rtos-how-to-avoid-netconn-block-the-thread-forever) | B 类:netconn 阻塞超时/非阻塞的常见问法 | +| [mbed-os issue #13197:Non-blocking recv with lwip](https://github.com/ARMmbed/mbed-os/issues/13197) | B 类:LwIPSocket 非阻塞实现的真实案例 | +| [Zephyr:net_context API](https://docs.zephyrproject.org/latest/doxygen/html/net__context_8h.html) | C 类:回调在 RX 线程上下文执行 | +| [ESP-IDF:Event Loop Library](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/esp_event.html) | C 类:事件循环 + 队列 | +| [librdkafka INTRODUCTION](https://github.com/confluentinc/librdkafka/blob/master/INTRODUCTION.md) | D 类:多线程库 + 有界 poll | +| [WaterDrop:One Thread to Poll Them All(librdkafka 后台线程剖析)](https://mensfeld.pl/2026/02/waterdrop-fd-polling-50-percent-faster/) | D 类:后台线程 `rd_kafka_poll(timeout)` 循环 | +| [WIZnet W5500 产品页](https://wiznet.io/products/ethernet-chips/w5500) | E 类:同类 offload 芯片定义 | +| [FreeRTOS 论坛:Handle several sockets (W5500)](https://forums.freertos.org/t/handle-several-sockets-w5500-tcp-stack/14524) | E 类:多任务共享 offload 芯片的加锁/串行化实践 | +| 库内:`错误码传播审计.md` E-02 明细 / `docs/测试说明/链路延迟测试说明.md` §8.3 / AGENTS.md SPI 铁律 | 本项目约束与已验证基线 | diff --git a/docs/问题记录/错误码传播审计.md b/docs/问题记录/错误码传播审计.md index 7d9dc83a..2585588d 100644 --- a/docs/问题记录/错误码传播审计.md +++ b/docs/问题记录/错误码传播审计.md @@ -11,7 +11,8 @@ > + 父审计组(net 核心/事件标志/UI/启动路径)交叉覆盖,**全部关键发现经父审计组逐行代码复核**(含芯片手册 > 原文核对)后才入册。 > **配套文档**:`docs/项目审计改进清单.md`(交叉引用见 §6)、`docs/问题记录/*_Trap_Records.md`(模块陷阱史)、 -> `docs/网络配置说明.md`、`AGENTS.md`(09-21 教训已固化条目)。 +> `docs/网络配置说明.md`、`AGENTS.md`(09-21 教训已固化条目)、`docs/问题记录/Net层阻塞模型业界对照与演进方案.md` +> (E-02 配套:业界模式对照 + pending 请求演进方案利弊/改动量,第二档 #1 排期时用)。 --- @@ -398,7 +399,7 @@ E-60"write_enable WRTC2 与手册矛盾"经三方复核(手册表头 D7~D0 顺 | # | 项 | 内容 | |---|---|---| -| 1 | **E-02+E-06+E-20+E-21** | 阻塞模型改造:lftpd 控制通道/timeSync 改 `DONTWAIT` + 调用方状态机(ui_sink 模式推广);`net_process_messages` 长阻塞间隙按 tick 继续排空队列;`accept_timeout` 墙钟计时 + 去跨任务 state 直读。**E-02 部分已闭环(2026-09-22,f8d6e90+4a1605b)**:两触发源改 `net_recv_to` 分片有界读(落地方案 = 有界阻塞片,非 DONTWAIT 重投),台架 A/B 实证;E-06/E-20/E-21 部分仍待办 | +| 1 | **E-02+E-06+E-20+E-21** | 阻塞模型改造:lftpd 控制通道/timeSync 改 `DONTWAIT` + 调用方状态机(ui_sink 模式推广);`net_process_messages` 长阻塞间隙按 tick 继续排空队列;`accept_timeout` 墙钟计时 + 去跨任务 state 直读。**E-02 部分已闭环(2026-09-22,f8d6e90+4a1605b)**:两触发源改 `net_recv_to` 分片有界读(落地方案 = 有界阻塞片,非 DONTWAIT 重投),台架 A/B 实证;E-06/E-20/E-21 部分仍待办(候选公共地基 = pending 请求机制,利弊/改动量/验证门禁见 `docs/问题记录/Net层阻塞模型业界对照与演进方案.md` §6) | | 2 | **E-05** | `net_socket/net_bind/net_getsockname` 并入队列(新消息类型);过渡方案:alloc+init 临界区 + `s_dynamic_port` 收归 netTask | | 3 | **E-10/E-42/E-53/E-54** | GD5F 驱动错误通道统一:init 重试 1~2 次;`wait_busy`/轮询路径 HAL 检查;ECC 使能回读校验;错误向上传递 | | 4 | **E-22** | lftpd/timeSync 向 ui_sink close 规则看齐(EOF 不 close、auto_relisten 常驻、仅代次变化/判死时重建) |