lwip---(二)数据包pbuf

01/09 09:53
阅读数 128

  总结一下,LWIP中常用到的内存分配策略有两种,一种是内存堆分配,一种是内存池分配前者可以说能随心所欲的分配我们需要的合理大小的内存块,缺点是当经过多次的分配释放后,内存堆中间会出现很多碎片,使得需要分配较大内存块时分配失败后者分配速度快,就是简单的链表操作,因为各种类型的POOL是我们事先建立好的,但是采用POOL会有些情况下会浪费掉一定的内存空间。在LWIP中,将这两种分配策略混合使用,达到了很好的内存使用效率。

  下面我们将来看看LWIP中是怎样合理利用这两种分配策略的。这就顺利的过渡到了这节要讨论的话题:LWIP的数据包缓冲的实现。 在协议栈中移动的数据包,最无疑的是整个内存管理中最重要的部分了。

   数据包的种类和大小也可以说是五花八门,数数,首先从网卡上来的原始数据包,它可以是长达上千个字节的TCP数据包,也可以是仅有几个字节的ICMP数据包;再从要发送的数据包看,上层应用可能将自己要发送的千奇百怪形态各异的数据包递交给LWIP协议栈发送,这些数据可能存在于应用程序管理的内存空间内,也可能存在于某个ROM上。注意,这里有个核心的东西是当数据在各层之间传递时,LWIP极力禁止数据的拷贝工作,因为这样会耗费大量的时间和内存

  综上,LWIP必须有个高效的数据包管理核心,它即能海纳百川似的兼容各种类型的数据,又能避免在各层之间的复制数据的巨大开销。

  数据包管理机构采用数据结构pbuf来描述数据包,其源码如下:

struct pbuf {
   
   
	struct pbuf *next;                 //针指向下一个pbuf结构
    void *payload;                     //数据指针,指向该pbuf管理的数据的起始地址
    u16_t tot_len;                     //当前pbuf中的有效数据长度
    u16_t len;                         //当前pbuf和其后所有pbuf的有效数据的长度
    u8_t  type;                        //pbuf的类型
    u8_t flags;                        //pbuf的类型
    u16_t ref;                         //该pbuf被引用的次数
};

  这个看似简单的数据结构,却内容丰富!
  next字段指针指向下一个pbuf结构,因为实际发送或接收的数据包可能很大,而每个pbuf能够管理的数据可能很少,所以,往往需要多个pbuf结构才能完全描述一个数据包。所以,所有的描述同一个数据包的pbuf结构需要链在一个链表上,这一点用next实现。
  payload是数据指针,指向该pbuf管理的数据的起始地址,这里,数据的起始地址可以是紧跟在pbuf结构之后的RAM,也可能是在ROM上的某个地址,而决定这点的是当前pbuf是什么类型的,即type字段的值,这在下面将继续讨论。
  len字段表示当前pbuf中的有效数据长度。
  而tot_len表示当前pbuf和其后所有pbuf的有效数据的长度。显然,tot_len字段是len字段与pbuf链中随后一个pbuftot_len字段的和;pbuf链中第一个pbuftot_len字段表示整个数据包的长度,而最后一个pbuftot_len字段必和len字段相等。
  type字段表示pbuf的类型,主要有四种类型,这点基本上涉及到pbuf管理中最难的部分,将在下节仔细讨论。
  文档上说flags字段也表示pbuf的类型,不懂,type字段不是说明了pbuf的类型吗?不过在源代码里,初始化一个pbuf的时候,是将该字段的值设为0,而在其他地方也没有用到该字段,所以,这里直接忽略掉。
  最后ref字段表示该pbuf被引用的次数。这里又是一个纠结的地方啊。初始化一个pbuf的时候,ref字段值被设置为1,当有其他pbufnext指针指向该pbuf时,该pbufref字段值加一。所以,要删除一个pbuf时,ref的值必须为1才能删除成功,否则删除失败。






  pbuf的类型很多。pbuf有四类:PBUF_RAM、PBUF_ROM、PBUF_REF和PBUF_POOL。下面,一个一个的来看看各种类型的特点。

  PBUF_RAM类型的pbuf主要通过内存堆分配得到的。这种类型的pbuf在协议栈中是用得最多的。协议栈要发送的数据和应用程序要传递的数据一般都采用这个形式。申请PBUF_RAM类型时,协议栈会在内存堆中分配相应的大小,注意,这里的大小包括如前所述的pbuf结构头大小和相应数据缓冲区,他们是在一片连续的内存区的。下面来看看源代码是怎样申请PBUF_RAM型的。
  其中ppbuf型指针。

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。关于这个offset,也有一大堆可以讨论的东西,不过先到此打住。总之,分配成功的PBUF_RAM类型的pbuf如下图:
在这里插入图片描述
   图中是分配指定大小的数据缓冲的结果,系统调用会分配多个固定大小的PBUF_POOL类型pbuf,并把这些pbufs链成一个链表,以满足用户的分配空间请求。

PBUF_ROMPBUF_REF类型的pbuf基本相同,它们的申请都是在内存堆中分配一个相应的pbuf结构头,而不申请数据区的空间。这就是它们与PBUF_RAMPBUF_POOL的最大区别。PBUF_ROMPBUF_REF类型的区别在于前者指向ROM空间内的某段数据,而后者指向RAM空间内的某段数据。下面来看看源代码是怎样申请PBUF_ROMPBUF_REF类型的。其中ppbuf型指针。

p = memp_malloc(MEMP_PBUF);

  可以看出,系统是调用内存池分配函数memp_malloc进行内存分配的。而此刻请求的内存池类型为MEMP_PBUF,而不是MEMP_PBUF_POOLMEMP_PBUF类型的内存池大小恰好为一个pbuf头的大小,因为这种池是LWIP专为PBUF_ROMPBUF_REF类型的pbuf量身制作的。LWIP还是真的很周到啊,它会为不同的数据结构量身定做不同类型的池。正确分配的PBUF_ROMPBUF_REF类型的pbuf,其结构如下图:

在这里插入图片描述
  注:以上所有图片都来自文档《Design and Implementation of the LWIP:TCP/IP Stack》,这些图都有个共同的错误,即lentot_len字段位置搞反了,窃喜。
最后说明,对于一个数据包,它可能使用上述的任意的pbuf类型,很可能的情况是,一大串不同类型的pbufs连在一起,用以保存一个数据包的数据。

  下节看点,关于pbuf的内存释放问题。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部