[PATCH v3 1/1] mbuf: add optional no-copy dynamic field storage

Randy L Tice rtice at cisco.com
Mon Sep 28 20:16:54 CEST 2026


From: Randy L Tice <rtice at cisco.com>
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.

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 <rtice at cisco.com>
---
 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:
-- 
2.35.6



More information about the dev mailing list