【lwip】04-网络数据包流向
前言
了解了lwip的内存管理后,接下来就是网络数据包的了解。
注意与内部lwip消息的区别:网络数据包用于网络数据的流转,而内部lwip消息用于内部协作。
本篇开始,结合源码独立分析lwip,逐步拆解lwip开源库。
参考:
- 本文:https://www.cnblogs.com/lizhuming/p/16630602.html
- 李柱明笔记汇总:https://www.cnblogs.com/lizhuming/p/16557045.html
4.1 TCPIP分层与lwip数据共享
TCPIP分层思想:
- 在标准的TCP/IP协议栈中,各层之间都是一个独立的模块,每层只需要负责本层的工作即可,不会越界到其他层次去读写数据,数据传输需要层层拷贝。
lwip数据共享:
- lwip的主要目标是嵌入式设备,作为轻量级的TCP/IP协议栈,模糊掉标准的TCP/IP分层思想,可以提高数据处理效率和内存空间利用率。
- 即是数据在lwip的tcpip协议栈各层中是公用的,各层只需要处理各层负责的字段即可。
4.2 协议栈线程模型
多线程模型:
- 协议栈各个层次都独立成为一个线程。
- 这种线程模型严格分层,代码容易维护,功能组件容易增删,但是层次数据需要通过线程通信进行交互,可能存在层层拷贝,不适用于嵌入式设备。
协议栈与操作系统融合:
- 协议栈成为操作系统的一部分。
- 线程与协议栈内核之间都是通过操作系统提供的函数来实现,协议栈各层之间与线程就没有很严格的分层结构,各层之间能交叉存取,从而提高效率。
协议栈内核与操作系统相互隔离:(lwip在用)
- 协议栈只是操作系统的一条独立的线程。
- 用户程序能驻留在协议栈内部(回调方式),协议栈通过回调函数实现用户与协议栈之间的数据交互。(RAW API接口编程)
- 也可以让用户程序单独实现一个线程,通过信号量、消息等IPC通信机制与协议栈线程进行数据交互。(NETCONN API和Socket API 编程)
4.3 pbuf 结构体
注意:
- pbuf链表中第一个pbuf是有
layer
字段的,用于存放协议头部,而在它后面的pbuf则是没有该字段。 - pbuf链表中,其中的节点pbuf可以由不同pbuf类型来组成的。
源代码:
/* 数据包结构体 pbuf */
struct pbuf {
/* 单向非循环链表,指向接着的下一个pbuf */
struct pbuf *next;
/* 指向buffer中的实际数据空间地址 */
void *payload;
/* pbuf链表中当前pbuf及其链表后的pbuf数据长度总和 */
u16_t tot_len;
/* 当前pbuf数据长度 */
u16_t len;
/* pbuf的类型,LwIP 中有 4 种 pbuf 的类型 */
u8_t type_internal;
/* 表示当前pbuf的属性 */
u8_t flags;
/* pbuf 被引用的次数 */
LWIP_PBUF_REF_T ref;
/* 记录传入的数据包中输入 netif 的索引 */
u8_t if_idx;
/* 用户自定义 */
LWIP_PBUF_CUSTOM_DATA
};
4.3.1 pbuf的标志位flags
有以下属性:
/** 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
/** 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
4.4 pbuf的类型
pbuf的类型,主要是以pbuf的空间结构和空间来源来区别的。
这些标志位可以在lwip内核内部其它地方判断当前pbuf的内存属性。
/* Base flags for pbuf_type definitions: */
/* 这个标志位表示pbuf数据结构和数据区的地址连续。 */
#define PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS 0x80
/* 表示存储在该pbuf中的数据可以更改。 */
#define PBUF_TYPE_FLAG_DATA_VOLATILE 0x40
/* 4 bit预留给16个分配源(例如堆、pool1、pool2等)
* 在内部,使用: 0=heap, 1=MEMP_PBUF, 2=MEMP_PBUF_POOL -> 13 自由类型 */
#define PBUF_TYPE_ALLOC_SRC_MASK 0x0F
/* 表示此pbuf用于RX(如果没有设置,则表示用于TX)
* 这个标志可以用来保留一些备用的RX缓冲区,例如接收TCP ack以解除连接阻塞。 */
#define PBUF_ALLOC_FLAG_RX 0x0100
/* 表示应用程序需要pbuf有效负载处于一个整体中 */
#define PBUF_ALLOC_FLAG_DATA_CONTIGUOUS 0x0200
#define PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP 0x00
#define PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF 0x01
#define PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL 0x02
/* 应用程序的第一种pbuf分配类型 */
#define PBUF_TYPE_ALLOC_SRC_MASK_APP_MIN 0x03
/* 应用程序的最后一种pbuf分配类型 */
#define PBUF_TYPE_ALLOC_SRC_MASK_APP_MAX PBUF_TYPE_ALLOC_SRC_MASK
/**
* @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;
4.4.1 PBUF_RAM类型
PBUF_RAM类型的pbuf:
- PBUF_RAM类型的pbuf空间是由内存堆分配;
- pbuf的数据管理区和数据区地址空间是连续的;
- 多用于发送数据。
4.4.2 PBUF_ROM类型
PBUF_ROM类型的pbuf:
- PBUF_ROM类型的pbuf结构体空间是由内存池分配,即是MEMP_PBUF类型的POOL;(不包含数据区)
- pbuf的数据管理区和数据区地址空间是不连续的,PBUF_ROM的数据区存在ROM中,一般是静态数据。
4.4.3 PBUF_REF类型
PBUF_REF类型的pbuf和PBUF_ROM类型的pbuf结构一样,只是数据区的存储地址一个在RAM区一个在ROM区。
PBUF_REF类型的pbuf:
- PBUF_REF类型的pbuf结构体空间是由内存池分配,即是MEMP_PBUF类型的POOL;(不包含数据区)
- pbuf的数据管理区和数据区地址空间是不连续的,PBUF_REF的数据区存在RAM中。
4.4.4 PBUF_POOL类型
PBUF_POOL类型的pbuf:
- PBUF_POOL类型的pbuf空间是由内存池分配;
- pbuf的数据管理区和数据区地址空间是连续的;
- 该pbuf的实际空间大小是固定的;
- 多用于接收数据,因为空间申请快。
- 不要用于TX,因为如果当内存池为空了,TCP在排队等待,就会接收不了TCP ACK。
系统会初始化两个与pbuf相关的内存池:
- MEMP_PBUF:用于存放pbuf数据结构的内存池。主要用于pbuf数据结构和数据区地址不连续的PBUF_ROM、PBUF_REF类型的pbuf。
- MEMP_ PBUF_POOL:用于存放pbuf数据结构和数据区地址连续的内存池。主要供给PBUF_POOL类型的pbuf。
PBUF_POOL类型的pbuf链表如下图所示:
- 由于PBUF_POOL类型的pbuf内存是由内存池分配的,所以pbuf链表中最后一个pbuf存在空间浪费的可能。
MEMP_PBUF_POOL类型的pbuf长度:PBUF_POOL_BUFSIZE_ALIGNED
/* Since the pool is created in memp, PBUF_POOL_BUFSIZE will be automatically
aligned there. Therefore, PBUF_POOL_BUFSIZE_ALIGNED can be used here. */
#define PBUF_POOL_BUFSIZE_ALIGNED LWIP_MEM_ALIGN_SIZE(PBUF_POOL_BUFSIZE)
PBUF_POOL_BUFSIZE_ALIGNED
长度是整个TCPIP协议栈从链路层到传输层的最大报文长度的size,是包含TCP_MSS, TRANSPORT header, IP header, and link header,还有一个原始层首部(默认为0),且需要字节。
/**
* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. The default is
* designed to accommodate single full size TCP frame in one pbuf, including
* TCP_MSS, IP header, and link header.
*/
#if !defined PBUF_POOL_BUFSIZE || defined __DOXYGEN__
#define PBUF_POOL_BUFSIZE LWIP_MEM_ALIGN_SIZE(TCP_MSS+PBUF_IP_HLEN+PBUF_TRANSPORT_HLEN+PBUF_LINK_ENCAPSULATION_HLEN+PBUF_LINK_HLEN)
#endif
TCP_MSS
:除去头部之后,一个网络包所能容纳的 TCP 数据的最大长度。参考下图。
/**
* TCP_MSS: TCP Maximum segment size. (default is 536, a conservative default,
* you might want to increase this.)
* For the receive side, this MSS is advertised to the remote side
* when opening a connection. For the transmit size, this MSS sets
* an upper limit on the MSS advertised by the remote host.
*/
#if !defined TCP_MSS || defined __DOXYGEN__
#define TCP_MSS 536
#endif
PBUF_IP_HLEN
:IP层首部长度。
#if LWIP_IPV6
#define PBUF_IP_HLEN 40
#else
#define PBUF_IP_HLEN 20
#endif
PBUF_TRANSPORT_HLEN
:传输层首部长度。
#define PBUF_TRANSPORT_HLEN 20
PBUF_LINK_ENCAPSULATION_HLEN
:原始层首部长度。默认为0。
/**
* PBUF_LINK_ENCAPSULATION_HLEN: the number of bytes that should be allocated
* for an additional encapsulation header before ethernet headers (e.g. 802.11)
*/
#if !defined PBUF_LINK_ENCAPSULATION_HLEN || defined __DOXYGEN__
#define PBUF_LINK_ENCAPSULATION_HLEN 0
#endif
PBUF_LINK_HLEN
:链路层首部长度。
/** ETH_PAD_SIZE: number of bytes added before the ethernet header to ensure
* alignment of payload after that header. Since the header is 14 bytes long,
* without this padding e.g. addresses in the IP header will not be aligned
* on a 32-bit boundary, so setting this to 2 can speed up 32-bit-platforms.
*/
#if !defined ETH_PAD_SIZE || defined __DOXYGEN__
#define ETH_PAD_SIZE 0
#endif
/**
* @defgroup lwip_opts_pbuf PBUF
* @ingroup lwip_opts
* @{
*/
/**
* PBUF_LINK_HLEN: the number of bytes that should be allocated for a
* link level header. The default is 14, the standard value for
* Ethernet.
*/
#if !defined PBUF_LINK_HLEN || defined __DOXYGEN__
#if defined LWIP_HOOK_VLAN_SET && !defined __DOXYGEN__
#define PBUF_LINK_HLEN (18 + ETH_PAD_SIZE)
#else /* LWIP_HOOK_VLAN_SET */
#define PBUF_LINK_HLEN (14 + ETH_PAD_SIZE)
#endif /* LWIP_HOOK_VLAN_SET */
#endif
4.5 pbuf_alloc()
pbuf_alloc()
是数据包申请函数:(详细直接分析源码)
pbuf_layer layer
:协议层枚举,直接就是该层首部大小了。不同的协议层,layer大小不一样。u16_t length
:pbuf有效载荷的大小。和layer
参数共同决定pbuf空间大小。pbuf_type type
:pbuf的类型,决定了pbuf空间怎样分配和空间来源。
4.5.1 各层首部大小
层级越高,首部预留的空间要越大,因为往下层传的时候需要下层需要填充该层的首部。
相关宏:
- PBUF_LINK_ENCAPSULATION_HLEN:原始层首部长度。默认为0,在TCPIP协议栈中不预留空间。
- PBUF_LINK_HLEN:链路层首部长度。默认是以太网的标准值,14。可以按系统bit长度来偏移,让IP层从系统对齐地址起。
- PBUF_IP_HLEN:IP层首部长度。默认ipv4是20,如果使能了ipv6,就是40。
- PBUF_TRANSPORT_HLEN:传输层首部长度。默认20。
/**
* @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;
4.5.2 各个pbuf类型的空间分配实现(简要)
PBUF_REF
和PBUF_ROM
类型,只分配pbuf数据结构空间,从MEMP_PBUF
内存池中获得。
PBUF_POOL
类型,空间从MEMP_PBUF_POOL
内存池中获得,如果一个pbuf节点不够,就会以链表的方式获取。
PBUF_RAM
类型,空间从内存堆中获得,一次性获取。
4.5.3 PBUF_POOL类型malloc实现
由于MEMP_PBUF_POOL
内存池中每个节点的空间大小都是固定的,所以可能会出现一个节点不够用的情况,这样就需要pbuf链表的形式管理申请到的空间。
调用q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);
申请一个MEMP_PBUF_POOL
类型的pbuf节点空间。
如果内存池为空,可以通过调用PBUF_POOL_IS_EMPTY();
来释放ooseq链表中释放无序报文的MEMP_PBUF_POOL内存池空间,但是本次申请也是需要退出的。
退出本次空间申请前,需要释放本次循环申请MEMP_PBUF_POOL
类型的pbuf节点空间。如调用pbuf_free(p);
。
当前pbuf节点空间申请成功后:
获取当前pbuf节点实际需要的、有效的数据空间长度。
如果是首个pbuf节点,还需要根据layer
参数预留首部空间。
初始化当前pbuf节点。
更新变量值。
4.5.4 PBUF_RAM类型malloc实现
先计算申请堆空间长度:pbuf数据结构空间+首部空间+用户实际申请的空间。注意字节对齐。
mem_size_t payload_len = (mem_size_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);
通过p = (struct pbuf *)mem_malloc(alloc_len);
从内存堆中申请。
void *mem_malloc(mem_size_t size_in);
这个函数有三种实现方式:
- 基于自定义。
- 基于内存池。
- 基于内存堆。
申请成功后初始化当前pbuf节点。
4.5.5 pbuf_alloc()源码
/**
* @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:
/* 对于这两个pbuf类型,只分配pbuf数据结构空间 */
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;
/* 从MEMP_PBUF_POOL内存池中申请 */
q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);
if (q == NULL) {
/* 如果MEMP_PBUF_POOL内存池为空,可能会从ooseq链表中释放无序报文的MEMP_PBUF_POOL内存池空间 */
PBUF_POOL_IS_EMPTY();
/* 释放这个pbuf链表的空间 */
if (p) {
pbuf_free(p);
}
/* 返回NULL,申请失败 */
return NULL;
}
/* 获取当前pbuf节点实际需要的、有效的数据空间长度。 */
qlen = LWIP_MIN(rem_len, (u16_t)(PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)));
/* 初始化当前pbuf节点 */
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) {
/* 如果是首个pbuf节点,则当前pbuf节点指针就是当前pbuf链表头 */
p = q;
} else {
/* 当前pbuf节点插入pbuf链表 */
last->next = q;
}
/* 更新变量 */
last = q;
rem_len = (u16_t)(rem_len - qlen);
/* 只有首个pbuf节点才需要首部空间,后面的pbuf节点不需要 */
offset = 0;
} while (rem_len > 0);
break;
}
case PBUF_RAM: {
/* 当前pbuf需要的数据区空间大小,包括首部预留空间和用户实际申请载体空间大小 */
mem_size_t payload_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(offset) + LWIP_MEM_ALIGN_SIZE(length));
/* 实际申请空间大小是当前pbuf的数据结构管理大小和数据区大小 */
mem_size_t alloc_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF) + payload_len);
/* 检查申请的长度是否溢出系统位长 */
if ((payload_len < LWIP_MEM_ALIGN_SIZE(length)) ||
(alloc_len < LWIP_MEM_ALIGN_SIZE(length))) {
return NULL;
}
/* 从内存堆中申请 */
p = (struct pbuf *)mem_malloc(alloc_len);
if (p == NULL) {
return NULL;
}
/* 申请成功后,初始化当前pbuf节点 */
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;
}
4.6 pbuf_free()
4.6.1 相关参数
函数原型:u8_t pbuf_free(struct pbuf *p)
struct pbuf *p
:需要解除应用的pbuf或pbuf链表头。- 返回:返回被释放空间的pbuf个数。
4.6.2 释放pbuf空间的条件
pbuf_free()
用于释放pbuf空间。
pbuf中ref字段就是记录pbuf数据包被引用的次数;
该字段在pbuf申请初始化时,被置为1,表示还没有被引用。
后续被引用一次,该字段+1,被pbuf_free()一次,该字段-1。
当ref字段值为0时,该pbuf空间才可以备释放。
4.6.3 释放pbuf链表的逻辑
pbuf_free()
释放pbuf链表的逻辑:
- 从pbuf链表首节点开始ref减1,为0则直接释放当前节点;
- 直到pbuf链表全部释放完毕或遇到ref减1后不为0的pbuf节点为止。后续的pbuf节点的ref字段也不会减1。
因为lwip检索pbuf链表,ref减一后不为0,则认为当前pbuf节点为下一个数据包的首个pbuf节点。
所以,pbuf_free()
遇到释放pbuf链表时,只会处理第一个数据包占用的pbuf节点。
pbuf链表中的同一个数据包的分界线实现,是通过在每个数据包的首个pbuf节点多加一个ref引用标志。
举个几个栗子:
- [一个pbuf链表的ref] --> [经过
pbuf_free()
释放后的pbuf链表的ref] - [1--2--3--3] --> [..--1--3--3]
- [2--1--2] --> [1--1--2]
- [1--1--2] --> [..--..--1]
4.6.4 pbuf_free()使用说明
-
不能调用
pbuf_free()
释放包队列(packet queue)空间。 -
如果需要释放pbuf链表,则必须传入pbuf链表头指针,切不能传入中间pbuf节点的指针,避免内存管理异常。
-
pbuf的引用计数器ref等于指向pbuf(或指向pbuf)的指针的数量。
- 创建pbuf时,ref为1,就是只有一个指针指向当前pbuf。
4.6.5 pbuf_free()实现说明
每个pbuf的ref字段操作都需要实现线程安全、原子性操作。
在多线程的系统下,可以在lwipports.h
文件中实现这些宏定义:
- 这些宏是在lwip内部必要时,实现线程安全、原子性操作。
- 下面只是例子,进入临界操作。
- 当然其它能实现线程安全和原子性操作的都可以,如锁。(这个纯属个人推测,并未思考过多问题)
#define SYS_ARCH_DECL_PROTECT(x) uint32_t x /* 定义一个变量 */
#define SYS_ARCH_PROTECT(x) x = osiEnterCritical() /* 进入临界 */
#define SYS_ARCH_UNPROTECT(x) osiExitCritical(x) /* 退出临界 */
4.6.7 pbuf_free()源码分析
/**
* @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);/* 断言 */
/* 如果屏蔽了lwip的断言功能,则继续,打印log并返回0 */
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; /* 用于记录释放了多少个pbuf节点的空间 */
/* 从pbuf链表头开始释放引用ref */
while (p != NULL) {
LWIP_PBUF_REF_T ref;
SYS_ARCH_DECL_PROTECT(old_level); /* 宏定义接口:定义old_level变量 */
/* 对ref变量实现线程安全操作,维护其原子性。如在多线程系统中,可以进入临界处理。 */
SYS_ARCH_PROTECT(old_level); /* 宏定义接口:如进入临界处理 */
/* 所有pbuf中至少被引用一次,如果小于1,说明传入的地址异常或者当前pbuf被踩,这种情况下可进入断言。 */
LWIP_ASSERT("pbuf_free: p->ref > 0", p->ref > 0);
/* 当前pbuf的ref减1 */
ref = --(p->ref);
SYS_ARCH_UNPROTECT(old_level); /* 宏定义接口:如退出临界处理 */
/* 如果为0,说明当前pbuf没有被其它地方引用,可释放空间 */
if (ref == 0) {
/* 在释放该pbuf空间前,先记录这个pbuf节点的下一个pbuf节点 */
q = p->next;
LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: deallocating %p\n", (void *)p));
alloc_src = pbuf_get_allocsrc(p); /* 获取当前pbuf的内存来源。通过pbuf的类型type_internal字段来判断当前pbuf的内存来源 */
#if LWIP_SUPPORT_CUSTOM_PBUF
/* 检查当前pbuf是否是用户层维护的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); /* 用户的free回调必须存在 */
pc->custom_free_function(p); /* 调用用户侧的回调来实现在lwip内核让用户层释放当前pbuf空间 */
} else
#endif /* LWIP_SUPPORT_CUSTOM_PBUF */
{
/* 如果当前pbuf的空间来源于MEMP_PBUF_POOL内存池,则调用memp_free()将其释放 */
if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL) {
memp_free(MEMP_PBUF_POOL, p);
/* 如果当前pbuf的空间来源于MEMP_PBUF内存池,则调用memp_free()将其释放 */
} else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF) {
memp_free(MEMP_PBUF, p);
/* 如果当前pbuf的空间来源于内存堆,则调用mem_free()将其释放 */
} 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++; /* 更新释放了多少个pbuf空间 */
/* 进入下一个pbuf */
p = q;
} else {/* 遇到ref不为0的,说明从这个pbuf依然被其它地方引用,pbuf链表上剩下的pbuf也是这样。可以理解为当前ref不为0的pbuf为新的一个数据包的首个pbuf,后续的pbuf不需要减ref引用。
因为pbuf_free的释放原则是释放一个数据包的pbuf。 */
LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: %p has ref %"U16_F", ending here.\n", (void *)p, (u16_t)ref));
/* 后续pbuf不需要被处理,p置为NULL标记退出循环处理。 */
p = NULL;
}
}
PERF_STOP("pbuf_free"); /* 默认为空定义,用户可添加自己的操作 */
/* 返回已分配的pbuf的数量 */
return count;
}
4.7 其它pbuf处理函数
4.7.1 pbuf_realloc()
用于裁剪pbuf(链表)尾部空间。
只能裁剪,不能扩张。
对于PBUF_RAM
类型的pbuf,是可能会释放尾部空间,而其它三种pbuf类型,不会释放空间,只会修改pbuf中的长度字段值。
而对于pbuf链表,在截断分界线后的pbuf,都会调用pbuf_free()
进行释放。当ref减1后为0,也会存在真正释放空间的可能。
对于pbuf链表,即是截断分界线后的pbuf没有被真正释放空间,这个pbuf链表也会截断,拆分链表。
下面用到的void *mem_trim(void *rmem, mem_size_t newsize)
函数属于内存管理的内存堆范畴,这里不对其进行源码剖析,可简单说明:
-
用于裁剪内存堆尾部空间,不支持内存扩充。
-
函数简要实现内容:
- 计算被裁剪的空间能否构成下一个内存堆节点。
- 符合则初始化新的内存堆节点,插入内存堆链表。
- 返回NULL或者传入的rmem地址。
/**
* @ingroup pbuf
* Shrink a pbuf chain to a desired length.
*
* @param p pbuf to shrink.
* @param new_len desired new length of pbuf chain
*
* Depending on the desired length, the first few pbufs in a chain might
* be skipped and left unchanged. The new last pbuf in the chain will be
* resized, and any remaining pbufs will be freed.
*
* @note If the pbuf is ROM/REF, only the ->tot_len and ->len fields are adjusted.
* @note May not be called on a packet queue.
*
* @note Despite its name, pbuf_realloc cannot grow the size of a pbuf (chain).
*/
void
pbuf_realloc(struct pbuf *p, u16_t new_len)
{
struct pbuf *q;
u16_t rem_len; /* remaining length */
u16_t shrink;
LWIP_ASSERT("pbuf_realloc: p != NULL", p != NULL);
/* 不支持空间扩充 */
if (new_len >= p->tot_len) {
return;
}
/* 计算需要裁剪的空间size */
shrink = (u16_t)(p->tot_len - new_len);
/* 先遍历应该留在链中的所有pbufs */
rem_len = new_len;
q = p;
/* 找出截取分界线所在的pbuf */
while (rem_len > q->len) { /* 遍历保留下来的pbuf */
/* 通过pbuf长度减少剩余长度 */
rem_len = (u16_t)(rem_len - q->len);
/* 减少pbuf保存的总长度 */
q->tot_len = (u16_t)(q->tot_len - shrink);
/* 进入下一个pbuf */
q = q->next;
LWIP_ASSERT("pbuf_realloc: q != NULL", q != NULL);
}
/* 当前q就是截取分界线所在的pbuf */
/* rem_len 也是当前q的期望长度 */
/* 只有PBUF_RAM类型才会真正释放多余空间 */
/* 其它pbuf类型只是修改pbuf长度字段值 */
/* 只需要通过判断PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP标志位即可判断当前pbuf是否是PBUF_RAM类型 */
if (pbuf_match_allocsrc(q, PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP) && (rem_len != q->len)
#if LWIP_SUPPORT_CUSTOM_PBUF
&& ((q->flags & PBUF_FLAG_IS_CUSTOM) == 0)
#endif /* LWIP_SUPPORT_CUSTOM_PBUF */
) {
/* 裁剪空间。在内存堆的角度看新期望的空间size需要加上pbuf的数据结构size */
q = (struct pbuf *)mem_trim(q, (mem_size_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;
if (q->next != NULL) {
/* 释放链中剩余的pbuf */
pbuf_free(q->next);
}
/* 截断当前pbuf链表 */
q->next = NULL;
}
4.7.2 pbuf_header()
调整pbuf中的payload指针以隐藏或显示数据区前的首部字段。
payload指针偏移后,pbuf中的len和tot_len字段也会刷新。
不支持PBUF_ROM和PBUF_REF类型的pbuf修改payload指针偏移。
/**
* 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);
}
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);
}
}
隐藏部分头部字段,如下层转交pbuf到上层时的处理。调用pbuf_remove_header()
实现。
/**
* 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;
/* 不能偏移到超出pbuf数据区的末端 */
LWIP_ERROR("increment_magnitude <= p->len", (increment_magnitude <= p->len), return 1;);
/* 备份当前pbuf的payload指针 */
payload = p->payload;
LWIP_UNUSED_ARG(payload); /* only used in LWIP_DEBUGF below */
/* 更新pbuf的payload指针 */
p->payload = (u8_t *)p->payload + header_size_decrement;
/* 更新pbuf长度字段 */
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;
}
暴露部分头部字段,如上层转交pbuf到下层时的处理。调用pbuf_add_header_impl()
实现。
/**
* 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;
/* 防止溢出 */
if ((u16_t)(increment_magnitude + p->tot_len) < increment_magnitude) {
return 1;
}
type_internal = p->type_internal;
if (type_internal & PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS) { /* 当前pbuf类型数据管理和数据区地址连续 */
/* 计算新的payload指针 */
payload = (u8_t *)p->payload - header_size_increment;
/* 越界检查 */
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)));
return 1;
}
} else { /* pbuf数据管理和数据区地址不连续的,其实就是 PBUF_REF/PBUF_ROM 类型 */
if (force) { /* 允许对 PBUF_REF/PBUF_ROM 类型操作 */
payload = (u8_t *)p->payload - header_size_increment;
} else {
/* 不允许对 PBUF_REF/PBUF_ROM 类型操作 */
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));
/* 修改pbuf字段 */
p->payload = payload;
p->len = (u16_t)(p->len + increment_magnitude);
p->tot_len = (u16_t)(p->tot_len + increment_magnitude);
return 0;
}
4.7.3 pbuf_take()
pbuf_take()函数用于向pbuf的数据区域拷贝数据。
虽然该函数内部实现只是限制了传入的数据长度不能大于pbuf的tot_len,
但是该函数在使用来说,建议只用于复制buf->tot_len的等价数据,即是数据传入的数据刚好填满pbuf。
/**
* @ingroup pbuf
* Copy application supplied data into a pbuf.
* This function can only be used to copy the equivalent of buf->tot_len data.
*
* @param buf pbuf to fill with data
* @param dataptr application supplied data buffer
* @param len length of the application supplied data buffer
*
* @return ERR_OK if successful, ERR_MEM if the pbuf is not big enough
*/
err_t
pbuf_take(struct pbuf *buf, const void *dataptr, u16_t len)
{
struct pbuf *p;
size_t buf_copy_len;
size_t total_copy_len = len;
size_t copied_total = 0;
LWIP_ERROR("pbuf_take: invalid buf", (buf != NULL), return ERR_ARG;);
LWIP_ERROR("pbuf_take: invalid dataptr", (dataptr != NULL), return ERR_ARG;);
LWIP_ERROR("pbuf_take: buf not large enough", (buf->tot_len >= len), return ERR_MEM;);
if ((buf == NULL) || (dataptr == NULL) || (buf->tot_len < len)) {
return ERR_ARG;
}
/* Note some systems use byte copy if dataptr or one of the pbuf payload pointers are unaligned. */
for (p = buf; total_copy_len != 0; p = p->next) {
LWIP_ASSERT("pbuf_take: invalid pbuf", p != NULL);
buf_copy_len = total_copy_len;
if (buf_copy_len > p->len) {
/* this pbuf cannot hold all remaining data */
buf_copy_len = p->len;
}
/* copy the necessary parts of the buffer */
MEMCPY(p->payload, &((const char *)dataptr)[copied_total], buf_copy_len);
total_copy_len -= buf_copy_len;
copied_total += buf_copy_len;
}
LWIP_ASSERT("did not copy all data", total_copy_len == 0 && copied_total == len);
return ERR_OK;
}
4.7.4 pbuf_copy()
pbuf_copy()函数用于将一个任何类型的pbuf中的数据拷贝到一个PBUF_RAM类型的pbuf中。
用于代表lwIP堆栈对包进行排队,如ARP队列。
/**
* @ingroup pbuf
* Create PBUF_RAM copies of pbufs.
*
* Used to queue packets on behalf of the lwIP stack, such as
* ARP based queueing.
*
* @note You MUST explicitly use p = pbuf_take(p);
*
* @note Only one packet is copied, no packet queue!
*
* @param p_to pbuf destination of the copy
* @param p_from pbuf source of the copy
*
* @return ERR_OK if pbuf was copied
* ERR_ARG if one of the pbufs is NULL or p_to is not big
* enough to hold p_from
*/
err_t
pbuf_copy(struct pbuf *p_to, const struct pbuf *p_from)
{
LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_copy(%p, %p)\n",
(const void *)p_to, (const void *)p_from));
LWIP_ERROR("pbuf_copy: invalid source", p_from != NULL, return ERR_ARG;);
return pbuf_copy_partial_pbuf(p_to, p_from, p_from->tot_len, 0);
}
4.7.5 pbuf_cat()
pbuf_cat() 用于拼接两个pbuf链表,且后面接入的pbuf链表不与前面的pbuf链表有分割标志,拼接后,后面的pbuf链表不能被其它地方引用了。
如果还想保留后面的pbuf链表能被其它地方引用的权限,就使用pbuf_chain()
函数来拼接。
需要注意的是,该函数的实现没有对tot_len字段溢出监测处理,所以使用时需要预判,两个链表的tot_len拼接后不要有溢出的可能。
/**
* @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.
*/
}
4.7.6 pbuf_ref()
pbuf_ref()函数用于将pbuf中的值加1。
4.7.7 pbuf_chain()
pbuf_chain()函数用于连接两个pbuf(链表)为一个pbuf链表。
调用该函数后,后接入的链表不能使用pbuf_free()对其进行释放了。
而且后面接入的pbuf链表的首个pbuf节点的ref引用字段+1,作为两个数据包的分割点。
/**
* @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));
}
4.7.8 更多
pbuf_dechain()
用于把pbuf链表中的首个pbuf节点进行拖链,且,返回新的pbuf链表。
参考pbuf.c
和pbuf.h
4.8 网卡中使用的pbuf
网卡中的回调函数需要根据网卡设备类型来实现。
low_level_output()
和low_level_input()
函数是网卡的南向直接操作函数,是对网卡设备的写、读处理。
相当于网卡设备的驱动范畴的函数。
4.8.1 low_level_output()
low_level_output()
函数只是单纯的往网卡设备发送数据。
一般把这个函数插入到netif->linkoutput()
中,供其网卡调用。给是ARP用来往链路层发送数据。
该函数类型如下:
- 传入的数据是pbuf形式,该函数的实现需要从pbuf从获取数据体出来,发送出去。
/** Function prototype for netif->linkoutput functions. Only used for ethernet
* netifs. This function is called by ARP when a packet shall be sent.
*
* @param netif The netif which shall send a packet
* @param p The packet to send (raw ethernet packet)
*/
typedef err_t (*netif_linkoutput_fn)(struct netif *netif, struct pbuf *p);
4.8.2 low_level_input()
low_level_input()
从网卡设备中接收数据。
在lwip中,该函数需要实现从网卡设备中获取数据,并把数据组装成为pbuf形式,是MEMP_PBUF_POOL
类型的pbuf。
该函数不会直接插入到netif的数据结构中,因为网卡没有直接调用该函数的主动性,是靠外部收到数据后触发执行low_level_input()
函数获取数据;
然后再调用netif->input()
把数据按要求上交给TCPIP协议栈。