From 9c887818bbb38d8efb89af37d991c5910fb878aa Mon Sep 17 00:00:00 2001 From: leizongkun Date: Tue, 4 Nov 2025 12:01:14 +0800 Subject: [PATCH] backends: Add support of one guest numa node alloc memory from multi host nodes It provides QEMU with a more flexible memory NUMA binding approach, allowing a guest NUMA node to allocate memory to different host NUMA nodes according to specified proportions. Signed-off-by: wangzhigang Signed-off-by: zhangliang Signed-off-by: leizongkun --- backends/hostmem.c | 131 ++++++++++++++++++++++++++++++++++ include/sysemu/hostmem.h | 3 + meson.build | 8 +++ meson_options.txt | 3 + qapi/qom.json | 1 + scripts/meson-buildoptions.sh | 5 ++ 6 files changed, 151 insertions(+) diff --git a/backends/hostmem.c b/backends/hostmem.c index 747e7838c0..9c5162760f 100644 --- a/backends/hostmem.c +++ b/backends/hostmem.c @@ -20,6 +20,12 @@ #include "qom/object_interfaces.h" #include "qemu/mmap-alloc.h" #include "qemu/madvise.h" +#ifdef CONFIG_MBIND_PROPORTION +#include "qemu/option.h" +#include "sysemu/sysemu.h" +#include "qemu/log.h" +#include "qemu/units.h" +#endif #ifdef CONFIG_NUMA #include @@ -32,6 +38,12 @@ QEMU_BUILD_BUG_ON(HOST_MEM_POLICY_DEFAULT != MPOL_DEFAULT); QEMU_BUILD_BUG_ON(HOST_MEM_POLICY_PREFERRED != MPOL_PREFERRED); QEMU_BUILD_BUG_ON(HOST_MEM_POLICY_BIND != MPOL_BIND); QEMU_BUILD_BUG_ON(HOST_MEM_POLICY_INTERLEAVE != MPOL_INTERLEAVE); + +#ifdef CONFIG_MBIND_PROPORTION +#define PROPORTION_MAX_NUM 11 +#define PER_PROPORTION_MAX_LENGTH 32 +#define PROPORTION_MAX_LENGTH 512 +#endif #endif char * @@ -137,6 +149,22 @@ out: #endif } +#ifdef CONFIG_MBIND_PROPORTION +static void +host_memory_backend_set_propertion(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ +#ifdef CONFIG_NUMA + HostMemoryBackend *backend = MEMORY_BACKEND(obj); + char *pro; + visit_type_str(v, name, &pro, errp); + backend->propertion = pro; +#else + error_setg(errp, "NUMA node binding are not supported by this QEMU"); +#endif +} +#endif + static int host_memory_backend_get_policy(Object *obj, Error **errp G_GNUC_UNUSED) { @@ -319,6 +347,95 @@ size_t host_memory_backend_pagesize(HostMemoryBackend *memdev) return pagesize; } +#ifdef CONFIG_MBIND_PROPORTION +static int mbind_by_proportions(void *ptr, const char *bind_proportions, uint64_t sz) +{ + char proportion_str[PROPORTION_MAX_LENGTH]; + char proportions[PROPORTION_MAX_NUM][PER_PROPORTION_MAX_LENGTH]; + int proportions_num, i; + char *token; + uint64_t size = 0; + long mbind_ret = -1; + uint64_t size_total = 0; + + if (strlen(bind_proportions) >= PROPORTION_MAX_LENGTH) { + qemu_log("the lenth of bind_proportions is too long, max len is %d\n", PROPORTION_MAX_LENGTH); + return -1; + } + if (memcpy(proportion_str, bind_proportions, strlen(bind_proportions) + 1) == NULL) { + qemu_log("failed to copy bind_proportions\n"); + return -1; + } + proportions_num = 0; + token = strtok(proportion_str, ":"); + while (token != NULL) { + if (strlen(token) + 1 >= PER_PROPORTION_MAX_LENGTH ) { + qemu_log("bind_proportions token is too long, max len is %d\n", PER_PROPORTION_MAX_LENGTH); + return -1; + } + if (memcpy(proportions[proportions_num], token, strlen(token) + 1) == NULL) { + qemu_log("failed to copy token\n"); + return -1; + } + proportions_num++; + if (proportions_num >= PROPORTION_MAX_NUM) { + qemu_log("invalid proportions number, max is %d\n", PROPORTION_MAX_NUM); + return -1; + } + token = strtok(NULL, ":"); + } + + for (i = 0; i < proportions_num; i++) { + unsigned long tmp_lastbit, tmp_maxnode; + char prop[PROPORTION_MAX_LENGTH]; + char *end, *prop_token, *pos; + long int node_id; + long size_token; + DECLARE_BITMAP(tmp_host_nodes, MAX_NODES + 1) = {0}; + + ptr = (void*)((char*)ptr + size); + if (memcpy(prop, proportions[i], strlen(proportions[i]) + 1) == NULL) { + qemu_log("failed to copy propertion"); + return -1; + } + prop_token = strtok(prop, "-"); + if (prop_token == NULL) { + return -1; + } + size_token = strtol(prop_token, &end, 10); + if (*end != '\0') { + return -1; + } + size = size_token * MiB; + size_total += size; + prop_token = strtok(NULL, "-"); + pos = strstr(prop_token, "node"); + pos += strlen("node"); + node_id = strtol(pos, &end, 10); + if (*end != '\0') { + perror("failed to convert node_id from string to number"); + return -1; + } + bitmap_set(tmp_host_nodes, node_id, 1); + tmp_lastbit = find_last_bit(tmp_host_nodes, MAX_NODES); + tmp_maxnode = (tmp_lastbit + 1) % (MAX_NODES + 1); + qemu_log("mbind args: addr %p, size '%" PRIu64 "', node: %ld, \n", ptr, size, node_id); + mbind_ret = mbind(ptr, size, MPOL_BIND, tmp_host_nodes, tmp_maxnode + 1, + MPOL_MF_STRICT | MPOL_MF_MOVE); + if (mbind_ret < 0) { + perror("failed to mbind address to host node"); + return -1; + } + } + if (size_total != sz) { + qemu_log("invalid proportion config, length %" PRIu64 " is not same as " + "all tokens '%" PRIu64 "'", sz, size_total); + return -1; + } + return 0; +} +#endif + static void host_memory_backend_memory_complete(UserCreatable *uc, Error **errp) { @@ -352,6 +469,17 @@ host_memory_backend_memory_complete(UserCreatable *uc, Error **errp) * this doesn't catch hugepage case. */ unsigned flags = MPOL_MF_STRICT | MPOL_MF_MOVE; int mode = backend->policy; +#ifdef CONFIG_MBIND_PROPORTION + const char *proportion = backend->propertion; + if (proportion != NULL) { + if (mbind_by_proportions(ptr, proportion, sz) < 0) { + error_setg(errp, "failed to mbind_by_proportions"); + return; + } + free(backend->propertion); + goto prealloc; + } +#endif /* check for invalid host-nodes and policies and give more verbose * error messages than mbind(). */ @@ -398,6 +526,9 @@ host_memory_backend_memory_complete(UserCreatable *uc, Error **errp) * This is necessary to guarantee memory is allocated with * specified NUMA policy in place. */ +#ifdef CONFIG_MBIND_PROPORTION +prealloc: +#endif if (backend->prealloc) { qemu_prealloc_mem(memory_region_get_fd(&backend->mr), ptr, sz, backend->prealloc_threads, diff --git a/include/sysemu/hostmem.h b/include/sysemu/hostmem.h index 39326f1d4f..83cb9e468d 100644 --- a/include/sysemu/hostmem.h +++ b/include/sysemu/hostmem.h @@ -70,6 +70,9 @@ struct HostMemoryBackend { ThreadContext *prealloc_context; DECLARE_BITMAP(host_nodes, MAX_NODES + 1); HostMemPolicy policy; +#ifdef CONFIG_MBIND_PROPORTION + char *propertion; +#endif MemoryRegion mr; }; diff --git a/meson.build b/meson.build index 6a8410fabb..50b1e31edf 100644 --- a/meson.build +++ b/meson.build @@ -566,6 +566,13 @@ have_tpm = get_option('tpm') \ .require(targetos != 'windows', error_message: 'TPM emulation only available on POSIX systems') \ .allowed() +# mbind_proportion +have_mbind_proportion = get_option('mbind_by_proportion') \ + .require(targetos == 'linux', error_message: 'mbind_by_proportion is supported only on Linux') \ + .allowed() + +config_host_data.set('CONFIG_MBIND_PROPORTION', have_mbind_proportion) + # vhost have_vhost_user = get_option('vhost_user') \ .disable_auto_if(targetos != 'linux') \ @@ -4487,6 +4494,7 @@ summary_info += {'libudev': libudev} summary_info += {'FUSE lseek': fuse_lseek.found()} summary_info += {'selinux': selinux} summary_info += {'libdw': libdw} +summary_info += {'mbind proportion': have_mbind_proportion} summary(summary_info, bool_yn: true, section: 'Dependencies') if host_arch == 'unknown' diff --git a/meson_options.txt b/meson_options.txt index 61996300d5..94a9b479bd 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -374,3 +374,6 @@ option('qemu_ga_version', type: 'string', value: '', option('hexagon_idef_parser', type : 'boolean', value : true, description: 'use idef-parser to automatically generate TCG code for the Hexagon frontend') + +option('mbind_by_proportion', type: 'feature', value: 'auto', + description: ' support of one guest numa node alloc memory from multi host nodes') diff --git a/qapi/qom.json b/qapi/qom.json index e0590a6019..d1fbe2b0a2 100644 --- a/qapi/qom.json +++ b/qapi/qom.json @@ -624,6 +624,7 @@ { 'struct': 'MemoryBackendProperties', 'data': { '*dump': 'bool', '*host-nodes': ['uint16'], + '*host-nodes-propertion': 'str', '*merge': 'bool', '*policy': 'HostMemPolicy', '*prealloc': 'bool', diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh index 8604fe8ffa..d5d9130540 100644 --- a/scripts/meson-buildoptions.sh +++ b/scripts/meson-buildoptions.sh @@ -225,6 +225,9 @@ meson_options_help() { printf "%s\n" ' zstd zstd compression support' printf "%s\n" ' qpl Query Processing Library support' printf "%s\n" ' uadk UADK Library support' + printf "%s\n" ' mbind-by-proportion' + printf "%s\n" ' support of one guest numa node alloc memory from multi' + printf "%s\n" ' host nodes' } _meson_option_parse() { case $1 in @@ -571,6 +574,8 @@ _meson_option_parse() { --disable-qpl) printf "%s" -Dqpl=disabled ;; --enable-uadk) printf "%s" -Duadk=enabled ;; --disable-uadk) printf "%s" -Duadk=disabled ;; + --enable-mbind-by-proportion) printf "%s" -Dmbind_by_proportion=enabled ;; + --disable-mbind-by-proportion) printf "%s" -Dmbind_by_proportion=disabled ;; *) return 1 ;; esac } -- Gitee