概述
协议栈的本质:
TCP/IP协议栈的实现,本质上就是对数据包的管理。在LwIP中,定义了一个pbuf结构体对数据包进行管理。
pbuf管理数据包的步骤:
1、用户产生要传输的数据
2、用户在内存堆/内存池中申请一个pbuf结构体
3、将数据拷贝到申请的内存中,加上应用层的首部
4、依次加上各层的首部,直至发送出去
pbuf相关特征:
pbuf管理的数据能够在各层去访问,即:每个层访问的数据内存空间是一个空间,而不是每层之间相互独立。
pbuf结构体:
struct pbuf {
struct pbuf *next;
void *payload;
u16_t tot_len;
u16_t len;
u8_t type_internal;
u8_t flags;
LWIP_PBUF_REF_T ref;
u8_t if_idx;
LWIP_PBUF_CUSTOM_DATA
};
next:当一个数据段需要分段来管理时会有多个pbuf,这些pbuf以链表形式进行连接
payload:指向数据区的指针
tot_len:pbuf的总长度,tot_len = len+next->len+....
len:当前pbuf中数据的总长度
type_internal:pbuf的类型,该值定义在pbuf_type枚举类型中
pbuf的类型 | 分配方式 | pbuf内容 |
PBUF_RAM | 内存堆 | pbuf控制头+数据区域 (一般情况下使用) |
PBUF_POOL | 内存池 | pbuf控制头+数据区域 (常用于中断) |
PBUF_ROM | 内存池 | 只有pbuf控制头,数据区域在ROM中 |
PBUF_REF | 内存池 | 只有pbuf控制头,数据区域在RAM中 |
ref:pbuf被引用的次数
pbuf_layer枚举类型:
pbuf_layer规定了pbuf的层次,不同的层次对应了不同的预留的报头空间。
typedef enum {
PBUF_TRANSPORT = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN,
PBUF_IP = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN,
PBUF_LINK = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN,
PBUF_RAW_TX = PBUF_LINK_ENCAPSULATION_HLEN,
PBUF_RAW = 0
} pbuf_layer;
PBUF_TRANSPORT:传输层报头的空闲空间,一般发送数据选择该层次
PBUF_IP:网络层IP头的空闲空间,用作ICMP协议发送
PBUF_LINK:链路层报头(IP分段、ARP数据包)
PBUF_RAW_TX:以太网前附加封装头的空闲空间
PBUF_RAW:原始层,不预留任何空间,即:没有报头,只有数据
相关函数
1、pbuf_alloc
函数功能:
根据类型(pbuf_type),申请pbuf内存。
函数结构:
通过传入的类型参数,进行相应的内存申请,整个函数就一个switch-case
- PBUF_REF、PBUF_ROM:
它们就是在MEMP_PBUF内存池申请一个pbuf结构体空间,payload指向NULL
- PBUF_POOL
它是在MEMP_PBUF_POOL内存池申请一个pbuf+数据的空间,之后需要判断申请的数据空间是否足够,如果不足够,需要以链表形式将多个pbuf链接。
注意:在数据空间中,只有第一个pbuf有报文头,但每个空间都有一个pbuf控制块。
- PBUF_RAM
它是在内存堆里申请一个pbuf+数据空间的内存堆
PBUF_REF、PBUF_ROM函数实现:
/* 在pbuf_alloc中相关的代码 */
/* 该代码最终调用pbuf_alloc_reference */
case PBUF_REF: /* fall through */
case PBUF_ROM:
p = pbuf_alloc_reference(NULL, length, type);
break;
/* pbuf_alloc_reference的代码 */
struct pbuf *
pbuf_alloc_reference(void *payload, u16_t length, pbuf_type type)
{
struct pbuf *p;
LWIP_ASSERT("invalid pbuf_type", (type == PBUF_REF) || (type == PBUF_ROM));
//在MEMP_PBUF内存池中申请空间
p = (struct pbuf *)memp_malloc(MEMP_PBUF);
if (p == NULL) {
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
("pbuf_alloc_reference: Could not allocate MEMP_PBUF for PBUF_%s.\n",
(type == PBUF_ROM) ? "ROM" : "REF"));
return NULL;
}
//初始化pbuf,注意这里的payload=NULL,因为该类型中不包含数据
pbuf_init_alloced_pbuf(p, payload, length, length, type, 0);
return p;
}
PBUF_POOL函数实现:
case PBUF_POOL: {
//q为新的pbuf,last为最后一个pbuf,p为pbuf的头
//申请空间后,以尾插法将新节点连接
struct pbuf *q, *last;
u16_t rem_len; /* remaining length */
p = NULL;
last = NULL;
rem_len = length;
do {
u16_t qlen;
//申请内存池,该内存池的大小 = pbuf大小+PBUF_POOL_BUFSIZE_ALIGNED(数据空间)
q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);
if (q == NULL) {
PBUF_POOL_IS_EMPTY();
/* free chain so far allocated */
if (p) {
pbuf_free(p);
}
/* bail out unsuccessfully */
return NULL;
}
//取数据长度与可用数据空间的最小值,可用空间 = 总数据空间 - 报文头空间
//PBUF_POOL_BUFSIZE_ALIGNED是总数据空间,pbuf的空间已经被减去了,因此该计算值是数据可用的空间
qlen = LWIP_MIN(rem_len, (u16_t)(PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)));
//初始化pbuf管理的空间,payload偏移 pbuf大小 + 报文头大小
pbuf_init_alloced_pbuf(q, LWIP_MEM_ALIGN((void *)((u8_t *)q + SIZEOF_STRUCT_PBUF + offset)),
rem_len, qlen, type, 0);
LWIP_ASSERT("pbuf_alloc: pbuf q->payload properly aligned",
((mem_ptr_t)q->payload % MEM_ALIGNMENT) == 0);
LWIP_ASSERT("PBUF_POOL_BUFSIZE must be bigger than MEM_ALIGNMENT",
(PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)) > 0 );
if (p == NULL) {
/* allocated head of pbuf chain (into p) */
p = q;
} else {
/* make previous pbuf point to this pbuf */
last->next = q;
}
last = q;
rem_len = (u16_t)(rem_len - qlen);
offset = 0;
} while (rem_len > 0);
break;
}
PBUF_RAM函数实现:
case PBUF_RAM: {
mem_size_t payload_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(offset) + LWIP_MEM_ALIGN_SIZE(length));
mem_size_t alloc_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF) + payload_len);
/* bug #50040: Check for integer overflow when calculating alloc_len */
if ((payload_len < LWIP_MEM_ALIGN_SIZE(length)) ||
(alloc_len < LWIP_MEM_ALIGN_SIZE(length))) {
return NULL;
}
//在内存堆中申请空间
p = (struct pbuf *)mem_malloc(alloc_len);
if (p == NULL) {
return NULL;
}
//初始化pbuf
pbuf_init_alloced_pbuf(p, LWIP_MEM_ALIGN((void *)((u8_t *)p + SIZEOF_STRUCT_PBUF + offset)),
length, length, type, 0);
LWIP_ASSERT("pbuf_alloc: pbuf->payload properly aligned",
((mem_ptr_t)p->payload % MEM_ALIGNMENT) == 0);
break;
}
pbuf_alloc完整函数:
struct pbuf *
pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type)
{
struct pbuf *p;
u16_t offset = (u16_t)layer;
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F")\n", length));
//根据类型,申请对应的pbuf空间
switch (type) {
case PBUF_REF: /* fall through */
case PBUF_ROM:
p = pbuf_alloc_reference(NULL, length, type);
break;
case PBUF_POOL: {
struct pbuf *q, *last;//q为新的pbuf,last为最后一个pbuf
u16_t rem_len; /* remaining length */
p = NULL; //p为pbuf的头
last = NULL;
rem_len = length;
do {
u16_t qlen;
//申请内存池,该内存池的大小 = pbuf大小+PBUF_POOL_BUFSIZE_ALIGNED(数据空间)
q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);
if (q == NULL) {
PBUF_POOL_IS_EMPTY();
/* free chain so far allocated */
if (p) {
pbuf_free(p);
}
/* bail out unsuccessfully */
return NULL;
}
//取数据长度与可用数据空间的最小值,可用空间 = 总数据空间 - 报文头空间
//PBUF_POOL_BUFSIZE_ALIGNED是总数据空间,pbuf的空间已经被减去了,因此该计算值是数据可用的空间
qlen = LWIP_MIN(rem_len, (u16_t)(PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)));
//初始化pbuf管理的空间,payload偏移 pbuf大小 + 报文头大小
pbuf_init_alloced_pbuf(q, LWIP_MEM_ALIGN((void *)((u8_t *)q + SIZEOF_STRUCT_PBUF + offset)),
rem_len, qlen, type, 0);
LWIP_ASSERT("pbuf_alloc: pbuf q->payload properly aligned",
((mem_ptr_t)q->payload % MEM_ALIGNMENT) == 0);
LWIP_ASSERT("PBUF_POOL_BUFSIZE must be bigger than MEM_ALIGNMENT",
(PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)) > 0 );
if (p == NULL) {
/* allocated head of pbuf chain (into p) */
p = q;
} else {
/* make previous pbuf point to this pbuf */
last->next = q;
}
last = q;
rem_len = (u16_t)(rem_len - qlen);
offset = 0;
} while (rem_len > 0);
break;
}
case PBUF_RAM: {
mem_size_t payload_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(offset) + LWIP_MEM_ALIGN_SIZE(length));
mem_size_t alloc_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF) + payload_len);
/* bug #50040: Check for integer overflow when calculating alloc_len */
if ((payload_len < LWIP_MEM_ALIGN_SIZE(length)) ||
(alloc_len < LWIP_MEM_ALIGN_SIZE(length))) {
return NULL;
}
/* If pbuf is to be allocated in RAM, allocate memory for it. */
p = (struct pbuf *)mem_malloc(alloc_len);
if (p == NULL) {
return NULL;
}
pbuf_init_alloced_pbuf(p, LWIP_MEM_ALIGN((void *)((u8_t *)p + SIZEOF_STRUCT_PBUF + offset)),
length, length, type, 0);
LWIP_ASSERT("pbuf_alloc: pbuf->payload properly aligned",
((mem_ptr_t)p->payload % MEM_ALIGNMENT) == 0);
break;
}
default:
LWIP_ASSERT("pbuf_alloc: erroneous type", 0);
return NULL;
}
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F") == %p\n", length, (void *)p));
return p;
}
2、pbuf_free
函数功能:
释放pbuf内存
函数结构:
以链表遍历的形式遍历并释放pbuf
释放时,首先通过ref判断pbuf是否在其他地方被使用,只有未被使用的pbuf才能被释放
之后获得pbuf的类型,并根据pbuf的类型进行相应操作的释放
函数实现:
u8_t
pbuf_free(struct pbuf *p)
{
u8_t alloc_src;
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;
count = 0;
/* de-allocate all consecutive pbufs from the head of the chain that
* obtain a zero reference count after decrementing*/
//以链表遍历的情况进行释放
while (p != NULL) {
LWIP_PBUF_REF_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=1,之后if (ref == 0)是为了确保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));
//获取到pbuf的类型
alloc_src = pbuf_get_allocsrc(p);
#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 */
{
//根据pbuf的类型进行相应的释放操作
/* is this a pbuf from the pool? */
if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL) {
memp_free(MEMP_PBUF_POOL, p);
/* is this a ROM or RAM referencing pbuf? */
} else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF) {
memp_free(MEMP_PBUF, p);
/* type == PBUF_RAM */
} else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP) {
mem_free(p);
} else {
/* @todo: support freeing other types */
LWIP_ASSERT("invalid pbuf type", 0);
}
}
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, (u16_t)ref));
/* stop walking through the chain */
p = NULL;
}
}
PERF_STOP("pbuf_free");
/* return number of de-allocated pbufs */
return count;
}