解决qemu虚拟机中内存偏小的问题

2023-10-28

问题描述:
最近测试部报了一个问题,云平台中设置大于4GB的内存并设置numa,启动linux2.6.32内核的客户机,之后操作系统中查看实际内存是1.9G,比设置内存小了大概2.1GB。

使用版本信息如下:
QEMU version: 3.0.0
guest os kernel version: 2.6.32
host kernel version: 4.9.0

问题排查如下:
1)在系统中排查问题
通过如下命令查看60系统下内存槽硬件信息,说明内存卡硬件识别正常:

$ dmidecode -t memory

在这里插入图片描述
在这里插入图片描述
通过$ free -m查看,系统识别出来却有问题:
在这里插入图片描述
通过测试如下命令启动QEMU虚拟机必然复现该问题:

$ qemu-system-x86_64 -enable-kvm -name guest=vm1,debug-threads=on -machine pc-i440fx-2.6,accel=kvm,usb=off,dump-guest-core=off -cpu Westmere -m size=4194304k,slots=16,maxmem=16777216k -realtime mlock=off -smp 1,sockets=1,cores=1,threads=1 -numa node,nodeid=0,cpus=0 -uuid fd3535db-2558-43e9-b067-314f48211343 -no-user-config -rtc base=localtime -no-shutdown -boot menu=on,strict=on -device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 -drive file=/opt/issue32/generic.qcow2,format=raw,if=none,id=drive-ide0-0-0 -device ide-hd,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0,bootindex=1 -spice port=5900,addr=0.0.0.0,disable-ticketing,seamless-migration=on -k en-us -device cirrus-vga,id=video0,bus=pci.0,addr=0x4 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3 -msg timestamp=on

(这里的slots为16,所以先抛下一个问题:“为什么实测slots为3的时候不会出现2.1G的内存占用?”)
2)接下来在2.6.32内核源码中排查该问题:
通过debug发现是bootmem释放未占用的内存到buddy内存系统的时候少了2.1G,接下来继续研究bootmem的内存分配。

setup_node_bootmem->init_bootmem_node->free_boot_mem_with_active_regions->early_res_to_bootmem(这里干的好事)

bootmem是系统初始化期间的临时内存分配系统,通过位图来标识内存占用,bootmem自身的初始化很简单,首先将位图所有位置1,表示所有页已保留,之后通过free_boot_mem_with_active_regions将可用页帧置0,而free_boot_mem_with_active_regions中会调用early_res_to_bootmem将early_res分配器占用的内存页继承到bootmem中来,early_res是在bootmem之前更早的内存分配器,排查发现是early_res分配器使用过程中占用了2.1G的内存页,而bootmem继承了它导致未释放至buddy子系统。
因此继续向前排查,在这里发现了问题:

start_kernel->setup_arch->initmem_init->acpi_scan_nodes->compute_hash_shift->allocate_cachealigned_memnodemap->reserve_early

CONFIG_ACPI_NUMA确定此选项后内核会编译acpi_numa_init函数,获取numa支持,从硬件系统的acpi表中得到物理硬件的nodes信息,ACPI是在系统启动阶段由BIOS/UEFI收集各方面信息并创建的。而2.1G的内存分配正是在allocate_cachealigned_memnodemap中通过reserve_early完成的。
allocate_cachealigned_memnodemap分配的大小计算如下:

static int __init allocate_cachealigned_memnodemap(void)
{
	。。。
	nodemap_size = roundup(sizeof(s16) * memnodemapsize, L1_CACHE_BYTES);
	。。。
	reserve_early(nodemap_addr, nodemap_addr + nodemap_size, "MEMNODEMAP");
	。。。
}

memnodemapsize定义如下:

#define memnodemapsize memnode.mapsize

而memnode.mapsize的计算和设置是在compute_hash_shift->extract_lsb_from_nodes中完成的:

/*
 * The LSB of all start and end addresses in the node map is the value of the
 * maximum possible shift.
 */
static int __init extract_lsb_from_nodes(const struct bootnode *nodes,
					 int numnodes)
{
	int i, nodes_used = 0;
	unsigned long start, end;
	unsigned long bitfield = 0, memtop = 0;

	for (i = 0; i < numnodes; i++) {
		start = nodes[i].start;
		end = nodes[i].end;
		if (start >= end)
			continue;
		bitfield |= start;
		nodes_used++;
		if (end > memtop)
			memtop = end;
	}
	if (nodes_used <= 1)
		i = 63;
	else
		i = find_first_bit(&bitfield, sizeof(unsigned long)*8);
	memnodemapsize = (memtop >> i)+1;
	return i;
}

extract_lsb_from_nodes中将所有内存节点的start值进行了一个位或。之前埋下过一个问题也可以解释了“为什么slots为3的时候不会出现2.1G的内存reserve?”其实答案在于最后memnodemapsize = (memtop >> i)+1中,将unsigned long转换成了unsigned int,而i=0,直接导致了高32位的精度丢失,而在slot=3的时候恰好1全在高32位上面,导致mapsize=1,因此没有内存占用。所以这里又可以抛出一个问题了 ;)
(为什么内存区域会随着slots个数而变化?)

node区域范围有如下四个:
a) 0-655,360
b)1,048,576-3,221,225,472
c)4,200,000,000-5,300,000,000
d)35,433,480,192-35,433,480,191
最后一个区域范围恰好是hotplug的内存区域范围,很明显这里的hotplug区域范围有问题,怎么可能只有1。由于start只比end小1,也就直接导致了根据start位与计算的memnodemapsize值偏大,并且还出现了精度丢失!

好了,现在知道问题的直接导致是热插拔内存区设置的有问题,但是为什么会有问题? hotplug内存区参数是怎么读取的,之后想起开机启动的时候好像也报了和hotplug区相关的问题:
在这里插入图片描述
系统启动过程报hotplug区的过小,而hotplug参数也是来自于SRAT表的解析,SRAT(静态资源亲和性表)是ACPI规范的一部分,SRAT表解析流程如下:

start_kernel->setup_arch()->acpi_numa_init()->acpi_table_parse_srat()->acpi_table_parse_entries()
->acpi_parse_memory_affinity()->acpi_numa_memory_affinity_init()->update_nodes_add()

update_nodes_add函数中:

    if ((signed long)(end - start) < NODE_MIN_SIZE) {
        printk(KERN_ERR "SRAT: Hotplug area too small\n");
        return;
    }

这里的SRAT表是前期BIOS存放在内存中的,其根本来源还是qemu。

3)因此就开始了在qemu源码中排查问题的旅途
qemu中建立SRAT表的流程:

main->qemu_run_machine_init_done_notifiers->notifier_list_notify->pc_machine_done->acpi_setup->acpi_build->build_srat

build_srat是构建SRAT表的函数,其中有:

    if (hotplugabble_address_space_size) {
        build_srat_hotpluggable_memory(table_data, machine->device_memory->base,
                                       hotplugabble_address_space_size,
                                       pcms->numa_nodes - 1);
    }

这里的hotplug memory的基址是machine->device_memory->base=5,368,709,120,这个数值从前面的区域范围表中看起来是合理的,为什么这个base addr进入虚拟机后就变成
35,433,480,191了?

    ram_addr_t hotplugabble_address_space_size =
            object_property_get_int(OBJECT(pcms), PC_MACHINE_DEVMEM_REGION_SIZE,
                                		   NULL);

往前看,前面获取的hotplugaable_address_space_size=30064771072恰好是(35,433,480,192 - 5,368,709,120),也就是说end地址传入虚拟机是正确的。

最终在如下代码段找到了问题所在,在info为null是,build_srat_memory设置的start=end-1,size=1,和虚拟机中读取到的完全相符,看来是这里导致的该bug。

static void build_srat_hotpluggable_memory(GArray *table_data, uint64_t base,
                                           uint64_t len, int default_node)
{
    MemoryDeviceInfoList *info_list = qmp_memory_device_list();
    MemoryDeviceInfoList *info;
。。。
    for (cur = base, info = info_list;
         cur < end;
         cur += size, info = info->next) {
        numamem = acpi_data_push(table_data, sizeof *numamem);

        if (!info) {
            build_srat_memory(numamem, end - 1, 1, default_node,
                              MEM_AFFINITY_HOTPLUGGABLE | MEM_AFFINITY_ENABLED);
            break;
        }
。。。

通过参考最新版的的代码以及老版本(2.x)的代码稍作修改即可。

PS:这里其实也可以通过设置device模型为pc-dimm,让info不为null,比如如下:

-numa node -object memory-backend-ram,policy=default,size=4G,id=mem-mem1 -device pc-dimm,node=0,id=dimm-mem1,memdev=mem-mem1

#)
这里最后再解释下为什么内存区域会随着slots个数而变化:
hotplugin内存区域初始化流程如下:

main->machine_run_board_init->pc_init_v3_0->pc_init1->pc_memory_init

如下可知device_mem_size = max_size - ram_size + 1G * slots

device_mem_size = machine->maxram_size - machine->ram_size
        if (pcmc->enforce_aligned_dimm) {
            /* size device region assuming 1G page max alignment per slot */
            device_mem_size += (1 * GiB) * machine->ram_slots;
        }

device_mem_size = 12G(16G-4G) + 1G * 16(slots) = 28G(30064771072)。这里的1G * slots是考虑到大页对齐的问题,支持单页最大1G。所以hotplug区域也会随着slots而变化。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

解决qemu虚拟机中内存偏小的问题 的相关文章

  • 前端布局 Flex(弹性)布局

    1 flex布局优点 操作方便 布局极为简单 移动端应用很广泛 pc端浏览器支持情况较差 IE11或者更低版本 不支持或仅部分支持 2 flex布局原理 flex意为 弹性布局 用来为盒状模型提供最大的灵活性 任何一个容器都可以指定为fle

随机推荐

  • Flutter实时动态UI刷新、数据交互

    setState 简介 setState 函数的作用是标记 StatefulWidget 中的 State 发生变化 需要重新构建 UI 即让Flutter架构自动实时刷新UI 当 StatefulWidget 的 State 发生变化时
  • 乔戈里推荐的新版Java学习路线,开源!

    Java 学习路线一条龙版 by 程序员鱼皮 所以我又抽空做了新版的 Java 学习路线一条龙 补充了很多内容 比如面试题 常用 Java 类库 常用软件等 让整个路线 字数翻倍 同时区分了各知识点的学习必要性 使得无论是急着找工作还是想花
  • vue3中setup语法糖下父子组件之间如何传递数据

    vue3中setup语法糖下父子组件之间如何传递数据 先弄明白什么是父子组件 父传子 子传父 组件间通信都有哪些方式 父子组件通信和兄弟组件通信的区别 先弄明白什么是父子组件 父子组件 分为父组件和子组件 Vue3中 父组件指的是包含一个或
  • 前端实现动态导航栏样式

  • 如何在电脑中安装虚拟机?

    第一步 先下载vmware软件 新手小白第一次下载软件会特别麻烦 自己也有可能在官网找不到相对应的软件 比如现在刚接触的我 内心无数的懵逼烦躁 还有很多很多的负面情绪 也懒得一个一个去摸索 下面是压缩包以及后续所需的激活码 加我百度网盘 别
  • 51单片机定时器/计数器T0

    选择方式0 方式1 方式2时 T0 T1的工作情况相同 选择方式3时 T0 T1的工作情况不同 方式0 13位定时器 计数器 TH0的高8位 TL0的低5位 方式1 16位定时器 计数器 TH0的高8位 TL0的低8位 方式2 自动重装的8
  • git出现error: invalid object for ‘xxxxx‘

    该问题说明git本地仓库 git objects里丢失了部分文件 执行git hash object w xxxxx 即可修复 xxxxx是invalid object for后面的文件路径
  • 向上管理(中高层核心能力的表现)

    向上管理 即在工作中为了让公司或上级以及自己取得更好的结果而下意识地配合上级一起工作的过程 在职业生涯中 向上管理其实也是工作能力的一部分 一项重要的职业技能 管理者不仅要做好向下管理 他们还要学会向上管理 1 向下管理 主要涉及的是管理者
  • 基于51单片机霍尔传感器测速(仿真+源程序)

    资料编号 196 下面是该资料仿真演示视频 196 基于51单片机霍尔传感器测速 仿真 源程序 全套资料 功能简介 51单片机计数测速转速测量 在仿真中等价于测量外部脉冲频率 如果修改输入脉冲的频率 在数码管上可实时显示当前频率 功能 霍尔
  • MTCNN人脸及特征点检测---代码应用详解(基于ncnn架构)

    本博记录为卤煮理解 如有疏漏 请指正 转载请注明出处 卤煮 非文艺小燕儿 本博地址 MTCNN人脸及特征点检测 代码应用详解 本文主要讲述当你拿到MTCNN的caffemodel后 如何使用它对一张图里的人脸进行检测和特征点标定 相当于一个
  • Qt信号和槽机制

    Qt信号和槽机制 什么是信号和槽 当某个事件发生 就执行一个操作 发生的事情就是信号 执行的操作就是槽 函数 给二者加上主体 信号发出者发出信号 信号接收者执行操作 将二者联系起来 松耦合 connect 函数 connect sender
  • 网络安全必备1000道面试题集锦(附答案)

    前言 以下为网络安全各个方向涉及的面试题 星数越多代表问题出现的几率越大 祝各位都能找到满意的工作 注 本套面试题 已整理成pdf文档 但内容还在持续更新中 因为无论如何都不可能覆盖所有的面试问题 更多的还是希望由点达面 查漏补缺 一 渗透
  • Error: @vitejs/plugin-vue requires vue (>=3.2.13) or @vue/compiler-sfc to be present in the dependen

    1 没有下载安装axios运行依赖 2 或者缺少这个库没有安装 npm i vue compiler sfc 3 node版本冲不冲突
  • Android Button、TextView等控件使用Toolbar中默认的返回图标

    Button backButton findViewById R id back button backButton setBackgroundResource R drawable abc ic ab back material R dr
  • 网站框架识别方法

    cms一般有dedecms 织梦 dzcms phpweb phpwind phpcms ecshop dvbbs siteweaver aspcms 帝国 zblog wordpress等 一般cms都有特定的文件 只需要识别特定的文件便
  • 运行jetty-maven-plugin时,出现错误

    ERROR Failed to execute goal org eclipse jetty jetty maven plugin 9 4 0 v20161208 run default cli on project kind perm w
  • 【漏洞复现】CVE-2023-22809 sudo提权漏洞

    一 前言 漏洞简介 Sudo中的sudoedit对处理用户提供的环境变量 如SUDO EDITOR VISUAL和EDITOR 中传递的额外参数存在缺陷 当用户指定的编辑器包含绕过sudoers策略的 参数时 拥有sudoedit访问权限的
  • 「iOS」swift 和 objectivec 获得对象的 class 或者 Type 的方法

    一 oc 中使用 oc 中非常简单 一行搞定 NSString str1 test str1 class 这里的 str1 class 就是获取对象 class 的方法 二 swift 中使用 时间紧 任务重 上代码 var str Str
  • Windows 找不到文件 ‘chrome‘。请确认文件名是否正确后,再试一次。

    Windows 找不到文件 chrome 请确认文件名是否正确后 再试一次 错误 当不运行 IDEA 项目 通过快捷键进入浏览器时 可能会出现以下错误 原因 未设置 chrome 路径 导致找不到路径报错 解决办法 1 在桌面上找到 chr
  • 解决qemu虚拟机中内存偏小的问题

    问题描述 最近测试部报了一个问题 云平台中设置大于4GB的内存并设置numa 启动linux2 6 32内核的客户机 之后操作系统中查看实际内存是1 9G 比设置内存小了大概2 1GB 使用版本信息如下 QEMU version 3 0 0