KVM VCPU requests(vcpu请求)
通过翻译内核文档和源码学习vcpu requests
kernel v6.2
文章目录
- KVM VCPU requests(vcpu请求)
-
- VCPU Kicks(vcpu激活)
- VCPU Mode
- VCPU请求内部结构
-
- 与体系结构无关的请求
- VCPU请求掩码
-
- VCPU请求标志
- 具有关联状态的VCPU请求
- 确保请求可见
-
- 减少IPI
- 等待确认
- 无请求的 VCPU Kicks
- VCPU睡眠
- vcpu->requests 处理
- 参考
KVM支持一个内部API,允许线程请求VCPU线程执行某些活动。例如,线程可能请求一个VCPU刷新其TLB通过VCPU requests。该API包括以下函数:
/* 检查是否有待处理的请求针对VCPU @vcpu。 */ bool kvm_request_pending(struct kvm_vcpu *vcpu); /* 检查VCPU @vcpu是否有请求@req待处理。 */ bool kvm_test_request(int req, struct kvm_vcpu *vcpu); /* 清除VCPU @vcpu的请求@req。 */ void kvm_clear_request(int req, struct kvm_vcpu *vcpu); /* * 检查VCPU @vcpu是否有请求@req待处理。当请求待处理时,它将被清除,并发出一个内存屏障,与kvm_make_request()中的内存屏障配对。 */ bool kvm_check_request(int req, struct kvm_vcpu *vcpu); /* * 使VCPU @vcpu的请求@req。在设置请求之前,发出一个内存屏障,与kvm_check_request()中的内存屏障配对。 */ void kvm_make_request(int req, struct kvm_vcpu *vcpu); /* 使结构体kvm @kvm的所有VCPU请求@req。 */ bool kvm_make_all_cpus_request(struct kvm *kvm, unsigned int req);
通常,请求方希望VCPU尽快在请求后执行活动。这意味着大多数请求(kvm_make_request()调用)后面会调用kvm_vcpu_kick(),
如下:
case APIC_DM_STARTUP: result = 1; // 将SIPi(Startup Inter Processor interrupt)的向量设置为传入的vector apic->sipi_vector = vector; // 确保sipi_vector对接收方可见 smp_wmb(); // 设置KVM_APIC_SIPI位,表示有SIPi事件等待处理 set_bit(KVM_APIC_SIPI, &apic->pending_events); // 发送KVM_REQ_EVENT请求,通知VCPU有新事件需要处理 kvm_make_request(KVM_REQ_EVENT, vcpu); // 唤醒VCPU以处理事件 kvm_vcpu_kick(vcpu); break; /* * 唤醒一个处于睡眠状态的vcpu的线程,或者将处于guest模式的vcpu切换到主机的内核模式。 */ void kvm_vcpu_kick(struct kvm_vcpu *vcpu)
而kvm_make_all_cpus_request()将所有VCPU的触发内置其中。
bool kvm_make_all_cpus_request_except(struct kvm *kvm, unsigned int req, struct kvm_vcpu *except) { struct kvm_vcpu *vcpu; struct cpumask *cpus; unsigned long i; bool called; int me; // 获取当前CPU编号 me = get_cpu(); // 获取本地CPU掩码 cpus = this_cpu_cpumask_var_ptr(cpu_kick_mask); cpumask_clear(cpus); // 遍历KVM中的每个VCPU kvm_for_each_vcpu(i, vcpu, kvm) { // 如果VCPU是要排除的VCPU,则跳过 if (vcpu == except) continue; // 向该VCPU发出请求,并更新本地CPU掩码 kvm_make_vcpu_request(vcpu, req, cpus, me); } // 调用kvm_kick_many_cpus来唤醒多个CPU,根据req中是否包含KVM_REQUEST_WAIT来决定是否等待 called = kvm_kick_many_cpus(cpus, !!(req & KVM_REQUEST_WAIT)); // 释放当前CPU编号 put_cpu(); return called; } bool kvm_make_all_cpus_request(struct kvm *kvm, unsigned int req) { return kvm_make_all_cpus_request_except(kvm, req, NULL); }
VCPU Kicks(vcpu激活)
VCPU kick的目标是使VCPU线程退出客户模式,以执行一些KVM维护工作。为此,将发送一个核间中断处理请求(IPI),强制进行客户模式退出。然而,在激活时,VCPU线程可能并未处于客户模式。因此,根据VCPU线程的模式和状态,激活可能采取另外两种操作。以下列出了这三种操作:
- 发送一个IPI。这将强制进行客户模式退出。
- 唤醒处于休眠状态的VCPU。休眠的VCPU是在等待队列上等待的处于客户模式之外的VCPU线程。唤醒它们将线程从等待队列中移除,允许线程再次运行。可以通过KVM_REQUEST_NO_WAKEUP来抑制此行为。
- 什么也不做。当VCPU不处于客户模式且VCPU线程未处于休眠状态时,则无需执行任何操作。
VCPU Mode
VCPU具有一个模式状态字段,即
-
OUTSIDE_GUEST_MODE
VCPU线程处于客户模式之外。
-
IN_GUEST_MODE
VCPU线程处于客户模式中。
-
EXITING_GUEST_MODE
VCPU线程正在从IN_GUEST_MODE过渡到OUTSIDE_GUEST_MODE。
-
READING_SHADOW_PAGE_TABLES
VCPU线程处于客户模式之外,但希望发送某些VCPU请求(即KVM_REQ_TLB_FLUSH)的发送者等待,直到VCPU线程完成读取页表。
VCPU请求内部结构
VCPU请求简单地是
clear_bit(KVM_REQ_UNBLOCK & KVM_REQUEST_MASK, &vcpu->requests);
然而,VCPU请求的使用者应该避免这样做,因为这将破坏抽象。前8位保留给与体系结构无关的请求,所有附加位可用于与体系结构相关的请求。
与体系结构无关的请求
-
KVM_REQ_TLB_FLUSH
KVM的通用MMU通知程序可能需要刷新客户的所有TLB项,调用
kvm_flush_remote_tlbs() 来完成。选择使用通用kvm_flush_remote_tlbs() 实现的架构将需要处理这个VCPU请求。 -
KVM_REQ_VM_DEAD
此请求通知所有VCPU,VM已死亡且不可用,例如由于致命错误或因为VM的状态已被有意销毁。
-
KVM_REQ_UNBLOCK
此请求通知VCPU退出
kvm_vcpu_block 。例如,它可以由在主机上代表VCPU运行的定时器处理程序使用,或者确保唤醒vcpu为了设备来更新中断路由信息。 -
KVM_REQ_OUTSIDE_GUEST_MODE
这个 “请求” 确保目标VCPU在请求发送者继续之前已经退出了客户模式。目标不需要执行任何操作,因此实际上不会为目标记录任何请求。这个请求类似于 “kick”,但与 “kick” 不同,它保证VCPU实际上已经退出了客户模式。Kick仅保证将来某个时候VCPU将退出,例如,先前的kick可能已经启动了这个过程,但不能保证即将被kick的VCPU已完全退出了客户模式。
VCPU请求掩码
在使用位操作之前,应通过
VCPU请求标志
-
KVM_REQUEST_NO_WAKEUP
此标志应用于仅需要处于客户模式的VCPUs立即注意的请求,即,休眠的VCPU不需要为处理这些请求而唤醒。当休眠的VCPU稍后因某种原因被唤醒时,它们将处理这些请求。
-
KVM_REQUEST_WAIT
当使用
kvm_make_all_cpus_request() 发出带有此标志的请求时,调用者将等待每个VCPU在继续之前确认其IPI。此标志仅适用于将接收IPI的VCPU。例如,如果VCPU正在休眠,因此不需要IPI,则请求线程不等待。这意味着此标志可以安全地与KVM_REQUEST_NO_WAKEUP 结合使用。
具有关联状态的VCPU请求
希望接收的VCPU处理新状态的请求方需要确保新写入的状态在接收VCPU线程的CPU观察到请求时是可观察的。这意味着在写入新状态后并在设置VCPU请求位之前,必须插入写内存屏障。此外,在接收VCPU线程方面,在读取请求位后并在继续读取与之关联的新状态之前,必须插入相应的读屏障。
函数对kvm_check_request()和kvm_make_request()提供了内存屏障,允许API在内部处理此要求。
确保请求可见
在向VCPUs发出请求时,我们希望确保接收的VCPU在没有处理请求的情况下不会在客户模式下执行太长时间。只要我们确保VCPU线程在进入客户模式之前检查
一个解决方案,除了s390外,所有体系结构都适用,即:
- 在禁用中断和最后一次
kvm_request_pending() 检查之间,将vcpu->mode 设置为IN_GUEST_MODE ; - 在进入客户模式时以原子方式启用中断。
这个解决方案还要求在请求线程和接收VCPU线程中都要小心地放置内存屏障。通过内存屏障,我们可以排除VCPU线程在最后一次检查时观察到
CPU1 CPU2 ================= ================= local_irq_disable(); WRITE_ONCE(vcpu->mode, IN_GUEST_MODE); kvm_make_request(REQ, vcpu); smp_mb(); smp_mb(); if (kvm_request_pending(vcpu)) { if (READ_ONCE(vcpu->mode) == IN_GUEST_MODE) { ...abort guest entry... ...send IPI... } }
正如前文所述,IPI仅对处于客户模式的VCPU线程或已禁用中断的线程有用。这就是为什么在设置
简而言之,这段代码中的设计目的是为了确保在进行一系列操作(如设置
减少IPI
由于只需要一个 IPI 来让一个 VCPU 检查任何/所有请求,它们可以被合并。这可以通过在第一个发送 kick 的 IPI 中同时将 VCPU 模式更改为非
等待确认
那些设置了
无请求的 VCPU Kicks
由于是否发送 IPI 的决定依赖于两个变量的 Dekker 内存屏障模式,因此可以明确指出,无请求的 VCPU Kick 几乎总是不正确的。没有确保无需生成 IPI 的 Kick 仍会导致接收 VCPU 执行操作的保证,就像请求伴随的 Kick 的最终
唯一的例外是 x86 的
VCPU睡眠
VCPU 线程在调用可能使其进入休眠状态的函数之前和/或之后,例如
vcpu->requests 处理
上次
该函数开始时会坚持vcpu上是否有未处理的requests,如果有未处理的requests,则调用对应的处理函数或者退出到用户空间qemu进行处理:
static int vcpu_enter_guest(struct kvm_vcpu *vcpu) { ... if (kvm_request_pending(vcpu)) { // 检查并处理 VM 是否处于死亡状态的请求 if (kvm_check_request(KVM_REQ_VM_DEAD, vcpu)) { r = -EIO; goto out; } // 检查并处理脏环检查的请求 if (kvm_dirty_ring_check_request(vcpu)) { r = 0; goto out; } // 检查并处理获取嵌套状态页面的请求 if (kvm_check_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu)) { if (unlikely(!kvm_x86_ops.nested_ops->get_nested_state_pages(vcpu))) { r = 0; goto out; } } // 处理其他请求,包括时钟更新、迁移定时器、全局时钟更新等 if (kvm_check_request(KVM_REQ_MMU_FREE_OBSOLETE_ROOTS, vcpu)) kvm_mmu_free_obsolete_roots(vcpu); if (kvm_check_request(KVM_REQ_MIGRATE_TIMER, vcpu)) __kvm_migrate_timers(vcpu); if (kvm_check_request(KVM_REQ_MASTERCLOCK_UPDATE, vcpu)) kvm_update_masterclock(vcpu->kvm); if (kvm_check_request(KVM_REQ_GLOBAL_CLOCK_UPDATE, vcpu)) kvm_gen_kvmclock_update(vcpu); if (kvm_check_request(KVM_REQ_CLOCK_UPDATE, vcpu)) { r = kvm_guest_time_update(vcpu); if (unlikely(r)) goto out; } if (kvm_check_request(KVM_REQ_MMU_SYNC, vcpu)) kvm_mmu_sync_roots(vcpu); if (kvm_check_request(KVM_REQ_LOAD_MMU_PGD, vcpu)) kvm_mmu_load_pgd(vcpu); // 检查并处理 TLB 刷新的请求 if (kvm_check_request(KVM_REQ_TLB_FLUSH, vcpu)) kvm_vcpu_flush_tlb_all(vcpu); // 服务本地 TLB 刷新请求 kvm_service_local_tlb_flush_requests(vcpu); // 在 Hyper-V 的精确刷新失败时,回退到"full"客户机刷新 if (kvm_check_request(KVM_REQ_HV_TLB_FLUSH, vcpu) && kvm_hv_vcpu_flush_tlb(vcpu)) kvm_vcpu_flush_tlb_guest(vcpu); // 处理其他请求,如 TPR 访问报告、三重故障、APF 停机、IOAPIC EOI 退出等 if (kvm_check_request(KVM_REQ_REPORT_TPR_ACCESS, vcpu)) { vcpu->run->exit_reason = KVM_EXIT_TPR_ACCESS; r = 0; goto out; } if (kvm_test_request(KVM_REQ_TRIPLE_FAULT, vcpu)) { if (is_guest_mode(vcpu)) kvm_x86_ops.nested_ops->triple_fault(vcpu); if (kvm_check_request(KVM_REQ_TRIPLE_FAULT, vcpu)) { vcpu->run->exit_reason = KVM_EXIT_SHUTDOWN; vcpu->mmio_needed = 0; r = 0; goto out; } } if (kvm_check_request(KVM_REQ_APF_HALT, vcpu)) { /* Page is swapped out. Do synthetic halt */ vcpu->arch.apf.halted = true; r = 1; goto out; } if (kvm_check_request(KVM_REQ_STEAL_UPDATE, vcpu)) record_steal_time(vcpu); #ifdef CONFIG_KVM_SMM if (kvm_check_request(KVM_REQ_SMI, vcpu)) process_smi(vcpu);` #endif if (kvm_check_request(KVM_REQ_NMI, vcpu)) process_nmi(vcpu); if (kvm_check_request(KVM_REQ_PMU, vcpu)) kvm_pmu_handle_event(vcpu); if (kvm_check_request(KVM_REQ_PMI, vcpu)) kvm_pmu_deliver_pmi(vcpu); if (kvm_check_request(KVM_REQ_IOAPIC_EOI_EXIT, vcpu)) { BUG_ON(vcpu->arch.pending_ioapic_eoi > 255); if (test_bit(vcpu->arch.pending_ioapic_eoi, vcpu->arch.ioapic_handled_vectors)) { vcpu->run->exit_reason = KVM_EXIT_IOAPIC_EOI; vcpu->run->eoi.vector = vcpu->arch.pending_ioapic_eoi; r = 0; goto out; } } if (kvm_check_request(KVM_REQ_SCAN_IOAPIC, vcpu)) vcpu_scan_ioapic(vcpu); if (kvm_check_request(KVM_REQ_LOAD_EOI_EXITMAP, vcpu)) vcpu_load_eoi_exitmap(vcpu); if (kvm_check_request(KVM_REQ_APIC_PAGE_RELOAD, vcpu)) kvm_vcpu_reload_apic_access_page(vcpu); if (kvm_check_request(KVM_REQ_HV_CRASH, vcpu)) { vcpu->run->exit_reason = KVM_EXIT_SYSTEM_EVENT; vcpu->run->system_event.type = KVM_SYSTEM_EVENT_CRASH; vcpu->run->system_event.ndata = 0; r = 0; goto out; } if (kvm_check_request(KVM_REQ_HV_RESET, vcpu)) { vcpu->run->exit_reason = KVM_EXIT_SYSTEM_EVENT; vcpu->run->system_event.type = KVM_SYSTEM_EVENT_RESET; vcpu->run->system_event.ndata = 0; r = 0; goto out; } if (kvm_check_request(KVM_REQ_HV_EXIT, vcpu)) { struct kvm_vcpu_hv *hv_vcpu = to_hv_vcpu(vcpu); vcpu->run->exit_reason = KVM_EXIT_HYPERV; vcpu->run->hyperv = hv_vcpu->exit; r = 0; goto out; } // 处理 Hyper-V 定时器等 if (kvm_check_request(KVM_REQ_HV_STIMER, vcpu)) kvm_hv_process_stimers(vcpu); if (kvm_check_request(KVM_REQ_APICV_UPDATE, vcpu)) kvm_vcpu_update_apicv(vcpu); if (kvm_check_request(KVM_REQ_APF_READY, vcpu)) kvm_check_async_pf_completion(vcpu); if (kvm_check_request(KVM_REQ_MSR_FILTER_CHANGED, vcpu)) static_call(kvm_x86_msr_filter_changed)(vcpu); // 处理更新 CPU 脏日志记录等请求 if (kvm_check_request(KVM_REQ_UPDATE_CPU_DIRTY_LOGGING, vcpu)) static_call(kvm_x86_update_cpu_dirty_logging)(vcpu); } ...
参考
https://docs.kernel.org/virt/kvm/vcpu-requests.html
https://cloud.tencent.com/developer/article/1069832