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

工作笔记----lwip的数据管理结构pbuf源码解析

工作笔记----lwip的数据管理结构pbuf源码解析

@@ Author:明月清了个风

@@ Date:2025.9.23

@@ PS:在上一篇中已经讲解了lwip的内存管理策略,这一篇中将对lwip的数据管理结构pbuf的源码进行解析。lwip在各模块各层之间传递数据,使用了统一的数据包管理结构pbuf,通过传递pbuf指针避免了数据进行传输时的内存拷贝.

@@ Version:First published date 2025.9.23

以前的文章

FreeRTOS中的lwIP网络任务为什么会让出CPU

lwip网络任务初始化问题排查

基于FreeRTOS的lwIP网络任接收过程,从MAC至协议栈

lwip的内存管理策略解析

pbuf结构体

struct pbuf {struct pbuf *next;  	/* 用于构建pbuf链 */void *payload;		/* 实际数据buffer指针 */u16_t tot_len;		/* 从本pbuf开始(包括)后面所有pbuf所含数据量 */u16_t len;			/* 本pbuf所含数据量 */u8_t type_internal;	/* 位域,用于保存一些flag位 */u8_t flags;			/* 同样是位域,保存一些flag */LWIP_PBUF_REF_T ref;	/* 记录有多少指针指向本pbuf */u8_t if_idx;			/* 输入数据包的网络接口索引,也就是数据包从哪个netif进来的 */
};

pbuf_alloc函数

这个源码并不长,直接贴出来了,省略了一些条件编译和debug的内容

struct pbuf *
pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type)
{struct pbuf *p;u16_t offset = (u16_t)layer;switch (type) {case PBUF_REF: /* fall through */case PBUF_ROM:p = pbuf_alloc_reference(NULL, length, type);break;case PBUF_POOL: {struct pbuf *q, *last;u16_t rem_len; /* remaining length */p = NULL;last = NULL;rem_len = length;do {u16_t qlen;q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);if (q == NULL) {PBUF_POOL_IS_EMPTY();if (p) {pbuf_free(p);}return NULL;}qlen = LWIP_MIN(rem_len, (u16_t)(PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)));pbuf_init_alloced_pbuf(q, LWIP_MEM_ALIGN((void *)((u8_t *)q + SIZEOF_STRUCT_PBUF + offset)),rem_len, qlen, type, 0);if (p == NULL) {p = q;} else {last->next = q;}last = q;rem_len = (u16_t)(rem_len - qlen);offset = 0;} while (rem_len > 0);break;}case PBUF_RAM: {u16_t payload_len = (u16_t)(LWIP_MEM_ALIGN_SIZE(offset) + LWIP_MEM_ALIGN_SIZE(length));mem_size_t alloc_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF) + payload_len);if ((payload_len < LWIP_MEM_ALIGN_SIZE(length)) ||(alloc_len < LWIP_MEM_ALIGN_SIZE(length))) {return NULL;}p = (struct pbuf *)mem_malloc(alloc_len);if (p == NULL) {return NULL;}pbuf_init_alloced_pbuf(p, LWIP_MEM_ALIGN((void *)((u8_t *)p + SIZEOF_STRUCT_PBUF + offset)),length, length, type, 0);break;}default:return NULL;}return p;
}

首先来看传入的三个参数,分别是pbuf_layer,length,pbuf_type

📖pbuf_layer参数

该参数为枚举变量,共有五种,源码如下:

typedef enum {PBUF_TRANSPORT = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN,PBUF_IP = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN,PBUF_LINK = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN,PBUF_RAW_TX = PBUF_LINK_ENCAPSULATION_HLEN,PBUF_RAW = 0
} pbuf_layer;

可以看到,该参数定义的是一些长度,对于用于不同层的pbuf,所要求的长度是不同的,在pbuf_alloc()函数中,该长度赋值给变量offset,在后面会进一步使用这个值的地方

📖length参数

该参数就是需要分配的数据包长度,后续会在函数中赋值给变量rem_len

📖pbuf_type参数

该参数指定了分配的pbuf类型,也决定了pbuf的分配方式,函数中的switch就是根据这个type来分的,总共有以下四类

typedef enum {PBUF_RAM = (PBUF_ALLOC_FLAG_DATA_CONTIGUOUS | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP),PBUF_ROM = PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF,PBUF_REF = (PBUF_TYPE_FLAG_DATA_VOLATILE | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF),PBUF_POOL = (PBUF_ALLOC_FLAG_RX | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL)
} pbuf_type;

简单来说,这个类型指定了pbuf的数据存储在哪里,且pbuf及其数据的构成也不同

对于PBUF_RAM来说,注释原文如下

/** pbuf data is stored in RAM, used for TX mostly, struct pbuf and its payload are allocated in one piece of contiguous memory (so the first payload byte can be calculated from struct pbuf). pbuf_alloc() allocates PBUF_RAM pbufs as unchained pbufs (although that might change in future versions).
This should be used for all OUTGOING packets (TX).*/

也就是说,该类型的pbuf数据与该pbuf结构体在内存中是连续存储的,可以通过偏移pbuf指针直接找到该pbuf指向的数据起始地址,并且该类型的pbuf不是链式的,通常用于发出的数据包。

**对于PBUF_ROM来说,**注释原文如下:

/** pbuf data is stored in ROM, i.e. struct pbuf and its payload are located in totally different memory areas. Since it points to ROM, payload does not have to be copied when queued for transmission. */

也就是说,该类型的pbuf是存储在ROM中的,他与其所指向的数据(pbuf->payload)不是一块连续的内存空间,这意味着传输时不用去拷贝这部分数据

对于PBUF_REF来说,注释原文如下:

/** pbuf comes from the pbuf pool. Much like PBUF_ROM but payload might change so it has to be duplicated when queued before transmitting, depending on who has a ‘ref’ to it. */

该类型的pbuf来源于pbuf pool,也就是内存池预先分配好的,但是当需要进行传输时,这一类的pbuf数据是有可能会需要拷贝的。

对于PBUF_POOL来说,注释原文如下:

/** pbuf payload refers to RAM. This one comes from a pool and should be used for RX. Payload can be chained (scatter-gather RX) but like PBUF_RAM, struct pbuf and its payload are allocated in one piece of contiguous memory (so the first payload byte can be calculated from struct pbuf). Don’t use this for TX, if the pool becomes empty e.g. because of TCP queuing, you are unable to receive TCP acks! */

该类pbuf从内存池进行分配,被用于接收数据包,并且所包含数据与pbuf结构体本身是连续的。这也符合在之前的文章基于FreeRTOS的lwIP网络任接收过程,从MAC至协议栈中我们看到的数据包接收过程。

🏃接下来看具体的分配过程

记录了pbuf_layer对应的offset后就根据传入的pbuf_type选择不同的分配方式

对于PBUF_REFPBUF_ROM来说,都通过pbuf_alloc_reference(NULL, length, type);函数进行分配,先来看这个函数,该函数源码如下:

struct pbuf *
pbuf_alloc_reference(void *payload, u16_t length, pbuf_type type)
{struct pbuf *p;p = (struct pbuf *)memp_malloc(MEMP_PBUF);if (p == NULL) {return NULL;}pbuf_init_alloced_pbuf(p, payload, length, length, type, 0);return p;
}

可以看到对于上面这两种pbuf来说都直接通过memp_malloc从内存池分配pbuf结构体,而不分配存储数据的空间,非常简单。

对于pbuf_pool来说,其分配过程在pbuf_alloc函数中完成,主要的分配过程是一个do {} while循环,循环条件就是rem_len > 0,也就是每次循环都会分配一部分空间来容纳目标数据包,直到待分配内存大小rem_len = 0

在循环体中,首先从内存池中分配pbuf结构体q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);;然后比较两个数值的大小,一个是rem_len,另一个是(u16_t)(PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)),这里的PBUF_POOL_BUFSIZE_ALIGNED 是一个pbuf中能够容纳的最多的数据大小,这个值还需要减去pbuf_layer对应的offset才是一个pbuf真实能够存储的数据大小。

算出一个pbuf要分配的数据内存大小qlen后,就通过pbuf_init_alloced_pbuf()函数初始化这个pbuf,并且构建pbuf链,更新rem_len =(u16_t)(rem_len - qlen)

通过不停循环上面的过程即可分配足够大小的pbuf.

对于pbuf_ram来说,分配过程更加简单了,直接通过下面两行代码算出总共要分配多少大小的数据空间,这个空间包括了pbuf结构体本身,offset以及数据包大小length

u16_t payload_len = (u16_t)(LWIP_MEM_ALIGN_SIZE(offset) + LWIP_MEM_ALIGN_SIZE(length));
mem_size_t alloc_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF) + payload_len);

然后直接通过上一篇讲过的p = (struct pbuf *)mem_malloc(alloc_len);进行分配并通过pbuf_init_alloced_pbuf()初始化。

至此,pbuf的分配过程就讲完了

pbuf_free函数

这个源码也并不长,直接贴出来了,去掉了内核保护和debug代码,该函数的返回值是成功释放的pbuf数量

u8_t
pbuf_free(struct pbuf *p)
{u8_t alloc_src;struct pbuf *q;u8_t count;if (p == NULL) {return 0;}count = 0;while (p != NULL) {LWIP_PBUF_REF_T ref;ref = --(p->ref);if (ref == 0) {q = p->next;alloc_src = pbuf_get_allocsrc(p);
#if LWIP_SUPPORT_CUSTOM_PBUF  /* 对于自定义pbuf,会执行自定义的释放函数 */if ((p->flags & PBUF_FLAG_IS_CUSTOM) != 0) {struct pbuf_custom *pc = (struct pbuf_custom *)p;pc->custom_free_function(p);} else
#endif /* LWIP_SUPPORT_CUSTOM_PBUF */{if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL) {memp_free(MEMP_PBUF_POOL, p);} else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF) {memp_free(MEMP_PBUF, p);} else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP) {mem_free(p);} else {}}count++;p = q;} else {p = NULL;}}return count;
}

释放的过程是从pbuf链的头部开始(也可能不是链),通过对pbuf->ref减1进行释放,如果pbuf->ref变为0,则表示不再有任何指针指向该pbuf,因此可以进行释放,根据pbuf的不同类型调用不同的释放函数,也就是memp_free或者mem_free。当遍历到某个pbuf的ref自减后不为0,那么就会停止释放过程。

其他函数

可以看到其他函数大多都是链表的操作过程,且操作原理和上面两个关键函数起始差不多,因此就不展开细讲了,虽然lwip提供了很多的API,但其实常用的并不多,甚至很多时候不会需要自己操作pbuf。

http://www.dtcms.com/a/398358.html

相关文章:

  • 生产环境实战:Spring Cloud Sleuth与Zipkin分布式链路追踪实践
  • 学习React-15-useImperativeHandle
  • 响应式网站案列小学生做电子小报的网站
  • 【AskAI系列课程】:P4.将AI助手集成到Astro网站前端
  • 自注意力机制(Self-Attention)简介
  • App 代上架全流程解析 iOS 应用代上架服务、苹果应用发布步骤、ipa 文件上传与 App Store 审核经验
  • 学习日报 20250921|MQ (Kafka)面试深度复盘
  • 趣味学Solana(启航)
  • 期权末日论效应怎么来的?
  • iOS 混淆与反调试反 Hook 实战,运行时防护、注入检测与安全加固流程
  • 建设工程管理网站邹平建设网站
  • wordpress英文下主题怎么换苏州seo专家教优化网站结构
  • 《灼灼韶华》还原民国上海滩,虎鲸文娱虚拟拍摄让创作突破时空束缚
  • Redo Log 与 Crash Recovery:MySQL 事务持久化的核心技术
  • 金乡网站建设公司云南企业网站
  • 设计模式(C++)详解——职责链模式 (Chain of Responsibility)(1)
  • 酒店网站免费建设国际新闻今天最新
  • 企业产品网络安全日志9月23日-WAF应急
  • 嵌入式硬件工程师:绝缘栅型场效应管
  • HTTPS 请求抓包实战,从请求捕获到解密分析的逐步流程与工具组合(https 请求抓包、iOS 真机、SSL Pinning 排查)
  • 怎么学习cuda?
  • iOS 开发指南全解析 从入门到应用上架、Xcode 使用教程、ipa 打包上传与 App Store 审核实战经验
  • iOS 26 帧率测试实战指南,Liquid Glass 动画性能、滚动滑动帧率对比、旧机型流畅性与 uni-app 优化策略
  • 在网站上签失业保险怎样做网站对公司的重要性
  • php网站模板 php网站源码 PHP源码网
  • 万能PDF工具箱(PDF Candy)安装教程
  • 两款功能强大的密码学工具箱
  • umijs 4.0学习 - 路由
  • 【Java】P7 Java数组完全指南:从基础到进阶
  • PTZ相机AI相关的知识体系