diff --git a/MDK-ARM/keil-build-viewer-record.txt b/MDK-ARM/keil-build-viewer-record.txt index 30fdeda..4e9ed0f 100644 --- a/MDK-ARM/keil-build-viewer-record.txt +++ b/MDK-ARM/keil-build-viewer-record.txt @@ -17,7 +17,7 @@ 778 0 0 2 0 0 ip4.o 46 0 4 0 0 0 ip4_addr.o 44 0 0 0 12 0 iwdg.o - 2842 0 185 12 272 0 main.o + 3212 0 185 12 272 0 main.o 828 0 0 12 4115 0 mem.o 196 0 244 32 6464 0 memp.o 582 0 0 12 0 0 netif.o @@ -43,13 +43,13 @@ 490 0 0 0 0 0 stm32f1xx_it.o 2 0 24 4 0 0 system_stm32f1xx.o 3474 0 193 32 0 0 tcp.o - 1232 0 0 0 1120 0 tcp_client.o + 1556 0 0 0 1072 0 tcp_client.o 3684 0 0 36 20 0 tcp_in.o 3862 0 0 0 0 0 tcp_out.o - 986 0 0 0 1104 0 tcp_server.o + 1346 0 0 0 1048 0 tcp_server.o 164 0 0 0 72 0 tim.o 374 0 16 12 0 0 timeouts.o - 1538 0 0 0 2936 0 uart_trans.o + 1590 0 0 0 2936 0 uart_trans.o 816 0 0 0 624 0 usart.o Object Totals @@ -57,8 +57,8 @@ Memory Map of the image Load Region LR_IROM1 - Execution Region ER_IROM1 (Exec base: 0x08000000, Size: 0x0000D72C, Max: 0x00010000, END) + Execution Region ER_IROM1 (Exec base: 0x08000000, Size: 0x0000DB7C, Max: 0x00010000, END) - Execution Region RW_IRAM1 (Exec base: 0x20000000, Size: 0x00005000, Max: 0x00005000, END) + Execution Region RW_IRAM1 (Exec base: 0x20000000, Size: 0x00004F98, Max: 0x00005000, END) Image component sizes \ No newline at end of file diff --git a/项目技术实现.md b/项目技术实现.md index 9327c80..4eff7d2 100644 --- a/项目技术实现.md +++ b/项目技术实现.md @@ -184,6 +184,68 @@ EN,LPORT,RIP,RPORT,UART 2. 统一受 `LINK[idx]` 配置驱动 3. 由调度层决定实例与 UART 的数据交换路径 +### 6.4 `v1.1.0` 低 RAM TCP 背压修复 + +`v1.1.0` 起,`TCP -> UART` 路径补充如下实现约束,用于解决“TCP 接收过快、UART 发送过慢时本地缓存被冲垮”的问题,同时尽量不新增静态 RAM: + +1. 继续复用 `tcp_server` / `tcp_client` 现有 `RX ring`,不为每个连接新增独立的大块 pending payload 缓冲。 +2. `tcp_server_on_recv()` / `tcp_client_on_recv()` 不再在回调内立即 `tcp_recved()`。 +3. lwIP 交来的 `pbuf` 在回调中通过 `pbuf_ref()` 转为应用持有,再释放回调上下文的原始引用;后续由应用在主循环中继续把数据泵入 `RX ring`,最终在消费完成后释放。 +4. 当 `RX ring` 暂时装不下时,剩余数据保留在 `hold_pbuf + hold_offset` 中,等待主循环下一轮继续搬运。 +5. 只有当数据真正从 `TCP RX ring` 被 `drop` 掉,也就是已经被下游 `UART` 接收进入发送路径时,才调用 `tcp_recved()` 释放 TCP 接收窗口。 + +这样做的效果是: + +1. `UART` 慢时,TCP 窗口不会继续无条件放大。 +2. 对端发送速度会被 lwIP 接收窗口自然压制。 +3. 修复点建立在已有 ring 与主循环调度之上,不引入 `FreeRTOS` 或新的大块静态缓存。 + +#### RAW 与 MUX 的分流规则 + +在 `v1.1.0` 中,`TCP` 侧拿到的都是纯 payload,因此 `TCP` 背压逻辑在 `RAW` 与 `MUX` 两种模式下共用到 `UART commit` 之前: + +1. `RAW` 模式: + - 主循环先查看 `uart_trans_tx_free()` + - 再按 `min(tcp_available, tx_free, APP_TCP_TO_UART_CHUNK_SIZE)` 从 TCP ring `peek` + - `uart_trans_write()` 实际写入多少,就 `drop + tcp_recved` 多少 +2. `MUX` 模式: + - `TCP` payload 本身不带帧头尾 + - 只有当 `UART TX free >= payload_len + 6` 时,才在栈上临时编码一帧并一次性写入 `UART TX ring` + - 只有整帧成功入队后,才按原始 payload 长度执行 `drop + tcp_recved` + +该设计保证: + +1. `RAW` 模式允许流式逐步提交 +2. `MUX` 模式保持“单个 UART 输出帧必须完整入队”的语义 +3. `TCP` 接收窗口始终以真实下游消费进度为准,而不是以“回调里已经 memcpy 到本地”作为提交点 + +#### RAM 与 chunk 策略 + +为给新增的 `hold_pbuf / hold_offset` 状态字段让位,并进一步降低单轮转发压力,`v1.1.0` 同步采用以下策略: + +1. 新增 `APP_TCP_TO_UART_CHUNK_SIZE = 128` +2. `TCP_SERVER_RX_BUFFER_SIZE` 从 `512` 调整为 `480` +3. `TCP_CLIENT_RX_BUFFER_SIZE` 从 `512` 调整为 `480` + +设计意图: + +1. 利用更小的单次转发块提升主循环调度颗粒度 +2. 让 `MUX` 模式下 `payload + 6` 更容易完整进入 `UART TX ring` +3. 在静态 RAM 已接近上限时,为少量新状态字段回收空间 + +#### 构建基线 + +`v1.1.0` 以 `MDK-ARM/TCP2UART.uvprojx` 的 `TCP2UART` Target 为构建验收基线。 + +当前一次通过的参考结果: + +1. `errors = 0` +2. `warnings = 0` +3. `flash_bytes = 56544` +4. `ram_bytes = 20376` + +该结果说明修复后工程仍满足 `STM32F103R8T6` 的 `20KB RAM` 上限,但余量已经很小;后续若继续增加功能,应优先考虑复用现有缓冲与状态,而不是增加新的静态大数组。 + ## 七、主循环实现方向 主循环仍保持裸机轮询风格: