lwIP TCP/IP 協議棧筆記之七: 網絡數據包

目錄

1. TCP/IP 協議分層

2. lwIP的線程模型

3. pbuf 結構體說明

4. pbuf 的類型

4.1 PBUF_RAM類型

4.2 PBUF_POOL 類型

4.3 PBUF_ROM和PBUF_REF 類型

5. pbuf_alloc()

6. pbuf_free()

7. 其它pbuf 操作函數

7.1 pbuf_realloc()

7.2 pbuf_header()

7.3 pbuf_take()

8. 網卡中使用的pbuf


TCP/IP 是一種數據通信機制,因此,協議棧的實現本質上就是對數據包進行處理,爲了實現高效的效率,LwIP 數據包管理要提供一種高效處理的機制。協議棧各層能對數據包進行靈活的處理,同時減少數據在各層間傳遞時的時間與空間開銷,這是提高協議棧工作效率的關鍵點。在BSD 的實現中,一個描述數據包的結構體叫做mbuf,同樣的 在 LwIP中,也有個類似的結構,稱之爲 pbuf,本章所有知識點將圍繞 pbuf 而展開。

1. TCP/IP 協議分層

在標準的TCP/IP 協議棧中,各層之間都是一個獨立的模塊,它有着很清晰的層次結構,每一層只負責完成該層的處理,不會越界到其他層次去讀寫數據。

LwIP 只是一個輕量級TCP/IP 協議棧,它只是一個較完整的TCP/IP 協議,多應用在嵌入式領域中,由於處理器的性能有限,LwIP 並沒有採用很明確的分層結構,它假設各層之間的部分數據和結構體和實現原理在其他層是可見的,簡單來說就傳輸層知道IP 層是如何封裝數據、傳遞數據的,IP 層知道鏈路層是怎麼封裝數據的等等。

爲什麼要模糊分層的處理?簡單來說就是爲了提高效率,例如鏈路層完成數據包在物理線路上傳輸的封裝;IP 層完成數據包的選擇和路由,負責將數據包發送到目標主機;傳輸層負責根據IP 地址將數據包傳輸到指定主機,端口號識別一臺主機的線程,向不同的應用層遞交數據;但是,如果按照標準的TCP/IP 協議棧這種嚴格的分層思想,在數據傳輸的時候就需要層層拷貝,因爲各層之間的內存都不是共用的,在鏈路層遞交到IP 層需要拷貝,在IP 層遞交到傳輸層需要拷貝,反之亦然,這樣子每當收到或者發送一個數據的時候都要CPU 去拷貝數據,這個效率就太慢了。

所以LwIP 假設各層之間的資源都是共用的,各層之間的實現方式也是已知的,那麼在IP 層往傳輸層遞交數據的時候,鏈路層往IP 層遞交數據的時候就無需再次拷貝,直接操作協議棧中屬於其他層次的字段,得到相應的信息,然後直接讀取傳遞的數據即可,這樣處理的方式就無需拷貝,各個層次之間存在交叉存取數據的現象,既節省系統的空間也節省處理的時間,而且更加靈活。

在小型嵌入式設備中,LwIP 與用戶程序之間通常沒有太嚴格的分層結構,這種方式允許用戶處理數據與內核之間變得更加寬鬆。LwIP 假設用戶完全瞭解協議棧內部的數據處理機制,用戶程序可以直接訪問協議棧內部各層的數據包,可以讓協議棧與用戶使用同樣的內存區域,允許用戶直接對這片區域進行讀寫操作,這樣子就很好地避免了拷貝的現象,當然這樣子的做法也有缺陷,取決於用戶對協議棧處理過程的瞭解程度,因爲數據是公共的,如果處理不正確那就讓協議棧也沒法正常工作。

當然,除了標準的TCP/IP 協議,還存在很多其他的TCP/IP 協議,即使這些協議棧內部存在着模糊分層、交叉存取現象,但是對協議棧外部的應用層則保持着明顯的分層結構,在操作系統中,TCP/IP 協議棧往往被設計爲內核代碼的一部分,用戶可以的函數僅僅是協議棧爲用戶提供的那些,或者直接完全封裝起來,用戶的操作類似於讀寫文件的方式進行(如BSD Socket),這樣子用戶就無法避免數據的拷貝,在數據發送的時候,用戶數據必須從用戶區域拷貝到協議棧內部,在數據接收的時候,協議棧內部數據也將被拷貝到用戶區域。

簡而言之,對於資源有限的嵌入式開發,平衡軟件設計層次之間的耦合度和資源消耗。

2. lwIP的線程模型

線程模型可以理解爲協議棧的實現被劃分在多個線程之中,如讓協議棧的各個層次都獨立成爲一個線程,在這種模式下,各個層次都有嚴格分層結構,各個層次的提供的API接口也是分層清晰的,這樣固然使得編程更加簡便、高效、靈活。但對於嵌入式設備,嚴格的分層,勢必引起更多數據的傳輸、拷貝及線程切換,這是很大的開銷。如從鏈路層到應用層,數據要經歷數次的拷貝和線程切換,這樣使得協議棧的效率低下。

其次,協議棧與操作系統融合,成爲操作系統的一部分,這樣子用戶線程與協議棧內核之間都是通過操作系統提供的函數來實現的,這種情況讓協議棧各層之間與用戶線程就沒有很嚴格的分層結構,各層之間能交叉存取,從而提高效率。

LwIP 採用了另一種方式,讓協議棧內核與操作系統相互隔離,協議棧僅僅作爲操作系統的一個獨立線程存在,用戶程序能駐留在協議棧內部,協議棧通過回調函數實現用戶與協議棧之間的數據交互;也可讓用戶程序單獨實現一個線程,與協議棧使用系統的信號量和郵箱等 IPC 通信機制聯繫起來,進行數據的交互。

當使用第一種通過回調函數進行交互情況的時候,也就是我們所說的RAW API 編程。當通過操作系統IPC通信機制的時候,就是另外兩種API 編程,即NETCONN API 和Socket API。當然這樣子既有優點也有缺點,優點就是能在任何的操作系統中移植,缺點就是受到操作系統的影響。

因爲即使LwIP 作爲一個獨立的線程,也是需要藉助操作系統進行調度的,因此,協議棧的響應的實時性會有一定影響,並且建議設置LwIP 線程的優先級爲最高優先級。

3. pbuf 結構體說明

pbuf 就是一個描述協議棧中數據包的數據結構,LwIP 中在pbuf.c 和pubf.h 實現了協議棧數據包管理的所有函數與數據結構。

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

next 是一個pbuf 類型的指針,指向下一個pbuf,因爲網絡中的數據包可能很大,單個數據包被分割成幾個pbuf來記錄數據,並以單鏈表的形式連接起來,被稱爲pbuf 鏈表。

payload 是一個指向數據區域的指針,指向該pbuf 管理的數據區域起始地址,這裏的數據區域可以是緊跟在pbuf 結構體地址後面的RAM空間,也可以是ROM中的某個地址上,取決於pbuf 的類型。

tot_len 中記錄的是當前pbuf 及其後續pbuf 所有數據的長度,例如如果當前pbuf 是pbuf 鏈表上第一個數據結構,那麼tot_len 就記錄着整個pbuf 鏈表中所有pbuf 中數據的長度;如果當前pbuf 是鏈表上最後一個數據結構,那就記錄着當前pbuf 的
長度。

len 表示當前pbuf 中有效的數據長度

type_internal 表示pbuf 的類型,LwIP 中有4 種pbuf 的類型,並且使用了一個枚舉類型的數據結構定義,見4.

flags 字段在初始化的時候一般被初始化爲0

ref 表示該pbuf 被引用的次數, 引用計數總是等於引用此pbuf的指針數。 這可以是來自應用程序,堆棧本身或pbuf->鏈中的下一個指針的指針。初始化一個pbuf 的時候,ref 會被設置爲1,因爲該pbuf 的地址一點會被返回一個指針變量,當有其他指針指向pbuf 的時候,就必須調用相關函數將ref 字段加1。

if_idx 用於記錄傳入的數據包中輸入netif 的索引,也就是netif 中num 字段。

4. pbuf 的類型

pbuf 的類型有4 種,分別爲PBUF_RAM、PBUF_POOL 、PBUF_ROM、PBUF_REF

/**
 * @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.1 PBUF_RAM類型

PBUF_RAM類型的pbuf 空間是通過內存堆分配而來的,一般協議棧中要發送的數據都是採用這種形式,這種類型的pbuf 在協議棧中使用得最多,在申請這種pbuf 內存塊的時候,協議棧會在管理的內存堆中根據需要的大小進行分配對應的內存空間,這種pbuf 內存塊包含數據空間以及pbuf 數據結構區域,在連續的RAM內存空間中。

內核申請這類型的pbuf 時,也算上了協議首部的空間,當然是根據協議棧不同層次需要的首部進行申請,LwIP 也使用一個枚舉類型對不同的協議棧分層需要的首部大小進行定義。

PBUF_RAM類型的pbuf 示意圖具體見下圖 ,圖中可以看出整個pbuf 就是一個連續的內存區域,layer(offset)就是各層協議的首部,如TCP 報文首部、IP 首部、以太網幀首部等,預留出來的這些空間是爲了在各個協議層中靈活地處理這些數據,當然layer 的大小也可以是0,具體是多少就與數據包的申請方式有關。

4.2 PBUF_POOL 類型

PBUF_POOL 類型的pbuf 與PBUF_RAM類型的pbuf 都是差不多的,其pbuf 結構體與數據緩衝區也是存在於連續的內存塊中,但它的空間是通過內存池分配的,這種類型的pbuf 可以在極短的時間內分配得到,因爲這是內存池分配策略的優勢,在網卡接收數據的時候,LwIP 一般就使用這種類型的pbuf 來存儲接收到的數據,申請PBUF_POOL 類型時,協議棧會在內存池中分配適當的內存池個數以滿足需要的數據區域大小。

除此之外,在系統進行內存池初始化的時候,還需初始化兩個與pbuf 相關的內存池,分別爲MEMP_PBUF、MEMP_ PBUF_POOL。

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 內存池是專門用於存放pbuf 數據結構的內存池,主要用於PBUF_ROM、PBUF_REF 類型的pbuf,其大小爲sizeof(struct pbuf),內存塊的數量爲MEMP_NUM_PBUF;

MEMP_PBUF_POOL 則包含pbuf 結構與數據區域,也就是PBUF_POOL 類型的pbuf,內存塊的大小爲PBUF_POOL_BUFSIZE,其值由用戶自己定義,默認爲590(536+40+0+14)字節,當然也可以由我們定義TCP_MSS 的大小改變該宏定義,我們將宏定義TCP_MSS 的值定義爲1460,這樣子我們PBUF_POOL 類型的pbuf 的內存池大小爲1514(1460+40+0+14),內存塊的個數爲PBUF_POOL_SIZE。

如果按照默認的內存大小,對於有些很大的以太網數據包,可能就需要多個pbuf 才能將這些數據存放下來,這就需要申請多個pbuf,因爲是PBUF_POOL 類型的pbuf,所以申請內存空間只需要調用memp_malloc()函數進行申請即可。然後再將這些pbuf 通過鏈表的形式連接起組成pbuf 鏈表上,以保證用戶的空間需求,分配與連接成功的pbuf 示意圖。

4.3 PBUF_ROM和PBUF_REF 類型

PBUF_ROM和PBUF_REF 類型的pbuf 基本是一樣的,它們在內存池申請的pbuf 不包含數據區域,只包含pbuf 結構體,即MEMP_PBUF 類型的POOL,這也是PBUF_ROM和PBUF_REF 與前面兩種類型的pbuf 最大的差別。

PBUF_ROM類型的pbuf 的數據區域存儲在ROM中,是一段靜態數據,而PBUF_REF 類型的pbuf 的數據區域存儲在RAM空間中。申請這兩種類型的pbuf 時候也是只需要調用memp_malloc()函數從內存池中申請即可,申請內存的大小就是MEMP_PBUF,它只是一個pbuf 結構體大小,正確分配到的pbuf 內存塊示意圖。

 注意:對於一個數據包,它可能會使用任意類型的pbuf 進行描述,也可能使用多種不同的pbuf 一起描述,如下圖 所示,就是採用多種pbuf 描述一個數據包,但是無論怎麼樣描述,數據包的處理都是不變的,payload 指向的始終是數據區域,採用鏈表的形式連接起來的數據包,其tot_len 字段永遠是記錄當前及其後續pbuf 的總大小。

5. pbuf_alloc()

數據包申請函數pbuf_alloc()在系統中的許多地方都會用到,例如在網卡接收數據時,需要申請一個數據包,然後將網卡中的數據填入數據包中;在發送數據的時候,協議棧會申請一個pbuf 數據包,並將即將發送的數據裝入到pbuf 中的數據區域,同時相關的協議首部信息也會被填入到pbuf 中的layer 區域內,所以pbuf 數據包的申請函數幾乎無處不在,存在協議棧於各層之中,當然,在不同層的協議中,layer 字段的大小是不一樣的,因爲不一樣的協議其首部大小是不同的。協議棧中各層首部的大小都會被預留出來,LwIP 採用枚舉類型的變量將各個層的首部大小記錄下來,在申請的時候就把layer 需要空間的大小根據協議進行分配。

 

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

/**
 * @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().
   */
  /* 傳輸層協議首部內存空間,如UDP、TCP 報文協議首部 */
  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 的類型和數據包在哪一層被申請。layer 值,當數據包申請時,所處的層次不同,就會導致預留空間的的layer 值不同。

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_ROM與PBUF_REF 類
         型的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) {
          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 結構體的成員變量 */
        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 */
          /* 將這些pbuf 連接成pbuf 鏈表。 */
          last->next = q;
        }
        last = q;
        rem_len = (u16_t)(rem_len - qlen);  // 計算存下所有數據需要的長度
        offset = 0;
      } while (rem_len > 0);                // 繼續分配內存塊,直到將所有的數據裝下爲止
      break;
    }
    case PBUF_RAM: {
      u16_t payload_len = (u16_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_alloc()函數的思路很清晰,根據傳入的pbuf 類型及協議層次layer,去申請對應的pbuf,就能預留出對應的協議首部空間,對於PBUF_ROM與PBUF_REF 類型的pbuf,內核不會申請數據區域,因此,pbuf 結構體中payload 指針就需要用戶自己去設置,我們通常在申請PBUF_ROM與PBUF_REF 類型的pbuf 成功後,緊接着就將payload 指針指向某個數據區域。

/* 例子 */
p = pbuf_alloc(PBUF_TRANSPORT, 1472, PBUF_RAM);

6. pbuf_free()

數據包pbuf 的釋放是必須的,因爲當內核處理完數據就要將這些資源進行回收,否則就會造成內存泄漏,在後續的數據處理中無法再次申請內存。當底層將數據發送出去後或者當應用層將數據處理完畢的時候,數據包就要被釋放掉。

釋放數據包有條件,pbuf 中ref 字段就是記錄pbuf 數據包被引用的次數,在申請pbuf 的時候,ref 字段就被初始化爲1,當釋放pbuf 的時候,先將ref減1,如果ref 減1 後爲0,則表示能釋放pbuf 數據包,此外,能被內核釋放的pbuf 數據包只能是首節點或者其他地方未被引用過的節點,如果用戶錯誤地調用pbuf 釋放函數,將pbuf 鏈表中的某個中間節點刪除了,那麼必然會導致錯誤。

一個數據包可能會使用鏈表的形式將多個pbuf 連接起來,那麼假如刪除一個首節點,怎麼保證刪除完屬於一個數據包的數據呢?LwIP 的數據包釋放函數會自動刪除屬於一個數據包中連同首節點在內所有pbuf。

舉個例子,假設一個數據包需要3 個pbuf 連接起來,那麼在刪除第一個pbuf 的時候,內核會檢測一下它下一個pbuf釋放與首節點是否存儲同一個數據包的數據,如果是那就將第二個節點也刪除掉,同理第三個也會被刪除。但如果刪除某個pbuf 鏈表的首節點時,鏈表中第二個節點的pbuf 中ref字段不爲0,則表示該節點還在其他地方被引用,那麼第二個節點不與第一個節點存儲同一個數據包,那麼就不會刪除第二個節點。

下面用示意圖來解釋一下刪除的過程,假設有4 個pbuf 鏈表,鏈表中每個pbuf 的ref都有一個值,當調用pbuf_free()刪除第一個節點的時候,剩下的pbuf 變化情況,具體見下圖:

7. 其它pbuf 操作函數

7.1 pbuf_realloc()

pbuf_realloc()函數在相應pbuf(鏈表)尾部釋放一定的空間,將數據包pbuf 中的數據長度減少爲某個長度值。對於PBUF_RAM類型的pbuf,函數將調用內存堆管理中介紹到的mem_realloc()函數,釋放這些多餘的空間。對於其他三種類型的pbuf,該函數只是修改pbuf 中的長度字段值,並不釋放對應的內存池空間。

7.2 pbuf_header()

pbuf_header()函數用於調整pbuf 的payload 指針(向前或向後移動一定字節數) 。

函數使payload 指針指向數據區前的首部字段,這就爲各層對數據包首部的操作提供了方便。當然,進行這個操作的時候,len 和tot_len 字段值也會隨之更新

7.3 pbuf_take()

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

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

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

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

8. 網卡中使用的pbuf

  • low_level_output()

  • low_level_input()

  • ethernetif_input()

https://blog.csdn.net/XieWinter/article/details/99544178#4.4%20low_level_output()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章