【lwip】04-網路數據包流向


前言

了解了lwip的記憶體管理後,接下來就是網路數據包的了解。

注意與內部lwip消息的區別:網路數據包用於網路數據的流轉,而內部lwip消息用於內部協作。

本篇開始,結合源碼獨立分析lwip,逐步拆解lwip開源庫。

參考:

4.1 TCPIP分層與lwip數據共享

TCPIP分層思想

  • 在標準的TCP/IP協議棧中,各層之間都是一個獨立的模組,每層只需要負責本層的工作即可,不會越界到其他層次去讀寫數據,數據傳輸需要層層拷貝。

lwip數據共享

  • lwip的主要目標是嵌入式設備,作為輕量級的TCP/IP協議棧,模糊掉標準的TCP/IP分層思想,可以提高數據處理效率和記憶體空間利用率。
  • 即是數據在lwip的tcpip協議棧各層中是公用的,各層只需要處理各層負責的欄位即可。

4.2 協議棧執行緒模型

多執行緒模型:

  • 協議棧各個層次都獨立成為一個執行緒。
  • 這種執行緒模型嚴格分層,程式碼容易維護,功能組件容易增刪,但是層次數據需要通過執行緒通訊進行交互,可能存在層層拷貝,不適用於嵌入式設備。

協議棧與作業系統融合:

  • 協議棧成為作業系統的一部分。
  • 執行緒與協議棧內核之間都是通過作業系統提供的函數來實現,協議棧各層之間與執行緒就沒有很嚴格的分層結構,各層之間能交叉存取,從而提高效率。

協議棧內核與作業系統相互隔離:(lwip在用)

  • 協議棧只是作業系統的一條獨立的執行緒。
  • 用戶程式能駐留在協議棧內部(回調方式),協議棧通過回調函數實現用戶與協議棧之間的數據交互。(RAW API介面編程)
  • 也可以讓用戶程式單獨實現一個執行緒,通過訊號量、消息等IPC通訊機制與協議棧執行緒進行數據交互。(NETCONN API和Socket API 編程)

4.3 pbuf 結構體

注意:

  • pbuf鏈表中第一個pbuf是有layer欄位的,用於存放協議頭部,而在它後面的pbuf則是沒有該欄位。
  • pbuf鏈表中,其中的節點pbuf可以由不同pbuf類型來組成的。

源程式碼:

/* 數據包結構體 pbuf */
struct pbuf {
  /* 單向非循環鏈表,指向接著的下一個pbuf */
  struct pbuf *next;

  /* 指向buffer中的實際數據空間地址 */
  void *payload;

  /* pbuf鏈表中當前pbuf及其鏈表後的pbuf數據長度總和 */
  u16_t tot_len;

  /* 當前pbuf數據長度 */
  u16_t len;

  /*  pbuf的類型,LwIP 中有 4 種 pbuf 的類型 */
  u8_t type_internal;

  /* 表示當前pbuf的屬性 */
  u8_t flags;

  /* pbuf 被引用的次數 */
  LWIP_PBUF_REF_T ref;

  /* 記錄傳入的數據包中輸入 netif 的索引 */
  u8_t if_idx;

  /* 用戶自定義 */
  LWIP_PBUF_CUSTOM_DATA
};

4.3.1 pbuf的標誌位flags

有以下屬性:

/** indicates this packet's data should be immediately passed to the application */
#define PBUF_FLAG_PUSH      0x01U
/** indicates this is a custom pbuf: pbuf_free calls pbuf_custom->custom_free_function()
    when the last reference is released (plus custom PBUF_RAM cannot be trimmed) */
#define PBUF_FLAG_IS_CUSTOM 0x02U
/** indicates this pbuf is UDP multicast to be looped back */
#define PBUF_FLAG_MCASTLOOP 0x04U
/** indicates this pbuf was received as link-level broadcast */
#define PBUF_FLAG_LLBCAST   0x08U
/** indicates this pbuf was received as link-level multicast */
#define PBUF_FLAG_LLMCAST   0x10U
/** indicates this pbuf includes a TCP FIN flag */
#define PBUF_FLAG_TCP_FIN   0x20U

4.4 pbuf的類型

pbuf的類型,主要是以pbuf的空間結構和空間來源來區別的。

這些標誌位可以在lwip內核內部其它地方判斷當前pbuf的記憶體屬性。

/* Base flags for pbuf_type definitions: */

/* 這個標誌位表示pbuf數據結構和數據區的地址連續。 */
#define PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS       0x80
/* 表示存儲在該pbuf中的數據可以更改。 */
#define PBUF_TYPE_FLAG_DATA_VOLATILE                0x40
/* 4 bit預留給16個分配源(例如堆、pool1、pool2等)
 * 在內部,使用: 0=heap, 1=MEMP_PBUF, 2=MEMP_PBUF_POOL -> 13 自由類型 */
#define PBUF_TYPE_ALLOC_SRC_MASK                    0x0F
/* 表示此pbuf用於RX(如果沒有設置,則表示用於TX)
 * 這個標誌可以用來保留一些備用的RX緩衝區,例如接收TCP ack以解除連接阻塞。 */
#define PBUF_ALLOC_FLAG_RX                          0x0100
/* 表示應用程式需要pbuf有效負載處於一個整體中 */
#define PBUF_ALLOC_FLAG_DATA_CONTIGUOUS             0x0200

#define PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP           0x00
#define PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF      0x01
#define PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL 0x02
/* 應用程式的第一種pbuf分配類型 */
#define PBUF_TYPE_ALLOC_SRC_MASK_APP_MIN            0x03
/* 應用程式的最後一種pbuf分配類型 */
#define PBUF_TYPE_ALLOC_SRC_MASK_APP_MAX            PBUF_TYPE_ALLOC_SRC_MASK
/**
 * @ingroup pbuf
 * Enumeration of pbuf types
 */
typedef enum {
  /** pbuf data is stored in RAM, used for TX mostly, struct pbuf and its payload
      are allocated in one piece of contiguous memory (so the first payload byte
      can be calculated from struct pbuf).
      pbuf_alloc() allocates PBUF_RAM pbufs as unchained pbufs (although that might
      change in future versions).
      This should be used for all OUTGOING packets (TX).*/
  PBUF_RAM = (PBUF_ALLOC_FLAG_DATA_CONTIGUOUS | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP),
  /** pbuf data is stored in ROM, i.e. struct pbuf and its payload are located in
      totally different memory areas. Since it points to ROM, payload does not
      have to be copied when queued for transmission. */
  PBUF_ROM = PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF,
  /** pbuf comes from the pbuf pool. Much like PBUF_ROM but payload might change
      so it has to be duplicated when queued before transmitting, depending on
      who has a 'ref' to it. */
  PBUF_REF = (PBUF_TYPE_FLAG_DATA_VOLATILE | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF),
  /** pbuf payload refers to RAM. This one comes from a pool and should be used
      for RX. Payload can be chained (scatter-gather RX) but like PBUF_RAM, struct
      pbuf and its payload are allocated in one piece of contiguous memory (so
      the first payload byte can be calculated from struct pbuf).
      Don't use this for TX, if the pool becomes empty e.g. because of TCP queuing,
      you are unable to receive TCP acks! */
  PBUF_POOL = (PBUF_ALLOC_FLAG_RX | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL)
} pbuf_type;

4.4.1 PBUF_RAM類型

PBUF_RAM類型的pbuf:

  • PBUF_RAM類型的pbuf空間是由記憶體堆分配;
  • pbuf的數據管理區和數據區地址空間是連續的;
  • 多用於發送數據。

4.4.2 PBUF_ROM類型

PBUF_ROM類型的pbuf:

  • PBUF_ROM類型的pbuf結構體空間是由記憶體池分配,即是MEMP_PBUF類型的POOL;(不包含數據區)
  • pbuf的數據管理區和數據區地址空間是不連續的,PBUF_ROM的數據區存在ROM中,一般是靜態數據。

4.4.3 PBUF_REF類型

PBUF_REF類型的pbuf和PBUF_ROM類型的pbuf結構一樣,只是數據區的存儲地址一個在RAM區一個在ROM區。

PBUF_REF類型的pbuf:

  • PBUF_REF類型的pbuf結構體空間是由記憶體池分配,即是MEMP_PBUF類型的POOL;(不包含數據區)
  • pbuf的數據管理區和數據區地址空間是不連續的,PBUF_REF的數據區存在RAM中。

4.4.4 PBUF_POOL類型

PBUF_POOL類型的pbuf:

  • PBUF_POOL類型的pbuf空間是由記憶體池分配;
  • pbuf的數據管理區和數據區地址空間是連續的;
  • 該pbuf的實際空間大小是固定的;
  • 多用於接收數據,因為空間申請快。
  • 不要用於TX,因為如果當記憶體池為空了,TCP在排隊等待,就會接收不了TCP ACK。

系統會初始化兩個與pbuf相關的記憶體池:

  • MEMP_PBUF:用於存放pbuf數據結構的記憶體池。主要用於pbuf數據結構和數據區地址不連續的PBUF_ROM、PBUF_REF類型的pbuf。
  • MEMP_ PBUF_POOL:用於存放pbuf數據結構和數據區地址連續的記憶體池。主要供給PBUF_POOL類型的pbuf。

PBUF_POOL類型的pbuf鏈表如下圖所示:

  • 由於PBUF_POOL類型的pbuf記憶體是由記憶體池分配的,所以pbuf鏈表中最後一個pbuf存在空間浪費的可能。

MEMP_PBUF_POOL類型的pbuf長度:PBUF_POOL_BUFSIZE_ALIGNED

/* Since the pool is created in memp, PBUF_POOL_BUFSIZE will be automatically
   aligned there. Therefore, PBUF_POOL_BUFSIZE_ALIGNED can be used here. */
#define PBUF_POOL_BUFSIZE_ALIGNED LWIP_MEM_ALIGN_SIZE(PBUF_POOL_BUFSIZE)

PBUF_POOL_BUFSIZE_ALIGNED長度是整個TCPIP協議棧從鏈路層到傳輸層的最大報文長度的size,是包含TCP_MSS, TRANSPORT header, IP header, and link header,還有一個原始層首部(默認為0),且需要位元組。

/**
 * PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. The default is
 * designed to accommodate single full size TCP frame in one pbuf, including
 * TCP_MSS, IP header, and link header.
 */
#if !defined PBUF_POOL_BUFSIZE || defined __DOXYGEN__
#define PBUF_POOL_BUFSIZE               LWIP_MEM_ALIGN_SIZE(TCP_MSS+PBUF_IP_HLEN+PBUF_TRANSPORT_HLEN+PBUF_LINK_ENCAPSULATION_HLEN+PBUF_LINK_HLEN)
#endif

TCP_MSS:除去頭部之後,一個網路包所能容納的 TCP 數據的最大長度。參考下圖。

/**
 * TCP_MSS: TCP Maximum segment size. (default is 536, a conservative default,
 * you might want to increase this.)
 * For the receive side, this MSS is advertised to the remote side
 * when opening a connection. For the transmit size, this MSS sets
 * an upper limit on the MSS advertised by the remote host.
 */
#if !defined TCP_MSS || defined __DOXYGEN__
#define TCP_MSS                         536
#endif

PBUF_IP_HLEN:IP層首部長度。

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

PBUF_TRANSPORT_HLEN:傳輸層首部長度。

#define PBUF_TRANSPORT_HLEN 20

PBUF_LINK_ENCAPSULATION_HLEN:原始層首部長度。默認為0。

/**
 * PBUF_LINK_ENCAPSULATION_HLEN: the number of bytes that should be allocated
 * for an additional encapsulation header before ethernet headers (e.g. 802.11)
 */
#if !defined PBUF_LINK_ENCAPSULATION_HLEN || defined __DOXYGEN__
#define PBUF_LINK_ENCAPSULATION_HLEN    0
#endif

PBUF_LINK_HLEN:鏈路層首部長度。

/** ETH_PAD_SIZE: number of bytes added before the ethernet header to ensure
 * alignment of payload after that header. Since the header is 14 bytes long,
 * without this padding e.g. addresses in the IP header will not be aligned
 * on a 32-bit boundary, so setting this to 2 can speed up 32-bit-platforms.
 */
#if !defined ETH_PAD_SIZE || defined __DOXYGEN__
#define ETH_PAD_SIZE                    0
#endif

/**
 * @defgroup lwip_opts_pbuf PBUF
 * @ingroup lwip_opts
 * @{
 */
/**
 * PBUF_LINK_HLEN: the number of bytes that should be allocated for a
 * link level header. The default is 14, the standard value for
 * Ethernet.
 */
#if !defined PBUF_LINK_HLEN || defined __DOXYGEN__
#if defined LWIP_HOOK_VLAN_SET && !defined __DOXYGEN__
#define PBUF_LINK_HLEN                  (18 + ETH_PAD_SIZE)
#else /* LWIP_HOOK_VLAN_SET */
#define PBUF_LINK_HLEN                  (14 + ETH_PAD_SIZE)
#endif /* LWIP_HOOK_VLAN_SET */
#endif

4.5 pbuf_alloc()

pbuf_alloc()是數據包申請函數:(詳細直接分析源碼)

  • pbuf_layer layer:協議層枚舉,直接就是該層首部大小了。不同的協議層,layer大小不一樣。
  • u16_t length:pbuf有效載荷的大小。和layer參數共同決定pbuf空間大小。
  • pbuf_type type:pbuf的類型,決定了pbuf空間怎樣分配和空間來源。

4.5.1 各層首部大小

層級越高,首部預留的空間要越大,因為往下層傳的時候需要下層需要填充該層的首部。

相關宏:

  • PBUF_LINK_ENCAPSULATION_HLEN:原始層首部長度。默認為0,在TCPIP協議棧中不預留空間。
  • PBUF_LINK_HLEN:鏈路層首部長度。默認是乙太網的標準值,14。可以按系統bit長度來偏移,讓IP層從系統對齊地址起。
  • PBUF_IP_HLEN:IP層首部長度。默認ipv4是20,如果使能了ipv6,就是40。
  • PBUF_TRANSPORT_HLEN:傳輸層首部長度。默認20。
/**
 * @ingroup pbuf
 * Enumeration of pbuf layers
 */
typedef enum {
  /** Includes spare room for transport layer header, e.g. UDP header.
   * Use this if you intend to pass the pbuf to functions like udp_send().
   */
  PBUF_TRANSPORT = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN,
  /** Includes spare room for IP header.
   * Use this if you intend to pass the pbuf to functions like raw_send().
   */
  PBUF_IP = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN,
  /** Includes spare room for link layer header (ethernet header).
   * Use this if you intend to pass the pbuf to functions like ethernet_output().
   * @see PBUF_LINK_HLEN
   */
  PBUF_LINK = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN,
  /** Includes spare room for additional encapsulation header before ethernet
   * headers (e.g. 802.11).
   * Use this if you intend to pass the pbuf to functions like netif->linkoutput().
   * @see PBUF_LINK_ENCAPSULATION_HLEN
   */
  PBUF_RAW_TX = PBUF_LINK_ENCAPSULATION_HLEN,
  /** Use this for input packets in a netif driver when calling netif->input()
   * in the most common case - ethernet-layer netif driver. */
  PBUF_RAW = 0
} pbuf_layer;

4.5.2 各個pbuf類型的空間分配實現(簡要)

PBUF_REFPBUF_ROM類型,只分配pbuf數據結構空間,從MEMP_PBUF記憶體池中獲得。

PBUF_POOL類型,空間從MEMP_PBUF_POOL記憶體池中獲得,如果一個pbuf節點不夠,就會以鏈表的方式獲取。

PBUF_RAM類型,空間從記憶體堆中獲得,一次性獲取。

4.5.3 PBUF_POOL類型malloc實現

由於MEMP_PBUF_POOL記憶體池中每個節點的空間大小都是固定的,所以可能會出現一個節點不夠用的情況,這樣就需要pbuf鏈表的形式管理申請到的空間。

調用q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);申請一個MEMP_PBUF_POOL類型的pbuf節點空間。

如果記憶體池為空,可以通過調用PBUF_POOL_IS_EMPTY();來釋放ooseq鏈表中釋放無序報文的MEMP_PBUF_POOL記憶體池空間,但是本次申請也是需要退出的。

退出本次空間申請前,需要釋放本次循環申請MEMP_PBUF_POOL類型的pbuf節點空間。如調用pbuf_free(p);

當前pbuf節點空間申請成功後:

獲取當前pbuf節點實際需要的、有效的數據空間長度。

如果是首個pbuf節點,還需要根據layer參數預留首部空間。

初始化當前pbuf節點。

更新變數值。

4.5.4 PBUF_RAM類型malloc實現

先計算申請堆空間長度:pbuf數據結構空間+首部空間+用戶實際申請的空間。注意位元組對齊。

mem_size_t payload_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(offset) + LWIP_MEM_ALIGN_SIZE(length));
mem_size_t alloc_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF) + payload_len);

通過p = (struct pbuf *)mem_malloc(alloc_len);從記憶體堆中申請。

void *mem_malloc(mem_size_t size_in);這個函數有三種實現方式:

  1. 基於自定義。
  2. 基於記憶體池。
  3. 基於記憶體堆。

申請成功後初始化當前pbuf節點。

4.5.5 pbuf_alloc()源碼

/**
 * @ingroup pbuf
 * Allocates a pbuf of the given type (possibly a chain for PBUF_POOL type).
 *
 * The actual memory allocated for the pbuf is determined by the
 * layer at which the pbuf is allocated and the requested size
 * (from the size parameter).
 *
 * @param layer header size
 * @param length size of the pbuf's payload
 * @param type this parameter decides how and where the pbuf
 * should be allocated as follows:
 *
 * - PBUF_RAM: buffer memory for pbuf is allocated as one large
 *             chunk. This includes protocol headers as well.
 * - PBUF_ROM: no buffer memory is allocated for the pbuf, even for
 *             protocol headers. Additional headers must be prepended
 *             by allocating another pbuf and chain in to the front of
 *             the ROM pbuf. It is assumed that the memory used is really
 *             similar to ROM in that it is immutable and will not be
 *             changed. Memory which is dynamic should generally not
 *             be attached to PBUF_ROM pbufs. Use PBUF_REF instead.
 * - PBUF_REF: no buffer memory is allocated for the pbuf, even for
 *             protocol headers. It is assumed that the pbuf is only
 *             being used in a single thread. If the pbuf gets queued,
 *             then pbuf_take should be called to copy the buffer.
 * - PBUF_POOL: the pbuf is allocated as a pbuf chain, with pbufs from
 *              the pbuf pool that is allocated during pbuf_init().
 *
 * @return the allocated pbuf. If multiple pbufs where allocated, this
 * is the first pbuf of a pbuf chain.
 */
struct pbuf *
pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type)
{
  struct pbuf *p;
  u16_t offset = (u16_t)layer;
  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F")\n", length));

  switch (type) {
    case PBUF_REF: /* fall through */
    case PBUF_ROM:
      /* 對於這兩個pbuf類型,只分配pbuf數據結構空間 */
      p = pbuf_alloc_reference(NULL, length, type);
      break;
    case PBUF_POOL: {
      struct pbuf *q, *last;
      u16_t rem_len; /* remaining length */
      p = NULL;
      last = NULL;
      rem_len = length;
      do {
        u16_t qlen;
        /* 從MEMP_PBUF_POOL記憶體池中申請 */
        q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);
        if (q == NULL) {
          /* 如果MEMP_PBUF_POOL記憶體池為空,可能會從ooseq鏈表中釋放無序報文的MEMP_PBUF_POOL記憶體池空間 */
          PBUF_POOL_IS_EMPTY();
          /* 釋放這個pbuf鏈表的空間 */
          if (p) {
            pbuf_free(p);
          }
          /* 返回NULL,申請失敗 */
          return NULL;
        }
        /* 獲取當前pbuf節點實際需要的、有效的數據空間長度。 */
        qlen = LWIP_MIN(rem_len, (u16_t)(PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)));
        /* 初始化當前pbuf節點 */
        pbuf_init_alloced_pbuf(q, LWIP_MEM_ALIGN((void *)((u8_t *)q + SIZEOF_STRUCT_PBUF + offset)),
                               rem_len, qlen, type, 0);
        LWIP_ASSERT("pbuf_alloc: pbuf q->payload properly aligned",
                    ((mem_ptr_t)q->payload % MEM_ALIGNMENT) == 0);
        LWIP_ASSERT("PBUF_POOL_BUFSIZE must be bigger than MEM_ALIGNMENT",
                    (PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)) > 0 );
        if (p == NULL) {
          /* 如果是首個pbuf節點,則當前pbuf節點指針就是當前pbuf鏈表頭 */
          p = q;
        } else {
          /* 當前pbuf節點插入pbuf鏈表 */
          last->next = q;
        }
        /* 更新變數 */
        last = q;
        rem_len = (u16_t)(rem_len - qlen);
        /* 只有首個pbuf節點才需要首部空間,後面的pbuf節點不需要 */
        offset = 0;
      } while (rem_len > 0);
      break;
    }
    case PBUF_RAM: {
      /* 當前pbuf需要的數據區空間大小,包括首部預留空間和用戶實際申請載體空間大小 */
      mem_size_t payload_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(offset) + LWIP_MEM_ALIGN_SIZE(length));
      /* 實際申請空間大小是當前pbuf的數據結構管理大小和數據區大小 */
      mem_size_t alloc_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF) + payload_len);

      /* 檢查申請的長度是否溢出系統位長 */
      if ((payload_len < LWIP_MEM_ALIGN_SIZE(length)) ||
          (alloc_len < LWIP_MEM_ALIGN_SIZE(length))) {
        return NULL;
      }

      /* 從記憶體堆中申請 */
      p = (struct pbuf *)mem_malloc(alloc_len);
      if (p == NULL) {
        return NULL;
      }
      /* 申請成功後,初始化當前pbuf節點 */
      pbuf_init_alloced_pbuf(p, LWIP_MEM_ALIGN((void *)((u8_t *)p + SIZEOF_STRUCT_PBUF + offset)),
                             length, length, type, 0);
      LWIP_ASSERT("pbuf_alloc: pbuf->payload properly aligned",
                  ((mem_ptr_t)p->payload % MEM_ALIGNMENT) == 0);
      break;
    }
    default:
      LWIP_ASSERT("pbuf_alloc: erroneous type", 0);
      return NULL;
  }
  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F") == %p\n", length, (void *)p));
  return p;
}

4.6 pbuf_free()

4.6.1 相關參數

函數原型:u8_t pbuf_free(struct pbuf *p)

  • struct pbuf *p:需要解除應用的pbuf或pbuf鏈表頭。
  • 返回:返回被釋放空間的pbuf個數。

4.6.2 釋放pbuf空間的條件

pbuf_free()用於釋放pbuf空間。

pbuf中ref欄位就是記錄pbuf數據包被引用的次數;

該欄位在pbuf申請初始化時,被置為1,表示還沒有被引用。

後續被引用一次,該欄位+1,被pbuf_free()一次,該欄位-1。

當ref欄位值為0時,該pbuf空間才可以備釋放。

4.6.3 釋放pbuf鏈表的邏輯

pbuf_free()釋放pbuf鏈表的邏輯:

  • 從pbuf鏈表首節點開始ref減1,為0則直接釋放當前節點;
  • 直到pbuf鏈表全部釋放完畢或遇到ref減1後不為0的pbuf節點為止。後續的pbuf節點的ref欄位也不會減1。

因為lwip檢索pbuf鏈表,ref減一後不為0,則認為當前pbuf節點為下一個數據包的首個pbuf節點。

所以,pbuf_free()遇到釋放pbuf鏈表時,只會處理第一個數據包佔用的pbuf節點。

pbuf鏈表中的同一個數據包的分界線實現,是通過在每個數據包的首個pbuf節點多加一個ref引用標誌。

舉個幾個栗子:

  • [一個pbuf鏈表的ref] –> [經過pbuf_free()釋放後的pbuf鏈表的ref]
  • [1–2–3–3] –> [..–1–3–3]
  • [2–1–2] –> [1–1–2]
  • [1–1–2] –> [..–..–1]

4.6.4 pbuf_free()使用說明

  1. 不能調用pbuf_free()釋放包隊列(packet queue)空間。

  2. 如果需要釋放pbuf鏈表,則必須傳入pbuf鏈表頭指針,切不能傳入中間pbuf節點的指針,避免記憶體管理異常。

  3. pbuf的引用計數器ref等於指向pbuf(或指向pbuf)的指針的數量。

    • 創建pbuf時,ref為1,就是只有一個指針指向當前pbuf。

4.6.5 pbuf_free()實現說明

每個pbuf的ref欄位操作都需要實現執行緒安全、原子性操作。

在多執行緒的系統下,可以在lwipports.h文件中實現這些宏定義:

  • 這些宏是在lwip內部必要時,實現執行緒安全、原子性操作。
  • 下面只是例子,進入臨界操作。
  • 當然其它能實現執行緒安全和原子性操作的都可以,如鎖。(這個純屬個人推測,並未思考過多問題
#define SYS_ARCH_DECL_PROTECT(x) uint32_t x /* 定義一個變數 */
#define SYS_ARCH_PROTECT(x) x = osiEnterCritical() /* 進入臨界 */
#define SYS_ARCH_UNPROTECT(x) osiExitCritical(x) /* 退出臨界 */

4.6.7 pbuf_free()源碼分析

/**
 * @ingroup pbuf
 * Dereference a pbuf chain or queue and deallocate any no-longer-used
 * pbufs at the head of this chain or queue.
 *
 * Decrements the pbuf reference count. If it reaches zero, the pbuf is
 * deallocated.
 *
 * For a pbuf chain, this is repeated for each pbuf in the chain,
 * up to the first pbuf which has a non-zero reference count after
 * decrementing. So, when all reference counts are one, the whole
 * chain is free'd.
 *
 * @param p The pbuf (chain) to be dereferenced.
 *
 * @return the number of pbufs that were de-allocated
 * from the head of the chain.
 *
 * @note MUST NOT be called on a packet queue (Not verified to work yet).
 * @note the reference counter of a pbuf equals the number of pointers
 * that refer to the pbuf (or into the pbuf).
 *
 * @internal examples:
 *
 * Assuming existing chains a->b->c with the following reference
 * counts, calling pbuf_free(a) results in:
 *
 * 1->2->3 becomes ...1->3
 * 3->3->3 becomes 2->3->3
 * 1->1->2 becomes ......1
 * 2->1->1 becomes 1->1->1
 * 1->1->1 becomes .......
 *
 */
u8_t
pbuf_free(struct pbuf *p)
{
  u8_t alloc_src;
  struct pbuf *q;
  u8_t count;

  if (p == NULL) {
    LWIP_ASSERT("p != NULL", p != NULL);/* 斷言 */
    /* 如果屏蔽了lwip的斷言功能,則繼續,列印log並返回0 */
    LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
                ("pbuf_free(p == NULL) was called.\n"));
    return 0;
  }
  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free(%p)\n", (void *)p));

  PERF_START; /* 默認為空定義,用戶可添加自己的操作 */

  count = 0; /* 用於記錄釋放了多少個pbuf節點的空間 */

  /* 從pbuf鏈表頭開始釋放引用ref */
  while (p != NULL) {
    LWIP_PBUF_REF_T ref;
    SYS_ARCH_DECL_PROTECT(old_level); /* 宏定義介面:定義old_level變數 */
    /* 對ref變數實現執行緒安全操作,維護其原子性。如在多執行緒系統中,可以進入臨界處理。 */
    SYS_ARCH_PROTECT(old_level); /* 宏定義介面:如進入臨界處理 */
    /* 所有pbuf中至少被引用一次,如果小於1,說明傳入的地址異常或者當前pbuf被踩,這種情況下可進入斷言。 */
    LWIP_ASSERT("pbuf_free: p->ref > 0", p->ref > 0);
    /* 當前pbuf的ref減1 */
    ref = --(p->ref);
    SYS_ARCH_UNPROTECT(old_level); /* 宏定義介面:如退出臨界處理 */
    /* 如果為0,說明當前pbuf沒有被其它地方引用,可釋放空間 */
    if (ref == 0) {
      /* 在釋放該pbuf空間前,先記錄這個pbuf節點的下一個pbuf節點 */
      q = p->next;
      LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: deallocating %p\n", (void *)p));
      alloc_src = pbuf_get_allocsrc(p); /* 獲取當前pbuf的記憶體來源。通過pbuf的類型type_internal欄位來判斷當前pbuf的記憶體來源 */
#if LWIP_SUPPORT_CUSTOM_PBUF
      /* 檢查當前pbuf是否是用戶層維護的pbuf */
      if ((p->flags & PBUF_FLAG_IS_CUSTOM) != 0) {
        struct pbuf_custom *pc = (struct pbuf_custom *)p;
        LWIP_ASSERT("pc->custom_free_function != NULL", pc->custom_free_function != NULL); /* 用戶的free回調必須存在 */
        pc->custom_free_function(p); /* 調用用戶側的回調來實現在lwip內核讓用戶層釋放當前pbuf空間 */
      } else
#endif /* LWIP_SUPPORT_CUSTOM_PBUF */
      {
        /* 如果當前pbuf的空間來源於MEMP_PBUF_POOL記憶體池,則調用memp_free()將其釋放 */
        if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL) {
          memp_free(MEMP_PBUF_POOL, p);
          /* 如果當前pbuf的空間來源於MEMP_PBUF記憶體池,則調用memp_free()將其釋放 */
        } else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF) {
          memp_free(MEMP_PBUF, p);
          /* 如果當前pbuf的空間來源於記憶體堆,則調用mem_free()將其釋放 */
        } else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP) {
          mem_free(p);
        } else {
          /* @todo: support freeing other types */
          LWIP_ASSERT("invalid pbuf type", 0);
        }
      }
      count++; /* 更新釋放了多少個pbuf空間 */
      /* 進入下一個pbuf */
      p = q;
    } else {/* 遇到ref不為0的,說明從這個pbuf依然被其它地方引用,pbuf鏈表上剩下的pbuf也是這樣。可以理解為當前ref不為0的pbuf為新的一個數據包的首個pbuf,後續的pbuf不需要減ref引用。
        因為pbuf_free的釋放原則是釋放一個數據包的pbuf。 */
      LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: %p has ref %"U16_F", ending here.\n", (void *)p, (u16_t)ref));
      /* 後續pbuf不需要被處理,p置為NULL標記退出循環處理。 */
      p = NULL;
    }
  }
  PERF_STOP("pbuf_free"); /* 默認為空定義,用戶可添加自己的操作 */
  /* 返回已分配的pbuf的數量 */
  return count;
}

4.7 其它pbuf處理函數

4.7.1 pbuf_realloc()

用於裁剪pbuf(鏈表)尾部空間。

只能裁剪,不能擴張。

對於PBUF_RAM類型的pbuf,是可能會釋放尾部空間,而其它三種pbuf類型,不會釋放空間,只會修改pbuf中的長度欄位值。

而對於pbuf鏈表,在截斷分界線後的pbuf,都會調用pbuf_free()進行釋放。當ref減1後為0,也會存在真正釋放空間的可能。

對於pbuf鏈表,即是截斷分界線後的pbuf沒有被真正釋放空間,這個pbuf鏈表也會截斷,拆分鏈表。

下面用到的void *mem_trim(void *rmem, mem_size_t newsize)函數屬於記憶體管理的記憶體堆範疇,這裡不對其進行源碼剖析,可簡單說明:

  • 用於裁剪記憶體堆尾部空間,不支援記憶體擴充。

  • 函數簡要實現內容:

    • 計算被裁剪的空間能否構成下一個記憶體堆節點。
    • 符合則初始化新的記憶體堆節點,插入記憶體堆鏈表。
    • 返回NULL或者傳入的rmem地址。
/**
 * @ingroup pbuf
 * Shrink a pbuf chain to a desired length.
 *
 * @param p pbuf to shrink.
 * @param new_len desired new length of pbuf chain
 *
 * Depending on the desired length, the first few pbufs in a chain might
 * be skipped and left unchanged. The new last pbuf in the chain will be
 * resized, and any remaining pbufs will be freed.
 *
 * @note If the pbuf is ROM/REF, only the ->tot_len and ->len fields are adjusted.
 * @note May not be called on a packet queue.
 *
 * @note Despite its name, pbuf_realloc cannot grow the size of a pbuf (chain).
 */
void
pbuf_realloc(struct pbuf *p, u16_t new_len)
{
  struct pbuf *q;
  u16_t rem_len; /* remaining length */
  u16_t shrink;

  LWIP_ASSERT("pbuf_realloc: p != NULL", p != NULL);

  /* 不支援空間擴充 */
  if (new_len >= p->tot_len) {
    return;
  }

  /* 計算需要裁剪的空間size */
  shrink = (u16_t)(p->tot_len - new_len);

  /* 先遍歷應該留在鏈中的所有pbufs */
  rem_len = new_len;
  q = p;
  /* 找出截取分界線所在的pbuf */
  while (rem_len > q->len) { /* 遍歷保留下來的pbuf */
    /* 通過pbuf長度減少剩餘長度 */
    rem_len = (u16_t)(rem_len - q->len);
    /* 減少pbuf保存的總長度 */
    q->tot_len = (u16_t)(q->tot_len - shrink);
    /* 進入下一個pbuf */
    q = q->next;
    LWIP_ASSERT("pbuf_realloc: q != NULL", q != NULL);
  }
  /* 當前q就是截取分界線所在的pbuf */
  /* rem_len 也是當前q的期望長度 */

  /* 只有PBUF_RAM類型才會真正釋放多餘空間 */
  /* 其它pbuf類型只是修改pbuf長度欄位值 */
  /* 只需要通過判斷PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP標誌位即可判斷當前pbuf是否是PBUF_RAM類型 */
  if (pbuf_match_allocsrc(q, PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP) && (rem_len != q->len) 
#if LWIP_SUPPORT_CUSTOM_PBUF
      && ((q->flags & PBUF_FLAG_IS_CUSTOM) == 0)
#endif /* LWIP_SUPPORT_CUSTOM_PBUF */
     ) {
    /* 裁剪空間。在記憶體堆的角度看新期望的空間size需要加上pbuf的數據結構size */
    q = (struct pbuf *)mem_trim(q, (mem_size_t)(((u8_t *)q->payload - (u8_t *)q) + rem_len));
    LWIP_ASSERT("mem_trim returned q == NULL", q != NULL);
  }
  /* 調整這個pbuf的長度欄位 */
  q->len = rem_len;
  q->tot_len = q->len;

  if (q->next != NULL) {
    /* 釋放鏈中剩餘的pbuf */
    pbuf_free(q->next);
  }
  /* 截斷當前pbuf鏈表 */
  q->next = NULL;
}

4.7.2 pbuf_header()

調整pbuf中的payload指針以隱藏或顯示數據區前的首部欄位。

payload指針偏移後,pbuf中的len和tot_len欄位也會刷新。

不支援PBUF_ROM和PBUF_REF類型的pbuf修改payload指針偏移。

/**
 * Adjusts the payload pointer to hide or reveal headers in the payload.
 *
 * Adjusts the ->payload pointer so that space for a header
 * (dis)appears in the pbuf payload.
 *
 * The ->payload, ->tot_len and ->len fields are adjusted.
 *
 * @param p pbuf to change the header size.
 * @param header_size_increment Number of bytes to increment header size which
 * increases the size of the pbuf. New space is on the front.
 * (Using a negative value decreases the header size.)
 * If header_size_increment is 0, this function does nothing and returns successful.
 *
 * PBUF_ROM and PBUF_REF type buffers cannot have their sizes increased, so
 * the call will fail. A check is made that the increase in header size does
 * not move the payload pointer in front of the start of the buffer.
 * @return non-zero on failure, zero on success.
 *
 */
u8_t
pbuf_header(struct pbuf *p, s16_t header_size_increment)
{
  return pbuf_header_impl(p, header_size_increment, 0);
}

static u8_t
pbuf_header_impl(struct pbuf *p, s16_t header_size_increment, u8_t force)
{
  if (header_size_increment < 0) {
    return pbuf_remove_header(p, (size_t) - header_size_increment);
  } else {
    return pbuf_add_header_impl(p, (size_t)header_size_increment, force);
  }
}

隱藏部分頭部欄位,如下層轉交pbuf到上層時的處理。調用pbuf_remove_header()實現。

/**
 * Adjusts the payload pointer to hide headers in the payload.
 *
 * Adjusts the ->payload pointer so that space for a header
 * disappears in the pbuf payload.
 *
 * The ->payload, ->tot_len and ->len fields are adjusted.
 *
 * @param p pbuf to change the header size.
 * @param header_size_decrement Number of bytes to decrement header size which
 *          decreases the size of the pbuf.
 *          If header_size_decrement is 0, this function does nothing and returns successful.
 * @return non-zero on failure, zero on success.
 *
 */
u8_t
pbuf_remove_header(struct pbuf *p, size_t header_size_decrement)
{
  void *payload;
  u16_t increment_magnitude;

  /* 參數校驗 */
  LWIP_ASSERT("p != NULL", p != NULL);
  if ((p == NULL) || (header_size_decrement > 0xFFFF)) {
    return 1;
  }
  if (header_size_decrement == 0) {
    return 0;
  }

  increment_magnitude = (u16_t)header_size_decrement;
  /* 不能偏移到超出pbuf數據區的末端 */
  LWIP_ERROR("increment_magnitude <= p->len", (increment_magnitude <= p->len), return 1;);

  /* 備份當前pbuf的payload指針 */
  payload = p->payload;
  LWIP_UNUSED_ARG(payload); /* only used in LWIP_DEBUGF below */

  /* 更新pbuf的payload指針 */
  p->payload = (u8_t *)p->payload + header_size_decrement;
  /* 更新pbuf長度欄位 */
  p->len = (u16_t)(p->len - increment_magnitude);
  p->tot_len = (u16_t)(p->tot_len - increment_magnitude);

  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_remove_header: old %p new %p (%"U16_F")\n",
              (void *)payload, (void *)p->payload, increment_magnitude));

  return 0;
}

暴露部分頭部欄位,如上層轉交pbuf到下層時的處理。調用pbuf_add_header_impl()實現。

/**
 * Adjusts the payload pointer to reveal headers in the payload.
 * @see pbuf_add_header.
 *
 * @param p pbuf to change the header size.
 * @param header_size_increment Number of bytes to increment header size.
 * @param force Allow 'header_size_increment > 0' for PBUF_REF/PBUF_ROM types
 *
 * @return non-zero on failure, zero on success.
 *
 */
static u8_t
pbuf_add_header_impl(struct pbuf *p, size_t header_size_increment, u8_t force)
{
  u16_t type_internal;
  void *payload;
  u16_t increment_magnitude;

  /* 參數校驗 */
  LWIP_ASSERT("p != NULL", p != NULL);
  if ((p == NULL) || (header_size_increment > 0xFFFF)) {
    return 1;
  }
  if (header_size_increment == 0) {
    return 0;
  }

  increment_magnitude = (u16_t)header_size_increment;
  /* 防止溢出 */
  if ((u16_t)(increment_magnitude + p->tot_len) < increment_magnitude) {
    return 1;
  }

  type_internal = p->type_internal;

  if (type_internal & PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS) { /* 當前pbuf類型數據管理和數據區地址連續 */
    /* 計算新的payload指針 */
    payload = (u8_t *)p->payload - header_size_increment;
    /* 越界檢查 */
    if ((u8_t *)payload < (u8_t *)p + SIZEOF_STRUCT_PBUF) {
      LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_TRACE,
                   ("pbuf_add_header: failed as %p < %p (not enough space for new header size)\n",
                    (void *)payload, (void *)((u8_t *)p + SIZEOF_STRUCT_PBUF)));
      return 1;
    }
  } else { /* pbuf數據管理和數據區地址不連續的,其實就是 PBUF_REF/PBUF_ROM 類型 */
    if (force) { /* 允許對 PBUF_REF/PBUF_ROM 類型操作 */
      payload = (u8_t *)p->payload - header_size_increment;
    } else {
      /* 不允許對 PBUF_REF/PBUF_ROM 類型操作 */
      return 1;
    }
  }
  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_add_header: old %p new %p (%"U16_F")\n",
              (void *)p->payload, (void *)payload, increment_magnitude));

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

  return 0;
}

4.7.3 pbuf_take()

pbuf_take()函數用於向pbuf的數據區域拷貝數據。

雖然該函數內部實現只是限制了傳入的數據長度不能大於pbuf的tot_len,

但是該函數在使用來說,建議只用於複製buf->tot_len的等價數據,即是數據傳入的數據剛好填滿pbuf。

/**
 * @ingroup pbuf
 * Copy application supplied data into a pbuf.
 * This function can only be used to copy the equivalent of buf->tot_len data.
 *
 * @param buf pbuf to fill with data
 * @param dataptr application supplied data buffer
 * @param len length of the application supplied data buffer
 *
 * @return ERR_OK if successful, ERR_MEM if the pbuf is not big enough
 */
err_t
pbuf_take(struct pbuf *buf, const void *dataptr, u16_t len)
{
  struct pbuf *p;
  size_t buf_copy_len;
  size_t total_copy_len = len;
  size_t copied_total = 0;

  LWIP_ERROR("pbuf_take: invalid buf", (buf != NULL), return ERR_ARG;);
  LWIP_ERROR("pbuf_take: invalid dataptr", (dataptr != NULL), return ERR_ARG;);
  LWIP_ERROR("pbuf_take: buf not large enough", (buf->tot_len >= len), return ERR_MEM;);

  if ((buf == NULL) || (dataptr == NULL) || (buf->tot_len < len)) {
    return ERR_ARG;
  }

  /* Note some systems use byte copy if dataptr or one of the pbuf payload pointers are unaligned. */
  for (p = buf; total_copy_len != 0; p = p->next) {
    LWIP_ASSERT("pbuf_take: invalid pbuf", p != NULL);
    buf_copy_len = total_copy_len;
    if (buf_copy_len > p->len) {
      /* this pbuf cannot hold all remaining data */
      buf_copy_len = p->len;
    }
    /* copy the necessary parts of the buffer */
    MEMCPY(p->payload, &((const char *)dataptr)[copied_total], buf_copy_len);
    total_copy_len -= buf_copy_len;
    copied_total += buf_copy_len;
  }
  LWIP_ASSERT("did not copy all data", total_copy_len == 0 && copied_total == len);
  return ERR_OK;
}

4.7.4 pbuf_copy()

pbuf_copy()函數用於將一個任何類型的pbuf中的數據拷貝到一個PBUF_RAM類型的pbuf中。

用於代表lwIP堆棧對包進行排隊,如ARP隊列。

/**
 * @ingroup pbuf
 * Create PBUF_RAM copies of pbufs.
 *
 * Used to queue packets on behalf of the lwIP stack, such as
 * ARP based queueing.
 *
 * @note You MUST explicitly use p = pbuf_take(p);
 *
 * @note Only one packet is copied, no packet queue!
 *
 * @param p_to pbuf destination of the copy
 * @param p_from pbuf source of the copy
 *
 * @return ERR_OK if pbuf was copied
 *         ERR_ARG if one of the pbufs is NULL or p_to is not big
 *                 enough to hold p_from
 */
err_t
pbuf_copy(struct pbuf *p_to, const struct pbuf *p_from)
{
  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_copy(%p, %p)\n",
              (const void *)p_to, (const void *)p_from));

  LWIP_ERROR("pbuf_copy: invalid source", p_from != NULL, return ERR_ARG;);
  return pbuf_copy_partial_pbuf(p_to, p_from, p_from->tot_len, 0);
}

4.7.5 pbuf_cat()

pbuf_cat() 用於拼接兩個pbuf鏈表,且後面接入的pbuf鏈表不與前面的pbuf鏈表有分割標誌,拼接後,後面的pbuf鏈表不能被其它地方引用了。

如果還想保留後面的pbuf鏈表能被其它地方引用的許可權,就使用pbuf_chain()函數來拼接。

需要注意的是,該函數的實現沒有對tot_len欄位溢出監測處理,所以使用時需要預判,兩個鏈表的tot_len拼接後不要有溢出的可能。

/**
 * @ingroup pbuf
 * Concatenate two pbufs (each may be a pbuf chain) and take over
 * the caller's reference of the tail pbuf.
 *
 * @note The caller MAY NOT reference the tail pbuf afterwards.
 * Use pbuf_chain() for that purpose.
 *
 * This function explicitly does not check for tot_len overflow to prevent
 * failing to queue too long pbufs. This can produce invalid pbufs, so
 * handle with care!
 *
 * @see pbuf_chain()
 */
void
pbuf_cat(struct pbuf *h, struct pbuf *t)
{
  struct pbuf *p;

  LWIP_ERROR("(h != NULL) && (t != NULL) (programmer violates API)",
             ((h != NULL) && (t != NULL)), return;);

  /* proceed to last pbuf of chain */
  for (p = h; p->next != NULL; p = p->next) {
    /* add total length of second chain to all totals of first chain */
    p->tot_len = (u16_t)(p->tot_len + t->tot_len);
  }
  /* { p is last pbuf of first h chain, p->next == NULL } */
  LWIP_ASSERT("p->tot_len == p->len (of last pbuf in chain)", p->tot_len == p->len);
  LWIP_ASSERT("p->next == NULL", p->next == NULL);
  /* add total length of second chain to last pbuf total of first chain */
  p->tot_len = (u16_t)(p->tot_len + t->tot_len);
  /* chain last pbuf of head (p) with first of tail (t) */
  p->next = t;
  /* p->next now references t, but the caller will drop its reference to t,
   * so netto there is no change to the reference count of t.
   */
}

4.7.6 pbuf_ref()

pbuf_ref()函數用於將pbuf中的值加1。

4.7.7 pbuf_chain()

pbuf_chain()函數用於連接兩個pbuf(鏈表)為一個pbuf鏈表。

調用該函數後,後接入的鏈表不能使用pbuf_free()對其進行釋放了。

而且後面接入的pbuf鏈表的首個pbuf節點的ref引用欄位+1,作為兩個數據包的分割點。

/**
 * @ingroup pbuf
 * Chain two pbufs (or pbuf chains) together.
 *
 * The caller MUST call pbuf_free(t) once it has stopped using it.
 * Use pbuf_cat() instead if you no longer use t.
 *
 * @param h head pbuf (chain)
 * @param t tail pbuf (chain)
 * @note The pbufs MUST belong to the same packet.
 * @note MAY NOT be called on a packet queue.
 *
 * The ->tot_len fields of all pbufs of the head chain are adjusted.
 * The ->next field of the last pbuf of the head chain is adjusted.
 * The ->ref field of the first pbuf of the tail chain is adjusted.
 *
 */
void
pbuf_chain(struct pbuf *h, struct pbuf *t)
{
  pbuf_cat(h, t);
  /* t is now referenced by h */
  pbuf_ref(t);
  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_chain: %p references %p\n", (void *)h, (void *)t));
}

4.7.8 更多

pbuf_dechain()用於把pbuf鏈表中的首個pbuf節點進行拖鏈,且,返回新的pbuf鏈表。

參考pbuf.cpbuf.h

4.8 網卡中使用的pbuf

網卡中的回調函數需要根據網卡設備類型來實現。

low_level_output()low_level_input()函數是網卡的南向直接操作函數,是對網卡設備的寫、讀處理。

相當於網卡設備的驅動範疇的函數。

image.png

4.8.1 low_level_output()

low_level_output()函數只是單純的往網卡設備發送數據。

一般把這個函數插入到netif->linkoutput()中,供其網卡調用。給是ARP用來往鏈路層發送數據。

該函數類型如下:

  • 傳入的數據是pbuf形式,該函數的實現需要從pbuf從獲取數據體出來,發送出去。
/** Function prototype for netif->linkoutput functions. Only used for ethernet
 * netifs. This function is called by ARP when a packet shall be sent.
 *
 * @param netif The netif which shall send a packet
 * @param p The packet to send (raw ethernet packet)
 */
typedef err_t (*netif_linkoutput_fn)(struct netif *netif, struct pbuf *p);

4.8.2 low_level_input()

low_level_input()從網卡設備中接收數據。

在lwip中,該函數需要實現從網卡設備中獲取數據,並把數據組裝成為pbuf形式,是MEMP_PBUF_POOL類型的pbuf。

該函數不會直接插入到netif的數據結構中,因為網卡沒有直接調用該函數的主動性,是靠外部收到數據後觸發執行low_level_input()函數獲取數據;

然後再調用netif->input()把數據按要求上交給TCPIP協議棧。