LwIP协议栈之数据包pbuf

前言

在动态内存管理那篇文章中讲到了内存分配策略:内存堆分配内存池分配。两者互有优缺点,如何合理利用两种分配策略,就需要介绍LWIP的数据包缓冲的实现。

数据包pbuf源码详解

从网卡上来的原始数据包:有长达上千字节的TCP数据包,也有仅几个字节的ICMP数据包;
从要发送的数据包:上层应用可能将各种数据包递交给LWIP协议栈发送,这些数据包有可能存在于应用程序管理的内存空间内,也可能存在于某个ROM上。
这么复杂的工作致使LWIP必须有个高效的数据包管理核心,数据包管理机构出现了。
数据包管理机构采用数据结构 pbuf 来描述数据包,其源码如下:

struct pbuf {
struct pbuf *next;
void *payload;
u16_t tot_len;
u16_t len;
u8_t type;
u8_t flags;
u16_t ref;
};
  • next字段指针指向下一个pbuf结构,因为实际发送或接收的数据包可能很大,而每个pbuf能够管理的数据可能很少,所以往往需要多个pbuf结构才能完全描述一个数据包。
  • payload是数据指针,指向该pbuf管理的数据的起始地址,数据的起始地址可以是紧跟在pbuf结构之后的RAM,也可能是在ROM上的某个地址,而决定这点的是当前pbuf的类型。
  • len字段表示当前pbuf中的有效长度
  • tot_len表示当前pbuf和其后所有pbuf的有效数据的长度;
  • type字段表示pbuf的类型,主要是四种类型
  • flags也表示pbuf的类型,但是在初始化一个pbuf时,将该字段的值设为0,而在其他地方没有用到该字段,可以直接忽略
  • ref字段表示该pbuf被引用的次数,初始化一个pbuf时,ref字段值被设置为1,当有其他pbuf的next指针指向该pbuf时,该pbuf的ref字段值加一。

pbuf的类型:PBUF_RAM、PBUF_ROM、PBUF_REF和PBUF_POOL。

PBUF_RAM类型

PBUF_RAM 类型的 pbuf 主要通过内存堆分配得到的。申请PBUF_RAM的源代码如下:

p = (struct pbuf*)mem_malloc(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF + offset) + LWIP_MEM_ALIGN_SIZE(length));

可以看出,系统是调用内存堆分配函数mem_malloc进行内存分配的。分配空间的大小包括:pbuf结构头大小SIZEOF_STRUCT_PBUF,需要的数据存储空间大小length,还有一个offset。分配成功的PBUF_RAM类型的pbuf如下图:
在这里插入图片描述

从上图可以看出pbuf头和相应数据在同一片连续的内存区种,payload并没有指向pbuf头结束即ref字段之后,而是隔了一定的区域。这段区域就是上面的offset的大小,用来存储数据的包头,如TCP包头,IP包头等。

PBUF_POOL类型

PBUF_POOL类型和PBUF_RAM类型的pbuf有很大的相似之处,但它主要通过内存池分配得到的。
在接受数据包时,LWIP一般采用这种方式包装数据,申请PBUF_POOL类型时,协议栈会在内存池中分配适当的内存池个数以满足需要的申请大小。
申请PBUF_POOL型源代码如下:

p = memp_malloc(MEMP_PBUF_POOL);

可以看出,系统是调用内存池分配函数memp_malloc进行内存分配的。分配成功的PBUF_POOL类型的pbuf如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zk8zAjCP-1596020535578)(images/8.png)]

图中是分配指定大小的数据缓冲的结果,系统调用会分配多个固定大小的PBUF_POOL类型pbuf,并把这些pbufs链成一个链表,以满足用户的分配空间请求。

PBUF_ROM和PBUF_REF类型

PBUF_ROM 和 PBUF_REF 类型的 pbuf 基本相同,它们的申请都是在内存堆中分配一个相应的pbuf结构头,而不申请数据区的空间
PBUF_ROM 和 PBUF_REF 类型的区别在于前者指向 ROM 空间内的某段数据,而后者指
向 RAM 空间内的某段数据。
申请PBUF_ROM类型的源代码如下:

p = memp_malloc(MEMP_PBUF);

可以看出,系统是调用内存池分配函数memp_malloc进行内存分配的,而MEMP_PBUF类型的内存池大小恰好为一个pbuf头的大小。它会为不同的数据结构量身定做不同类型的池。分配成功之后的结构如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nK3MerBz-1596020535580)(images/9.png)]

pbuf释放

pbuf的申请主要是通过内存堆分配和内存池分配来实现。所以pbuf的释放也必须按照两种情况来讨论。

pbuf能被释放的前提:当pbuf的ref字段为1时,该pbuf才可以被删除,所以位于pbufs链表中间的pbuf结构是不会被删除成功的,因为他们的ref值至少是2。能被删除的pbuf必然是某个pbufs链的首节点。
当可以删除某个pbuf结构时,LWIP首先检查这个pbuf是属于前节讲到的四个类型中的哪个,根据类型的不同,调用不同的内存释放函数进行删除。
PBUF_POOL 类型和PBUF_ROM 类型、 PBUF_REF 类型需要通过 memp_free()函数删除,
PBUF_RAM 类型需要通过 mem_free()函数删除(PBUF_RAM类型来自于内存堆,需要通过mem_free()函数将pbuf释放回内存堆)。

内存堆管理模块根据所申请分配的大小来搜索所有未被使用的内存分配块,检索到的最先满足条件的内存块将分配给申请者,分配成功后,内存管理模块会马上中已经分配走了的数据区后面再插入一个小小的结构体,并用next和prev指针将这个结构体穿起来。
当内存释放时,为了防止内存碎片的产生,上一个与下一个分配块的使用标志会被检查,如果他们中的任何一个还未被使用,这个内存块将被合并到一个更大的未使用内存块中。
POOL 结构的起始处有个 next 指针,用于指向同类型的下一个 POOL,用于将同类型的 POOL 连接成一个单向链表,这里应该有必要仔细看看 POOL池是怎样初始化的,代码很简单:

memp = LWIP_MEM_ALIGN(memp_memory);
for (i = 0; i < MEMP_MAX; ++i) { //对各种类型的 POOL 依次操作
	memp_tab[i] = NULL; //空闲链表头初始为空
	for (j = 0; j < memp_num[i]; ++j) { //把同类 POOL 链成链表
		memp->next = memp_tab[i];
		memp_tab[i] = memp;
		memp = (struct memp *)((u8_t *)memp + MEMP_SIZE + memp_sizes[i]);//取得下
	} //一个 POOL 的地址
}

memp_tab 用于指向某类 POOL 空闲链表的起始节点;memp_num 表示各种类型 POOL 的个数;memp_sizes 表示各种类型单个 POOL 的大小,对于 MEMP_PBUF_POOL 和 MEMP_PBUF 型的 POOL,其大小是 pbuf 头和 pbuf 可装载数据大小的总和。

POOL池的释放:首先根据POOL的类型找到相应空闲链表头memp_tab,将该POOL插在链表头上,并把memp_tab指向链表头。

参考资料

老衲五木:TCP/IP协议栈LwIP的设计与实现

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值