侧边栏壁纸
  • 累计撰写 53 篇文章
  • 累计收到 3 条评论

P1-04 · mbuf:包的一生都住在里面

PacketForge
2026-08-18 / 0 评论 / 0 阅读 /
对应路线图: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)

struct rte_mbuf 双 cache line 布局

第一缓存行的真实定义(节选,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. */
};

三个设计点:

  1. 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;。
  2. 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)。
  3. 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;

对照图:

mbuf 段链表 vs sk_buff 页片段数组

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 问

  1. rearm_data 打包 4 字段的意义? → 收包热路径一次 8B store 重置 4 个字段;对应 rte_mbuf_core.h:488-516。
  2. headroom 为什么 128B? → 隧道封装零拷贝 prepend;VXLAN 外层 50B 加得下。
  3. 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/。


下一篇:P1-05 · PMD:把中断换成轮询

0

评论 (0)

取消