LWIP数据包管理学习

------------------pbuf.h-------------------
struct pbuf{
    struct pbuf* next;          //构成pbuf链表时指向下一个pbuf结构
    void* payload;               //数据指针,指向该pbuf所记录的数据区域。
    u16_t tot_len;               //当前pbuf及其后续所有pbuf中包含的数据总长度
    u16_t len;                      //当前pbuf的数据长度
    u8_t   type;                   //当前pbuf的类型
    u8_t   flags;                  //状态位,未用到
    u16_t ref;                     //指向该pbuf的指针数,即该pbuf被引用的次数。
}
LWIP数据包管理学习_第1张图片
 一个pbuf结构的内容就是这样子的,后面一大块空白的就是数据区。注意,payload的箭头没有指向数据区的开头,这里有一个offset。这个一段offset是为了在LWIP各层中填充首部用的,offset还有大用处,后面会讲。如果数据比较多,一个pbuf装不下,就得用多个pbuf来装,并且串成链表。像这样
LWIP数据包管理学习_第2张图片
注意了,只有这个链表头的pbuf有offset,其他pbuf没有。这个应该不难理解吧。这里还要说一点,并不是一定数据多了就要做成链表,得看pbuf中的type。
pbuf这个结构里值得重点讲的就是type了。
----------pbuf.h--------------------------
type enum{
    PBUF_RAM,
    PBUF_ROM,
    PBUF_REF,
    PBUF_POOL
}pbuf_type;
--------------------------------------------
为什么要分类型呢?这是为了效率和节省空间。pbuf是个不小的数据结构呢,要知道后面的数据区动辄几十上百字节,所以你要用pbuf就得动用内存申请。内存有内存池和内存堆两种,该申请哪一种呢。type就是为了描述这个而来,PBUF_RAM就是从内存堆中申请,PBUF_POOL就是从内存池中申请。PBUF_ROM和PBUF_REF是从哪申请呢?LWIP的作者为了尽量减少数据的拷贝带来的开销,兼顾灵活性。在这整了两种特别的pbuf类型,刚刚上面讲的PBUF_RAM PBUF_POOL从内存池内存堆中申请的时候是连后面的数据区都一起申请的,所以申请得到的内存是一大块的。而PBUF_REF PBUF_ROM 申请的就只是pbuf结构体,没有申请后面的数据区。为什么要这样做呢,这是考虑到在使用过程中,比如说我有一个全局的数组,里面存了我要发送的数据,如果我申请的是PBUF_RAM PBUF_POOL的话,我就得把这个全局数组的内容拷贝到申请的pbuf的数据区中,前面说了拷贝数据是个很大的开销,我们想要避免。所以就有了这个PBUF_REF PBUF_ROM,你申请得到这种类型的pbuf后,只要把里面的payload指针指向你的数据就好了,不用拷贝。这两者的区别只在于PBUF_REF指向的数据必须在RAM空间内,而PBUF_ROM指向的数据必须在ROM空间内。然后我们要说说PBUF_RAM PBUF_POOL的区别,PBUF_RAM的话从内存堆申请,不管你数据多大,如果申请成功就一个pbuf。PBUF_POOL的话,如果数据太大就会成一个链表。从这点上来讲,为了成功率还是尽量用POOL吧,毕竟如果内存堆用得多了,可能比较碎片化,你想再申请个大的pbuf估计会不成功。


最后讲讲操作pbuf的函数,重点就是申请和释放两个函数了。
struct pbuf* pbuf_alloc(pbuf_layer layer,u16_t length,pbuf_type type)
这个是申请函数,length就是数据长度啦,如果你申请的不带数据区的PBUF_REF PBUF_ROM的话,这个长度也没太大意义了。type就上面讲的类型啦。
layer是啥东东?原来LWIP在申请pbuf的时候还要说明要申请的是那一层的pbuf,不同层的pbuf的offset也不同。
-------------pbuf,h----------------------
typedef enum{
    PBUF_TRANSPORT,    //传输层
    PBUF_IP,                    //网络层
    PBUF_LINK,                //链路层
    PBUF_RAM                //原始层,不预留空间
}pbuf_layer;
-------------------------------------------------
释放函数是u8_t pbuf_free(struct pbuf* p)
pbuf的释放就要小心了,这里要讲到pbuf里的ref这个变量,这个变量记录了这个pbuf被引用的次数,在pbuf刚申请的时候整个变量是1。至于pbuf是怎么被引用的,暂时还不清楚。如果pbuf是串成链表的话,链表里表头之后的每一个pbuf的ref都是1,就是说每一个pbuf都被前面一个pbuf引用。pbuf在释放的时候,就会把pbuf的ref值减1,然后函数会判断ref减完之后是不是变成0,如果是0就会根据pbuf的类型调用内存池或者内存堆回收函数进行回收。前面刚讲,如果是链表的话,每一个pbuf都被前面的引用,所以如果你把表头回收了,第二个pbuf的ref就得减1,那它就变成0了,又回收,第二个被回收了,第三个的ref就得减1,又变0,又回收,这样连锁反应下去就把整条链表都收回去了。然后这里就有个很危险的事了,这个pbuf_free函数,你要传的参数是链表头指针,假如你传的不是链表头而是指向链表中间的某个pbuf
的指针,那就出大事了,这个pbuf_free可不会帮你检查是不是链表头,这样子势必会导致一部分pbuf没被回收,意味着一部分内存池就这样死了,以后没办法用了。
pbuf_realloc函数在相应pbuf(链表)尾部释放一定的空间,将数据包pbuf中的数据长度减少为某个长度值。对于PBUF_RAM类型的pbuf,函数将调用内存堆管理中介绍到的mem_realloc函数,释放这些多余的空间。对于其他三种类型的pbuf,该函数只是修改pbuf中的长度字段值,并不释放对应的内存池空间。
pbuf_header函数用于调整pbuf的payload指针(向前或向后移动一定字节数),在前面也说到过,在pbuf的数据区前可能会预留一些协议首部空间,而pbuf被创建时,payload指针是指向数据区的,为了实现对这些预留空间的操作,可以调用函数pbuf_header使payload指针指向数据区前的首部字段,这就为各层对数据包首部的操作提供了方便。当然,进行这个操作的时候,len和tot_len字段值也会随之更新。
pbuf_take函数用于向pbuf的数据区域拷贝数据。pbuf_copy函数用于将一个任何类型的pbuf中的数据拷贝到一个PBUF_RAM类型的pbuf中。pbuf_chain函数用于连接两个pbuf(链表)为一个pbuf链表。pbuf_ref函数用于将pbuf中的值加1。

你可能感兴趣的:(第三方库)