共用中转槽方案定案:+3KB(数据筐2KB+单槽),Trap21图解与N-7同步
This commit is contained in:
+1
-1
@@ -13,7 +13,7 @@
|
||||
| N-2 | **49s 全系统日志静默(defaultTask/NAND syslog 排空停滞)**——串口日志 49s 无行(testTask 10s 周期行/netTask 事件行/[STACK] 行全缺),期间 net 栈照常处理外部连接(FTP banner 92ms)→ 日志管线(defaultTask 排空 → NAND+UART)停滞,队列满后各任务日志行被丢;系统功能不受损 | 2026-09-22 17:23:52~17:24:41(L3 台架 F2 运行中,gen 181) | **A/B 归因:L3 不触碰 defaultTask/syslog 路径 → 既有存储侧现象**。待办:① 复现条件定位(NAND 写/GC 长尾 vs UART TX 卡滞——对照 `CH395F` 无关的 SD2506 侧日志)② 日志队列满的丢弃计数/告警(现静默丢行,排障不可见)③ 与 S8(非阻塞日志)合并评估 | `错误码传播审计.md` E-02 阶段二"台架新发现②";`链路延迟测试说明.md` §6.7 注 |
|
||||
| N-3 | **L5 队列往返基线(1223/988µs)的 L3 后重验**——L5 测 DONTWAIT 队列全程;L3 对该路径逐位不变、pending tick 无 pending 时为 µs 级指针读 → 机制上无影响,但基线数值属分片时代,严格重锁需跑一轮 Phase 13(需 PC peer,窗口与 N-1 定位可并案) | 2026-09-22(L3 收尾留档) | 低风险:10ms/100ms 阈值余量 >8×;下次 Phase 13 窗口顺带重锁即可 | `链路延迟测试说明.md` §6.4 L5 注¹ |
|
||||
| N-6 | **Trap 22:快速重连致 CONNECT 中断丢失 → 单连接监听静默死亡(P1,修复待批准)**——轻负载 + 重监听历史 + 断连后 <~1s 重连:芯片握手正常但 MCU 无 standalone accept(状态滞留 LISTENING)→ `net_send` 全失败(-1)→ 断开时 DISCONNECT 落 LISTENING 分支仅告警 → 芯片自动关闭 Socket → 端口永久拒连(不自恢复) | 2026-09-22(Trap 21 二次台架 A 组 run2;出现 1/1,未做 10/10 规模复验) | 修复方案:`handle_disconnect_event` LISTENING 分支回读芯片 socket 状态,非可监听即 `net_relisten_try` 重建(D20 的 LISTENING 分支姊妹情形)+ 可选:重监听后清残留中断位;应用侧规避:单连接 socket 断连后重连 ≥1~2s 退避(lftpd 快速 PASV / 客户端快速重试是生产风险场景)。生产影响:数据通道监听静默死亡 → 后续数据连接全拒,严重度等同端口失效 | `CH395F_Trap_Records.md` Trap 22 |
|
||||
| N-7 | **Trap 21 生产修复:net 层非阻塞 RX 读强制排空至空(P1,待批准实施)**——致命类 = 部分窗口重通告(排空后缓冲残留):大 Δ(1KB 块读,C/C0 配方)10/10 确定性停摆;小 Δ(64B)相位刀口(一次性落点 10/10 过 vs 5/5 停摆仅数 ms 时移之差)且持续重复必停摆(50/50);排空至空(窗口→满)恒安全(40/40,含 10ms 节拍 E0)、Δ=0 命令(状态读/中断读/poll/TX)恒安全 → **无需时间守卫/冷却**(旧"排空后 RX 暂缓 + 120ms 冷却"设计作废:C0 证明部分排空自身即致命,E0 证明排空至空后 10ms 再排空安全) | 2026-09-22(精确定位轮:E/O/B/F1/S1–S4/C/C0/V1F/O1f 共 11 组台架 110+ 运行 + netcmd 命令序列日志) | 实施要点(2026-09-22 复核细化,完整方案见 `问题记录/CH395F_Trap21_机制图解.md` §5):net 层新增 `net_drain_recv()` 助手,替换 5 处 `min(货架现有, 调用方len)` 读路径——`net_recv_locked:1205`、`net_pending_tick:1514`/`:1536`、`net_pending_settle_close:1618`、`net_recvfrom:2072`:先交付中转筐 → 筐空则 `avail = get_recv_len()` 后**按 avail 全量读空**入筐 → 只交 `min(avail, len)` 给调用方,余量留筐、下次不发芯片命令直接交付(上层筐大小与解析逻辑零改动);中转筐按生产并发 socket 配 4 槽 × 2048B = 8KB(主 SRAM 余量 47.2KB,占 17%);槽位全占时该读暂缓(WOULDBLOCK / pending 继续等),不发命令即无残余。**只改 lftpd 数据筐(1KB→2KB)不足以覆盖**:控制行读、UI 64B、时间同步 16B 为同型雷(且单纯加大控制行缓冲会丢流水线命令)。台架回归:C/C0 与 V1 配方由 10/10 停摆 → 10/10 通过,E 系列保持 40/40;Δ=0 命令(状态读/poll/TX)不受限 | `CH395F_Trap_Records.md` Trap 21;`CH395F_Trap21_机制图解.md`;`Drivers/BSP/NET/net_socket.c`(5 处读路径);`Drivers/BSP/NET/lftpd/lftpd.c` receive_file 块循环 |
|
||||
| N-7 | **Trap 21 生产修复:net 层非阻塞 RX 读强制排空至空(P1,待批准实施)**——致命类 = 部分窗口重通告(排空后缓冲残留):大 Δ(1KB 块读,C/C0 配方)10/10 确定性停摆;小 Δ(64B)相位刀口(一次性落点 10/10 过 vs 5/5 停摆仅数 ms 时移之差)且持续重复必停摆(50/50);排空至空(窗口→满)恒安全(40/40,含 10ms 节拍 E0)、Δ=0 命令(状态读/中断读/poll/TX)恒安全 → **无需时间守卫/冷却**(旧"排空后 RX 暂缓 + 120ms 冷却"设计作废:C0 证明部分排空自身即致命,E0 证明排空至空后 10ms 再排空安全) | 2026-09-22(精确定位轮:E/O/B/F1/S1–S4/C/C0/V1F/O1f 共 11 组台架 110+ 运行 + netcmd 命令序列日志) | 实施要点(2026-09-22 复核细化,完整方案见 `问题记录/CH395F_Trap21_机制图解.md` §5):net 层新增 `net_drain_recv()` 助手,替换 5 处 `min(货架现有, 调用方len)` 读路径——`net_recv_locked:1205`、`net_pending_tick:1514`/`:1536`、`net_pending_settle_close:1618`、`net_recvfrom:2072`:先交付中转筐 → 筐空则 `avail = get_recv_len()` 后**按 avail 全量读空**入筐 → 只交 `min(avail, len)` 给调用方,余量留筐、下次不发芯片命令直接交付(上层筐大小与解析逻辑零改动);中转槽设计(2026-09-22 复核:**一个共用槽即可**,8KB 方案作废):数据通道自带筐 1KB→2KB(`lftpd.c:187`,`len=2048 ⇒ min(avail,len)==avail`,天然读必读空、永不入槽、不参与争用),控制行 512B / UI 64B / 时间同步 16B 共用**一个 2048B 槽**;槽被别的 socket 占用时该读暂缓(WOULDBLOCK / pending 继续等),不发命令即无残余 → 合计 **+3KB**(主 SRAM 余量 47.2KB,占 6.3%),槽数 1→2 仅一行改动;争用暂缓记限频日志。**只改 lftpd 数据筐不足以覆盖**:控制行读、UI 64B、时间同步 16B 为同型雷(且单纯加大控制行缓冲会丢流水线命令;零槽方案需 +6.5KB 并重构控制行读取器)。台架回归:C/C0 与 V1 配方由 10/10 停摆 → 10/10 通过,E 系列保持 40/40;Δ=0 命令(状态读/poll/TX)不受限 | `CH395F_Trap_Records.md` Trap 21;`CH395F_Trap21_机制图解.md`;`Drivers/BSP/NET/net_socket.c`(5 处读路径);`Drivers/BSP/NET/lftpd/lftpd.c` receive_file 块循环 |
|
||||
|
||||
## 存储 / 日志管线
|
||||
|
||||
|
||||
@@ -118,19 +118,34 @@ flowchart TD
|
||||
|
||||
net 层新增 `net_drain_recv(sockfd, buf, len)`,替换第 4 节表中的 5 处:
|
||||
|
||||
1. 该 socket 的中转筐有货 → **直接交付**(不发芯片命令);
|
||||
2. 中转筐空 → `avail = ch395f_get_recv_len(fd)`(Δ=0,安全);
|
||||
3. **按 `avail` 全量读空**进中转筐(`read_len == avail`,恒排空至空);
|
||||
4. 拷 `min(avail, len)` 给调用方,**余量留在筐里**,下次直接交付。
|
||||
1. **共用中转槽**里若有本 socket 的余量 → **直接交付**(不发芯片命令);
|
||||
2. 槽空闲 → `avail = ch395f_get_recv_len(fd)`(Δ=0,安全);
|
||||
3. **按 `avail` 全量读空**进槽(`read_len == avail`,恒排空至空);
|
||||
4. 拷 `min(avail, len)` 给调用方,**余量留在槽里**(登记 owner fd / 偏移 / 长度),下次直接交付;
|
||||
5. 槽被**别的** socket 占用 → **暂缓本次读**(不发芯片命令),DONTWAIT 返 WOULDBLOCK、pending 保持等待。
|
||||
|
||||
驱动层无需改动(一次 SPI 事务本来就能搬满 2KB)。
|
||||
|
||||
### 5.3 中转筐设计与 RAM 账
|
||||
### 5.3 中转筐设计:**一个共用槽就够**(+3KB,不是 8KB)
|
||||
|
||||
- 每槽 2048B;**按生产并发 socket 数配槽**:lftpd 控制 + lftpd 数据 + UI sink + 时间同步 = 4 槽 = **8KB**。
|
||||
- RAM 实测(2026-09-23 map):主 SRAM 静态 82,760 / 131,072 B → **余量 47.2KB**,8KB 约占余量 17%,可接受(预算口径见 `docs/内存管理/`)。
|
||||
- **槽位全占**时:该次读**暂缓**(DONTWAIT 返回 WOULDBLOCK;pending 继续等待),**不发芯片命令 → 不产生残余 → 不会封嘴**;槽位由该 socket 的下一次读立即释放,不会长期饿死。
|
||||
- 上层**零改动**:调用方的筐仍是原大小,每次拿到的字节数范围不变 → 按行/按帧/按字节的解析逻辑完全不受影响。
|
||||
**可以共用。** 共用的安全性不靠"没人同时用",而靠一条铁律:**槽被占用时,别的 socket 就不发芯片命令**(DONTWAIT 返 WOULDBLOCK、pending 继续等)——不发命令 = 不产生残余 = 不会封嘴。**共用只影响等待时间,不影响安全。**
|
||||
|
||||
但要共用,必须先让**热路径自己不占槽**:
|
||||
|
||||
| 读方 | 筐 | 处理 |
|
||||
|---|---|---|
|
||||
| lftpd 数据通道(STOR,唯一大流量) | 1024B → **2048B(+1KB,`lftpd.c:187`)** | 货架最多 2048B,`len=2048` ⇒ `min(avail,len) == avail` ⇒ **天然读必读空**,永不入槽、永不参与争用 |
|
||||
| lftpd 控制行读(`read_buffer[512]`) | 512B | 共用槽(客户端流水线发命令时才留残余) |
|
||||
| UI sink(`RM_RX_BUF=64`) | 64B | 共用槽(帧大于 64B 时) |
|
||||
| 时间同步(栈上 16B) | 16B | 共用槽 |
|
||||
|
||||
- **为什么热路径不能共用**:它的循环是 `net_recv → f_write(NAND) → net_recv`,而 NAND 写入有长尾(GC/擦除;本仓库已记录过百毫秒级停顿)。若它占着共用槽去写盘,**其他 socket 全部读不了**(FTP 控制通道哑掉、UI 卡顿)→ 所以给它自带 2KB 筐,直接退出争用。
|
||||
- **冷读方的占槽时间**:读 → 解析 →(必要时)发应答,全是 CPU 短动作(控制行 = 解析 + `net_send`,TX 命令不碰货架;UI = 逐字节喂解析器 + 失效重绘,真正渲染由 LVGL 后续处理;时间同步 = 解析 8B),毫秒级;而这几个通道的预算都是 30s 级,完全容忍。
|
||||
- **RAM 账**:共用槽 2048B + 数据通道加筐 1024B = **+3KB**(主 SRAM 余量 47.2KB,占 6.3%)。若现场显示确有争用,把槽数 1 → 2(+2KB)即可,是一行改动。
|
||||
- **可观测性**:因槽被占而暂缓时记一次限频日志(如 `stage busy fd=N`),别让"变慢"成为新的黑盒。
|
||||
- 上层**零改动**(除 lftpd 数据筐 1024→2048):调用方筐大小与每次拿到的字节数范围都不变 → 解析逻辑不受影响。
|
||||
|
||||
**被否决的"零槽方案"**(所有调用方自带 2KB 筐、完全不设槽):账上要 **+6.5KB**(数据 +1KB、控制行 512→2048 需 +1.5KB、UI +2KB、时间同步 +2KB),而且控制行读必须重构成"带余量的行读取器"(`lftpd_inet_read_line` 一旦找到 `\r\n` 就返回,多读进来的流水线命令会被丢弃),要改 4 个子系统且没有强制手段——比 +3KB 的共用槽更贵、更脆。
|
||||
|
||||
### 5.4 被否决的方案(及原因)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user