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