对应路线图:Phase 1 (Day 22-49) | DPDK 24.11.6 LTS | 内核对照 v6.6
代码出处:source/dpdk-stable-24.11.6/lib/mbuf/,引用均标文件:行号
上一篇:P1-03 ring | 下一篇:P1-05 PMD
一句话定位
mbuf 之于 DPDK,等于 sk_buff 之于内核:数据面所有结构都围绕它转。mbuf 的设计处处为 cache line 优化:一个包的处理全程只碰 1-2 个 cache line。
真实布局(lib/mbuf/rte_mbuf_core.h:467-674)
第一缓存行的真实定义(节选,rte_mbuf_core.h:468-518):
struct __rte_cache_aligned rte_mbuf {
void *buf_addr; /**< Virtual address of segment buffer. */
#if RTE_IOVA_IN_MBUF
alignas(sizeof(rte_iova_t)) rte_iova_t buf_iova;
// ...
#endif
/* next 8 bytes are initialised on RX descriptor rearm */
union {
uint64_t rearm_data[1];
__extension__
struct {
uint16_t data_off;
RTE_ATOMIC(uint16_t) refcnt; // 引用计数(原子性可配)
uint16_t nb_segs; // 仅首段有效
uint16_t port; // 入端口号
};
};
uint64_t ol_flags; /**< Offload features. */
/* remaining 24 bytes are set on RX when pulling packet from descriptor */
union {
void *rx_descriptor_fields1[24 / sizeof(void *)];
__extension__
struct {
union { uint32_t packet_type; ... };
uint32_t pkt_len; /**< Total pkt len: sum of all segments. */
uint16_t data_len; /**< Amount of data in segment buffer. */
uint16_t vlan_tci;
union { uint32_t rss; ... } hash; /**< RSS hash result */
// ...
};
};
struct rte_mempool *pool; /**< Pool from which mbuf was allocated. */第二缓存行(节选,rte_mbuf_core.h:606-673):
/* second cache line - fields only used in slow path or on TX */
#if RTE_IOVA_IN_MBUF
alignas(RTE_CACHE_LINE_MIN_SIZE)
struct rte_mbuf *next; // 分段链(IOVA_IN_MBUF=1 时在第二行)
#endif
/* fields to support TX offloads */
union {
uint64_t tx_offload; /**< combined for easy fetch */
__extension__
struct {
uint64_t l2_len:RTE_MBUF_L2_LEN_BITS;
uint64_t l3_len:RTE_MBUF_L3_LEN_BITS;
uint64_t l4_len:RTE_MBUF_L4_LEN_BITS;
uint64_t tso_segsz:RTE_MBUF_TSO_SEGSZ_BITS;
uint64_t outer_l3_len:RTE_MBUF_OUTL3_LEN_BITS;
uint64_t outer_l2_len:RTE_MBUF_OUTL2_LEN_BITS;
};
};
struct rte_mbuf_ext_shared_info *shinfo; // 外部缓冲共享信息
uint16_t priv_size;
uint16_t timesync;
uint32_t dynfield1[9]; /**< Reserved for dynamic fields. */
};三个设计点:
- rearm_data 打包:收包时要复位的 data_off/refcnt/nb_segs/port 打包成 64 位,一条 8B store 完成 mbuf 重装。实测证据在
ixgbe_rx_alloc_bufs(ixgbe_rxtx.c:1654-1655):rte_mbuf_refcnt_set(mb, 1); mb->data_off = RTE_PKTMBUF_HEADROOM;。 - headroom = 128B(
build/rte_build_config.h:454):留给封装。VXLAN 外层头 = ETH14 + IP20 + UDP8 + VXLAN8 = 50B,128 绰绰有余。rte_pktmbuf_prepend()向前扩 data_off,零拷贝加隧道头。默认缓冲RTE_MBUF_DEFAULT_BUF_SIZE = 2048 + 128(rte_mbuf_core.h:405-407)。 - pool 指针:mbuf 记得自己的 mempool(rte_mbuf_core.h:604),
rte_pktmbuf_free才能按池归还。
外部缓冲:rte_mbuf_ext_shared_info(rte_mbuf_core.h:684-688)
struct rte_mbuf_ext_shared_info {
rte_mbuf_extbuf_free_callback_t free_cb; /**< Free callback function */
void *fcb_opaque; /**< Free callback argument */
RTE_ATOMIC(uint16_t) refcnt;
};rte_pktmbuf_attach_extbuf() 把用户自己的内存挂进 mbuf:refcnt 原子计数管共享,归零时调用户的 free_cb——生命周期完全由用户控制。典型场景:TLS 记录、文件缓存直接交给网卡 DMA 收发,全程不拷贝。这是 P1 零拷贝 echo 的技术基础。
🐧 内核对照:sk_buff 的真实结构
head 区模型(skbuff.h:3122-3124):
#ifndef NET_SKB_PAD
#define NET_SKB_PAD max(32, L1_CACHE_BYTES)
#endif(x86 上 L1_CACHE_BYTES=64,即 64B headroom——和 mbuf 的 128B 是同一思想,留的余量不同。)
非线性数据(include/linux/skbuff.h:572-597,全文):
struct skb_shared_info {
__u8 flags;
__u8 meta_len;
__u8 nr_frags;
__u8 tx_flags;
unsigned short gso_size;
unsigned short gso_segs;
struct sk_buff *frag_list;
struct skb_shared_hwtstamps hwtstamps;
unsigned int gso_type;
u32 tskey;
/*
* Warning : all fields before dataref are cleared in __alloc_skb()
*/
atomic_t dataref;
unsigned int xdp_frags_size;
void * destructor_arg;
/* must be last field, see pskb_expand_head() */
skb_frag_t frags[MAX_SKB_FRAGS];
};struct sk_buff 开头(skbuff.h:843-876 节选):
struct sk_buff {
union {
struct {
/* These two members must be first to match sk_buff_head. */
struct sk_buff *next;
struct sk_buff *prev;
// ...
};
struct rb_node rbnode; /* used in netem, ip4 defrag, and tcp stack */
struct list_head list;
struct llist_node ll_node;
};
struct sock *sk;
// ...
/*
* This is the control buffer. It is free to use for every
* layer. ... This is owned by whoever has the skb queued ATM.
*/
char cb[48] __aligned(8);
// ...
unsigned int len,
data_len;对照图:
| DPDK mbuf | 内核 sk_buff | 差异本质 |
|---|---|---|
| 数据区独立,buf_addr 指针引用 | head 区 kmalloc 一段(末端挂 shared_info) | 都要 headroom(128 vs 64) |
| 多段 = mbuf 链(next 指针) | 非线性数据 = frags[] 页数组 | 段链表 vs 页片段数组 |
refcnt(rearm_data 内) | skb_shared_info.dataref(原子计数) | 同为引用计数,skb 放在数据区末端共享结构 |
| mbuf 记得 pool,free 即归还 | 每包进 slab/page_pool 分配 | 池化预分配 vs 按需分配+池化回收 |
面试金句:mbuf 把"段"建模为元数据链表,适配用户态预分配池 + 批量轮询;skb 把"非线性数据"建模为页数组,适配内核页缓存零拷贝生态——不是谁好谁坏,是谁服务谁的场景。
面试 3 问
- rearm_data 打包 4 字段的意义? → 收包热路径一次 8B store 重置 4 个字段;对应 rte_mbuf_core.h:488-516。
- headroom 为什么 128B? → 隧道封装零拷贝 prepend;VXLAN 外层 50B 加得下。
- extbuf 与 clone 的计数分别在哪? → extbuf 在
rte_mbuf_ext_shared_info.refcnt(用户内存共享);clone 在 mbuf 自身 refcnt。
🔧 动手实验
写零拷贝 echo(code/P1/zerocopy-echo/):不分配新 mbuf,in-place 交换 src/dst MAC+IP 原路发回。dperf 打满对比"重新分配 mbuf"版本,量化省掉的 mempool get/put 值多少 Mpps。数据落点 perf-data/P1/。
评论 (0)