当前位置: 首页 > news >正文

网络数据包

LwIP 网络数据包(pbuf 与 ethernetif 驱动)详解与实践

本文系统梳理 LwIP 的 pbuf 数据包与以太网端口驱动接口 ethernetif(以 STM32 HAL 代码为例),结合内存管理(memp/mem)说明 pbuf 类型、内存来源、关键 API、典型数据流、零拷贝思路,以及常见问题与调优建议,提升可读性与可操作性。


1. 为什么 LwIP“模糊分层”(性能取舍)

  • 标准 TCP/IP 栈严格分层,层间通过清晰 API 交互,通常导致多次内存拷贝与多线程切换,嵌入式环境成本高。
  • LwIP 在“栈内部”允许跨层可见(例如 TCP 层直接读取 IP 首部信息),尽量复用同一块内存,降低拷贝与调度开销。
  • 对“外部应用 API”仍保持清晰接口(RAW、NETCONN、Socket),方便移植与开发。

结论:LwIP 用 pbuf 将数据包在各层共享,减少拷贝;驱动层 ethernetif 负责将硬件收/发的数据搬运到/从 pbuf。


2. pbuf 概览:结构、类型、内存来源

在这里插入图片描述
pbuf 是协议栈的数据包描述符,支持单个或链式。核心字段(简化):

  • next:下一段 pbuf(链表)
  • payload:指向有效数据的起始地址
  • len:本 pbuf 段的有效负载长度
  • tot_len:从本段开始到链尾的总长度
  • ref:引用计数(>1 表示被多处引用)
  • type_internal/flags:类型与标志位(含内存来源)

在这里插入图片描述

pbuf 的类型(按用途与内存来源):

  • PBUF_RAM:在堆(mem_malloc)上分配一块连续内存,struct pbuf 与 payload 同块内存。常用于发送(TX)。
  • PBUF_POOL:从“pbuf 池”(MEMP_PBUF_POOL)分配,struct pbuf 与 payload 同块内存,固定块大小。常用于接收(RX)。
  • PBUF_ROM:仅分配 struct pbuf(MEMP_PBUF),payload 指向只读常量区(不拷贝数据)。
  • PBUF_REF:仅分配 struct pbuf(MEMP_PBUF),payload 指向外部 RAM(数据可变,通常需要复制或谨慎管理生命周期)。
    在这里插入图片描述
    内存池对应关系:
  • MEMP_PBUF:仅存放 struct pbuf(供 PBUF_ROM/PBUF_REF 等使用)。
  • MEMP_PBUF_POOL:存放“包含头部+数据区”的 pbuf(即 PBUF_POOL 类型)。

关键内存配置:

  • PBUF_POOL_BUFSIZE:每个 PBUF_POOL 块大小,建议 ≥ MTU + 链路层头(以太网常用 1514/1520;1460+40+14=1514)。
  • PBUF_POOL_SIZE:PBUF_POOL 块数量。
  • MEMP_NUM_PBUF:struct pbuf 池数量(建议 ≥ PBUF_POOL_SIZE 再加裕量)。
    在这里插入图片描述
    公式参考(以太网 TCP):
  • PBUF_POOL_BUFSIZE ≈ TCP_MSS + 40(IP+TCP) + PBUF_LINK_HLEN(14) + 可能的封装预留
    • 536 MSS → 590(536+40+14),值得注意的是TCP头部不一定是固定20字节,最长可到60字节
    • 1460 MSS → 1514(1460+40+14)
  • 实践中常对齐至 1520 或 1536,以满足 DMA/Cache 对齐与 VLAN 头(+4B)的需要。

3. pbuf 分配的“层级”与预留头部

pbuf_alloc(layer, length, type) 的 layer 决定在 payload 前预留的协议首部空间:

  • PBUF_TRANSPORT:预留 以太网+IP(20/40)+传输层(20) 头
  • PBUF_IP:预留 以太网+IP 头
  • PBUF_LINK:仅预留 以太网头
  • PBUF_RAW_TX:预留链路层封装头(自定义封装)
  • PBUF_RAW:不预留(驱动接收用)

配合 pbuf_header()/pbuf_add_header() 可以前后移动 payload 指针,便于各层“就地”填充/剥离协议头,避免拷贝。


4. pbuf 关键 API 与行为

  • 分配与释放
    • pbuf_alloc(layer, len, type):分配 pbuf/链
    • pbuf_alloc_reference(payload,len,type):仅分配 struct pbuf,引用外部数据
    • pbuf_free§:按 ref 递减,ref 降为 0 时释放。注意必须从“链表头”释放;非头结点释放会残留严重内存泄漏或破坏链。
  • 链接与复制
    • pbuf_chain(head, tail):将两个链连接;tail 被 head 引用(ref++)
    • pbuf_cat(head, tail):将 tail 直接挂到 head 尾(调用方不能再持有 tail 的引用)
    • pbuf_copy(dst, src):把 src 数据拷贝到 dst(PBUF_RAM)
    • pbuf_clone(layer, type, src):克隆到新 pbuf
    • pbuf_coalesce(p, layer):合并链为单块(常用于发送)
  • 长度与头部调整
    • pbuf_realloc(p, new_len):裁剪链尾部长度(PBUF_RAM 可收缩堆块;其他类型只改字段)
    • pbuf_header()/pbuf_add_header()/pbuf_remove_header():调整 payload 位置
    • pbuf_free_header(q, size):移除链表头部若干字节,必要时释放头部 pbuf
  • 数据访问
    • pbuf_take(buf, dataptr, len) / pbuf_take_at(…, offset):将数据拷贝到 pbuf
    • pbuf_copy_partial(p, dst, len, offset):从 pbuf 按偏移复制
    • pbuf_get_contiguous(p, tmpbuf, tmpbufsize, len, offset):获取连续内存视图(零拷贝或拷贝到 tmpbuf)
    • pbuf_get_at/pbuf_put_at/pbuf_memcmp/pbuf_memfind/pbuf_strstr:按偏移读取/写入/比较

调试/辅助:

  • PBUF_NEEDS_COPY§:宏判断是否需要复制(默认:非 ROM 的链表要复制)
  • PBUF_POOL_FREE_OOSEQ:当 PBUF_POOL 耗尽会尝试释放 TCP 乱序队列中的 pbuf,缓解 RX 饥饿

常见陷阱:

  • 释放非链表头:导致泄漏或破坏链
  • 忘记 pbuf_ref:多处共享同一 pbuf 却未增加引用,会过早释放
  • 使用 PBUF_RAM 做零拷贝发送:若堆不在 DMA 区或 Cache 未清,可能 DMA 读不到数据
  • 长时间持有 PBUF_POOL 用于 TX:会饿死 RX(尤其高吞吐 TCP)
    在这里插入图片描述
    在这里插入图片描述

5. PBUF_POOL 与 MEMP_PBUF/MEMP_PBUF_POOL 的区别与联动

  • MEMP_PBUF:仅“pbuf 头”的池(struct pbuf),服务于 PBUF_ROM/PBUF_REF(引用外部数据)和某些场景的头结点。
  • MEMP_PBUF_POOL:完整“头+payload”的固定块池,服务于 PBUF_POOL(RX 最佳)。

分配逻辑(简化):

  • pbuf_alloc(PBUF_POOL):使用 memp_malloc(MEMP_PBUF_POOL),按 PBUF_POOL_BUFSIZE 切段组链。
  • pbuf_alloc_reference(…, PBUF_REF/PBUF_ROM):使用 memp_malloc(MEMP_PBUF) 分配结构体,payload 指向外部/常量区。
  • pbuf_alloc(PBUF_RAM):使用 mem_malloc 在堆上分配一块连续内存(头+payload)。

配置建议:

  • PBUF_POOL_BUFSIZE ≥ MTU + 头部预留;以太网通常 1514/1520;考虑 VLAN、对齐、DMA。
  • PBUF_POOL_SIZE ≥ RX 描述符数量 × 2~4 + 应用峰值冗余。
  • MEMP_NUM_PBUF ≥ PBUF_POOL_SIZE + 额外裕量(例如 +16)。

6. ethernetif 端口(STM32 HAL)详解

以你提供的 ethernetif.c 为例,驱动层职责是“把硬件收上来的帧填入 pbuf 并上送栈;把栈要发的 pbuf 复制到 DMA TX 缓冲并启动发送”。

6.1 low_level_init(netif)

  • 设置 MAC 地址、MTU、NETIF_FLAG_ETHARP/NETIF_FLAG_BROADCAST/NETIF_FLAG_LINK_UP
  • 创建信号量/邮箱/线程(示例:s_xSemaphore、tx_sem、eth_tx_mb、ethernetif_input 线程)
  • 使能中断、时钟,启动 HAL_ETH_Start
  • 注意:
    • 若 LWIP_ARP=1:建议 netif->output = etharp_output;netif->linkoutput = low_level_output
    • NO_SYS=0(有 RTOS)时,低层接收一般放线程里,或在中断里给信号量

6.2 low_level_output(netif, p)

  • 从 pbuf 链复制到一个或多个 DMA TX 缓冲(ETH_TX_BUF_SIZE 粒度),计算 framelength
  • 描述符忙时返回 ERR_USE(注意:上层通常不会重试 UDP,TCP 会因定时器重发)
  • 关键点:
    • 确保 TX 描述符环足够大,并正确遍历 Buffer2NextDescAddr
    • Cache 一致性:发送前对 TX 缓冲做 DCache Clean(若启用 Cache)
    • 避免长时间持有互斥/临界区,减少阻塞
    • 硬件支持散点聚集可减少 memcpy(高级 MAC/DMAs)

建议优化:

  • 用 TX 环管理“队列满”:阻塞等待/排队,或返回 ERR_USE 让上层有机会恢复
  • 避免在临界区内做大块 memcpy
  • 统计 TX 丢包/拥塞,指导 ETH_TX_BUF_NUM 调整

6.3 low_level_input(netif)

  • HAL_ETH_GetReceivedFrame 得到帧长与首个缓冲指针
  • p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL) 分配 pbuf 链
  • 将 RX 缓冲的数据复制到 p->payload 链(分页复制)
  • 释放所有使用过的 RX 描述符(置 OWN),清 RBUS,恢复接收
  • 注意:
    • RX DMA 缓冲需 DCache Invalidate(从内存读前)
    • ETH_RX_BUF_SIZE 要与 DMA 配置匹配;pbuf 分段复制时注意偏移
    • PBUF_POOL 耗尽时 p==NULL:可能触发 PBUF_POOL_FREE_OOSEQ 或丢包,需打点统计

6.4 ethernetif_input 线程

  • 等待 s_xSemaphore(通常在 ETH 中断服务中释放)
  • 循环调用 low_level_input 获取 pbuf,调用 netif->input(p, netif) 交给栈(tcpip_thread)
  • 失败时 pbuf_free§
  • 建议:
    • netif->input 可能切到 tcpip 线程,避免在临界区内调用
    • 批量处理(你的 goto TRY_GET_NEXT_FRAGMENT 思路):减少线程切换开销
    • 线程优先级要高,避免丢包;栈与驱动之间 mbox 大小要足够(TCPIP_MBOX_SIZE)

6.5 链路回调与协商

  • ethernetif_update_config:链路 up/down、自动协商、重启 MAC
  • 建议结合 LWIP_NETIF_LINK_CALLBACK/LWIP_NETIF_STATUS_CALLBACK 上报

7. 零拷贝思路(进阶)

  • RX 零拷贝:将 DMA RX 缓冲封装为“自定义 pbuf”(LWIP_SUPPORT_CUSTOM_PBUF=1),把“释放函数”写成归还 DMA 缓冲。要点:
    • pbuf_alloced_custom 填充 pbuf_custom,payload 指向 DMA 缓冲;设置 custom_free_function
    • NO_SYS=0 时,跨线程共享要管理好生命周期/引用计数
    • Cache:RX 前 Invalidate,TX 前 Clean
  • TX 零拷贝:直接让 MAC 发送 pbuf 链 payload(硬件支持散点聚集+DMA 可直接访问),并在发送完成回调里 pbuf_free
    • 保证 pbuf 生命周期覆盖 DMA 发送窗口(不要提前 free)
    • 避免使用 PBUF_POOL 进行 TX 长时间排队(饿死 RX)

8. 典型数据流(发送与接收)

发送(应用→栈→驱动→MAC):

  1. 应用构造数据(可能在 PBUF_RAM),协议层用 pbuf_header 预留/填充头
  2. netif->linkoutput 指向 low_level_output,驱动将 pbuf 链拷贝至 DMA TX 缓冲
  3. HAL_ETH_TransmitFrame 启动 DMA

接收(MAC→驱动→栈→应用):

  1. 中断/轮询检测到帧,low_level_input 读取 DMA RX 缓冲
  2. 分配 PBUF_POOL 链,复制数据
  3. netif->input(p, netif) 上送到 tcpip_thread;上层解析并递交给套接字/回调

注意:PBUF_POOL 链在驱动与栈共享,尽量避免额外拷贝;释放时只调用 pbuf_free。


9. 配置与调优建议(lwipopts.h 相关)

必须关注:

  • 内存与对齐
    • MEM_ALIGNMENT:大多平台设为 4 或 8。DMA/Cache 平台不要设 1
    • PBUF_POOL_BUFSIZE:建议 ≥ MTU + 头部(1514/1520),可向上对齐
    • PBUF_POOL_SIZE:≥ RX 描述符 × 2~4 + 峰值
    • MEMP_NUM_PBUF:≥ PBUF_POOL_SIZE + 余量(8~16)
    • MEM_SIZE:堆大小(服务 PBUF_RAM/控制结构),TCP 推荐从 16KB 起步按需调整
  • 线程/并发
    • NO_SYS=0,SYS_LIGHTWEIGHT_PROT=1,确保 sys_arch* 实现正确
    • LWIP_TCPIP_CORE_LOCKING 可用但要有正确的 tcpip 线程与 mbox 尺寸
  • 协议相关
    • TCP_MSS:以太网通常 1460
    • TCP_WND/TCP_SND_BUF/TCP_SND_QUEUELEN:与吞吐/内存成正比
    • CHECKSUM_GEN_* / CHECKSUM_CHECK_*:是否由硬件完成(硬件校验则禁用软件重复计算)
  • 调试
    • LWIP_STATS/MEM_STATS/MEMP_STATS 打开,观察池/堆使用率、失败计数
    • MEM_SANITY_CHECK/MEMP_SANITY_CHECK/MEM_OVERFLOW_CHECK:开发阶段建议开启

示例(以太网、MSS=1460):

  • PBUF_LINK_HLEN 14,PBUF_IP_HLEN 20,PBUF_TRANSPORT_HLEN 20
  • PBUF_POOL_BUFSIZE = LWIP_MEM_ALIGN_SIZE(1460+40+14) ≈ 1514(可对齐至 1520/1536)

10. 常见问题与排查

  • 接收丢包 / pbuf_alloc(PBUF_POOL) 返回 NULL
    • PBUF_POOL_SIZE 太小;PBUF_POOL 被 TX 长时间占用;统计 pbuf_pool 空闲/失败数
    • 开启 PBUF_POOL_FREE_OOSEQ(TCP 场景)以回收乱序缓冲
  • 发送返回 ERR_USE
    • TX 描述符不足/队列满;阻塞等待或扩展环大小;统计并调参 ETH_TX_BUF_NUM
  • “释放崩溃 / double free”
    • 非头结点传给 pbuf_free;多处共享但未 pbuf_ref;开启 SANITY 与 OVERFLOW_CHECK
  • 数据错乱 / 校验错
    • Cache 未 Clean/Invalidate;堆或池未对齐;硬件校验配置与宏不一致
  • 吞吐低
    • PBUF_POOL_BUFSIZE 太小导致链式过多;MSS/WND 太小;线程/mbox 过小;频繁 memcpy

11. 进阶:自定义 pbuf(零拷贝 RX 示例片段)

/* 简要示例:将 DMA RX 缓冲封装为自定义 pbuf(释放时归还 DMA 缓冲) */
struct my_rx_buf { uint8_t *dma; uint16_t len; /* ... */ };
static void my_pbuf_free(struct pbuf *p) {struct pbuf_custom *pc = (struct pbuf_custom*)p;struct my_rx_buf *rx = (struct my_rx_buf*)pc->pbuf.payload /* or pc user field */;/* 归还 rx->dma 给 RX 环,并置 OWN 给 DMA *//* ... */
}struct pbuf_custom pc;
pbuf_alloced_custom(PBUF_RAW, rx->len, PBUF_POOL /*标记用途*/, &pc,rx->dma /*payload_mem*/, rx->len /*payload_mem_len*/);
pc.custom_free_function = my_pbuf_free;
/* 上送栈:netif->input(&pc.pbuf, netif); */

要点:

  • payload_mem 必须满足对齐要求
  • 自定义 pbuf 释放后不可再访问 DMA 缓冲
  • Cache 一致性要处理到位

12. 结语与建议

  • RX 首选 PBUF_POOL(固定块,低碎片,确定性);TX 可用 PBUF_RAM 或硬件支持下的零拷贝。
  • 池与堆“混用”是常态,但不要跨 API 释放(pbuf_free/memp_free/mem_free 不要混用)。
  • 带上统计数据做调参,优先保证 RX 不饥饿(PBUF_POOL 充足、线程优先级合理、mbox 足够)。
  • 硬件 DMA/Cache 平台优先考虑对齐与缓存一致性,必要时牺牲少量内存换更稳定吞吐。
  • 结合你的目标硬件(RX/TX 描述符数、Cache 情况)与 lwipopts.h,确定 PBUF_POOL_*、MEMP_NUM_PBUF、MEM_ALIGNMENT、MEM_SIZE 的初值,并用 LWIP_STATS 验证运行期水位后收敛。
http://www.dtcms.com/a/339631.html

相关文章:

  • 用relation-graph构建关系图谱 vue版
  • 单片 、物联网、51单片机、软硬件之基于STM32与蓝牙的仓储管控系统的设计与实现/基于物联网的仓库管理系统
  • 【实时Linux实战系列】基于实时Linux的物联网系统设计
  • AI硬件 - AMD显卡架构演进及产品线
  • 快速傅里叶变换:数字信号处理的基石算法
  • ubuntu24.04 用apt安装的mysql修改存储路径(文件夹、目录)
  • Linux 系统下安装 uv 并在 PyCharm 中使用指南
  • Z-Score归一化:原理、作用与实战解析
  • UGUI源码剖析(10):总结——基于源码分析的UGUI设计原则与性能优化策略
  • Spring框架-数据访问层和事务管理
  • 不止是耳机,运动与商务的结合!雷登A7开放式蓝牙耳机
  • Linux下的软件编程——IPC机制(信号和共享内存)
  • QT6(常用界面组件的使用和布局管理)
  • 芯片没有型号能解密程序么?
  • 多线程—飞机大战排行榜功能(2.0版本)
  • 量化交易 - 概念板块对应股票 python代码
  • IEEE Transactions on Communications (TCOM) 投稿状态记录
  • 右值引用与移动语义【C++进阶每日一学】
  • Chrome插件开发【windows】
  • Infusing fine-grained visual knowledge to Vision-Language Models
  • Kotlin 协程之Channel
  • Python脚本每天爬取微博热搜-升级版
  • 电商数据分析可视化预测系统
  • GPT-5论文写作全流程提示词库
  • 点大餐饮独立版系统源码v1.0.3+uniapp前端+搭建教程
  • 图像采集卡与工业相机:机器视觉“双剑合璧”的效能解析
  • spring事务(@Transactional)失效的情景及处理
  • RabbitMQ:SpringAMQP 多消费者绑定同一队列
  • shenyu网关与与nacos业务网关的定位
  • 源网荷储 + 微电网:1+1 如何大于 2?能源协同解决方案