lwip使用pbuf对数据进行发送与接收,灵活的pbuf结构体使得数据在不同网络层之间传输时可以减少内存的开销,内存复制所占用的时间,一切都是为了节约内存,增加数据在不同层之间传递的速度。
数据如何从tcp层一层层传递到最底层的物理层并发送出去呢?
应用层需要发送的数据传递到tcp层时,tcp层给数据添加首部数据,tcp层传递给ip层时,ip层将tcp层的所有数据(payload和首部数据)当成发送的数据,并给这份数据添加首部,这样一层层传递下去,如图:
为了更好的描述以上的数据传递过程,pbuf他来了。
struct pbuf {
struct pbuf *next; //指向下一个pbuf
void *payload; //指向buff中的真实数据
u16_t tot_len; //该len与其后所有pbuf的len
u16_t len; //payload中数据长度,不包括首部
u8_t type;
u8_t flags;
u16_t ref; //buffer被引用次数,包括next
};
1,pbuf结构体很简单,他支持单向链表,其核心是payload指针,该指针指向真实的数据起始地址,而payload前面的有一段长度为offset的偏移内存,这个内存是用于存放数据的首部的;这点与第一节所述一样。
PBUF_RAM 示意图:
对于不同网络层的pbuf,其首部的数据也是不同的,所以其对应的pbuf中offset的长度也是不一样。
例如:tcp报文中,固定首部通常是20个字节,还有4*n的选项字段和填充字段,所以tcp层的pbuf中,offset的最小值是20,其后payload指向tcp的数据。
2,len表示的是payload数据的长度,也就是不包括首部。
3,tot_len:表示当前pbuf和后面所有pbuf的len之和。
3,flag是记录pbuf的一些标志。其值如下:
/** indicates this packet's data should be immediately passed to the application */
#define PBUF_FLAG_PUSH 0x01U //立即发送
/** indicates this is a custom pbuf: pbuf_free calls pbuf_custom->custom_free_function()
when the last reference is released (plus custom PBUF_RAM cannot be trimmed) */
#define PBUF_FLAG_IS_CUSTOM 0x02U
/** indicates this pbuf is UDP multicast to be looped back */
#define PBUF_FLAG_MCASTLOOP 0x04U //udp多播返回
/** indicates this pbuf was received as link-level broadcast */
#define PBUF_FLAG_LLBCAST 0x08U //链路层的广播
/** indicates this pbuf was received as link-level multicast */
#define PBUF_FLAG_LLMCAST 0x10U //链路层的多播
/** indicates this pbuf includes a TCP FIN flag */
#define PBUF_FLAG_TCP_FIN 0x20U //tcp挥手标志
4,ref:表示该pbuf被外部引用的次数,也包括被上一个pbuf的next引用的情况,该变量用于防止在释放pbuf后导致内存读取错误。
5,由于payload指向的内存的性质不同,导致了pbuf的类型不同,pbuf的类型可用分为四种:在理解pbuf时,将pbuf结构体与payload指向的内存分开思考。
PBUF_RAM, 用于发送,pbuf和payload在连续的内存上
PBUF_ROM, pbuf在内存中,payload在外存
PBUF_REF, pbuf来自内存池,payload是其他程序段分配的内存,所以payload可能会被修改,发送时要复制payload;
PBUF_POOL, 用于接收,使用内存池分配。pbuf和payload在同一内存,pool内存大小是固定的,所以实际情况可能是多个pbuf连在一起。
PBUF_POOL示意图:
PBUF_REF与PBUF_ROM 示意图
通过pbuf_alloc()创建一个pbuf,要创建一个pbuf,需要知道三个参数:
1,这个pbuf所在的网络层,以此来确定offset的值,上层的offset不仅要为自己的首部留出空间,而且还需要为下层的首部留出空间,所以层级越高,offset越大。
2,存放数据的大小
3,pbuf的类型
代码如下,参考注释和第二节的各种类型的示意图,理解代码逻辑
//创建一个pbuf
//layer:网络层
//length:数据长度
//type:buffer类型
pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type)
{
struct pbuf *p, *q, *r;
u16_t offset; //payload在buffer的偏移
s32_t rem_len; /* remaining length */
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F")\n", length));
/* determine header offset */
//根据层级不同,计算不同的偏移空间,越高的层偏移越大
switch (layer) {
case PBUF_TRANSPORT:
/* add room for transport (often TCP) layer header */
offset = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN;
break;
case PBUF_IP:
/* add room for IP layer header */
offset = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN;
break;
case PBUF_LINK:
/* add room for link layer header */
offset = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN;
break;
case PBUF_RAW_TX:
/* add room for encapsulating link layer headers (e.g. 802.11) */
offset = PBUF_LINK_ENCAPSULATION_HLEN;
break;
case PBUF_RAW:
/* no offset (e.g. RX buffers or chain successors) */
offset = 0;
break;
default:
LWIP_ASSERT("pbuf_alloc: bad pbuf layer", 0);
return NULL;
}
//使用不同类型的内存,内存分配代码不同
switch (type) {
case PBUF_POOL: //通过内存池分配,可能需要若干个pool
/* allocate head of pbuf chain into p */
p = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL); //先分配第一个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 */
//移动payload在offset之后
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 */
//计算第一个pbuf的payload长度:若length小于一个pool,则就是length,否则就是(pool长度-偏移)
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; //引用次数+1
/* now allocate the tail of the pbuf chain */
/* remember first pbuf for linkage in next iteration */
r = p; //保存第一个pbuf指针
/* remaining length to be allocated */
rem_len = length - p->len; //还需要分配的长度
/* any remaining pbufs to be allocated? */
//还要再分配pool,直到满足所需内存,除第一个pbuf外,其他pbuf不需要offset预留空间给首部\
以下pbuf以链表组织在第一个pbuf后
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 unsuccessfully */
return NULL;
}
//设置pbuf的字段
q->type = type;
q->flags = 0;
q->next = NULL;
/* make previous pbuf point to this pbuf */
r->next = q; //将pbuf与前面的pbuf连接
/* 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); //!不是第一个pbuf,不需要偏移
q->payload = (void *)((u8_t *)q + SIZEOF_STRUCT_PBUF); //payload只需要移动固定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; //被next引用
/* 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:
{
//分配的内存=pbuf结构体大小+偏移大小+真实数据大小
mem_size_t alloc_len = LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF + offset) + LWIP_MEM_ALIGN_SIZE(length);
/* bug #50040: Check for integer overflow when calculating alloc_len */
//检验alloc_len(u16)是否溢出
if (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;
}
/* Set up internal structure of the pbuf. */
//初始化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;
}
释放pbuf需要注意pbuf被引用的次数。
当pbuf的ref成员为0时,则可以被释放,其后的pbuf会被判断是否需要被释放。若ref>0,则将ref-1并退出;具体的释放方式是通过调用内存释放函数进行释放,代码及注释如下:
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);
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; //记录被释放的pbuf数量
while (p != NULL) {
u16_t ref;
SYS_ARCH_DECL_PROTECT(old_level); //申请临界保护变量
SYS_ARCH_PROTECT(old_level); //进入临界区
LWIP_ASSERT("pbuf_free: p->ref > 0", p->ref > 0);
ref = --(p->ref); //该pbuf引用次数-1
SYS_ARCH_UNPROTECT(old_level);
//若引用次数为0,根据pbuf不同类型释放
if (ref == 0) {
q = p->next; //保存下一个pbuf
LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: deallocating %p\n", (void *)p));
type = p->type;
#if LWIP_SUPPORT_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
{
//pool类型释放MEMP_PBUF_POOL
if (type == PBUF_POOL) {
memp_free(MEMP_PBUF_POOL, p);
} else if (type == PBUF_ROM || type == PBUF_REF) {
#if ESP_LWIP
if (p->l2_owner != NULL
&& p->l2_buf != NULL
&& p->l2_owner->l2_buffer_free_notify != NULL) {
p->l2_owner->l2_buffer_free_notify(p->l2_buf);
}
#endif
memp_free(MEMP_PBUF, p);
/* type == PBUF_RAM */
} else {
mem_free(p);
}
}
count++;
//检查下一个是否也需要释放
p = q;
} else { //不为0,退出释放
LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: %p has ref %"U16_F", ending here.\n", (void *)p, ref));
p = NULL;
}
}
PERF_STOP("pbuf_free");
return count;
}
pbuf在使用中,可能原先分配的内存过大,需要调整为小点的内存,使用函数pbuf_realloc(struct pbuf *p, u16_t new_len);
可以为pbuf链表重新分配内存:
其重点是对pbuf链表的操作,即找到链表中new_len所在的那个pbuf,然后给他重新分配内存,并释放它后面的pbuf。
代码及注释如下:
//给pbuf减肥,新的长度为new——len
void
pbuf_realloc(struct pbuf *p, u16_t new_len)
{
struct pbuf *q;
u16_t rem_len; /* remaining length */
s32_t grow; //需要增加的长度,其实这个值是负数,也就是长度实际上是减少
LWIP_ASSERT("pbuf_realloc: p != NULL", p != NULL);
LWIP_ASSERT("pbuf_realloc: sane p->type", p->type == PBUF_POOL ||
p->type == PBUF_ROM ||
p->type == PBUF_RAM ||
p->type == PBUF_REF);
//新长度不能大于pbuf链的总长
if (new_len >= p->tot_len) {
/* enlarging not yet supported */
return;
}
grow = new_len - p->tot_len; //需要减少的长度
rem_len = new_len; //剩余长度=全新的长度
q = p;
//从pbuf链开始往下找,找到满足pbuf链表中长度为new_len时的pbuf
while (rem_len > q->len) {
rem_len -= q->len; //每经过一个pbuf剩余长度就减少p->len
LWIP_ASSERT("grow < max_u16_t", grow < 0xffff);
q->tot_len += (u16_t)grow; //该pbuf的tot_len减少
q = q->next; //下一个
LWIP_ASSERT("pbuf_realloc: q != NULL", q != NULL);
}
//找到最后一个pbuf,PBUF_RAM类型且rem_len小于pbuf原来的大小,则重新分配pbuf的大小
if ((q->type == PBUF_RAM) && (rem_len != q->len)
#if LWIP_SUPPORT_CUSTOM_PBUF
&& ((q->flags & PBUF_FLAG_IS_CUSTOM) == 0)
#endif
) {
//新的pbuf=首部大小(payload-q)+rem_len
q = (struct pbuf *)mem_trim(q, (u16_t)((u8_t *)q->payload - (u8_t *)q) + rem_len);
LWIP_ASSERT("mem_trim returned q == NULL", q != NULL);
}
//调节最后一个pbuf的长度
q->len = rem_len;
q->tot_len = q->len;
//q后面的pbuf不会被使用了,释放掉
if (q->next != NULL) {
pbuf_free(q->next);
}
q->next = NULL;
}
在第一节我们可以看到,payload指针在数据传递过程中需要频繁的移动,这个过程是由函数pbuf_header();实现的。
//header_size_increment>0,payload前移,数据传递下层
//header_size_increment<0,payload后移,数据传递上层
pbuf_header(struct pbuf *p, s16_t header_size_increment)
{
return pbuf_header_impl(p, header_size_increment, 0);
}
函数很简单,header_size_increment决定了payload移动的方向和距离。通过调用pbuf_header_impl();实现;
其中,需要判断pbuf结构体与payload指向的地址是否连续,如果连续的情况(如RAM,POOL类型)则需要注意payload指针不能超出边界。代码注释如下:
//移动pbuf首部地址
//header_size_increment>0 首部在payload外,需要放到payload中
pbuf_header_impl(struct pbuf *p, s16_t header_size_increment, u8_t force)
{
u16_t type;
void *payload;
u16_t increment_magnitude; //位移
LWIP_ASSERT("p != NULL", p != NULL);
if ((header_size_increment == 0) || (p == NULL)) {
return 0;
}
//计算header_size_increment的绝对值
if (header_size_increment < 0) {
increment_magnitude = (u16_t)-header_size_increment;
/* Check that we aren't going to move off the end of the pbuf */
LWIP_ERROR("increment_magnitude <= p->len", (increment_magnitude <= p->len), return 1;);
} else {
increment_magnitude = (u16_t)header_size_increment;
}
type = p->type;
/* remember current payload pointer */
payload = p->payload; //暂存原payload
/* pbuf types containing payloads? */
//如果payload与pbuf结构体是在连续内存,则直接移动payload
if (type == PBUF_RAM || type == PBUF_POOL) {
/* set new payload pointer */
p->payload = (u8_t *)p->payload - header_size_increment;
/* boundary check fails? */
//如果payload超过buffer头部内存边界,则复原payload,退出
if ((u8_t *)p->payload < (u8_t *)p + SIZEOF_STRUCT_PBUF) {
LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_TRACE,
("pbuf_header: failed as %p < %p (not enough space for new header size)\n",
(void *)p->payload, (void *)((u8_t *)p + SIZEOF_STRUCT_PBUF)));
/* restore old payload pointer */
p->payload = payload;
/* bail out unsuccessfully */
return 1;
}
//如果pbuf与payload内存不连续,则无需检查是否超出边界
} else if (type == PBUF_REF || type == PBUF_ROM) {
/* hide a header in the payload? */
//header_size_increment < 0说明首部在payload中,将payload指针后移
if ((header_size_increment < 0) && (increment_magnitude <= p->len)) {
/* increase payload pointer */
p->payload = (u8_t *)p->payload - header_size_increment;
} else if ((header_size_increment > 0) && force) {
p->payload = (u8_t *)p->payload - header_size_increment; //首部在payload外,payload需要前移
} else {
/* cannot expand payload to front (yet!)
* bail out unsuccessfully */
return 1;
}
} else {
/* Unknown type */
LWIP_ASSERT("bad pbuf type", 0);
return 1;
}
/* modify pbuf length fields */
//更新pbuf的成员
p->len += header_size_increment;
p->tot_len += header_size_increment;
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_header: old %p new %p (%"S16_F")\n",
(void *)payload, (void *)p->payload, header_size_increment));
return 0;
}
学习好pbuf,重点是理解pbuf结构体的payload成员,以及对各个类型的pbuf有一个抽象的认识。最好搭配示意图理解pbuf在数据传递过程中的灵活性。
pbuf是lwip协议各层数据传递的基础,掌握得好,后面事半功倍。
pbuf其他函数比较少用,读者可以自己去看,加深对pbuf的理解。