数据包管理
TCP/IP是一种数据通信机制,因此,协议栈的实现本质上就是对数据包进行处理。数据包管理应该能提供一种高效的机制,使协议栈各层能对数据包进行灵活的处理,同时减少数据在各层间传递时的时间与空间开销,这是提高协议栈工作效率的关键点。在BSD的实现中,有个描述和管理数据包的结构叫做mbuf,而在LwIP中,也有个类似的结构,称之为pbuf。在本文中,将讨论LwIP的数据包管理是怎样合理利用动态内存池策略和动态内存堆分配策略。
- LwIP的层间结构与编程模型
- 数据包管理结构pbuf
- 数据包管理相关操作函数
一、LwIP的层间结构与编程模型
1. LwIp的分层特点
在标准TCP/IP协议结构中,各个层都被描述为一个独立的模块形式,每一层负责完成一个独立的通信问题。如果单片机按照这种严格的分层模式来实现TCP/IP协议,会使数据包在各层间的递交变得非常慢,因为它涉及一系列的内存拷贝问题,因此,系统总体系能也会受到影响。为避免此,LwIP内部并没有采用完整的分层结构,它会假设各层间的部分数据结构和实现原理在其他层是可见的。这样,在数据包递交过程中,各层协议可以直接对数据包中属于其他层间协议的字段进行操作,这将使整个协议栈对数据包的操作更加灵活。
LwIP实现时,参考了TCP/IP协议的分层思想,即每层都在一个单独的模块中实现,并为其他层次模块提供一些输入/输出函数。但即使这样,严格上说,LwIP并没有遵循严格的分层机制,正如前面讨论的那样,为了节省时间和空间上的开销,各个层间之间可能存在交叉存取的现象。
另一方面,在许多其他TCP/IP协议的实现中,即使内核各层存在着一定的交错现象,但在用户应用程序和协议栈内核之间也会保持着很明显的分层建构。但在小型嵌入式设备使用得操作系统中,操作系统代码和用户程序代码之间通常都没有出现明显分层的现象,这种方式允许用户程序和操作系统内核之间使用更多宽松的方式进行通信。如共享内存等。这可以避免数据在用户程序和LwIP协议栈之间拷贝时的时间和内存开销。
2. 协议进程模型
LwIP协议栈内核与操作系统内核互相隔离,而同时整个协议栈作为操作系统中的一个单独进程而存在。用户应用程序可以驻留在协议栈内核的进程中(raw/callback API),也可以实现为一个单独的进程(sequential API和socket API)。
二、数据包管理
1. 数据包结构pbuf
struct pbuf { struct pbuf *next;//构成pbuf链表时指向一下一个pbuf结构 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及其后续所有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;//状态位,未使用到 /** * 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被引用的次数,初始时为1 };
2. pbuf类型
typedef enum { PBUF_RAM, //pbuf描述的数据在pbuf结构之后的连续内存堆中 PBUF_ROM, //pbuf描述的数据在ROM中 PBUF_REF,//pbuf描述的数据在RAM中,但位置与pbuf结构所处位置无关 PBUF_POOL //pbuf描述的数据与其描述的数据处于同一内存池中 } pbuf_type;
PBUF_RAM
PBUF_POOL
PBUF_ROM/PBUF_REF
三、数据包管理相关操作函数
LwIP定义了四个层次,当数据包申请时,所处的层次不同,会导致预留空间的offset值不同。层次的定义是通过一个美剧类型pbuf_layer来实现的,如下:
#define PBUF_TRANSPORT_HLEN 20 //TCP报文首部长度 #define PBUF_IP_HLEN 20 //IP数据包首部长度 typedef enum { PBUF_TRANSPORT, //传输层 PBUF_IP, //网络层 PBUF_LINK, //链路层 PBUF_RAW //原始层,不预留任何空间 } pbuf_layer;
1. 数据包申请函数
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 */ LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F")\n", 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: LWIP_ASSERT("pbuf_alloc: bad pbuf layer", 0); return NULL; } switch (type) { case PBUF_POOL: /* allocate head of pbuf chain into p */ p = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL); LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc: allocated pbuf %p\n", (void *)p)); 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))); LWIP_ASSERT("pbuf_alloc: pbuf p->payload properly aligned", ((mem_ptr_t)p->payload % MEM_ALIGNMENT) == 0); /* 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)); LWIP_ASSERT("check p->payload + p->len does not overflow pbuf", ((u8_t*)p->payload + p->len <= (u8_t*)p + SIZEOF_STRUCT_PBUF + PBUF_POOL_BUFSIZE_ALIGNED)); LWIP_ASSERT("PBUF_POOL_BUFSIZE must be bigger than MEM_ALIGNMENT", (PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)) > 0 ); /* 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 */ LWIP_ASSERT("rem_len < max_u16_t", rem_len < 0xffff); 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); LWIP_ASSERT("pbuf_alloc: pbuf q->payload properly aligned", ((mem_ptr_t)q->payload % MEM_ALIGNMENT) == 0); LWIP_ASSERT("check p->payload + p->len does not overflow pbuf", ((u8_t*)p->payload + p->len <= (u8_t*)p + SIZEOF_STRUCT_PBUF + PBUF_POOL_BUFSIZE_ALIGNED)); 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; LWIP_ASSERT("pbuf_alloc: pbuf->payload properly aligned", ((mem_ptr_t)p->payload % MEM_ALIGNMENT) == 0); 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) { LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("pbuf_alloc: Could not allocate MEMP_PBUF for PBUF_%s.\n", (type == PBUF_ROM) ? "ROM" : "REF")); 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: LWIP_ASSERT("pbuf_alloc: erroneous type", 0); return NULL; } /* set reference count */ p->ref = 1; /* set flags */ p->flags = 0; LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F") == %p\n", length, (void *)p)); return p; }
2. 数据包释放函数
/** * @internal examples: * * Assuming existing chains a->b->c with the following reference * counts, calling pbuf_free(a) results in: * * 1->2->3 becomes ...1->3 * 3->3->3 becomes 2->3->3 * 1->1->2 becomes ......1 * 2->1->1 becomes 1->1->1 * 1->1->1 becomes ....... * */ 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; } LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free(%p)\n", (void *)p)); PERF_START; LWIP_ASSERT("pbuf_free: sane type", p->type == PBUF_RAM || p->type == PBUF_ROM || p->type == PBUF_REF || p->type == PBUF_POOL); 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; SYS_ARCH_DECL_PROTECT(old_level); /* Since decrementing ref cannot be guaranteed to be a single machine operation * we must protect it. We put the new ref into a local variable to prevent * further protection. */ SYS_ARCH_PROTECT(old_level); /* all pbufs in a chain are referenced at least once */ LWIP_ASSERT("pbuf_free: p->ref > 0", p->ref > 0); /* decrease reference count (number of pointers to pbuf) */ ref = --(p->ref); SYS_ARCH_UNPROTECT(old_level); /* this pbuf is no longer referenced to? */ if (ref == 0) { /* remember next pbuf in chain for next iteration */ q = p->next; LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: deallocating %p\n", (void *)p)); 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; LWIP_ASSERT("pc->custom_free_function != NULL", pc->custom_free_function != NULL); 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 { LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: %p has ref %"U16_F", ending here.\n", (void *)p, ref)); /* stop walking through the chain */ p = NULL; } } PERF_STOP("pbuf_free"); /* return number of de-allocated pbufs */ return count; }
3. 其他数据包操作函数
(1)pbuf_realloc:在相应pbuf(链表)尾部释放一定的空间,将数据包pbuf中的数据长度减少为某个长度值。
(2)pubf_header:用于调整pbuf的payload指针,常用于实现对pbuf中预留空间的操作。
(3)pbuf_take:用于向pbuf的数据区域拷贝数据。
(4)pbuf_copy:用于将一个任何类型的pbuf中的数据拷贝到一个PBUF_RAM类型的pbuf中。
(5)pbuf_chain:用于连接两个pbuf(链表)为一个pbuf链表。
(6)pbuf_ref:用于将pbuf中的ref加1。