28.09.2026 19:16, Randy L Tice пишет:
From: Randy L Tice <[email protected]>
Date: Thu, 03 Sep 2026 09:13:28 -0400

Add build-time support for optional cache-line-aligned dynamic-field
storage at the end of struct rte_mbuf.

The mbuf_dynfield3_size Meson option sets RTE_MBUF_DYNFIELD3_SIZE in
rte_build_config.h. A non-zero value enables the extra area and grows
every mbuf by the configured amount.
I am strongly opposed to that patch.
Inside mbuf we already do have priv_size that allows user to store his/her specific
data straight after rte_mbuf in adjacent manner.
It worked well so far for many use-cases (including VPP) and I don't see any
reason why this is not enough.
From other side - making size of core rte_mbuf configurable at run-time,
will affect DPDK ABI stability in a negative way.
Fro my perspective it is much plausible in terms of ABI stability and predictability
to have just one fixed layout for the mbuf.
Konstantin
When enabled, dynfield3 is reserved for dynamic fields registered with
RTE_MBUF_DYNFIELD_F_NO_COPY. Such fields are allocated from dynfield3
and are not copied by the generic mbuf dynamic-field copy helper used by
mbuf copy and clone operations. Dynamic fields registered without this
flag continue to use the existing copied dynamic-field storage and are
prevented from overlapping dynfield3.

Disable the octeontx mempool driver when the option is enabled because
it requires sizeof(struct rte_mbuf) to remain at most 128 bytes.

Signed-off-by: Randy L Tice <[email protected]>
---
  app/test/test_mbuf.c                   | 72 ++++++++++++++++++++++++--
  config/meson.build                     |  5 ++
  devtools/test-meson-builds.sh          |  3 ++
  doc/guides/prog_guide/mbuf_lib.rst     |  9 ++++
  doc/guides/rel_notes/release_26_11.rst | 17 +++++-
  drivers/mempool/octeontx/meson.build   |  5 ++
  lib/mbuf/rte_mbuf_core.h               |  6 +++
  lib/mbuf/rte_mbuf_dyn.c                | 54 ++++++++++++++++---
  lib/mbuf/rte_mbuf_dyn.h                | 11 +++-
  meson_options.txt                      |  2 +
  10 files changed, 172 insertions(+), 12 deletions(-)

diff --git a/app/test/test_mbuf.c b/app/test/test_mbuf.c
index db23259745..c03878bd96 100644
--- a/app/test/test_mbuf.c
+++ b/app/test/test_mbuf.c
@@ -2569,7 +2569,7 @@ test_mbuf_dyn(struct rte_mempool *pktmbuf_pool)
        };
        const struct rte_mbuf_dynfield dynfield_fail_big = {
                .name = "test-dynfield-fail-big",
-               .size = 256,
+               .size = sizeof(struct rte_mbuf),
                .align = 1,
                .flags = 0,
        };
@@ -2583,8 +2583,28 @@ test_mbuf_dyn(struct rte_mempool *pktmbuf_pool)
                .name = "test-dynfield",
                .size = sizeof(uint8_t),
                .align = alignof(uint8_t),
-               .flags = 1,
+               .flags = RTE_MBUF_DYNFIELD_F_NO_COPY << 1,
+       };
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+       const struct rte_mbuf_dynfield dynfield3_no_copy = {
+               .name = "test-dynfield3-no-copy",
+               .size = sizeof(uint64_t),
+               .align = alignof(uint64_t),
+               .flags = RTE_MBUF_DYNFIELD_F_NO_COPY,
+       };
+       const struct rte_mbuf_dynfield dynfield_no_copy_bad_offset = {
+               .name = "test-dynfield-no-copy-bad-offset",
+               .size = sizeof(uint64_t),
+               .align = alignof(uint64_t),
+               .flags = RTE_MBUF_DYNFIELD_F_NO_COPY,
+       };
+       const struct rte_mbuf_dynfield dynfield_copy_bad_offset = {
+               .name = "test-dynfield-copy-bad-offset",
+               .size = 2 * sizeof(uint64_t),
+               .align = alignof(uint64_t),
+               .flags = 0,
        };
+#endif
        const struct rte_mbuf_dynflag dynflag_fail_flag = {
                .name = "test-dynflag",
                .flags = 1,
@@ -2602,7 +2622,11 @@ test_mbuf_dyn(struct rte_mempool *pktmbuf_pool)
                .flags = 0,
        };
        struct rte_mbuf *m = NULL;
+       struct rte_mbuf *mc = NULL;
        int offset, offset2, offset3;
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+       int dynfield3_no_copy_offset;
+#endif
        int flag, flag2, flag3;
        int ret;
@@ -2654,6 +2678,29 @@ test_mbuf_dyn(struct rte_mempool *pktmbuf_pool)
        if (ret != -1)
                GOTO_FAIL("dynamic field creation should fail (invalid flag)");
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+       dynfield3_no_copy_offset = 
rte_mbuf_dynfield_register_offset(&dynfield3_no_copy,
+                       offsetof(struct rte_mbuf, dynfield3));
+       if (dynfield3_no_copy_offset != offsetof(struct rte_mbuf, dynfield3))
+               GOTO_FAIL("failed to register no-copy dynfield3 field, offset=%d: 
%s",
+                       dynfield3_no_copy_offset, strerror(errno));
+
+       ret = rte_mbuf_dynfield_register_offset(&dynfield_no_copy_bad_offset,
+                       offsetof(struct rte_mbuf, dynfield1[0]));
+       if (ret != -1)
+               GOTO_FAIL("no-copy dynamic field creation should fail outside 
dynfield3");
+
+       ret = rte_mbuf_dynfield_register_offset(&dynfield_copy_bad_offset,
+                       offsetof(struct rte_mbuf, dynfield3));
+       if (ret != -1)
+               GOTO_FAIL("copied dynamic field creation should fail in 
dynfield3");
+
+       ret = rte_mbuf_dynfield_register_offset(&dynfield_copy_bad_offset,
+                       offsetof(struct rte_mbuf, dynfield3) - 
sizeof(uint64_t));
+       if (ret != -1)
+               GOTO_FAIL("copied dynamic field creation should fail when straddling 
dynfield3");
+#endif
+
        ret = rte_mbuf_dynflag_register(&dynflag_fail_flag);
        if (ret != -1)
                GOTO_FAIL("dynamic flag creation should fail (invalid flag)");
@@ -2693,13 +2740,29 @@ test_mbuf_dyn(struct rte_mempool *pktmbuf_pool)
        if (*RTE_MBUF_DYNFIELD(m, offset2, uint16_t *) != 1000)
                GOTO_FAIL("failed to read dynamic field");
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+       *RTE_MBUF_DYNFIELD(m, dynfield3_no_copy_offset, uint64_t *) =
+               UINT64_C(0x8877665544332211);
+       mc = rte_pktmbuf_alloc(pktmbuf_pool);
+       if (mc == NULL)
+               GOTO_FAIL("Cannot allocate mbuf for dynamic field copy test");
+       *RTE_MBUF_DYNFIELD(mc, dynfield3_no_copy_offset, uint64_t *) =
+               UINT64_C(0xa5a5a5a5a5a5a5a5);
+       rte_mbuf_dynfield_copy(mc, m);
+       if (*RTE_MBUF_DYNFIELD(mc, dynfield3_no_copy_offset, uint64_t *) !=
+                       UINT64_C(0xa5a5a5a5a5a5a5a5))
+               GOTO_FAIL("copied no-copy dynfield3 dynamic field");
+#endif
+
        /* set a dynamic flag */
        m->ol_flags |= (1ULL << flag);
rte_mbuf_dyn_dump(stdout);
+       rte_pktmbuf_free(mc);
        rte_pktmbuf_free(m);
        return 0;
  fail:
+       rte_pktmbuf_free(mc);
        rte_pktmbuf_free(m);
        return -1;
  }
@@ -2776,8 +2839,9 @@ test_mbuf(void)
        struct rte_mempool *pktmbuf_pool = NULL;
        struct rte_mempool *pktmbuf_pool2 = NULL;
-
-       RTE_BUILD_BUG_ON(sizeof(struct rte_mbuf) != RTE_CACHE_LINE_MIN_SIZE * 
2);
+       RTE_BUILD_BUG_ON(sizeof(struct rte_mbuf) !=
+               RTE_CACHE_LINE_MIN_SIZE * 2 +
+               RTE_MBUF_DYNFIELD3_SIZE);
/* create pktmbuf pool if it does not exist */
        pktmbuf_pool = rte_pktmbuf_pool_create("test_pktmbuf_pool",
diff --git a/config/meson.build b/config/meson.build
index 344f68822b..a6bd25b9ac 100644
--- a/config/meson.build
+++ b/config/meson.build
@@ -384,6 +384,8 @@ dpdk_conf.set('RTE_LIBEAL_USE_HPET', get_option('use_hpet'))
  dpdk_conf.set('RTE_ENABLE_STDATOMIC', get_option('enable_stdatomic'))
  dpdk_conf.set('RTE_ENABLE_TRACE_FP', get_option('enable_trace_fp'))
  dpdk_conf.set('RTE_PKTMBUF_HEADROOM', get_option('pkt_mbuf_headroom'))
+mbuf_dynfield3_size = get_option('mbuf_dynfield3_size')
+dpdk_conf.set('RTE_MBUF_DYNFIELD3_SIZE', mbuf_dynfield3_size)
  # values which have defaults which may be overridden
  dpdk_conf.set('RTE_MAX_VFIO_GROUPS', 64)
  dpdk_conf.set('RTE_DRIVER_MEMPOOL_BUCKET_SIZE_KB', 64)
@@ -395,6 +397,9 @@ dpdk_conf.set10('RTE_IOVA_IN_MBUF', 
get_option('enable_iova_as_pa'))
compile_time_cpuflags = []
  subdir(arch_subdir)
+if mbuf_dynfield3_size % dpdk_conf.get('RTE_CACHE_LINE_SIZE') != 0
+    error('mbuf_dynfield3_size must be a multiple of RTE_CACHE_LINE_SIZE')
+endif
  dpdk_conf.set('RTE_COMPILE_TIME_CPUFLAGS', ','.join(compile_time_cpuflags))
# apply cross-specific options
diff --git a/devtools/test-meson-builds.sh b/devtools/test-meson-builds.sh
index 11e4be3f88..23115def8c 100755
--- a/devtools/test-meson-builds.sh
+++ b/devtools/test-meson-builds.sh
@@ -259,6 +259,9 @@ fi
  build build-x86-generic cc skipABI --buildtype=debug -Dcheck_includes=true \
        -Dlibdir=lib -Dcpu_instruction_set=$generic_isa $use_shared
+build build-mbuf-dynfield3 cc skipABI --buildtype=debug \
+       -Dmbuf_dynfield3_size=256 $use_shared
+
  # 32-bit with default compiler
  if check_cc_flags '-m32' ; then
        target_override='i386-pc-linux-gnu'
diff --git a/doc/guides/prog_guide/mbuf_lib.rst 
b/doc/guides/prog_guide/mbuf_lib.rst
index cf64add109..ca0efb99c1 100644
--- a/doc/guides/prog_guide/mbuf_lib.rst
+++ b/doc/guides/prog_guide/mbuf_lib.rst
@@ -234,6 +234,15 @@ The dynamic fields and flags are managed with the 
functions ``rte_mbuf_dyn*``.
It is not possible to unregister fields or flags. +The build option ``mbuf_dynfield3_size`` can add extra cache-line-aligned
+dynamic field storage to ``struct rte_mbuf``.  This increases every mbuf by
+the configured amount and changes the mbuf layout, so applications and
+secondary processes must be built with the same value as the primary process.
+The option defaults to ``0``.  The extra storage is reserved for dynamic
+fields registered with ``RTE_MBUF_DYNFIELD_F_NO_COPY``.  These fields are not
+copied by mbuf copy and clone operations.  Dynamic fields registered without
+this flag continue to use the existing copied dynamic-field storage.
+
  .. _direct_indirect_buffer:
Direct and Indirect Buffers
diff --git a/doc/guides/rel_notes/release_26_11.rst 
b/doc/guides/rel_notes/release_26_11.rst
index dec96ccbc7..cff68d7034 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -60,6 +60,22 @@ New Features
    Added the experimental ``rte_cpu_socket_id()`` function
    to map an OS logical CPU ID to the NUMA socket containing that CPU.
+* **Added optional extra mbuf dynamic field storage.**
+
+  Added ``mbuf_dynfield3_size`` build option to enable a
+  cache-line-aligned ``dynfield3`` area in ``struct rte_mbuf``.
+  The configured size is defined as ``RTE_MBUF_DYNFIELD3_SIZE``
+  in ``rte_build_config.h``.
+  The extra storage is reserved for dynamic fields registered with
+  ``RTE_MBUF_DYNFIELD_F_NO_COPY``.
+  These fields are not copied by generic mbuf copy and clone operations.
+  Dynamic fields registered without this flag continue to use the existing
+  copied dynamic-field storage.
+  Applications and secondary processes must be built with the same
+  non-zero value as the primary process.
+  The ``mempool/octeontx`` driver is disabled when this option is
+  enabled because it requires a fixed 128-byte mbuf header.
+
  * **Added TPID support to VLAN tag insertion.**
Added ``rte_vlan_insert_tpid()`` to the net library.
@@ -338,7 +354,6 @@ Known Issues
     Also, make sure to start the actual text at the margin.
     =======================================================
-
  Tested Platforms
  ----------------
diff --git a/drivers/mempool/octeontx/meson.build b/drivers/mempool/octeontx/meson.build
index 3ccecac75d..01aece94be 100644
--- a/drivers/mempool/octeontx/meson.build
+++ b/drivers/mempool/octeontx/meson.build
@@ -6,6 +6,11 @@ if not is_linux or not dpdk_conf.get('RTE_ARCH_64')
      reason = 'only supported on 64-bit Linux'
      subdir_done()
  endif
+if get_option('mbuf_dynfield3_size') != 0
+    build = false
+    reason = 'requires sizeof(struct rte_mbuf) <= 128'
+    subdir_done()
+endif
sources = files(
          'octeontx_fpavf.c',
diff --git a/lib/mbuf/rte_mbuf_core.h b/lib/mbuf/rte_mbuf_core.h
index 98b0bd9ca7..b4f204b268 100644
--- a/lib/mbuf/rte_mbuf_core.h
+++ b/lib/mbuf/rte_mbuf_core.h
@@ -686,6 +686,12 @@ struct __rte_cache_aligned rte_mbuf {
        uint16_t timesync;
uint32_t dynfield1[9]; /**< Reserved for dynamic fields. */
+
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+       alignas(RTE_CACHE_LINE_SIZE)
+       uint64_t dynfield3[RTE_MBUF_DYNFIELD3_SIZE / sizeof(uint64_t)];
+       /**< Reserved cache-line-aligned space for dynamic fields. */
+#endif /* RTE_MBUF_DYNFIELD3_SIZE > 0 */
  };
/**
diff --git a/lib/mbuf/rte_mbuf_dyn.c b/lib/mbuf/rte_mbuf_dyn.c
index 5987c9dee8..7cecbc9ee6 100644
--- a/lib/mbuf/rte_mbuf_dyn.c
+++ b/lib/mbuf/rte_mbuf_dyn.c
@@ -3,6 +3,7 @@
   */
#include <stdalign.h>
+#include <stddef.h>
  #include <sys/queue.h>
  #include <stdint.h>
  #include <limits.h>
@@ -51,7 +52,7 @@ struct mbuf_dyn_shm {
         * The value is the size of the biggest aligned element that
         * can fit in the zone.
         */
-       uint8_t free_space[sizeof(struct rte_mbuf)];
+       uint16_t free_space[sizeof(struct rte_mbuf)];
        /** Bitfield of available flags. */
        uint64_t free_flags;
  };
@@ -135,6 +136,9 @@ init_shared_mem(void)
  #if !RTE_IOVA_IN_MBUF
                mark_free(dynfield2);
  #endif
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+               mark_free(dynfield3);
+#endif
/* init free_flags */
                for (mask = RTE_MBUF_F_FIRST_FREE; mask <= RTE_MBUF_F_LAST_FREE; mask 
<<= 1)
@@ -147,11 +151,48 @@ init_shared_mem(void)
  }
/* check if this offset can be used */
+static bool
+dynfield_in_dynfield3(size_t offset, size_t size)
+{
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+       size_t dynfield3_offset = offsetof(struct rte_mbuf, dynfield3);
+
+       return offset >= dynfield3_offset &&
+               size <= sizeof(((struct rte_mbuf *)0)->dynfield3) &&
+               offset - dynfield3_offset <= sizeof(((struct rte_mbuf 
*)0)->dynfield3) - size;
+#else
+       RTE_SET_USED(offset);
+       RTE_SET_USED(size);
+       return false;
+#endif
+}
+
+static bool
+dynfield_overlaps_dynfield3(size_t offset, size_t size)
+{
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+       size_t dynfield3_offset = offsetof(struct rte_mbuf, dynfield3);
+       size_t dynfield3_end = dynfield3_offset + sizeof(((struct rte_mbuf 
*)0)->dynfield3);
+
+       return offset < dynfield3_end && offset + size > dynfield3_offset;
+#else
+       RTE_SET_USED(offset);
+       RTE_SET_USED(size);
+       return false;
+#endif
+}
+
  static int
-check_offset(size_t offset, size_t size, size_t align)
+check_offset(size_t offset, size_t size, size_t align, unsigned int flags)
  {
        size_t i;
+ if ((flags & RTE_MBUF_DYNFIELD_F_NO_COPY) != 0 &&
+                       !dynfield_in_dynfield3(offset, size))
+               return -1;
+       if ((flags & RTE_MBUF_DYNFIELD_F_NO_COPY) == 0 &&
+                       dynfield_overlaps_dynfield3(offset, size))
+               return -1;
        if ((offset & (align - 1)) != 0)
                return -1;
        if (offset + size > sizeof(struct rte_mbuf))
@@ -268,7 +309,7 @@ __rte_mbuf_dynfield_register_offset(const struct 
rte_mbuf_dynfield *params,
                     offset < sizeof(struct rte_mbuf);
                     offset++) {
                        if (check_offset(offset, params->size,
-                                               params->align) == 0 &&
+                                               params->align, params->flags) == 0 
&&
                                        shm->free_space[offset] < best_zone) {
                                best_zone = shm->free_space[offset];
                                req = offset;
@@ -279,7 +320,8 @@ __rte_mbuf_dynfield_register_offset(const struct 
rte_mbuf_dynfield *params,
                        return -1;
                }
        } else {
-               if (check_offset(req, params->size, params->align) < 0) {
+               if (check_offset(req, params->size, params->align,
+                               params->flags) < 0) {
                        rte_errno = EBUSY;
                        return -1;
                }
@@ -342,7 +384,7 @@ rte_mbuf_dynfield_register_offset(const struct 
rte_mbuf_dynfield *params,
                rte_errno = EINVAL;
                return -1;
        }
-       if (params->flags != 0) {
+       if ((params->flags & ~RTE_MBUF_DYNFIELD_F_NO_COPY) != 0) {
                rte_errno = EINVAL;
                return -1;
        }
@@ -573,7 +615,7 @@ void rte_mbuf_dyn_dump(FILE *out)
        for (i = 0; i < sizeof(struct rte_mbuf); i++) {
                if ((i % 8) == 0)
                        fprintf(out, "  %4.4zx: ", i);
-               fprintf(out, "%2.2x%s", shm->free_space[i],
+               fprintf(out, "%4.4x%s", shm->free_space[i],
                        (i % 8 != 7) ? " " : "\n");
        }
        fprintf(out, "Free bit in mbuf->ol_flags (0 = occupied, 1 = free):\n");
diff --git a/lib/mbuf/rte_mbuf_dyn.h b/lib/mbuf/rte_mbuf_dyn.h
index 20ce505bb4..d9a46b3c33 100644
--- a/lib/mbuf/rte_mbuf_dyn.h
+++ b/lib/mbuf/rte_mbuf_dyn.h
@@ -69,6 +69,7 @@
  #include <stdio.h>
  #include <stdint.h>
+#include <rte_bitops.h>
  #include <rte_stdatomic.h>
#ifdef __cplusplus
@@ -80,6 +81,14 @@ extern "C" {
   */
  #define RTE_MBUF_DYN_NAMESIZE 64
+/**
+ * Do not copy this dynamic field during mbuf clone or copy.
+ *
+ * Fields using this flag are allocated from the optional dynfield3 area
+ * configured by the mbuf_dynfield3_size build option.
+ */
+#define RTE_MBUF_DYNFIELD_F_NO_COPY RTE_BIT32(0)
+
  /**
   * Structure describing the parameters of a mbuf dynamic field.
   */
@@ -87,7 +96,7 @@ struct rte_mbuf_dynfield {
        char name[RTE_MBUF_DYN_NAMESIZE]; /**< Name of the field. */
        size_t size;        /**< The number of bytes to reserve. */
        size_t align;       /**< The alignment constraint (power of 2). */
-       unsigned int flags; /**< Reserved for future use, must be 0. */
+       unsigned int flags; /**< Dynamic field flags. */
  };
/**
diff --git a/meson_options.txt b/meson_options.txt
index e28d24054c..1248498a3d 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -44,6 +44,8 @@ option('max_numa_nodes', type: 'string', value: 'default', 
description:
         'Set the highest NUMA node supported by EAL; "default" is different per-arch, 
"detect" detects the highest NUMA node on the build machine.')
  option('enable_iova_as_pa', type: 'boolean', value: true, description:
         'Support the use of physical addresses for IO addresses, such as used 
by UIO or VFIO in no-IOMMU mode. When disabled, DPDK can only run with IOMMU 
support for address mappings, but will have more space available in the mbuf 
structure.')
+option('mbuf_dynfield3_size', type: 'integer', min: 0, value: 0, description:
+       'Size of optional extra mbuf dynamic field area, in bytes.')
  option('mbuf_refcnt_atomic', type: 'boolean', value: true, description:
         'Atomically access the mbuf refcnt.')
  option('platform', type: 'string', value: 'native', description:

Reply via email to