程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 內存管理pbuf.c源碼解析——LwIP學習,pbuf.clwip

內存管理pbuf.c源碼解析——LwIP學習,pbuf.clwip

編輯:C++入門知識

內存管理pbuf.c源碼解析——LwIP學習,pbuf.clwip


聲明:個人所寫所有博客均為自己在學習中的記錄與感想,或為在學習中總結他人學習成果,但因本人才疏學淺,如果大家在閱讀過程中發現錯誤,歡迎大家指正。

本文自己尚有認為寫的不完整的地方,源代碼沒有完全理清,以後會不定期更新

  上一篇分析了pbuf.h頭文件,這次來分析LwIP的內核(core文件夾)中的pbuf.c源代碼。本人使用的LwIP源代碼為Lwip-1.4.1版本。
  pbuf.h文件分析地址:http://blog.csdn.net/angel_94/article/details/50111163

一、關於pbuf.c

  LwIP的內核(core文件夾)文件中pbuf.c是包含協議棧內核使用的數據包管理函數,用於協議棧層次間的數據傳遞,避免數據拷貝。我們來分析pbuf.c源代碼。

其中主要包含的函數:

  pbuf_alloc():內存申請函數
  pbuf_realloc():調整收縮pbuf的大小,在相應pbuf(鏈表)尾部釋放一定的空間,將數據包pbuf中的數據長度減少為某個長度值
  pbuf_header():調整payload指針和長度字段以便為pbuf中的數據預置包頭,常用於實現對pbuf預留孔間的操作
  pbuf_free():數據包釋放函數
  pbuf_ref():用於將pbuf中的ref加1
  pbuf_chain():用於連接pbufs,連接兩個pbuf(鏈表)為一個pbuf鏈表
  pbuf_dechain():用於連接pbufs
  pbuf_copy():用於將一個任何類型的pbuf中的數據拷貝到一個PBUf_RAM類型的pbuf中
  pbuf_take():用於向pbuf的數據區域拷貝數據

二、內存申請函數 pbuf_alloc()

1、關於pbuf

  內存申請函數是這個文件中最重要的函數
  pbuf是LwIP信息包的內部表示,為最小限度協議棧的特殊需求而設計。pbufs與BSD實現中使用的mbufs相似。pbuf結構即支持動態內存分配保存信息包內容,也支持讓信息包數據駐留在靜態存儲區。pbufs可以在一個鏈表中鏈接在一起,被稱為一個pbuf鏈,這樣一個信息包可以穿越幾個pbufs。
  pbufs有四種類型:PBUF_RAM、PBUF_ROM、PBUF_REF、PBUF_POOL。
  程序經常使用LWIP_DEBUGF()函數:LWIP_DEBUGF()是LwIP協議棧的調試信息輸出函數
  1.可以查看函數的調用關系,跟蹤程序流程
  2.查看各種協議的調試信息,關鍵變量的值

2、pbuf_alloc()函數

/**
 * Allocates a pbuf of the given type (possibly a chain for PBUF_POOL type).
 *分配一個給定類型(可能是一個PBUF_POOL型的鏈)的pbuf
 *
 * 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).
 *實際內存分配的pbuf是由分配的pbuf的層次和請求的大小決定的
 *
 * @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_RAM:為pbuf緩沖存儲器分配一大塊。這也包括協議頭
 * 
 * - 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
 *沒有緩沖內存分配給PBUF,即使是協議頭。附加協議頭必須預先在到ROM中的pbuf之前分配另一個pbuf和鏈表考慮
 *假定內存使用的內存和ROM是非常相似的,它是不可改變的而且不會改變。
 *內存是動態的,一般不應附著到PBUF_ROM中的pbuf。使用PBUF_REF相反。
 *
 * - 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,即使是協議頭。假定這個pbuf只被使用在單個線程中。
 *如果pbuf被排隊,然後pbuf_take應該叫做復制緩沖區
 *
 * - PBUF_POOL: the pbuf is allocated as a pbuf chain, with pbufs from
 *              the pbuf pool that is allocated during pbuf_init().
 *PBUF_POOL:所述的pbuf被分配為pbuf鏈,與來自pbuf池的pbufs在pbuf_init()即內存初始化函數中被分配
 *
 *
 * @return the allocated pbuf. If multiple pbufs where allocated, this
 * is the first pbuf of a pbuf chain.
 * 返回被分配的pbuf。如果多個pbuf被分配,這是pbuf鏈中的第一個pbuf
 */
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 */
seitch(layer) { /* pbuf的層次 */
case PBUF_TRANSPORT:
    /* add room for transport (often TCP) layer header */
    /* 如果為傳輸層申請pbuf,那麼有效數據的偏移位置為PBUF_LINK_HLEN +PBUF_IP_HLEN +PBUF_TRANSPPORT_HLEN */
    offset  = PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPPORT_HLEN;
    break;
case PBUF_IP:
    /* add room for IP layer header */
    /* 如果為ip層申請pbuf,那麼有效數據的偏移位置為PBUF_ip_HLEN + PBUF_LINK_HLEN */
    offset= PBUF_LINK_HLEN + PBUF_IP_HLEN;
case PBUF_LINK:
    /* add room for link layer header */
    /* 如果是鏈路層申請pbuf內存,那麼有效數據的偏移位置為PBUF_LINK_HLEN */
    offset = PBUF_LINK_HLEN;
    break;
case PBUF_RAM:
    /*如果為原始層申請pbuf內存,那麼數據偏移位置就是0,不預留任何空間*/
    offset= 0;
    break;
default:
    LWIP_ASSERT("pbuf_alloc:bad pbuf layer", 0);
    return NULL;
    }

 /********************************************************/
switch (type){ /* pbuf的類型 */
    case PBUF_POOL: /*PBUF_POOL分配一個鏈表,鏈表上每個元素所管理的內存最大不超過PBUF_POOL_BUFSIZE*/
        /* allocate head of pbuf chain into p */
        p = (struct pbuf *)memep_malloc(MEMP_PBUF_POOL);/* 為PBUF_POOL類型時 */
        /*PBUF_POOL類型和PBUF_RAM類型的都由內存池分配得到,此處調用了memp_malloc,在memp.c文件中 */
        /* 分配的內存池類型為MEMP_PBUF_POOL*/
        LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc: allocated pbuf %p\n"), (void *)p)); /* p是pbuf類型的指針*/
        /*LWIP_DEBUGF是LwIP協議棧的調試信息輸出函數 */
        /*1.可以查看函數的調用關系,跟蹤程序流程。*/
        /*2.查看各種協議的調試信息,關鍵變量的值*/
        if(p == NULL){
            PBUF_POOL_IS_EMPTY();
            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)));
        /* payload指向pbuf管理的數據的起始地址,payload指向的起始地址即為offset。*/
        /*以offset為基准,登記有效數據存儲的起始偏移位置到p->payload[luther.gliethttp] */
        LWIP_ASSERT("pbuf_alloc: pbuf p->payload properly aligned",
            ((mem_ptr_t)p->payload % MEM_ALIGNMENT) == 0);
    /* the total length of the pbuf chain is the requested size */
    p->tot_len = length; /* 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鏈表上的每個元素所能存儲的最大數據為PBUF_POOL_BUFSIZE。如果超過,如果超過該值,那麼就會使用鏈表方式。*/
    /*鏈接其他很多個pbuf,直到申請的size數據全部能夠正常存儲為止[luther.gliethttp]*/

    LWIP_ASSERT("check p->payload + p->len does not overflow pbuf",
                ((u8_t*)p->payload + p->len <=
                 (u8_t*)p + SIZEOF_STRUCT_PBUF + PBUF_POOL_BUFSIZE_ALIGNED));
    LWIP_ASSERT("PBUF_POOL_BUFSIZE must be bigger than MEM_ALIGNMENT",
      (PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)) > 0 );
    /* set reference count (needed here in case we fail) */
    p->ref = 1; /* ref表示當前pbuf被引用的次數 */

    /* now allocate the tail of the pbuf chain */

    /* 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);
      if (q == NULL) {
        PBUF_POOL_IS_EMPTY();
        /* free chain so far allocated */
        pbuf_free(p);
        /* 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 */
      LWIP_ASSERT("rem_len < max_u16_t", rem_len < 0xffff);
      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);
      LWIP_ASSERT("pbuf_alloc: pbuf q->payload properly aligned",
              ((mem_ptr_t)q->payload % MEM_ALIGNMENT) == 0);
      LWIP_ASSERT("check p->payload + p->len does not overflow pbuf",
                  ((u8_t*)p->payload + p->len <=
                   (u8_t*)p + SIZEOF_STRUCT_PBUF + PBUF_POOL_BUFSIZE_ALIGNED));
      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: /*PBUF類型內存,一次性分配size大小的連續內存*/
    /* 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));
    /*PBUF_RAM由內存堆分配內存,此處調用了mem_malloc(),為內存堆分配函數,在mem.c文件中。*/
    /*分配的空間大小包括:pbuf結構頭大小size_struct_pbuf,需要的數據存儲空間大小length。*/
    /*還有一個offset,即有效數據起始偏移位置 */
    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;

    LWIP_ASSERT("pbuf_alloc: pbuf->payload properly aligned",
           ((mem_ptr_t)p->payload % MEM_ALIGNMENT) == 0);
    break;

  /********************************************************/
  /* pbuf references existing (non-volatile static constant) ROM payload? */
  case PBUF_ROM: /*ROM只需要分配小小的管理pbuf的控制管理內存*/
  /* pbuf references existing (externally allocated) RAM payload? */
  case PBUF_REF:
    /* only allocate memory for the pbuf structure */
    /*只需申請pbufs頭控制結構體所需內存即可[luther.gliethttp]*/
    p = (struct pbuf *)memp_malloc(MEMP_PBUF);
    /*分配的內存池類型為MEMP_PBUF,大小恰為一個pbuf頭的大小*/
    if (p == NULL) {
      LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
                  ("pbuf_alloc: Could not allocate MEMP_PBUF for PBUF_%s.\n",
                  (type == PBUF_ROM) ? "ROM" : "REF"));
      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:
    LWIP_ASSERT("pbuf_alloc: erroneous type", 0);
    return NULL;
  }
  /* set reference count */
  p->ref = 1;
  /* set flags */
  p->flags = 0;
  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F") == %p\n", length, (void *)p));
  return p;
}

三、連接pbufs

連接pbufs主要使用了三個函數:pbuf_cat()、pbuf_ref()、pbuf_chain()。

1、pbuf_ref():將 pbuf中的 ref 加1

/**
 * Increment the reference count of the pbuf
 * 增量的pbuf的引用次數
 * @param p pbuf to increase reference counter of
 * 參數p的pbuf引用計數增加
 * /
void
pbuf_ref(struct pbuf *p)
{
    SYS_ARCH_DECL_PROTECT(old_level);
    /* pbuf given? */
    if(p != NULL) {
    SYS_ARCH_PROTECT(old_level);
    ++(p->ref); /*當pbuf不為空時將當前pbuf的ref加1*/
    SYS_ARCH_UNPROTECT(old_level);
    }
}

  ref是pbuf結構中定義的一個16位無符號整數,包含一個引用計數,表示該pbuf被引用的次數,初始化一個pbuf的時候,ref字段值被設置為1,當有其他pbuf的next值針指向該pbuf時,該pbuf的字段值加1,所以要刪除一個pbuf時,ref的值必須為1才能刪除成功,否則刪除失敗。 統計有多少個指針指向這個pbuf。這些指針可能是應用程序的指針,協議棧自己的指針或者數據鏈中的pbuf->next指針,ref為0時,才可以釋放pbuf

2、pbuf_cat()

/**
 * Concatenate two pbufs (each may be a pbuf chain) and take over
 * the caller's reference of the tail pbuf.
 * 連接兩個pbufs(每個可能都是鏈表)並替代引用的呼叫方的尾部pbuf
 * @note The caller MAY NOT reference the tail pbuf afterwards.
 * 注意對方可能之後不提及尾部的pbuf
 * Use pbuf_chain() for that purpose.
 * 目的是使用pbuf_chain(),在pbuf_chain()之中使用這個函數來連接兩個pbufs
 * @see pbuf_chain() 
 */
void
pbuf_cat(struct pbuf *h, struct pbuf *t)
{
    struct pbuf *p; /* 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){
        /*使 p = h,當p的next指針(即h的next指針)不是空的時候,即後面還有pbuf*/
        /* add total length of second chain to all totals of first chain */
        /*第二個鏈的總長度加到第一個鏈上的總和*/
        p->tot_len += t->tot_len;
    }
    /* { p is last pbuf of first h chain, p->next == NULL } */
    /* p是第一個 h鏈的末尾pbuf,p->next為空,也就是p的後面再也沒有pbuf*/
    LWIP_ASSERT("p->tot_len == p->len (of last pbuf in chain)", p->tot_len == p->len); 
    /* 我還沒找到LWIP_ASSERT()是什麼,感覺是在聲明注釋說的意思。有知道的請告訴我 */
    LWIP_ASSERT("p->next == NULL", p->next == NULL);
    /* add total length of second chain to last pbuf total of first chain */
    p->tot_len += t->tot_len;
    /* chain last pbuf of head (p) with first of tail (t) */
    /* 連接後一個pbuf的頭(即 p的頭)和第一個的尾部(即 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.
    * p的next指針現在引用t,但是呼叫方將放棄引用t。所以netto沒有改變t的引用計數
    */
}

3、pbuf_chain()

/**
 * Chain two pbufs (or pbuf chains) together.
 * 連接兩個pbufs(或者pbufs鏈表)在一起
 * The caller MUST call pbuf_free(t) once it has stopped
 * using it. Use pbuf_cat() instead if you no longer use t.
 * 呼叫方必須調用pbuf_free(t)一旦終止使用它。使用pbuf_cat()而不是你不在使用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); //將 h和 t進行連接
  /* t is now referenced by h    t現在被h引用*/
  pbuf_ref(t); /* 將t的ref加1,也就是t被引用了一次*/
  LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_chain: %p references %p\n", (void *)h, (void *)t));
}

4、pbuf的釋放函數pbuf_free()

  pbuf的申請主要是通過兩種方式實現的,即內存池分配與內存堆分配而得到的,所以pbuf的釋放也要按照兩種方式進行。
  在刪除一個pbuf結構之前,首先要檢查這個pbuf是那種類型的,根據類型的不同,使用不同的內存釋放函數進程刪除,即內存堆釋放函數或者內存池釋放函數。PBUF_POOL、PBUF_ROM、PBUF_REF是由內存池分配的,需要調用memp_free()函數進行刪除,而PBUF_RAM是由內存堆分配的,需要調用mem_free()函數進行刪除。

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

  LWIP_ASSERT("pbuf_free: sane type",
    p->type == PBUF_RAM || p->type == PBUF_ROM ||
    p->type == PBUF_REF || p->type == PBUF_POOL);

  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;
      LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: deallocating %p\n", (void *)p));
      type = p->type;
#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 (type == PBUF_POOL) {
          /*PBUF_POOL是MEMP_PBUF_POOL類型的,由內存池分配,調用memp_free()*/
          memp_free(MEMP_PBUF_POOL, p);
        /* is this a ROM or RAM referencing pbuf? */
        } else if (type == PBUF_ROM || type == PBUF_REF) {
          /*PBUF_ROM和PBUF_REF是MEMP_PBUF類型的,由內存池分配,調用memp_free()*/
          memp_free(MEMP_PBUF, p);
        /* type == PBUF_RAM */
        } else {
          /*PBUF_RAM是由內存堆分配的,調用mem_free進行釋放*/
          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 {
      LWIP_DEBUGF( PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: %p has ref %"U16_F", ending here.\n", (void *)p, ref));
      /* stop walking through the chain */
      p = NULL;
    }
  }
  PERF_STOP("pbuf_free");
  /* return number of de-allocated pbufs */
  return count;
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved