[PATCH v3 1/1] mbuf: add optional no-copy dynamic field storage
Konstantin Ananyev
konstantin.v.ananyev at yandex.ru
Tue Sep 29 13:59:58 CEST 2026
28.09.2026 19:16, Randy L Tice пишет:
> 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.
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 <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:
More information about the dev
mailing list