LWIP网络数据包

19人阅读 评论(0) 收藏 举报
分类:

这一章我们来看看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;
}

这个函数的实现过程很清晰,首先根据包申请时传入的协议层参数,计算出需要在pbuf数据区前预留的长度offset,然后根据pbuf的类型进行申请。PBUF_POLL类型的申请较为麻烦,因为有可能申请的长度比较大,需要几个POOL连在一起。PBUF_REF和PBUF_ROM类型申请最简单,他们只是在内存池MEMP_PBUF中分配了一个pbuf结构空间,然后初始化相关字段,注意这两种类型的payload指针需要自己进行设置,通常在调用完函数pbuf_alloc后,调用者需要将payload指向某一个区域。

看看两个例子:

p=pbuf_alloc(PBUF_RAW,len,PBUF_RAM);//

这个调用语句申请了一个PBUF_RAM类型的pbuf,且其申请的协议层为PBUF_RAW,,所以不会在数据区预留出任何首部空间。

在TCP层申请一个数据包时

p = pbuf_alloc(PBUF_TRANSPORT,len,PBUF_RAM);

数据报分配函数使用PBUF_RAM类型的pbuf,且数据区前会预留一部分空间,预留54个字节,TCP首部20个字节,IP层首部20个字节,以太网帧首部14个字节,当数据报往下传递时,各层协议就可以直接操作这些预留中的数据,避免了数据的拷贝

数据报的释放

当底层完成数据包的发送或者上层完成数据的处理后,数据包必须释放,只有这样才能保证内存空间的持续可用。

Pbuf的ref字段表示该pbuf被引用的次数,当pbuf被创建时,该字段的初始值为1,代表被引用一次,只有当pbuf的ref字段为0时,该pbuf才能被删除。此外,能被删除的pbuf必然是某个pbuf链表的首节点或者是在其他地方被引用过的节点。这一点一定要格外注意。

       举几个简单的例子,假如现在我们的pbuf链表由A\B\C三个pbuf结构连接起来,结构A---B---C,利用pfree(A)来删除pbuf结构,下面用ABC几组不同的ref值来看删除结果

1、         A(1)---B(2)---C(3),删除后变成 B(1)---C(3)

2、         A(3)---B(3)---C(3),删除后变成A(2)--- B(3)---C(3)

3、         A(1)---B(1)---C(2),删除后变成 C(1)

4、         A(2)---B(1)---C(1),删除后变成 A(1)---B(1)---C(1)

5、         A(1)---B(1)---C(1),删除后无节点

对于第4种情况,如果我们使用pbuf_free(B),那B和C节点就被删除了,但A节点仍然指向B,这就使系统埋下了隐患。

看一下释放函数的实现

/**
 * Dereference a pbuf chain or queue and deallocate any no-longer-used
 * pbufs at the head of this chain or queue.
 *
 * Decrements the pbuf reference count. If it reaches zero, the pbuf is
 * deallocated.
 *
 * For a pbuf chain, this is repeated for each pbuf in the chain,
 * up to the first pbuf which has a non-zero reference count after
 * decrementing. So, when all reference counts are one, the whole
 * chain is free'd.
 *
 * @param p The pbuf (chain) to be dereferenced.
 *
 * @return the number of pbufs that were de-allocated
 * from the head of the chain.
 *
 * @note MUST NOT be called on a packet queue (Not verified to work yet).
 * @note the reference counter of a pbuf equals the number of pointers
 * that refer to the pbuf (or into the pbuf).
 *
 * @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) {                       //p为NULL,返回
    return 0;
  }
 
  count = 0;                             //记录值清零
  /* 循环查找p链表*/
  while (p != NULL) {
    u16_t ref;
    SYS_ARCH_DECL_PROTECT(old_level);   //申请临界保护变量
 
    SYS_ARCH_PROTECT(old_level);         //进入临界区

    ref = --(p->ref);                    //ref值自减
    SYS_ARCH_UNPROTECT(old_level);        //退出临界区
    /*ref字段是0,说明可以删除了*/
    if (ref == 0) {
      /* p链表中的下一个pbuf */
      q = p->next;
     
      type = p->type;
      /*根据不同的类型,申请不同的内存释放函数*/
      {
        /* 从内存池申请的, PBUF_POOL类型 */
        if (type == PBUF_POOL) {
          memp_free(MEMP_PBUF_POOL, p);
        /* 是PBUF_ROM或PBUF_REF) 类型的  */
        } else if (type == PBUF_ROM || type == PBUF_REF) {
          memp_free(MEMP_PBUF, p);
        /* 是PBUF_RAM类型的 */
        } else {
          mem_free(p);
        }
      }
      count++;
      /* 处理链表的下一个pbuf */
      p = q;

    } else {
     
      /* ref字段不是0,p为NULL,跳出while*/
      p = NULL;
    }
  }

  return count;
}
pbuf_free检查pbuf属于哪一种类型的pbuf,调用不同的释放函数进行删除。PBUF_POOL、PBUF_ROM和PBUF_ROM都通过memp_free来实现,PBUF_RAM使用mem_free来删除














查看评论

LWIP和DDR3配合实现 数据接收和发送(zedboard)

在LWIP的基础上,在Echo.c文件中的recv_callback()函数中,显示以太网的数据存储。 添加zynq对DDR3的支持文件和首地址定义(可在xparameters.h中查询) #...
  • liuheda
  • liuheda
  • 2016-08-17 16:45:29
  • 1909

lwip之数据收发流程

lwip从逻辑上看也是分为4层:链路层、网络层(IP、ARP、(ICMP、IGMP这两个协议是网络层的补充协议,并不严格属于网络层))、传输层(TCP、UDP)、应用层,基本等同TCP/IP,只是各层...
  • banruoju
  • banruoju
  • 2017-01-11 09:43:16
  • 6647

网卡驱动与lwip之间的连接

转载:http://blog.sina.com.cn/s/blog_62a85b950101am8b.html 《LwIP协议栈源码详解——TCP/IP协议的实现》网络接口结构 我只是不想...
  • yqa1027473639
  • yqa1027473639
  • 2017-03-14 11:36:46
  • 670

Lwip 使用流程

Lwip首先进行内存分配:Mem_init()内存栈起止地址,空闲列表初始化;Memp_init()内存池初始化,两者的详细比较会在http://my.oschina.net/u/274829/blo...
  • jrunw
  • jrunw
  • 2017-03-28 13:48:46
  • 886

lwip TCP传输速率振荡问题解决

【转贴】lwip TCP传输速率振荡问题解决 这些天使用VDSP集成的在VDK上移植的lwip协议栈传输视频数据,使用交叉线在DSP BF537与PC机之间用TCP传数据,速率变化很大,在5...
  • ShuoWangLiangXian
  • ShuoWangLiangXian
  • 2014-03-04 16:39:01
  • 1882

踩坑LWIP实现GPRS联网

GPRS LWIP
  • u011354506
  • u011354506
  • 2017-01-15 22:41:36
  • 1453

基于LwIP socket的TCP客户端

前面介绍了UDP客户端与UDP服务器编写的基本流程,我们都知道UDP是无连接的,下面介绍一下基于连接的TCP的编写方法,首先介绍TCP客户端编写流程,其步骤如下所示 1、创建一个基于流的socket...
  • noWorries
  • noWorries
  • 2013-04-27 09:57:29
  • 4380

LWIP使用经验---变态级(好文章)

LWIP使用经验 一 LWIP内存管理 数据包管理设置内存大小宏编译开关 二 LWIP启动时序三 LWIP运行逻辑 接收数据包SequentialAPI函数调用 四 TCPIP核心知识点 滑动...
  • yangzhao0001
  • yangzhao0001
  • 2015-09-21 14:19:53
  • 5166

lwip查看版本和下载源代码

1、查看版本 打开 lwip/CHANGELOG,可以看见(STABLE-1.X.X),例如(STABLE-1.3.2)。 (STABLE-1.3.2),这就是版本号。 2、下载源代码 ...
  • yangzhao0001
  • yangzhao0001
  • 2015-11-10 10:58:26
  • 4010
    个人资料
    持之以恒
    等级:
    访问量: 5972
    积分: 766
    排名: 6万+
    最新评论