1987 字
10 分钟
kvm_set_memslot-虚拟机内存修改函数
kvm_set_memslot-虚拟机内存修改函数
简介
简单来说,kvm_set_memslot 是 KVM 中用于执行所有与客户机(Guest)内存布局相关的原子性修改的核心函数。它就像一个总指挥,负责安全、有序地创建、删除、移动或修改虚拟机内存区域(memslot)的属性。
每当用户空间(如 QEMU)通过 KVM_SET_USER_MEMORY_REGION ioctl 请求改变虚拟机的内存布局时,最终都会调用到这个函数。
virt/kvm/kvm_main.c
static int kvm_set_memslot(struct kvm *kvm,
struct kvm_memory_slot *old,
struct kvm_memory_slot *new,
enum kvm_mr_change change)
{
struct kvm_memory_slot *invalid_slot;
int r;
/*
* Released in kvm_swap_active_memslots().
*
* Must be held from before the current memslots are copied until after
* the new memslots are installed with rcu_assign_pointer, then
* released before the synchronize srcu in kvm_swap_active_memslots().
*
* When modifying memslots outside of the slots_lock, must be held
* before reading the pointer to the current memslots until after all
* changes to those memslots are complete.
*
* These rules ensure that installing new memslots does not lose
* changes made to the previous memslots.
*
* 这个锁在 kvm_swap_active_memslots() 中释放。
*
* 必须从复制当前 memslots 之前一直持有,直到用 rcu_assign_pointer 安装
* 新的 memslots 之后。然后在 kvm_swap_active_memslots() 中的
* synchronize_srcu 之前释放。
*
* 当在 slots_lock 之外修改 memslots 时,必须在读取当前 memslots 指针之前
* 持有该锁,直到对这些 memslots 的所有更改完成之后。
*
* 这些规则确保安装新的 memslots 不会丢失对先前 memslots 的更改。
*/
mutex_lock(&kvm->slots_arch_lock);
/*
* Invalidate the old slot if it's being deleted or moved. This is
* done prior to actually deleting/moving the memslot to allow vCPUs to
* continue running by ensuring there are no mappings or shadow pages
* for the memslot when it is deleted/moved. Without pre-invalidation
* (and without a lock), a window would exist between effecting the
* delete/move and committing the changes in arch code where KVM or a
* guest could access a non-existent memslot.
*
* Modifications are done on a temporary, unreachable slot. The old
* slot needs to be preserved in case a later step fails and the
* invalidation needs to be reverted.
*
* 如果旧的内存槽正在被删除或移动,则先将其无效化。这样做是为了让 vCPU
* 可以继续运行,通过确保在删除/移动内存槽时,不存在该槽的映射或影子页。
* 如果没有预先无效化(并且没有锁),在执行删除/移动和在架构代码中提交
* 变更之间会存在一个时间窗口,KVM 或客户机可能会访问一个不存在的内存槽。
*
* 修改是在一个临时的、不可达的槽上进行的。旧的槽需要被保留,以防后续
* 步骤失败时需要回滚无效化操作。
*/
if (change == KVM_MR_DELETE || change == KVM_MR_MOVE) {
invalid_slot = kzalloc(sizeof(*invalid_slot), GFP_KERNEL_ACCOUNT);
if (!invalid_slot) {
mutex_unlock(&kvm->slots_arch_lock);
return -ENOMEM;
}
kvm_invalidate_memslot(kvm, old, invalid_slot);
}
// 调用准备函数,进行检查和资源分配(如 dirty_bitmap)
r = kvm_prepare_memory_region(kvm, old, new, change);
if (r) {
/*
* For DELETE/MOVE, revert the above INVALID change. No
* modifications required since the original slot was preserved
* in the inactive slots. Changing the active memslots also
* release slots_arch_lock.
*
* 对于 DELETE/MOVE 操作,回滚上面的 INVALID 变更。
* 由于原始的槽被保留在非活动槽中,所以不需要修改。
* 更改活动内存槽也会释放 slots_arch_lock。
*/
if (change == KVM_MR_DELETE || change == KVM_MR_MOVE) {
kvm_activate_memslot(kvm, invalid_slot, old);
kfree(invalid_slot);
} else {
mutex_unlock(&kvm->slots_arch_lock);
}
return r;
}
/*
* For DELETE and MOVE, the working slot is now active as the INVALID
* version of the old slot. MOVE is particularly special as it reuses
* the old slot and returns a copy of the old slot (in working_slot).
* For CREATE, there is no old slot. For DELETE and FLAGS_ONLY, the
* old slot is detached but otherwise preserved.
*
* 对于 DELETE 和 MOVE,工作槽现在作为旧槽的 INVALID 版本处于活动状态。
* MOVE 特别之处在于它重用了旧槽,并返回旧槽的一个副本(在 working_slot 中)。
* 对于 CREATE,没有旧槽。对于 DELETE 和 FLAGS_ONLY,旧槽被分离但被保留。
*/
// 根据不同的变更类型,执行相应的内存槽操作
if (change == KVM_MR_CREATE)
// 如果是创建新的内存槽
kvm_create_memslot(kvm, new);
else if (change == KVM_MR_DELETE)
// 如果是删除内存槽
kvm_delete_memslot(kvm, old, invalid_slot);
else if (change == KVM_MR_MOVE)
// 如果是移动内存槽(修改其客户机物理地址 GPA)
kvm_move_memslot(kvm, old, new, invalid_slot);
else if (change == KVM_MR_FLAGS_ONLY)
// 如果只是修改内存槽的标志位(例如,开启/关闭脏页日志)
kvm_update_flags_memslot(kvm, old, new);
else
BUG();
/* Free the temporary INVALID slot used for DELETE and MOVE. */
// 释放用于 DELETE 和 MOVE 的临时 INVALID 槽。
if (change == KVM_MR_DELETE || change == KVM_MR_MOVE)
kfree(invalid_slot);
/*
* No need to refresh new->arch, changes after dropping slots_arch_lock
* will directly hit the final, active memslot. Architectures are
* responsible for knowing that new->arch may be stale.
*
* 无需刷新 new->arch,因为在释放 slots_arch_lock 之后的更改将直接作用于
* 最终的活动内存槽。各个体系结构需要自己负责处理 new->arch 可能是陈旧数据的情况。
*/
kvm_commit_memory_region(kvm, old, new, change);
return 0;
}
这个函数具体做了什么?
kvm_set_memslot 的工作可以根据传入的 change 类型分为几个主要场景:
KVM_MR_CREATE: 创建一个新的内存区域(比如给虚拟机添加内存)。KVM_MR_DELETE: 删除一个现有的内存区域。KVM_MR_MOVE: 移动一个内存区域,即改变它在客户机物理地址空间(GPA)中的位置,但其在宿主机上的用户空间地址(HVA)不变。KVM_MR_FLAGS_ONLY: 只修改一个内存区域的标志位,比如开启或关闭脏页日志(KVM_MEM_LOG_DIRTY_PAGES),或者将其设置为只读。
核心工作流程是怎样的?
为了保证在多 VCPU 环境下安全地修改内存布局,kvm_set_memslot 遵循一个非常严谨和复杂的流程,可以概括为以下几个关键步骤:
加锁 (
mutex_lock(&kvm->slots_arch_lock)): 首先,它会获取一个重要的锁,以确保在整个多步骤的修改过程中,不会有其他线程来干扰内存槽的配置,防止竞态条件。预先失效 (仅针对
DELETE和MOVE):- 这是一个非常关键的安全步骤。在真正删除或移动一个内存槽之前,它会先创建一个临时的、标记为“无效”(INVALID)的副本。
- 然后,它会激活这个无效副本,并强制所有 VCPU 刷新它们的 TLB 和影子页表,确保没有任何 VCPU 还持有对旧内存区域的有效映射。
- 这样就杜绝了在后续操作中,有 VCPU 访问到一个已经被删除或正在被移动的、处于不一致状态的内存区域的风险。
准备阶段 (
kvm_prepare_memory_region):- 这是您之前关注的函数。在此阶段,KVM 会为新的内存槽配置做准备工作。
- 最重要的任务就是分配和初始化脏页位图(dirty bitmap)。如果开启了“初始全脏”优化,位图会在这里被全部设置为 1;否则,它会被创建并保持为 0。
- 如果准备阶段失败(例如内存不足),函数会安全地回滚之前的操作并返回错误。
执行变更:
- 根据
change的类型,调用相应的辅助函数(kvm_create_memslot,kvm_delete_memslot,kvm_move_memslot,kvm_update_flags_memslot)。 - 这些函数会更新 KVM 内部的内存槽数据结构(比如红黑树),将新的配置应用到“非活动”的内存槽集合中,然后通过一个原子操作(
rcu_assign_pointer)将其切换为“活动”状态。
- 根据
提交阶段 (
kvm_commit_memory_region):- 这是最后一步,负责调用特定于体系结构的代码(
kvm_arch_commit_memory_region)来最终完成变更。 - 正是在这个阶段的深层调用链中,如果未使用优化,KVM 会执行传统的、耗时的写保护操作(即调用
kvm_mmu_slot_remove_write_access)。 - 同时,它还会清理旧的、不再需要的资源,比如释放旧的
memslot结构体或旧的脏页位图。
- 这是最后一步,负责调用特定于体系结构的代码(
总结
kvm_set_memslot 是一个高度封装的、健壮的接口。它通过锁、RCU(读-拷贝-更新)、原子操作和分阶段提交等多种机制,确保了对虚拟机内存布局的修改是原子且安全的,即使在有多个 VCPU 同时运行的情况下也不会出错。它是连接 KVM 通用层内存管理逻辑和具体体系结构 MMU 实现的关键桥梁。
kvm_set_memslot-虚拟机内存修改函数
https://scudays.github.io/posts/icsas/risc-v-kvm热迁移优化-part3-以小块逐步启用脏页日志/kvm_set_memslot-虚拟机内存修改函数/