一、概述
lwIP - A Lightweight TCP/IP stack
The focus of the lwIP TCP/IP implementation is to reduce resource usage while still having a full scale TCP. This makes lwIP suitable for use in embedded systems with tens of kilobytes of free RAM and room for around 40 kilobytes of code ROM.
Main features include:
以上是LwIP官网的一段描述,LwIP全名:Lightweight IP,意思是轻量化的TCP/IP协议,是瑞典计算机科学院(SICS)的Adam Dunkels开发的一个小型开源的TCP/IP协议栈。LwIP的设计初衷是:用少量的资源消耗实现一个较为完整的TCP/IP协议栈,其中“完整”主要指的是TCP协议的完整性,实现的重点是在保持TCP协议主要功能的基础上减少对RAM的占用。此外LwIP既可以移植到操作系统上运行,也可以在无操作系统的情况下独立运行。
LwIP 具有主要特性:
LwIP在嵌入式中使用有以下优点:
LwIP的不足:
LwIP尽管有如此多的优点,但它毕竟是为嵌入式而生,所以并没有很完整地实现TCP/IP协议栈。相比于Linux和Windows系统自带的TCP/IP协议栈,LwIP的功能不算完整和强大。但对于大多数物联网领域的网络应用程序,LwIP已经足够了。
二、网络数据包处理
1、TCP/IP是一种数据通信机制,因此,协议栈的实现本质上就是对数据包进行处理,为了实现高效的效率,LwIP数据包管理要提供一种高效处理的机制。协议栈各层能对数据包进行灵活的处理,同时减少数据在各层间传递时的时间与空间开销,这是提高协议栈工作效率的关键点。在BSD的实现中,一个描述数据包的结构体叫做mbuf,同样的在LwIP中,也有个类似的结构,称之为pbuf。
标准协议栈 | LwIP协议栈 |
---|---|
严格分层 | 模糊分层 |
数据传输层层拷贝 | 数据传输直接操作 |
数据包私有 | 数据包共享 |
效率低 | 效率高 |
数据处理要求低 | 数据处理要求高 |
完整的TCP/IP协议 | 较完整TCP/IP协议 |
2、pbuf结构体就是一个描述协议栈中数据包的数据结构:
/** Main packet buffer struct */
struct pbuf
{
/** next pbuf in singly linked pbuf chain */
struct pbuf *next;
/** pointer to the actual data in the buffer */
void *payload;
/**
* total length of this buffer and all next buffers in chain
* belonging to the same packet.
*
* For non-queue packet chains this is the invariant:
* p->tot_len == p->len + (p->next? p->next->tot_len: 0)
*/
u16_t tot_len;
/** length of this buffer */
u16_t len;
/** a bit field indicating pbuf type and allocation sources
(see PBUF_TYPE_FLAG_*, PBUF_ALLOC_FLAG_* and PBUF_TYPE_ALLOC_SRC_MASK)
*/
u8_t type_internal;
/** misc flags */
u8_t flags;
/**
* the reference count always equals the number of pointers
* that refer to this pbuf. This can be pointers from an application,
* the stack itself, or pbuf->next pointers from a chain.
*/
LWIP_PBUF_REF_T ref;
/** For incoming packets, this contains the input netif's index */
u8_t if_idx;
};
3、pbuf的类型:
/**
* @ingroup pbuf
* Enumeration of pbuf types
*/
typedef enum
{
/** pbuf data is stored in RAM, used for TX mostly, struct pbuf and its payload
are allocated in one piece of contiguous memory (so the first payload byte
can be calculated from struct pbuf).
pbuf_alloc() allocates PBUF_RAM pbufs as unchained pbufs (although that might
change in future versions).
This should be used for all OUTGOING packets (TX).*/
PBUF_RAM = (PBUF_ALLOC_FLAG_DATA_CONTIGUOUS | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP),
/** pbuf data is stored in ROM, i.e. struct pbuf and its payload are located in
totally different memory areas. Since it points to ROM, payload does not
have to be copied when queued for transmission. */
PBUF_ROM = PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF,
/** pbuf comes from the pbuf pool. Much like PBUF_ROM but payload might change
so it has to be duplicated when queued before transmitting, depending on
who has a 'ref' to it. */
PBUF_REF = (PBUF_TYPE_FLAG_DATA_VOLATILE | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF),
/** pbuf payload refers to RAM. This one comes from a pool and should be used
for RX. Payload can be chained (scatter-gather RX) but like PBUF_RAM, struct
pbuf and its payload are allocated in one piece of contiguous memory (so
the first payload byte can be calculated from struct pbuf).
Don't use this for TX, if the pool becomes empty e.g. because of TCP queuing,
you are unable to receive TCP acks! */
PBUF_POOL = (PBUF_ALLOC_FLAG_RX | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL)
} pbuf_type;
/**
* @ingroup pbuf
* Enumeration of pbuf layers
*/
typedef enum
{
/** Includes spare room for transport layer header, e.g. UDP header.
* Use this if you intend to pass the pbuf to functions like udp_send().
*/
PBUF_TRANSPORT = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN,
/** Includes spare room for IP header.
* Use this if you intend to pass the pbuf to functions like raw_send().
*/
PBUF_IP = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN,
/** Includes spare room for link layer header (ethernet header).
* Use this if you intend to pass the pbuf to functions like ethernet_output().
* @see PBUF_LINK_HLEN
*/
PBUF_LINK = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN,
/** Includes spare room for additional encapsulation header before ethernet
* headers (e.g. 802.11).
* Use this if you intend to pass the pbuf to functions like netif->linkoutput().
* @see PBUF_LINK_ENCAPSULATION_HLEN
*/
PBUF_RAW_TX = PBUF_LINK_ENCAPSULATION_HLEN,
/** Use this for input packets in a netif driver when calling netif->input()
* in the most common case - ethernet-layer netif driver. */
PBUF_RAW = 0
} pbuf_layer;
PBUF_RAM类型的pbuf:
layer(offset)就是各层协议的首部,如TCP报文首部、IP首部、以太网帧首部等,预留出来的这些空间是为了在各个协议层中灵活地处理这些数据,当然layer的大小也可以是 0,具体是多少就与数据包的申请方式有关。
struct pbuf *pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type);
举个例子,假设TCP协议需要申请一个pbuf数据包,那么就会调用下面代码进行申请:
p = pbuf_alloc(PBUF_TRANSPORT, 1472, PBUF_RAM);
内核就会根据这句代码进行分配一个PBUF_RAM类型的pbuf,其数据区域大小是1472字节,并且会根据协议层次进行预留协议首部空间,由于是传输层,所以内核需要预留54个字节空间,即以太网帧首部长度PBUF_LINK_HLEN(14字节)、IP数据报首部长度PBUF_IP_HLEN(20字节)、TCP首部长度PBUF_TRANSPORT_HLEN(20字节)。当数据报往下层递交的时候,其他层
直接填充对应的协议首部即可,无需对数据进行拷贝等操作,这也是LwIP能快速处理的优势。
/* Initialize members of struct pbuf after allocation */
static void pbuf_init_alloced_pbuf(struct pbuf *p, void *payload, u16_t tot_len, u16_t len, pbuf_type type, u8_t flags)
{
p->next = NULL;
p->payload = payload;
p->tot_len = tot_len;
p->len = len;
p->type_internal = (u8_t)type;
p->flags = flags;
p->ref = 1;
p->if_idx = NETIF_NO_INDEX;
}
/**
* @ingroup pbuf
* 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 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;
u16_t offset = (u16_t)layer;
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F")\n", length));
switch (type)
{
case PBUF_REF: /* fall through */
case PBUF_ROM:
p = pbuf_alloc_reference(NULL, length, type);
break;
case PBUF_POOL:
{
struct pbuf *q, *last;
u16_t rem_len; /* remaining length */
p = NULL;
last = NULL;
rem_len = length;
do
{
u16_t qlen;
q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);
if (q == NULL)
{
PBUF_POOL_IS_EMPTY();
/* free chain so far allocated */
if (p)
{
pbuf_free(p);
}
/* bail out unsuccessfully */
return NULL;
}
qlen = LWIP_MIN(rem_len, (u16_t)(PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)));
pbuf_init_alloced_pbuf(q, LWIP_MEM_ALIGN((void *)((u8_t *)q + SIZEOF_STRUCT_PBUF + offset)), rem_len, qlen, type, 0);
LWIP_ASSERT("pbuf_alloc: pbuf q->payload properly aligned", ((mem_ptr_t)q->payload % MEM_ALIGNMENT) == 0);
LWIP_ASSERT("PBUF_POOL_BUFSIZE must be bigger than MEM_ALIGNMENT", (PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)) > 0);
if (p == NULL)
{
/* allocated head of pbuf chain (into p) */
p = q;
}
else
{
/* make previous pbuf point to this pbuf */
last->next = q;
}
last = q;
rem_len = (u16_t)(rem_len - qlen);
offset = 0;
} while (rem_len > 0);
break;
}
case PBUF_RAM:
{
u16_t payload_len = (u16_t)(LWIP_MEM_ALIGN_SIZE(offset) + LWIP_MEM_ALIGN_SIZE(length));
mem_size_t alloc_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF) + payload_len);
/* bug #50040: Check for integer overflow when calculating alloc_len */
if ((payload_len < LWIP_MEM_ALIGN_SIZE(length)) || (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;
}
pbuf_init_alloced_pbuf(p, LWIP_MEM_ALIGN((void *)((u8_t *)p + SIZEOF_STRUCT_PBUF + offset)), length, length, type, 0);
LWIP_ASSERT("pbuf_alloc: pbuf->payload properly aligned", ((mem_ptr_t)p->payload % MEM_ALIGNMENT) == 0);
break;
}
default:
LWIP_ASSERT("pbuf_alloc: erroneous type", 0);
return NULL;
}
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F") == %p\n", length, (void *)p));
return p;
}
/**
* @ingroup pbuf
* 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)
{
u8_t alloc_src;
struct pbuf *q;
u8_t count;
if (p == NULL)
{
LWIP_ASSERT("p != NULL", p != NULL);
/* if assertions are disabled, proceed with debug output */
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;
count = 0;
/* de-allocate all consecutive pbufs from the head of the chain that
* obtain a zero reference count after decrementing*/
while (p != NULL)
{
LWIP_PBUF_REF_T ref;
SYS_ARCH_DECL_PROTECT(old_level);
/* Since decrementing ref cannot be guaranteed to be a single machine operation
* we must protect it. We put the new ref into a local variable to prevent
* further protection. */
SYS_ARCH_PROTECT(old_level);
/* all pbufs in a chain are referenced at least once */
LWIP_ASSERT("pbuf_free: p->ref > 0", p->ref > 0);
/* decrease reference count (number of pointers to pbuf) */
ref = --(p->ref);
SYS_ARCH_UNPROTECT(old_level);
/* this pbuf is no longer referenced to? */
if (ref == 0)
{
/* remember next pbuf in chain for next iteration */
q = p->next;
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: deallocating %p\n", (void *)p));
alloc_src = pbuf_get_allocsrc(p);
#if LWIP_SUPPORT_CUSTOM_PBUF
/* is this a 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 /* LWIP_SUPPORT_CUSTOM_PBUF */
{
/* is this a pbuf from the pool? */
if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL)
{
memp_free(MEMP_PBUF_POOL, p);
/* is this a ROM or RAM referencing pbuf? */
}
else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF)
{
memp_free(MEMP_PBUF, p);
/* type == PBUF_RAM */
}
else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP)
{
mem_free(p);
}
else
{
/* @todo: support freeing other types */
LWIP_ASSERT("invalid pbuf type", 0);
}
}
count++;
/* proceed to next pbuf */
p = q;
/* p->ref > 0, this pbuf is still referenced to */
/* (and so the remaining pbufs in chain as well) */
}
else
{
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: %p has ref %"U16_F", ending here.\n", (void *)p, (u16_t)ref));
/* stop walking through the chain */
p = NULL;
}
}
PERF_STOP("pbuf_free");
/* return number of de-allocated pbufs */
return count;
}
/**
* Adjusts the payload pointer to hide headers in the payload.
*
* Adjusts the ->payload pointer so that space for a header
* disappears in the pbuf payload.
*
* The ->payload, ->tot_len and ->len fields are adjusted.
*
* @param p pbuf to change the header size.
* @param header_size_decrement Number of bytes to decrement header size which
* decreases the size of the pbuf.
* If header_size_decrement is 0, this function does nothing and returns successful.
* @return non-zero on failure, zero on success.
*
*/
u8_t pbuf_remove_header(struct pbuf *p, size_t header_size_decrement)
{
void *payload;
u16_t increment_magnitude;
LWIP_ASSERT("p != NULL", p != NULL);
if ((p == NULL) || (header_size_decrement > 0xFFFF))
{
return 1;
}
if (header_size_decrement == 0)
{
return 0;
}
increment_magnitude = (u16_t)header_size_decrement;
/* 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;);
/* remember current payload pointer */
payload = p->payload;
LWIP_UNUSED_ARG(payload); /* only used in LWIP_DEBUGF below */
/* increase payload pointer (guarded by length check above) */
p->payload = (u8_t *)p->payload + header_size_decrement;
/* modify pbuf length fields */
p->len = (u16_t)(p->len - increment_magnitude);
p->tot_len = (u16_t)(p->tot_len - increment_magnitude);
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_remove_header: old %p new %p (%"U16_F")\n", (void *)payload, (void *)p->payload, increment_magnitude));
return 0;
}
/**
* Adjusts the payload pointer to reveal headers in the payload.
* @see pbuf_add_header.
*
* @param p pbuf to change the header size.
* @param header_size_increment Number of bytes to increment header size.
* @param force Allow 'header_size_increment > 0' for PBUF_REF/PBUF_ROM types
*
* @return non-zero on failure, zero on success.
*
*/
static u8_t pbuf_add_header_impl(struct pbuf *p, size_t header_size_increment, u8_t force)
{
u16_t type_internal;
void *payload;
u16_t increment_magnitude;
LWIP_ASSERT("p != NULL", p != NULL);
if ((p == NULL) || (header_size_increment > 0xFFFF))
{
return 1;
}
if (header_size_increment == 0)
{
return 0;
}
increment_magnitude = (u16_t)header_size_increment;
/* Do not allow tot_len to wrap as a result. */
if ((u16_t)(increment_magnitude + p->tot_len) < increment_magnitude)
{
return 1;
}
type_internal = p->type_internal;
/* pbuf types containing payloads? */
if (type_internal & PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS)
{
/* set new payload pointer */
payload = (u8_t *)p->payload - header_size_increment;
/* boundary check fails? */
if ((u8_t *)payload < (u8_t *)p + SIZEOF_STRUCT_PBUF)
{
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_add_header: failed as %p < %p (not enough space for new header size)\n", (void *)payload, (void *)((u8_t *)p + SIZEOF_STRUCT_PBUF)));
/* bail out unsuccessfully */
return 1;
}
/* pbuf types referring to external payloads? */
}
else
{
/* hide a header in the payload? */
if (force)
{
payload = (u8_t *)p->payload - header_size_increment;
}
else
{
/* cannot expand payload to front (yet!)
* bail out unsuccessfully */
return 1;
}
}
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_add_header: old %p new %p (%"U16_F")\n", (void *)p->payload, (void *)payload, increment_magnitude));
/* modify pbuf fields */
p->payload = payload;
p->len = (u16_t)(p->len + increment_magnitude);
p->tot_len = (u16_t)(p->tot_len + increment_magnitude);
return 0;
}
static u8_t pbuf_header_impl(struct pbuf *p, s16_t header_size_increment, u8_t force)
{
if (header_size_increment < 0)
{
return pbuf_remove_header(p, (size_t) - header_size_increment);
}
else
{
return pbuf_add_header_impl(p, (size_t)header_size_increment, force);
}
}
/**
* Adjusts the payload pointer to hide or reveal headers in the payload.
* * Adjusts the ->payload pointer so that space for a header
* (dis)appears in the pbuf payload.
* * The ->payload, ->tot_len and ->len fields are adjusted.
* * @param p pbuf to change the header size.
* @param header_size_increment Number of bytes to increment header size which
* increases the size of the pbuf. New space is on the front.
* (Using a negative value decreases the header size.)
* If header_size_increment is 0, this function does nothing and returns successful.
* * PBUF_ROM and PBUF_REF type buffers cannot have their sizes increased, so
* the call will fail. A check is made that the increase in header size does
* not move the payload pointer in front of the start of the buffer.
* @return non-zero on failure, zero on success.
* */
u8_t pbuf_header(struct pbuf *p, s16_t header_size_increment)
{
return pbuf_header_impl(p, header_size_increment, 0);
}
/**
* @ingroup pbuf
* Concatenate two pbufs (each may be a pbuf chain) and take over
* the caller's reference of the tail pbuf.
* * @note The caller MAY NOT reference the tail pbuf afterwards.
* Use pbuf_chain() for that purpose.
* * This function explicitly does not check for tot_len overflow to prevent
* failing to queue too long pbufs. This can produce invalid pbufs, so
* handle with care!
* * @see pbuf_chain()
*/
void pbuf_cat(struct pbuf *h, struct pbuf *t)
{
struct pbuf *p;
LWIP_ERROR("(h != NULL) && (t != NULL) (programmer violates API)", ((h != NULL) && (t != NULL)), return;);
/* proceed to last pbuf of chain */
for (p = h; p->next != NULL; p = p->next)
{
/* add total length of second chain to all totals of first chain */
p->tot_len = (u16_t)(p->tot_len + t->tot_len);
}
/* { p is last pbuf of first h chain, p->next == NULL } */
LWIP_ASSERT("p->tot_len == p->len (of last pbuf in chain)", p->tot_len == p->len);
LWIP_ASSERT("p->next == NULL", p->next == NULL);
/* add total length of second chain to last pbuf total of first chain */
p->tot_len = (u16_t)(p->tot_len + t->tot_len);
/* chain last pbuf of head (p) with first of tail (t) */
p->next = t;
/* p->next now references t, but the caller will drop its reference to t,
* so netto there is no change to the reference count of t.
*/
}
/**
* @ingroup pbuf
* Increment the reference count of the pbuf.
*
* @param p pbuf to increase reference counter of
*
*/
void pbuf_ref(struct pbuf *p)
{
/* pbuf given? */
if (p != NULL)
{
SYS_ARCH_SET(p->ref, (LWIP_PBUF_REF_T)(p->ref + 1));
LWIP_ASSERT("pbuf ref overflow", p->ref > 0);
}
}
/**
* @ingroup pbuf
* Chain two pbufs (or pbuf chains) together.
* * The caller MUST call pbuf_free(t) once it has stopped
* using it. Use pbuf_cat() instead if you no longer use t.
* * @param h head pbuf (chain)
* @param t tail pbuf (chain)
* @note The pbufs MUST belong to the same packet.
* @note MAY NOT be called on a packet queue.
* * The ->tot_len fields of all pbufs of the head chain are adjusted.
* The ->next field of the last pbuf of the head chain is adjusted.
* The ->ref field of the first pbuf of the tail chain is adjusted.
* */
void pbuf_chain(struct pbuf *h, struct pbuf *t)
{
pbuf_cat(h, t);
/* t is now referenced by h */
pbuf_ref(t);
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_chain: %p references %p\n", (void *)h, (void *)t));
}
/**
* Dechains the first pbuf from its succeeding pbufs in the chain.
*
* Makes p->tot_len field equal to p->len.
* @param p pbuf to dechain
* @return remainder of the pbuf chain, or NULL if it was de-allocated.
* @note May not be called on a packet queue.
*/
struct pbuf *pbuf_dechain(struct pbuf *p)
{
struct pbuf *q;
u8_t tail_gone = 1;
/* tail */
q = p->next;
/* pbuf has successor in chain? */
if (q != NULL)
{
/* assert tot_len invariant: (p->tot_len == p->len + (p->next? p->next->tot_len: 0) */
LWIP_ASSERT("p->tot_len == p->len + q->tot_len", q->tot_len == p->tot_len - p->len);
/* enforce invariant if assertion is disabled */
q->tot_len = (u16_t)(p->tot_len - p->len);
/* decouple pbuf from remainder */
p->next = NULL;
/* total length of pbuf p is its own length only */
p->tot_len = p->len;
/* q is no longer referenced by p, free it */
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_dechain: unreferencing %p\n", (void *)q));
tail_gone = pbuf_free(q);
if (tail_gone > 0)
{
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_dechain: deallocated %p (as it is no longer referenced)\n", (void *)q));
}
/* return remaining tail or NULL if deallocated */
}
/* assert tot_len invariant: (p->tot_len == p->len + (p->next? p->next->tot_len: 0) */
LWIP_ASSERT("p->tot_len == p->len", p->tot_len == p->len);
return ((tail_gone > 0) ? NULL : q);
}
以上部分内容引自《[野火]LwIP应用开发实战指南》。