LwIP协议栈的数据包管理
首先,谈谈数据包管理的重要性。我们知道TCP/IP协议机构中,每一层都被描述成独立(各个层被封装)的模块,每一层负责完成自己独立的事情。这样在性能优良的处理器上跑TCP/IP协议是没有问题的,一断我们的处理器很LOW的时候,特别是嵌入式开发的时候,这样做就不行。因为每一层都独立后,每一层之间的通信涉及到繁琐的数据的复制传递,这既消耗内存,也耗费时间。
LwIP协议栈是TCP/IP协议的一种轻量级实现。它主要是为了避免当处理器性能不好的时候,数据的传递耗费大量的内存和时间。故它并没有采用完整的分层结构,它会假设各层之间的部分数据结构和实现原理在其他层是可见的。这样数据在传递过程中就不需要频繁的复制和传送了,各个层的字段会直接操作这些数据(因为是可见的)。省内存、省时间。这些可见的数据就需要一种有效的管理机制,这就是数据包管理的由来,由此也可以看出有效的管理数据包是十分重要的。这涉及到LWIP协议栈设计的核心思想。
同时,LwIP协议栈采用了这样一种进程模型:
(1)协议栈内核和操作系统相互隔离,同时整个协议栈作为操作系统的一个进程存在。这样用户应用程序和协议栈之间的通信是通过回调函数实现的,即:Raw/Callback API.
(2)用户应用程序可以单独作为一个进程存在
即:Sequential API 和 Socket API
在LwIP协议源码中,与数据包管理的文件是 pbuf.c和pbuf.h ,在pbuf.h中,定义了一个pbuf结构体:
struct pbuf {
/** next pbuf in singly linked pbuf chain */
struct pbuf *next; //构成pbuf链表时指向下一个pbuf结构
/** pointer to the actual data in the buffer */
void *payload; //数据指针,指向pbuf所记录的数据区域
/**
* total length of this buffer and all next buffers in chain
* belonging to the same packet.
*
* For non-queue packet chains this is the invariant:
* p->tot_len == p->len + (p->next? p->next->tot_len: 0)
*/
u16_t tot_len; //整个pbuf链表的长度
/** length of this buffer */
u16_t len; //当前pbuf链表的长度
/** pbuf_type as u8_t instead of enum to save space */
u8_t /*pbuf_type*/ type; //pbuf类型
/** misc flags */
u8_t flags; //未使用,一般为0
/**
* the reference count always equals the number of pointers
* that refer to this pbuf. This can be pointers from an application,
* the stack itself, or pbuf->next pointers from a chain.
*/
u16_t ref; //引用的总数
};
上面提到了pbuf的类型,pbuf有4中类型
typedef enum {
PBUF_RAM, /* pbuf data is stored in RAM */
PBUF_ROM, /* pbuf data is stored in ROM */
PBUF_REF, /* pbuf comes from the pbuf pool */
PBUF_POOL /* pbuf payload refers to RAM */
} pbuf_type;
对于 PBUF_RAM类型的pbuf,其内存分配函数为:
p=(struct pbuf *)mem_malloc(LWIP_ALIGN_SIZE(SIZEOF_STRUCT_PBUF+offset)+LWIP_ALIGN_SIZE(size));
注意,这里用的是内存堆分配函数。
对于 PBUF_POOL类型的pbuf,其内存分配函数为:
P=memp_malloc(MEMP_PBUF_POOL); //注意这里用的是内存池分配函数
图2. PBUF_POOL型pbuf
下面看看源码是怎么申请PBUF_ROM, PBUF_REF类型的,其中p是pbuf指针
p=memp_malloc(MEMP_PBUF); //注意这里用的是内存池分配函数,分配的pool刚好存储pbuf结构体,内存空间在其他地 //方,ROM或者是RAM区 ,不像前面两个是在连续的存储空间
数据包的形式多种多样,常见的就是以上几种类型组合起来的pbuf链表
下面来看看数据包的一些管理函数:
(1)数据包申请函数
struct pbuf *pbuf_alloc(pbuf_layer l, u16_t length, pbuf_type type);
参数l指明pbuf所处的层次,pbuf_layer 类型在pbuf.h的文件中的定义为:
typedef enum {
PBUF_TRANSPORT, //传输层
PBUF_IP, //网络层
PBUF_LINK, //链路层
PBUF_RAW //原始层,offset的值为0
} pbuf_layer;
参数length表示要申请的pbuf的数据区长度,参数type支出需要申请的pbuf类型
(2)数据包释放函数
直接给出源码:
u8_t pbuf_free(struct pbuf *p)
{
u16_t type;
struct pbuf *q;
u8_t count;
if (p == NULL) {
LWIP_ASSERT("p != NULL", p != NULL);
/* if assertions are disabled, proceed with debug output */
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("pbuf_free(p == NULL) was called.\n"));
return 0;
}
其他数据包管理函数为:
void pbuf_realloc(struct pbuf *p, u16_t size);
u8_t pbuf_header(struct pbuf *p, s16_t header_size);
void pbuf_ref(struct pbuf *p);
u8_t pbuf_free(struct pbuf *p);
u8_t pbuf_clen(struct pbuf *p);
void pbuf_cat(struct pbuf *head, struct pbuf *tail);
void pbuf_chain(struct pbuf *head, struct pbuf *tail);
struct pbuf *pbuf_dechain(struct pbuf *p);
err_t pbuf_copy(struct pbuf *p_to, struct pbuf *p_from);
u16_t pbuf_copy_partial(struct pbuf *p, void *dataptr, u16_t len, u16_t offset);
err_t pbuf_take(struct pbuf *buf, const void *dataptr, u16_t len);
struct pbuf *pbuf_coalesce(struct pbuf *p, pbuf_layer layer);
#if LWIP_CHECKSUM_ON_COPY
err_t pbuf_fill_chksum(struct pbuf *p, u16_t start_offset, const void *dataptr,
u16_t len, u16_t *chksum);
#endif /* LWIP_CHECKSUM_ON_COPY */
u8_t pbuf_get_at(struct pbuf* p, u16_t offset);
u16_t pbuf_memcmp(struct pbuf* p, u16_t offset, const void* s2, u16_t n);
u16_t pbuf_memfind(struct pbuf* p, const void* mem, u16_t mem_len, u16_t start_offset);
u16_t pbuf_strstr(struct pbuf* p, const char* substr);