LwIP協議棧(1):簡介與pbuf

概述
  Lwip是瑞典計算機科學院(SICS)的Adam Dunkels 開發的一個小型開源的TCP/IP協議棧。
  LwIP是Light Weight (輕型)IP協議,有無操作系統的支持都可以運行。LwIP實現的重點是在保持TCP協議主要功能的基礎上減少對RAM 的佔用,它只需十幾KB的RAM和40K左右的ROM就可以運行,這使LwIP協議棧適合在低端的嵌入式系統中使用。[1]
  LwIP協議棧主要關注的是怎麼樣減少內存的使用和代碼的大小,這樣就可以讓lwIP適用於資源有限的小型平臺例如嵌入式系統。爲了簡化處理過程和內存要求,lwIP對API進行了裁減,可以不需要複製一些數據。
  LwIP 由幾個模塊組成,除 TCP/IP 協議的實現模塊外( IP, ICMP, UDP, TCP),還有包括許多相關支持模塊。這些支持模塊包括:操作系統模擬層、緩衝與內存管理子系統、網絡接口函數及一組 Internet 校驗和計算函數。

進程模型
  LwIP 則採取將所有協議駐留在同一個進程的方式,以便獨立於操作系統內核之外。應用程序既可以駐留在 LwIP 的進程中,也可以使用一個單獨的進程。應用程序與 TCP/IP 協議棧通訊可以採用兩種方法:一種是函數調用,這適用於應用程序與 LwIP 使用同一個進程的情況;另一種是使用更抽象的 API。
  LwIP 在用戶空間而不是操作系統內核實現,既有優點也有缺點。把 LwIP 作爲一個進程的主要優點是可以輕易的移植到不同的操作系統中。

操作系統模擬層
  爲了方便 LwIP 移植,屬於操作系統的函數調用及數據結構並沒有在代碼中直接使用,而是用操作系統模擬層來代替對這些函數的使用。操作系統模擬層使用統一的接口提供定時器、進程同步及消息傳遞機制等諸如此類的系統服務。原則上,移植 LwIP,只需針對目標操作系統修改模擬層實現即可。

緩衝與內存管理
  通訊系統裏的內存與緩衝管理模塊首要考慮的是如何適應不同大小的內存需求,一個TCP 段可能有幾百個字節,而一個 ICMP 回顯數據卻僅有幾個字節。還有,爲了避免複製,應該儘可能的讓緩衝區中的數據內容駐留在不能被網絡子系統管理的存儲區中,比如應用程序存儲區或者 ROM。
  LWIP 的動態內存管理機制可以有三種:C運行時庫自帶的內存分配策略、動態內存堆(HEAP)分配策略和動態內存池(POOL)分配策略。默認情況下,我們選擇使用 LWIP 自身的動態內存堆分配策略。

  一個典型的 LWIP 應用系統包括這樣的三個進程:首先啓動的是上層應用程序進程,然後是 LWIP 協議棧進程,最後是底層硬件數據包接收發送進程。通常 LWIP協議棧進程是在應用程序中調用 LWIP 協議棧初始化函數來創建的。注意 LWIP 協議棧進程一般具有最高的優先級,以便實時正確的對數據進行響應。

數據包pbuf:
  
  LwIP採用數據結構 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;

  /** pbuf_type as u8_t instead of enum to save space */
  u8_t /*pbuf_type*/ type;

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

  各成員含義上面的註釋已經說得很清楚了。
  關於採用鏈表結構,是因爲實際發送或接收的數據包可能很大,而每個 pbuf 能夠管理的數據可能很少,所以,往往需要多個 pbuf 結構才能完全描述一個數據包。
  另外,最後的 ref 字段表示該 pbuf 被引用的次數。這裏又是一個糾結的地方啊。初始化一個 pbuf 的時候, ref 字段值被設置爲 1,當有其他 pbuf 的 next 指針指向該 pbuf 時,該 pbuf 的 ref 字段值加一。所以,要刪除一個 pbuf 時, ref 的值必須爲 1 才能刪除成功,否則刪除失敗。
  上圖中注意 payload 並沒有指向 ref 字段之後,而是隔了一定的區域。這段區域就是offset 的大小,這段區域用來存儲數據的包頭,如 TCP 包頭, IP 包頭等。當然, offset 也可以是 0。

來看代碼:

/**
 * 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 flag to define 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, *q, *r;
  u16_t offset;
  s32_t rem_len; /* remaining length */
  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F")\n", length));

  /* determine header offset */
  offset = 0;
  switch (layer) { //注意這裏從協議棧上層開始,方便offset從上層往下疊加,因此也沒加 break
  case PBUF_TRANSPORT:
    /* add room for transport (often TCP) layer header */
    offset += PBUF_TRANSPORT_HLEN;
    /* FALLTHROUGH */
  case PBUF_IP:
    /* add room for IP layer header */
    offset += PBUF_IP_HLEN;
    /* FALLTHROUGH */
  case PBUF_LINK:
    /* add room for link layer header */
    offset += PBUF_LINK_HLEN;
    break;
  case PBUF_RAW:
    break;
  default:
    LWIP_ASSERT("pbuf_alloc: bad pbuf layer", 0);
    return NULL;
  }

  switch (type) {
  case PBUF_POOL:
    /* allocate head of pbuf chain into p */
    p = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL); //分配第一個pbuf

    if (p == NULL) {
      return NULL;
    }
    p->type = type;
    p->next = NULL;

    /* make the payload pointer point 'offset' bytes into pbuf data memory */
    p->payload = LWIP_MEM_ALIGN((void *)((u8_t *)p + (SIZEOF_STRUCT_PBUF + offset)));

    /* the total length of the pbuf chain is the requested size */
    p->tot_len = length; //該pbuf及其以後pbuf的負載數據總長度
    /* set the length of the first pbuf in the chain */
    p->len = LWIP_MIN(length, PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)); //負載數據可能大於分配空間長度,也有可能小於,取當前pbuf實際的負載長度

    /* set reference count (needed here in case we fail) */
    p->ref = 1;

    /* now allocate the tail of the pbuf chain */
    //如果一個pbuf不夠的話,接着分配
    /* remember first pbuf for linkage in next iteration */
    r = p;
    /* remaining length to be allocated */
    rem_len = length - p->len;

    /* any remaining pbufs to be allocated? */
    while (rem_len > 0) {
      q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL); //從第二個pbuf開始,不再需要TCP/IP之類的頭,所以沒有offset
      if (q == NULL) {
        /* free chain so far allocated */
        pbuf_free(p);     //注意這裏,如果當前pbuf分配不成功,要把之前分配的所有pbuf都釋放掉
        /* bail out unsuccesfully */
        return NULL;
      }
      q->type = type;
      q->flags = 0;
      q->next = NULL;
      /* make previous pbuf point to this pbuf */
      r->next = q;
      /* set total length of this pbuf and next in chain */
      q->tot_len = (u16_t)rem_len;
      /* this pbuf length is pool size, unless smaller sized tail */
      q->len = LWIP_MIN((u16_t)rem_len, PBUF_POOL_BUFSIZE_ALIGNED);
      q->payload = (void *)((u8_t *)q + SIZEOF_STRUCT_PBUF);

      q->ref = 1;
      /* calculate remaining length to be allocated */
      rem_len -= q->len;
      /* remember this pbuf for linkage in next iteration */
      r = q;
    }
    /* end of chain */
    /*r->next = NULL;*/

    break;
  case PBUF_RAM:
    /* If pbuf is to be allocated in RAM, allocate memory for it. */
    p = (struct pbuf*)mem_malloc(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF + offset) + LWIP_MEM_ALIGN_SIZE(length));
    if (p == NULL) {
      return NULL;
    }
    /* Set up internal structure of the pbuf. */
    p->payload = LWIP_MEM_ALIGN((void *)((u8_t *)p + SIZEOF_STRUCT_PBUF + offset));
    p->len = p->tot_len = length;
    p->next = NULL;
    p->type = type;
    break;
  /* pbuf references existing (non-volatile static constant) ROM payload? */
  case PBUF_ROM:
  /* pbuf references existing (externally allocated) RAM payload? */
  case PBUF_REF:
    /* only allocate memory for the pbuf structure */
    p = (struct pbuf *)memp_malloc(MEMP_PBUF);
    if (p == NULL) {
      return NULL;
    }
    /* caller must set this field properly, afterwards */
    p->payload = NULL;
    p->len = p->tot_len = length;
    p->next = NULL;
    p->type = type;
    break;
  default:
    return NULL;
  }
  /* set reference count */
  p->ref = 1;
  /* set flags */
  p->flags = 0;
  return p;
}



/**
 * 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)
{
  u16_t type;
  struct pbuf *q;
  u8_t count;

  if (p == NULL) {
    return 0;
  }

  count = 0;
  /* de-allocate all consecutive pbufs from the head of the chain that
   * obtain a zero reference count after decrementing*/
  while (p != NULL) {
    u16_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;

      type = p->type;

      /* is this a pbuf from the pool? */
      if (type == PBUF_POOL) {
        memp_free(MEMP_PBUF_POOL, p);
      /* is this a ROM or RAM referencing pbuf? */
      } else if (type == PBUF_ROM || type == PBUF_REF) {
        memp_free(MEMP_PBUF, p);
      /* type == PBUF_RAM */
      } else {
        mem_free(p);
      }

      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 {
      /* stop walking through the chain */
      p = NULL;
    }
  }
  /* return number of de-allocated pbufs */
  return count;
}


/**
 *
 * 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, struct pbuf *p_from)
{
  u16_t offset_to=0, offset_from=0, len;

  /* is the target big enough to hold the source? */
  LWIP_ERROR("pbuf_copy: target not big enough to hold source", ((p_to != NULL) &&
             (p_from != NULL) && (p_to->tot_len >= p_from->tot_len)), return ERR_ARG;);

  /* iterate through pbuf chain */
  do
  {
    LWIP_ASSERT("p_to != NULL", p_to != NULL);

    /* copy one part of the original chain */
    if ((p_to->len - offset_to) >= (p_from->len - offset_from)) { //每次拷貝的長度是源端和目標端當前pbuf所剩空間的較小值,offset爲當前pbuf拷貝數據的偏移量
      /* complete current p_from fits into current p_to */
      len = p_from->len - offset_from;
    } else {
      /* current p_from does not fit into current p_to */
      len = p_to->len - offset_to;
    }

    MEMCPY((u8_t*)p_to->payload + offset_to, (u8_t*)p_from->payload + offset_from, len);
    offset_to += len;
    offset_from += len;

    if (offset_to == p_to->len) { //目標端當前pbuf空間已滿,轉向下一個pbuf,記得offset清零
      /* on to next p_to (if any) */
      offset_to = 0;
      p_to = p_to->next;
    }

    if (offset_from >= p_from->len) { //源端當前pbuf數據已拷貝完,轉向下一個pbuf,記得offset清零
      /* on to next p_from (if any) */
      offset_from = 0;
      p_from = p_from->next;
    }

    if((p_from != NULL) && (p_from->len == p_from->tot_len)) {
      /* don't copy more than one packet! */
      LWIP_ERROR("pbuf_copy() does not allow packet queues!\n",
                 (p_from->next == NULL), return ERR_VAL;);
    }
    if((p_to != NULL) && (p_to->len == p_to->tot_len)) {
      /* don't copy more than one packet! */
      LWIP_ERROR("pbuf_copy() does not allow packet queues!\n",
                  (p_to->next == NULL), return ERR_VAL;);
    }
  } while (p_from);

  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_copy: end of chain reached.\n"));
  return ERR_OK;
}

  可以看到,回收 pbuf 使用pbuf_free()函數,該函數首先要減少 pbuf 索引計數(reference count)。如果引用計數已經減爲 0,這個 pbuf 被回收。對於一個pbuf鏈來說,只有前一個pbuf被回收,纔會考慮回收後面的pbuf,如果前面pbuf計數還不爲0,則直接返回。

發佈了89 篇原創文章 · 獲贊 9 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章