04-LWIP(网络数据包PBUF)

目录

1、协议栈模型

2、PBUF网络数据包

2.1、PBUF结构体

2.2、PBUF结构类型

2.2.1、PBUF_RAM类型

[2.2.2、 PBUF_ROM类型](#2.2.2、 PBUF_ROM类型)

[2.2.3、 PBUF_REF类型](#2.2.3、 PBUF_REF类型)

2.2.4、PBUF_POOL类型

3、PUBF重要函数源码解析

3.1、pbuf_alloc()

3.1.1、参数

3.1.2、PBUF_REF/ROM分支源码

3.1.3、PBUF_POOL分支源码

3.1.4、PBUF_RAM分支源码

3.2、pbuf_free()

3.2.1、参数

3.2.2、源码解析

4、常用API函数介绍

4.1、pbuf_realloc

4.2、pbuf_header

4.2.1、pbuf_remove_header

4.2.2、pbuf_add_header_impl

4.3、pbuf_take

4.4、pbuf_copy

4.5、pbuf_cat

4.6、pbuf_ref

4.7、pbuf_chain


TCP/IP分层思想:

  • 在标准的TCP/IP协议栈中,各层之间都是一个独立的模块,每层只需要负责本层的工作即可,不会越界到其他层次去读写数据,数据传输需要层层拷贝;

LWIP数据共享:

  • lwip的主要目标是嵌入式设备,作为轻量级的TCP/IP协议栈,模糊标准的TCP/IP分层思想,可以提高数据处理效率和内存空间利用率;
  • 即数据在lwip的tcpip协议栈各层中是公用的,各层只需要处理各层负责的字段即可;

1、协议栈模型

多线程模型:

  • 协议栈各个层次都独立成为一个线程;
  • 这种线程模型严格分层,代码容易维护,功能组件容易增删,但是层次数据需要通过线程通信进行交互,可能存在层层拷贝,不适用于嵌入式设备;

协议栈与操作系统融合:

  • 协议栈成为操作系统的一部分;
  • 线程与协议栈内核之间都是通过操作系统提供的函数来实现,协议栈各层之间与线程就没有很严格的分层结构,各层之间能交叉存取,从而提高效率;

协议栈内核与操作系统相互隔离:(lwip在用)

  • 协议栈只是操作系统的一条独立的线程;

  • 用户程序能驻留在协议栈内部(回调方式),协议栈通过回调函数实现用户与协议栈之间的数据交互;(RAW API接口编程)

  • 也可以让用户程序单独实现一个线程,通过信号量、消息等IPC通信机制与协议栈线程进行数据交互;(NETCONN API和Socket API 编程)

2、PBUF网络数据包

2.1、PBUF结构体

cpp 复制代码
/* 数据包结构体 pbuf */
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;
};
  • flag标志: 用于表示pbuf当前的属性;
cpp 复制代码
// 表示这个数据包应当立即推送给应用层
#define PBUF_FLAG_PUSH            0x01
// 表示这是 custom pbuf,释放时会调用 pbuf_custom->custom_free_function()
#define PBUF_FLAG_IS_CUSTOM       0x02
// 表示这是 UDP 组播包,需要环回
#define PBUF_FLAG_MCASTLOOP       0x04
// 表示这是链路层广播包
#define PBUF_FLAG_LLBCAST         0x08
// 表示这是链路层多播包
#define PBUF_FLAG_LLMCAST         0x10
// 表示这个 pbuf 包含 TCP FIN 标志
#define PBUF_FLAG_TCP_FIN         0x20
  • type_internal: 表示 pbuf 的内部存储属性,包括 payload 是否连续、数据是否易变、从哪里分配;
cpp 复制代码
/* 这个标志位表示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

2.2、PBUF结构类型

lwip中主要存在4种PBUF类型:

cpp 复制代码
typedef enum {

  PBUF_RAM = (PBUF_ALLOC_FLAG_DATA_CONTIGUOUS | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP),

  PBUF_ROM = PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF,

  PBUF_REF = (PBUF_TYPE_FLAG_DATA_VOLATILE | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF),

  PBUF_POOL = (PBUF_ALLOC_FLAG_RX | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL)
} pbuf_type;

2.2.1、PBUF_RAM类型

PBUF_RAM类型的pbuf:

  • PBUF_RAM类型的pbuf空间是由内存堆分配;
  • pbuf的数据管理区和数据区地址空间是连续的;
  • 多用于发送数据;

2.2.2、 PBUF_ROM类型

PBUF_ROM类型的pbuf:

  • PBUF_ROM类型的pbuf结构体空间是由内存池分配,即是MEMP_PBUF类型的POOL;(不包含数据区)
  • pbuf的数据管理区和数据区地址空间是不连续的,PBUF_ROM的数据区存在ROM中,一般是静态数据;

2.2.3、 PBUF_REF类型

PBUF_REF类型的pbuf:

  • PBUF_REF类型的pbuf结构体空间是由内存池分配,即是MEMP_PBUF类型的POOL;(不包含数据区)
  • pbuf的数据管理区和数据区地址空间是不连续的,PBUF_REF的数据区存在RAM中;

2.2.4、PBUF_POOL类型

PBUF_POOL类型的pbuf:

  • PBUF_POOL类型的pbuf空间是由内存池分配;
  • pbuf的数据管理区和数据区地址空间是连续的;
  • 该pbuf的实际空间大小是固定的;
  • 多用于接收数据,因为空间申请快。
  • 不要用于TX,因为如果当内存池为空了,TCP在排队等待,就会接收不了TCP ACK;

MEMP_PBUF_POOL类型的pbuf长度:PBUF_POOL_BUFSIZE_ALIGNED

cpp 复制代码
/* 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),且需要字节。

cpp 复制代码
/**
 * 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 数据的最大长度;

PBUF_IP_HLEN:IP层首部长度;

cpp 复制代码
#if LWIP_IPV6
#define PBUF_IP_HLEN        40
#else
#define PBUF_IP_HLEN        20
#endif

PBUF_TRANSPORT_HLEN:传输层首部长度;

cpp 复制代码
#define PBUF_TRANSPORT_HLEN 20

PBUF_LINK_ENCAPSULATION_HLEN:原始层首部长度。默认为0;

cpp 复制代码
/**
 * 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:链路层首部长度;

cpp 复制代码
/** 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

3、PUBF重要函数源码解析

3.1、pbuf_alloc()

cpp 复制代码
struct pbuf *
pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type)
{
  struct pbuf *p;
  u16_t offset = (u16_t)layer;
 
  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);
        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);
      break;
    }
    default:
      return NULL;
  }
  return p;
}

3.1.1、参数

  • pbuf_layer layer:协议层枚举,直接就是该层首部大小了。不同的协议层,layer大小不一样;
cpp 复制代码
typedef enum {

  PBUF_TRANSPORT = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN,

  PBUF_IP = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN,

  PBUF_LINK = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN,

  PBUF_RAW_TX = PBUF_LINK_ENCAPSULATION_HLEN,

  PBUF_RAW = 0
} pbuf_layer;
  • u16_t length:pbuf有效载荷的大小。和layer参数共同决定pbuf空间大小;
  • pbuf_type type:pbuf的类型,决定了pbuf空间怎样分配和空间来源;

3.1.2、PBUF_REF/ROM分支源码

cpp 复制代码
    case PBUF_REF: /* fall through */
    case PBUF_ROM:
      /* 对于这两个pbuf类型,只分配pbuf数据结构空间 */
      p = pbuf_alloc_reference(NULL, length, type);
      break;

调用 p = pbuf_alloc_reference(NULL, length, type) 申请pbuf结构体管理空间,但还没有指向任何数据区;(有一点和下图中的payload指向不符合,此时payload还未指向任何区域,指向为NULL)

3.1.3、PBUF_POOL分支源码

由于MEMP_PBUF_POOL内存池中每个节点的空间大小都是固定的,所以可能会出现一个节点不够用的情况,这样就需要pbuf链表的形式管理申请空间。

cpp 复制代码
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);
        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;
    }
  • 第一Pbuf节点 ------ 若需要的空间(要考虑 layer 帧头消耗的空间) > 最大一个节点可挂在的空间,就只分配一个节点最大可挂载的空间,初始化此节点,payload 指向申请到的空间向后偏移 PBUF struct + layer 大小的空间;
  • 往后Pbuf节点 ------ 初始化剩余节点空间,并将其和上一节点连接起来;

3.1.4、PBUF_RAM分支源码

cpp 复制代码
    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);
      break;
    }
  • 计算出数据区域需要的空间大小:layer大小 + 需要的数据区大小;
  • 实际申请大小:数据区需要的空间大小 + 结构体大小;
  • 防溢出操作;
  • 从堆中申请空间,初始化节点;

注:PUBF_RAM 是使用heap管理的方式申请空间的;

3.2、pbuf_free()

3.2.1、参数

- struct pbuf *p:需要释放的pbuf链表头;

**- return:**返回被释放空间的pbuf个数;

3.2.2、源码解析

cpp 复制代码
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;
}
  • 防止传参错误,在p = NULL时直接退出函数;
  • while循环中,会循环到直到释放完PBUF链表;
  • 每次循环要完成的任务:

1、 减少当前pbuf的被引用次数,只有当被引用次数为0时才可释放空间。否则会导致正在引用的对象出错;

2、判断PBUF的申请来源,不同来源按不同方式进行释放。如果PBUF为用户维护对象,则使用用户层释放回调函数;

  • 每次循环增加一次Count,记录释放了多少个PUBF节点,最后将此值进行返回;

4、常用API函数介绍

4.1、pbuf_realloc

cpp 复制代码
void
pbuf_realloc(struct pbuf *p, u16_t new_len)
{
  struct pbuf *q;
  u16_t rem_len; /* remaining length */
  u16_t shrink;

  /* 不支持空间扩充 */
  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;
     }
  /* 当前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));
  }
  /* 调整这个pbuf的长度字段 */
  q->len = rem_len;
  q->tot_len = q->len;

  if (q->next != NULL) {
    /* 释放链中剩余的pbuf */
    pbuf_free(q->next);
  }
  /* 截断当前pbuf链表 */
  q->next = NULL;
}

参数:

复制代码
- struct pbuf *p (pbuf链表头)
- u16_t new_len(新长度,这个长度必须 < 原始长度)

作用:

用于裁剪pbuf(链表)尾部空间,只能裁剪pbuf空间,不能对其进行扩张;

函数解析:

  • 函数只支持裁剪操作,若传入新空间的长度 > 旧空间长度则直接退出函数;
  • 计算需要裁剪的空间大小;
  • 通过while循环遍历出pbuf链表会在哪个pbuf节点上截断,并将其下个节点赋值给q;(考虑到使用POOL分配的数据,其内存大小是一块,所以释放只能释放其后面的节点,这会导致有一部分空间未被释放)
  • 通过heap方式申请到的空间对其进行截断释放;(只有heap方式申请到的空间才可以被完全的真正被释放掉)
  • 重新调整pbuf的长度,pbuf_free递归释放掉分界节点之后的所有节点,并截断当前pbuf链表;

4.2、pbuf_header

cpp 复制代码
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);
  }
}

作用:

给包 "加/剥" 头;(header_size_increment > 0 时,给包加头;

header_size_increment < 0 时,给包剥头;)

其中 pbuf_remove_header 是隐藏部分头字段,它在下层转交pbuf到上层时调用;

pbuf_add_header_impl 是暴露部分头字段,它在上层转交pbuf到下层时调用;

4.2.1、pbuf_remove_header

它在下层转交pbuf到上层时调用。

cpp 复制代码
u8_t
pbuf_remove_header(struct pbuf *p, size_t header_size_decrement)
{
  void *payload;
  u16_t increment_magnitude;

  /* 参数校验 */
  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);

  return 0;
}

函数的主要操作:

其实就是在加保护的基础上,将payload指针后移,并同步更新 pbuf 的长度;

4.2.2、pbuf_add_header_impl

它在上层转交pbuf到下层时调用。

cpp 复制代码
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;

  /* 参数校验 */
  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) {
      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;
    }
  }

  /* 修改pbuf字段 */
  p->payload = payload;
  p->len = (u16_t)(p->len + increment_magnitude);
  p->tot_len = (u16_t)(p->tot_len + increment_magnitude);

  return 0;
}

函数的主要操作:

其实就是在加保护的基础上,将payload指针前移暴露layer层头帧,并同步更新 pbuf 的长度;

4.3、pbuf_take

用于向pbuf的数据区域拷贝数据。

cpp 复制代码
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;

  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) {
    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;
  }
  return ERR_OK;
}

函数的主要操作:

在进行输入数据参数验证的基础上,对数据进行分节点的拷贝。如果数据总大小 > 一个节点的容量,则将剩余数据拷贝到下个节点并更新数据长度;

4.4、pbuf_copy

用于将一个任何类型的pbuf中的数据拷贝到一个PBUF_RAM类型的pbuf中。

cpp 复制代码
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);
}

函数的主要操作:

其中 pbuf_copy_partial_pbuf 函数是用于拷贝部分数据的函数,最后一个参数代表拷贝数据的偏移量;(源数据偏移多少字节后,拷贝到目标缓冲区)

4.5、pbuf_cat

用于拼接两个pbuf链表,且后面接入的pbuf链表不与前面的pbuf链表有分割标志,拼接后,后面的pbuf链表不能被其它地方引用了。

注:该函数的实现没有对tot_len字段溢出监测处理,所以使用时需要预判,两个链表的tot_len拼接后不要有溢出的可能;

cpp 复制代码
void
pbuf_cat(struct pbuf *h, struct pbuf *t)
{
  struct pbuf *p;

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

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

函数的主要操作:

其实就是将两个pbuf类型节点连接起来,形成链;

4.6、pbuf_ref

用于将pbuf中的值加1。

4.7、pbuf_chain

用于连接两个pbuf(链表)为一个pbuf链表;

此时t节点,不受h节点控制。因为h节点就算释放了,t节点也不会被连带释放因为其还会有一个 索引在。

且后面接入的pbuf链表的首个pbuf节点的ref引用字段+1,作为两个数据包的分割点;

cpp 复制代码
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));
}

将h和t两个节点进行连接,连接成功后将t的引用进行++;(t的ref++为两个数据包的分割点,保证t不受h控制)

相关推荐
cc4422bb1 小时前
bgp练习
网络
allnlei2 小时前
两种 AAC 码流封装详解:Raw(ASC) vs ADTS
网络·aac
Yang96112 小时前
鼎讯信通 RM-1000:助力风电信号覆盖与设备稳定运行
大数据·网络
SXJR2 小时前
langchain4j是如何保证tools或者funcation call不出错的
java·网络·数据库·ai·语言模型
Sagittarius_A*2 小时前
H3CSE 高性能园区网:NQA 网络质量分析详解
网络
m0_730801132 小时前
ospf实验作业
网络
郑洁文2 小时前
基于网络爬虫的XSS漏洞检测系统的设计与实现
网络·爬虫·网络安全·xss
饿了吃洗衣凝珠3 小时前
ospf笔记
网络·tcp/ip·智能路由器
上海云盾安全满满4 小时前
改善用户体验 从CDN网络加速开始
网络·ux