LWIP网络数据包

这一章我们来看看LWIP中网络数据包pbuf。在协议栈内核中移动的数据包,无疑是整个内核最关键的部分。数据包的种类和大小五花八门:首先是网卡上接收的原始数据包,它可以是包含TCP报文的长达数百字节的数据包,也可以是仅有几十字节的ARP数据包;然后是要发送的数据包,上层应用可能是各种各样的数据交给LWIP内核发送。

数据包管理机构采用数据结构pbuf来描述协议栈中使用的数据包,结构pbuf的定义如下:

struct pbuf {
  struct pbuf *next;  //pbuf链表中指向下一个pbuf结构
  void *payload;    //数据指针,指向该pbuf所记录的数据区域
  u16_t tot_len;    //当前pbuf及后续所有pbuf中所包含的数据//总长度
   u16_t len;       //当前pbuf中数据的长度
  u8_t  type;       //当前pbuf的类型
  u8_t flags;        //状态位未用到    
  u16_t ref;         //指向该pbuf的指针数,即该pbuf被引用//的次数
};

next: 这个指向下一个pbuf结构,因为实际发送或接收的数据包可能很大,而每个pbuf能够管理的数据有限,所以,存在需要多个pbuf结构才能完全描述一个数据包的情况。此时,所有描述同一个数据包的pbuf需要连接在一个链表上。

payload:数据指针,指向该pbuf管理的数据起始地址。数据地址可以在RAM空间中,也可以在处在ROM中的某个地址,这个与参数type相关。

totlen:表示的当前pbuf以及以后所有pbuf的有效数据的总长度。len则表示的是当前pbuf的数据长度。所以,totlen是当前len字段和pbuf链表中下一个pbuf的totl_len字段之和。pbuf链表中第一个pbuf的tol_len字段表示整个数据包的长度。而最后一个pbuf的tot_len字段同len字段相等。

type字段表示pbuf的类型,具体来说pbuf有四种类型,待会儿详细解释

flags这里暂时没有用到

ref字段表示的是该pbuf被引用的次数。引用表示有其他指针指向当前buf,这里的指针可以是其他pbuf的next指针,也可以是其他任何形式的指针。初始化一个pbuf的时候,ref字段值被设置为1。

pbuf的类型

pbuf有四种类型: PBUF_RAM, PBUF_ROM, PBUF_REF, PBUF_POOL.


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的空间是通过内存堆分配得到的。这也是最常用的一种类型。申请PBUF_RAM类型的pbuf时协议栈会在内存堆中分配相应空间,这里的大小包括如前面所述的pbuf结构和相应数据缓冲区的大小,并且,他们是在一片连续的内存堆存储空间的。分配完成后,结构如下


Payload指向的并不一定是数据区域的开始,可以设定一定的offset,这个offset常用来存储TCP报文首部、IP首部等等,payload指向一般是除去帧头的首部后的数据区域。

       PBUF_POOL类型和PBUF_RAM类型的pbuf有很大的相似之处,但他的空间是通过内存分配池得到的。这种类型的pbuf可以在极短的时间内得到分配。在网卡接收数据包时,我们就使用了这种方式包装数据。事实上,在系统初始化内存池的时候,还会初始化两类与数据报pbuf密切相关的POOL,这个可以参考上一章讲内存池时候的内容。它的名字分别是MEMP_PBUF和MEMP_PBUF_POOL,前者是用来存储pbuf结构的,主要在后两类PBUF_REF和PBUF_POOL中使用。而后者MEMP_PBUF_POOL的空间不仅包含了pbuf结构,还包含了LWIP认为协议栈可能使用的最大TCP数据报空间,默认长度为590个字节,很显然,这个长度小于某些大的以太网数据报,此时可能需要几个MEMP+PBUF_POOL空间才能放下这样一个大的数据报。


分配成功后如上图。

PBUF_RAM和PBUF_POOL的区别在于,PBUF_RAM的数据区域是在一起的,大小也是不固定的。而PBUF_POOL的数据区域大小是固定的,可以多个pbuf数据连接在一起形成链表关系。

剩余的两个PBUF_ROM和PBUF_REF比较类似,他们都是在内存池中分配一个相应的pbuf结构,但不申请数据区的空间,他们两者的区别在于PBUF_ROM指向ROM空间内的数据,后者指向RAM空间内的某段数据。在发送某些静态数据时,可以采用这两种类型的pbuf,这可以大大节省协议栈的内存空间,结构如下


另外,对于一个数据包来讲,它可能使用上述任意的pbuf类型来描述,还可以一大串不同类型的pbuf连在一起,共同保存一个数据包的数据


数据包申请函数

        数据包申请函数pbuf_alloc在系统中的许多地方都会用到,在网卡接收数据时,需要申请一个数据包,然后将网卡中的数据填入数据包中

 
///用于接收数据包的最底层函数
//neitif:网卡结构体指针
//返回值:pbuf数据结构体指针
static struct pbuf * low_level_input(struct netif *netif)
{  
	struct pbuf *p, *q;
	u16_t len;
……..	
	len=frame.length;//得到包大小
	buffer=(u8 *)frame.buffer;//得到包数据地址 
	p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL);//pbufs内存池分配pbuf
	……….
	return p;
}

        又例如发送数据包时,协议栈的某层会申请一个pbuf,并将相应的数据装入到数据区域,同时相关的协议首部信息也会被填入到pbuf预留数据区域内。数据包申请函数有两个重要的参数:数据包pbuf的类型和数据包在那一层被申请。数据包类型就是我们之前讲的那四种,数据包在那一层申请这个参数主要是为了确定前面所说的偏移offset值。LWIP定义了四个层次,当数据包申请时,所处的层次不同,就会导致预留空间的的offset值不同。层次的定义是通过一个枚举类型pbuf_layer来实现

typedef enum {
  PBUF_TRANSPORT,           //传输层
  PBUF_IP,                   //网络层    
  PBUF_LINK,                //链路层
  PBUF_RAW                /原始层,不预留任何空间
} pbuf_layer;
#define PBUF_TRANSPORT_HLEN 20   //TCP报文首部长度
#define PBUF_IP_HLEN        20    /IP数据报文长度

这里还定义了IP报文首部和TCP报文首部长度。

pbuf分配函数pbuf_alloc的实现如下:

/**
 * Allocates a pbuf of the given type (possibly a chain for PBUF_POOL type).
 *
 * The actual memory allocated for the pbuf is determined by the
 * layer at which the pbuf is allocated and the requested size
 * (from the size parameter).
 *
 * @param layer flag to define header size
 * @param length size of the pbuf's payload
 * @param type this parameter decides how and where the pbuf
 * should be allocated as follows:
 *
 * - PBUF_RAM: buffer memory for pbuf is allocated as one large
 *             chunk. This includes protocol headers as well.
 * - PBUF_ROM: no buffer memory is allocated for the pbuf, even for
 *             protocol headers. Additional headers must be prepended
 *             by allocating another pbuf and chain in to the front of
 *             the ROM pbuf. It is assumed that the memory used is really
 *             similar to ROM in that it is immutable and will not be
 *             changed. Memory which is dynamic should generally not
 *             be attached to PBUF_ROM pbufs. Use PBUF_REF instead.
 * - PBUF_REF: no buffer memory is allocated for the pbuf, even for
 *             protocol headers. It is assumed that the pbuf is only
 *             being used in a single thread. If the pbuf gets queued,
 *             then pbuf_take should be called to copy the buffer.
 * - PBUF_POOL: the pbuf is allocated as a pbuf chain, with pbufs from
 *              the pbuf pool that is allocated during pbuf_init().
 *
 * @return the allocated pbuf. If multiple pbufs where allocated, this
 * is the first pbuf of a pbuf chain.
 */
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;                                                          //还需要申请的数据空间长度
 
  /*根据申请的层次不同,预留不同的空间长度 */
  switch (layer) {
  case PBUF_TRANSPORT:
    /* 传输层,预留以太网帧首部+IP层首部+TCP层首部 */
    offset = PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN;
    break;
  case PBUF_IP:
    /* 网络层,预留以太网首部+IP层首部 */
    offset = PBUF_LINK_HLEN + PBUF_IP_HLEN;
    break;
  case PBUF_LINK:
    /* 网络接口层,预留以太网首部 */
    offset = PBUF_LINK_HLEN;
    break;
  case PBUF_RAW:
    /*原始层,不用预留*/
    offset = 0;
    break;
  default:
    LWIP_ASSERT("pbuf_alloc: bad pbuf layer", 0);
    return NULL;
  }

  switch (type) {
  case PBUF_POOL:
    /* PBUF_POOL类型,根据长度不同,可能需要多个POOL */
    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;

    /* 初始化payload字段,指向数据其实区域,预留出首部空间 */
    p->payload = LWIP_MEM_ALIGN((void *)((u8_t *)p + (SIZEOF_STRUCT_PBUF + offset)));
    /* tot_len为总长度 */
    p->tot_len = length;
    /* 当前pbuf的长度*/
    p->len = LWIP_MIN(length, PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset));

    /* ref字段设置为1 */
    p->ref = 1;

    /* r用于记录链表p上的最后一个pbuf*/
    r = p;
    /* 还需分配的长度 */
    rem_len = length - p->len;
    /* 多次分配直到rem_len不大于0 */
    while (rem_len > 0) {
      /*分配一个POLL*/
      q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);
      /*若分配失败,释放链表上的所有pbuf*/
	if (q == NULL) {
        PBUF_POOL_IS_EMPTY();
        /* free chain so far allocated */
        pbuf_free(p);
        /* bail out unsuccesfully */
        return NULL;
      }
      /*分配成功,初始化各个字段,并将q加入链表p中*/
      q->type = type;
      q->flags = 0;
      q->next = NULL;
     
      r->next = q;

      q->tot_len = (u16_t)rem_len;
 
      q->len = LWIP_MIN((u16_t)rem_len, PBUF_POOL_BUFSIZE_ALIGNED);
      q->payload = (void *)((u8_t *)q + SIZEOF_STRUCT_PBUF);

      q->ref = 1;
      /* 还需申请的长度 */
      rem_len -= q->len;
      /* r指向链表p的最后要给pbuf */
      r = q;
    }
    /* end of chain */
    /*r->next = NULL;*/

    break;
  case PBUF_RAM:
    /* 在内存堆中申请 */
    p = (struct pbuf*)mem_malloc(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF + offset) + LWIP_MEM_ALIGN_SIZE(length));
    if (p == NULL) {
      return NULL;
    }
    /* 申请成功,初始化各个字段 */
    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_ROM和PBUF_REF只分配pbuf结构的空间,不申请数据空间:*/
  case PBUF_ROM:
 
  case PBUF_REF:
  
    p = (struct pbuf *)memp_malloc(MEMP_PBUF);
    if (p == NULL) {

      return NULL;
    }
    /* 初始化各个字段,payload字段除外,需要用户来根据实际情况来设置 */
    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;

  return p;
}















猜你喜欢

转载自blog.csdn.net/u012142460/article/details/79962441