LwIP系列--数据包处理和PBUF结构详解

这篇具有很好参考价值的文章主要介绍了LwIP系列--数据包处理和PBUF结构详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、目的

在之前的博文中我们介绍了LwIP中的内存堆和内存池实现细节以及它们之间的优缺点对比。

本篇我们将介绍LwIP中另一个比较重要的知识点,即数据包管理和PBUF结构;个人认为PBUF的设计是LwIP整个源码的亮点之一(充分考虑了数据包处理的高效需求)。

关于内存堆和内存池的知识点请阅读《LwIP系列--内存管理(堆内存)详解》、《LwIP系列--内存管理(堆内存)详解》这两篇博文进行学习理解,本文不再赘述。

LwIP中的数据包是指一个完整的消息,例如在传输层就是一个TCP分片,在网络层就是一个IP分片,在数据链路层就是一个以太网包,因为TCP/IP是一个分层的协议族,每一层都有自己的封装头部。

关于TCP/IP协议栈的相关知识,推荐大家阅读谢希仁老师的《计算机网络》这本书。

PBUF结构是数据包的基础,PBUF结构中的数据部分可能是从内存堆或者内存池中分配,也可以引用(指向)其他现有的数据包;PBUF结构本身与数据部分占用的内存可以是连续内存,也可以分散的。

二、介绍

在正式介绍PBUF结构之前我们需要回顾一下LwIP中跟PBUF相关的内存池。

相关宏
#define SIZEOF_STRUCT_PBUF        LWIP_MEM_ALIGN_SIZE(sizeof(struct pbuf))
/* 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)

SIZEOF_STRUCT_PBUF是结构体struct pbuf对齐后的大小

在源文件memp.c中有这样的宏定义

#define LWIP_MEMPOOL(name,num,size,desc) LWIP_MEMPOOL_DECLARE(name,num,size,desc)

其中LWIP_MEMPOOL_DECLARE在头文件memp.h中定义,用于定义内存池相关的数据结构

/**
 * @ingroup mempool
 * Declare a private memory pool
 * Private mempools example:
 * .h: only when pool is used in multiple .c files: LWIP_MEMPOOL_PROTOTYPE(my_private_pool);
 * .c:
 *   - in global variables section: LWIP_MEMPOOL_DECLARE(my_private_pool, 10, sizeof(foo), "Some description")
 *   - call ONCE before using pool (e.g. in some init() function): LWIP_MEMPOOL_INIT(my_private_pool);
 *   - allocate: void* my_new_mem = LWIP_MEMPOOL_ALLOC(my_private_pool);
 *   - free: LWIP_MEMPOOL_FREE(my_private_pool, my_new_mem);
 *
 * To relocate a pool, declare it as extern in cc.h. Example for GCC:
 *   extern u8_t \_\_attribute\_\_((section(".onchip_mem"))) memp_memory_my_private_pool_base[];
 */
#define LWIP_MEMPOOL_DECLARE(name,num,size,desc) \
  LWIP_DECLARE_MEMORY_ALIGNED(memp_memory_ ## name ## _base, ((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size)))); \
    \
  LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(memp_stats_ ## name) \
    \
  static struct memp *memp_tab_ ## name; \
    \
  const struct memp_desc memp_ ## name = { \
    DECLARE_LWIP_MEMPOOL_DESC(desc) \
    LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(memp_stats_ ## name) \
    LWIP_MEM_ALIGN_SIZE(size), \
    (num), \
    memp_memory_ ## name ## _base, \
    &memp_tab_ ## name \
  };

在头文件mem_std.h中有这样的宏定义

#ifndef LWIP_PBUF_MEMPOOL
/* This treats "pbuf pools" just like any other pool.
 * Allocates buffers for a pbuf struct AND a payload size */
#define LWIP_PBUF_MEMPOOL(name, num, payload, desc) LWIP_MEMPOOL(name, num, (LWIP_MEM_ALIGN_SIZE(sizeof(struct pbuf)) + LWIP_MEM_ALIGN_SIZE(payload)), desc)
#endif /* LWIP_PBUF_MEMPOOL */

/*
 * A list of pools of pbuf's used by LWIP.
 *
 * LWIP_PBUF_MEMPOOL(pool_name, number_elements, pbuf_payload_size, pool_description)
 *     creates a pool name MEMP_pool_name. description is used in stats.c
 *     This allocates enough space for the pbuf struct and a payload.
 *     (Example: pbuf_payload_size=0 allocates only size for the struct)
 */
LWIP_MEMPOOL(PBUF,           MEMP_NUM_PBUF,            sizeof(struct pbuf),           "PBUF_REF/ROM")
LWIP_PBUF_MEMPOOL(PBUF_POOL, PBUF_POOL_SIZE,           PBUF_POOL_BUFSIZE,             "PBUF_POOL")

上面的代码片段声明了两类内存池MEMP_PBUF/MEMP_PBUF_POOL

这两个内存池的区别为:

MEMP_PBUF内存池每个内存块的大小为sizeof(struct pbuf),即pbuf结构本身的大小(对齐的);

MEMP_PBUF_POOL内存池每个内存块的大小为LWIP_MEM_ALIGN_SIZE(sizeof(struct pbuf)) + LWIP_MEM_ALIGN_SIZE(PBUF_POOL_BUFSIZE),即对齐的pbuf结构的大小加上对齐的一个最大TCP数据包的大小以及各层的封装头部的总和,通过之前的学习大家应该都知道通过内存池分配的一个特点是分配快速,时间相对确定,所以这特别有利于快速接收数据包,有数据包要接收时就立刻从MEMP_PBUF_POOL内存池中分配一个完整的PBUF(包括结构体本身和数据空间),故在LwIP中跟这种内存池相关的PBUF类型为PBUF_POOL

#define PBUF_POOL_BUFSIZE               LWIP_MEM_ALIGN_SIZE(TCP_MSS+PBUF_IP_HLEN+PBUF_TRANSPORT_HLEN+PBUF_LINK_ENCAPSULATION_HLEN+PBUF_LINK_HLEN)

跟MEMP_PBUF内存池有关的PBUF类型为PBUF_ROM/PBUF_REF。

PBUF类型

数据包处理的一个重要特征就是要高效,内存分配要高效这是毋庸置疑的,如果数据频繁拷贝,这个肯定会影响效率;所以PBUF结构存在四种类型,分别为

/* Base flags for pbuf_type definitions: */

/** Indicates that the payload directly follows the struct pbuf.
 *  This makes @ref pbuf_header work in both directions. */
#define PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS       0x80
/** Indicates the data stored in this pbuf can change. If this pbuf needs
 * to be queued, it must be copied/duplicated. */
#define PBUF_TYPE_FLAG_DATA_VOLATILE                0x40
/** 4 bits are reserved for 16 allocation sources (e.g. heap, pool1, pool2, etc)
 * Internally, we use: 0=heap, 1=MEMP_PBUF, 2=MEMP_PBUF_POOL -> 13 types free*/
#define PBUF_TYPE_ALLOC_SRC_MASK                    0x0F
/** Indicates this pbuf is used for RX (if not set, indicates use for TX).
 * This information can be used to keep some spare RX buffers e.g. for
 * receiving TCP ACKs to unblock a connection) */
#define PBUF_ALLOC_FLAG_RX                          0x0100
/** Indicates the application needs the pbuf payload to be in one piece */
#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
/** First pbuf allocation type for applications */
#define PBUF_TYPE_ALLOC_SRC_MASK_APP_MIN            0x03
/** Last pbuf allocation type for applications */
#define PBUF_TYPE_ALLOC_SRC_MASK_APP_MAX            PBUF_TYPE_ALLOC_SRC_MASK

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;

其中以下宏定义用于标记PBUF从哪里进行分配

#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
/** First pbuf allocation type for applications */
#define PBUF_TYPE_ALLOC_SRC_MASK_APP_MIN            0x03
/** Last pbuf allocation type for applications */
#define PBUF_TYPE_ALLOC_SRC_MASK_APP_MAX            PBUF_TYPE_ALLOC_SRC_MASK

PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP从内存堆中分配

PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF从MEMP_PBUF内存池中分配PBUF结构

PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL从MEMP_PBUF_POOL内存池分配一个完整PBUF。

/** Indicates that the payload directly follows the struct pbuf.
 *  This makes @ref pbuf_header work in both directions. */
#define PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS       0x80

PBUF结构体和PBUF管理的数据这两者占用的内存空间是连续的

/** Indicates the data stored in this pbuf can change. If this pbuf needs
 * to be queued, it must be copied/duplicated. */
#define PBUF_TYPE_FLAG_DATA_VOLATILE                0x40

PBUF结构管理的数据部分可能会改变,故如果此PBUF需要排队,则数据部分需要深拷贝

/** Indicates this pbuf is used for RX (if not set, indicates use for TX).
 * This information can be used to keep some spare RX buffers e.g. for
 * receiving TCP ACKs to unblock a connection) */
#define PBUF_ALLOC_FLAG_RX                          0x0100

标记此PBUF用于接收,否则用于发送

/** Indicates the application needs the pbuf payload to be in one piece */
#define PBUF_ALLOC_FLAG_DATA_CONTIGUOUS             0x0200

标记此PBUF管理的数据必须连续,即只能由一个PBUF来管理

下面对各个类型的特点进行了总结

PBUF_RAM
  • 从内存堆中分配,即通过mem_alloc函数分配

  • pbuf结构自身和数据占用的内存是连续的

  • 一般用于发送

  • 通过pbuf_alloc函数分配的PBUF_RAM类型的pbuf一般都是独立的(不是链表形式),即一个PBUF结构管理整个数据部分,请看下面的图1

PBUF_ROM
  • 从内存池MEMP_PBUF中分配pbuf结构体本身

  • 数据部分和pbuf结构不是连续的内存

  • 数据部分一般指向不变的内容

  • 排队缓存时不需要拷贝数据部分

PBUF_REF
  • 从内存池MEMP_PBUF中分配pbuf结构体本身

  • 数据部分和pbuf结构不是连续的内存

  • 排队缓存时需要拷贝数据部分

  • 数据部分会改变

PBUF_POOL
  • 从内存池MEMP_PBUF_POOL中分配pbuf结构体本身和数据内存

  • 一般用于接收

  • 禁止用于发送

  • pbuf结构自身和数据占用的内存是连续的

  • 数据部分可以级联,即PBUF_POOL类型的A和B可以通过next指针构成一个单链表,请看下面的图2


既然上面我们已经介绍了PBUF的类型,那么接下来我们先介绍一下PBUF结构的定义

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;

  /** In case the user needs to store data custom data on a pbuf */
  LWIP_PBUF_CUSTOM_DATA
};

各个字段含义:

next:pbuf结构通过此字段可以构成链表,具体可以看下面的图1-2-3

payload:当前pbuf管理的数据部分的地址(指向数据区域)

tot_len:整个pbuf链表的长度,如果只有一个pbuf那么tot_len等于len(如果级联情况下则代表是某个数据包的最后一个pbuf)

len:当前pbuf结构数据部分的长度

type_internal:当前pbuf结构的一些类型信息

flags:额外的标记信息

ref:当前pbuf结构被引用的次数

if_idx:接收数据包时标记数据包从哪个网络接口接收的

LWIP_PBUF_CUSTOM_DATA:挂接的用户数据

数据包、数据包队列、PBUF三者之间的关系
  • 一个数据包可以由一个pbuf结构描述,也可以由多个pbuf结构来描述,即链式pbuf结构(pbuf chain)

  • 多个数据包可以通过pbuf结构来缓存组成数据包队列,即多个pbuf结构链接起来,通过判断某个PBUF结构的tot_len是否等于len来找到每个数据包的结尾,即队列pbuf(packet queue)

  • 一个数据包队列可以包含一个或者多个pbuf链,每个pbuf链包含一个或者多个pbuf结构

  • 某个数据包的最后一个pbuf结构的next指针非空,则说明这个数据包是数据包队列的一部分

为了方便大家更好的理解两者的关系,请查看以下图示

LwIP系列--数据包处理和PBUF结构详解

图1

上图是一个PBUF结构构成的一个数据包,其next字段为NULL,并且tot_len等于len。

LwIP系列--数据包处理和PBUF结构详解

图2

上图是两个PBUF结构构成的一个数据包,其中A的next指向B,并且A的tot_len等于B的tot_len加上A的len;B的next指向NULL,并且B的tot_len等于len。

LwIP系列--数据包处理和PBUF结构详解

图3

上图中数据包1由PBUF A和B组成(链),数据包2只有一个PBUF结构。

PBUF Layer说明

在博客的最开始我们提到不同的层有不同的数据包头部,在LwIP定义了以下几种pbuf layer

/* @todo: We need a mechanism to prevent wasting memory in every pbuf
   (TCP vs. UDP, IPv4 vs. IPv6: UDP/IPv4 packets may waste up to 28 bytes) */

#define PBUF_TRANSPORT_HLEN 20
#if LWIP_IPV6
#define PBUF_IP_HLEN        40
#else
#define PBUF_IP_HLEN        20
#endif

/**
 * 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 || LWIP_VLAN_PCP) && !defined __DOXYGEN__
 #define PBUF_LINK_HLEN                  (18 + ETH_PAD_SIZE)
#else /* LWIP_HOOK_VLAN_SET || LWIP_VLAN_PCP */
 #define PBUF_LINK_HLEN                  (14 + ETH_PAD_SIZE)
#endif /* LWIP_HOOK_VLAN_SET || LWIP_VLAN_PCP */
 #endif

/**
 * 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

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_TRANSPORT代表传输层

PBUF_IP代表网络层

PBUF_LINK代表数据链路层

PBUF_RAW_TX用于发送

PBUF_RAW用于接收

pbuf分配

pbuf_alloc函数用于分配一个新的pbuf或者pbuf链

/**
 * @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: {
      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);

      /* 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;
}

其中PBUF_REF/PBUF_ROM/PBUF_RAM的分配都很简单

我们主要讲解一下PBUF_POOL的注意点

①从MEMP_PBUF_POOL内存池中分配一个pbuf结构

②如果当前分配的pbuf结构失败,则需要释放已经分配的pbuf结构(可能需要分配pbuf链,所以需要从MEMP_PBUF_POOL内存池中分配多个pbuf结构)

③获取内存块中数据区减去偏移头部后的长度,即第一个pbuf可以管理的数据部分的长度

④初始化当前pbuf结构体各个字段,注意LWIP_MEM_ALIGN((void *)((u8_t *)q + SIZEOF_STRUCT_PBUF + offset))是当前pbuf的payload指向的地址

⑤⑥⑦将各个pbuf通过next构成链表

⑧获取当前pbuf的下一个pbuf结构分配时还需要的长度

⑨只有第一个pbuf有offset偏移

⑩还有数据没有被pbuf管理则继续分配pbuf结构


pbuf释放
/**
 * @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 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;
}

注意:请求释放的pbuf可能是pbuf链也可能是pbuf队列

①申请保护,避免多线程访问问题

②将当前pbuf结构的ref次数减一

③如果当前pbuf的ref等于0,则对齐占用的内存进行释放

④先记录当前pbuf的下一个pbuf的地址

⑤获取当前pbuf的分配属性(从哪里分配的)

⑥如果是自定义的pbuf,则调用自定义pbuf的custom_free_function函数进行释放

⑦根据分配源进行释放

⑧如果当前pbuf的ref非0则退出循环

举例说明

如果有三个pbuf结构A-B-C构成一个单链表

 * 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 .......

至此关于PBUF的知识点基本讲解完毕,后续在介绍网络接口和收发数据包时再进一步讲解其使用。文章来源地址https://www.toymoban.com/news/detail-459009.html

到了这里,关于LwIP系列--数据包处理和PBUF结构详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Pandas 数据分析系列1--Series&DataFrame数据结构详解

       P andas 是一个开源的数据分析和数据处理库,是基于 NumPy 开发的。它提供了灵活且高效的数据结构,使得处理和分析结构化、缺失和时间序列数据变得更加容易。其在数据分析和数据处理领域广泛应用,在金融、社交媒体、科学研究等领域都有很高的使用率和广泛的应用

    2024年02月08日
    浏览(41)
  • ArcGIS笔记13_利用ArcGIS制作岸线与水深地形数据?建立水动力模型之前的数据收集与处理?

    在 利用MIKE建立水动力模型 ( 详见【MIKE水动力笔记】系列 )之前,需要收集、处理和制作诸多数据和资料,主要有 岸线数据、水深地形数据、开边界潮位驱动数据、风场数据、潮位和海流观测资料和站点潮汐调和常数资料 等。本篇主要介绍这些资料的获取与处理。 其中岸

    2024年02月08日
    浏览(44)
  • [QT编程系列-21]:基本框架 - QT常见数据结构:QString、QList、QVector、QMap、QHash、QSet、QPair详解

    目录 1 QString 2 QList 3 QVector 4 QMap 5 QHash 6 QSet 7 QPair QString是Qt中用于存储和操作字符串的类。它提供了丰富的字符串处理方法和功能。 以下是QString的一些主要特点和常用操作: 创建QString对象: 获取字符串内容和长度: 字符串比较和搜索: 字符串分割和连接: 字符串格式

    2024年02月16日
    浏览(41)
  • LwIP系列(5):TCP 3次握手+4次挥手+状态机转换

    TCP的3次握手、4次挥手以及TCP状态机,是TCP的核心概念,我们在分析LwIp中TCP相关代码流程,也需要熟悉这些流程,本文就详细介绍这些概念。 网上针对为什么是3次握手,会有很多的分析,尤其有些文章会特别强调client和server的seq序列号同步,众说纷纭吧,我个人倾向于: 防止

    2024年02月13日
    浏览(71)
  • 「PHP系列」PHP文件处理及文件上传详解

    PHP 提供了丰富的文件处理功能,使得开发者可以方便地在服务器端进行文件的创建、读取、写入、删除等操作。以下是一些常见的 PHP 文件处理操作及其示例: 使用 fopen() 函数打开文件: 模式可以是以下之一: \\\"r\\\" :只读方式打开,将文件指针指向文件头。 \\\"w\\\" :写入方式打

    2024年04月22日
    浏览(45)
  • WebSocket的那些事(6- RabbitMQ STOMP目的地详解)

    在上节 WebSocket的那些事(5-Spring STOMP支持之连接外部消息代理)中我们已经简单介绍了各种目的地类型,如下图: 这一节我们来详细探讨一下各种目的地类型的区别。 在 MESSAGE 报文中请求头 destination 的格式如下: 发布到默认交换机的消息目的地格式为: /queue/queue_name 。 发

    2024年02月08日
    浏览(45)
  • 玩转Mysql系列 - 第20篇:异常捕获及处理详解

    这是Mysql系列第20篇。 环境:mysql5.7.25,cmd命令中进行演示。 代码中被[]包含的表示可选,|符号分开的表示可选其一。 需求背景 我们在写存储过程的时候,可能会出现下列一些情况: 插入的数据违反唯一约束,导致插入失败 插入或者更新数据超过字段最大长度,导致操作失

    2024年02月09日
    浏览(37)
  • FPGA - 7系列 FPGA内部结构之CLB -02- CLB功能详解

    本文翻译自UG474第二章,主要对7系列FPGAs CLB结构进行详细介绍。这些细节对设计优化和验证很有帮助。 CLB 在 7 系列 FPGA 中按列排列。 7 系列是基于 ASMBL架构提供的独特柱状方法的第四代产品。ASMBL 架构 Xilinx 创建了高级硅模块块 (ASMBL) 架构,以支持具有针对不同应用领域优化

    2024年02月06日
    浏览(38)
  • 深入浅出 Yolo 系列之 Yolov7 基础网络结构详解

    从 2015 年的 YOLOV1 ,2016 年 YOLOV2 , 2018 年的 YOLOV3 ,到 2020 年的 YOLOV4 、 YOLOV5 , 以及最近出现的 YOLOV76 和 YOLOV7 可以说 YOLO 系列见证了深度学习时代目标检测的演化。对于 YOLO 的基础知识以及 YOLOV1 到 YOLOV5 可以去看大白的 YOLO 系列,本文主要对 YOLOV7 的网络结构进行一个梳理

    2024年02月04日
    浏览(48)
  • 【数据结构系列】链表

    💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学习,不断总结,共同进步,活到老学到老 导航 檀越剑指大厂系列:全面总

    2024年02月13日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包