qemu-kvm如何mem 热插拔

本文详细介绍如何在Linux环境下实现内存热插拔功能,包括必要的内核配置项、支持内存热插拔所需的固件更新及具体测试步骤。
linux内核已经实现了内存热插的支持,当一个linux系统不管运行在  物理环境  或者 虚拟环境   时只要宿主能提供内存热插拔机制,linux内核就能相应的增加或者减少内存。

目前已知的VMware已经可以做到内存热插。

改变内存容量有两种方式:

40 1.1 purpose of memory hotplug
41 ------------
42 Memory Hotplug allows users to increase/decrease the amount of memory.
43 Generally, there are two purposes.
44 
45 (A) For changing the amount of memory.
46    This is to allow a feature like capacity on demand.
47 (B) For installing/removing DIMMs or NUMA-nodes physically.
48    This is to exchange DIMMs/NUMA-nodes, reduce power consumption, etc.

前些天检查maillist时发现已经有人实现了DIMMs 方式更改内存大小。这种方式还没有并入mainline tree。

40 1.1 purpose of memory hotplug
41 ------------
42 Memory Hotplug allows users to increase/decrease the amount of memory.
43 Generally, there are two purposes.
44 
45 (A) For changing the amount of memory.
46    This is to allow a feature like capacity on demand.
47 (B) For installing/removing DIMMs or NUMA-nodes physically.
48    This is to exchange DIMMs/NUMA-nodes, reduce power consumption, etc.

可以测试下:

1)下载带有 dimm hotplug 功能的qemu-kvm:

git clone -b  memhp-v3-alt https://github.com/vliaskov/qemu-kvm/commits/memhp-v3-alt 
配置编译:

$./configure --target-list="i386-softmmu x86_64-softmmu i386-linux-user x86_64-linux-user" --enable-kvm  
$make

2)下载linux内核 linux-3.0.1.tar.bz2 做测试:(32bit)

linux memory hotplug 需要内核编译进如下选项:

- For all memory hotplug  
    Memory model -> Sparse Memory  (CONFIG_SPARSEMEM)  
location:  
Processor type and features    
    Memory model (Sparse Memory)  --->  
    Allow for memory hot-add       (CONFIG_MEMORY_HOTPLUG)  
location:  
Power management and ACPI options  
    ACPI (Advanced Configuration and Power Interface) Support  
        Memory Hotplug  

- To enable memory removal, the followings are also necessary  
    Allow for memory hot remove    (CONFIG_MEMORY_HOTREMOVE)  
    Page Migration                 (CONFIG_MIGRATION)  
- For ACPI memory hotplug, the followings are also necessary  
    Memory hotplug (under ACPI Support menu) (CONFIG_ACPI_HOTPLUG_MEMORY)  
    This option can be kernel module.  
  
- As a related configuration, if your box has a feature of NUMA-node hotplug  
  via ACPI, then this option is necessary too.  
    ACPI0004,PNP0A05 and PNP0A06 Container Driver (under ACPI Support menu)  
    (CONFIG_ACPI_CONTAINER).  
    This option can be kernel module too.  

配置好之后就可以用make 了。

编译完成后将得到的bzImage文件拷贝出来。

3)制作启动所需固件:

当内存hotplugged之后,内核需要知道新增加上的内存并且制作内存管理表,生成sysfs文件来操作内存。

由于这需要固件的支持,而现有的qemu mainline 中包含的bios并不支持相应的功能,所以需要下载bios来重新编译:

git clone -b memhp-v3-alt https://github.com/vliaskov/seabios/commits/memhp-v3-alt  
创建一个放固件的目录:bios

里面放的固件如下所示:(这些都是编译后qemu目录下的)

.  
├── bios.bin  
├── keymaps  
│   ├── common  
│   ├── en-us  
│   └── modifiers  
├── kvmvapic.bin  
├── linuxboot.bin  
├── multiboot.bin  
├── optionrom  
│   ├── kvmvapic.bin  
│   ├── linuxboot.bin  
│   └── multiboot.bin  
├── ppc_rom.bin  
├── pxe-e1000.rom  
├── sgabios.bin  
├── slof.bin  
├── spapr-rtas.bin  
├── vgabios.bin  
├── vgabios-cirrus.bin  
├── vgabios-qxl.bin  
├── vgabios-stdvga.bin  
└── vgabios-vmware.bin  

将编译好的固件 ./out/bios.bin  替换上面的  bios.bin。

4)制作文件系统busybox.img

可参考如下连接:

http://blog.youkuaiyun.com/gudujianjsk/article/details/7655855  
现在测试用的内核文件都得到了,用如下脚本开启测试:

./../qemu-kvm/i386-softmmu/qemu-system-i386 -enable-kvm -m 512 -kernel bzImage -L ./../bios -append "root=/dev/sda" -dimm id=dimm0,size=512M,node=0,populated=off -boot c -smp 1  -hda busybox.img -monitor stdio -k en-us -vnc :2  
启动后系统显示如下:


由于编译时配置将 CONFIG_ACPI_HOTPLUG_MEMORY 配置为内核模块了,所以这里需要首先加载一个内核模块:acpi_memhotplug.ko.

所有的sections都在sysfs下有它们的设备信息。每一个section都是在 /sys/devices/system/memory/目录下的内存块的一部分。

/sys/devices/system/memory/memoryXXX
在这里XXX就是内存ID。


在monitor中动态 增加dimm:

sae@lx15:/var/dbg/test-dimm/busybox$ sudo ./test-box.sh   
QEMU 1.2.50 monitor - type 'help' for more information  
(qemu) device_add dimm,id=dimm0  
此时在虚拟机中可见多了个memory1,其状态是offline:


此时新增加的内存还没有做好使用的准备。为了使用新增加的内存你需要把新增加的内存设置为online 状态


此时用free查看总计的内存数量:


测试完毕。

http://www.mjmwired.net/kernel/Documentation/memory-hotplug.txt

int __kvm_set_memory_region(struct kvm *kvm, const struct kvm_userspace_memory_region *mem) { int r; gfn_t base_gfn; unsigned long npages; struct kvm_memory_slot *slot; struct kvm_memory_slot old, new; struct kvm_memslots *slots = NULL, *old_memslots; int as_id, id; enum kvm_mr_change change; r = check_memory_region_flags(mem); if (r) goto out; r = -EINVAL; as_id = mem->slot >> 16; id = (u16)mem->slot; /* General sanity checks */ if (mem->memory_size & (PAGE_SIZE - 1)) goto out; if (mem->guest_phys_addr & (PAGE_SIZE - 1)) goto out; /* We can read the guest memory with __xxx_user() later on. */ if ((id < KVM_USER_MEM_SLOTS) && ((mem->userspace_addr & (PAGE_SIZE - 1)) || (mem->userspace_addr != untagged_addr(mem->userspace_addr)) || !access_ok((void __user *)(unsigned long)mem->userspace_addr, mem->memory_size))) goto out; if (as_id >= KVM_ADDRESS_SPACE_NUM || id >= KVM_MEM_SLOTS_NUM) goto out; if (mem->guest_phys_addr + mem->memory_size < mem->guest_phys_addr) goto out; slot = id_to_memslot(__kvm_memslots(kvm, as_id), id); base_gfn = mem->guest_phys_addr >> PAGE_SHIFT; npages = mem->memory_size >> PAGE_SHIFT; if (npages > KVM_MEM_MAX_NR_PAGES) goto out; new = old = *slot; new.id = id; new.base_gfn = base_gfn; new.npages = npages; new.flags = mem->flags; if (npages) { if (!old.npages) change = KVM_MR_CREATE; else { /* Modify an existing slot. */ if ((mem->userspace_addr != old.userspace_addr) || (npages != old.npages) || ((new.flags ^ old.flags) & KVM_MEM_READONLY)) goto out; if (base_gfn != old.base_gfn) change = KVM_MR_MOVE; else if (new.flags != old.flags) change = KVM_MR_FLAGS_ONLY; else { /* Nothing to change. */ r = 0; goto out; } } } else { if (!old.npages) goto out; change = KVM_MR_DELETE; new.base_gfn = 0; new.flags = 0; }
03-21
<think>好的,我现在需要分析用户提供的关于`__kvm_set_memory_region`函数的代码片段,并给出详细的解释。用户可能希望了解这个函数在KVM中的作用,尤其是与QEMU设置内存区域以及EPT页表管理的关系。 首先,我需要回忆一下KVM的内存管理机制。KVM通过内存槽(memory slots)来管理虚拟机的物理内存区域。当QEMU调用`KVM_SET_USER_MEMORY_REGION` ioctl时,KVM会使用这个函数来设置或更新对应的内存槽。这个函数负责验证参数,处理内存区域的创建、移动、修改或删除,并确保内存区域的正确映射,从而影响EPT页表的构建。 接下来,我需要逐段分析代码的结构和逻辑。函数开始处进行了多个参数检查,比如内存对齐、地址空间标识符(as_id)和槽位ID的有效性。如果参数不符合要求,返回错误。例如,检查`mem->memory_size`是否是页大小的倍数,guest物理地址是否页对齐,用户空间地址是否可访问等。 然后,代码获取现有的内存槽(如果存在),并计算新的内存槽参数,如基页框号(base_gfn)和页数(npages)。根据操作类型(创建、移动、修改标志、删除),函数会确定变更类型(change),并执行相应的处理。例如,如果npages不为零且旧槽位不存在,则是创建操作;如果基页框号改变,则是移动操作;如果只是标志改变,则是修改标志操作;如果npages为零,则是删除操作。 在这个过程中,需要理解每个条件分支的作用。例如,当修改现有槽位时,检查用户空间地址、页数和只读标志是否改变,如果这些参数有变化则不允许修改,直接返回错误。这说明KVM不允许动态改变这些参数,可能需要先删除再重新创建槽位。 此外,代码中对内存槽的变更会影响到EPT页表,因为EPT负责将客户机物理地址(GPA)映射到宿主机物理地址(HPA)。当内存区域被创建、移动或删除时,KVM需要更新EPT页表以反映这些变化。例如,删除内存区域时,对应的EPT页表项会被清除,可能导致后续访问时触发EPT_VIOLATION,进而由KVM处理。 我还需要联系之前用户的问题关于QEMU建立EPT页表的过程,明确这个函数在其中的作用。虽然QEMU不直接管理EPT,但通过调用`KVM_SET_USER_MEMORY_REGION`,触发KVM中的这个函数来注册或更新内存区域,从而间接影响EPT的构建。例如,当QEMU分配新的内存区域并注册时,KVM会创建对应的内存槽,之后在虚拟机访问该区域时,KVM动态填充EPT页表项。 可能存在一些疑问点,比如`slot`结构体的具体成员和作用,`kvm_memslots`的管理方式,以及变更类型(KVM_MR_*)如何影响后续处理流程。此外,函数中如何处理内存区域的覆盖或冲突的情况?例如,如果新建的内存槽与现有槽位有重叠,是否在此函数中检查?从代码中似乎没有显式的重叠检查,可能在调用此函数前已经由上层处理,或者在后续操作中处理。 还需要注意权限检查,例如用户空间地址的可访问性(`access_ok`),确保用户态程序不能通过此ioctl访问非法内存区域。安全措施在此函数中非常重要,防止虚拟机逃逸或宿主机内存泄露。 最后,总结这个函数的主要流程:参数验证 → 确定变更类型 → 更新内存槽 → 触发EPT相关更新。整个过程确保了虚拟机内存区域的正确映射,进而影响EPT页表的状态,保证内存虚拟化的正确性和效率。</think>以下是关于`__kvm_set_memory_region`函数的详细分析,结合其在KVM内存管理及EPT页表构建中的作用: --- ### 一、函数功能概述 `__kvm_set_memory_region`是KVM内核模块中**处理内存区域注册/更新的核心函数**。它通过`KVM_SET_USER_MEMORY_REGION` ioctl被QEMU调用,用于: 1. **定义虚拟机物理内存布局**:将客户机物理地址(GPA)映射到宿主机用户空间虚拟地址(HVA)。 2. **触发EPT页表更新**:间接影响EPT页表项的填充或失效。 3. **管理内存槽(Memory Slot)**:创建、移动、修改或删除内存区域。 --- ### 二、关键代码逻辑解析 #### 1. **参数校验(Sanity Checks)** ```c // 检查内存对齐、地址合法性等 if (mem->memory_size & (PAGE_SIZE - 1)) goto out; // 内存大小必须是页对齐的 if (mem->guest_phys_addr & (PAGE_SIZE - 1)) goto out; // GPA必须页对齐 if (as_id >= KVM_ADDRESS_SPACE_NUM || id >= KVM_MEM_SLOTS_NUM) goto out; // 地址空间和槽位ID是否合法 ``` - **作用**:确保QEMU传递的请求符合KVM内存管理约束,避免非法内存操作。 - **失败场景**:若对齐错误或ID越界,直接返回`-EINVAL`。 #### 2. **内存槽操作类型判断** ```c if (npages) { if (!old.npages) change = KVM_MR_CREATE; // 创建新槽位 else { // 修改现有槽位(仅允许标志或基地址变更) if (base_gfn != old.base_gfn) change = KVM_MR_MOVE; // 移动槽位 else if (new.flags != old.flags) change = KVM_MR_FLAGS_ONLY; // 仅修改标志 else r = 0; goto out; // 无变化,直接返回 } } else { if (!old.npages) goto out; change = KVM_MR_DELETE; // 删除槽位 } ``` - **操作类型**: - **CREATE**:首次注册内存区域(如虚拟机启动时初始化RAM)。 - **MOVE**:动态迁移内存区域(如内存热插拔后的地址调整)。 - **FLAGS_ONLY**:修改内存属性(如设置只读标志)。 - **DELETE**:移除内存区域(如内存热拔出)。 #### 3. **内存槽更新与EPT关联** ```c // 更新内存槽后,触发KVM内存系统更新 slots = kvm_dup_memslots(__kvm_memslots(kvm, as_id), change); // 将新槽位插入memslots r = kvm_set_memslot(kvm, &new, &old, as_id, change); ``` - **对EPT的影响**: - **CREATE/MOVE**:触发EPT页表项**批量失效**(通过`kvm_flush_remote_tlbs`),后续访问时按需填充新项。 - **DELETE**:直接清除EPT中对应GPA的映射,后续访问将导致EPT_VIOLATION。 - **FLAGS_ONLY**:修改EPT页表项的权限位(如设置写保护)。 --- ### 三、与QEMU的交互流程 #### 1. **QEMU触发调用** 当QEMU执行以下操作时调用该函数: - 虚拟机启动时初始化内存(`pc.ram`)。 - 动态添加/删除内存(如内存热插拔)。 - 设备模拟需要MMIO映射(如PCI设备BAR空间)。 #### 2. **参数传递示例** ```c struct kvm_userspace_memory_region region = { .slot = 0, // 内存槽ID .guest_phys_addr = 0, // GPA起始地址 .memory_size = 4ULL * 1024 * 1024 * 1024, // 内存大小(4GB) .userspace_addr = (uintptr_t)ram_ptr, // QEMU进程的HVA }; ioctl(kvm_fd, KVM_SET_USER_MEMORY_REGION, &region); ``` - **关键字段**: - `slot`:标识内存区域的唯一ID(0通常用于主内存)。 - `userspace_addr`:宿主机用户空间地址(HVA),KVM通过宿主机页表(HVA→HPA)间接完成GPA→HPA的EPT映射。 --- ### 四、对EPT页表的具体影响 #### 1. **CREATE操作** - **EPT状态**:新增内存区域对应的EPT页表项初始为空。 - **首次访问**:触发EPT_VIOLATION → KVM根据HVA→HPA填充EPT项。 #### 2. **MOVE操作** - **EPT处理**: ```c kvm_zap_gfn_range(kvm, old.base_gfn, old.base_gfn + old.npages); // 失效旧GPA的EPT项 // 新GPA的EPT项将在后续访问时按需填充 ``` #### 3. **DELETE操作** - **EPT状态**:直接清除对应GPA范围的EPT项,后续访问触发`KVM_EXIT_MMIO`(由QEMU处理设备模拟)。 --- ### 五、安全与权限控制 #### 1. **用户空间地址校验** ```c if ((id < KVM_USER_MEM_SLOTS) && (!access_ok((void __user *)(unsigned long)mem->userspace_addr, mem->memory_size))) goto out; ``` - **目的**:防止恶意用户通过伪造`userspace_addr`访问非法内存。 - **约束**:仅允许合法用户态地址注册(通过`access_ok`检查)。 #### 2. **标志位过滤** ```c r = check_memory_region_flags(mem); ``` - **内部实现**:检查`mem->flags`是否包含非法标志(如KVM_MEM_LOG_DIRTY_PAGES需要内核支持)。 --- ### 六、性能优化相关 #### 1. **大页支持** 若QEMU使用大页(Huge Page)分配内存: - KVM会尝试直接填充EPT大页项(2MB/1GB),减少页表层级。 - **代码路径**:`kvm_mmu_get_page` → `host_pfn_mapping_level`。 #### 2. **脏页跟踪** ```c if (new.flags & KVM_MEM_LOG_DIRTY_PAGES) kvm_slot_enable_logging(&new); // 启用EPT写保护 ``` - **机制**:通过EPT的写保护位(Write Protection)捕获脏页,记录到位图中。 --- ### 七、典型错误码 | 错误码 | 触发条件 | |--------|----------| | `-EINVAL` | 参数不合法(如未对齐、ID越界) | | `-EFAULT` | 用户空间地址不可访问 | | `-EEXIST` | 内存槽冲突(需先删除旧槽位) | --- ### 总结 `__kvm_set_memory_region`是KVM内存虚拟化的核心枢纽: 1. **桥梁作用**:将QEMU定义的内存布局转化为KVM内存槽,进而影响EPT映射。 2. **动态性**:支持内存热插拔、设备MMIO动态映射等场景。 3. **安全性**:严格校验参数,防止宿主机内存泄露。 理解此函数是分析KVM内存管理、EPT缺页处理及虚拟机性能调优的关键。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值