On 5/26/2026 6:16 PM, Bruce Richardson wrote:
On Fri, Mar 13, 2026 at 04:06:37PM +0000, Anatoly Burakov wrote:
Currently, the VA space limits placed on DPDK memory are only informed by
the default configuration coming from `rte_config.h` file. Add an EAL flag
to specify per-page size memory limits explicitly, thereby overriding the
default VA space reservations.

Signed-off-by: Anatoly Burakov <[email protected]>
---
Acked-by: Bruce Richardson <[email protected]>

CI reports some errors with 32-bit builds. Also a couple of small comments
inline below.

Thanks for the series, looks some nice simplification.

  app/test/test.c                               |   1 +
  app/test/test_eal_flags.c                     | 126 ++++++++++++++++++
  doc/guides/linux_gsg/linux_eal_parameters.rst |  13 ++
  .../prog_guide/env_abstraction_layer.rst      |  27 +++-
  lib/eal/common/eal_common_dynmem.c            |   9 ++
  lib/eal/common/eal_common_options.c           | 121 +++++++++++++++++
  lib/eal/common/eal_internal_cfg.h             |   6 +
  lib/eal/common/eal_option_list.h              |   1 +
  8 files changed, 302 insertions(+), 2 deletions(-)

diff --git a/app/test/test.c b/app/test/test.c
index 58ef52f312..c610c3588e 100644
--- a/app/test/test.c
+++ b/app/test/test.c
@@ -80,6 +80,7 @@ do_recursive_call(void)
                        { "test_memory_flags", no_action },
                        { "test_file_prefix", no_action },
                        { "test_no_huge_flag", no_action },
+                       { "test_pagesz_mem_flags", no_action },
                        { "test_panic", test_panic },
                        { "test_exit", test_exit },
  #ifdef RTE_LIB_TIMER
diff --git a/app/test/test_eal_flags.c b/app/test/test_eal_flags.c
index b3a8d0ae6f..4e1038be75 100644
--- a/app/test/test_eal_flags.c
+++ b/app/test/test_eal_flags.c
@@ -95,6 +95,14 @@ test_misc_flags(void)
        return TEST_SKIPPED;
  }
+static int
+test_pagesz_mem_flags(void)
+{
+       printf("pagesz_mem_flags not supported on Windows, skipping test\n");
+       return TEST_SKIPPED;
+}
+
+
  #else
#include <libgen.h>
@@ -1502,6 +1510,123 @@ populate_socket_mem_param(int num_sockets, const char 
*mem,
        offset += written;
  }
+/*
+ * Tests for correct handling of --pagesz-mem flag
+ */
+static int
+test_pagesz_mem_flags(void)
+{
+#ifdef RTE_EXEC_ENV_FREEBSD
+       /* FreeBSD does not support --pagesz-mem */
+       return 0;
+#else
+       const char *in_memory = "--in-memory";
+
+       /* invalid: no value */
+       const char * const argv0[] = {prgname, eal_debug_logs, no_pci,
+                       "--file-prefix=" memtest, in_memory, "--pagesz-mem="};
+
+       /* invalid: no colon (missing limit) */
+       const char * const argv1[] = {prgname, eal_debug_logs, no_pci,
+                       "--file-prefix=" memtest, in_memory, "--pagesz-mem=2M"};
+
+       /* invalid: colon present but limit is empty */
+       const char * const argv2[] = {prgname, eal_debug_logs, no_pci,
+                       "--file-prefix=" memtest, in_memory, 
"--pagesz-mem=2M:"};
+
+       /* invalid: limit not aligned to page size (3M is not a multiple of 2M) 
*/
+       const char * const argv3[] = {prgname, eal_debug_logs, no_pci,
+                       "--file-prefix=" memtest, in_memory, 
"--pagesz-mem=2M:3M"};
+
+       /* invalid: garbage value */
+       const char * const argv4[] = {prgname, eal_debug_logs, no_pci,
+                       "--file-prefix=" memtest, in_memory, 
"--pagesz-mem=garbage"};
+
+       /* invalid: garbage value */
+       const char * const argv5[] = {prgname, eal_debug_logs, no_pci,
+                       "--file-prefix=" memtest, in_memory, 
"--pagesz-mem=2M:garbage"};
+
+       /* invalid: --pagesz-mem combined with --no-huge */
+       const char * const argv6[] = {prgname, eal_debug_logs, no_pci,
+                       "--file-prefix=" memtest, in_memory, no_huge, 
"--pagesz-mem=2M:2M"};
+
+       /* valid: single well-formed aligned pair */
+       const char * const argv7[] = {prgname, eal_debug_logs, no_pci,
+                       "--file-prefix=" memtest, in_memory, 
"--pagesz-mem=2M:64M"};
+
+       /* valid: multiple occurrences */
+       const char * const argv8[] = {prgname, eal_debug_logs, no_pci,
+                       "--file-prefix=" memtest, in_memory,
+                       "--pagesz-mem=2M:64M", "--pagesz-mem=1K:8K"};
+
+       /* valid: fake page size set to zero (ignored but syntactically valid) 
*/
+       const char * const argv9[] = {prgname, eal_debug_logs, no_pci,
+                       "--file-prefix=" memtest, in_memory, 
"--pagesz-mem=1K:0"};
+
+       /* invalid: page size must be a power of two */
+       const char * const argv10[] = {prgname, eal_debug_logs, no_pci,
+                       "--file-prefix=" memtest, in_memory, 
"--pagesz-mem=3M:6M"};
+
+       if (launch_proc(argv0) == 0) {
+               printf("Error (line %d) - process run ok with empty 
--pagesz-mem!\n",
+                       __LINE__);
+               return -1;
+       }
+       if (launch_proc(argv1) == 0) {
+               printf("Error (line %d) - process run ok with --pagesz-mem missing 
colon!\n",
+                       __LINE__);
+               return -1;
+       }
+       if (launch_proc(argv2) == 0) {
+               printf("Error (line %d) - process run ok with --pagesz-mem missing 
limit!\n",
+                       __LINE__);
+               return -1;
+       }
+       if (launch_proc(argv3) == 0) {
+               printf("Error (line %d) - process run ok with --pagesz-mem unaligned 
limit!\n",
+                       __LINE__);
+               return -1;
+       }
+       if (launch_proc(argv4) == 0) {
+               printf("Error (line %d) - process run ok with --pagesz-mem garbage 
value!\n",
+                       __LINE__);
+               return -1;
+       }
+       if (launch_proc(argv5) == 0) {
+               printf("Error (line %d) - process run ok with --pagesz-mem garbage 
value!\n",
+                       __LINE__);
+               return -1;
+       }
+       if (launch_proc(argv6) == 0) {
+               printf("Error (line %d) - process run ok with --pagesz-mem and 
--no-huge!\n",
+                       __LINE__);
+               return -1;
+       }
+       if (launch_proc(argv7) != 0) {
+               printf("Error (line %d) - process failed with valid 
--pagesz-mem!\n",
+                       __LINE__);
+               return -1;
+       }
+       if (launch_proc(argv8) != 0) {
+               printf("Error (line %d) - process failed with multiple valid 
--pagesz-mem!\n",
+                       __LINE__);
+               return -1;
+       }
+       if (launch_proc(argv9) != 0) {
+               printf("Error (line %d) - process failed with --pagesz-mem zero 
limit!\n",
+                       __LINE__);
+               return -1;
+       }
+       if (launch_proc(argv10) == 0) {
+               printf("Error (line %d) - process run ok with non-power-of-two 
pagesz!\n",
+                       __LINE__);
+               return -1;
+       }
+
+       return 0;
+#endif /* !RTE_EXEC_ENV_FREEBSD */
+}
+
  /*
   * Tests for correct handling of -m and --socket-mem flags
   */
@@ -1683,5 +1808,6 @@ REGISTER_FAST_TEST(eal_flags_b_opt_autotest, NOHUGE_SKIP, 
ASAN_SKIP, test_invali
  REGISTER_FAST_TEST(eal_flags_vdev_opt_autotest, NOHUGE_SKIP, ASAN_SKIP, 
test_invalid_vdev_flag);
  REGISTER_FAST_TEST(eal_flags_r_opt_autotest, NOHUGE_SKIP, ASAN_SKIP, 
test_invalid_r_flag);
  REGISTER_FAST_TEST(eal_flags_mem_autotest, NOHUGE_SKIP, ASAN_SKIP, 
test_memory_flags);
+REGISTER_FAST_TEST(eal_flags_pagesz_mem_autotest, NOHUGE_SKIP, ASAN_SKIP, 
test_pagesz_mem_flags);
  REGISTER_FAST_TEST(eal_flags_file_prefix_autotest, NOHUGE_SKIP, ASAN_SKIP, 
test_file_prefix);
  REGISTER_FAST_TEST(eal_flags_misc_autotest, NOHUGE_SKIP, ASAN_SKIP, 
test_misc_flags);
diff --git a/doc/guides/linux_gsg/linux_eal_parameters.rst 
b/doc/guides/linux_gsg/linux_eal_parameters.rst
index 7c5b26ce26..ce38dd128a 100644
--- a/doc/guides/linux_gsg/linux_eal_parameters.rst
+++ b/doc/guides/linux_gsg/linux_eal_parameters.rst
@@ -75,6 +75,19 @@ Memory-related options
      Place a per-NUMA node upper limit on memory use (non-legacy memory mode 
only).
      0 will disable the limit for a particular NUMA node.
+* ``--pagesz-mem <page size:limit>``
+
+    Set memory limit per hugepage size.
+    Each time the option is used, provide a single ``<pagesz>:<limit>`` pair;
+    repeat the option to specify additional page sizes.
+    Both values support K/M/G/T suffixes (for example ``2M:32G``).
+
+    The memory limit must be a multiple of page size.
+
+    For example::
+
+        --pagesz-mem 2M:32G --pagesz-mem 1G:512G
+
  *   ``--single-file-segments``
Create fewer files in hugetlbfs (non-legacy mode only).
diff --git a/doc/guides/prog_guide/env_abstraction_layer.rst 
b/doc/guides/prog_guide/env_abstraction_layer.rst
index 63e0568afa..e2adf0a184 100644
--- a/doc/guides/prog_guide/env_abstraction_layer.rst
+++ b/doc/guides/prog_guide/env_abstraction_layer.rst
@@ -204,13 +204,36 @@ of virtual memory being preallocated at startup by 
editing the following config
  variables:
* ``RTE_MAX_MEMSEG_LISTS`` controls how many segment lists can DPDK have
-* ``RTE_MAX_MEMSEG_PER_TYPE`` controls how many segments each memory type
+* ``RTE_MAX_MEMSEG_PER_TYPE`` sets the default number of segments each memory 
type
    can have (where "type" is defined as "page size + NUMA node" combination)
-* ``RTE_MAX_MEM_MB_PER_TYPE`` controls how much megabytes of memory each
+* ``RTE_MAX_MEM_MB_PER_TYPE`` sets the default amount of memory each
    memory type can address
Normally, these options do not need to be changed. +Runtime Override of Per-Page-Size Memory Limits
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+By default, DPDK uses compile-time configured limits for memory allocation per 
page size
+(as set by ``RTE_MAX_MEM_MB_PER_TYPE``).
+These limits apply uniformly across all NUMA nodes for a given page size.
+
+It is possible to override these defaults at runtime using the 
``--pagesz-mem`` option,
+which allows specifying custom memory limits for each page size. This is 
useful when:
+
+* The default limits may be insufficient or excessive for your workload
+* You want to dedicate more memory to specific page sizes
+
+The ``--pagesz-mem`` option accepts exactly one ``<pagesz>:<limit>`` pair per
+occurrence, where ``pagesz`` is a page size (e.g., ``2M``, ``4M``, ``1G``)
+and ``limit`` is the maximum memory to reserve for that page size (e.g., 
``64G``, ``512M``).
+Both values support standard binary suffixes (K, M, G, T).
+Memory limits must be aligned to their corresponding page size.
+
+Multiple page sizes can be specified by repeating the option::
+
+  --pagesz-mem 2M:64G --pagesz-mem 1G:512G
+
  .. note::
Preallocated virtual memory is not to be confused with preallocated hugepage
diff --git a/lib/eal/common/eal_common_dynmem.c 
b/lib/eal/common/eal_common_dynmem.c
index c33fbdea6d..7096f46ff3 100644
--- a/lib/eal/common/eal_common_dynmem.c
+++ b/lib/eal/common/eal_common_dynmem.c
@@ -127,6 +127,11 @@ eal_dynmem_memseg_lists_init(void)
                mem_va_len += type->mem_sz;
        }
+ if (mem_va_len == 0) {
+               EAL_LOG(ERR, "No virtual memory will be reserved");
+               goto out;
+       }
+
        mem_va_addr = eal_get_virtual_area(NULL, &mem_va_len,
                        mem_va_page_sz, 0, 0);
        if (mem_va_addr == NULL) {
@@ -141,6 +146,10 @@ eal_dynmem_memseg_lists_init(void)
                uint64_t pagesz;
                int socket_id;
+ /* skip page sizes with zero memory limit */
+               if (type->n_segs == 0)
+                       continue;
+
                pagesz = type->page_sz;
                socket_id = type->socket_id;
diff --git a/lib/eal/common/eal_common_options.c b/lib/eal/common/eal_common_options.c
index bbc4427524..0532d27aaa 100644
--- a/lib/eal/common/eal_common_options.c
+++ b/lib/eal/common/eal_common_options.c
@@ -21,6 +21,7 @@
  #endif
#include <rte_string_fns.h>
+#include <rte_common.h>
  #include <rte_eal.h>
  #include <rte_log.h>
  #include <rte_lcore.h>
@@ -233,6 +234,20 @@ eal_collate_args(int argc, char **argv)
                EAL_LOG(ERR, "Options allow (-a) and block (-b) can't be used at the 
same time");
                return -1;
        }
+#ifdef RTE_EXEC_ENV_FREEBSD
+       if (!TAILQ_EMPTY(&args.pagesz_mem)) {
+               EAL_LOG(ERR, "Option pagesz-mem is not supported on FreeBSD");
+               return -1;
+       }
+#endif
+       if (!TAILQ_EMPTY(&args.pagesz_mem) && args.no_huge) {
+               EAL_LOG(ERR, "Options pagesz-mem and no-huge can't be used at the 
same time");
+               return -1;
+       }
+       if (!TAILQ_EMPTY(&args.pagesz_mem) && args.legacy_mem) {
+               EAL_LOG(ERR, "Options pagesz-mem and legacy-mem can't be used at the 
same time");
+               return -1;
+       }
/* for non-list args, we can just check for zero/null values using macro */
        if (CONFLICTING_OPTIONS(args, coremask, lcores) ||
@@ -511,7 +526,10 @@ eal_reset_internal_config(struct internal_config 
*internal_cfg)
                                sizeof(internal_cfg->hugepage_info[0]));
                internal_cfg->hugepage_info[i].lock_descriptor = -1;
                internal_cfg->hugepage_mem_sz_limits[i] = 0;
+               internal_cfg->pagesz_mem_overrides[i].pagesz = 0;
+               internal_cfg->pagesz_mem_overrides[i].limit = 0;
        }
+       internal_cfg->num_pagesz_mem_overrides = 0;
        internal_cfg->base_virtaddr = 0;
/* if set to NONE, interrupt mode is determined automatically */
@@ -1867,6 +1885,96 @@ eal_parse_socket_arg(char *strval, volatile uint64_t 
*socket_arg)
        return 0;
  }
+static int
+eal_parse_pagesz_mem(char *strval, struct internal_config *internal_cfg)
+{
+       char strval_cpy[1024];
+       char *fields[3];
+       char *pagesz_str, *mem_str;
+       int arg_num;
+       int len;
+       unsigned int i;
+       uint64_t pagesz, mem_limit;
+       struct pagesz_mem_override *pmo;
+
+       len = strnlen(strval, 1024);
+       if (len >= 1024) {
+               EAL_LOG(ERR, "--pagesz-mem parameter is too long");
+               return -1;
+       }
+
+       rte_strlcpy(strval_cpy, strval, sizeof(strval_cpy));
+
+       /* parse exactly one pagesz:mem pair per --pagesz-mem option */
+       arg_num = rte_strsplit(strval_cpy, len, fields, RTE_DIM(fields), ':');
+       if (arg_num != 2 || fields[0][0] == '\0' || fields[1][0] == '\0') {
+               EAL_LOG(ERR, "--pagesz-mem parameter format is invalid, expected 
<pagesz>:<limit>");
+               return -1;
+       }
+       pagesz_str = fields[0];
+       mem_str = fields[1];
+
+       /* reject accidental multiple pairs in one option */
+       if (strchr(mem_str, ',') != NULL) {
+               EAL_LOG(ERR, "--pagesz-mem accepts one <pagesz>:<limit> pair per 
option");
+               return -1;
+       }

If multiple options are given, then the rte_strsplit should return >2 when
splitting on ":". I'd suggest checking for the comma first, before even
doing the strlcpy.

The intention is to have exactly one pair in one option, so multiple options are not, um, an option. However, effectively there should *not* be commas anywhere in the string, so perhaps we can check for commas. However, *technically* there could be things other than commas, and they should really not have any effect on parsing except to trigger an error, so perhaps a specific check for comma is not needed as any deviation from `--pagesz-mem=A:B` will be invalid.

--
Thanks,
Anatoly

Reply via email to