目錄
6.1 pbuf結構體
6.2 pbuf 的類型
6.2.1 PBUF_RAM 類型的pbuf
6.2.2 PBUF_POOL 類型的pbuf
6.2.3 PBUF_ROM 和 PBUF_REF 類型pbuf
6.3 pbuf
6.3.1 pbuf_alloc()
6.3.2 pbuf_free()
6.4 其它pbuf 操作函數
6.5 網卡中使用的 pbuf
6.5.1 low_level_output()
6.5.2 low_level_input()
6.5.3 ethernetif_input()
6.1 pbuf結構體
在 lwIP 協議棧中,網絡數據包的處理是核心環節,其通過pbuf 結構體實現數據的封裝、傳遞與管理,貫穿從硬件接收到底層協議解析再到應用層處理的全流程,同時兼顧內存效率與協議棧各層的協作需求。
pbuf是 lwIP 中描述網絡數據包的基礎數據結構,其設計采用鏈式結構,支持將多個內存塊串聯成一個完整的數據包,既適應嵌入式系統有限的內存資源,又能高效處理不同長度的網絡數據。每個 pbuf 包含數據區(存儲實際數據包內容)和控制信息(如指向 next 節點的指針、數據長度、 payload 指針等),其中payload
字段指向當前層級協議數據的起始位置(例如,在以太網層指向以太網幀首部,進入 IP 層后則指向 IP 首部,通過調整該指針實現協議層間的數據傳遞,無需復制數據)。
pbuf 就是一個描述協議棧中數據包的數據結構,LwIP 中在 pbuf.c 和 pubf.h 實現了協議棧數據包管理的所有函數與數據結構,pbuf 數據結構的定義具體見代碼如下。
/*** 數據包緩沖區結構,用于在協議棧中存儲和傳遞網絡數據包* 采用鏈表結構,支持將多個緩沖區串聯成一個完整的數據包*/
struct pbuf {struct pbuf *next; // 鏈表指針,指向當前緩沖區的下一個pbuf// 用于將多個pbuf串聯成一個完整的數據包鏈void *payload; // 指向當前緩沖區中實際數據的起始地址u16_t tot_len; // 整個數據包(當前pbuf+后續所有pbuf)的總長度u16_t len; // 當前pbuf中實際存儲的數據長度// 僅表示本節點的數據長度,不包含后續節點u8_t type_internal; // 內部類型標識(位字段)// 用于表示pbuf的內存分配方式和緩沖區類型// (如靜態分配/動態分配、協議頭/數據體等)u8_t flags; // 標志位集合// 用于存儲數據包的控制信息(如是否需要分片、是否已校驗等)LWIP_PBUF_REF_T ref; // 引用計數變量,記錄當前pbuf被引用的次數u8_t if_idx; // 網絡接口索引
};
struct pbuf *next:next 是一個pbuf 類型的指針,指向下一個 pbuf,因為網絡中的數據包可能很大,而pbuf 能管理的數據包大小有限,就會采用鏈表的形式將所有的 pbuf 包連接起來組成一個鏈表。
void *payload:payload 是一個指向數據區域的指針,指向該pbuf 管理的數據區域起始地址,這里的數據區域可以是緊跟在 pbuf 結構體地址后面的RAM 空間,也可以是ROM 中的某個地址上,取決于 pbuf 的類型。
tot_len:tot_len 中記錄的是當前pbuf 及其后續pbuf 所有數據的長度,例如如果當前pbuf 是pbuf 鏈表上第一個數據結構,那么 tot_len 就記錄著整個pbuf 鏈表中所有pbuf 中數據的長度;如果當前 pbuf 是鏈表上最后一個數據結構,那就記錄著當前 pbuf 的長度。
len:len 表示當前pbuf 中有效的數據長度。
type_internal:type_internal 表示pbuf 的類型,LwIP 中有 4 種pbuf 的類型,并且使用了一個枚舉類型的數據結構定義他們。
flags:flags 字段在初始化的時候一般被初始化為0。
ref:ref 表示該pbuf 被引用的次數,引用表示有其他指針指向當前 pbuf這里的指針,可以是pbuf 的next 指針,也可以是其他任意形式的指針,初始化一個 pbuf 的時候,ref 會被設置為1,因為該pbuf 的地址一點會被返回一個指針變量,當有其他指針指向pbuf 的時候,就必須調用相關函數將 ref 字段加1。
if_idx:if_idx 用于記錄傳入的數據包中輸入 netif 的索引,也就是netif 中num 字段。
6.2 pbuf 的類型
pbuf
包含四種,其差異體現在數據存儲位置與管理方式:PBUF_RAM
將數據存于動態分配的 RAM,支持靈活讀寫,適用于需修改數據的場景(如 TCP 數據包構建);PBUF_ROM
通過指針引用 ROM/Flash 中的只讀靜態數據,可節省 RAM,用于發送預存報文;PBUF_REF
僅引用外部已存在的 RAM 數據,避免內存復制,適合臨時使用外部緩沖區的場景(需確保數據有效性);PBUF_POOL
從預分配的固定大小內存池獲取緩沖區,分配釋放高效,多用于實時性要求高的網絡接收場景(如以太網幀接收)。這些類型均服務于不同的數據包處理需求,且不影響pbuf
常用的鏈式結構,協議棧會自動適配或允許用戶通過pbuf_alloc()
指定使用。
/** pbuf(數據包緩沖區)類型枚舉,定義不同內存分配和管理方式 */
typedef enum {/** * 基于RAM的緩沖區:* 數據連續存儲,結構體與數據在連續內存空間,內存來自標準堆 */PBUF_RAM = (PBUF_ALLOC_FLAG_DATA_CONTIGUOUS | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP),/** * 數據在ROM/Flash中的緩沖區:* 內存來自標準pbuf內存池,數據通常不可修改 */PBUF_ROM = PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF,/** * 引用外部內存的緩沖區:* 數據存于易失性內存,內存來自標準pbuf內存池,僅引用外部數據不復制 */PBUF_REF = (PBUF_TYPE_FLAG_DATA_VOLATILE | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF),/** * 從專用內存池分配的緩沖區:* 用于接收數據,結構體與數據連續,內存來自專用pbuf內存池 */PBUF_POOL = (PBUF_ALLOC_FLAG_RX | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL)
} pbuf_type;
6.2.1 PBUF_RAM 類型的pbuf
PBUF_RAM 類型的 pbuf 空間是通過內存堆分配而來的,這種類型的pbuf 在協議棧中使用得最多,一般協議棧中要發送的數據都是采用這種形式,這種 pbuf 內存塊包含數據空間以及pbuf 數據結構區域,在連續的RAM 內存空間中。
很多人又會有疑問了,不是說各個協議層都有首部嗎,這些內存空間在哪呢?內核申請這類型的pbuf 時,也算上了協議首部的空間,當然是根據協議棧不同層次需要的首部進行申請,LwIP 也使用一個枚舉類型對不同的協議棧分層需要的首部大小進行定義。那么申請這種 pbuf 是怎么樣申請的呢?
/*** 申請指定類型的pbuf數據包緩沖區* * @param layer 協議層類型(如PBUF_RAW表示原始數據層,PBUF_TRANSPORT表示傳輸層)* 用于標識緩沖區在協議棧中的使用層級* @param length 所需緩沖區的長度(字節數),即需要存儲的數據大小* @param type 緩沖區類型(如PBUF_RAM、PBUF_POOL等),決定內存分配方式和管理策略* * @return 成功時返回指向分配的pbuf結構體的指針,失敗時返回NULL*/
struct pbuf * pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type); // 示例1:分配原始數據層(PBUF_RAW)的RAM緩沖區,長度為req_len+1字節
struct pbuf *p;
p = pbuf_alloc(PBUF_RAW, (u16_t)(req_len + 1), PBUF_RAM); // 示例2:分配傳輸層(PBUF_TRANSPORT)的RAM緩沖區,長度為1472字節
p = pbuf_alloc(PBUF_TRANSPORT, 1472, PBUF_RAM);
PBUF_RAM 類型的 pbuf 示意圖具體見圖,圖中可以看出整個pbuf 就是一個連續的內存區域,layer(offset)就是各層協議的首部,如TCP 報文首部、IP 首部、以太網幀首部等,預留出來的這些空間是為了在各個協議層中靈活地處理這些數據,當然 layer 的大小也可以是0,具體是多少就與數據包的申請方式有關。
圖 PBUF_RAM 類型的 pbuf
6.2.2 PBUF_POOL 類型的pbuf
PBUF_POOL 類型其pbuf 結構體與數據緩沖區也是存在于連續的內存塊中,但它的空間是通過內存池分配的,這種類型的pbuf 可以在極短的時間內分配得到,因為這是內存池分配策略的優勢,在網卡接收數據的時候,LwIP 一般就使用這種類型的 pbuf 來存儲接收到的數據,申請 PBUF_POOL 類型時,協議棧會在內存池中分配適當的內存池個數以滿足需要的數據區域大小。
在系統進行內存池初始化的時候,還初始化兩個與pbuf 相關的內存池,分別為MEMP_PBUF、MEMP_ PBUF_POOL。
/* pbuf 相關的內存池*/
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,其值由用戶自己定義。
如果按照默認的內存大小,對于有些很大的以太網數據包,可能就需要多個pbuf 才能將這些數據存放下來,這就需要申請多個pbuf,因為是PBUF_POOL 類型的pbuf,所以申請內存空間只需要調用 memp_malloc()函數進行申請即可。
pbuf 鏈表中第一個 pbuf 是有 layer 字段的,用于存放協議頭部,而在它后面的pbuf 則是沒有該字段。
圖 PBUF_POOL 類型 pbuf(組成 pbuf 鏈表)
6.2.3 PBUF_ROM 和 PBUF_REF 類型pbuf
PBUF_ROM 和PBUF_REF 類型在內存池申請的pbuf 不包含數據區域,只包含pbuf 結構體。 PBUF_ROM 類型的 pbuf 的數據區域存儲在ROM 中,是一段靜態數據,而PBUF_REF 類型的pbuf 的數據區域存儲在RAM 空間中。
圖 PBUF_ROM/PBUF_REF 類型 pbuf
對于一個數據包,它可能會使用任意類型的pbuf 進行描述,也可能使用多種不同的 pbuf 一起描述,如圖 所示。
圖 不同類型的 pbuf 組成 pbuf 鏈表
6.3 pbuf
6.3.1 pbuf_alloc()
數據包申請函數pbuf_alloc()在系統中的許多地方都會用到,例如在網卡接收數據時、在發送數據的時候,同時相關的協議首部信息也會被填入到 pbuf 中的 layer 區域內,所以 pbuf 數據包的申請函數幾乎無處不在,存在協議棧于各層之中。當然,在不同層的協議中,layer 字段的大小是不一樣的,因為不一樣的協議其首部大小是不同的,在申請的時候就把layer 需要空間的大小根據協議進行分配。
/*** @brief 定義pbuf的協議層次枚舉,用于指定在分配緩沖區時需預留的各層協議首部空間*/
typedef enum {/* 傳輸層:包含傳輸層、網絡層、鏈路層及封裝層首部的總預留空間* 適用于UDP、TCP等傳輸層協議報文,自動預留各層協議首部所需內存*/PBUF_TRANSPORT = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN,/* 網絡層:包含網絡層、鏈路層及封裝層首部的總預留空間* 適用于IP等網絡層協議報文,預留對應層級首部所需內存*/PBUF_IP = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN,/* 鏈路層:包含鏈路層及封裝層首部的總預留空間* 適用于以太網等鏈路層協議報文,預留對應層級首部所需內存*/PBUF_LINK = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN,/* 原始發送層:僅包含封裝層首部的預留空間* PBUF_LINK_ENCAPSULATION_HLEN宏默認值為0,通常用于發送原始數據時使用*/PBUF_RAW_TX = PBUF_LINK_ENCAPSULATION_HLEN,/* 原始層:不預留任何協議首部空間* 適用于直接處理純數據,無需添加各層協議首部的場景*/PBUF_RAW = 0
} pbuf_layer;
數據包申請函數有兩個重要的參數:數據包pbuf 的類型和數據包在哪一層被申請,數據包在哪一層申請這個參數主要是為了預留各層協議的內存大小,也就是前面所說的 layer 值。
struct pbuf * pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type)
{
}
pbuf_alloc()函數的思路很清晰,根據傳入的 pbuf 類型及協議層次layer,去申請對應的pbuf,就能預留出對應的協議首部空間。舉個例子,假設TCP 協議需要申請一個pbuf 數據包,那么就會調用下面代碼進行申請:
p = pbuf_alloc(PBUF_TRANSPORT, 1472, PBUF_RAM);
內核就會根據這句代碼進行分配一個PBUF_RAM 類型的pbuf,其數據區域大小是1472 字節,并且會根據協議層次進行預留協議首部空間,由于是傳輸層,所以內核需要預留54 個字節空間。
/*** @ingroup pbuf* 分配指定類型的pbuf(對于PBUF_POOL類型可能返回一個pbuf鏈表)** pbuf實際分配的內存大小由分配時指定的協議層(layer)和請求的 payload 長度(length)共同決定** @param layer 協議層,用于計算需預留的首部偏移量(不同層級對應不同首部總長度)* @param length pbuf的payload數據長度(字節數)* @param type 決定pbuf的分配方式和內存來源,具體如下:** - PBUF_RAM: 從堆中分配一整塊連續內存,包含協議首部和payload* - PBUF_ROM: 不分配緩沖區內存(包括協議首部),數據通常來自只讀存儲(如ROM/Flash)。* 如需添加首部,需額外分配pbuf并鏈接到當前pbuf前。適用于不可修改的靜態數據。* - PBUF_REF: 不分配緩沖區內存(包括協議首部),適用于單線程場景下引用外部動態內存。* 若需入隊,需調用pbuf_take復制數據。* - PBUF_POOL: 從pbuf內存池(pbuf_init()初始化)分配,可能形成pbuf鏈表** @return 分配成功時返回pbuf指針(多pbuf時返回鏈表頭);失敗返回NULL*/
struct pbuf *
pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type)
{struct pbuf *p; // 指向分配的pbuf(或鏈表頭)u16_t offset = (u16_t)layer; // 根據協議層計算的首部偏移量(需預留的首部空間)// 調試輸出:打印分配的長度LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%"U16_F")\n", length));// 根據pbuf類型選擇不同的分配策略switch (type) {// PBUF_REF和PBUF_ROM均引用外部內存,共用同一分配邏輯case PBUF_REF: /* fall through */case PBUF_ROM:// 調用引用型pbuf分配函數(不分配新內存,僅創建pbuf結構引用外部數據)p = pbuf_alloc_reference(NULL, length, type);break;// 從pbuf內存池分配(可能形成鏈表)case PBUF_POOL: {struct pbuf *q, *last; // q:當前分配的pbuf;last:鏈表尾指針u16_t rem_len; // 剩余待分配的長度p = NULL; // 初始化鏈表頭為NULLlast = NULL; // 初始化鏈表尾為NULLrem_len = length; // 初始化剩余長度為總需求長度// 循環分配pbuf,直到滿足總長度需求do {u16_t qlen; // 當前pbuf的payload長度// 從內存池分配一個pbuf結構q = (struct pbuf *)memp_malloc(MEMP_PBUF_POOL);if (q == NULL) { // 內存池耗盡PBUF_POOL_IS_EMPTY(); // 觸發內存池空的鉤子函數// 釋放已分配的pbuf鏈表if (p) {pbuf_free(p);}return NULL; // 分配失敗}// 計算當前pbuf可容納的payload長度(不超過內存池緩沖區大小減去偏移)qlen = LWIP_MIN(rem_len, (u16_t)(PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)));// 初始化已分配的pbuf(設置payload指針、長度等)pbuf_init_alloced_pbuf(q, LWIP_MEM_ALIGN((void *)((u8_t *)q + SIZEOF_STRUCT_PBUF + offset)),rem_len, qlen, type, 0);// 斷言:檢查payload地址是否按要求對齊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,作為鏈表頭p = q;} else { // 非第一個pbuf,鏈接到鏈表尾last->next = q;}last = q; // 更新鏈表尾為當前pbufrem_len = (u16_t)(rem_len - qlen); // 更新剩余長度offset = 0; // 后續pbuf無需再預留首部偏移(僅第一個需考慮)} while (rem_len > 0); // 直到剩余長度為0break;}// 從堆分配連續內存的pbufcase PBUF_RAM: {// 計算payload總長度(含偏移對齊和數據長度對齊)u16_t payload_len = (u16_t)(LWIP_MEM_ALIGN_SIZE(offset) + LWIP_MEM_ALIGN_SIZE(length));// 計算總分配長度(pbuf結構體大小對齊 + payload總長度)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(設置payload指針、長度等)pbuf_init_alloced_pbuf(p, LWIP_MEM_ALIGN((void *)((u8_t *)p + SIZEOF_STRUCT_PBUF + offset)),length, length, type, 0);// 斷言:檢查payload地址是否按要求對齊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;
}
6.3.2 pbuf_free()
數據包pbuf 的釋放是必須的,因為當內核處理完數據就要將這些資源進行回收,否則就會造成內存泄漏,在后續的數據處理中無法再次申請內存。
當然,既然要釋放數據包,那么肯定有條件,pbuf 中 ref 字段就是記錄pbuf 數據包被引用的次數,在申請pbuf 的時候,ref 字段就被初始化為 1,當釋放pbuf 的時候,先將 ref減1,如果ref 減1 后為 0,則表示能釋放pbuf 數據包。此外,能被內核釋放的 pbuf 數據包只能是首節點或者其他地方未被引用過的節點,如果用戶錯誤地調用pbuf 釋放函數,將pbuf 鏈表中的某個中間節點刪除了,那么必然會導致錯誤。
u8_t pbuf_free(struct pbuf *p)
{
}
pbuf_free
函數原型如下:
/*** @ingroup pbuf* 解除對pbuf鏈或隊列的引用,并釋放鏈/隊列頭部所有不再被使用的pbuf** 遞減pbuf的引用計數。當引用計數減至0時,該pbuf將被釋放** 對于pbuf鏈,此操作會對鏈中每個pbuf重復執行,直到遇到第一個遞減后引用計數仍不為0的pbuf為止。* 因此,當所有pbuf的引用計數均為1時,整個鏈都會被釋放** @param p 要解除引用的pbuf(鏈)** @return 從鏈頭部釋放的pbuf數量** @note 禁止在數據包隊列上調用(尚未驗證其在隊列上的有效性)* @note pbuf的引用計數等于指向該pbuf(或pbuf內部)的指針數量** @internal 示例:* 假設現有鏈 a->b->c 的引用計數如下,調用pbuf_free(a)后結果為:* 1->2->3 → ...1->3(釋放a)* 3->3->3 → 2->3->3(僅a的計數減1)* 1->1->2 → ......1(釋放a和b)* 2->1->1 → 1->1->1(僅a的計數減1)* 1->1->1 → .......(釋放整個鏈)*/
u8_t
pbuf_free(struct pbuf *p)
{u8_t alloc_src; // pbuf的內存分配來源(用于決定釋放方式)struct pbuf *q; // 臨時保存下一個pbuf的指針u8_t count; // 已釋放的pbuf數量// 處理空指針情況if (p == NULL) {LWIP_ASSERT("p != NULL", p != NULL); // 斷言失敗(預期p不為空)/* 若斷言被禁用,輸出調試信息 */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; // 初始化釋放計數為0/* 從鏈頭部開始,釋放所有引用計數遞減后為0的連續pbuf */while (p != NULL) {LWIP_PBUF_REF_T ref; // 保存遞減后的引用計數SYS_ARCH_DECL_PROTECT(old_level); // 聲明臨界區保護變量/* 由于遞減引用計數可能不是原子操作,需要進行臨界區保護* 將新的引用計數存入局部變量,避免后續操作再次保護 */SYS_ARCH_PROTECT(old_level); // 進入臨界區/* 鏈中的所有pbuf至少有一個引用 */LWIP_ASSERT("pbuf_free: p->ref > 0", p->ref > 0);/* 遞減引用計數(指向該pbuf的指針數量) */ref = --(p->ref);SYS_ARCH_UNPROTECT(old_level); // 退出臨界區/* 該pbuf不再被引用? */if (ref == 0) {q = p->next; // 保存下一個pbuf的指針(當前pbuf即將被釋放)LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: deallocating %p\n", (void *)p)); // 調試輸出alloc_src = pbuf_get_allocsrc(p); // 獲取pbuf的內存分配來源#if LWIP_SUPPORT_CUSTOM_PBUF/* 是否為自定義pbuf? */if ((p->flags & PBUF_FLAG_IS_CUSTOM) != 0) {struct pbuf_custom *pc = (struct pbuf_custom *)p; // 轉換為自定義pbuf類型LWIP_ASSERT("pc->custom_free_function != NULL", pc->custom_free_function != NULL); // 確保釋放函數存在pc->custom_free_function(p); // 調用自定義釋放函數} else
#endif /* LWIP_SUPPORT_CUSTOM_PBUF */{/* 是否為內存池(PBUF_POOL)分配的pbuf? */if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL) {memp_free(MEMP_PBUF_POOL, p); // 從pbuf池釋放/* 是否為ROM或REF類型(從標準pbuf內存池分配)? */} else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF) {memp_free(MEMP_PBUF, p); // 從標準pbuf內存池釋放/* 是否為RAM類型(從堆分配)? */} else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP) {mem_free(p); // 從堆釋放} else {/* @todo: 支持釋放其他類型 */LWIP_ASSERT("invalid pbuf type", 0); // 斷言失敗(無效的pbuf類型)}}count++; // 釋放計數加1p = q; // 處理下一個pbuf} else {// p->ref > 0,該pbuf仍被引用(鏈中剩余pbuf也同理)LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: %p has ref %"U16_F", ending here.\n", (void *)p, (u16_t)ref));p = NULL; // 停止遍歷鏈}}PERF_STOP("pbuf_free"); // 性能統計結束return count; // 返回釋放的pbuf數量
}
6.4 其它pbuf 操作函數
pbuf_realloc (pbuf *p, u16_t new_len):重新分配 p 指向的 pbuf 內存,調整其總長度為 new_len。若 new_len 小于原長度,會截斷短數據;若大于原長度,會擴展空間(可能需要分配新內存并復制數據)。返回值為調整后的 pbuf 指針(可能與原指針不同),失敗時返回 NULL。
pbuf_header (pbuf *p, s16_t header_size):調整 pbuf 的頭部空間,header_size 為正數時增加頭部空間(用于添加協議頭),為負數時減少頭部空間(需確保不超過當前可用頭部空間)。
pbuf_take (pbuf *p, const void *dataptr, u16_t len):將 dataptr 指向的長度為 len 的數據復制到 p 指向的 pbuf 中,從 pbuf 的起始位置開始存儲。要求 pbuf 的總長度至少為 len,否則可能導致數據截斷或錯誤。
pbuf_copy (pbuf *dst, const pbuf *src):將 src 指向的 pbuf 鏈中的數據完整復制到 dst 指向的 pbuf 中。要求 dst 的總長度不小于 src 的總長度,否則復制會不完整。
pbuf_chain (pbuf *head, pbuf *tail):將 tail 指向的 pbuf 鏈鏈接到 head 指向的 pbuf 鏈末尾,形成更長的 pbuf 鏈。head 必須是完整的 pbuf 鏈(可通過 head->next 遍歷至鏈尾),tail 也需是獨立的 pbuf 鏈。操作后 head 的 tot_len 會更新為兩鏈總長度之和。
6.5 網卡中使用的 pbuf
6.5.1 low_level_output()
網卡發送數據是通過 low_level_output()函數實現的,該函數是一個底層驅動函數,這要求用戶熟悉網卡底層特性,還要熟悉pbuf 數據包。首先說說發送數據的過程,用戶在應用層想要通過一個網卡發送數據,那么就要將數據傳入 LwIP 內核中,經過內核的層層封裝,存儲在 pbuf 數據包中。當數據發送的時候,就要將屬于一個數據包的數據全部發送出去,此處需要注意的是,屬于同一個數據包中的所有數據都必須放在同一個以太網幀中發送。low_level_output()函數原型如下。
low_level_output(struct netif *netif, struct pbuf *p)
{
}
6.5.2 low_level_input()
與 low_level_output()函數相反的是 low_level_input()函數,該函數用于從網卡中接收一個數據包,并將數據包封裝在 pbuf 中遞交給上層。
staticstruct pbuf *low_level_input(struct netif *netif)
{
}
6.5.3 ethernetif_input()
low_level_output()函數只是完成了網卡驅動接收,但是還沒將 pbuf 數據包遞交給上層,那么又是誰將pbuf 數據包遞交給上層的呢?
ethernetif_input()函數會被周期性調用,這樣子就能接收網卡的數據,在接收完畢,就能將數據通過網卡 netif 的input 接口將pbuf 遞交給上層。