背景
TCP/IP 是一种数据通信机制,本质是对数据包进行处理。
链路层判断收到数据包类型,会提取数据字段,记录主机物理地址信息;
IP层提取IP地址,实现数据的存储、转发,根据数据包编号实现数据包重装,提取数据包中关于传输层的信息,向传输层递交数据包并记录递交结果;
传输层中TCP使用数据包信息更新TCP状态机,并向应用程序递交数据。
数据包管理应该是一种高效管理机制,使协议栈各层对数据灵活处理同时还能减少数据在各层间传递时的时间和空间开销。
协议栈本是严格分层的,但是这会使层间数据递交非常慢,为避免这种情况,代码内部没有采用完整的分层结构,部分数据结构和实现原理再其他层是可见的。协议栈实现时,仍然是贯彻分层思想,每层都是独立模块实现,提供 输入输出函数。但是不是严格的分层机制,各层级存在交叉存取现象。
一般来讲,协议栈是设计为内核代码的一部分,与用户层保持着完全的分层封装。但是小型嵌入式设备中,内核空间和用户空间没有明显的分层现象,这就允许用户和内核之间有更多宽松的数据处理机制。
LWIP采用如下进程模型:
协议栈作为一个操作系统的独立进程,用户可以留驻在协议栈进程 中,也可以是一个独立的进程。第一种方式,用户与协议栈通信通过回调函数;第二种方式,用户和协议栈通信需要调用系统提供的信号量和邮箱机制。
PBUF结构体
设计PBUF包的核心是能容纳不同类型的数据,又能避免各层间数据拷贝。
结构体定义如下:
struct pbuf {
/** next pbuf in singly linked pbuf chain */
struct pbuf *next;
/** pointer to the actual data in the buffer */
void *payload;
/**
* 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;
/** length of this buffer */
u16_t len;
/** pbuf_type as u8_t instead of enum to save space */
u8_t /*pbuf_type*/ type;
/** misc flags */
u8_t flags;
/**
* 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;
};
名称 | 描述 |
---|---|
next | 指向下一个pbuf数据结构 |
payload | 数据起始地址 |
tot_len | 当前和其后面所有pbuf有效数据总长度 |
len | 有效数据长度 |
type | pbuf数据类型 |
flags | 没用到 |
ref | 该pbuf被引用的次数 |
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_RAM类型是通过内存堆分配的,这种类型是协议栈使用最多的,协议栈中待发数据和应用程序的待发数据一般都采用这个形式。
下面是源代码申请这种类型:
p = (struct pbuf*)mem_malloc(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF + offset) + LWIP_MEM_ALIGN_SIZE(length));
分配空间大小有pbuf结构大小、数据存储空间大小、offset。分配成功后的数据结构如下图所示:
从图中可以看出,payload并非指向整个数据区的起始处,而是间隔了offset,这段长度内存通常是各种首部字段,如TCP报文首部、IP首部、以太网首部等。
PBUF_ROM
PBUF_ROM类型是内存池中分配一个相应的pbuf结构,不申请数据区的空间。发送一些静态数据,可以采用这种数据类型。
PBUF_REF
PBUF_REF同PBUF_ROM相同,内存池中分配一个相应的pbuf结构,不申请数据区的空间。
p = (struct pbuf *)memp_malloc(MEMP_PBUF);
PBUF_POOL
PBUF_POOL类型是通过内存池分配得到,下面是源代码申请这种类型:
p = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);
数据包管理
数据包申请函数pbuf_alloc(),有两个重要参数,一个是数据包pbuf类型,上面已经讲的很详细了;第二个是协议栈的层次,根据层次决定offset值不同。
struct pbuf * pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type)
{
struct pbuf *p, *q, *r;
u16_t offset;
s32_t rem_len; /* remaining length */
/* determine header offset */
offset = 0;
switch (layer) {
case PBUF_TRANSPORT:
/* add room for transport (often TCP) layer header */
offset += PBUF_TRANSPORT_HLEN;
/* FALLTHROUGH */
case PBUF_IP:
/* add room for IP layer header */
offset += PBUF_IP_HLEN;
/* FALLTHROUGH */
case PBUF_LINK:
/* add room for link layer header */
offset += PBUF_LINK_HLEN;
break;
case PBUF_RAW:
break;
default:
return NULL;
}
switch (type) {
case PBUF_POOL:
/* allocate head of pbuf chain into p */
p = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);
if (p == NULL) {
PBUF_POOL_IS_EMPTY();
return NULL;
}
p->type = type;
p->next = NULL;
/* make the payload pointer point 'offset' bytes into pbuf data memory */
p->payload = LWIP_MEM_ALIGN((void *)((u8_t *)p + (SIZEOF_STRUCT_PBUF + offset)));
/* the total length of the pbuf chain is the requested size */
p->tot_len = length;
/* set the length of the first pbuf in the chain */
p->len = LWIP_MIN(length, PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset));
/* set reference count (needed here in case we fail) */
p->ref = 1;
/* now allocate the tail of the pbuf chain */
/* remember first pbuf for linkage in next iteration */
r = p;
/* remaining length to be allocated */
rem_len = length - p->len;
/* any remaining pbufs to be allocated? */
while (rem_len > 0) {
q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);
if (q == NULL) {
PBUF_POOL_IS_EMPTY();
/* free chain so far allocated */
pbuf_free(p);
/* bail out unsuccesfully */
return NULL;
}
q->type = type;
q->flags = 0;
q->next = NULL;
/* make previous pbuf point to this pbuf */
r->next = q;
/* set total length of this pbuf and next in chain */
q->tot_len = (u16_t)rem_len;
/* this pbuf length is pool size, unless smaller sized tail */
q->len = LWIP_MIN((u16_t)rem_len, PBUF_POOL_BUFSIZE_ALIGNED);
q->payload = (void *)((u8_t *)q + SIZEOF_STRUCT_PBUF);
q->ref = 1;
/* calculate remaining length to be allocated */
rem_len -= q->len;
/* remember this pbuf for linkage in next iteration */
r = q;
}
/* end of chain */
/*r->next = NULL;*/
break;
case PBUF_RAM:
/* If pbuf is to be allocated in RAM, allocate memory for it. */
p = (struct pbuf*)mem_malloc(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF + offset) + LWIP_MEM_ALIGN_SIZE(length));
if (p == NULL) {
return NULL;
}
/* Set up internal structure of the pbuf. */
p->payload = LWIP_MEM_ALIGN((void *)((u8_t *)p + SIZEOF_STRUCT_PBUF + offset));
p->len = p->tot_len = length;
p->next = NULL;
p->type = type;
break;
/* pbuf references existing (non-volatile static constant) ROM payload? */
case PBUF_ROM:
/* pbuf references existing (externally allocated) RAM payload? */
case PBUF_REF:
/* only allocate memory for the pbuf structure */
p = (struct pbuf *)memp_malloc(MEMP_PBUF);
if (p == NULL) {
return NULL;
}
/* caller must set this field properly, afterwards */
p->payload = NULL;
p->len = p->tot_len = length;
p->next = NULL;
p->type = type;
break;
default:
return NULL;
}
/* set reference count */
p->ref = 1;
/* set flags */
p->flags = 0;
return p;
}
数据包释放函数pbuf_free(),需要注意的是能被删除的节点一定是pbuf链表的首节点或其他地方引用过的节点。
u8_t pbuf_free(struct pbuf *p)
{
u16_t type;
struct pbuf *q;
u8_t count;
if (p == NULL) {
/* if assertions are disabled, proceed with debug output */
return 0;
}
PERF_START;
count = 0;
/* de-allocate all consecutive pbufs from the head of the chain that
* obtain a zero reference count after decrementing*/
while (p != NULL) {
u16_t ref;
/* this pbuf is no longer referenced to? */
if (ref == 0) {
/* remember next pbuf in chain for next iteration */
q = p->next;
type = p->type;
#if LWIP_SUPPORT_CUSTOM_PBUF
/* is this a custom 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 */
{
/* is this a pbuf from the pool? */
if (type == PBUF_POOL) {
memp_free(MEMP_PBUF_POOL, p);
/* is this a ROM or RAM referencing pbuf? */
} else if (type == PBUF_ROM || type == PBUF_REF) {
memp_free(MEMP_PBUF, p);
/* type == PBUF_RAM */
} else {
mem_free(p);
}
}
count++;
/* proceed to next pbuf */
p = q;
/* p->ref > 0, this pbuf is still referenced to */
/* (and so the remaining pbufs in chain as well) */
} else {
/* stop walking through the chain */
p = NULL;
}
}
PERF_STOP("pbuf_free");
/* return number of de-allocated pbufs */
return count;
}
当删除某个pbuf结构时,会检测其类型,根据类型调用不同的释放函数释放。