lwip协议栈源码分析之pbuf

文章目录

    • 一,数据在tcp/ip层中的传递
    • 二,认识pbuf结构体
    • 二,创建一个pbuf
    • 三,释放pbuf
    • 四,给pbuf链表减肥
    • 五,移动payload
    • 六,小结

lwip使用pbuf对数据进行发送与接收,灵活的pbuf结构体使得数据在不同网络层之间传输时可以减少内存的开销,内存复制所占用的时间,一切都是为了节约内存,增加数据在不同层之间传递的速度。

一,数据在tcp/ip层中的传递

数据如何从tcp层一层层传递到最底层的物理层并发送出去呢?

应用层需要发送的数据传递到tcp层时,tcp层给数据添加首部数据,tcp层传递给ip层时,ip层将tcp层的所有数据(payload和首部数据)当成发送的数据,并给这份数据添加首部,这样一层层传递下去,如图:
lwip协议栈源码分析之pbuf_第1张图片

二,认识pbuf结构体

为了更好的描述以上的数据传递过程,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 示意图:
lwip协议栈源码分析之pbuf_第2张图片
对于不同网络层的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示意图:
lwip协议栈源码分析之pbuf_第3张图片
PBUF_REF与PBUF_ROM 示意图
lwip协议栈源码分析之pbuf_第4张图片

二,创建一个pbuf

通过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被引用的次数。

当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在使用中,可能原先分配的内存过大,需要调整为小点的内存,使用函数pbuf_realloc(struct pbuf *p, u16_t new_len);可以为pbuf链表重新分配内存:

其重点是对pbuf链表的操作,即找到链表中new_len所在的那个pbuf,然后给他重新分配内存,并释放它后面的pbuf。
lwip协议栈源码分析之pbuf_第5张图片

代码及注释如下:

//给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

在第一节我们可以看到,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的理解。
lwip协议栈源码分析之pbuf_第6张图片

你可能感兴趣的:(LWIP,lwip,tcpip,嵌入式互联网,互联网,pbuf)