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

news2024/7/4 6:14:55

一、目的

在之前的博文中我们介绍了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指针非空,则说明这个数据包是数据包队列的一部分

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

图1

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

图2

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

图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的知识点基本讲解完毕,后续在介绍网络接口和收发数据包时再进一步讲解其使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/189493.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

泛微发布数字化营销管理平台-九川汇

泛微全程数字化营销管理平台——九川汇&#xff0c;对内可以跨部门、跨组织高效协同&#xff0c;对外借助企业微信快速连接客户。 营销活动是企业运营中重要的一环&#xff0c;数字化工具如何满足企业的营销管理需求&#xff1a; 1、如何有效助力销售活动&#xff1a;帮助销售…

WebServer重写(一):日志库双缓冲和阻塞队列压测对比

目录前言重构动机模块介绍FileUtil&#xff0c;LogFile&#xff0c;LogStream&#xff0c;LoggingAsyncLogging&#xff08;重要&#xff09;压测源码前言 上次参考TinyWebserver的实现思路是&#xff1a;实现一个blockQueue, 然后实现一个日志接口类&#xff0c;这个接口类承…

ROS小车研究笔记1/31/2023 小车硬件结构及键盘移动控制节点

1 小车硬件结构 1 中控设备 上方的单片机用于控制电机运动&#xff0c;搭载wifi模块和电量显示屏。下方为树莓派&#xff0c;安装了ROS系统和Ubuntu系统&#xff0c;用于整个小车控制。显示屏和树莓派相连 2 传感器系统 激光雷达及转换器。激光雷达和转换器相连&#xff0…

【Rust】7. 枚举和模式匹配

7.1 枚举&#xff08;可存储不同类型的值&#xff09; 7.1.1 基本概念 7.1.2 枚举的简洁用法&#xff1a;构造函数 7.1.3 枚举的优势&#xff1a;处理不同类型和数量的数据 枚举成员的类型&#xff1a;字符串、数字类型、结构体、枚举注意&#xff1a;在未将标准库枚举引入当…

Java——两两交换链表中的节点

题目链接 leetcode在线oj题——两两交换链表中的节点 题目描述 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 题目示例 …

Vue中的$children与$parent讲解

$children与$parent直接演示代码父组件&#xff1a;<template><div><h2>BABA有存款: {{ money }}</h2><button>找小明借钱100</button><br /><button>找小红借钱150</button><br /><button>找所有孩子借钱2…

Day875.怎么给字符串字段加索引 -MySQL实战

怎么给字符串字段加索引 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于怎么给字符串字段加索引的内容。 现在&#xff0c;几乎所有的系统都支持邮箱登录&#xff0c;如何在邮箱这样的字段上建立合理的索引。 假设&#xff0c;现在维护一个支持邮箱登录的系统&…

【计算机图形学(译)】 一、介绍

【计算机图形学(译&#xff09;】 一、介绍1 介绍 Introduction1.1 图形领域 (Graphics Areas)1.2 主要应用 (Major Applications)1.3 图形APls (Graphics APIs)1.4 图形管线 (Graphics Pinpline)1.5 数值问题 (Numerical Issues)1.6 效率 (Efficiency)1.7 设计和编写图形程序 …

Detectron2部署教程,着重ONNX(从官网翻译)

本教程翻译至这里 https://detectron2.readthedocs.io/en/latest/tutorials/deployment.html detectron2模型训练以后如果想要部署&#xff0c;就需要导出专门的模型才可以。 三种模型导出方式 detectron2支持的模型导出方式有&#xff1a; tracing 该方式导出的格式是Torch…

常量池/String常见面试题

目录 常量池与运行时常量池 字符串常量池String_Table 字符串变量拼接 字符串常量拼接 字符串延迟加载 字符串intern方法 总结StringTable的特点 常量池与运行时常量池 二进制字节码包括 类的基本信息,常量池,类方法定义(包含虚拟机指令) class文件中除了有类的版本,字…

新突破:科学家发现全新的量子纠缠效应

布鲁克海文国家实验室&#xff08;图片来源&#xff1a;网络&#xff09;布鲁克海文国家实验室的科学家发现了一种全新的量子纠缠效应&#xff0c;即使宇宙距离相隔广阔&#xff0c;量子纠缠也会使粒子奇迹般地联系在一起。这一发现使他们能够捕捉到原子内部的奇特世界。这项研…

【算法自由之路】二叉树的递归套路

【算法自由之路】二叉树的递归套路 预热&#xff0c;二叉树的后继节点 话不多说&#xff0c;首先是一道练手题&#xff0c;寻找二叉树任意给定节点的后继节点&#xff0c;此二叉树具备一个指向父节点的指针。 后继节点&#xff1a;在中序遍历中于给定节点后一个打印的节点 p…

SpringBoot实现配置文件的加密和解密

一、项目搭建 1.新建一个springBoot项目 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocatio…

DAMO-YOLO : A Report on Real-Time Object Detection Design

DAMO-YOLO:实时目标检测设计报告在本报告中&#xff0c;我们提出了一种被称为DAMO-YOLO的快速准确的物体检测方法&#xff0c;该方法比最先进的YOLO系列具有更高的性能。DAMO-YOLO是由YOLO扩展而来的&#xff0c;它采用了一些新技术&#xff0c;包括神经结构搜索(NAS)、高效的重…

LeetCode——1669. 合并两个链表

一、题目 给你两个链表 list1 和 list2 &#xff0c;它们包含的元素分别为 n 个和 m 个。 请你将 list1 中下标从 a 到 b 的全部节点都删除&#xff0c;并将list2 接在被删除节点的位置。 下图中蓝色边和节点展示了操作后的结果&#xff1a; 请你返回结果链表的头指针。 来…

【Qt】3.菜单栏、工具栏、状态栏、铆接部件、核心部件、资源文件

目录 菜单栏 工具栏 代码 mainwindow.cpp 结果 状态栏 铆接部件 核心部件 代码 mainwindow.cpp 结果 资源文件 代码 mainwindow.cpp 结果 菜单栏 只能有一个 menuBar()返回一个QMenuBar *bar 将bar放入到窗口中setMenuBar(bar) 添加菜单addMenu("文件&…

三年了,回村了

今年回老家了&#xff0c;因为工作和疫情等原因已经三年多没回了&#xff0c;思乡之情已经压不住了。 老家是一个五线小城市&#xff0c;属于南方典型的鱼米之乡&#xff1a;依山傍水、山清水秀。同时还有一个知名白酒厂&#xff1a;白云边&#xff0c;经济发展还不错。 老家…

从“语义网”到“去中心化”,Web3.0到底是个啥?

什么是Web3.0&#xff0c;为什么近两年这个概念又再一次火出了圈&#xff0c;但凡A股上市公司正在做或者准备做的业务与它沾上边&#xff0c;总会有那么几次异动。 这个概念到底是金融市场布下的骗局&#xff0c;还是未来互联网发展的趋势&#xff0c;在大家的眼里都是褒贬不一…

Redis 核心原理串讲(下),架构演进之高扩展

文章目录Redis 核心原理总览&#xff08;全局篇&#xff09;前言一、数据分片1、集群&#xff1f;2、分片&#xff1f;3、分片固定&#xff1f;4、元数据二、集群1、代理集群2、分片集群3、代理 分片集群三、生产实践总结Redis 核心原理总览&#xff08;全局篇&#xff09; 正…

新的一年,如何打开超级APP发展格局

本文开始我们先来明确一个概念&#xff1a;超级APP是什么&#xff1f;百度百科的定义是——那些拥有庞大的用户数&#xff0c;成为用户手机上的"装机必备”的基础应用。实际上各大互联网平台也给出了不同的解释&#xff0c;但相同点是他们都认为超级APP就应该超级个性化&a…