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指针非空,则说明这个数据包是数据包队列的一部分

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

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

    物联沃分享整理
    物联沃-IOTWORD物联网 » LwIP系列:深入解析数据包处理和PBUF结构

    发表评论