KVM: Dirty ring interface
Subsystems (8)
cover KVM: Dirty ring interface
Branch is here: https://github.com/xzpeter/linux/tree/kvm-dirty-ring
Overview
============
This is a continued work from Lei Cao <[email protected]> and Paolo on the KVM dirty ring interface. To make it simple, I'll still start with version 1 as RFC.
The new dirty ring interface is another way to collect dirty pages for the virtual machine, but it is different from the existing dirty logging interface in a few ways, majorly:
- Data format: The dirty data was in a ring format rather than a bitmap format, so the size of data to sync for dirty logging does not depend on the size of guest memory any more, but speed of dirtying. Also, the dirty ring is per-vcpu (currently plus another per-vm ring, so total ring number is N+1), while the dirty bitmap is per-vm.
- Data copy: The sync of dirty pages does not need data copy any more, but instead the ring is shared between the userspace and kernel by page sharings (mmap() on either the vm fd or vcpu fd)
- Interface: Instead of using the old KVM_GET_DIRTY_LOG, KVM_CLEAR_DIRTY_LOG interfaces, the new ring uses a new interface called KVM_RESET_DIRTY_RINGS when we want to reset the collected dirty pages to protected mode again (works like KVM_CLEAR_DIRTY_LOG, but ring based)
And more.
I would appreciate if the reviewers can start with patch "KVM:
Implement ring-based dirty memory tracking", especially the document update part for the big picture. Then I'll avoid copying into most of them into cover letter again.
I marked this series as RFC because I'm at least uncertain on this change of vcpu_enter_guest():
if (kvm_check_request(KVM_REQ_DIRTY_RING_FULL, vcpu)) {
vcpu->run->exit_reason = KVM_EXIT_DIRTY_RING_FULL;
/*
* If this is requested, it means that we've
* marked the dirty bit in the dirty ring BUT
* we've not written the date. Do it now.
*/
r = kvm_emulate_instruction(vcpu, 0);
r = r >= 0 ? 0 : r;
goto out;
}I did a kvm_emulate_instruction() when dirty ring reaches softlimit and want to exit to userspace, however I'm not really sure whether there could have any side effect. I'd appreciate any comment of above, or anything else.
Tests
===========
I wanted to continue work on the QEMU part, but after I noticed that the interface might still prone to change, I posted this series first. However to make sure it's at least working, I've provided unit tests together with the series. The unit tests should be able to test the series in at least three major paths:
(1) ./dirty_log_test -M dirty-ring
This tests async ring operations: this should be the major work mode for the dirty ring interface, say, when the kernel is queuing more data, the userspace is collecting too. Ring can hardly reaches full when working like this, because in most cases the collection could be fast.
(2) ./dirty_log_test -M dirty-ring -c 1024
This set the ring size to be very small so that ring soft-full always triggers (soft-full is a soft limit of the ring state, when the dirty ring reaches the soft limit it'll do a userspace exit and let the userspace to collect the data).
(3) ./dirty_log_test -M dirty-ring-wait-queue
This sololy test the extreme case where ring is full. When the ring is completely full, the thread (no matter vcpu or not) will be put onto a per-vm waitqueue, and KVM_RESET_DIRTY_RINGS will wake the threads up (assuming until which the ring will not be full any more).
Thanks,
Cao, Lei (2):
KVM: Add kvm/vcpu argument to mark_dirty_page_in_slot
KVM: X86: Implement ring-based dirty memory tracking
Paolo Bonzini (1):
KVM: Move running VCPU from ARM to common code
Peter Xu (12):
KVM: Add build-time error check on kvm_run size
KVM: Implement ring-based dirty memory tracking
KVM: Make dirty ring exclusive to dirty bitmap log
KVM: Introduce dirty ring wait queue
KVM: selftests: Always clear dirty bitmap after iteration
KVM: selftests: Sync uapi/linux/kvm.h to tools/
KVM: selftests: Use a single binary for dirty/clear log test
KVM: selftests: Introduce after_vcpu_run hook for dirty log test
KVM: selftests: Add dirty ring buffer test
KVM: selftests: Let dirty_log_test async for dirty ring test
KVM: selftests: Add "-c" parameter to dirty log test
KVM: selftests: Test dirty ring waitqueue
Documentation/virt/kvm/api.txt | 116 +++++ arch/arm/include/asm/kvm_host.h | 2 - arch/arm64/include/asm/kvm_host.h | 2 - arch/x86/include/asm/kvm_host.h | 5 + arch/x86/include/uapi/asm/kvm.h | 1 + arch/x86/kvm/Makefile | 3 +- arch/x86/kvm/mmu/mmu.c | 6 + arch/x86/kvm/vmx/vmx.c | 7 + arch/x86/kvm/x86.c | 12 + include/linux/kvm_dirty_ring.h | 67 +++ include/linux/kvm_host.h | 37 ++ include/linux/kvm_types.h | 1 + include/uapi/linux/kvm.h | 36 ++ tools/include/uapi/linux/kvm.h | 47 ++ tools/testing/selftests/kvm/Makefile | 2 - .../selftests/kvm/clear_dirty_log_test.c | 2 - tools/testing/selftests/kvm/dirty_log_test.c | 452 ++++++++++++++++-- .../testing/selftests/kvm/include/kvm_util.h | 6 + tools/testing/selftests/kvm/lib/kvm_util.c | 103 ++++ .../selftests/kvm/lib/kvm_util_internal.h | 5 + virt/kvm/arm/arm.c | 29 -- virt/kvm/arm/perf.c | 6 +- virt/kvm/arm/vgic/vgic-mmio.c | 15 +- virt/kvm/dirty_ring.c | 156 ++++++ virt/kvm/kvm_main.c | 315 +++++++++++- 25 files changed, 1329 insertions(+), 104 deletions(-) create mode 100644 include/linux/kvm_dirty_ring.h delete mode 100644 tools/testing/selftests/kvm/clear_dirty_log_test.c create mode 100644 virt/kvm/dirty_ring.c
Signature · 1 line
2.21.0
RFC PATCH 1/15 KVM: Move running VCPU from ARM to common code
From: Paolo Bonzini <[email protected]>
For ring-based dirty log tracking, it will be more efficient to account writes during schedule-out or schedule-in to the currently running VCPU. We would like to do it even if the write doesn't use the current VCPU's address space, as is the case for cached writes (see commit 4e335d9e7ddb, "Revert "KVM: Support vCPU-based gfn->hva cache"", 2017-05-02).
Therefore, add a mechanism to track the currently-loaded kvm_vcpu struct. There is already something similar in KVM/ARM; one important difference is that kvm_arch_vcpu_{load,put} have two callers in virt/kvm/kvm_main.c:
we have to update both the architecture-independent vcpu_{load,put} and the preempt notifiers.
Another change made in the process is to allow using kvm_get_running_vcpu() in preemptible code. This is allowed because preempt notifiers ensure that the value does not change even after the VCPU thread is migrated.
--- arch/arm/include/asm/kvm_host.h | 2 -- arch/arm64/include/asm/kvm_host.h | 2 -- include/linux/kvm_host.h | 3 +++ virt/kvm/arm/arm.c | 29 ----------------------------- virt/kvm/arm/perf.c | 6 +++--- virt/kvm/arm/vgic/vgic-mmio.c | 15 +++------------ virt/kvm/kvm_main.c | 25 ++++++++++++++++++++++++- 7 files changed, 33 insertions(+), 49 deletions(-)
arch/arm/include/asm/kvm_host.h +0 -2
@@ -284,8 +284,6 @@ int kvm_arm_copy_reg_indices(struct kvm_vcpu *vcpu, u64 __user *indices);int kvm_age_hva (struct kvm *kvm,unsigned long start,unsigned long end);int kvm_test_age_hva (struct kvm *kvm,unsigned long hva); -struct kvm_vcpu *kvm_arm_get_running_vcpu (void ); -struct kvm_vcpu __percpu **kvm_get_running_vcpus (void );void kvm_arm_halt_guest (struct kvm *kvm);void kvm_arm_resume_guest (struct kvm *kvm);
arch/arm64/include/asm/kvm_host.h +0 -2
@@ -446,8 +446,6 @@ int kvm_set_spte_hva(struct kvm *kvm, unsigned long hva, pte_t pte);int kvm_age_hva (struct kvm *kvm,unsigned long start,unsigned long end);int kvm_test_age_hva (struct kvm *kvm,unsigned long hva); -struct kvm_vcpu *kvm_arm_get_running_vcpu (void ); -struct kvm_vcpu *__percpu *kvm_get_running_vcpus (void );void kvm_arm_halt_guest (struct kvm *kvm);void kvm_arm_resume_guest (struct kvm *kvm);
include/linux/kvm_host.h +3 -0
@@ -1342,6 +1342,9 @@ static inline void kvm_vcpu_set_dy_eligible(struct kvm_vcpu *vcpu, bool val) }#endif /* CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT */ +struct kvm_vcpu *kvm_get_running_vcpu (void ); +struct kvm_vcpu __percpu **kvm_get_running_vcpus (void ); +#ifdef CONFIG_HAVE_KVM_IRQ_BYPASS bool kvm_arch_has_irq_bypass (void ); int kvm_arch_irq_bypass_add_producer(struct irq_bypass_consumer *,
virt/kvm/arm/arm.c +0 -29
@@ -51,9 +51,6 @@ __asm__(".arch_extension virt");DEFINE_PER_CPU (kvm_host_data_t, kvm_host_data); static DEFINE_PER_CPU(unsigned long, kvm_arm_hyp_stack_page); -/* Per-CPU variable containing the currently running vcpu. */ -static DEFINE_PER_CPU(struct kvm_vcpu *, kvm_arm_running_vcpu); -/* The VMID used in the VTTBR */ static atomic64_t kvm_vmid_gen =ATOMIC64_INIT (1 );static u32 kvm_next_vmid;
@@ -62,31 +59,8 @@ static DEFINE_SPINLOCK(kvm_vmid_lock);static bool vgic_present; static DEFINE_PER_CPU(unsigned char, kvm_arm_hardware_enabled); - -static void kvm_arm_set_running_vcpu (struct kvm_vcpu *vcpu) -{ -__this_cpu_write (kvm_arm_running_vcpu, vcpu); -} -DEFINE_STATIC_KEY_FALSE (userspace_irqchip_in_use); -/** -* kvm_arm_get_running_vcpu - get the vcpu running on the current CPU. -* Must be called from non-preemptible context -*/ -struct kvm_vcpu *kvm_arm_get_running_vcpu (void ) -{ -return __this_cpu_read (kvm_arm_running_vcpu); -} - -/** -* kvm_arm_get_running_vcpus - get the per-CPU array of currently running vcpus. -*/ -struct kvm_vcpu *__percpu *kvm_get_running_vcpus (void ) -{ -return &kvm_arm_running_vcpu; -} -int kvm_arch_vcpu_should_kick (struct kvm_vcpu *vcpu) {return kvm_vcpu_exiting_guest_mode (vcpu) ==IN_GUEST_MODE ;
@@ -406,7 +380,6 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu) vcpu->cpu = cpu; vcpu->arch.host_cpu_context = &cpu_data->host_ctxt; -kvm_arm_set_running_vcpu (vcpu);kvm_vgic_load (vcpu);kvm_timer_vcpu_load (vcpu);kvm_vcpu_load_sysregs (vcpu);
@@ -432,8 +405,6 @@ void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu)kvm_vcpu_pmu_restore_host (vcpu); vcpu->cpu =-1 ; - -kvm_arm_set_running_vcpu (NULL ); }static void vcpu_power_off (struct kvm_vcpu *vcpu)
virt/kvm/arm/perf.c +3 -3
@@ -13,14 +13,14 @@static int kvm_is_in_guest (void ) { -return kvm_arm_get_running_vcpu () !=NULL ; +return kvm_get_running_vcpu () !=NULL ; }static int kvm_is_user_mode (void ) {struct kvm_vcpu *vcpu; - vcpu =kvm_arm_get_running_vcpu (); + vcpu =kvm_get_running_vcpu ();if (vcpu)return !vcpu_mode_priv (vcpu);
@@ -32,7 +32,7 @@ static unsigned long kvm_get_guest_ip(void) {struct kvm_vcpu *vcpu; - vcpu =kvm_arm_get_running_vcpu (); + vcpu =kvm_get_running_vcpu ();if (vcpu)return *vcpu_pc (vcpu);
virt/kvm/arm/vgic/vgic-mmio.c +3 -12
@@ -190,15 +190,6 @@ unsigned long vgic_mmio_read_pending(struct kvm_vcpu *vcpu,* value later will give us the same value as we update the per-CPU variable * in the preempt notifier handlers. */ -static struct kvm_vcpu *vgic_get_mmio_requester_vcpu (void ) -{ -struct kvm_vcpu *vcpu; - -preempt_disable (); - vcpu =kvm_arm_get_running_vcpu (); -preempt_enable (); -return vcpu; -}/* Must be called with irq->irq_lock held */ static void vgic_hw_irq_spending(struct kvm_vcpu *vcpu, struct vgic_irq *irq,
@@ -221,7 +212,7 @@ void vgic_mmio_write_spending(struct kvm_vcpu *vcpu, gpa_t addr, unsigned int len, unsigned long val) { - bool is_uaccess = !vgic_get_mmio_requester_vcpu(); + bool is_uaccess = !kvm_get_running_vcpu();u32 intid =VGIC_ADDR_TO_INTID (addr,1 );int i;unsigned long flags;
@@ -274,7 +265,7 @@ void vgic_mmio_write_cpending(struct kvm_vcpu *vcpu, gpa_t addr, unsigned int len, unsigned long val) { - bool is_uaccess = !vgic_get_mmio_requester_vcpu(); + bool is_uaccess = !kvm_get_running_vcpu();u32 intid =VGIC_ADDR_TO_INTID (addr,1 );int i;unsigned long flags;
@@ -335,7 +326,7 @@ static void vgic_mmio_change_active(struct kvm_vcpu *vcpu, struct vgic_irq *irq, bool active) {unsigned long flags; -struct kvm_vcpu *requester_vcpu =vgic_get_mmio_requester_vcpu (); +struct kvm_vcpu *requester_vcpu =kvm_get_running_vcpu ();raw_spin_lock_irqsave (&irq->irq_lock, flags);
virt/kvm/kvm_main.c +24 -1
@@ -108,6 +108,7 @@ struct kmem_cache *kvm_vcpu_cache;EXPORT_SYMBOL_GPL (kvm_vcpu_cache);static __read_mostly struct preempt_ops kvm_preempt_ops; +static DEFINE_PER_CPU(struct kvm_vcpu *, kvm_running_vcpu);struct dentry *kvm_debugfs_dir;EXPORT_SYMBOL_GPL (kvm_debugfs_dir);
@@ -197,6 +198,8 @@ bool kvm_is_reserved_pfn(kvm_pfn_t pfn)void vcpu_load (struct kvm_vcpu *vcpu) {int cpu =get_cpu (); + +__this_cpu_write (kvm_running_vcpu, vcpu);preempt_notifier_register (&vcpu->preempt_notifier);kvm_arch_vcpu_load (vcpu, cpu);put_cpu ();
@@ -208,6 +211,7 @@ void vcpu_put(struct kvm_vcpu *vcpu)preempt_disable ();kvm_arch_vcpu_put (vcpu);preempt_notifier_unregister (&vcpu->preempt_notifier); +__this_cpu_write (kvm_running_vcpu,NULL );preempt_enable (); }EXPORT_SYMBOL_GPL (vcpu_put);
@@ -4304,8 +4308,8 @@ static void kvm_sched_in(struct preempt_notifier *pn, int cpu)WRITE_ONCE (vcpu->preempted, false);WRITE_ONCE (vcpu->ready, false); +__this_cpu_write (kvm_running_vcpu, vcpu);kvm_arch_sched_in (vcpu, cpu); -kvm_arch_vcpu_load (vcpu, cpu); }
@@ -4319,6 +4323,25 @@ static void kvm_sched_out(struct preempt_notifier *pn,WRITE_ONCE (vcpu->ready, true); }kvm_arch_vcpu_put (vcpu); +__this_cpu_write (kvm_running_vcpu,NULL ); +} + +/** +* kvm_get_running_vcpu - get the vcpu running on the current CPU. +* Thanks to preempt notifiers, this can also be called from +* preemptible context. +*/ +struct kvm_vcpu *kvm_get_running_vcpu (void ) +{ +return __this_cpu_read (kvm_running_vcpu); +} + +/** +* kvm_get_running_vcpus - get the per-CPU array of currently running vcpus. +*/ +struct kvm_vcpu *__percpu *kvm_get_running_vcpus (void ) +{ +return &kvm_running_vcpu; }static void check_processor_compat (void *rtn)
Signature · 1 line
2.21.0
kbuild test robot
build botRe: [PATCH RFC 01/15] KVM: Move running VCPU from ARM to common code
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system. BTW, we also suggest to use '--base' option to specify the base tree in git format-patch, please…
Hi Peter,
[FYI, it's a private test report for your RFC patch.]
[auto build test ERROR on kvm/linux-next]
[also build test ERROR on next-20191129]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system. BTW, we also suggest to use '--base' option to specify the base tree in git format-patch, please see https://stackoverflow.com/a/37406982]
url: https://github.com/0day-ci/linux/commits/Peter-Xu/KVM-Dirty-ring-interface/20191130-054051 base: https://git.kernel.org/pub/scm/virt/kvm/kvm.git linux-next config: arm64-defconfig (attached as .config) compiler: aarch64-linux-gcc (GCC) 7.4.0
reproduce:
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross
# save the attached .config to linux build tree
GCC_VERSION=7.4.0 make.cross ARCH=arm64
If you fix the issue, kindly add following tag
All error/warnings (new ones prefixed by >>):
arch/arm64/kvm/../../../virt/kvm/arm/arch_timer.c: In function 'kvm_arch_timer_get_input_level':
>> arch/arm64/kvm/../../../virt/kvm/arm/arch_timer.c:1025:26: error: implicit declaration of function 'kvm_arm_get_running_vcpu'; did you mean 'kvm_get_running_vcpu'? [-Werror=implicit-function-declaration]
struct kvm_vcpu *vcpu = kvm_arm_get_running_vcpu();
^~~~~~~~~~~~~~~~~~~~~~~~
kvm_get_running_vcpu
>> arch/arm64/kvm/../../../virt/kvm/arm/arch_timer.c:1025:26: warning: initialization makes pointer from integer without a cast [-Wint-conversion]cc1: some warnings being treated as errors
vim +1025 arch/arm64/kvm/../../../virt/kvm/arm/arch_timer.c
99a1db7a2c9b2e Christoffer Dall 2017-05-02 1022
4c60e360d6dfa4 Christoffer Dall 2017-10-27 1023 bool kvm_arch_timer_get_input_level(int vintid)
4c60e360d6dfa4 Christoffer Dall 2017-10-27 1024 {
4c60e360d6dfa4 Christoffer Dall 2017-10-27 @1025 struct kvm_vcpu *vcpu = kvm_arm_get_running_vcpu();
4c60e360d6dfa4 Christoffer Dall 2017-10-27 1026 struct arch_timer_context *timer;
4c60e360d6dfa4 Christoffer Dall 2017-10-27 1027
4c60e360d6dfa4 Christoffer Dall 2017-10-27 1028 if (vintid == vcpu_vtimer(vcpu)->irq.irq)
4c60e360d6dfa4 Christoffer Dall 2017-10-27 1029 timer = vcpu_vtimer(vcpu);
9e01dc76be6a3b Christoffer Dall 2019-02-19 1030 else if (vintid == vcpu_ptimer(vcpu)->irq.irq)
9e01dc76be6a3b Christoffer Dall 2019-02-19 1031 timer = vcpu_ptimer(vcpu);
4c60e360d6dfa4 Christoffer Dall 2017-10-27 1032 else
9e01dc76be6a3b Christoffer Dall 2019-02-19 1033 BUG();
4c60e360d6dfa4 Christoffer Dall 2017-10-27 1034
4c60e360d6dfa4 Christoffer Dall 2017-10-27 1035 return kvm_timer_should_fire(timer);
4c60e360d6dfa4 Christoffer Dall 2017-10-27 1036 }
4c60e360d6dfa4 Christoffer Dall 2017-10-27 1037 :::::: The code at line 1025 was first introduced by commit
:::::: 4c60e360d6dfa4d9c3586b687f348eeb3fd675dd KVM: arm/arm64: Provide a get_input_level for the arch timer
:::::: TO: Christoffer Dall <[email protected]> :::::: CC: Christoffer Dall <[email protected]>
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org Intel Corporation
Re: [PATCH RFC 01/15] KVM: Move running VCPU from ARM to common code
In reply to Peter XuQuoted 14 lines · Peter Xu
From: Paolo Bonzini <[email protected]>
For ring-based dirty log tracking, it will be more efficient to account writes during schedule-out or schedule-in to the currently running VCPU. We would like to do it even if the write doesn't use the current VCPU's address space, as is the case for cached writes (see commit
4e335d9e7ddb, "Revert "KVM: Support vCPU-basedgfn->hvacache"", 2017-05-02).Therefore, add a mechanism to track the currently-loaded
kvm_vcpustruct. There is already something similar in KVM/ARM; one important difference is that kvm_arch_vcpu_{load,put} have two callers invirt/kvm/kvm_main.c:
we have to update both the architecture-independent vcpu_{load,put} and the preempt notifiers.
Quoted 3 lines
Another change made in the process is to allow using
kvm_get_running_vcpu()in preemptible code. This is allowed because preempt notifiers ensure that the value does not change even after the VCPU thread is migrated.
In case it was clear, I strongly dislike adding kvm_get_running_vcpu(). IMO, it's a unnecessary hack. The proper change to ensure a valid vCPU is seen by mark_page_dirty_in_ring() when there is a current vCPU is to plumb the vCPU down through the various call stacks. Looking up the call stacks for mark_page_dirty() and mark_page_dirty_in_slot(), they all originate with a vcpu->kvm within a few functions, except for the rare case where the write is coming from a non-vcpu ioctl(), in which case there is no current vCPU.
The proper change is obviously much bigger in scope and would require touching gobs of arch specific code, but IMO the end result would be worth the effort. E.g. there's a decent chance it would reduce the API between common KVM and arch specific code by eliminating the exports of variants that take "struct kvm *" instead of "struct kvm_vcpu *".
Paolo Bonzini
maintainerRe: [PATCH RFC 01/15] KVM: Move running VCPU from ARM to common code
Quoted 9 lines · Sean Christopherson
In case it was clear, I strongly dislike adding
kvm_get_running_vcpu(). IMO, it's a unnecessary hack. The proper change to ensure a valid vCPU is seen bymark_page_dirty_in_ring()when there is a current vCPU is to plumb the vCPU down through the various call stacks. Looking up the call stacks formark_page_dirty()andmark_page_dirty_in_slot(), they all originate with avcpu->kvmwithin a few functions, except for the rare case where the write is coming from a non-vcpuioctl(), in which case there is no current vCPU.
Quoted 5 lines
The proper change is obviously much bigger in scope and would require touching gobs of arch specific code, but IMO the end result would be worth the effort. E.g. there's a decent chance it would reduce the API between common KVM and arch specific code by eliminating the exports of variants that take "struct kvm *" instead of "struct
kvm_vcpu*".
It's not that simple. In some cases, the "struct kvm *" cannot be easily replaced with a "struct kvm_vcpu *" without making the API less intuitive; for example think of a function that takes a kvm_vcpu pointer but then calls gfn_to_hva(vcpu->kvm) instead of the expected kvm_vcpu_gfn_to_hva(vcpu).
That said, looking at the code again after a couple years I agree that the usage of kvm_get_running_vcpu() is ugly. But I don't think it's kvm_get_running_vcpu()'s fault, rather it's the vCPU argument in mark_page_dirty_in_slot and mark_page_dirty_in_ring that is confusing and we should not be adding.
kvm_get_running_vcpu() basically means "you can use the per-vCPU ring and avoid locking", nothing more. Right now we need the vCPU argument in mark_page_dirty_in_ring for kvm_arch_vcpu_memslots_id(vcpu), but that is unnecessary and is the real source of confusion (possibly bugs too) if it gets out of sync.
Instead, let's add an as_id field to struct kvm_memory_slot (which is trivial to initialize in __kvm_set_memory_region), and just do
as_id = slot->as_id; vcpu = kvm_get_running_vcpu();
in mark_page_dirty_in_ring.
Paolo
Re: [PATCH RFC 01/15] KVM: Move running VCPU from ARM to common code
Quoted 34 lines · Paolo Bonzini
Quoted 9 lines · Sean Christopherson
In case it was clear, I strongly dislike adding
kvm_get_running_vcpu(). IMO, it's a unnecessary hack. The proper change to ensure a valid vCPU is seen bymark_page_dirty_in_ring()when there is a current vCPU is to plumb the vCPU down through the various call stacks. Looking up the call stacks formark_page_dirty()andmark_page_dirty_in_slot(), they all originate with avcpu->kvmwithin a few functions, except for the rare case where the write is coming from a non-vcpuioctl(), in which case there is no current vCPU.Quoted 5 lines
The proper change is obviously much bigger in scope and would require touching gobs of arch specific code, but IMO the end result would be worth the effort. E.g. there's a decent chance it would reduce the API between common KVM and arch specific code by eliminating the exports of variants that take "struct kvm *" instead of "struct
kvm_vcpu*".It's not that simple. In some cases, the "struct kvm *" cannot be easily replaced with a "struct
kvm_vcpu*" without making the API less intuitive; for example think of a function that takes akvm_vcpupointer but then callsgfn_to_hva(vcpu->kvm)instead of the expectedkvm_vcpu_gfn_to_hva(vcpu).That said, looking at the code again after a couple years I agree that the usage of
kvm_get_running_vcpu()is ugly. But I don't think it's kvm_get_running_vcpu()'s fault, rather it's the vCPU argument inmark_page_dirty_in_slotandmark_page_dirty_in_ringthat is confusing and we should not be adding.
kvm_get_running_vcpu()basically means "you can use the per-vCPU ring and avoid locking", nothing more. Right now we need the vCPU argument inmark_page_dirty_in_ringforkvm_arch_vcpu_memslots_id(vcpu), but that is unnecessary and is the real source of confusion (possibly bugs too) if it gets out of sync.
Quoted 7 lines
Instead, let's add an
as_idfield to structkvm_memory_slot(which is trivial to initialize in__kvm_set_memory_region), and just doas_id = slot->as_id; vcpu = kvm_get_running_vcpu();in
mark_page_dirty_in_ring.
Looks good. I'm adding another patch for it, and dropping patch 2 then.
Thanks,
Signature · 1 line
Peter Xu
RFC PATCH 2/15 KVM: Add kvm/vcpu argument to mark_dirty_page_in_slot
In reply to Peter XuFrom: "Cao, Lei" <[email protected]>
--- virt/kvm/kvm_main.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-)
virt/kvm/kvm_main.c +17 -9
@@ -145,7 +145,10 @@ static void hardware_disable_all(void);static void kvm_io_bus_destroy (struct kvm_io_bus *bus); -static void mark_page_dirty_in_slot (struct kvm_memory_slot *memslot,gfn_t gfn); +static void mark_page_dirty_in_slot (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *memslot, +gfn_t gfn); __visible bool kvm_rebooting;EXPORT_SYMBOL_GPL (kvm_rebooting);
@@ -2077,7 +2080,8 @@ int kvm_vcpu_read_guest_atomic(struct kvm_vcpu *vcpu, gpa_t gpa, }EXPORT_SYMBOL_GPL (kvm_vcpu_read_guest_atomic); -static int __kvm_write_guest_page (struct kvm_memory_slot *memslot,gfn_t gfn, +static int __kvm_write_guest_page (struct kvm *kvm,struct kvm_vcpu *vcpu, +struct kvm_memory_slot *memslot,gfn_t gfn,const void *data,int offset,int len) {int r;
@@ -2089,7 +2093,7 @@ static int __kvm_write_guest_page(struct kvm_memory_slot *memslot, gfn_t gfn, r =__copy_to_user ((void __user *)addr + offset, data, len);if (r)return -EFAULT ; -mark_page_dirty_in_slot (memslot, gfn); +mark_page_dirty_in_slot (kvm, vcpu, memslot, gfn);return 0 ; }
@@ -2098,7 +2102,8 @@ int kvm_write_guest_page(struct kvm *kvm, gfn_t gfn, {struct kvm_memory_slot *slot =gfn_to_memslot (kvm, gfn); -return __kvm_write_guest_page (slot, gfn, data, offset, len); +return __kvm_write_guest_page (kvm,NULL , slot, gfn, data, + offset, len); }EXPORT_SYMBOL_GPL (kvm_write_guest_page);
@@ -2107,7 +2112,8 @@ int kvm_vcpu_write_guest_page(struct kvm_vcpu *vcpu, gfn_t gfn, {struct kvm_memory_slot *slot =kvm_vcpu_gfn_to_memslot (vcpu, gfn); -return __kvm_write_guest_page (slot, gfn, data, offset, len); +return __kvm_write_guest_page (vcpu->kvm, vcpu, slot, gfn, data, + offset, len); }EXPORT_SYMBOL_GPL (kvm_vcpu_write_guest_page);
@@ -2221,7 +2227,7 @@ int kvm_write_guest_offset_cached(struct kvm *kvm, struct gfn_to_hva_cache *ghc, r =__copy_to_user ((void __user *)ghc->hva + offset, data, len);if (r)return -EFAULT ; -mark_page_dirty_in_slot (ghc->memslot, gpa >>PAGE_SHIFT ); +mark_page_dirty_in_slot (kvm,NULL , ghc->memslot, gpa >>PAGE_SHIFT );return 0 ; }
@@ -2286,7 +2292,9 @@ int kvm_clear_guest(struct kvm *kvm, gpa_t gpa, unsigned long len) }EXPORT_SYMBOL_GPL (kvm_clear_guest); -static void mark_page_dirty_in_slot (struct kvm_memory_slot *memslot, +static void mark_page_dirty_in_slot (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *memslot,gfn_t gfn) {if (memslot && memslot->dirty_bitmap) {
@@ -2301,7 +2309,7 @@ void mark_page_dirty(struct kvm *kvm, gfn_t gfn)struct kvm_memory_slot *memslot; memslot =gfn_to_memslot (kvm, gfn); -mark_page_dirty_in_slot (memslot, gfn); +mark_page_dirty_in_slot (kvm,NULL , memslot, gfn); }EXPORT_SYMBOL_GPL (mark_page_dirty);
@@ -2310,7 +2318,7 @@ void kvm_vcpu_mark_page_dirty(struct kvm_vcpu *vcpu, gfn_t gfn)struct kvm_memory_slot *memslot; memslot =kvm_vcpu_gfn_to_memslot (vcpu, gfn); -mark_page_dirty_in_slot (memslot, gfn); +mark_page_dirty_in_slot (vcpu->kvm, vcpu, memslot, gfn); }EXPORT_SYMBOL_GPL (kvm_vcpu_mark_page_dirty);
Signature · 1 line
2.21.0
Re: [PATCH RFC 02/15] KVM: Add kvm/vcpu argument to mark_dirty_page_in_slot
On Fri, Nov 29, 2019 at 04:34:52PM -0500, Peter Xu wrote:
Why?
Quoted 14 lines
From: "Cao, Lei" <[email protected]>
Signed-off-by Cao, Lei <[email protected]>Signed-off-by Paolo Bonzini <[email protected]>Signed-off-by Peter Xu <[email protected]>--- virt/kvm/kvm_main.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-)virt/kvm/kvm_main.c +0
-0
virt/kvm/kvm_main.c static void hardware_disable_all(void); · line 151
Quoted 8 lines
virt/kvm/kvm_main.c +4
-1@@ -145,7 +145,10 @@ static void hardware_disable_all(void);static void kvm_io_bus_destroy (struct kvm_io_bus *bus); -static void mark_page_dirty_in_slot (struct kvm_memory_slot *memslot,gfn_t gfn); +static void mark_page_dirty_in_slot (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *memslot, +gfn_t gfn);
Why both? Passing @vcpu gets you @kvm.
Re: [PATCH RFC 02/15] KVM: Add kvm/vcpu argument to mark_dirty_page_in_slot
Quoted 3 lines · Sean Christopherson
On Fri, Nov 29, 2019 at 04:34:52PM -0500, Peter Xu wrote:
Why?
[1]
Quoted 18 lines
Quoted 18 lines
From: "Cao, Lei" <[email protected]>
Signed-off-by Cao, Lei <[email protected]>Signed-off-by Paolo Bonzini <[email protected]>Signed-off-by Peter Xu <[email protected]>--- virt/kvm/kvm_main.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-)virt/kvm/kvm_main.c +0
-0@@ -145,7 +145,10 @@ static void hardware_disable_all(void);static void kvm_io_bus_destroy (struct kvm_io_bus *bus);
Quoted 7 lines
virt/kvm/kvm_main.c static void hardware_disable_all(void); · line 151
Quoted 5 lines
virt/kvm/kvm_main.c +4
-1-static void mark_page_dirty_in_slot (struct kvm_memory_slot *memslot,gfn_t gfn); +static void mark_page_dirty_in_slot (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *memslot, +gfn_t gfn);Why both? Passing @vcpu gets you @kvm.
You are right on that I should fill in something at [1]..
Because @vcpu can be NULL (if you continue to read this patch, you'll see sometimes NULL is passed in), and we at least need a context to mark the dirty ring. That's also why we need a per-vm dirty ring to be the fallback of the cases where we don't have vcpu context.
Thanks,
Signature · 1 line
Peter Xu
RFC PATCH 3/15 KVM: Add build-time error check on kvm_run size
In reply to Peter XuIt's already going to reach 2400 Bytes (which is over half of page size on 4K page archs), so maybe it's good to have this build-time check in case it overflows when adding new fields.
--- virt/kvm/kvm_main.c | 2 ++ 1 file changed, 2 insertions(+)
virt/kvm/kvm_main.c +2 -0
@@ -352,6 +352,8 @@ int kvm_vcpu_init(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned id) } vcpu->run =page_address (page); +BUILD_BUG_ON (sizeof (struct kvm_run ) >PAGE_SIZE ); +kvm_vcpu_set_in_spin_loop (vcpu, false);kvm_vcpu_set_dy_eligible (vcpu, false); vcpu->preempted = false;
Signature · 1 line
2.21.0
Re: [PATCH RFC 03/15] KVM: Add build-time error check on kvm_run size
Quoted 3 lines · Peter Xu
It's already going to reach 2400 Bytes (which is over half of page size on 4K page archs), so maybe it's good to have this build-time check in case it overflows when adding new fields.
Please explain why exceeding PAGE_SIZE is a bad thing. I realize it's almost absurdly obvious when looking at the code, but a) the patch itself does not provide that context and b) the changelog should hold up on its own, e.g. in a mostly hypothetical case where the allocation of vcpu->run were changed to something else.
Quoted 21 lines
Signed-off-by Peter Xu <[email protected]>--- virt/kvm/kvm_main.c | 2 ++ 1 file changed, 2 insertions(+)virt/kvm/kvm_main.c +2
-0@@ -352,6 +352,8 @@ int kvm_vcpu_init(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned id) } vcpu->run =page_address (page); +BUILD_BUG_ON (sizeof (struct kvm_run ) >PAGE_SIZE ); +kvm_vcpu_set_in_spin_loop (vcpu, false);kvm_vcpu_set_dy_eligible (vcpu, false); vcpu->preempted = false;Signature · 1 line
2.21.0
Re: [PATCH RFC 03/15] KVM: Add build-time error check on kvm_run size
Quoted 9 lines · Sean Christopherson
Quoted 3 lines · Peter Xu
It's already going to reach 2400 Bytes (which is over half of page size on 4K page archs), so maybe it's good to have this build-time check in case it overflows when adding new fields.
Please explain why exceeding
PAGE_SIZEis a bad thing. I realize it's almost absurdly obvious when looking at the code, but a) the patch itself does not provide that context and b) the changelog should hold up on its own,
Right, I'll enhance the commit message.
Quoted 2 lines
e.g. in a mostly hypothetical case where the allocation of
vcpu->runwere changed to something else.
And that's why I added BUILD_BUG_ON right beneath that allocation. :)
It's just a helper for developers when adding new kvm_run fields, not a risk for anyone who wants to start allocating more pages for it.
Thanks,
Quoted 23 lines
Quoted 22 lines
Signed-off-by Peter Xu <[email protected]>--- virt/kvm/kvm_main.c | 2 ++ 1 file changed, 2 insertions(+)virt/kvm/kvm_main.c +2
-0@@ -352,6 +352,8 @@ int kvm_vcpu_init(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned id) } vcpu->run =page_address (page); +BUILD_BUG_ON (sizeof (struct kvm_run ) >PAGE_SIZE ); +kvm_vcpu_set_in_spin_loop (vcpu, false);kvm_vcpu_set_dy_eligible (vcpu, false); vcpu->preempted = false;Signature · 1 line
2.21.0
Signature · 1 line
Peter Xu
Re: [PATCH RFC 03/15] KVM: Add build-time error check on kvm_run size
Quoted 11 lines · Peter Xu
Quoted 9 lines · Sean Christopherson
Quoted 3 lines · Peter Xu
It's already going to reach 2400 Bytes (which is over half of page size on 4K page archs), so maybe it's good to have this build-time check in case it overflows when adding new fields.
Please explain why exceeding
PAGE_SIZEis a bad thing. I realize it's almost absurdly obvious when looking at the code, but a) the patch itself does not provide that context and b) the changelog should hold up on its own,
Quoted 6 lines
Right, I'll enhance the commit message.
Quoted 2 lines
e.g. in a mostly hypothetical case where the allocation of
vcpu->runwere changed to something else.And that's why I added
BUILD_BUG_ONright beneath that allocation. :)
My point is that if the allocation were changed to no longer be a straightforward alloc_page() then someone reading the combined code would have no idea why the BUILD_BUG_ON() exists. It's a bit ridiculous for this case because the specific constraints of vcpu->run make it highly unlikely to use anything else, but that's beside the point.
Quoted 2 lines
It's just a helper for developers when adding new
kvm_runfields, not a risk for anyone who wants to start allocating more pages for it.
But by adding a BUILD_BUG_ON without explaining *why*, you're placing an extra burden on someone that wants to increase the size of kvm->run, e.g. it's not at all obvious from the changelog whether this patch is adding the BUILD_BUG_ON purely because the code allocates memory for vcpu->run via alloc_page(), or if there is some fundamental aspect of vcpu->run that requires it to never span multiple pages.
Re: [PATCH RFC 03/15] KVM: Add build-time error check on kvm_run size
Quoted 28 lines · Sean Christopherson
Quoted 11 lines · Peter Xu
Quoted 9 lines · Sean Christopherson
Quoted 3 lines · Peter Xu
It's already going to reach 2400 Bytes (which is over half of page size on 4K page archs), so maybe it's good to have this build-time check in case it overflows when adding new fields.
Please explain why exceeding
PAGE_SIZEis a bad thing. I realize it's almost absurdly obvious when looking at the code, but a) the patch itself does not provide that context and b) the changelog should hold up on its own,Quoted 6 lines
Right, I'll enhance the commit message.
Quoted 2 lines
e.g. in a mostly hypothetical case where the allocation of
vcpu->runwere changed to something else.And that's why I added
BUILD_BUG_ONright beneath that allocation. :)My point is that if the allocation were changed to no longer be a straightforward
alloc_page()then someone reading the combined code would have no idea why theBUILD_BUG_ON()exists. It's a bit ridiculous for this case because the specific constraints ofvcpu->runmake it highly unlikely to use anything else, but that's beside the point.Quoted 2 lines
It's just a helper for developers when adding new
kvm_runfields, not a risk for anyone who wants to start allocating more pages for it.
Quoted 6 lines
But by adding a
BUILD_BUG_ONwithout explaining *why*, you're placing an extra burden on someone that wants to increase the size ofkvm->run, e.g. it's not at all obvious from the changelog whether this patch is adding theBUILD_BUG_ONpurely because the code allocates memory forvcpu->runviaalloc_page(), or if there is some fundamental aspect ofvcpu->runthat requires it to never span multiple pages.
How about I add a comment above it?
/*
* Currently kvm_run only uses one physical page. Warn the develper
* if kvm_run accidentaly grows more than that.
*/
BUILD_BUG_ON(...);
Thanks,
Signature · 1 line
Peter Xu
Re: [PATCH RFC 03/15] KVM: Add build-time error check on kvm_run size
Quoted 36 lines · Peter Xu
Quoted 28 lines · Sean Christopherson
Quoted 11 lines · Peter Xu
Quoted 9 lines · Sean Christopherson
Quoted 3 lines · Peter Xu
It's already going to reach 2400 Bytes (which is over half of page size on 4K page archs), so maybe it's good to have this build-time check in case it overflows when adding new fields.
Please explain why exceeding
PAGE_SIZEis a bad thing. I realize it's almost absurdly obvious when looking at the code, but a) the patch itself does not provide that context and b) the changelog should hold up on its own,Quoted 6 lines
Right, I'll enhance the commit message.
Quoted 2 lines
e.g. in a mostly hypothetical case where the allocation of
vcpu->runwere changed to something else.And that's why I added
BUILD_BUG_ONright beneath that allocation. :)My point is that if the allocation were changed to no longer be a straightforward
alloc_page()then someone reading the combined code would have no idea why theBUILD_BUG_ON()exists. It's a bit ridiculous for this case because the specific constraints ofvcpu->runmake it highly unlikely to use anything else, but that's beside the point.Quoted 2 lines
It's just a helper for developers when adding new
kvm_runfields, not a risk for anyone who wants to start allocating more pages for it.Quoted 6 lines
But by adding a
BUILD_BUG_ONwithout explaining *why*, you're placing an extra burden on someone that wants to increase the size ofkvm->run, e.g. it's not at all obvious from the changelog whether this patch is adding theBUILD_BUG_ONpurely because the code allocates memory forvcpu->runviaalloc_page(), or if there is some fundamental aspect ofvcpu->runthat requires it to never span multiple pages.
Quoted 7 lines
How about I add a comment above it?
/*
* Currentlykvm_runonly uses one physical page. Warn the develper
* ifkvm_runaccidentaly grows more than that.
*/
BUILD_BUG_ON(...);
No need for a comment, adding a blurb in the changelog is sufficient.
The lengthy response was just trying to explain why it's helpful to explicitly justify a change that may seem obvious in the current codebase. Apologies if it only confused things.
Paolo Bonzini
maintainerRe: [PATCH RFC 03/15] KVM: Add build-time error check on kvm_run size
In reply to Sean ChristophersonQuoted 3 lines · Sean Christopherson
Quoted 3 lines
Quoted 2 lines
e.g. in a mostly hypothetical case where the allocation of
vcpu->runwere changed to something else.And that's why I added
BUILD_BUG_ONright beneath that allocation. :)
It's not exactly beneath it (it's out of the patch context at least). I think a comment is not strictly necessary, but a better commit message is and, since you are at it, I would put the BUILD_BUG_ON *before* the allocation. That makes it more obvious that you are checking the invariant before allocating.
Paolo
Re: [PATCH RFC 03/15] KVM: Add build-time error check on kvm_run size
Quoted 10 lines · Paolo Bonzini
Quoted 3 lines · Sean Christopherson
Quoted 3 lines
Quoted 2 lines
e.g. in a mostly hypothetical case where the allocation of
vcpu->runwere changed to something else.And that's why I added
BUILD_BUG_ONright beneath that allocation. :)It's not exactly beneath it (it's out of the patch context at least). I think a comment is not strictly necessary, but a better commit message is and, since you are at it, I would put the
BUILD_BUG_ON*before* the allocation. That makes it more obvious that you are checking the invariant before allocating.
Makes sense, will do. Thanks for both of your reviews.
Signature · 1 line
Peter Xu
RFC PATCH 4/15 KVM: Implement ring-based dirty memory tracking
In reply to Peter XuThis patch is heavily based on previous work from Lei Cao <[email protected]> and Paolo Bonzini <[email protected]>. [1]
KVM currently uses large bitmaps to track dirty memory. These bitmaps are copied to userspace when userspace queries KVM for its dirty page information. The use of bitmaps is mostly sufficient for live migration, as large parts of memory are be dirtied from one log-dirty pass to another. However, in a checkpointing system, the number of dirty pages is small and in fact it is often bounded---the VM is paused when it has dirtied a pre-defined number of pages. Traversing a large, sparsely populated bitmap to find set bits is time-consuming, as is copying the bitmap to user-space.
A similar issue will be there for live migration when the guest memory is huge while the page dirty procedure is trivial. In that case for each dirty sync we need to pull the whole dirty bitmap to userspace and analyse every bit even if it's mostly zeros.
The preferred data structure for above scenarios is a dense list of guest frame numbers (GFN). This patch series stores the dirty list in kernel memory that can be memory mapped into userspace to allow speedy harvesting.
We defined two new data structures:
struct kvm_dirty_ring;
struct kvm_dirty_ring_indexes;
Firstly, kvm_dirty_ring is defined to represent a ring of dirty pages. When dirty tracking is enabled, we can push dirty gfn onto the ring.
Secondly, kvm_dirty_ring_indexes is defined to represent the user/kernel interface of each ring. Currently it contains two
indexes: (1) avail_index represents where we should push our next PFN (written by kernel), while (2) fetch_index represents where the userspace should fetch the next dirty PFN (written by userspace).
One complete ring is composed by one kvm_dirty_ring plus its corresponding kvm_dirty_ring_indexes.
Currently, we have N+1 rings for each VM of N vcpus:
- for each vcpu, we have 1 per-vcpu dirty ring,
- for each vm, we have 1 per-vm dirty ring
Please refer to the documentation update in this patch for more details.
Note that this patch implements the core logic of dirty ring buffer. It's still disabled for all archs for now. Also, we'll address some of the other issues in follow up patches before it's firstly enabled on x86.
[1] https://patchwork.kernel.org/patch/10471409/
--- Documentation/virt/kvm/api.txt | 109 +++++++++++++++ arch/x86/kvm/Makefile | 3 +- include/linux/kvm_dirty_ring.h | 67 +++++++++ include/linux/kvm_host.h | 33 +++++ include/linux/kvm_types.h | 1 + include/uapi/linux/kvm.h | 36 +++++ virt/kvm/dirty_ring.c | 156 +++++++++++++++++++++ virt/kvm/kvm_main.c | 240 ++++++++++++++++++++++++++++++++- 8 files changed, 642 insertions(+), 3 deletions(-) create mode 100644 include/linux/kvm_dirty_ring.h create mode 100644 virt/kvm/dirty_ring.c
Documentation/virt/kvm/api.txt +109 -0
@@ -231,6 +231,7 @@ Based on their initialization different VMs may have different capabilities. It is thus encouraged to use the vm ioctl to query for capabilities (available with KVM_CAP_CHECK_EXTENSION_VM on the vm fd) + 4.5 KVM_GET_VCPU_MMAP_SIZE Capability: basic
@@ -243,6 +244,18 @@ The KVM_RUN ioctl (cf.) communicates with userspace via a shared memory region. This ioctl returns the size of that region. See the KVM_RUN documentation for details. +Besides the size of the KVM_RUN communication region, other areas of +the VCPU file descriptor can be mmap-ed, including: + +- if KVM_CAP_COALESCED_MMIO is available, a page at + KVM_COALESCED_MMIO_PAGE_OFFSET * PAGE_SIZE; for historical reasons, + this page is included in the result of KVM_GET_VCPU_MMAP_SIZE. + KVM_CAP_COALESCED_MMIO is not documented yet. + +- if KVM_CAP_DIRTY_LOG_RING is available, a number of pages at + KVM_DIRTY_LOG_PAGE_OFFSET * PAGE_SIZE. For more information on + KVM_CAP_DIRTY_LOG_RING, see section 8.3. + 4.6 KVM_SET_MEMORY_REGION
@@ -5358,6 +5371,7 @@ CPU when the exception is taken. If this virtual SError is taken to EL1 using AArch64, this value will be reported in the ISS field of ESR_ELx. See KVM_CAP_VCPU_EVENTS for more details. + 8.20 KVM_CAP_HYPERV_SEND_IPI Architectures: x86
@@ -5365,6 +5379,7 @@ Architectures: x86 This capability indicates that KVM supports paravirtualized Hyper-V IPI send hypercalls: HvCallSendSyntheticClusterIpi, HvCallSendSyntheticClusterIpiEx. + 8.21 KVM_CAP_HYPERV_DIRECT_TLBFLUSH Architecture: x86
@@ -5378,3 +5393,97 @@ handling by KVM (as some KVM hypercall may be mistakenly treated as TLB flush hypercalls by Hyper-V) so userspace should disable KVM identification in CPUID and only exposes Hyper-V identification. In this case, guest thinks it's running on Hyper-V and only use Hyper-V hypercalls. + +8.22 KVM_CAP_DIRTY_LOG_RING + +Architectures: x86 +Parameters: args[0] - size of the dirty log ring + +KVM is capable of tracking dirty memory using ring buffers that are +mmaped into userspace; there is one dirty ring per vcpu and one global +ring per vm. + +One dirty ring has the following two major structures: + +struct kvm_dirty_ring { + u16 dirty_index; + u16 reset_index; + u32 size; + u32 soft_limit; + spinlock_t lock; + struct kvm_dirty_gfn *dirty_gfns; +}; + +struct kvm_dirty_ring_indexes { + __u32 avail_index; /* set by kernel */ + __u32 fetch_index; /* set by userspace */ +}; + +While for each of the dirty entry it's defined as: + +struct kvm_dirty_gfn { + __u32 pad; + __u32 slot; /* as_id | slot_id */ + __u64 offset; +}; + +The fields in kvm_dirty_ring will be only internal to KVM itself, +while the fields in kvm_dirty_ring_indexes will be exposed to +userspace to be either read or written. + +The two indices in the ring buffer are free running counters. + +In pseudocode, processing the ring buffer looks like this: + + idx = load-acquire(&ring->fetch_index); + while (idx != ring->avail_index) { + struct kvm_dirty_gfn *entry; + entry = &ring->dirty_gfns[idx & (size - 1)]; + ... + + idx++; + } + ring->fetch_index = idx; + +Userspace calls KVM_ENABLE_CAP ioctl right after KVM_CREATE_VM ioctl +to enable this capability for the new guest and set the size of the +rings. It is only allowed before creating any vCPU, and the size of +the ring must be a power of two. The larger the ring buffer, the less +likely the ring is full and the VM is forced to exit to userspace. The +optimal size depends on the workload, but it is recommended that it be +at least 64 KiB (4096 entries). + +After the capability is enabled, userspace can mmap the global ring +buffer (kvm_dirty_gfn[], offset KVM_DIRTY_LOG_PAGE_OFFSET) and the +indexes (kvm_dirty_ring_indexes, offset 0) from the VM file +descriptor. The per-vcpu dirty ring instead is mmapped when the vcpu +is created, similar to the kvm_run struct (kvm_dirty_ring_indexes +locates inside kvm_run, while kvm_dirty_gfn[] at offset +KVM_DIRTY_LOG_PAGE_OFFSET). + +Just like for dirty page bitmaps, the buffer tracks writes to +all user memory regions for which the KVM_MEM_LOG_DIRTY_PAGES flag was +set in KVM_SET_USER_MEMORY_REGION. Once a memory region is registered +with the flag set, userspace can start harvesting dirty pages from the +ring buffer. + +To harvest the dirty pages, userspace accesses the mmaped ring buffer +to read the dirty GFNs up to avail_index, and sets the fetch_index +accordingly. This can be done when the guest is running or paused, +and dirty pages need not be collected all at once. After processing +one or more entries in the ring buffer, userspace calls the VM ioctl +KVM_RESET_DIRTY_RINGS to notify the kernel that it has updated +fetch_index and to mark those pages clean. Therefore, the ioctl +must be called *before* reading the content of the dirty pages. + +However, there is a major difference comparing to the +KVM_GET_DIRTY_LOG interface in that when reading the dirty ring from +userspace it's still possible that the kernel has not yet flushed the +hardware dirty buffers into the kernel buffer. To achieve that, one +needs to kick the vcpu out for a hardware buffer flush (vmexit). + +If one of the ring buffers is full, the guest will exit to userspace +with the exit reason set to KVM_EXIT_DIRTY_LOG_FULL, and the +KVM_RUN ioctl will return -EINTR. Once that happens, userspace +should pause all the vcpus, then harvest all the dirty pages and +rearm the dirty traps. It can unpause the guest after that.
arch/x86/kvm/Makefile +2 -1
@@ -5,7 +5,8 @@ ccflags-y += -Iarch/x86/kvm KVM := ../../../virt/kvm kvm-y += $(KVM)/kvm_main.o $(KVM)/coalesced_mmio.o \ - $(KVM)/eventfd.o $(KVM)/irqchip.o $(KVM)/vfio.o + $(KVM)/eventfd.o $(KVM)/irqchip.o $(KVM)/vfio.o \ + $(KVM)/dirty_ring.o kvm-$(CONFIG_KVM_ASYNC_PF) += $(KVM)/async_pf.o kvm-y += x86.o emulate.o i8259.o irq.o lapic.o \
include/linux/kvm_dirty_ring.h new file +67 -0
67 lines not on this page · raw message
include/linux/kvm_host.h +33 -0
@@ -34,6 +34,7 @@#include <linux/kvm_types.h> #include <asm/kvm_host.h> +#include <linux/kvm_dirty_ring.h> #ifndef KVM_MAX_VCPU_ID #define KVM_MAX_VCPU_ID KVM_MAX_VCPUS
@@ -146,6 +147,7 @@ static inline bool is_error_page(struct page *page)#define KVM_REQ_MMU_RELOAD (1 | KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)#define KVM_REQ_PENDING_TIMER 2#define KVM_REQ_UNHALT 3 +#define KVM_REQ_DIRTY_RING_FULL 4#define KVM_REQUEST_ARCH_BASE 8#define KVM_ARCH_REQ_FLAGS (nr, flags) ({ \
@@ -321,6 +323,7 @@ struct kvm_vcpu {bool ready;struct kvm_vcpu_arch arch;struct dentry *debugfs_dentry; +struct kvm_dirty_ring dirty_ring; };static inline int kvm_vcpu_exiting_guest_mode (struct kvm_vcpu *vcpu)
@@ -501,6 +504,10 @@ struct kvm {struct srcu_struct srcu;struct srcu_struct irq_srcu;pid_t userspace_pid; +/* Data structure to be exported by mmap(kvm->fd, 0) */ +struct kvm_vm_run *vm_run; +u32 dirty_ring_size; +struct kvm_dirty_ring vm_dirty_ring; };#define kvm_err (fmt, ...) \
@@ -832,6 +839,8 @@ void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,gfn_t gfn_offset, unsigned long mask); +void kvm_reset_dirty_gfn (struct kvm *kvm,u32 slot,u64 offset,u64 mask); +int kvm_vm_ioctl_get_dirty_log (struct kvm *kvm,struct kvm_dirty_log *log); int kvm_vm_ioctl_clear_dirty_log(struct kvm *kvm,
@@ -1411,4 +1420,28 @@ int kvm_vm_create_worker_thread(struct kvm *kvm, kvm_vm_thread_fn_t thread_fn, uintptr_t data, const char *name, struct task_struct **thread_ptr); +/* +* This defines how many reserved entries we want to keep before we +* kick the vcpu to the userspace to avoid dirty ring full. This +* value can be tuned to higher if e.g. PML is enabled on the host. +*/ +#define KVM_DIRTY_RING_RSVD_ENTRIES 64 + +/* Max number of entries allowed for each kvm dirty ring */ +#define KVM_DIRTY_RING_MAX_ENTRIES 65536 + +/* +* Arch needs to define these macro after implementing the dirty ring +* feature. KVM_DIRTY_LOG_PAGE_OFFSET should be defined as the +* starting page offset of the dirty ring structures, while +* KVM_DIRTY_RING_VERSION should be defined as >=1. By default, this +* feature is off on all archs. +*/ +#ifndef KVM_DIRTY_LOG_PAGE_OFFSET +#define KVM_DIRTY_LOG_PAGE_OFFSET 0 +#endif +#ifndef KVM_DIRTY_RING_VERSION +#define KVM_DIRTY_RING_VERSION 0 +#endif +#endif
include/linux/kvm_types.h +1 -0
@@ -11,6 +11,7 @@ struct kvm_irq_routing_table;struct kvm_memory_slot ;struct kvm_one_reg ;struct kvm_run ; +struct kvm_vm_run ;struct kvm_userspace_memory_region ;struct kvm_vcpu ;struct kvm_vcpu_init ;
include/uapi/linux/kvm.h +36 -0
@@ -236,6 +236,7 @@ struct kvm_hyperv_exit {#define KVM_EXIT_IOAPIC_EOI 26#define KVM_EXIT_HYPERV 27#define KVM_EXIT_ARM_NISV 28 +#define KVM_EXIT_DIRTY_RING_FULL 29/* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */
@@ -247,6 +248,11 @@ struct kvm_hyperv_exit {/* Encounter unexpected vm-exit reason */ #define KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON 4 +struct kvm_dirty_ring_indexes { +__u32 avail_index;/* set by kernel */ +__u32 fetch_index;/* set by userspace */ +}; +/* for KVM_RUN, returned by mmap(vcpu_fd, offset=0) */ struct kvm_run {/* in */
@@ -421,6 +427,13 @@ struct kvm_run {struct kvm_sync_regs regs;char padding[SYNC_REGS_SIZE_BYTES ]; } s; + +struct kvm_dirty_ring_indexes vcpu_ring_indexes; +}; + +/* Returned by mmap(kvm->fd, offset=0) */ +struct kvm_vm_run { +struct kvm_dirty_ring_indexes vm_ring_indexes; };/* for KVM_REGISTER_COALESCED_MMIO / KVM_UNREGISTER_COALESCED_MMIO */
@@ -1009,6 +1022,7 @@ struct kvm_ppc_resize_hpt {#define KVM_CAP_PPC_GUEST_DEBUG_SSTEP 176#define KVM_CAP_ARM_NISV_TO_USER 177#define KVM_CAP_ARM_INJECT_EXT_DABT 178 +#define KVM_CAP_DIRTY_LOG_RING 179#ifdef KVM_CAP_IRQ_ROUTING
@@ -1472,6 +1486,9 @@ struct kvm_enc_region {/* Available with KVM_CAP_ARM_SVE */ #define KVM_ARM_VCPU_FINALIZE _IOW(KVMIO, 0xc2, int) +/* Available with KVM_CAP_DIRTY_LOG_RING */ +#define KVM_RESET_DIRTY_RINGS _IO(KVMIO, 0xc3) +/* Secure Encrypted Virtualization command */ enum sev_cmd_id {/* Guest initialization commands */
@@ -1622,4 +1639,23 @@ struct kvm_hyperv_eventfd {#define KVM_HYPERV_CONN_ID_MASK 0x00ffffff#define KVM_HYPERV_EVENTFD_DEASSIGN (1 << 0) +/* +* The following are the requirements for supporting dirty log ring +* (by enabling KVM_DIRTY_LOG_PAGE_OFFSET). +* +* 1. Memory accesses by KVM should call kvm_vcpu_write_* instead +* of kvm_write_* so that the global dirty ring is not filled up +* too quickly. +* 2. kvm_arch_mmu_enable_log_dirty_pt_masked should be defined for +* enabling dirty logging. +* 3. There should not be a separate step to synchronize hardware +* dirty bitmap with KVM's. +*/ + +struct kvm_dirty_gfn { +__u32 pad; +__u32 slot; +__u64 offset; +}; +#endif /* __LINUX_KVM_H */
virt/kvm/dirty_ring.c new file +156 -0
156 lines not on this page · raw message
virt/kvm/kvm_main.c +238 -2
343 lines not on this page · raw message
Signature · 1 line
2.21.0
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 38 lines · Peter Xu
This patch is heavily based on previous work from Lei Cao <[email protected]> and Paolo Bonzini <[email protected]>. [1]
KVM currently uses large bitmaps to track dirty memory. These bitmaps are copied to userspace when userspace queries KVM for its dirty page information. The use of bitmaps is mostly sufficient for live migration, as large parts of memory are be dirtied from one log-dirty pass to another. However, in a checkpointing system, the number of dirty pages is small and in fact it is often bounded---the VM is paused when it has dirtied a pre-defined number of pages. Traversing a large, sparsely populated bitmap to find set bits is time-consuming, as is copying the bitmap to user-space.
A similar issue will be there for live migration when the guest memory is huge while the page dirty procedure is trivial. In that case for each dirty sync we need to pull the whole dirty bitmap to userspace and analyse every bit even if it's mostly zeros.
The preferred data structure for above scenarios is a dense list of guest frame numbers (GFN). This patch series stores the dirty list in kernel memory that can be memory mapped into userspace to allow speedy harvesting.
We defined two new data structures:
struct
kvm_dirty_ring;
structkvm_dirty_ring_indexes;Firstly,
kvm_dirty_ringis defined to represent a ring of dirty pages. When dirty tracking is enabled, we can push dirty gfn onto the ring.Secondly,
kvm_dirty_ring_indexesis defined to represent the user/kernel interface of each ring. Currently it contains two
indexes: (1)avail_indexrepresents where we should push our next PFN (written by kernel), while (2)fetch_indexrepresents where the userspace should fetch the next dirty PFN (written by userspace).
Quoted 7 lines
One complete ring is composed by one
kvm_dirty_ringplus its correspondingkvm_dirty_ring_indexes.Currently, we have N+1 rings for each VM of N vcpus:
- for each vcpu, we have 1 per-vcpu dirty ring,
- for each vm, we have 1 per-vm dirty ring
Why? I assume the purpose of per-vcpu rings is to avoid contention between threads, but the motiviation needs to be explicitly stated. And why is a per-vm fallback ring needed?
If my assumption is correct, have other approaches been tried/profiled? E.g. using cmpxchg to reserve N number of entries in a shared ring. IMO, adding kvm_get_running_vcpu() is a hack that is just asking for future abuse and the vcpu/vm/as_id interactions in mark_page_dirty_in_ring() look extremely fragile. I also dislike having two different mechanisms for accessing the ring (lock for per-vm, something else for per-vcpu).
Quoted 14 lines
Please refer to the documentation update in this patch for more details.
Note that this patch implements the core logic of dirty ring buffer. It's still disabled for all archs for now. Also, we'll address some of the other issues in follow up patches before it's firstly enabled on x86.
[1] https://patchwork.kernel.org/patch/10471409/
Signed-off-by Lei Cao <[email protected]>Signed-off-by Paolo Bonzini <[email protected]>Signed-off-by Peter Xu <[email protected]>---
...
Quoted 9 lines
virt/kvm/dirty_ring.c new file +3
-0@@ -0,0 +1,156 @@ +#include <linux/kvm_host.h> +#include <linux/kvm.h> +#include <linux/vmalloc.h>
virt/kvm/dirty_ring.c line 13
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+#include <linux/kvm_dirty_ring.h> + +u32 kvm_dirty_ring_get_rsvd_entries (void ) +{ +return KVM_DIRTY_RING_RSVD_ENTRIES +kvm_cpu_dirty_log_size (); +} + +int kvm_dirty_ring_alloc (struct kvm *kvm,struct kvm_dirty_ring *ring) +{ +u32 size = kvm->dirty_ring_size;
Just pass in @size, that way you don't need @kvm. And the callers will be less ugly, e.g. the initial allocation won't need to speculatively set kvm->dirty_ring_size.
virt/kvm/dirty_ring.c line 22
Quoted 9 lines
virt/kvm/dirty_ring.c new file +9
-0+ + ring->dirty_gfns =vmalloc (size); +if (!ring->dirty_gfns) +return -ENOMEM ; +memset (ring->dirty_gfns,0 , size); + + ring->size = size /sizeof (struct kvm_dirty_gfn ); + ring->soft_limit = + (kvm->dirty_ring_size / sizeof(struct kvm_dirty_gfn)) -
And passing @size avoids issues like this where a local var is ignored.
Quoted 8 lines
virt/kvm/dirty_ring.c new file +8
-0+kvm_dirty_ring_get_rsvd_entries (); + ring->dirty_index =0 ; + ring->reset_index =0 ; +spin_lock_init (&ring->lock); + +return 0 ; +} +
...
virt/kvm/dirty_ring.c line 152
Quoted 3 lines
virt/kvm/dirty_ring.c new file +3
-0+void kvm_dirty_ring_free (struct kvm_dirty_ring *ring) +{ +if (ring->dirty_gfns) {
Why condition freeing the dirty ring on kvm->dirty_ring_size, this obviously protects itself. Not to mention vfree() also plays nice with a NULL input.
Quoted 29 lines
virt/kvm/dirty_ring.c new file +4
-0+vfree (ring->dirty_gfns); + ring->dirty_gfns =NULL ; + } +}virt/kvm/kvm_main.c +6
-0@@ -64,6 +64,8 @@#define CREATE_TRACE_POINTS #include <trace/events/kvm.h> +#include <linux/kvm_dirty_ring.h> +/* Worst case buffer size needed for holding an integer. */ #define ITOA_MAX_LEN 12@@ -149,6 +151,10 @@ static void mark_page_dirty_in_slot(struct kvm *kvm,struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, gfn_t gfn); +static void mark_page_dirty_in_ring (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *slot, +gfn_t gfn); __visible bool kvm_rebooting;EXPORT_SYMBOL_GPL (kvm_rebooting);
virt/kvm/kvm_main.c int kvm_vcpu_init(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned id) · line 372
Quoted 8 lines
virt/kvm/kvm_main.c +5
-0@@ -359,11 +365,22 @@ int kvm_vcpu_init(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned id) vcpu->preempted = false; vcpu->ready = false; +if (kvm->dirty_ring_size) { + r =kvm_dirty_ring_alloc (vcpu->kvm, &vcpu->dirty_ring); +if (r) { + kvm->dirty_ring_size =0 ; + goto fail_free_run;
This looks wrong, kvm->dirty_ring_size is used to free allocations, i.e. previous allocations will leak if a vcpu allocation fails.
Quoted 48 lines
virt/kvm/kvm_main.c +18
-1+ } + } + r =kvm_arch_vcpu_init (vcpu);if (r <0 ) - goto fail_free_run; + goto fail_free_ring;return 0 ; +fail_free_ring: +if (kvm->dirty_ring_size) +kvm_dirty_ring_free (&vcpu->dirty_ring); fail_free_run:free_page ((unsigned long )vcpu->run); fail:@@ -381,6 +398,8 @@ void kvm_vcpu_uninit(struct kvm_vcpu *vcpu)put_pid (rcu_dereference_protected (vcpu->pid,1 ));kvm_arch_vcpu_uninit (vcpu);free_page ((unsigned long )vcpu->run); +if (vcpu->kvm->dirty_ring_size) +kvm_dirty_ring_free (&vcpu->dirty_ring); }EXPORT_SYMBOL_GPL (kvm_vcpu_uninit);@@ -690,6 +709,7 @@ static struct kvm *kvm_create_vm(unsigned long type)struct kvm *kvm =kvm_arch_alloc_vm ();int r = -ENOMEM ;int i; +struct page *page;if (!kvm)return ERR_PTR (-ENOMEM );@@ -705,6 +725,14 @@ static struct kvm *kvm_create_vm(unsigned long type)BUILD_BUG_ON (KVM_MEM_SLOTS_NUM >SHRT_MAX ); + page =alloc_page (GFP_KERNEL | __GFP_ZERO); +if (!page) { + r = -ENOMEM ; + goto out_err_alloc_page; + } + kvm->vm_run =page_address (page); +BUILD_BUG_ON (sizeof (struct kvm_vm_run ) >PAGE_SIZE ); +if (init_srcu_struct (&kvm->srcu)) goto out_err_no_srcu;if (init_srcu_struct (&kvm->irq_srcu))
virt/kvm/kvm_main.c static struct kvm *kvm_create_vm(unsigned long type) · line 807
Quoted 5 lines
virt/kvm/kvm_main.c +2
-0@@ -775,6 +803,9 @@ static struct kvm *kvm_create_vm(unsigned long type) out_err_no_irq_srcu:cleanup_srcu_struct (&kvm->srcu); out_err_no_srcu: +free_page ((unsigned long )page); + kvm->vm_run =NULL ;
No need to nullify vm_run.
virt/kvm/kvm_main.c static void kvm_destroy_vm(struct kvm *kvm) · line 836
Quoted 11 lines
virt/kvm/kvm_main.c +4
-0+out_err_alloc_page:kvm_arch_free_vm (kvm);mmdrop (current->mm);return ERR_PTR (r);@@ -800,6 +831,15 @@ static void kvm_destroy_vm(struct kvm *kvm)int i;struct mm_struct *mm = kvm->mm; +if (kvm->dirty_ring_size) { +kvm_dirty_ring_free (&kvm->vm_dirty_ring); + }
Unnecessary parantheses.
Quoted 21 lines
virt/kvm/kvm_main.c +7
-1+ +if (kvm->vm_run) { +free_page ((unsigned long )kvm->vm_run); + kvm->vm_run =NULL ; + } +kvm_uevent_notify_change (KVM_EVENT_DESTROY_VM , kvm);kvm_destroy_vm_debugfs (kvm);kvm_arch_sync_events (kvm);@@ -2301,7 +2341,7 @@ static void mark_page_dirty_in_slot(struct kvm *kvm, {if (memslot && memslot->dirty_bitmap) {unsigned long rel_gfn = gfn - memslot->base_gfn; - +mark_page_dirty_in_ring (kvm, vcpu, memslot, gfn);set_bit_le (rel_gfn, memslot->dirty_bitmap); } }@@ -2649,6 +2689,13 @@ void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode) }EXPORT_SYMBOL_GPL (kvm_vcpu_on_spin);
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 42 lines · Sean Christopherson
Quoted 41 lines · Peter Xu
This patch is heavily based on previous work from Lei Cao <[email protected]> and Paolo Bonzini <[email protected]>. [1]
KVM currently uses large bitmaps to track dirty memory. These bitmaps are copied to userspace when userspace queries KVM for its dirty page information. The use of bitmaps is mostly sufficient for live migration, as large parts of memory are be dirtied from one log-dirty pass to another. However, in a checkpointing system, the number of dirty pages is small and in fact it is often bounded---the VM is paused when it has dirtied a pre-defined number of pages. Traversing a large, sparsely populated bitmap to find set bits is time-consuming, as is copying the bitmap to user-space.
A similar issue will be there for live migration when the guest memory is huge while the page dirty procedure is trivial. In that case for each dirty sync we need to pull the whole dirty bitmap to userspace and analyse every bit even if it's mostly zeros.
The preferred data structure for above scenarios is a dense list of guest frame numbers (GFN). This patch series stores the dirty list in kernel memory that can be memory mapped into userspace to allow speedy harvesting.
We defined two new data structures:
struct
kvm_dirty_ring;
structkvm_dirty_ring_indexes;Firstly,
kvm_dirty_ringis defined to represent a ring of dirty pages. When dirty tracking is enabled, we can push dirty gfn onto the ring.Secondly,
kvm_dirty_ring_indexesis defined to represent the user/kernel interface of each ring. Currently it contains two
indexes: (1)avail_indexrepresents where we should push our next PFN (written by kernel), while (2)fetch_indexrepresents where the userspace should fetch the next dirty PFN (written by userspace).One complete ring is composed by one
kvm_dirty_ringplus its correspondingkvm_dirty_ring_indexes.
Quoted 8 lines
Quoted 4 lines
Currently, we have N+1 rings for each VM of N vcpus:
- for each vcpu, we have 1 per-vcpu dirty ring,
- for each vm, we have 1 per-vm dirty ringWhy? I assume the purpose of per-vcpu rings is to avoid contention between threads, but the motiviation needs to be explicitly stated. And why is a per-vm fallback ring needed?
Yes, as explained in previous reply, the problem is there could have guest memory writes without vcpu contexts.
Quoted 3 lines
If my assumption is correct, have other approaches been tried/profiled? E.g. using cmpxchg to reserve N number of entries in a shared ring.
Not yet, but I'd be fine to try anything if there's better alternatives. Besides, could you help explain why sharing one ring and let each vcpu to reserve a region in the ring could be helpful in the pov of performance?
Quoted 4 lines
IMO,
addingkvm_get_running_vcpu()is a hack that is just asking for future abuse and the vcpu/vm/as_id interactions inmark_page_dirty_in_ring()look extremely fragile.
I agree. Another way is to put heavier traffic to the per-vm ring, but the downside could be that the per-vm ring could get full easier (but I haven't tested).
Quoted 2 lines
I also dislike having two different mechanisms
for accessing the ring (lock for per-vm, something else for per-vcpu).
Actually I proposed to drop the per-vm ring (actually I had a version that implemented this.. and I just changed it back to the per-vm ring later on, see below) and when there's no vcpu context I thought about:
(1) use vcpu0 ring
(2) or a better algo to pick up a per-vcpu ring (like, the less full ring, we can do many things here, e.g., we can easily maintain a structure track this so we can get O(1) search, I think)
I discussed this with Paolo, but I think Paolo preferred the per-vm ring because there's no good reason to choose vcpu0 as what (1) suggested. While if to choose (2) we probably need to lock even for per-cpu ring, so could be a bit slower.
Since this is still RFC, I think we still have chance to change this, depending on how the discussion goes.
Quoted 33 lines
Quoted 15 lines
Please refer to the documentation update in this patch for more details.
Note that this patch implements the core logic of dirty ring buffer. It's still disabled for all archs for now. Also, we'll address some of the other issues in follow up patches before it's firstly enabled on x86.
[1] https://patchwork.kernel.org/patch/10471409/
Signed-off-by Lei Cao <[email protected]>Signed-off-by Paolo Bonzini <[email protected]>Signed-off-by Peter Xu <[email protected]>---
...
Quoted 15 lines
virt/kvm/dirty_ring.c new file +9
-0@@ -0,0 +1,156 @@ +#include <linux/kvm_host.h> +#include <linux/kvm.h> +#include <linux/vmalloc.h> +#include <linux/kvm_dirty_ring.h> + +u32 kvm_dirty_ring_get_rsvd_entries (void ) +{ +return KVM_DIRTY_RING_RSVD_ENTRIES +kvm_cpu_dirty_log_size (); +}
Quoted 8 lines
virt/kvm/dirty_ring.c line 13
Quoted 4 lines
virt/kvm/dirty_ring.c new file +4
-0+ +int kvm_dirty_ring_alloc (struct kvm *kvm,struct kvm_dirty_ring *ring) +{ +u32 size = kvm->dirty_ring_size;Just pass in @size, that way you don't need @kvm. And the callers will be less ugly, e.g. the initial allocation won't need to speculatively set
kvm->dirty_ring_size.
Sure.
Quoted 24 lines
virt/kvm/dirty_ring.c line 22
Quoted 10 lines
virt/kvm/dirty_ring.c new file +9
-0+ + ring->dirty_gfns =vmalloc (size); +if (!ring->dirty_gfns) +return -ENOMEM ; +memset (ring->dirty_gfns,0 , size); + + ring->size = size /sizeof (struct kvm_dirty_gfn ); + ring->soft_limit = + (kvm->dirty_ring_size / sizeof(struct kvm_dirty_gfn)) -And passing @size avoids issues like this where a local var is ignored.
Quoted 8 lines
virt/kvm/dirty_ring.c new file +8
-0+kvm_dirty_ring_get_rsvd_entries (); + ring->dirty_index =0 ; + ring->reset_index =0 ; +spin_lock_init (&ring->lock); + +return 0 ; +} +...
Quoted 7 lines
virt/kvm/dirty_ring.c line 152
Quoted 3 lines
virt/kvm/dirty_ring.c new file +3
-0+void kvm_dirty_ring_free (struct kvm_dirty_ring *ring) +{ +if (ring->dirty_gfns) {Why condition freeing the dirty ring on
kvm->dirty_ring_size, this obviously protects itself. Not to mentionvfree()also plays nice with a NULL input.
Ok I can drop this check.
Quoted 33 lines
Quoted 33 lines
virt/kvm/dirty_ring.c new file +4
-0+vfree (ring->dirty_gfns); + ring->dirty_gfns =NULL ; + } +}virt/kvm/kvm_main.c +6
-0@@ -64,6 +64,8 @@#define CREATE_TRACE_POINTS #include <trace/events/kvm.h> +#include <linux/kvm_dirty_ring.h> +/* Worst case buffer size needed for holding an integer. */ #define ITOA_MAX_LEN 12@@ -149,6 +151,10 @@ static void mark_page_dirty_in_slot(struct kvm *kvm,struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, gfn_t gfn); +static void mark_page_dirty_in_ring (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *slot, +gfn_t gfn); __visible bool kvm_rebooting;EXPORT_SYMBOL_GPL (kvm_rebooting);@@ -359,11 +365,22 @@ int kvm_vcpu_init(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned id) vcpu->preempted = false; vcpu->ready = false;
Quoted 8 lines
virt/kvm/kvm_main.c int kvm_vcpu_init(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned id) · line 372
Quoted 5 lines
virt/kvm/kvm_main.c +5
-0+if (kvm->dirty_ring_size) { + r =kvm_dirty_ring_alloc (vcpu->kvm, &vcpu->dirty_ring); +if (r) { + kvm->dirty_ring_size =0 ; + goto fail_free_run;This looks wrong,
kvm->dirty_ring_sizeis used to free allocations, i.e. previous allocations will leak if a vcpu allocation fails.
You are right. That's an overkill.
Quoted 48 lines
Quoted 48 lines
virt/kvm/kvm_main.c +18
-1+ } + } + r =kvm_arch_vcpu_init (vcpu);if (r <0 ) - goto fail_free_run; + goto fail_free_ring;return 0 ; +fail_free_ring: +if (kvm->dirty_ring_size) +kvm_dirty_ring_free (&vcpu->dirty_ring); fail_free_run:free_page ((unsigned long )vcpu->run); fail:@@ -381,6 +398,8 @@ void kvm_vcpu_uninit(struct kvm_vcpu *vcpu)put_pid (rcu_dereference_protected (vcpu->pid,1 ));kvm_arch_vcpu_uninit (vcpu);free_page ((unsigned long )vcpu->run); +if (vcpu->kvm->dirty_ring_size) +kvm_dirty_ring_free (&vcpu->dirty_ring); }EXPORT_SYMBOL_GPL (kvm_vcpu_uninit);@@ -690,6 +709,7 @@ static struct kvm *kvm_create_vm(unsigned long type)struct kvm *kvm =kvm_arch_alloc_vm ();int r = -ENOMEM ;int i; +struct page *page;if (!kvm)return ERR_PTR (-ENOMEM );@@ -705,6 +725,14 @@ static struct kvm *kvm_create_vm(unsigned long type)BUILD_BUG_ON (KVM_MEM_SLOTS_NUM >SHRT_MAX ); + page =alloc_page (GFP_KERNEL | __GFP_ZERO); +if (!page) { + r = -ENOMEM ; + goto out_err_alloc_page; + } + kvm->vm_run =page_address (page); +BUILD_BUG_ON (sizeof (struct kvm_vm_run ) >PAGE_SIZE ); +if (init_srcu_struct (&kvm->srcu)) goto out_err_no_srcu;if (init_srcu_struct (&kvm->irq_srcu))
Quoted 8 lines
virt/kvm/kvm_main.c static struct kvm *kvm_create_vm(unsigned long type) · line 807
Quoted 6 lines
virt/kvm/kvm_main.c +2
-0@@ -775,6 +803,9 @@ static struct kvm *kvm_create_vm(unsigned long type) out_err_no_irq_srcu:cleanup_srcu_struct (&kvm->srcu); out_err_no_srcu: +free_page ((unsigned long )page); + kvm->vm_run =NULL ;No need to nullify
vm_run.
Ok.
Quoted 9 lines
Quoted 9 lines
virt/kvm/kvm_main.c +1
-0+out_err_alloc_page:kvm_arch_free_vm (kvm);mmdrop (current->mm);return ERR_PTR (r);@@ -800,6 +831,15 @@ static void kvm_destroy_vm(struct kvm *kvm)int i;struct mm_struct *mm = kvm->mm;
Quoted 5 lines
virt/kvm/kvm_main.c static void kvm_destroy_vm(struct kvm *kvm) · line 836
Quoted 3 lines
virt/kvm/kvm_main.c +3
-0+if (kvm->dirty_ring_size) { +kvm_dirty_ring_free (&kvm->vm_dirty_ring); + }Unnecessary parantheses.
True.
Thanks,
Quoted 23 lines
Quoted 22 lines
virt/kvm/kvm_main.c +7
-1+ +if (kvm->vm_run) { +free_page ((unsigned long )kvm->vm_run); + kvm->vm_run =NULL ; + } +kvm_uevent_notify_change (KVM_EVENT_DESTROY_VM , kvm);kvm_destroy_vm_debugfs (kvm);kvm_arch_sync_events (kvm);@@ -2301,7 +2341,7 @@ static void mark_page_dirty_in_slot(struct kvm *kvm, {if (memslot && memslot->dirty_bitmap) {unsigned long rel_gfn = gfn - memslot->base_gfn; - +mark_page_dirty_in_ring (kvm, vcpu, memslot, gfn);set_bit_le (rel_gfn, memslot->dirty_bitmap); } }@@ -2649,6 +2689,13 @@ void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode) }EXPORT_SYMBOL_GPL (kvm_vcpu_on_spin);
Signature · 1 line
Peter Xu
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 15 lines · Peter Xu
Quoted 9 lines · Sean Christopherson
Quoted 4 lines · Peter Xu
Currently, we have N+1 rings for each VM of N vcpus:
- for each vcpu, we have 1 per-vcpu dirty ring,
- for each vm, we have 1 per-vm dirty ringWhy? I assume the purpose of per-vcpu rings is to avoid contention between threads, but the motiviation needs to be explicitly stated. And why is a per-vm fallback ring needed?
Yes, as explained in previous reply, the problem is there could have guest memory writes without vcpu contexts.
Quoted 1 line
Quoted 7 lines
Quoted 2 lines
If my assumption is correct, have other approaches been tried/profiled? E.g. using cmpxchg to reserve N number of entries in a shared ring.
Not yet, but I'd be fine to try anything if there's better alternatives. Besides, could you help explain why sharing one ring and let each vcpu to reserve a region in the ring could be helpful in the pov of performance?
The goal would be to avoid taking a lock, or at least to avoid holding a lock for an extended duration, e.g. some sort of multi-step process where entries in the ring are first reserved, then filled, and finally marked valid. That'd allow the "fill" action to be done in parallel.
In case it isn't clear, I haven't thought through an actual solution :-).
My point is that I think it's worth exploring and profiling other implementations because the dual per-vm and per-vcpu rings has a few warts that we'd be stuck with forever.
Quoted 8 lines
Quoted 4 lines
IMO,
addingkvm_get_running_vcpu()is a hack that is just asking for future abuse and the vcpu/vm/as_id interactions inmark_page_dirty_in_ring()look extremely fragile.I agree. Another way is to put heavier traffic to the per-vm ring, but the downside could be that the per-vm ring could get full easier (but I haven't tested).
There's nothing that prevents increasing the size of the common ring each time a new vCPU is added. Alternatively, userspace could explicitly request or hint the desired ring size.
Quoted 9 lines
Quoted 2 lines
I also dislike having two different mechanisms
for accessing the ring (lock for per-vm, something else for per-vcpu).Actually I proposed to drop the per-vm ring (actually I had a version that implemented this.. and I just changed it back to the per-vm ring later on, see below) and when there's no vcpu context I thought about:
(1) use vcpu0 ring
Quoted 8 lines
(2) or a better algo to pick up a per-vcpu ring (like, the less full ring, we can do many things here, e.g., we can easily maintain a structure track this so we can get O(1) search, I think)
I discussed this with Paolo, but I think Paolo preferred the per-vm ring because there's no good reason to choose vcpu0 as what (1) suggested. While if to choose (2) we probably need to lock even for per-cpu ring, so could be a bit slower.
Ya, per-vm is definitely better than dumping on vcpu0. I'm hoping we can find a third option that provides comparable performance without using any per-vcpu rings.
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 24 lines · Sean Christopherson
Quoted 15 lines · Peter Xu
Quoted 9 lines · Sean Christopherson
Quoted 4 lines · Peter Xu
Currently, we have N+1 rings for each VM of N vcpus:
- for each vcpu, we have 1 per-vcpu dirty ring,
- for each vm, we have 1 per-vm dirty ringWhy? I assume the purpose of per-vcpu rings is to avoid contention between threads, but the motiviation needs to be explicitly stated. And why is a per-vm fallback ring needed?
Yes, as explained in previous reply, the problem is there could have guest memory writes without vcpu contexts.
Quoted 1 line
Quoted 7 lines
Quoted 2 lines
If my assumption is correct, have other approaches been tried/profiled? E.g. using cmpxchg to reserve N number of entries in a shared ring.
Not yet, but I'd be fine to try anything if there's better alternatives. Besides, could you help explain why sharing one ring and let each vcpu to reserve a region in the ring could be helpful in the pov of performance?
Quoted 4 lines
The goal would be to avoid taking a lock, or at least to avoid holding a lock for an extended duration, e.g. some sort of multi-step process where entries in the ring are first reserved, then filled, and finally marked valid. That'd allow the "fill" action to be done in parallel.
Considering that per-vcpu ring should be no worst than this, so iiuc you prefer a single per-vm ring here, which is without per-vcpu ring. However I don't see a good reason to split a per-vm resource into per-vcpu manually somehow, instead of using the per-vcpu structure directly like what this series does... Or could you show me what I've missed?
IMHO it's really a natural thought that we should use kvm_vcpu to split the ring as long as we still want to make it in parallel of the vcpus.
Quoted 2 lines
In case it isn't clear, I haven't thought through an actual solution :-).
Feel free to shoot when the ideas come. :) I'd be glad to test your idea, especially where it could be better!
Quoted 4 lines
My point is that I think it's worth exploring and profiling other implementations because the dual per-vm and per-vcpu rings has a few warts that we'd be stuck with forever.
I do agree that the interface could be a bit awkward to keep these two rings. Besides this, do you still have other concerns?
And when you say about profiling, I hope I understand it right that it should be something unrelated to this specific issue that we're discussing (say, on whether to use per-vm ring, or per-vm + per-vcpu rings) because for performance imho it's really the layout of the ring that could matter more, and how the ring is shared and accessed between the userspace and kernel.
For current implementation (I'm not sure whether that's initial version from Lei, or Paolo, anyway...), IMHO it's good enough from perf pov in that it at least supports:
(1) zero copy
(2) complete async model
(3) per-vcpu isolations
None of these is there for KVM_GET_DIRTY_LOG. Not to mention that tracking dirty bits are not really that "performance critical" - if you see in QEMU we have plenty of ways to explicitly turn down the CPU like cpu-throttle, just because dirtying pages and even with the whole tracking overhead is too fast already even using KVM_GET_DIRTY_LOG, and the slow thing is QEMU when collecting and sending the pages! :)
Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
IMO,
addingkvm_get_running_vcpu()is a hack that is just asking for future abuse and the vcpu/vm/as_id interactions inmark_page_dirty_in_ring()look extremely fragile.
Quoted 7 lines
Quoted 3 lines
I agree. Another way is to put heavier traffic to the per-vm ring, but the downside could be that the per-vm ring could get full easier (but I haven't tested).
There's nothing that prevents increasing the size of the common ring each time a new vCPU is added. Alternatively, userspace could explicitly request or hint the desired ring size.
Yeah I don't have strong opinion on this, but I just don't see it greatly helpful to explicitly expose this API to userspace. IMHO for now a global ring size should be good enough. If userspace wants to make it fast, the ring can hardly gets full (because the collection of the dirty ring can be really, really fast if the userspace wants).
Quoted 14 lines
Quoted 14 lines
Quoted 3 lines
I also dislike having two different mechanisms
for accessing the ring (lock for per-vm, something else for per-vcpu).Actually I proposed to drop the per-vm ring (actually I had a version that implemented this.. and I just changed it back to the per-vm ring later on, see below) and when there's no vcpu context I thought about:
(1) use vcpu0 ring
(2) or a better algo to pick up a per-vcpu ring (like, the less full ring, we can do many things here, e.g., we can easily maintain a structure track this so we can get O(1) search, I think)
Quoted 8 lines
Quoted 4 lines
I discussed this with Paolo, but I think Paolo preferred the per-vm ring because there's no good reason to choose vcpu0 as what (1) suggested. While if to choose (2) we probably need to lock even for per-cpu ring, so could be a bit slower.
Ya, per-vm is definitely better than dumping on vcpu0. I'm hoping we can find a third option that provides comparable performance without using any per-vcpu rings.
I'm still uncertain on whether it's a good idea to drop the per-vcpu ring (as stated above). But I'm still open to any further thoughts as long as I can start to understand when the only-per-vm ring would be better.
Thanks!
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
In reply to Sean ChristophersonQuoted 9 lines · Sean Christopherson
Quoted 5 lines
I discussed this with Paolo, but I think Paolo preferred the per-vm ring because there's no good reason to choose vcpu0 as what (1) suggested. While if to choose (2) we probably need to lock even for per-cpu ring, so could be a bit slower.
Ya, per-vm is definitely better than dumping on vcpu0. I'm hoping we can find a third option that provides comparable performance without using any per-vcpu rings.
The advantage of per-vCPU rings is that it naturally: 1) parallelizes the processing of dirty pages; 2) makes userspace vCPU thread do more work on vCPUs that dirty more pages.
I agree that on the producer side we could reserve multiple entries in the case of PML (and without PML only one entry should be added at a time). But I'm afraid that things get ugly when the ring is full, because you'd have to wait for all vCPUs to finish publishing the entries they have reserved.
It's ugly that we _also_ need a per-VM ring, but unfortunately some operations do not really have a vCPU that they can refer to.
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 15 lines · Paolo Bonzini
Quoted 9 lines · Sean Christopherson
Quoted 5 lines
I discussed this with Paolo, but I think Paolo preferred the per-vm ring because there's no good reason to choose vcpu0 as what (1) suggested. While if to choose (2) we probably need to lock even for per-cpu ring, so could be a bit slower.
Ya, per-vm is definitely better than dumping on vcpu0. I'm hoping we can find a third option that provides comparable performance without using any per-vcpu rings.
The advantage of per-vCPU rings is that it naturally: 1) parallelizes the processing of dirty pages; 2) makes userspace vCPU thread do more work on vCPUs that dirty more pages.
Quoted 5 lines
I agree that on the producer side we could reserve multiple entries in the case of PML (and without PML only one entry should be added at a time). But I'm afraid that things get ugly when the ring is full, because you'd have to wait for all vCPUs to finish publishing the entries they have reserved.
Ah, I take it the intended model is that userspace will only start pulling entries off the ring when KVM explicitly signals that the ring is "full"?
Rather than reserve entries, what if vCPUs reserved an entire ring? Create a pool of N=nr_vcpus rings that are shared by all vCPUs. To mark pages dirty, a vCPU claims a ring, pushes the pages into the ring, and then returns the ring to the pool. If pushing pages hits the soft limit, a request is made to drain the ring and the ring is not returned to the pool until it is drained.
Except for acquiring a ring, which likely can be heavily optimized, that'd allow parallel processing (#1), and would provide a facsimile of #2 as pushing more pages onto a ring would naturally increase the likelihood of triggering a drain. And it might be interesting to see the effect of using different methods of ring selection, e.g. pure round robin, LRU, last used on the current vCPU, etc...
Quoted 2 lines
It's ugly that we _also_ need a per-VM ring, but unfortunately some operations do not really have a vCPU that they can refer to.
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 16 lines · Sean Christopherson
Quoted 15 lines · Paolo Bonzini
Quoted 9 lines · Sean Christopherson
Quoted 5 lines
I discussed this with Paolo, but I think Paolo preferred the per-vm ring because there's no good reason to choose vcpu0 as what (1) suggested. While if to choose (2) we probably need to lock even for per-cpu ring, so could be a bit slower.
Ya, per-vm is definitely better than dumping on vcpu0. I'm hoping we can find a third option that provides comparable performance without using any per-vcpu rings.
The advantage of per-vCPU rings is that it naturally: 1) parallelizes the processing of dirty pages; 2) makes userspace vCPU thread do more work on vCPUs that dirty more pages.
Quoted 8 lines
Quoted 5 lines
I agree that on the producer side we could reserve multiple entries in the case of PML (and without PML only one entry should be added at a time). But I'm afraid that things get ugly when the ring is full, because you'd have to wait for all vCPUs to finish publishing the entries they have reserved.
Ah, I take it the intended model is that userspace will only start pulling entries off the ring when KVM explicitly signals that the ring is "full"?
No, it's not. But perhaps in the asynchronous case you can delay pushing the reserved entries to the consumer until a moment where no CPUs have left empty slots in the ring buffer (somebody must have done multi-producer ring buffers before). In the ring-full case that is harder because it requires synchronization.
Quoted 7 lines
Rather than reserve entries, what if vCPUs reserved an entire ring? Create a pool of N=nr_vcpus rings that are shared by all vCPUs. To mark pages dirty, a vCPU claims a ring, pushes the pages into the ring, and then returns the ring to the pool. If pushing pages hits the soft limit, a request is made to drain the ring and the ring is not returned to the pool until it is drained.
Quoted 6 lines
Except for acquiring a ring, which likely can be heavily optimized, that'd allow parallel processing (#1), and would provide a facsimile of #2 as pushing more pages onto a ring would naturally increase the likelihood of triggering a drain. And it might be interesting to see the effect of using different methods of ring selection, e.g. pure round robin, LRU, last used on the current vCPU, etc...
If you are creating nr_vcpus rings, and draining is done on the vCPU thread that has filled the ring, why not create nr_vcpus+1? The current code then is exactly the same as pre-claiming a ring per vCPU and never releasing it, and using a spinlock to claim the per-VM ring.
However, we could build on top of my other suggestion to add slot->as_id, and wrap kvm_get_running_vcpu() with a nice API, mimicking exactly what you've suggested. Maybe even add a scary comment around kvm_get_running_vcpu() suggesting that users only do so to avoid locking and wrap it with a nice API. Similar to what get_cpu/put_cpu do with smp_processor_id.
1) Add a pointer from struct kvm_dirty_ring to struct kvm_dirty_ring_indexes:
vcpu->dirty_ring->data = &vcpu->run->vcpu_ring_indexes; kvm->vm_dirty_ring->data = *kvm->vm_run->vm_ring_indexes;
2) push the ring choice and locking to two new functions
struct kvm_ring *kvm_get_dirty_ring(struct kvm *kvm)
{
struct kvm_vcpu *vcpu = kvm_get_running_vcpu();
if (vcpu && !WARN_ON_ONCE(vcpu->kvm != kvm)) {
return &vcpu->dirty_ring;
} else {
/*
* Put onto per vm ring because no vcpu context.
* We'll kick vcpu0 if ring is full.
*/
spin_lock(&kvm->vm_dirty_ring->lock);
return &kvm->vm_dirty_ring;
}
}void kvm_put_dirty_ring(struct kvm *kvm,
struct kvm_dirty_ring *ring)
{
struct kvm_vcpu *vcpu = kvm_get_running_vcpu();
bool full = kvm_dirty_ring_used(ring) >= ring->soft_limit;
if (ring == &kvm->vm_dirty_ring) {
if (vcpu == NULL)
vcpu = kvm->vcpus[0];
spin_unlock(&kvm->vm_dirty_ring->lock);
}
if (full)
kvm_make_request(KVM_REQ_DIRTY_RING_FULL, vcpu);
}3) simplify kvm_dirty_ring_push to
void kvm_dirty_ring_push(struct kvm_dirty_ring *ring,
u32 slot, u64 offset)
{
/* left as an exercise to the reader */
}and mark_page_dirty_in_ring to
static void mark_page_dirty_in_ring(struct kvm *kvm,
struct kvm_memory_slot *slot, gfn_t gfn)
{
struct kvm_dirty_ring *ring;
if (!kvm->dirty_ring_size)
return;
ring = kvm_get_dirty_ring(kvm);
kvm_dirty_ring_push(ring, (slot->as_id << 16) | slot->id,
gfn - slot->base_gfn);
kvm_put_dirty_ring(kvm, ring);
}Paolo
Quoted 3 lines
Quoted 2 lines
It's ugly that we _also_ need a per-VM ring, but unfortunately some operations do not really have a vCPU that they can refer to.
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 15 lines · Paolo Bonzini
Quoted 7 lines · Sean Christopherson
Rather than reserve entries, what if vCPUs reserved an entire ring? Create a pool of N=nr_vcpus rings that are shared by all vCPUs. To mark pages dirty, a vCPU claims a ring, pushes the pages into the ring, and then returns the ring to the pool. If pushing pages hits the soft limit, a request is made to drain the ring and the ring is not returned to the pool until it is drained.
Quoted 6 lines
Except for acquiring a ring, which likely can be heavily optimized, that'd allow parallel processing (#1), and would provide a facsimile of #2 as pushing more pages onto a ring would naturally increase the likelihood of triggering a drain. And it might be interesting to see the effect of using different methods of ring selection, e.g. pure round robin, LRU, last used on the current vCPU, etc...
Quoted 4 lines
If you are creating
nr_vcpusrings, and draining is done on the vCPU thread that has filled the ring, why not create nr_vcpus+1? The current code then is exactly the same as pre-claiming a ring per vCPU and never releasing it, and using a spinlock to claim the per-VM ring.
Because I really don't like kvm_get_running_vcpu() :-)
Binding the rings to vCPUs also makes for an inflexible API, e.g. the amount of memory required for the rings scales linearly with the number of vCPUs, or maybe there's a use case for having M:N vCPUs:rings.
That being said, I'm pretty clueless when it comes to implementing and tuning the userspace side of this type of stuff, so feel free to ignore my thoughts on the API.
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 16 lines · Sean Christopherson
Quoted 15 lines · Paolo Bonzini
Quoted 13 lines · Sean Christopherson
Rather than reserve entries, what if vCPUs reserved an entire ring? Create a pool of N=nr_vcpus rings that are shared by all vCPUs. To mark pages dirty, a vCPU claims a ring, pushes the pages into the ring, and then returns the ring to the pool. If pushing pages hits the soft limit, a request is made to drain the ring and the ring is not returned to the pool until it is drained.
Except for acquiring a ring, which likely can be heavily optimized, that'd allow parallel processing (#1), and would provide a facsimile of #2 as pushing more pages onto a ring would naturally increase the likelihood of triggering a drain. And it might be interesting to see the effect of using different methods of ring selection, e.g. pure round robin, LRU, last used on the current vCPU, etc...
Quoted 6 lines
Quoted 4 lines
If you are creating
nr_vcpusrings, and draining is done on the vCPU thread that has filled the ring, why not create nr_vcpus+1? The current code then is exactly the same as pre-claiming a ring per vCPU and never releasing it, and using a spinlock to claim the per-VM ring.Because I really don't like
kvm_get_running_vcpu():-)
I also don't like it particularly, but I think it's okay to wrap it into a nicer API.
Quoted 3 lines
Binding the rings to vCPUs also makes for an inflexible API, e.g. the amount of memory required for the rings scales linearly with the number of vCPUs, or maybe there's a use case for having M:N vCPUs:rings.
If we can get rid of the dirty bitmap, the amount of memory is probably going to be smaller anyway. For example at 64k per ring, 256 rings occupy 16 MiB of memory, and that is the cost of dirty bitmaps for 512 GiB of guest memory, and that's probably what you can expect for the memory of a 256-vCPU guest (at least roughly: if the memory is 128 GiB, the extra 12 MiB for dirty page rings don't really matter).
Paolo
Quoted 4 lines
That being said, I'm pretty clueless when it comes to implementing and tuning the userspace side of this type of stuff, so feel free to ignore my thoughts on the API.
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
In reply to Paolo BonziniQuoted 111 lines · Paolo Bonzini
Quoted 16 lines · Sean Christopherson
Quoted 15 lines · Paolo Bonzini
Quoted 9 lines · Sean Christopherson
Quoted 5 lines
I discussed this with Paolo, but I think Paolo preferred the per-vm ring because there's no good reason to choose vcpu0 as what (1) suggested. While if to choose (2) we probably need to lock even for per-cpu ring, so could be a bit slower.
Ya, per-vm is definitely better than dumping on vcpu0. I'm hoping we can find a third option that provides comparable performance without using any per-vcpu rings.
The advantage of per-vCPU rings is that it naturally: 1) parallelizes the processing of dirty pages; 2) makes userspace vCPU thread do more work on vCPUs that dirty more pages.
Quoted 8 lines
Quoted 5 lines
I agree that on the producer side we could reserve multiple entries in the case of PML (and without PML only one entry should be added at a time). But I'm afraid that things get ugly when the ring is full, because you'd have to wait for all vCPUs to finish publishing the entries they have reserved.
Ah, I take it the intended model is that userspace will only start pulling entries off the ring when KVM explicitly signals that the ring is "full"?
No, it's not. But perhaps in the asynchronous case you can delay pushing the reserved entries to the consumer until a moment where no CPUs have left empty slots in the ring buffer (somebody must have done multi-producer ring buffers before). In the ring-full case that is harder because it requires synchronization.
Quoted 7 lines
Rather than reserve entries, what if vCPUs reserved an entire ring? Create a pool of N=nr_vcpus rings that are shared by all vCPUs. To mark pages dirty, a vCPU claims a ring, pushes the pages into the ring, and then returns the ring to the pool. If pushing pages hits the soft limit, a request is made to drain the ring and the ring is not returned to the pool until it is drained.
Quoted 6 lines
Except for acquiring a ring, which likely can be heavily optimized, that'd allow parallel processing (#1), and would provide a facsimile of #2 as pushing more pages onto a ring would naturally increase the likelihood of triggering a drain. And it might be interesting to see the effect of using different methods of ring selection, e.g. pure round robin, LRU, last used on the current vCPU, etc...
If you are creating
nr_vcpusrings, and draining is done on the vCPU thread that has filled the ring, why not create nr_vcpus+1? The current code then is exactly the same as pre-claiming a ring per vCPU and never releasing it, and using a spinlock to claim the per-VM ring.However, we could build on top of my other suggestion to add
slot->as_id, and wrapkvm_get_running_vcpu()with a nice API, mimicking exactly what you've suggested. Maybe even add a scary comment aroundkvm_get_running_vcpu()suggesting that users only do so to avoid locking and wrap it with a nice API. Similar to what get_cpu/put_cpu do withsmp_processor_id.1) Add a pointer from struct
kvm_dirty_ringto structkvm_dirty_ring_indexes:
vcpu->dirty_ring->data=&vcpu->run->vcpu_ring_indexes;kvm->vm_dirty_ring->data=*kvm->vm_run->vm_ring_indexes;2) push the ring choice and locking to two new functions
struct
kvm_ring*kvm_get_dirty_ring(struct kvm *kvm){ struct kvm_vcpu *vcpu = kvm_get_running_vcpu(); if (vcpu && !WARN_ON_ONCE(vcpu->kvm != kvm)) { return &vcpu->dirty_ring; } else { /* * Put onto per vm ring because no vcpu context. * We'll kick vcpu0 if ring is full. */ spin_lock(&kvm->vm_dirty_ring->lock); return &kvm->vm_dirty_ring; } }void kvm_put_dirty_ring(struct kvm *kvm,
structkvm_dirty_ring*ring){ struct kvm_vcpu *vcpu = kvm_get_running_vcpu(); bool full = kvm_dirty_ring_used(ring) >= ring->soft_limit; if (ring == &kvm->vm_dirty_ring) { if (vcpu == NULL) vcpu = kvm->vcpus[0]; spin_unlock(&kvm->vm_dirty_ring->lock); } if (full) kvm_make_request(KVM_REQ_DIRTY_RING_FULL, vcpu); }3) simplify
kvm_dirty_ring_pushtovoid kvm_dirty_ring_push(struct
kvm_dirty_ring*ring,u32 slot, u64 offset) { /* left as an exercise to the reader */ }and
mark_page_dirty_in_ringtostatic void mark_page_dirty_in_ring(struct kvm *kvm,
structkvm_memory_slot*slot,gfn_tgfn)
Quoted 11 lines
{
struct kvm_dirty_ring *ring;
if (!kvm->dirty_ring_size)
return;
ring = kvm_get_dirty_ring(kvm);
kvm_dirty_ring_push(ring, (slot->as_id << 16) | slot->id,
gfn - slot->base_gfn);
kvm_put_dirty_ring(kvm, ring);
}I think I got the major point here. Unless Sean has some better idea in the future I'll go with this.
Just until recently I noticed that actually kvm_get_running_vcpu() has a real benefit in that it gives a very solid result on whether we're with the vcpu context, even more accurate than when we pass vcpu pointers around (because sometimes we just passed the kvm pointer along the stack even if we're with a vcpu context, just like what we did with mark_page_dirty_in_slot). I'm thinking whether I can start to use this information in the next post on solving an issue I encountered with the waitqueue.
Current waitqueue is still problematic in that it could wait even with the mmu lock held when with vcpu context.
The issue is KVM_RESET_DIRTY_RINGS needs the mmu lock to manipulate the write bits, while it's the only interface to also wake up the dirty ring sleepers. They could dead lock like this:
main thread vcpu thread
=========== ===========
kvm page fault
mark_page_dirty_in_slot mmu lock taken mark dirty, ring full queue on waitqueue (with mmu lock)
KVM_RESET_DIRTY_RINGS
take mmu lock <------------ deadlock here
reset ring gfns
wakeup dirty ring sleepers
And if we see if the mark_page_dirty_in_slot() is not with a vcpu context (e.g. kvm_mmu_page_fault) but with an ioctl context (those cases we'll use per-vm dirty ring) then it's probably fine.
My planned solution:
- When kvm_get_running_vcpu() != NULL, we postpone the waitqueue waits until we finished handling this page fault, probably in somewhere around vcpu_enter_guest, so that we can do wait_event() after the mmu lock released
- For per-vm ring full, I'll do what we do now (wait_event() as long in mark_page_dirty_in_ring) assuming it should not be with the mmu lock held
To achieve above, I think I really need to know exactly on whether we're with the vcpu context, where I suppose kvm_get_running_vcpu() would work for me then, rather than checking against vcpu pointer passed in.
I also wanted to let KVM_RUN return immediately if either per-vm ring or per-vcpu ring reaches softlimit always, instead of continue execution until the next dirty ring full event.
I'd be glad to receive any early comment before I move on to these.
Thanks!
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 6 lines · Peter Xu
Just until recently I noticed that actually
kvm_get_running_vcpu()has a real benefit in that it gives a very solid result on whether we're with the vcpu context, even more accurate than when we pass vcpu pointers around (because sometimes we just passed the kvm pointer along the stack even if we're with a vcpu context, just like what we did withmark_page_dirty_in_slot).
Right, that's the point.
Quoted 6 lines
I'm thinking whether I can start
to use this information in the next post on solving an issue I encountered with the waitqueue.Current waitqueue is still problematic in that it could wait even with the mmu lock held when with vcpu context.
I think the idea of the soft limit is that the waiting just cannot happen. That is, the number of dirtied pages _outside_ the guest (guest accesses are taken care of by PML, and are subtracted from the soft limit) cannot exceed hard_limit - (soft_limit + pml_size).
Quoted 21 lines
The issue is
KVM_RESET_DIRTY_RINGSneeds the mmu lock to manipulate the write bits, while it's the only interface to also wake up the dirty ring sleepers. They could dead lock like this:main thread vcpu thread
=========== ===========
kvm page fault
mark_page_dirty_in_slotmmu lock taken mark dirty, ring full queue on waitqueue (with mmu lock)
KVM_RESET_DIRTY_RINGS
take mmu lock <------------ deadlock here
reset ring gfns
wakeup dirty ring sleepersAnd if we see if the
mark_page_dirty_in_slot()is not with a vcpu context (e.g.kvm_mmu_page_fault) but with an ioctl context (those cases we'll use per-vm dirty ring) then it's probably fine.
Quoted 6 lines
My planned solution:
- When
kvm_get_running_vcpu()!= NULL, we postpone the waitqueue waits until we finished handling this page fault, probably in somewhere aroundvcpu_enter_guest, so that we can dowait_event()after the mmu lock released
I think this can cause a race:
vCPU 1 vCPU 2 host
---------------------------------------------------------------
mark page dirty
write to page
treat page as not dirty
add page to ring
where vCPU 2 skips the clean-page slow path entirely.
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 11 lines · Paolo Bonzini
Quoted 6 lines
I'm thinking whether I can start
to use this information in the next post on solving an issue I encountered with the waitqueue.Current waitqueue is still problematic in that it could wait even with the mmu lock held when with vcpu context.
I think the idea of the soft limit is that the waiting just cannot happen. That is, the number of dirtied pages _outside_ the guest (guest accesses are taken care of by PML, and are subtracted from the soft limit) cannot exceed
hard_limit- (soft_limit+pml_size).
So the question go backs to, whether this is guaranteed somehow? Or do you prefer us to keep the warn_on_once until it triggers then we can analyze (which I doubt..)?
One thing to mention is that for with-vcpu cases, we probably can even stop KVM_RUN immediately as long as either the per-vm or per-vcpu ring reaches the softlimit, then for vcpu case it should be easier to guarantee that. What I want to know is the rest of cases like ioctls or even something not from the userspace (which I think I should read more later..).
If the answer is yes, I'd be more than glad to drop the waitqueue.
Quoted 31 lines
Quoted 22 lines
The issue is
KVM_RESET_DIRTY_RINGSneeds the mmu lock to manipulate the write bits, while it's the only interface to also wake up the dirty ring sleepers. They could dead lock like this:main thread vcpu thread
=========== ===========
kvm page fault
mark_page_dirty_in_slotmmu lock taken mark dirty, ring full queue on waitqueue (with mmu lock)
KVM_RESET_DIRTY_RINGS
take mmu lock <------------ deadlock here
reset ring gfns
wakeup dirty ring sleepersAnd if we see if the
mark_page_dirty_in_slot()is not with a vcpu context (e.g.kvm_mmu_page_fault) but with an ioctl context (those cases we'll use per-vm dirty ring) then it's probably fine.Quoted 6 lines
My planned solution:
- When
kvm_get_running_vcpu()!= NULL, we postpone the waitqueue waits until we finished handling this page fault, probably in somewhere aroundvcpu_enter_guest, so that we can dowait_event()after the mmu lock releasedI think this can cause a race:
Quoted 8 lines
vCPU 1 vCPU 2 host
---------------------------------------------------------------
mark page dirty
write to page
treat page as not dirty
add page to ringwhere vCPU 2 skips the clean-page slow path entirely.
If we're still with the rule in userspace that we first do RESET then collect and send the pages (just like what we've discussed before), then IMHO it's fine to have vcpu2 to skip the slow path? Because RESET happens at "treat page as not dirty", then if we are sure that we only collect and send pages after that point, then the latest "write to page" data from vcpu2 won't be lost even if vcpu2 is not blocked by vcpu1's ring full?
Maybe we can also consider to let mark_page_dirty_in_slot() return a value, then the upper layer could have a chance to skip the spte update if mark_page_dirty_in_slot() fails to mark the dirty bit, so it can return directly with RET_PF_RETRY.
Thanks,
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 8 lines · Peter Xu
Quoted 7 lines · Paolo Bonzini
Quoted 6 lines
I'm thinking whether I can start
to use this information in the next post on solving an issue I encountered with the waitqueue.Current waitqueue is still problematic in that it could wait even with the mmu lock held when with vcpu context.
Quoted 8 lines
Quoted 4 lines
I think the idea of the soft limit is that the waiting just cannot happen. That is, the number of dirtied pages _outside_ the guest (guest accesses are taken care of by PML, and are subtracted from the soft limit) cannot exceed
hard_limit- (soft_limit+pml_size).So the question go backs to, whether this is guaranteed somehow? Or do you prefer us to keep the
warn_on_onceuntil it triggers then we can analyze (which I doubt..)?
Yes, I would like to keep the WARN_ON_ONCE just because you never know.
Of course it would be much better to audit the calls to kvm_write_guest and figure out how many could trigger (e.g. two from the operands of an emulated instruction, 5 from a nested EPT walk, 1 from a page walk, etc.).
Quoted 6 lines
One thing to mention is that for with-vcpu cases, we probably can even stop
KVM_RUNimmediately as long as either the per-vm or per-vcpu ring reaches the softlimit, then for vcpu case it should be easier to guarantee that. What I want to know is the rest of cases like ioctls or even something not from the userspace (which I think I should read more later..).
Which ioctls? Most ioctls shouldn't dirty memory at all.
Quoted 22 lines
Quoted 13 lines
Quoted 10 lines
And if we see if the
mark_page_dirty_in_slot()is not with a vcpu context (e.g.kvm_mmu_page_fault) but with an ioctl context (those cases we'll use per-vm dirty ring) then it's probably fine.My planned solution:
- When
kvm_get_running_vcpu()!= NULL, we postpone the waitqueue waits until we finished handling this page fault, probably in somewhere aroundvcpu_enter_guest, so that we can dowait_event()after the mmu lock releasedI think this can cause a race:
Quoted 8 lines
vCPU 1 vCPU 2 host
---------------------------------------------------------------
mark page dirty
write to page
treat page as not dirty
add page to ringwhere vCPU 2 skips the clean-page slow path entirely.
Quoted 7 lines
If we're still with the rule in userspace that we first do RESET then collect and send the pages (just like what we've discussed before), then IMHO it's fine to have vcpu2 to skip the slow path? Because RESET happens at "treat page as not dirty", then if we are sure that we only collect and send pages after that point, then the latest "write to page" data from vcpu2 won't be lost even if vcpu2 is not blocked by vcpu1's ring full?
Good point, the race would become
vCPU 1 vCPU 2 host
---------------------------------------------------------------
mark page dirty
write to page
reset rings
wait for mmu lock
add page to ring
release mmu lock
...do reset... release mmu lock
page is now dirty
Quoted 4 lines
Maybe we can also consider to let
mark_page_dirty_in_slot()return a value, then the upper layer could have a chance to skip the spte update ifmark_page_dirty_in_slot()fails to mark the dirty bit, so it can return directly withRET_PF_RETRY.
I don't think that's possible, most writes won't come from a page fault path and cannot retry.
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 18 lines · Paolo Bonzini
Quoted 8 lines · Peter Xu
Quoted 7 lines · Paolo Bonzini
Quoted 6 lines
I'm thinking whether I can start
to use this information in the next post on solving an issue I encountered with the waitqueue.Current waitqueue is still problematic in that it could wait even with the mmu lock held when with vcpu context.
Quoted 8 lines
Quoted 4 lines
I think the idea of the soft limit is that the waiting just cannot happen. That is, the number of dirtied pages _outside_ the guest (guest accesses are taken care of by PML, and are subtracted from the soft limit) cannot exceed
hard_limit- (soft_limit+pml_size).So the question go backs to, whether this is guaranteed somehow? Or do you prefer us to keep the
warn_on_onceuntil it triggers then we can analyze (which I doubt..)?
Quoted 5 lines
Yes, I would like to keep the
WARN_ON_ONCEjust because you never know.Of course it would be much better to audit the calls to
kvm_write_guestand figure out how many could trigger (e.g. two from the operands of an emulated instruction, 5 from a nested EPT walk, 1 from a page walk, etc.).
I would say we'd better either figure out all the caller's sites to prove it will never overflow, or, I think we'll need the waitqueue at least. The problem is if we release a kvm with WARN_ON_ONCE and at last we found that it can be triggered and ring full can't be avoided, then it means the interface and design is broken, and it could even be too late to fix it after the interface is published.
(Actually I was not certain on previous clear_dirty interface where we
introduced a new capability for it. I'm not sure whether that can be avoided because after all the initial version is not working at all, and we fixed it up without changing the interface. However for this one if at last we prove the design wrong, then we must introduce another capability for it IMHO, and the interface is prone to change too)
So, with the hope that we could avoid the waitqueue, I checked all the callers of mark_page_dirty_in_slot(). Since this initial work is only for x86, I didn't look more into other archs, assuming that can be done later when it is implemented for other archs (and this will for sure also cover the common code):
mark_page_dirty_in_slot calls, per-vm (x86 only)
__kvm_write_guest_page
kvm_write_guest_page
init_rmode_tss
vmx_set_tss_addr
kvm_vm_ioctl_set_tss_addr [*]
init_rmode_identity_map
vmx_create_vcpu [*]
vmx_write_pml_buffer
kvm_arch_write_log_dirty [&]
kvm_write_guest
kvm_hv_setup_tsc_page
kvm_guest_time_update [&]
nested_flush_cached_shadow_vmcs12 [&]
kvm_write_wall_clock [&]
kvm_pv_clock_pairing [&]
kvmgt_rw_gpa [?]
kvm_write_guest_offset_cached
kvm_steal_time_set_preempted [&]
kvm_write_guest_cached
pv_eoi_put_user [&]
kvm_lapic_sync_to_vapic [&]
kvm_setup_pvclock_page [&]
record_steal_time [&]
apf_put_user [&]
kvm_clear_guest_page
init_rmode_tss [*] (see above)
init_rmode_identity_map [*] (see above)
kvm_clear_guest
synic_set_msr
kvm_hv_set_msr [&]
kvm_write_guest_offset_cached [&] (see above)
mark_page_dirty
kvm_hv_set_msr_pw [&]We should only need to look at the leaves of the traces because they're where the dirty request starts. I'm marking all the leaves with below criteria then it'll be easier to focus:
Cases with [*]: should not matter much
[&]: actually with a per-vcpu context in the upper layer [?]: uncertain...
I'm a bit amazed after I took these notes, since I found that besides those that could probbaly be ignored (marked as [*]), most of the rest per-vm dirty requests are actually with a vcpu context.
Although now because we have kvm_get_running_vcpu() all cases for [&] should be fine without changing anything, but I tend to add another patch in the next post to convert all the [&] cases explicitly to pass vcpu pointer instead of kvm pointer to be clear if no one disagrees, then we verify that against kvm_get_running_vcpu().
So the only uncertainty now is kvmgt_rw_gpa() which is marked as [?]. Could this happen frequently? I would guess the answer is we don't know (which means it can).
Quoted 9 lines
Quoted 7 lines
One thing to mention is that for with-vcpu cases, we probably can even stop
KVM_RUNimmediately as long as either the per-vm or per-vcpu ring reaches the softlimit, then for vcpu case it should be easier to guarantee that. What I want to know is the rest of cases like ioctls or even something not from the userspace (which I think I should read more later..).Which ioctls? Most ioctls shouldn't dirty memory at all.
init_rmode_tss or init_rmode_identity_map. But I've marked them as unimportant because they should only happen once at boot.
Quoted 37 lines
Quoted 23 lines
Quoted 14 lines
Quoted 11 lines
And if we see if the
mark_page_dirty_in_slot()is not with a vcpu context (e.g.kvm_mmu_page_fault) but with an ioctl context (those cases we'll use per-vm dirty ring) then it's probably fine.My planned solution:
- When
kvm_get_running_vcpu()!= NULL, we postpone the waitqueue waits until we finished handling this page fault, probably in somewhere aroundvcpu_enter_guest, so that we can dowait_event()after the mmu lock releasedI think this can cause a race:
Quoted 8 lines
vCPU 1 vCPU 2 host
---------------------------------------------------------------
mark page dirty
write to page
treat page as not dirty
add page to ringwhere vCPU 2 skips the clean-page slow path entirely.
Quoted 7 lines
If we're still with the rule in userspace that we first do RESET then collect and send the pages (just like what we've discussed before), then IMHO it's fine to have vcpu2 to skip the slow path? Because RESET happens at "treat page as not dirty", then if we are sure that we only collect and send pages after that point, then the latest "write to page" data from vcpu2 won't be lost even if vcpu2 is not blocked by vcpu1's ring full?
Good point, the race would become
(quoted diff) +0
-0vCPU 1 vCPU 2 host --------------------------------------------------------------- mark page dirty write to page
Quoted 7 lines
reset rings
wait for mmu lock
add page to ring
release mmu lock
...do reset... release mmu lock
page is now dirty
Hmm, the page will be dirty after the reset, but is that an issue?
Or, could you help me to identify what I've missed?
Quoted 8 lines
Quoted 5 lines
Maybe we can also consider to let
mark_page_dirty_in_slot()return a value, then the upper layer could have a chance to skip the spte update ifmark_page_dirty_in_slot()fails to mark the dirty bit, so it can return directly withRET_PF_RETRY.I don't think that's possible, most writes won't come from a page fault path and cannot retry.
Yep, maybe I should say it in the other way round: we only wait if kvm_get_running_vcpu() == NULL. Then in somewhere near vcpu_enter_guest(), we add a check to wait if per-vcpu ring is full. Would that work?
Thanks,
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
[Alex and Kevin: there are doubts below regarding dirty page tracking from VFIO and mdev devices, which perhaps you can help with]
Quoted 5 lines · Peter Xu
init_rmode_tss
vmx_set_tss_addr
kvm_vm_ioctl_set_tss_addr [*]
init_rmode_identity_map
vmx_create_vcpu [*]These don't matter because their content is not visible to userspace (the backing storage is mmap-ed by __x86_set_memory_region). In fact, d
Quoted 9 lines
vmx_write_pml_buffer
kvm_arch_write_log_dirty [&]
kvm_write_guest
kvm_hv_setup_tsc_page
kvm_guest_time_update [&]
nested_flush_cached_shadow_vmcs12 [&]
kvm_write_wall_clock [&]
kvm_pv_clock_pairing [&]
kvmgt_rw_gpa [?]This then expands (partially) to
intel_gvt_hypervisor_write_gpa
emulate_csb_update
emulate_execlist_ctx_schedule_out
complete_execlist_workload
complete_current_workload
workload_thread
emulate_execlist_ctx_schedule_in
prepare_execlist_workload
prepare_workload
dispatch_workload
workload_thread
So KVMGT is always writing to GPAs instead of IOVAs and basically bypassing a guest IOMMU. So here it would be better if kvmgt was changed not use kvm_write_guest (also because I'd probably have nacked that if I had known :)).
As far as I know, there is some work on live migration with both VFIO and mdev, and that probably includes some dirty page tracking API. kvmgt could switch to that API, or there could be VFIO APIs similar to kvm_write_guest but taking IOVAs instead of GPAs. Advantage: this would fix the GPA/IOVA confusion. Disadvantage: userspace would lose the tracking of writes from mdev devices. Kevin, are these writes used in any way? Do the calls to intel_gvt_hypervisor_write_gpa covers all writes from kvmgt vGPUs, or can the hardware write to memory as well (which would be my guess if I didn't know anything about kvmgt, which I pretty much don't)?
Quoted 12 lines
We should only need to look at the leaves of the traces because they're where the dirty request starts. I'm marking all the leaves with below criteria then it'll be easier to focus:
Cases with [*]: should not matter much
[&]: actually with a per-vcpu context in the upper layer [?]: uncertain...I'm a bit amazed after I took these notes, since I found that besides those that could probbaly be ignored (marked as [*]), most of the rest per-vm dirty requests are actually with a vcpu context.
Quoted 5 lines
Although now because we have
kvm_get_running_vcpu()all cases for [&] should be fine without changing anything, but I tend to add another patch in the next post to convert all the [&] cases explicitly to pass vcpu pointer instead of kvm pointer to be clear if no one disagrees, then we verify that againstkvm_get_running_vcpu().
This is a good idea but remember not to convert those to kvm_vcpu_write_guest, because you _don't_ want these writes to touch SMRAM (most of the addresses are OS-controlled rather than firmware-controlled).
Quoted 2 lines
init_rmode_tssorinit_rmode_identity_map. But I've marked them as unimportant because they should only happen once at boot.
We need to check if userspace can add an arbitrary number of entries by calling KVM_SET_TSS_ADDR repeatedly. I think it can; we'd have to forbid multiple calls to KVM_SET_TSS_ADDR which is not a problem in general.
Quoted 22 lines
Quoted 14 lines
Quoted 7 lines
If we're still with the rule in userspace that we first do RESET then collect and send the pages (just like what we've discussed before), then IMHO it's fine to have vcpu2 to skip the slow path? Because RESET happens at "treat page as not dirty", then if we are sure that we only collect and send pages after that point, then the latest "write to page" data from vcpu2 won't be lost even if vcpu2 is not blocked by vcpu1's ring full?
Good point, the race would become
(quoted diff) +0
-0vCPU 1 vCPU 2 host --------------------------------------------------------------- mark page dirty write to pageQuoted 7 lines
reset rings
wait for mmu lock
add page to ring
release mmu lock
...do reset... release mmu lock
page is now dirty
Quoted 3 lines
Hmm, the page will be dirty after the reset, but is that an issue?
Or, could you help me to identify what I've missed?
Nothing: the race is always solved in such a way that there's no issue.
Quoted 7 lines
Quoted 2 lines
I don't think that's possible, most writes won't come from a page fault path and cannot retry.
Yep, maybe I should say it in the other way round: we only wait if
kvm_get_running_vcpu()== NULL. Then in somewhere nearvcpu_enter_guest(), we add a check to wait if per-vcpu ring is full. Would that work?
Yes, that should work, especially if we know that kvmgt is the only case that can wait. And since:
1) kvmgt doesn't really need dirty page tracking (because VFIO devices generally don't track dirty pages, and because kvmgt shouldn't be using kvm_write_guest anyway)
2) the real mode TSS and identity map shouldn't even be tracked, as they are invisible to userspace
it seems to me that kvm_get_running_vcpu() lets us get rid of the per-VM ring altogether.
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 10 lines · Paolo Bonzini
Quoted 5 lines
Although now because we have
kvm_get_running_vcpu()all cases for [&] should be fine without changing anything, but I tend to add another patch in the next post to convert all the [&] cases explicitly to pass vcpu pointer instead of kvm pointer to be clear if no one disagrees, then we verify that againstkvm_get_running_vcpu().This is a good idea but remember not to convert those to
kvm_vcpu_write_guest, because you _don't_ want these writes to touch SMRAM (most of the addresses are OS-controlled rather than firmware-controlled).
OK. I think I only need to pass in vcpu* instead of kvm* in kvm_write_guest_page() just like kvm_vcpu_write_guest(), however we still keep to only write to address space id==0 for that.
Quoted 7 lines
Quoted 3 lines
init_rmode_tssorinit_rmode_identity_map. But I've marked them as unimportant because they should only happen once at boot.We need to check if userspace can add an arbitrary number of entries by calling
KVM_SET_TSS_ADDRrepeatedly. I think it can; we'd have to forbid multiple calls toKVM_SET_TSS_ADDRwhich is not a problem in general.
Will do that altogether with the series. I can further change both of these calls to not track dirty at all, which shouldn't be hard, after all userspace didn't even know them, as you mentioned below.
Is there anything to explain what KVM_SET_TSS_ADDR is used for? This is the thing I found that is closest to useful (from api.txt):
This ioctl is required on Intel-based hosts. This is needed on Intel hardware because of a quirk in the virtualization implementation (see the internals documentation when it pops into existence).
So... has it really popped into existance somewhere? It would be good at least to know why it does not need to be migrated.
Quoted 15 lines
Quoted 7 lines
Quoted 2 lines
I don't think that's possible, most writes won't come from a page fault path and cannot retry.
Yep, maybe I should say it in the other way round: we only wait if
kvm_get_running_vcpu()== NULL. Then in somewhere nearvcpu_enter_guest(), we add a check to wait if per-vcpu ring is full. Would that work?Yes, that should work, especially if we know that kvmgt is the only case that can wait. And since:
1) kvmgt doesn't really need dirty page tracking (because VFIO devices generally don't track dirty pages, and because kvmgt shouldn't be using
kvm_write_guestanyway)
Quoted 5 lines
2) the real mode TSS and identity map shouldn't even be tracked, as they are invisible to userspace
it seems to me that
kvm_get_running_vcpu()lets us get rid of the per-VM ring altogether.
Yes, it would be perfect if so.
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 7 lines · Peter Xu
Quoted 6 lines · Paolo Bonzini
Quoted 5 lines
Although now because we have
kvm_get_running_vcpu()all cases for [&] should be fine without changing anything, but I tend to add another patch in the next post to convert all the [&] cases explicitly to pass vcpu pointer instead of kvm pointer to be clear if no one disagrees, then we verify that againstkvm_get_running_vcpu().
Quoted 8 lines
Quoted 4 lines
This is a good idea but remember not to convert those to
kvm_vcpu_write_guest, because you _don't_ want these writes to touch SMRAM (most of the addresses are OS-controlled rather than firmware-controlled).OK. I think I only need to pass in vcpu* instead of kvm* in
kvm_write_guest_page()just likekvm_vcpu_write_guest(), however we still keep to only write to address space id==0 for that.
No, please pass it all the way down to the [&] functions but not to kvm_write_guest_page. Those should keep using vcpu->kvm.
Quoted 7 lines
Quoted 6 lines
Quoted 2 lines
init_rmode_tssorinit_rmode_identity_map. But I've marked them as unimportant because they should only happen once at boot.We need to check if userspace can add an arbitrary number of entries by calling
KVM_SET_TSS_ADDRrepeatedly. I think it can; we'd have to forbid multiple calls toKVM_SET_TSS_ADDRwhich is not a problem in general.
Quoted 6 lines
Will do that altogether with the series. I can further change both of these calls to not track dirty at all, which shouldn't be hard, after all userspace didn't even know them, as you mentioned below.
Is there anything to explain what
KVM_SET_TSS_ADDRis used for? This is the thing I found that is closest to useful (from api.txt):
The best description is probably at https://lwn.net/Articles/658883/:
They are needed for unrestricted_guest=0. Remember that, in that case, the VM always runs in protected mode and with paging enabled. In order to emulate real mode you put the guest in a vm86 task, so you need some place for a TSS and for a page table, and they must be in guest RAM because the guest's TR and CR3 points to it. They are invisible to the guest, because the STR and MOV-from-CR instructions are invalid in vm86 mode, but it must be there.
If you don't call KVM_SET_TSS_ADDR you actually get a complaint in dmesg, and the TR stays at 0. I am not really sure what kind of bad things can happen with unrestricted_guest=0, probably you just get a VM Entry failure. The TSS takes 3 pages of memory. An interesting point is that you actually don't need to set the TR selector to a valid value (as you would do when running in "normal" vm86 mode), you can simply set the base and limit registers that are hidden in the processor, and generally inaccessible except through VMREAD/VMWRITE or system management mode. So KVM needs to set up a TSS but not a GDT.
For paging, instead, 1 page is enough because we have only 4GB of memory to address. KVM disables CR4.PAE (page address extensions, aka 8-byte entries in each page directory or page table) and enables CR4.PSE (page size extensions, aka 4MB huge pages support with 4-byte page directory entries). One page then fits 1024 4-byte page directory entries, each for a 4MB huge pages, totaling exactly 4GB. Here if you don't set it the page table is at address 0xFFFBC000. QEMU changes it to 0xFEFFC000 so that the BIOS can be up to 16MB in size (the default only allows 256k between 0xFFFC0000 and 0xFFFFFFFF).
The different handling, where only the page table has a default, is unfortunate, but so goes life...
Quoted 2 lines
So... has it really popped into existance somewhere? It would be good at least to know why it does not need to be migrated.
It does not need to be migrated just because the contents are constant.
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 13 lines · Paolo Bonzini
Quoted 12 lines · Peter Xu
Quoted 10 lines · Paolo Bonzini
Quoted 5 lines
Although now because we have
kvm_get_running_vcpu()all cases for [&] should be fine without changing anything, but I tend to add another patch in the next post to convert all the [&] cases explicitly to pass vcpu pointer instead of kvm pointer to be clear if no one disagrees, then we verify that againstkvm_get_running_vcpu().This is a good idea but remember not to convert those to
kvm_vcpu_write_guest, because you _don't_ want these writes to touch SMRAM (most of the addresses are OS-controlled rather than firmware-controlled).
Quoted 6 lines
Quoted 3 lines
OK. I think I only need to pass in vcpu* instead of kvm* in
kvm_write_guest_page()just likekvm_vcpu_write_guest(), however we still keep to only write to address space id==0 for that.No, please pass it all the way down to the [&] functions but not to
kvm_write_guest_page. Those should keep usingvcpu->kvm.
Actually I even wanted to refactor these helpers. I mean, we have two sets of helpers now, kvm_[vcpu]_{read|write}*(), so one set is per-vm, the other set is per-vcpu. IIUC the only difference of these two are whether we should consider ((vcpu)->arch.hflags & HF_SMM_MASK) or we just write to address space zero always. Could we unify them into a single set of helper (I'll just drop the *_vcpu_* helpers because it's longer when write) but we always pass in vcpu* as the first parameter? Then we add another parameter "vcpu_smm" to show whether we want to consider the HF_SMM_MASK flag.
Kvmgt is of course special here because it does not have vcpu context, but as we're going to rework that, I'd like to know whether you agree with above refactoring if without the kvmgt caller.
Quoted 45 lines
Quoted 8 lines
Quoted 7 lines
Quoted 3 lines
init_rmode_tssorinit_rmode_identity_map. But I've marked them as unimportant because they should only happen once at boot.We need to check if userspace can add an arbitrary number of entries by calling
KVM_SET_TSS_ADDRrepeatedly. I think it can; we'd have to forbid multiple calls toKVM_SET_TSS_ADDRwhich is not a problem in general.Quoted 6 lines
Will do that altogether with the series. I can further change both of these calls to not track dirty at all, which shouldn't be hard, after all userspace didn't even know them, as you mentioned below.
Is there anything to explain what
KVM_SET_TSS_ADDRis used for? This is the thing I found that is closest to useful (from api.txt):The best description is probably at https://lwn.net/Articles/658883/:
They are needed for unrestricted_guest=0. Remember that, in that case, the VM always runs in protected mode and with paging enabled. In order to emulate real mode you put the guest in a vm86 task, so you need some place for a TSS and for a page table, and they must be in guest RAM because the guest's TR and CR3 points to it. They are invisible to the guest, because the STR and MOV-from-CR instructions are invalid in vm86 mode, but it must be there.
If you don't call
KVM_SET_TSS_ADDRyou actually get a complaint in dmesg, and the TR stays at 0. I am not really sure what kind of bad things can happen with unrestricted_guest=0, probably you just get a VM Entry failure. The TSS takes 3 pages of memory. An interesting point is that you actually don't need to set the TR selector to a valid value (as you would do when running in "normal" vm86 mode), you can simply set the base and limit registers that are hidden in the processor, and generally inaccessible except through VMREAD/VMWRITE or system management mode. So KVM needs to set up a TSS but not a GDT.For paging, instead, 1 page is enough because we have only 4GB of memory to address. KVM disables CR4.PAE (page address extensions, aka 8-byte entries in each page directory or page table) and enables CR4.PSE (page size extensions, aka 4MB huge pages support with 4-byte page directory entries). One page then fits 1024 4-byte page directory entries, each for a 4MB huge pages, totaling exactly 4GB. Here if you don't set it the page table is at address
0xFFFBC000. QEMU changes it to0xFEFFC000so that the BIOS can be up to 16MB in size (the default only allows 256k between0xFFFC0000and0xFFFFFFFF).
Quoted 7 lines
The different handling, where only the page table has a default, is unfortunate, but so goes life...
Quoted 2 lines
So... has it really popped into existance somewhere? It would be good at least to know why it does not need to be migrated.
It does not need to be migrated just because the contents are constant.
OK, thanks! IIUC they should likely be all zeros then.
Do you think it's time to add most of these to kvm/api.txt? :) I can do that too if you like.
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 7 lines · Peter Xu
Quoted 2 lines
No, please pass it all the way down to the [&] functions but not to
kvm_write_guest_page. Those should keep usingvcpu->kvm.Actually I even wanted to refactor these helpers. I mean, we have two sets of helpers now, kvm_[vcpu]_{read|write}*(), so one set is per-vm, the other set is per-vcpu. IIUC the only difference of these two are whether we should consider ((vcpu)->arch.hflags &
HF_SMM_MASK) or we just write to address space zero always.
Right.
Quoted 5 lines
Could we unify them into a
single set of helper (I'll just drop the *_vcpu_* helpers because it's longer when write) but we always pass in vcpu* as the first parameter? Then we add another parameter "vcpu_smm" to show whether we want to consider theHF_SMM_MASKflag.
You'd have to check through all KVM implementations whether you always have the vCPU. Also non-x86 doesn't have address spaces, and by the time you add ", true" or ", false" it's longer than the "_vcpu_" you have removed. So, not a good idea in my opinion. :D
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 17 lines · Paolo Bonzini
Quoted 7 lines · Peter Xu
Quoted 2 lines
No, please pass it all the way down to the [&] functions but not to
kvm_write_guest_page. Those should keep usingvcpu->kvm.Actually I even wanted to refactor these helpers. I mean, we have two sets of helpers now, kvm_[vcpu]_{read|write}*(), so one set is per-vm, the other set is per-vcpu. IIUC the only difference of these two are whether we should consider ((vcpu)->arch.hflags &
HF_SMM_MASK) or we just write to address space zero always.Right.
Quoted 5 lines
Could we unify them into a
single set of helper (I'll just drop the *_vcpu_* helpers because it's longer when write) but we always pass in vcpu* as the first parameter? Then we add another parameter "vcpu_smm" to show whether we want to consider theHF_SMM_MASKflag.
Quoted 4 lines
You'd have to check through all KVM implementations whether you always have the vCPU. Also non-x86 doesn't have address spaces, and by the time you add ", true" or ", false" it's longer than the "_vcpu_" you have removed. So, not a good idea in my opinion. :D
Well, now I've changed my mind. :) (considering that we still have many places that will not have vcpu*...)
I can simply add that "vcpu_smm" parameter to kvm_vcpu_write_*() without removing the kvm_write_*() helpers. Then I'll be able to convert most of the kvm_write_*() (or its family) callers to kvm_vcpu_write*(..., vcpu_smm=false) calls where proper.
Would that be good?
Signature · 1 line
Peter Xu
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 26 lines · Peter Xu
Quoted 17 lines · Paolo Bonzini
Quoted 7 lines · Peter Xu
Quoted 2 lines
No, please pass it all the way down to the [&] functions but not to
kvm_write_guest_page. Those should keep usingvcpu->kvm.Actually I even wanted to refactor these helpers. I mean, we have two sets of helpers now, kvm_[vcpu]_{read|write}*(), so one set is per-vm, the other set is per-vcpu. IIUC the only difference of these two are whether we should consider ((vcpu)->arch.hflags &
HF_SMM_MASK) or we just write to address space zero always.Right.
Quoted 5 lines
Could we unify them into a
single set of helper (I'll just drop the *_vcpu_* helpers because it's longer when write) but we always pass in vcpu* as the first parameter? Then we add another parameter "vcpu_smm" to show whether we want to consider theHF_SMM_MASKflag.Quoted 4 lines
You'd have to check through all KVM implementations whether you always have the vCPU. Also non-x86 doesn't have address spaces, and by the time you add ", true" or ", false" it's longer than the "_vcpu_" you have removed. So, not a good idea in my opinion. :D
Well, now I've changed my mind. :) (considering that we still have many places that will not have vcpu*...)
Quoted 6 lines
I can simply add that "
vcpu_smm" parameter to kvm_vcpu_write_*() without removing the kvm_write_*() helpers. Then I'll be able to convert most of the kvm_write_*() (or its family) callers to kvm_vcpu_write*(..., vcpu_smm=false) calls where proper.Would that be good?
I've lost track of the problem you're trying to solve, but if you do something like "vcpu_smm=false", explicitly pass an address space ID instead of hardcoding x86 specific SMM crud, e.g.
kvm_vcpu_write*(..., as_id=0);
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 5 lines · Sean Christopherson
I've lost track of the problem you're trying to solve, but if you do something like "vcpu_smm=false", explicitly pass an address space ID instead of hardcoding x86 specific SMM crud, e.g.
kvm_vcpu_write*(..., as_id=0);
And the point of having kvm_vcpu_* vs. kvm_write_* was exactly to not having to hardcode the address space ID. If anything you could add a __kvm_vcpu_write_* API that takes vcpu+as_id, but really I'd prefer to keep kvm_get_running_vcpu() for now and then it can be refactored later.
There are already way too many memory r/w APIs...
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 5 lines · Paolo Bonzini
Quoted 4 lines · Sean Christopherson
I've lost track of the problem you're trying to solve, but if you do something like "vcpu_smm=false", explicitly pass an address space ID instead of hardcoding x86 specific SMM crud, e.g.
Quoted 7 lines
Quoted 1 line
kvm_vcpu_write*(..., as_id=0);And the point of having kvm_vcpu_* vs. kvm_write_* was exactly to not having to hardcode the address space ID. If anything you could add a __kvm_vcpu_write_* API that takes vcpu+as_id, but really I'd prefer to keep
kvm_get_running_vcpu()for now and then it can be refactored later.
There are already way too many memory r/w APIs...
Yeah actuall that's why I wanted to start working on that just in case it could help to unify all of them some day (and since we did go a few steps forward on that when discussing the dirty ring). But yeah kvm_get_running_vcpu() for sure works for us already; let's go the easy way this time. Thanks,
Signature · 1 line
Peter Xu
RE: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
In reply to Paolo BonziniQuoted 40 lines
From: Paolo Bonzini
Sent: Monday, December 16, 2019 6:08 PM[Alex and Kevin: there are doubts below regarding dirty page tracking from VFIO and mdev devices, which perhaps you can help with]
Quoted 5 lines · Peter Xu
init_rmode_tss vmx_set_tss_addr kvm_vm_ioctl_set_tss_addr [*] init_rmode_identity_map vmx_create_vcpu [*]These don't matter because their content is not visible to userspace (the backing storage is mmap-ed by
__x86_set_memory_region). In fact, dQuoted 9 lines
vmx_write_pml_buffer kvm_arch_write_log_dirty [&] kvm_write_guest kvm_hv_setup_tsc_page kvm_guest_time_update [&] nested_flush_cached_shadow_vmcs12 [&] kvm_write_wall_clock [&] kvm_pv_clock_pairing [&] kvmgt_rw_gpa [?]This then expands (partially) to
intel_gvt_hypervisor_write_gpa
emulate_csb_update
emulate_execlist_ctx_schedule_out
complete_execlist_workload
complete_current_workload
workload_thread
emulate_execlist_ctx_schedule_in
prepare_execlist_workload
prepare_workload
dispatch_workload
workload_thread
Quoted 4 lines
So KVMGT is always writing to GPAs instead of IOVAs and basically bypassing a guest IOMMU. So here it would be better if kvmgt was changed not use
kvm_write_guest(also because I'd probably have nacked that if I had known :)).
I agree.
Quoted 11 lines
As far as I know, there is some work on live migration with both VFIO and mdev, and that probably includes some dirty page tracking API. kvmgt could switch to that API, or there could be VFIO APIs similar to
kvm_write_guestbut taking IOVAs instead of GPAs. Advantage: this would fix the GPA/IOVA confusion. Disadvantage: userspace would lose the tracking of writes from mdev devices. Kevin, are these writes used in any way? Do the calls tointel_gvt_hypervisor_write_gpacovers all writes from kvmgt vGPUs, or can the hardware write to memory as well (which would be my guess if I didn't know anything about kvmgt, which I pretty much don't)?
intel_gvt_hypervisor_write_gpa covers all writes due to software mediation.
for hardware updates, it needs be mapped in IOMMU through vfio_pin_pages before any DMA happens. The ongoing dirty tracking effort in VFIO will take every pinned page through that API as dirtied.
However, currently VFIO doesn't implement any vfio_read/write_guest interface yet. and it doesn't make sense to use vfio_pin_pages for software dirtied pages, as pin is unnecessary and heavy involving iommu invalidation.
Alex, if you are OK we'll work on such interface and move kvmgt to use it. After it's accepted, we can also mark pages dirty through this new interface in Kirti's dirty page tracking series.
Thanks
Kevin
Quoted 82 lines
Quoted 13 lines
We should only need to look at the leaves of the traces because they're where the dirty request starts. I'm marking all the leaves with below criteria then it'll be easier to focus:
Cases with [*]: should not matter much
[&]: actually with a per-vcpu context in the upper layer [?]: uncertain...I'm a bit amazed after I took these notes, since I found that besides those that could probbaly be ignored (marked as [*]), most of the rest per-vm dirty requests are actually with a vcpu context.
Quoted 5 lines
Although now because we have
kvm_get_running_vcpu()all cases for [&] should be fine without changing anything, but I tend to add another patch in the next post to convert all the [&] cases explicitly to pass vcpu pointer instead of kvm pointer to be clear if no one disagrees, then we verify that againstkvm_get_running_vcpu().This is a good idea but remember not to convert those to
kvm_vcpu_write_guest, because you _don't_ want these writes to touch SMRAM (most of the addresses are OS-controlled rather than firmware-controlled).Quoted 2 lines
init_rmode_tssorinit_rmode_identity_map. But I've marked them as unimportant because they should only happen once at boot.We need to check if userspace can add an arbitrary number of entries by calling
KVM_SET_TSS_ADDRrepeatedly. I think it can; we'd have to forbid multiple calls toKVM_SET_TSS_ADDRwhich is not a problem in general.Quoted 22 lines
Quoted 14 lines
Quoted 7 lines
If we're still with the rule in userspace that we first do RESET then collect and send the pages (just like what we've discussed before), then IMHO it's fine to have vcpu2 to skip the slow path? Because RESET happens at "treat page as not dirty", then if we are sure that we only collect and send pages after that point, then the latest "write to page" data from vcpu2 won't be lost even if vcpu2 is not blocked by vcpu1's ring full?
Good point, the race would become
(quoted diff) +0
-0vCPU 1 vCPU 2 host --------------------------------------------------------------- mark page dirty write to pageQuoted 7 lines
reset rings
wait for mmu lock
add page to ring
release mmu lock
...do reset... release mmu lock
page is now dirtyQuoted 3 lines
Hmm, the page will be dirty after the reset, but is that an issue?
Or, could you help me to identify what I've missed?
Nothing: the race is always solved in such a way that there's no issue.
Quoted 7 lines
Quoted 2 lines
I don't think that's possible, most writes won't come from a page fault path and cannot retry.
Yep, maybe I should say it in the other way round: we only wait if
kvm_get_running_vcpu()== NULL. Then in somewhere nearvcpu_enter_guest(), we add a check to wait if per-vcpu ring is full. Would that work?Yes, that should work, especially if we know that kvmgt is the only case that can wait. And since:
1) kvmgt doesn't really need dirty page tracking (because VFIO devices generally don't track dirty pages, and because kvmgt shouldn't be using
kvm_write_guestanyway)2) the real mode TSS and identity map shouldn't even be tracked, as they are invisible to userspace
it seems to me that
kvm_get_running_vcpu()lets us get rid of the per-VM ring altogether.Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 65 lines · Kevin
Quoted 40 lines
From: Paolo Bonzini
Sent: Monday, December 16, 2019 6:08 PM[Alex and Kevin: there are doubts below regarding dirty page tracking from VFIO and mdev devices, which perhaps you can help with]
Quoted 5 lines · Peter Xu
init_rmode_tss vmx_set_tss_addr kvm_vm_ioctl_set_tss_addr [*] init_rmode_identity_map vmx_create_vcpu [*]These don't matter because their content is not visible to userspace (the backing storage is mmap-ed by
__x86_set_memory_region). In fact, dQuoted 9 lines
vmx_write_pml_buffer kvm_arch_write_log_dirty [&] kvm_write_guest kvm_hv_setup_tsc_page kvm_guest_time_update [&] nested_flush_cached_shadow_vmcs12 [&] kvm_write_wall_clock [&] kvm_pv_clock_pairing [&] kvmgt_rw_gpa [?]This then expands (partially) to
intel_gvt_hypervisor_write_gpa
emulate_csb_update
emulate_execlist_ctx_schedule_out
complete_execlist_workload
complete_current_workload
workload_thread
emulate_execlist_ctx_schedule_in
prepare_execlist_workload
prepare_workload
dispatch_workload
workload_threadQuoted 4 lines
So KVMGT is always writing to GPAs instead of IOVAs and basically bypassing a guest IOMMU. So here it would be better if kvmgt was changed not use
kvm_write_guest(also because I'd probably have nacked that if I had known :)).I agree.
Quoted 11 lines
As far as I know, there is some work on live migration with both VFIO and mdev, and that probably includes some dirty page tracking API. kvmgt could switch to that API, or there could be VFIO APIs similar to
kvm_write_guestbut taking IOVAs instead of GPAs. Advantage: this would fix the GPA/IOVA confusion. Disadvantage: userspace would lose the tracking of writes from mdev devices. Kevin, are these writes used in any way? Do the calls tointel_gvt_hypervisor_write_gpacovers all writes from kvmgt vGPUs, or can the hardware write to memory as well (which would be my guess if I didn't know anything about kvmgt, which I pretty much don't)?
intel_gvt_hypervisor_write_gpacovers all writes due to software mediation.for hardware updates, it needs be mapped in IOMMU through
vfio_pin_pagesbefore any DMA happens. The ongoing dirty tracking effort in VFIO will take every pinned page through that API as dirtied.
Quoted 7 lines
However, currently VFIO doesn't implement any vfio_read/write_guest interface yet. and it doesn't make sense to use
vfio_pin_pagesfor software dirtied pages, as pin is unnecessary and heavy involving iommu invalidation.Alex, if you are OK we'll work on such interface and move kvmgt to use it. After it's accepted, we can also mark pages dirty through this new interface in Kirti's dirty page tracking series.
I'm not sure what you're asking for, is it an interface for the host CPU to read/write the memory backing of a mapped IOVA range without pinning pages? That seems like something like that would make sense for an emulation model where a page does not need to be pinned for physical DMA. If you're asking more for an interface that understands the userspace driver is a VM (ie. implied using a _guest postfix on the function name) and knows about GPA mappings beyond the windows directly mapped for device access, I'd not look fondly on such a request. Thanks,
Alex
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 4 lines · Alex Williamson
Quoted 4 lines
Alex, if you are OK we'll work on such interface and move kvmgt to use it. After it's accepted, we can also mark pages dirty through this new interface in Kirti's dirty page tracking series.
Quoted 8 lines
I'm not sure what you're asking for, is it an interface for the host CPU to read/write the memory backing of a mapped IOVA range without pinning pages? That seems like something like that would make sense for an emulation model where a page does not need to be pinned for physical DMA. If you're asking more for an interface that understands the userspace driver is a VM (ie. implied using a
_guestpostfix on the function name) and knows about GPA mappings beyond the windows directly mapped for device access, I'd not look fondly on such a request.
No, it would definitely be the former, using IOVAs to access guest memory---kvmgt is currently doing the latter by calling into KVM, and I'm not really fond of that either.
Paolo
RE: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 16 lines
From: Paolo Bonzini <[email protected]>
Sent: Wednesday, December 18, 2019 12:31 AMQuoted 3 lines · Alex Williamson
Quoted 3 lines
Alex, if you are OK we'll work on such interface and move kvmgt to use it. After it's accepted, we can also mark pages dirty through this new
interface
Quoted 8 lines
Quoted 1 line
in Kirti's dirty page tracking series.
I'm not sure what you're asking for, is it an interface for the host CPU to read/write the memory backing of a mapped IOVA range without pinning pages? That seems like something like that would make sense for an emulation model where a page does not need to be pinned for physical DMA. If you're asking more for an interface that understands the userspace driver is a VM (ie. implied using a
_guestpostfix on the function name) and knows about GPA mappings beyond the windows
Quoted 7 lines
directly
Quoted 1 line
mapped for device access, I'd not look fondly on such a request.
No, it would definitely be the former, using IOVAs to access guest memory---kvmgt is currently doing the latter by calling into KVM, and I'm not really fond of that either.
Exactly. let's work on the fix.
Thanks
Kevin
A message that is not in the archive (sent off-list or not yet received): AADFC41AFE54684AB9EE6CBC0274A5D19D645E5F@SHSMSX104.ccr.corp.intel.com
RE: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 64 lines
From: Tian, Kevin
Sent: Tuesday, December 17, 2019 10:29 AMQuoted 40 lines
From: Paolo Bonzini
Sent: Monday, December 16, 2019 6:08 PM[Alex and Kevin: there are doubts below regarding dirty page tracking from VFIO and mdev devices, which perhaps you can help with]
Quoted 5 lines · Peter Xu
init_rmode_tss vmx_set_tss_addr kvm_vm_ioctl_set_tss_addr [*] init_rmode_identity_map vmx_create_vcpu [*]These don't matter because their content is not visible to userspace (the backing storage is mmap-ed by
__x86_set_memory_region). In fact, dQuoted 9 lines
vmx_write_pml_buffer kvm_arch_write_log_dirty [&] kvm_write_guest kvm_hv_setup_tsc_page kvm_guest_time_update [&] nested_flush_cached_shadow_vmcs12 [&] kvm_write_wall_clock [&] kvm_pv_clock_pairing [&] kvmgt_rw_gpa [?]This then expands (partially) to
intel_gvt_hypervisor_write_gpa
emulate_csb_update
emulate_execlist_ctx_schedule_out
complete_execlist_workload
complete_current_workload
workload_thread
emulate_execlist_ctx_schedule_in
prepare_execlist_workload
prepare_workload
dispatch_workload
workload_threadQuoted 4 lines
So KVMGT is always writing to GPAs instead of IOVAs and basically bypassing a guest IOMMU. So here it would be better if kvmgt was changed not use
kvm_write_guest(also because I'd probably have nacked that if I had known :)).I agree.
Quoted 11 lines
As far as I know, there is some work on live migration with both VFIO and mdev, and that probably includes some dirty page tracking API. kvmgt could switch to that API, or there could be VFIO APIs similar to
kvm_write_guestbut taking IOVAs instead of GPAs. Advantage: this would fix the GPA/IOVA confusion. Disadvantage: userspace would lose the tracking of writes from mdev devices. Kevin, are these writes used in any way? Do the calls tointel_gvt_hypervisor_write_gpacovers all writes from kvmgt vGPUs, or can the hardware write to memory as well (which would be my guess if I didn't know anything about kvmgt, which I pretty much don't)?
intel_gvt_hypervisor_write_gpacovers all writes due to software mediation.
Quoted 8 lines
for hardware updates, it needs be mapped in IOMMU through
vfio_pin_pages
before any DMA happens. The ongoing dirty tracking effort in VFIO will take every pinned page through that API as dirtied.However, currently VFIO doesn't implement any vfio_read/write_guest interface yet. and it doesn't make sense to use
vfio_pin_pagesfor software dirtied pages, as pin is unnecessary and heavy involving iommu invalidation.
One correction. vfio_pin_pages doesn't involve iommu invalidation. I should just mean that pinning the page is not necessary. We just need a kvm-like interface based on hva to access.
Quoted 5 lines
Alex, if you are OK we'll work on such interface and move kvmgt to use it. After it's accepted, we can also mark pages dirty through this new interface in Kirti's dirty page tracking series.
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 69 lines · Kevin
Quoted 69 lines
From: Tian, Kevin
Sent: Tuesday, December 17, 2019 10:29 AMQuoted 40 lines
From: Paolo Bonzini
Sent: Monday, December 16, 2019 6:08 PM[Alex and Kevin: there are doubts below regarding dirty page tracking from VFIO and mdev devices, which perhaps you can help with]
Quoted 5 lines · Peter Xu
init_rmode_tss vmx_set_tss_addr kvm_vm_ioctl_set_tss_addr [*] init_rmode_identity_map vmx_create_vcpu [*]These don't matter because their content is not visible to userspace (the backing storage is mmap-ed by
__x86_set_memory_region). In fact, dQuoted 9 lines
vmx_write_pml_buffer kvm_arch_write_log_dirty [&] kvm_write_guest kvm_hv_setup_tsc_page kvm_guest_time_update [&] nested_flush_cached_shadow_vmcs12 [&] kvm_write_wall_clock [&] kvm_pv_clock_pairing [&] kvmgt_rw_gpa [?]This then expands (partially) to
intel_gvt_hypervisor_write_gpa
emulate_csb_update
emulate_execlist_ctx_schedule_out
complete_execlist_workload
complete_current_workload
workload_thread
emulate_execlist_ctx_schedule_in
prepare_execlist_workload
prepare_workload
dispatch_workload
workload_threadQuoted 4 lines
So KVMGT is always writing to GPAs instead of IOVAs and basically bypassing a guest IOMMU. So here it would be better if kvmgt was changed not use
kvm_write_guest(also because I'd probably have nacked that if I had known :)).I agree.
Quoted 11 lines
As far as I know, there is some work on live migration with both VFIO and mdev, and that probably includes some dirty page tracking API. kvmgt could switch to that API, or there could be VFIO APIs similar to
kvm_write_guestbut taking IOVAs instead of GPAs. Advantage: this would fix the GPA/IOVA confusion. Disadvantage: userspace would lose the tracking of writes from mdev devices. Kevin, are these writes used in any way? Do the calls tointel_gvt_hypervisor_write_gpacovers all writes from kvmgt vGPUs, or can the hardware write to memory as well (which would be my guess if I didn't know anything about kvmgt, which I pretty much don't)?
intel_gvt_hypervisor_write_gpacovers all writes due to software mediation.for hardware updates, it needs be mapped in IOMMU through
vfio_pin_pages
before any DMA happens. The ongoing dirty tracking effort in VFIO will take every pinned page through that API as dirtied.
Quoted 8 lines
Quoted 3 lines
However, currently VFIO doesn't implement any vfio_read/write_guest interface yet. and it doesn't make sense to use
vfio_pin_pagesfor software dirtied pages, as pin is unnecessary and heavy involving iommu invalidation.One correction.
vfio_pin_pagesdoesn't involve iommu invalidation. I should just mean that pinning the page is not necessary. We just need a kvm-like interface based on hva to access.
And can we propose to differentiate read and write when calling vfio_pin_pages, e.g. vfio_pin_pages_read, vfio_pin_pages_write? Otherwise, calling to vfio_pin_pages will unnecessarily cause read pages to be dirty and sometimes reading guest pages is a way for device model to track dirty pages.
Quoted 5 lines
Quoted 5 lines
Alex, if you are OK we'll work on such interface and move kvmgt to use it. After it's accepted, we can also mark pages dirty through this new interface in Kirti's dirty page tracking series.
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 78 lines · On Tue, 17 Dec 2019 00:25:02 -0500 Yan Zhao <[email protected]> wrote:
Quoted 69 lines · Kevin
Quoted 69 lines
From: Tian, Kevin
Sent: Tuesday, December 17, 2019 10:29 AMQuoted 40 lines
From: Paolo Bonzini
Sent: Monday, December 16, 2019 6:08 PM[Alex and Kevin: there are doubts below regarding dirty page tracking from VFIO and mdev devices, which perhaps you can help with]
Quoted 5 lines · Peter Xu
init_rmode_tss vmx_set_tss_addr kvm_vm_ioctl_set_tss_addr [*] init_rmode_identity_map vmx_create_vcpu [*]These don't matter because their content is not visible to userspace (the backing storage is mmap-ed by
__x86_set_memory_region). In fact, dQuoted 9 lines
vmx_write_pml_buffer kvm_arch_write_log_dirty [&] kvm_write_guest kvm_hv_setup_tsc_page kvm_guest_time_update [&] nested_flush_cached_shadow_vmcs12 [&] kvm_write_wall_clock [&] kvm_pv_clock_pairing [&] kvmgt_rw_gpa [?]This then expands (partially) to
intel_gvt_hypervisor_write_gpa
emulate_csb_update
emulate_execlist_ctx_schedule_out
complete_execlist_workload
complete_current_workload
workload_thread
emulate_execlist_ctx_schedule_in
prepare_execlist_workload
prepare_workload
dispatch_workload
workload_threadQuoted 4 lines
So KVMGT is always writing to GPAs instead of IOVAs and basically bypassing a guest IOMMU. So here it would be better if kvmgt was changed not use
kvm_write_guest(also because I'd probably have nacked that if I had known :)).I agree.
Quoted 11 lines
As far as I know, there is some work on live migration with both VFIO and mdev, and that probably includes some dirty page tracking API. kvmgt could switch to that API, or there could be VFIO APIs similar to
kvm_write_guestbut taking IOVAs instead of GPAs. Advantage: this would fix the GPA/IOVA confusion. Disadvantage: userspace would lose the tracking of writes from mdev devices. Kevin, are these writes used in any way? Do the calls tointel_gvt_hypervisor_write_gpacovers all writes from kvmgt vGPUs, or can the hardware write to memory as well (which would be my guess if I didn't know anything about kvmgt, which I pretty much don't)?
intel_gvt_hypervisor_write_gpacovers all writes due to software mediation.for hardware updates, it needs be mapped in IOMMU through
vfio_pin_pages
before any DMA happens. The ongoing dirty tracking effort in VFIO will take every pinned page through that API as dirtied.Quoted 8 lines
Quoted 3 lines
However, currently VFIO doesn't implement any vfio_read/write_guest interface yet. and it doesn't make sense to use
vfio_pin_pagesfor software dirtied pages, as pin is unnecessary and heavy involving iommu invalidation.One correction.
vfio_pin_pagesdoesn't involve iommu invalidation. I should just mean that pinning the page is not necessary. We just need a kvm-like interface based on hva to access.
Quoted 5 lines
And can we propose to differentiate read and write when calling
vfio_pin_pages, e.g.vfio_pin_pages_read,vfio_pin_pages_write? Otherwise, calling tovfio_pin_pageswill unnecessarily cause read pages to be dirty and sometimes reading guest pages is a way for device model to track dirty pages.
Yes, I've discussed this with Kirti, when devices add more fine grained dirty tracking we'll probably need to extend the mdev pinned pages interface to allow vendor drivers to indicate a pinning is intended to be used as read-only and perhaps also a way to unpin a page that was pinned as read-write as clean, if the device did not write to it. So perhaps vfio_pin_pages_for_read() and vfio_unpin_pages_clean(). Thanks,
Alex
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
In reply to Peter Xuvirt/kvm/kvm_main.c static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) · line 3327
Quoted 6 lines · Peter Xu
virt/kvm/kvm_main.c +6
-0+static void mark_page_dirty_in_ring (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *slot, +gfn_t gfn) +{ +u32 as_id =0 ;
Redundant initialization of as_id.
Quoted 5 lines
virt/kvm/kvm_main.c +5
-0+u64 offset; +int ret; +struct kvm_dirty_ring *ring; +struct kvm_dirty_ring_indexes *indexes; +bool is_vm_ring;
virt/kvm/kvm_main.c static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) · line 3342
Quoted 10 lines
virt/kvm/kvm_main.c +10
-0+ +if (!kvm->dirty_ring_size) +return ; + + offset = gfn - slot->base_gfn; + +if (vcpu) { + as_id =kvm_arch_vcpu_memslots_id (vcpu); + }else { + as_id =0 ;
The setting of as_id is wrong, both with and without a vCPU. as_id should come from slot->as_id. It may not be actually broken in the current code base, but at best it's fragile, e.g. Ben's TDP MMU rewrite[*] adds a call to mark_page_dirty_in_slot() with a potentially non-zero as_id.
virt/kvm/kvm_main.c static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) · line 3355
Quoted 13 lines
virt/kvm/kvm_main.c +13
-0+ vcpu =kvm_get_running_vcpu (); + } + +if (vcpu) { + ring = &vcpu->dirty_ring; + indexes = &vcpu->run->vcpu_ring_indexes; + is_vm_ring = false; + }else { +/* +* Put onto per vm ring because no vcpu context. Kick +* vcpu0 if ring is full. +*/ + vcpu = kvm->vcpus[0 ];
Is this a rare event?
Quoted 20 lines
virt/kvm/kvm_main.c +20
-0+ ring = &kvm->vm_dirty_ring; + indexes = &kvm->vm_run->vm_ring_indexes; + is_vm_ring = true; + } + + ret =kvm_dirty_ring_push (ring, indexes, + (as_id <<16 )|slot->id, offset, + is_vm_ring); +if (ret <0 ) { +if (is_vm_ring) +pr_warn_once ("vcpu %d dirty log overflow\n" , + vcpu->vcpu_id); +else +pr_warn_once ("per-vm dirty log overflow\n" ); +return ; + } + +if (ret) +kvm_make_request (KVM_REQ_DIRTY_RING_FULL , vcpu); +}
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 2 lines · Sean Christopherson
The setting of
as_idis wrong, both with and without a vCPU.as_idshould come fromslot->as_id.
Which doesn't exist, but is an excellent suggestion nevertheless.
Quoted 7 lines
virt/kvm/kvm_main.c static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) · line 3355
Quoted 5 lines
virt/kvm/kvm_main.c +5
-0+/* +* Put onto per vm ring because no vcpu context. Kick +* vcpu0 if ring is full. +*/ + vcpu = kvm->vcpus[0 ];Is this a rare event?
Yes, every time a vCPU exit happens, the vCPU is supposed to reap the VM ring as well. (Most of the time it will be empty, and while the reaping of VM ring entries needs locking, the emptiness check doesn't).
Paolo
Quoted 21 lines
Quoted 20 lines
virt/kvm/kvm_main.c +20
-0+ ring = &kvm->vm_dirty_ring; + indexes = &kvm->vm_run->vm_ring_indexes; + is_vm_ring = true; + } + + ret =kvm_dirty_ring_push (ring, indexes, + (as_id <<16 )|slot->id, offset, + is_vm_ring); +if (ret <0 ) { +if (is_vm_ring) +pr_warn_once ("vcpu %d dirty log overflow\n" , + vcpu->vcpu_id); +else +pr_warn_once ("per-vm dirty log overflow\n" ); +return ; + } + +if (ret) +kvm_make_request (KVM_REQ_DIRTY_RING_FULL , vcpu); +}
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 5 lines · Paolo Bonzini
Quoted 2 lines · Sean Christopherson
The setting of
as_idis wrong, both with and without a vCPU.as_idshould come fromslot->as_id.Which doesn't exist, but is an excellent suggestion nevertheless.
Huh, I explicitly looked at the code to make sure as_id existed before making this suggestion. No idea what code I actually pulled up.
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
In reply to Peter XuQuoted 8 lines · Peter Xu
virt/kvm/dirty_ring.c new file +8
-0+int kvm_dirty_ring_push (struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes, +u32 slot,u64 offset,bool lock) +{ +int ret; +struct kvm_dirty_gfn *entry; + +if (lock)
virt/kvm/dirty_ring.c line 130
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+spin_lock (&ring->lock); + +if (kvm_dirty_ring_full (ring)) { + ret = -EBUSY ; + goto out; + } + + entry = &ring->dirty_gfns[ring->dirty_index & (ring->size -1 )]; + entry->slot = slot; + entry->offset = offset;
Haven't gone through the whole series, sorry if it was a silly question but I wonder things like this will suffer from similar issue on virtually tagged archs as mentioned in [1].
Is this better to allocate the ring from userspace and set to KVM instead? Then we can use copy_to/from_user() friends (a little bit slow on recent CPUs).
[1] https://lkml.org/lkml/2019/4/9/5
Thanks
Quoted 8 lines
virt/kvm/dirty_ring.c new file +8
-0+smp_wmb (); + ring->dirty_index++; +WRITE_ONCE (indexes->avail_index, ring->dirty_index); + ret =kvm_dirty_ring_used (ring) >= ring->soft_limit; +pr_info ("%s: slot %u offset %llu used %u\n" , + __func__, slot, offset,kvm_dirty_ring_used (ring)); + +out:
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 9 lines · Jason Wang
Quoted 4 lines
+ entry = &ring->dirty_gfns[ring->dirty_index & (
ring->size- 1)]; +entry->slot= slot;
+entry->offset= offset;Haven't gone through the whole series, sorry if it was a silly question but I wonder things like this will suffer from similar issue on virtually tagged archs as mentioned in [1].
There is no new infrastructure to track the dirty pages---it's just a different way to pass them to userspace.
Quoted 3 lines
Is this better to allocate the ring from userspace and set to KVM instead? Then we can use copy_to/from_user() friends (a little bit slow on recent CPUs).
Yeah, I don't think that would be better than mmap.
Paolo
Quoted 1 line
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 11 lines · Paolo Bonzini
Quoted 9 lines · Jason Wang
Quoted 4 lines
+ entry = &ring->dirty_gfns[ring->dirty_index & (
ring->size- 1)]; +entry->slot= slot;
+entry->offset= offset;Haven't gone through the whole series, sorry if it was a silly question but I wonder things like this will suffer from similar issue on virtually tagged archs as mentioned in [1].
Quoted 8 lines
There is no new infrastructure to track the dirty pages---it's just a different way to pass them to userspace.
Quoted 3 lines
Is this better to allocate the ring from userspace and set to KVM instead? Then we can use copy_to/from_user() friends (a little bit slow on recent CPUs).
Yeah, I don't think that would be better than mmap.
Yeah I agree, because I didn't see how copy_to/from_user() helped to do icache/dcache flushings...
Some context here: Jason raised this question offlist first on whether we should also need these flush_dcache_cache() helpers for operations like kvm dirty ring accesses. I feel like it should, however I've got two other questions, on:
- if we need to do flush_dcache_page() on kernel modified pages (assuming the same page has mapped to userspace), then why don't we need flush_cache_page() too on the page, where flush_cache_page() is defined not-a-nop on those archs?
- assuming an arch has not-a-nop impl for flush_[d]cache_page(), would atomic operations like cmpxchg really work for them (assuming that ISAs like cmpxchg should depend on cache consistency).
Sorry I think these are for sure a bit out of topic for kvm dirty ring patchset, but since we're at it, I'm raising the questions up in case there're answers..
Thanks,
Signature · 1 line
Peter Xu
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 12 lines · Peter Xu
Quoted 11 lines · Paolo Bonzini
Quoted 7 lines · Jason Wang
Quoted 3 lines
+ entry = &ring->dirty_gfns[ring->dirty_index & (
ring->size- 1)]; +entry->slot= slot;
+entry->offset= offset;Haven't gone through the whole series, sorry if it was a silly question but I wonder things like this will suffer from similar issue on virtually tagged archs as mentioned in [1].
There is no new infrastructure to track the dirty pages---it's just a different way to pass them to userspace.
Quoted 6 lines
Quoted 4 lines
Quoted 3 lines
Is this better to allocate the ring from userspace and set to KVM instead? Then we can use copy_to/from_user() friends (a little bit slow on recent CPUs).
Yeah, I don't think that would be better than mmap.
Yeah I agree, because I didn't see how copy_to/from_user() helped to do icache/dcache flushings...
It looks to me one advantage is that exact the same VA is used by both userspace and kernel so there will be no alias.
Thanks
Quoted 22 lines
Some context here: Jason raised this question offlist first on whether we should also need these
flush_dcache_cache()helpers for operations like kvm dirty ring accesses. I feel like it should, however I've got two other questions, on:- if we need to do
flush_dcache_page()on kernel modified pages (assuming the same page has mapped to userspace), then why don't we needflush_cache_page()too on the page, whereflush_cache_page()is defined not-a-nop on those archs?- assuming an arch has not-a-nop impl for flush_[d]cache_page(), would atomic operations like cmpxchg really work for them (assuming that ISAs like cmpxchg should depend on cache consistency).
Sorry I think these are for sure a bit out of topic for kvm dirty ring patchset, but since we're at it, I'm raising the questions up in case there're answers..
Thanks,
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 17 lines · Jason Wang
Quoted 15 lines · Peter Xu
Quoted 14 lines · Paolo Bonzini
Quoted 7 lines · Jason Wang
Quoted 3 lines
+ entry = &ring->dirty_gfns[ring->dirty_index & (
ring->size- 1)]; +entry->slot= slot;
+entry->offset= offset;Haven't gone through the whole series, sorry if it was a silly question but I wonder things like this will suffer from similar issue on virtually tagged archs as mentioned in [1].
There is no new infrastructure to track the dirty pages---it's just a different way to pass them to userspace.
Quoted 3 lines
Is this better to allocate the ring from userspace and set to KVM instead? Then we can use copy_to/from_user() friends (a little bit slow on recent CPUs).
Quoted 7 lines
Quoted 3 lines
Quoted 1 line
Yeah, I don't think that would be better than mmap.
Yeah I agree, because I didn't see how copy_to/from_user() helped to do icache/dcache flushings...
It looks to me one advantage is that exact the same VA is used by both userspace and kernel so there will be no alias.
Hmm.. but what if the page is mapped more than once in user? Thanks,
Signature · 1 line
Peter Xu
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 16 lines · Peter Xu
Quoted 15 lines · Jason Wang
Quoted 14 lines · Peter Xu
Quoted 13 lines · Paolo Bonzini
Quoted 6 lines · Jason Wang
Quoted 3 lines
+ entry = &ring->dirty_gfns[ring->dirty_index & (
ring->size- 1)]; +entry->slot= slot;
+entry->offset= offset;Haven't gone through the whole series, sorry if it was a silly question but I wonder things like this will suffer from similar issue on virtually tagged archs as mentioned in [1].
There is no new infrastructure to track the dirty pages---it's just a different way to pass them to userspace.
Quoted 3 lines
Is this better to allocate the ring from userspace and set to KVM instead? Then we can use copy_to/from_user() friends (a little bit slow on recent CPUs).
Quoted 7 lines
Quoted 6 lines
Quoted 3 lines
Quoted 1 line
Yeah, I don't think that would be better than mmap.
Yeah I agree, because I didn't see how copy_to/from_user() helped to do icache/dcache flushings...
It looks to me one advantage is that exact the same VA is used by both userspace and kernel so there will be no alias.
Hmm.. but what if the page is mapped more than once in user? Thanks,
Then it's the responsibility of userspace program to do the flush I think.
Thanks
Quoted 1 line
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
In reply to Paolo BonziniQuoted 7 lines · Paolo Bonzini
Quoted 6 lines · Jason Wang
Quoted 4 lines
+ entry = &ring->dirty_gfns[ring->dirty_index & (
ring->size- 1)]; +entry->slot= slot;
+entry->offset= offset;
Quoted 6 lines
Quoted 3 lines
Haven't gone through the whole series, sorry if it was a silly question but I wonder things like this will suffer from similar issue on virtually tagged archs as mentioned in [1].
There is no new infrastructure to track the dirty pages---it's just a different way to pass them to userspace.
Did you guys consider using one of the virtio ring formats? Maybe reusing vhost code?
If you did and it's not a good fit, this is something good to mention in the commit log.
I also wonder about performance numbers - any data here?
Quoted 10 lines
Quoted 3 lines
Is this better to allocate the ring from userspace and set to KVM instead? Then we can use copy_to/from_user() friends (a little bit slow on recent CPUs).
Yeah, I don't think that would be better than mmap.
Paolo
Quoted 1 line
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 4 lines · Michael S. Tsirkin
Quoted 2 lines
There is no new infrastructure to track the dirty pages---it's just a different way to pass them to userspace.
Did you guys consider using one of the virtio ring formats? Maybe reusing vhost code?
There are no used/available entries here, it's unidirectional (kernel->user).
Quoted 4 lines
If you did and it's not a good fit, this is something good to mention in the commit log.
I also wonder about performance numbers - any data here?
Yes some numbers would be useful. Note however that the improvement is asymptotical, O(#dirtied pages) vs O(#total pages) so it may differ depending on the workload.
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 8 lines · Paolo Bonzini
Quoted 4 lines · Michael S. Tsirkin
Quoted 2 lines
There is no new infrastructure to track the dirty pages---it's just a different way to pass them to userspace.
Did you guys consider using one of the virtio ring formats? Maybe reusing vhost code?
There are no used/available entries here, it's unidirectional (
kernel->user).
Agreed. Vring could be an overkill IMHO (the whole dirty_ring.c is 100+ LOC only).
Quoted 9 lines
Quoted 5 lines
If you did and it's not a good fit, this is something good to mention in the commit log.
I also wonder about performance numbers - any data here?
Yes some numbers would be useful. Note however that the improvement is asymptotical, O(#dirtied pages) vs O(#total pages) so it may differ depending on the workload.
Yes. I plan to give some numbers when start to work on the QEMU series (after this lands). However as Paolo said, those numbers would probably only be with some special case where I know the dirty ring could win. Frankly speaking I don't even know whether we should change the default logging mode when the QEMU work is done - I feel like the old logging interface is still good in many major cases (small vms, or high dirty rates). It could be that we just offer another option when the user could consider to solve specific problems.
Thanks,
Signature · 1 line
Peter Xu
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 4 lines · Peter Xu
Quoted 3 lines · Paolo Bonzini
Quoted 2 lines · Michael S. Tsirkin
Quoted 2 lines
There is no new infrastructure to track the dirty pages---it's just a different way to pass them to userspace.
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
Did you guys consider using one of the virtio ring formats? Maybe reusing vhost code?
There are no used/available entries here, it's unidirectional (
kernel->user).Agreed. Vring could be an overkill IMHO (the whole
dirty_ring.cis 100+ LOC only).
I guess you don't do polling/ event suppression and other tricks that virtio came up with for speed then? Why won't they be helpful for kvm? To put it another way, LOC is irrelevant, virtio is already in the kernel.
Anyway, this is something to be discussed in the cover letter.
Quoted 23 lines
Quoted 9 lines
Quoted 5 lines
If you did and it's not a good fit, this is something good to mention in the commit log.
I also wonder about performance numbers - any data here?
Yes some numbers would be useful. Note however that the improvement is asymptotical, O(#dirtied pages) vs O(#total pages) so it may differ depending on the workload.
Yes. I plan to give some numbers when start to work on the QEMU series (after this lands). However as Paolo said, those numbers would probably only be with some special case where I know the dirty ring could win. Frankly speaking I don't even know whether we should change the default logging mode when the QEMU work is done - I feel like the old logging interface is still good in many major cases (small vms, or high dirty rates). It could be that we just offer another option when the user could consider to solve specific problems.
Thanks,
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 8 lines · Michael S. Tsirkin
Quoted 7 lines · Peter Xu
Quoted 6 lines · Paolo Bonzini
Quoted 4 lines · Michael S. Tsirkin
Quoted 2 lines
There is no new infrastructure to track the dirty pages---it's just a different way to pass them to userspace.
Did you guys consider using one of the virtio ring formats? Maybe reusing vhost code?
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
There are no used/available entries here, it's unidirectional (
kernel->user).Agreed. Vring could be an overkill IMHO (the whole
dirty_ring.cis 100+ LOC only).I guess you don't do polling/ event suppression and other tricks that virtio came up with for speed then?
There are no interrupts either, so no need for event suppression. You have vmexits when the ring gets full (and that needs to be synchronous), but apart from that the migration thread will poll the rings once when it needs to send more pages.
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 9 lines · Paolo Bonzini
Quoted 8 lines · Michael S. Tsirkin
Quoted 7 lines · Peter Xu
Quoted 6 lines · Paolo Bonzini
Quoted 4 lines · Michael S. Tsirkin
Quoted 2 lines
There is no new infrastructure to track the dirty pages---it's just a different way to pass them to userspace.
Did you guys consider using one of the virtio ring formats? Maybe reusing vhost code?
Quoted 8 lines
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
There are no used/available entries here, it's unidirectional (
kernel->user).Agreed. Vring could be an overkill IMHO (the whole
dirty_ring.cis 100+ LOC only).I guess you don't do polling/ event suppression and other tricks that virtio came up with for speed then?
I looked at the code finally, there's actually available, and fetched is exactly like used. Not saying existing code is a great fit for you as you have an extra slot parameter to pass and it's reversed as compared to vhost, with kernel being the driver and userspace the device (even though vringh might fit, yet needs to be updated to support packed rings though). But sticking to an existing format is a good idea IMHO, or if not I think it's not a bad idea to add some justification.
Quoted 6 lines
There are no interrupts either, so no need for event suppression. You have vmexits when the ring gets full (and that needs to be synchronous), but apart from that the migration thread will poll the rings once when it needs to send more pages.
Paolo
OK don't use that then.
Signature · 1 line
MST
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 19 lines · Michael S. Tsirkin
Quoted 9 lines · Paolo Bonzini
Quoted 8 lines · Michael S. Tsirkin
Quoted 7 lines · Peter Xu
Quoted 6 lines · Paolo Bonzini
Quoted 4 lines · Michael S. Tsirkin
Quoted 2 lines
There is no new infrastructure to track the dirty pages---it's just a different way to pass them to userspace.
Did you guys consider using one of the virtio ring formats? Maybe reusing vhost code?
Quoted 8 lines
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
There are no used/available entries here, it's unidirectional (
kernel->user).Agreed. Vring could be an overkill IMHO (the whole
dirty_ring.cis 100+ LOC only).I guess you don't do polling/ event suppression and other tricks that virtio came up with for speed then?
Quoted 7 lines
I looked at the code finally, there's actually available, and fetched is exactly like used. Not saying existing code is a great fit for you as you have an extra slot parameter to pass and it's reversed as compared to vhost, with kernel being the driver and userspace the device (even though vringh might fit, yet needs to be updated to support packed rings though). But sticking to an existing format is a good idea IMHO, or if not I think it's not a bad idea to add some justification.
Right, I'll add a small paragraph in the next cover letter to justify.
Thanks,
Signature · 1 line
Peter Xu
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
In reply to Paolo BonziniQuoted 8 lines · Paolo Bonzini
Quoted 4 lines · Michael S. Tsirkin
Quoted 2 lines
There is no new infrastructure to track the dirty pages---it's just a different way to pass them to userspace.
Did you guys consider using one of the virtio ring formats? Maybe reusing vhost code?
There are no used/available entries here, it's unidirectional (
kernel->user).
Didn't look at the design yet, but flow control (to prevent overflow) goes the other way, doesn't it? That's what used is, essentially.
Quoted 10 lines
Quoted 4 lines
If you did and it's not a good fit, this is something good to mention in the commit log.
I also wonder about performance numbers - any data here?
Yes some numbers would be useful. Note however that the improvement is asymptotical, O(#dirtied pages) vs O(#total pages) so it may differ depending on the workload.
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
In reply to Peter XuQuoted 54 lines · Peter Xu
This patch is heavily based on previous work from Lei Cao <[email protected]> and Paolo Bonzini <[email protected]>. [1]
KVM currently uses large bitmaps to track dirty memory. These bitmaps are copied to userspace when userspace queries KVM for its dirty page information. The use of bitmaps is mostly sufficient for live migration, as large parts of memory are be dirtied from one log-dirty pass to another. However, in a checkpointing system, the number of dirty pages is small and in fact it is often bounded---the VM is paused when it has dirtied a pre-defined number of pages. Traversing a large, sparsely populated bitmap to find set bits is time-consuming, as is copying the bitmap to user-space.
A similar issue will be there for live migration when the guest memory is huge while the page dirty procedure is trivial. In that case for each dirty sync we need to pull the whole dirty bitmap to userspace and analyse every bit even if it's mostly zeros.
The preferred data structure for above scenarios is a dense list of guest frame numbers (GFN). This patch series stores the dirty list in kernel memory that can be memory mapped into userspace to allow speedy harvesting.
We defined two new data structures:
struct
kvm_dirty_ring;
structkvm_dirty_ring_indexes;Firstly,
kvm_dirty_ringis defined to represent a ring of dirty pages. When dirty tracking is enabled, we can push dirty gfn onto the ring.Secondly,
kvm_dirty_ring_indexesis defined to represent the user/kernel interface of each ring. Currently it contains two
indexes: (1)avail_indexrepresents where we should push our next PFN (written by kernel), while (2)fetch_indexrepresents where the userspace should fetch the next dirty PFN (written by userspace).One complete ring is composed by one
kvm_dirty_ringplus its correspondingkvm_dirty_ring_indexes.Currently, we have N+1 rings for each VM of N vcpus:
- for each vcpu, we have 1 per-vcpu dirty ring,
- for each vm, we have 1 per-vm dirty ringPlease refer to the documentation update in this patch for more details.
Note that this patch implements the core logic of dirty ring buffer. It's still disabled for all archs for now. Also, we'll address some of the other issues in follow up patches before it's firstly enabled on x86.
Quoted 5 lines
[1] https://patchwork.kernel.org/patch/10471409/
Signed-off-by Lei Cao <[email protected]>Signed-off-by Paolo Bonzini <[email protected]>Signed-off-by Peter Xu <[email protected]>
Thanks, that's interesting.
Quoted 34 lines
--- Documentation/virt/kvm/api.txt | 109 +++++++++++++++ arch/x86/kvm/Makefile | 3 +- include/linux/kvm_dirty_ring.h | 67 +++++++++ include/linux/kvm_host.h | 33 +++++ include/linux/kvm_types.h | 1 + include/uapi/linux/kvm.h | 36 +++++ virt/kvm/dirty_ring.c | 156 +++++++++++++++++++++ virt/kvm/kvm_main.c | 240 ++++++++++++++++++++++++++++++++- 8 files changed, 642 insertions(+), 3 deletions(-) create mode 100644 include/linux/kvm_dirty_ring.h create mode 100644 virt/kvm/dirty_ring.cDocumentation/virt/kvm/api.txt +6
-0@@ -231,6 +231,7 @@ Based on their initialization different VMs may have different capabilities. It is thus encouraged to use the vm ioctl to query for capabilities (available with KVM_CAP_CHECK_EXTENSION_VM on the vm fd) + 4.5 KVM_GET_VCPU_MMAP_SIZE Capability: basic@@ -243,6 +244,18 @@ The KVM_RUN ioctl (cf.) communicates with userspace via a shared memory region. This ioctl returns the size of that region. See the KVM_RUN documentation for details. +Besides the size of the KVM_RUN communication region, other areas of +the VCPU file descriptor can be mmap-ed, including: + +- if KVM_CAP_COALESCED_MMIO is available, a page at + KVM_COALESCED_MMIO_PAGE_OFFSET * PAGE_SIZE; for historical reasons,
Documentation/virt/kvm/api.txt The KVM_RUN ioctl (cf.) communicates with userspace via a shared · line 261
Quoted 10 lines
Documentation/virt/kvm/api.txt +7
-0+ this page is included in the result of KVM_GET_VCPU_MMAP_SIZE. + KVM_CAP_COALESCED_MMIO is not documented yet. + +- if KVM_CAP_DIRTY_LOG_RING is available, a number of pages at + KVM_DIRTY_LOG_PAGE_OFFSET * PAGE_SIZE. For more information on + KVM_CAP_DIRTY_LOG_RING, see section 8.3. + 4.6 KVM_SET_MEMORY_REGION
PAGE_SIZE being which value? It's not always trivial for userspace to know what's the PAGE_SIZE for the kernel ...
Quoted 34 lines
Documentation/virt/kvm/api.txt +16
-0@@ -5358,6 +5371,7 @@ CPU when the exception is taken. If this virtual SError is taken to EL1 using AArch64, this value will be reported in the ISS field of ESR_ELx. See KVM_CAP_VCPU_EVENTS for more details. + 8.20 KVM_CAP_HYPERV_SEND_IPI Architectures: x86@@ -5365,6 +5379,7 @@ Architectures: x86 This capability indicates that KVM supports paravirtualized Hyper-V IPI send hypercalls: HvCallSendSyntheticClusterIpi, HvCallSendSyntheticClusterIpiEx. + 8.21 KVM_CAP_HYPERV_DIRECT_TLBFLUSH Architecture: x86@@ -5378,3 +5393,97 @@ handling by KVM (as some KVM hypercall may be mistakenly treated as TLB flush hypercalls by Hyper-V) so userspace should disable KVM identification in CPUID and only exposes Hyper-V identification. In this case, guest thinks it's running on Hyper-V and only use Hyper-V hypercalls. + +8.22 KVM_CAP_DIRTY_LOG_RING + +Architectures: x86 +Parameters: args[0] - size of the dirty log ring + +KVM is capable of tracking dirty memory using ring buffers that are +mmaped into userspace; there is one dirty ring per vcpu and one global +ring per vm. + +One dirty ring has the following two major structures: + +struct kvm_dirty_ring { + u16 dirty_index;
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5419
Quoted 10 lines
Documentation/virt/kvm/api.txt +10
-0+ u16 reset_index; + u32 size; + u32 soft_limit; + spinlock_t lock; + struct kvm_dirty_gfn *dirty_gfns; +}; + +struct kvm_dirty_ring_indexes { + __u32 avail_index; /* set by kernel */ + __u32 fetch_index; /* set by userspace */
Sticking these next to each other seems to guarantee cache conflicts.
Avail/Fetch seems to mimic Virtio's avail/used exactly. I am not saying you must reuse the code really, but I think you should take a hard look at e.g. the virtio packed ring structure. We spent a bunch of time optimizing it for cache utilization. It seems kernel is the driver, making entries available, and userspace the device, using them. Again let's not develop a thread about this, but I think this is something to consider and discuss in future versions of the patches.
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5424
Quoted 5 lines
Documentation/virt/kvm/api.txt +5
-0+}; + +While for each of the dirty entry it's defined as: + +struct kvm_dirty_gfn {
What does GFN stand for?
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5428
Quoted 4 lines
Documentation/virt/kvm/api.txt +4
-0+ __u32 pad; + __u32 slot; /* as_id | slot_id */ + __u64 offset; +};
offset of what? a 4K page right? Seems like a waste e.g. for hugetlbfs... How about replacing pad with size instead?
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5432
Quoted 4 lines
Documentation/virt/kvm/api.txt +4
-0+ +The fields in kvm_dirty_ring will be only internal to KVM itself, +while the fields in kvm_dirty_ring_indexes will be exposed to +userspace to be either read or written.
I'm not sure what you are trying to say here. kvm_dirty_gfn seems to be part of UAPI.
Quoted 9 lines
Documentation/virt/kvm/api.txt +9
-0+ +The two indices in the ring buffer are free running counters. + +In pseudocode, processing the ring buffer looks like this: + + idx = load-acquire(&ring->fetch_index); + while (idx != ring->avail_index) { + struct kvm_dirty_gfn *entry; + entry = &ring->dirty_gfns[idx & (size - 1)];
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB
Quoted 10 lines
Documentation/virt/kvm/api.txt +10
-0+ ... + + idx++; + } + ring->fetch_index = idx; + +Userspace calls KVM_ENABLE_CAP ioctl right after KVM_CREATE_VM ioctl +to enable this capability for the new guest and set the size of the +rings. It is only allowed before creating any vCPU, and the size of +the ring must be a power of two.
All these seem like arbitrary limitations to me.
Sizing the ring correctly might prove to be a challenge.
Thus I think there's value in resizing the rings
without destroying VCPU.
Also, power of two just saves a branch here and there, but wastes lots of memory. Just wrap the index around to 0 and then users can select any size?
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5454
Quoted 4 lines
Documentation/virt/kvm/api.txt +3
-0The larger the ring buffer, the less +likely the ring is full and the VM is forced to exit to userspace. The +optimal size depends on the workload, but it is recommended that it be +at least 64 KiB (4096 entries).
OTOH larger buffers put lots of pressure on the system cache.
Quoted 25 lines
Documentation/virt/kvm/api.txt +25
-0+ +After the capability is enabled, userspace can mmap the global ring +buffer (kvm_dirty_gfn[], offset KVM_DIRTY_LOG_PAGE_OFFSET) and the +indexes (kvm_dirty_ring_indexes, offset 0) from the VM file +descriptor. The per-vcpu dirty ring instead is mmapped when the vcpu +is created, similar to the kvm_run struct (kvm_dirty_ring_indexes +locates inside kvm_run, while kvm_dirty_gfn[] at offset +KVM_DIRTY_LOG_PAGE_OFFSET). + +Just like for dirty page bitmaps, the buffer tracks writes to +all user memory regions for which the KVM_MEM_LOG_DIRTY_PAGES flag was +set in KVM_SET_USER_MEMORY_REGION. Once a memory region is registered +with the flag set, userspace can start harvesting dirty pages from the +ring buffer. + +To harvest the dirty pages, userspace accesses the mmaped ring buffer +to read the dirty GFNs up to avail_index, and sets the fetch_index +accordingly. This can be done when the guest is running or paused, +and dirty pages need not be collected all at once. After processing +one or more entries in the ring buffer, userspace calls the VM ioctl +KVM_RESET_DIRTY_RINGS to notify the kernel that it has updated +fetch_index and to mark those pages clean. Therefore, the ioctl +must be called *before* reading the content of the dirty pages. + +However, there is a major difference comparing to the
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5489
Quoted 10 lines
Documentation/virt/kvm/api.txt +10
-0+KVM_GET_DIRTY_LOG interface in that when reading the dirty ring from +userspace it's still possible that the kernel has not yet flushed the +hardware dirty buffers into the kernel buffer. To achieve that, one +needs to kick the vcpu out for a hardware buffer flush (vmexit). + +If one of the ring buffers is full, the guest will exit to userspace +with the exit reason set to KVM_EXIT_DIRTY_LOG_FULL, and the +KVM_RUN ioctl will return -EINTR. Once that happens, userspace +should pause all the vcpus, then harvest all the dirty pages and +rearm the dirty traps. It can unpause the guest after that.
This last item means that the performance impact of the feature is really hard to predict. Can improve some workloads drastically. Or can slow some down.
One solution could be to actually allow using this together with the existing bitmap. Userspace can then decide whether it wants to block VCPU on ring full, or just record ring full condition and recover by bitmap scanning.
Quoted 143 lines
arch/x86/kvm/Makefile +2
-1@@ -5,7 +5,8 @@ ccflags-y += -Iarch/x86/kvm KVM := ../../../virt/kvm kvm-y += $(KVM)/kvm_main.o $(KVM)/coalesced_mmio.o \ - $(KVM)/eventfd.o $(KVM)/irqchip.o $(KVM)/vfio.o + $(KVM)/eventfd.o $(KVM)/irqchip.o $(KVM)/vfio.o \ + $(KVM)/dirty_ring.o kvm-$(CONFIG_KVM_ASYNC_PF) += $(KVM)/async_pf.o kvm-y += x86.o emulate.o i8259.o irq.o lapic.o \include/linux/kvm_dirty_ring.h new file +67
-0@@ -0,0 +1,67 @@ +#ifndef KVM_DIRTY_RING_H +#define KVM_DIRTY_RING_H + +/* +* struct kvm_dirty_ring is defined in include/uapi/linux/kvm.h. +* +* dirty_ring: shared with userspace via mmap. It is the compact list +* that holds the dirty pages. +* dirty_index: free running counter that points to the next slot in +* dirty_ring->dirty_gfns where a new dirty page should go. +* reset_index: free running counter that points to the next dirty page +* in dirty_ring->dirty_gfns for which dirty trap needs to +* be reenabled +* size: size of the compact list, dirty_ring->dirty_gfns +* soft_limit: when the number of dirty pages in the list reaches this +* limit, vcpu that owns this ring should exit to userspace +* to allow userspace to harvest all the dirty pages +* lock: protects dirty_ring, only in use if this is the global +* ring +* +* The number of dirty pages in the ring is calculated by, +* dirty_index - reset_index +* +* kernel increments dirty_ring->indices.avail_index after dirty index +* is incremented. When userspace harvests the dirty pages, it increments +* dirty_ring->indices.fetch_index up to dirty_ring->indices.avail_index. +* When kernel reenables dirty traps for the dirty pages, it increments +* reset_index up to dirty_ring->indices.fetch_index. +* +*/ +struct kvm_dirty_ring { +u32 dirty_index; +u32 reset_index; +u32 size; +u32 soft_limit; +spinlock_t lock; +struct kvm_dirty_gfn *dirty_gfns; +}; + +u32 kvm_dirty_ring_get_rsvd_entries (void ); +int kvm_dirty_ring_alloc (struct kvm *kvm,struct kvm_dirty_ring *ring); + +/* +* called with kvm->slots_lock held, returns the number of +* processed pages. +*/ +int kvm_dirty_ring_reset (struct kvm *kvm, +struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes); + +/* +* returns 0: successfully pushed +* 1: successfully pushed, soft limit reached, +* vcpu should exit to userspace +* -EBUSY: unable to push, dirty ring full. +*/ +int kvm_dirty_ring_push (struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes, +u32 slot,u64 offset,bool lock); + +/* for use in vm_operations_struct */ +struct page *kvm_dirty_ring_get_page (struct kvm_dirty_ring *ring,u32 i); + +void kvm_dirty_ring_free (struct kvm_dirty_ring *ring); +bool kvm_dirty_ring_full (struct kvm_dirty_ring *ring); + +#endif include/linux/kvm_host.h +13
-0@@ -34,6 +34,7 @@#include <linux/kvm_types.h> #include <asm/kvm_host.h> +#include <linux/kvm_dirty_ring.h> #ifndef KVM_MAX_VCPU_ID #define KVM_MAX_VCPU_ID KVM_MAX_VCPUS@@ -146,6 +147,7 @@ static inline bool is_error_page(struct page *page)#define KVM_REQ_MMU_RELOAD (1 | KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)#define KVM_REQ_PENDING_TIMER 2#define KVM_REQ_UNHALT 3 +#define KVM_REQ_DIRTY_RING_FULL 4#define KVM_REQUEST_ARCH_BASE 8#define KVM_ARCH_REQ_FLAGS (nr, flags) ({ \@@ -321,6 +323,7 @@ struct kvm_vcpu {bool ready;struct kvm_vcpu_arch arch;struct dentry *debugfs_dentry; +struct kvm_dirty_ring dirty_ring; };static inline int kvm_vcpu_exiting_guest_mode (struct kvm_vcpu *vcpu)@@ -501,6 +504,10 @@ struct kvm {struct srcu_struct srcu;struct srcu_struct irq_srcu;pid_t userspace_pid; +/* Data structure to be exported by mmap(kvm->fd, 0) */ +struct kvm_vm_run *vm_run; +u32 dirty_ring_size; +struct kvm_dirty_ring vm_dirty_ring; };#define kvm_err (fmt, ...) \@@ -832,6 +839,8 @@ void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,gfn_t gfn_offset, unsigned long mask); +void kvm_reset_dirty_gfn (struct kvm *kvm,u32 slot,u64 offset,u64 mask); +int kvm_vm_ioctl_get_dirty_log (struct kvm *kvm,struct kvm_dirty_log *log); int kvm_vm_ioctl_clear_dirty_log(struct kvm *kvm,@@ -1411,4 +1420,28 @@ int kvm_vm_create_worker_thread(struct kvm *kvm, kvm_vm_thread_fn_t thread_fn, uintptr_t data, const char *name, struct task_struct **thread_ptr); +/* +* This defines how many reserved entries we want to keep before we +* kick the vcpu to the userspace to avoid dirty ring full. This +* value can be tuned to higher if e.g. PML is enabled on the host.
include/linux/kvm_host.h int kvm_vm_create_worker_thread(struct kvm *kvm, kvm_vm_thread_fn_t thread_fn, · line 1436
Quoted 10 lines
include/linux/kvm_host.h +10
-0+*/ +#define KVM_DIRTY_RING_RSVD_ENTRIES 64 + +/* Max number of entries allowed for each kvm dirty ring */ +#define KVM_DIRTY_RING_MAX_ENTRIES 65536 + +/* +* Arch needs to define these macro after implementing the dirty ring +* feature. KVM_DIRTY_LOG_PAGE_OFFSET should be defined as the +* starting page offset of the dirty ring structures,
Confused. Offset where? You set a default for everyone - where does arch want to override it?
include/linux/kvm_host.h int kvm_vm_create_worker_thread(struct kvm *kvm, kvm_vm_thread_fn_t thread_fn, · line 1445
Quoted 10 lines
while
+ *KVM_DIRTY_RING_VERSIONshould be defined as >=1. By default, this
+ * feature is off on all archs.
+ */include/linux/kvm_host.h +6
-0+#ifndef KVM_DIRTY_LOG_PAGE_OFFSET +#define KVM_DIRTY_LOG_PAGE_OFFSET 0 +#endif +#ifndef KVM_DIRTY_RING_VERSION +#define KVM_DIRTY_RING_VERSION 0 +#endif
One way versioning, with no bits and negotiation will make it hard to change down the road.
what's wrong with existing KVM capabilities that you feel there's a need for dedicated versioning for this?
Quoted 105 lines
include/linux/kvm_host.h +1
-0+#endif include/linux/kvm_types.h +1
-0@@ -11,6 +11,7 @@ struct kvm_irq_routing_table;struct kvm_memory_slot ;struct kvm_one_reg ;struct kvm_run ; +struct kvm_vm_run ;struct kvm_userspace_memory_region ;struct kvm_vcpu ;struct kvm_vcpu_init ;include/uapi/linux/kvm.h +36
-0@@ -236,6 +236,7 @@ struct kvm_hyperv_exit {#define KVM_EXIT_IOAPIC_EOI 26#define KVM_EXIT_HYPERV 27#define KVM_EXIT_ARM_NISV 28 +#define KVM_EXIT_DIRTY_RING_FULL 29/* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */ @@ -247,6 +248,11 @@ struct kvm_hyperv_exit {/* Encounter unexpected vm-exit reason */ #define KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON 4 +struct kvm_dirty_ring_indexes { +__u32 avail_index;/* set by kernel */ +__u32 fetch_index;/* set by userspace */ +}; +/* for KVM_RUN, returned by mmap(vcpu_fd, offset=0) */ struct kvm_run {/* in */ @@ -421,6 +427,13 @@ struct kvm_run {struct kvm_sync_regs regs;char padding[SYNC_REGS_SIZE_BYTES ]; } s; + +struct kvm_dirty_ring_indexes vcpu_ring_indexes; +}; + +/* Returned by mmap(kvm->fd, offset=0) */ +struct kvm_vm_run { +struct kvm_dirty_ring_indexes vm_ring_indexes; };/* for KVM_REGISTER_COALESCED_MMIO / KVM_UNREGISTER_COALESCED_MMIO */ @@ -1009,6 +1022,7 @@ struct kvm_ppc_resize_hpt {#define KVM_CAP_PPC_GUEST_DEBUG_SSTEP 176#define KVM_CAP_ARM_NISV_TO_USER 177#define KVM_CAP_ARM_INJECT_EXT_DABT 178 +#define KVM_CAP_DIRTY_LOG_RING 179#ifdef KVM_CAP_IRQ_ROUTING @@ -1472,6 +1486,9 @@ struct kvm_enc_region {/* Available with KVM_CAP_ARM_SVE */ #define KVM_ARM_VCPU_FINALIZE _IOW(KVMIO, 0xc2, int) +/* Available with KVM_CAP_DIRTY_LOG_RING */ +#define KVM_RESET_DIRTY_RINGS _IO(KVMIO, 0xc3) +/* Secure Encrypted Virtualization command */ enum sev_cmd_id {/* Guest initialization commands */ @@ -1622,4 +1639,23 @@ struct kvm_hyperv_eventfd {#define KVM_HYPERV_CONN_ID_MASK 0x00ffffff#define KVM_HYPERV_EVENTFD_DEASSIGN (1 << 0) +/* +* The following are the requirements for supporting dirty log ring +* (by enabling KVM_DIRTY_LOG_PAGE_OFFSET). +* +* 1. Memory accesses by KVM should call kvm_vcpu_write_* instead +* of kvm_write_* so that the global dirty ring is not filled up +* too quickly. +* 2. kvm_arch_mmu_enable_log_dirty_pt_masked should be defined for +* enabling dirty logging. +* 3. There should not be a separate step to synchronize hardware +* dirty bitmap with KVM's. +*/ + +struct kvm_dirty_gfn { +__u32 pad; +__u32 slot; +__u64 offset; +}; +#endif /* __LINUX_KVM_H */ virt/kvm/dirty_ring.c new file +5
-0@@ -0,0 +1,156 @@ +#include <linux/kvm_host.h> +#include <linux/kvm.h> +#include <linux/vmalloc.h> +#include <linux/kvm_dirty_ring.h> +
virt/kvm/dirty_ring.c line 15
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+u32 kvm_dirty_ring_get_rsvd_entries (void ) +{ +return KVM_DIRTY_RING_RSVD_ENTRIES +kvm_cpu_dirty_log_size (); +} + +int kvm_dirty_ring_alloc (struct kvm *kvm,struct kvm_dirty_ring *ring) +{ +u32 size = kvm->dirty_ring_size; + + ring->dirty_gfns =vmalloc (size);
So 1/2 a megabyte of kernel memory per VM that userspace locks up. Do we really have to though? Why not get a userspace pointer, write it with copy to user, and sidestep all this?
Quoted 27 lines
virt/kvm/dirty_ring.c new file +27
-0+if (!ring->dirty_gfns) +return -ENOMEM ; +memset (ring->dirty_gfns,0 , size); + + ring->size = size /sizeof (struct kvm_dirty_gfn ); + ring->soft_limit = + (kvm->dirty_ring_size /sizeof (struct kvm_dirty_gfn )) - +kvm_dirty_ring_get_rsvd_entries (); + ring->dirty_index =0 ; + ring->reset_index =0 ; +spin_lock_init (&ring->lock); + +return 0 ; +} + +int kvm_dirty_ring_reset (struct kvm *kvm, +struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes) +{ +u32 cur_slot, next_slot; +u64 cur_offset, next_offset; +unsigned long mask; +u32 fetch; +int count =0 ; +struct kvm_dirty_gfn *entry; + + fetch =READ_ONCE (indexes->fetch_index);
virt/kvm/dirty_ring.c line 52
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+if (fetch == ring->reset_index) +return 0 ; + + entry = &ring->dirty_gfns[ring->reset_index & (ring->size -1 )]; +/* +* The ring buffer is shared with userspace, which might mmap +* it and concurrently modify slot and offset. Userspace must +* not be trusted! READ_ONCE prevents the compiler from changing +* the values after they've been range-checked (the checks are +* in kvm_reset_dirty_gfn).
What it doesn't is prevent speculative attacks. That's why things like copy from user have a speculation barrier. Instead of worrying about that, unless it's really critical, I think you'd do well do just use copy to/from user.
virt/kvm/dirty_ring.c line 54
Quoted 2 lines
virt/kvm/dirty_ring.c new file +2
-0+*/ +smp_read_barrier_depends ();
What depends on what here? Looks suspicious ...
virt/kvm/dirty_ring.c line 62
Quoted 8 lines
virt/kvm/dirty_ring.c new file +8
-0+ cur_slot =READ_ONCE (entry->slot); + cur_offset =READ_ONCE (entry->offset); + mask =1 ; + count++; + ring->reset_index++; +while (ring->reset_index != fetch) { + entry = &ring->dirty_gfns[ring->reset_index & (ring->size -1 )]; +smp_read_barrier_depends ();
same concerns here
virt/kvm/dirty_ring.c line 69
Quoted 7 lines
virt/kvm/dirty_ring.c new file +7
-0+ next_slot =READ_ONCE (entry->slot); + next_offset =READ_ONCE (entry->offset); + ring->reset_index++; + count++; +/* +* Try to coalesce the reset operations when the guest is +* scanning pages in the same slot.
what does guest scanning mean?
Quoted 42 lines
virt/kvm/dirty_ring.c new file +42
-0+*/ +if (next_slot == cur_slot) { +int delta = next_offset - cur_offset; + +if (delta >=0 && delta <BITS_PER_LONG ) { + mask |=1ull << delta; +continue ; + } + +/* Backwards visit, careful about overflows! */ +if (delta > -BITS_PER_LONG && delta <0 && + (mask << -delta >> -delta) == mask) { + cur_offset = next_offset; + mask = (mask << -delta) |1 ; +continue ; + } + } +kvm_reset_dirty_gfn (kvm, cur_slot, cur_offset, mask); + cur_slot = next_slot; + cur_offset = next_offset; + mask =1 ; + } +kvm_reset_dirty_gfn (kvm, cur_slot, cur_offset, mask); + +return count; +} + +static inline u32 kvm_dirty_ring_used (struct kvm_dirty_ring *ring) +{ +return ring->dirty_index - ring->reset_index; +} + +bool kvm_dirty_ring_full (struct kvm_dirty_ring *ring) +{ +return kvm_dirty_ring_used (ring) >= ring->size; +} + +/* +* Returns: +* >0 if we should kick the vcpu out, +* =0 if the gfn pushed successfully, or, +* <0 if error (e.g. ring full)
virt/kvm/dirty_ring.c line 121
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+*/ +int kvm_dirty_ring_push (struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes, +u32 slot,u64 offset,bool lock) +{ +int ret; +struct kvm_dirty_gfn *entry; + +if (lock) +spin_lock (&ring->lock);
what's the story around locking here? Why is it safe not to take the lock sometimes?
Quoted 101 lines
virt/kvm/dirty_ring.c new file +35
-0+ +if (kvm_dirty_ring_full (ring)) { + ret = -EBUSY ; + goto out; + } + + entry = &ring->dirty_gfns[ring->dirty_index & (ring->size -1 )]; + entry->slot = slot; + entry->offset = offset; +smp_wmb (); + ring->dirty_index++; +WRITE_ONCE (indexes->avail_index, ring->dirty_index); + ret =kvm_dirty_ring_used (ring) >= ring->soft_limit; +pr_info ("%s: slot %u offset %llu used %u\n" , + __func__, slot, offset,kvm_dirty_ring_used (ring)); + +out: +if (lock) +spin_unlock (&ring->lock); + +return ret; +} + +struct page *kvm_dirty_ring_get_page (struct kvm_dirty_ring *ring,u32 i) +{ +return vmalloc_to_page ((void *)ring->dirty_gfns + i *PAGE_SIZE ); +} + +void kvm_dirty_ring_free (struct kvm_dirty_ring *ring) +{ +if (ring->dirty_gfns) { +vfree (ring->dirty_gfns); + ring->dirty_gfns =NULL ; + } +}virt/kvm/kvm_main.c +21
-1@@ -64,6 +64,8 @@#define CREATE_TRACE_POINTS #include <trace/events/kvm.h> +#include <linux/kvm_dirty_ring.h> +/* Worst case buffer size needed for holding an integer. */ #define ITOA_MAX_LEN 12@@ -149,6 +151,10 @@ static void mark_page_dirty_in_slot(struct kvm *kvm,struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, gfn_t gfn); +static void mark_page_dirty_in_ring (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *slot, +gfn_t gfn); __visible bool kvm_rebooting;EXPORT_SYMBOL_GPL (kvm_rebooting);@@ -359,11 +365,22 @@ int kvm_vcpu_init(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned id) vcpu->preempted = false; vcpu->ready = false; +if (kvm->dirty_ring_size) { + r =kvm_dirty_ring_alloc (vcpu->kvm, &vcpu->dirty_ring); +if (r) { + kvm->dirty_ring_size =0 ; + goto fail_free_run; + } + } + r =kvm_arch_vcpu_init (vcpu);if (r <0 ) - goto fail_free_run; + goto fail_free_ring;return 0 ; +fail_free_ring: +if (kvm->dirty_ring_size) +kvm_dirty_ring_free (&vcpu->dirty_ring); fail_free_run:free_page ((unsigned long )vcpu->run); fail:@@ -381,6 +398,8 @@ void kvm_vcpu_uninit(struct kvm_vcpu *vcpu)put_pid (rcu_dereference_protected (vcpu->pid,1 ));kvm_arch_vcpu_uninit (vcpu);free_page ((unsigned long )vcpu->run); +if (vcpu->kvm->dirty_ring_size) +kvm_dirty_ring_free (&vcpu->dirty_ring); }EXPORT_SYMBOL_GPL (kvm_vcpu_uninit);@@ -690,6 +709,7 @@ static struct kvm *kvm_create_vm(unsigned long type)struct kvm *kvm =kvm_arch_alloc_vm ();int r = -ENOMEM ;int i; +struct page *page;if (!kvm)return ERR_PTR (-ENOMEM );
virt/kvm/kvm_main.c static struct kvm *kvm_create_vm(unsigned long type) · line 733
Quoted 9 lines
virt/kvm/kvm_main.c +6
-0@@ -705,6 +725,14 @@ static struct kvm *kvm_create_vm(unsigned long type)BUILD_BUG_ON (KVM_MEM_SLOTS_NUM >SHRT_MAX ); + page =alloc_page (GFP_KERNEL | __GFP_ZERO); +if (!page) { + r = -ENOMEM ; + goto out_err_alloc_page; + } + kvm->vm_run =page_address (page);
So 4K with just 8 bytes used. Not as bad as 1/2Mbyte for the ring but still. What is wrong with just a pointer and calling put_user?
Quoted 100 lines
virt/kvm/kvm_main.c +51
-1+BUILD_BUG_ON (sizeof (struct kvm_vm_run ) >PAGE_SIZE ); +if (init_srcu_struct (&kvm->srcu)) goto out_err_no_srcu;if (init_srcu_struct (&kvm->irq_srcu))@@ -775,6 +803,9 @@ static struct kvm *kvm_create_vm(unsigned long type) out_err_no_irq_srcu:cleanup_srcu_struct (&kvm->srcu); out_err_no_srcu: +free_page ((unsigned long )page); + kvm->vm_run =NULL ; +out_err_alloc_page:kvm_arch_free_vm (kvm);mmdrop (current->mm);return ERR_PTR (r);@@ -800,6 +831,15 @@ static void kvm_destroy_vm(struct kvm *kvm)int i;struct mm_struct *mm = kvm->mm; +if (kvm->dirty_ring_size) { +kvm_dirty_ring_free (&kvm->vm_dirty_ring); + } + +if (kvm->vm_run) { +free_page ((unsigned long )kvm->vm_run); + kvm->vm_run =NULL ; + } +kvm_uevent_notify_change (KVM_EVENT_DESTROY_VM , kvm);kvm_destroy_vm_debugfs (kvm);kvm_arch_sync_events (kvm);@@ -2301,7 +2341,7 @@ static void mark_page_dirty_in_slot(struct kvm *kvm, {if (memslot && memslot->dirty_bitmap) {unsigned long rel_gfn = gfn - memslot->base_gfn; - +mark_page_dirty_in_ring (kvm, vcpu, memslot, gfn);set_bit_le (rel_gfn, memslot->dirty_bitmap); } }@@ -2649,6 +2689,13 @@ void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode) }EXPORT_SYMBOL_GPL (kvm_vcpu_on_spin); +static bool kvm_fault_in_dirty_ring (struct kvm *kvm,struct vm_fault *vmf) +{ +return (vmf->pgoff >=KVM_DIRTY_LOG_PAGE_OFFSET ) && + (vmf->pgoff <KVM_DIRTY_LOG_PAGE_OFFSET + + kvm->dirty_ring_size /PAGE_SIZE ); +} +static vm_fault_t kvm_vcpu_fault (struct vm_fault *vmf) {struct kvm_vcpu *vcpu = vmf->vma->vm_file->private_data;@@ -2664,6 +2711,10 @@ static vm_fault_t kvm_vcpu_fault(struct vm_fault *vmf) else if (vmf->pgoff == KVM_COALESCED_MMIO_PAGE_OFFSET) page =virt_to_page (vcpu->kvm->coalesced_mmio_ring);#endif + else if (kvm_fault_in_dirty_ring(vcpu->kvm, vmf)) + page =kvm_dirty_ring_get_page ( + &vcpu->dirty_ring, + vmf->pgoff -KVM_DIRTY_LOG_PAGE_OFFSET ); else return kvm_arch_vcpu_fault(vcpu, vmf);get_page (page);@@ -3259,12 +3310,162 @@ static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg)#endif case KVM_CAP_NR_MEMSLOTS :return KVM_USER_MEM_SLOTS ; +case KVM_CAP_DIRTY_LOG_RING : +/* Version will be zero if arch didn't implement it */ +return KVM_DIRTY_RING_VERSION ;default :break ; }return kvm_vm_ioctl_check_extension (kvm, arg); } +static void mark_page_dirty_in_ring (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *slot, +gfn_t gfn) +{ +u32 as_id =0 ; +u64 offset; +int ret; +struct kvm_dirty_ring *ring; +struct kvm_dirty_ring_indexes *indexes; +bool is_vm_ring; + +if (!kvm->dirty_ring_size) +return ; + + offset = gfn - slot->base_gfn; + +if (vcpu) { + as_id =kvm_arch_vcpu_memslots_id (vcpu); + }else { + as_id =0 ; + vcpu =kvm_get_running_vcpu ();
virt/kvm/kvm_main.c static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) · line 3353
Quoted 10 lines
virt/kvm/kvm_main.c +10
-0+ } + +if (vcpu) { + ring = &vcpu->dirty_ring; + indexes = &vcpu->run->vcpu_ring_indexes; + is_vm_ring = false; + }else { +/* +* Put onto per vm ring because no vcpu context. Kick +* vcpu0 if ring is full.
What about tasks on vcpu 0? Do guests realize it's a bad idea to put critical tasks there, they will be penalized disproportionally?
Quoted 56 lines
virt/kvm/kvm_main.c +56
-0+*/ + vcpu = kvm->vcpus[0 ]; + ring = &kvm->vm_dirty_ring; + indexes = &kvm->vm_run->vm_ring_indexes; + is_vm_ring = true; + } + + ret =kvm_dirty_ring_push (ring, indexes, + (as_id <<16 )|slot->id, offset, + is_vm_ring); +if (ret <0 ) { +if (is_vm_ring) +pr_warn_once ("vcpu %d dirty log overflow\n" , + vcpu->vcpu_id); +else +pr_warn_once ("per-vm dirty log overflow\n" ); +return ; + } + +if (ret) +kvm_make_request (KVM_REQ_DIRTY_RING_FULL , vcpu); +} + +void kvm_reset_dirty_gfn (struct kvm *kvm,u32 slot,u64 offset,u64 mask) +{ +struct kvm_memory_slot *memslot; +int as_id, id; + + as_id = slot >>16 ; + id = (u16 )slot; +if (as_id >=KVM_ADDRESS_SPACE_NUM || id >=KVM_USER_MEM_SLOTS ) +return ; + + memslot =id_to_memslot (__kvm_memslots (kvm, as_id), id); +if (offset >= memslot->npages) +return ; + +spin_lock (&kvm->mmu_lock); +/* FIXME: we should use a single AND operation, but there is no +* applicable atomic API. +*/ +while (mask) { +clear_bit_le (offset +__ffs (mask), memslot->dirty_bitmap); + mask &= mask -1 ; + } + +kvm_arch_mmu_enable_log_dirty_pt_masked (kvm, memslot, offset, mask); +spin_unlock (&kvm->mmu_lock); +} + +static int kvm_vm_ioctl_enable_dirty_log_ring (struct kvm *kvm,u32 size) +{ +int r; + +/* the size should be power of 2 */ +if (!size || (size & (size -1 )))
virt/kvm/kvm_main.c static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) · line 3419
Quoted 10 lines
virt/kvm/kvm_main.c +10
-0+return -EINVAL ; + +/* Should be bigger to keep the reserved entries, or a page */ +if (size <kvm_dirty_ring_get_rsvd_entries () * +sizeof (struct kvm_dirty_gfn ) || size <PAGE_SIZE ) +return -EINVAL ; + +if (size >KVM_DIRTY_RING_MAX_ENTRIES * +sizeof (struct kvm_dirty_gfn )) +return -E2BIG ;
KVM_DIRTY_RING_MAX_ENTRIES is not part of UAPI.
So how does userspace know what's legal?
Do you expect it to just try?
More likely it will just copy the number from kernel and can never ever make it smaller.
Quoted 113 lines
virt/kvm/kvm_main.c +84
-0+ +/* We only allow it to set once */ +if (kvm->dirty_ring_size) +return -EINVAL ; + +mutex_lock (&kvm->lock); + +if (kvm->created_vcpus) { +/* We don't allow to change this value after vcpu created */ + r = -EINVAL ; + }else { + kvm->dirty_ring_size = size; + r =kvm_dirty_ring_alloc (kvm, &kvm->vm_dirty_ring); +if (r) { +/* Unset dirty ring */ + kvm->dirty_ring_size =0 ; + } + } + +mutex_unlock (&kvm->lock); +return r; +} + +static int kvm_vm_ioctl_reset_dirty_pages (struct kvm *kvm) +{ +int i; +struct kvm_vcpu *vcpu; +int cleared =0 ; + +if (!kvm->dirty_ring_size) +return -EINVAL ; + +mutex_lock (&kvm->slots_lock); + + cleared +=kvm_dirty_ring_reset (kvm, &kvm->vm_dirty_ring, + &kvm->vm_run->vm_ring_indexes); + +kvm_for_each_vcpu (i, vcpu, kvm) + cleared +=kvm_dirty_ring_reset (vcpu->kvm, &vcpu->dirty_ring, + &vcpu->run->vcpu_ring_indexes); + +mutex_unlock (&kvm->slots_lock); + +if (cleared) +kvm_flush_remote_tlbs (kvm); + +return cleared; +} +int __attribute__((weak))kvm_vm_ioctl_enable_cap (struct kvm *kvm,struct kvm_enable_cap *cap) {@@ -3282,6 +3483,8 @@ static int kvm_vm_ioctl_enable_cap_generic(struct kvm *kvm, kvm->manual_dirty_log_protect = cap->args[0 ];return 0 ;#endif +case KVM_CAP_DIRTY_LOG_RING : +return kvm_vm_ioctl_enable_dirty_log_ring (kvm, cap->args[0 ]);default :return kvm_vm_ioctl_enable_cap (kvm, cap); }@@ -3469,6 +3672,9 @@ static long kvm_vm_ioctl(struct file *filp,case KVM_CHECK_EXTENSION : r =kvm_vm_ioctl_check_extension_generic (kvm, arg);break ; +case KVM_RESET_DIRTY_RINGS : + r =kvm_vm_ioctl_reset_dirty_pages (kvm); +break ;default : r =kvm_arch_vm_ioctl (filp, ioctl, arg); }@@ -3517,9 +3723,39 @@ static long kvm_vm_compat_ioctl(struct file *filp, }#endif +static vm_fault_t kvm_vm_fault (struct vm_fault *vmf) +{ +struct kvm *kvm = vmf->vma->vm_file->private_data; +struct page *page =NULL ; + +if (vmf->pgoff ==0 ) + page =virt_to_page (kvm->vm_run); +else if (kvm_fault_in_dirty_ring (kvm, vmf)) + page =kvm_dirty_ring_get_page ( + &kvm->vm_dirty_ring, + vmf->pgoff -KVM_DIRTY_LOG_PAGE_OFFSET ); +else +return VM_FAULT_SIGBUS ; + +get_page (page); + vmf->page = page; +return 0 ; +} + +static const struct vm_operations_struct kvm_vm_vm_ops = { + .fault = kvm_vm_fault, +}; + +static int kvm_vm_mmap (struct file *file,struct vm_area_struct *vma) +{ + vma->vm_ops = &kvm_vm_vm_ops; +return 0 ; +} +static struct file_operations kvm_vm_fops = { .release = kvm_vm_release, .unlocked_ioctl = kvm_vm_ioctl, + .mmap = kvm_vm_mmap, .llseek = noop_llseek,KVM_COMPAT (kvm_vm_compat_ioctl), };Signature · 1 line
2.21.0
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 6 lines · Michael S. Tsirkin
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5419
Quoted 4 lines
Documentation/virt/kvm/api.txt +4
-0+ +struct kvm_dirty_ring_indexes { + __u32 avail_index; /* set by kernel */ + __u32 fetch_index; /* set by userspace */Sticking these next to each other seems to guarantee cache conflicts.
I don't think that's an issue because you'd have a conflict anyway on the actual entry; userspace anyway has to read the kernel-written index, which will cause cache traffic.
Quoted 1 line
Avail/Fetch seems to mimic Virtio's avail/used exactly.
No, avail_index/fetch_index is just the producer and consumer indices respectively. There is only one ring buffer, not two as in virtio.
Quoted 8 lines
I am not saying
you must reuse the code really, but I think you should take a hard look at e.g. the virtio packed ring structure. We spent a bunch of time optimizing it for cache utilization. It seems kernel is the driver, making entries available, and userspace the device, using them. Again let's not develop a thread about this, but I think this is something to consider and discuss in future versions of the patches.
Even in the packed ring you have two cache lines accessed, one for the index and one for the descriptor. Here you have one, because the data is embedded in the ring buffer.
Quoted 9 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5424
Quoted 6 lines
Documentation/virt/kvm/api.txt +5
-0+}; + +While for each of the dirty entry it's defined as: + +struct kvm_dirty_gfn {What does GFN stand for?
Quoted 7 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5428
Quoted 4 lines
Documentation/virt/kvm/api.txt +4
-0+ __u32 pad; + __u32 slot; /* as_id | slot_id */ + __u64 offset; +};offset of what? a 4K page right? Seems like a waste e.g. for hugetlbfs... How about replacing pad with size instead?
No, it's an offset in the memslot (which will usually be >4GB for any VM with bigger memory than that).
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
In reply to Michael S. TsirkinQuoted 55 lines · Michael S. Tsirkin
Quoted 54 lines · Peter Xu
This patch is heavily based on previous work from Lei Cao <[email protected]> and Paolo Bonzini <[email protected]>. [1]
KVM currently uses large bitmaps to track dirty memory. These bitmaps are copied to userspace when userspace queries KVM for its dirty page information. The use of bitmaps is mostly sufficient for live migration, as large parts of memory are be dirtied from one log-dirty pass to another. However, in a checkpointing system, the number of dirty pages is small and in fact it is often bounded---the VM is paused when it has dirtied a pre-defined number of pages. Traversing a large, sparsely populated bitmap to find set bits is time-consuming, as is copying the bitmap to user-space.
A similar issue will be there for live migration when the guest memory is huge while the page dirty procedure is trivial. In that case for each dirty sync we need to pull the whole dirty bitmap to userspace and analyse every bit even if it's mostly zeros.
The preferred data structure for above scenarios is a dense list of guest frame numbers (GFN). This patch series stores the dirty list in kernel memory that can be memory mapped into userspace to allow speedy harvesting.
We defined two new data structures:
struct
kvm_dirty_ring;
structkvm_dirty_ring_indexes;Firstly,
kvm_dirty_ringis defined to represent a ring of dirty pages. When dirty tracking is enabled, we can push dirty gfn onto the ring.Secondly,
kvm_dirty_ring_indexesis defined to represent the user/kernel interface of each ring. Currently it contains two
indexes: (1)avail_indexrepresents where we should push our next PFN (written by kernel), while (2)fetch_indexrepresents where the userspace should fetch the next dirty PFN (written by userspace).One complete ring is composed by one
kvm_dirty_ringplus its correspondingkvm_dirty_ring_indexes.Currently, we have N+1 rings for each VM of N vcpus:
- for each vcpu, we have 1 per-vcpu dirty ring,
- for each vm, we have 1 per-vm dirty ringPlease refer to the documentation update in this patch for more details.
Note that this patch implements the core logic of dirty ring buffer. It's still disabled for all archs for now. Also, we'll address some of the other issues in follow up patches before it's firstly enabled on x86.
Quoted 8 lines
Quoted 5 lines
[1] https://patchwork.kernel.org/patch/10471409/
Signed-off-by Lei Cao <[email protected]>Signed-off-by Paolo Bonzini <[email protected]>Signed-off-by Peter Xu <[email protected]>Thanks, that's interesting.
Hi, Michael,
Thanks for reading the series.
Quoted 43 lines
Quoted 43 lines
--- Documentation/virt/kvm/api.txt | 109 +++++++++++++++ arch/x86/kvm/Makefile | 3 +- include/linux/kvm_dirty_ring.h | 67 +++++++++ include/linux/kvm_host.h | 33 +++++ include/linux/kvm_types.h | 1 + include/uapi/linux/kvm.h | 36 +++++ virt/kvm/dirty_ring.c | 156 +++++++++++++++++++++ virt/kvm/kvm_main.c | 240 ++++++++++++++++++++++++++++++++- 8 files changed, 642 insertions(+), 3 deletions(-) create mode 100644 include/linux/kvm_dirty_ring.h create mode 100644 virt/kvm/dirty_ring.cDocumentation/virt/kvm/api.txt +13
-0@@ -231,6 +231,7 @@ Based on their initialization different VMs may have different capabilities. It is thus encouraged to use the vm ioctl to query for capabilities (available with KVM_CAP_CHECK_EXTENSION_VM on the vm fd) + 4.5 KVM_GET_VCPU_MMAP_SIZE Capability: basic@@ -243,6 +244,18 @@ The KVM_RUN ioctl (cf.) communicates with userspace via a shared memory region. This ioctl returns the size of that region. See the KVM_RUN documentation for details. +Besides the size of the KVM_RUN communication region, other areas of +the VCPU file descriptor can be mmap-ed, including: + +- if KVM_CAP_COALESCED_MMIO is available, a page at + KVM_COALESCED_MMIO_PAGE_OFFSET * PAGE_SIZE; for historical reasons, + this page is included in the result of KVM_GET_VCPU_MMAP_SIZE. + KVM_CAP_COALESCED_MMIO is not documented yet. + +- if KVM_CAP_DIRTY_LOG_RING is available, a number of pages at + KVM_DIRTY_LOG_PAGE_OFFSET * PAGE_SIZE. For more information on + KVM_CAP_DIRTY_LOG_RING, see section 8.3. +
Quoted 5 lines
Documentation/virt/kvm/api.txt The KVM_RUN ioctl (cf.) communicates with userspace via a shared · line 261
Quoted 2 lines
Documentation/virt/kvm/api.txt +0
-04.6 KVM_SET_MEMORY_REGION
PAGE_SIZEbeing which value? It's not always trivial for userspace to know what's thePAGE_SIZEfor the kernel ...
I thought it can be easily fetched from getpagesize() or sysconf(PAGE_SIZE)? Especially considering that the document should be for kvm userspace, I'd say it should be common that a hypervisor process will need to know this probably in other tons of places.. no?
Quoted 49 lines
Quoted 36 lines
Documentation/virt/kvm/api.txt +16
-0@@ -5358,6 +5371,7 @@ CPU when the exception is taken. If this virtual SError is taken to EL1 using AArch64, this value will be reported in the ISS field of ESR_ELx. See KVM_CAP_VCPU_EVENTS for more details. + 8.20 KVM_CAP_HYPERV_SEND_IPI Architectures: x86@@ -5365,6 +5379,7 @@ Architectures: x86 This capability indicates that KVM supports paravirtualized Hyper-V IPI send hypercalls: HvCallSendSyntheticClusterIpi, HvCallSendSyntheticClusterIpiEx. + 8.21 KVM_CAP_HYPERV_DIRECT_TLBFLUSH Architecture: x86@@ -5378,3 +5393,97 @@ handling by KVM (as some KVM hypercall may be mistakenly treated as TLB flush hypercalls by Hyper-V) so userspace should disable KVM identification in CPUID and only exposes Hyper-V identification. In this case, guest thinks it's running on Hyper-V and only use Hyper-V hypercalls. + +8.22 KVM_CAP_DIRTY_LOG_RING + +Architectures: x86 +Parameters: args[0] - size of the dirty log ring + +KVM is capable of tracking dirty memory using ring buffers that are +mmaped into userspace; there is one dirty ring per vcpu and one global +ring per vm. + +One dirty ring has the following two major structures: + +struct kvm_dirty_ring { + u16 dirty_index;Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5419
Quoted 10 lines
Documentation/virt/kvm/api.txt +10
-0+ u16 reset_index; + u32 size; + u32 soft_limit; + spinlock_t lock; + struct kvm_dirty_gfn *dirty_gfns; +}; + +struct kvm_dirty_ring_indexes { + __u32 avail_index; /* set by kernel */ + __u32 fetch_index; /* set by userspace */Sticking these next to each other seems to guarantee cache conflicts.
Quoted 8 lines
Avail/Fetch seems to mimic Virtio's avail/used exactly. I am not saying you must reuse the code really, but I think you should take a hard look at e.g. the virtio packed ring structure. We spent a bunch of time optimizing it for cache utilization. It seems kernel is the driver, making entries available, and userspace the device, using them. Again let's not develop a thread about this, but I think this is something to consider and discuss in future versions of the patches.
I think I completely understand your concern. We should avoid wasting time on those are already there. I'm just afraid that it'll took even more time to use virtio for this use case while at last we don't really get much benefit out of it (e.g. most of the virtio features are not used).
Yeh let's not develop a thread for this topic - I will read more on virtio before my next post to see whether there's any chance we can share anything with virtio ring.
Quoted 9 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5424
Quoted 7 lines
Documentation/virt/kvm/api.txt +5
-0+}; + +While for each of the dirty entry it's defined as: + +struct kvm_dirty_gfn {What does GFN stand for?
It's guest frame number, iiuc. I'm not the one who named this, but that's what I understand..
Quoted 8 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5428
Quoted 5 lines
Documentation/virt/kvm/api.txt +4
-0+ __u32 pad; + __u32 slot; /* as_id | slot_id */ + __u64 offset; +};offset of what? a 4K page right? Seems like a waste e.g. for hugetlbfs... How about replacing pad with size instead?
As Paolo explained, it's the page frame number of the guest. IIUC even for hugetlbfs we track dirty bits in 4k size.
Quoted 8 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5432
Quoted 5 lines
Documentation/virt/kvm/api.txt +4
-0+ +The fields in kvm_dirty_ring will be only internal to KVM itself, +while the fields in kvm_dirty_ring_indexes will be exposed to +userspace to be either read or written.I'm not sure what you are trying to say here.
kvm_dirty_gfnseems to be part of UAPI.
It was talking about kvm_dirty_ring, which is kvm internal and not exposed to uapi. While kvm_dirty_gfn is exposed to the users.
Quoted 14 lines
Quoted 14 lines
Documentation/virt/kvm/api.txt +13
-0+ +The two indices in the ring buffer are free running counters. + +In pseudocode, processing the ring buffer looks like this: + + idx = load-acquire(&ring->fetch_index); + while (idx != ring->avail_index) { + struct kvm_dirty_gfn *entry; + entry = &ring->dirty_gfns[idx & (size - 1)]; + ... + + idx++; + }
Quoted 8 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB
Quoted 6 lines
Documentation/virt/kvm/api.txt +6
-0+ ring->fetch_index = idx; + +Userspace calls KVM_ENABLE_CAP ioctl right after KVM_CREATE_VM ioctl +to enable this capability for the new guest and set the size of the +rings. It is only allowed before creating any vCPU, and the size of +the ring must be a power of two.All these seem like arbitrary limitations to me.
The dependency of vcpu is partly because we need to create per-vcpu ring, so it's easier that we don't allow it to change after that.
Quoted 5 lines
Sizing the ring correctly might prove to be a challenge.
Thus I think there's value in resizing the rings
without destroying VCPU.
Do you have an example on when we could use this feature? My wild guess is that even if we try hard to allow resizing (assuming that won't bring more bugs, but I hightly doubt...), people may not use it at all.
The major scenario here is that kvm userspace will be collecting the dirty bits quickly, so the ring should not really get full easily. Then the ring size does not really matter much either, as long as it is bigger than some specific value to avoid vmexits due to full.
How about we start with the simple that we don't allow it to change? We can do that when the requirement comes.
Quoted 4 lines
Also, power of two just saves a branch here and there, but wastes lots of memory. Just wrap the index around to 0 and then users can select any size?
Same as above to postpone until we need it?
Quoted 51 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5454
Quoted 7 lines
Documentation/virt/kvm/api.txt +3
-0The larger the ring buffer, the less +likely the ring is full and the VM is forced to exit to userspace. The +optimal size depends on the workload, but it is recommended that it be +at least 64 KiB (4096 entries).OTOH larger buffers put lots of pressure on the system cache.
Quoted 25 lines
Documentation/virt/kvm/api.txt +25
-0+ +After the capability is enabled, userspace can mmap the global ring +buffer (kvm_dirty_gfn[], offset KVM_DIRTY_LOG_PAGE_OFFSET) and the +indexes (kvm_dirty_ring_indexes, offset 0) from the VM file +descriptor. The per-vcpu dirty ring instead is mmapped when the vcpu +is created, similar to the kvm_run struct (kvm_dirty_ring_indexes +locates inside kvm_run, while kvm_dirty_gfn[] at offset +KVM_DIRTY_LOG_PAGE_OFFSET). + +Just like for dirty page bitmaps, the buffer tracks writes to +all user memory regions for which the KVM_MEM_LOG_DIRTY_PAGES flag was +set in KVM_SET_USER_MEMORY_REGION. Once a memory region is registered +with the flag set, userspace can start harvesting dirty pages from the +ring buffer. + +To harvest the dirty pages, userspace accesses the mmaped ring buffer +to read the dirty GFNs up to avail_index, and sets the fetch_index +accordingly. This can be done when the guest is running or paused, +and dirty pages need not be collected all at once. After processing +one or more entries in the ring buffer, userspace calls the VM ioctl +KVM_RESET_DIRTY_RINGS to notify the kernel that it has updated +fetch_index and to mark those pages clean. Therefore, the ioctl +must be called *before* reading the content of the dirty pages. + +However, there is a major difference comparing to theDocumentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5489
Quoted 10 lines
Documentation/virt/kvm/api.txt +10
-0+KVM_GET_DIRTY_LOG interface in that when reading the dirty ring from +userspace it's still possible that the kernel has not yet flushed the +hardware dirty buffers into the kernel buffer. To achieve that, one +needs to kick the vcpu out for a hardware buffer flush (vmexit). + +If one of the ring buffers is full, the guest will exit to userspace +with the exit reason set to KVM_EXIT_DIRTY_LOG_FULL, and the +KVM_RUN ioctl will return -EINTR. Once that happens, userspace +should pause all the vcpus, then harvest all the dirty pages and +rearm the dirty traps. It can unpause the guest after that.This last item means that the performance impact of the feature is really hard to predict. Can improve some workloads drastically. Or can slow some down.
Quoted 4 lines
One solution could be to actually allow using this together with the existing bitmap. Userspace can then decide whether it wants to block VCPU on ring full, or just record ring full condition and recover by bitmap scanning.
That's true, but again allowing mixture use of the two might bring extra complexity as well (especially when after adding KVM_CLEAR_DIRTY_LOG).
My understanding of this is that normally we do only want either one of them depending on the major workload and the configuration of the guest. It's not trivial to try to provide a one-for-all solution. So again I would hope we can start from easy, then we extend when we have better ideas on how to leverage the two interfaces when the ideas really come, and then we can justify whether it's worth it to work on that complexity.
Quoted 150 lines
Quoted 150 lines
arch/x86/kvm/Makefile +2
-1@@ -5,7 +5,8 @@ ccflags-y += -Iarch/x86/kvm KVM := ../../../virt/kvm kvm-y += $(KVM)/kvm_main.o $(KVM)/coalesced_mmio.o \ - $(KVM)/eventfd.o $(KVM)/irqchip.o $(KVM)/vfio.o + $(KVM)/eventfd.o $(KVM)/irqchip.o $(KVM)/vfio.o \ + $(KVM)/dirty_ring.o kvm-$(CONFIG_KVM_ASYNC_PF) += $(KVM)/async_pf.o kvm-y += x86.o emulate.o i8259.o irq.o lapic.o \include/linux/kvm_dirty_ring.h new file +67
-0@@ -0,0 +1,67 @@ +#ifndef KVM_DIRTY_RING_H +#define KVM_DIRTY_RING_H + +/* +* struct kvm_dirty_ring is defined in include/uapi/linux/kvm.h. +* +* dirty_ring: shared with userspace via mmap. It is the compact list +* that holds the dirty pages. +* dirty_index: free running counter that points to the next slot in +* dirty_ring->dirty_gfns where a new dirty page should go. +* reset_index: free running counter that points to the next dirty page +* in dirty_ring->dirty_gfns for which dirty trap needs to +* be reenabled +* size: size of the compact list, dirty_ring->dirty_gfns +* soft_limit: when the number of dirty pages in the list reaches this +* limit, vcpu that owns this ring should exit to userspace +* to allow userspace to harvest all the dirty pages +* lock: protects dirty_ring, only in use if this is the global +* ring +* +* The number of dirty pages in the ring is calculated by, +* dirty_index - reset_index +* +* kernel increments dirty_ring->indices.avail_index after dirty index +* is incremented. When userspace harvests the dirty pages, it increments +* dirty_ring->indices.fetch_index up to dirty_ring->indices.avail_index. +* When kernel reenables dirty traps for the dirty pages, it increments +* reset_index up to dirty_ring->indices.fetch_index. +* +*/ +struct kvm_dirty_ring { +u32 dirty_index; +u32 reset_index; +u32 size; +u32 soft_limit; +spinlock_t lock; +struct kvm_dirty_gfn *dirty_gfns; +}; + +u32 kvm_dirty_ring_get_rsvd_entries (void ); +int kvm_dirty_ring_alloc (struct kvm *kvm,struct kvm_dirty_ring *ring); + +/* +* called with kvm->slots_lock held, returns the number of +* processed pages. +*/ +int kvm_dirty_ring_reset (struct kvm *kvm, +struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes); + +/* +* returns 0: successfully pushed +* 1: successfully pushed, soft limit reached, +* vcpu should exit to userspace +* -EBUSY: unable to push, dirty ring full. +*/ +int kvm_dirty_ring_push (struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes, +u32 slot,u64 offset,bool lock); + +/* for use in vm_operations_struct */ +struct page *kvm_dirty_ring_get_page (struct kvm_dirty_ring *ring,u32 i); + +void kvm_dirty_ring_free (struct kvm_dirty_ring *ring); +bool kvm_dirty_ring_full (struct kvm_dirty_ring *ring); + +#endif include/linux/kvm_host.h +18
-0@@ -34,6 +34,7 @@#include <linux/kvm_types.h> #include <asm/kvm_host.h> +#include <linux/kvm_dirty_ring.h> #ifndef KVM_MAX_VCPU_ID #define KVM_MAX_VCPU_ID KVM_MAX_VCPUS@@ -146,6 +147,7 @@ static inline bool is_error_page(struct page *page)#define KVM_REQ_MMU_RELOAD (1 | KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)#define KVM_REQ_PENDING_TIMER 2#define KVM_REQ_UNHALT 3 +#define KVM_REQ_DIRTY_RING_FULL 4#define KVM_REQUEST_ARCH_BASE 8#define KVM_ARCH_REQ_FLAGS (nr, flags) ({ \@@ -321,6 +323,7 @@ struct kvm_vcpu {bool ready;struct kvm_vcpu_arch arch;struct dentry *debugfs_dentry; +struct kvm_dirty_ring dirty_ring; };static inline int kvm_vcpu_exiting_guest_mode (struct kvm_vcpu *vcpu)@@ -501,6 +504,10 @@ struct kvm {struct srcu_struct srcu;struct srcu_struct irq_srcu;pid_t userspace_pid; +/* Data structure to be exported by mmap(kvm->fd, 0) */ +struct kvm_vm_run *vm_run; +u32 dirty_ring_size; +struct kvm_dirty_ring vm_dirty_ring; };#define kvm_err (fmt, ...) \@@ -832,6 +839,8 @@ void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,gfn_t gfn_offset, unsigned long mask); +void kvm_reset_dirty_gfn (struct kvm *kvm,u32 slot,u64 offset,u64 mask); +int kvm_vm_ioctl_get_dirty_log (struct kvm *kvm,struct kvm_dirty_log *log); int kvm_vm_ioctl_clear_dirty_log(struct kvm *kvm,@@ -1411,4 +1420,28 @@ int kvm_vm_create_worker_thread(struct kvm *kvm, kvm_vm_thread_fn_t thread_fn, uintptr_t data, const char *name, struct task_struct **thread_ptr); +/* +* This defines how many reserved entries we want to keep before we +* kick the vcpu to the userspace to avoid dirty ring full. This +* value can be tuned to higher if e.g. PML is enabled on the host. +*/ +#define KVM_DIRTY_RING_RSVD_ENTRIES 64 + +/* Max number of entries allowed for each kvm dirty ring */ +#define KVM_DIRTY_RING_MAX_ENTRIES 65536
Quoted 8 lines
include/linux/kvm_host.h int kvm_vm_create_worker_thread(struct kvm *kvm, kvm_vm_thread_fn_t thread_fn,
Quoted 5 lines
include/linux/kvm_host.h +5
-0+ +/* +* Arch needs to define these macro after implementing the dirty ring +* feature. KVM_DIRTY_LOG_PAGE_OFFSET should be defined as the +* starting page offset of the dirty ring structures, Confused. Offset where? You set a default for everyone - where does arch want to override it?
If arch defines KVM_DIRTY_LOG_PAGE_OFFSET then below will be a no-op, please see [1] on #ifndef.
Quoted 6 lines
Quoted 6 lines
while
+ *KVM_DIRTY_RING_VERSIONshould be defined as >=1. By default, this
+ * feature is off on all archs.
+ */
+#ifndefKVM_DIRTY_LOG_PAGE_OFFSET
[1]
Quoted 10 lines
include/linux/kvm_host.h int kvm_vm_create_worker_thread(struct kvm *kvm, kvm_vm_thread_fn_t thread_fn, · line 1445
Quoted 5 lines
include/linux/kvm_host.h +5
-0+#define KVM_DIRTY_LOG_PAGE_OFFSET 0 +#endif +#ifndef KVM_DIRTY_RING_VERSION +#define KVM_DIRTY_RING_VERSION 0 +#endif One way versioning, with no bits and negotiation will make it hard to change down the road.
what's wrong with existing KVM capabilities that you feel there's a need for dedicated versioning for this?
Frankly speaking I don't even think it'll change in the near future.. :)
Yeh kvm versioning could work too. Here we can also return a zero just like the most of the caps (or KVM_DIRTY_LOG_PAGE_OFFSET as in the original patchset, but it's really helpless either because it's defined in uapi), but I just don't see how it helps... So I returned a version number just in case we'd like to change the layout some day and when we don't want to bother introducing another cap bit for the same feature (like KVM_CAP_MANUAL_DIRTY_LOG_PROTECT and KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2).
Quoted 112 lines
Quoted 112 lines
include/linux/kvm_host.h +1
-0+#endif include/linux/kvm_types.h +1
-0@@ -11,6 +11,7 @@ struct kvm_irq_routing_table;struct kvm_memory_slot ;struct kvm_one_reg ;struct kvm_run ; +struct kvm_vm_run ;struct kvm_userspace_memory_region ;struct kvm_vcpu ;struct kvm_vcpu_init ;include/uapi/linux/kvm.h +36
-0@@ -236,6 +236,7 @@ struct kvm_hyperv_exit {#define KVM_EXIT_IOAPIC_EOI 26#define KVM_EXIT_HYPERV 27#define KVM_EXIT_ARM_NISV 28 +#define KVM_EXIT_DIRTY_RING_FULL 29/* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */ @@ -247,6 +248,11 @@ struct kvm_hyperv_exit {/* Encounter unexpected vm-exit reason */ #define KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON 4 +struct kvm_dirty_ring_indexes { +__u32 avail_index;/* set by kernel */ +__u32 fetch_index;/* set by userspace */ +}; +/* for KVM_RUN, returned by mmap(vcpu_fd, offset=0) */ struct kvm_run {/* in */ @@ -421,6 +427,13 @@ struct kvm_run {struct kvm_sync_regs regs;char padding[SYNC_REGS_SIZE_BYTES ]; } s; + +struct kvm_dirty_ring_indexes vcpu_ring_indexes; +}; + +/* Returned by mmap(kvm->fd, offset=0) */ +struct kvm_vm_run { +struct kvm_dirty_ring_indexes vm_ring_indexes; };/* for KVM_REGISTER_COALESCED_MMIO / KVM_UNREGISTER_COALESCED_MMIO */ @@ -1009,6 +1022,7 @@ struct kvm_ppc_resize_hpt {#define KVM_CAP_PPC_GUEST_DEBUG_SSTEP 176#define KVM_CAP_ARM_NISV_TO_USER 177#define KVM_CAP_ARM_INJECT_EXT_DABT 178 +#define KVM_CAP_DIRTY_LOG_RING 179#ifdef KVM_CAP_IRQ_ROUTING @@ -1472,6 +1486,9 @@ struct kvm_enc_region {/* Available with KVM_CAP_ARM_SVE */ #define KVM_ARM_VCPU_FINALIZE _IOW(KVMIO, 0xc2, int) +/* Available with KVM_CAP_DIRTY_LOG_RING */ +#define KVM_RESET_DIRTY_RINGS _IO(KVMIO, 0xc3) +/* Secure Encrypted Virtualization command */ enum sev_cmd_id {/* Guest initialization commands */ @@ -1622,4 +1639,23 @@ struct kvm_hyperv_eventfd {#define KVM_HYPERV_CONN_ID_MASK 0x00ffffff#define KVM_HYPERV_EVENTFD_DEASSIGN (1 << 0) +/* +* The following are the requirements for supporting dirty log ring +* (by enabling KVM_DIRTY_LOG_PAGE_OFFSET). +* +* 1. Memory accesses by KVM should call kvm_vcpu_write_* instead +* of kvm_write_* so that the global dirty ring is not filled up +* too quickly. +* 2. kvm_arch_mmu_enable_log_dirty_pt_masked should be defined for +* enabling dirty logging. +* 3. There should not be a separate step to synchronize hardware +* dirty bitmap with KVM's. +*/ + +struct kvm_dirty_gfn { +__u32 pad; +__u32 slot; +__u64 offset; +}; +#endif /* __LINUX_KVM_H */ virt/kvm/dirty_ring.c new file +11
-0@@ -0,0 +1,156 @@ +#include <linux/kvm_host.h> +#include <linux/kvm.h> +#include <linux/vmalloc.h> +#include <linux/kvm_dirty_ring.h> + +u32 kvm_dirty_ring_get_rsvd_entries (void ) +{ +return KVM_DIRTY_RING_RSVD_ENTRIES +kvm_cpu_dirty_log_size (); +} + +int kvm_dirty_ring_alloc (struct kvm *kvm,struct kvm_dirty_ring *ring)
Quoted 8 lines
virt/kvm/dirty_ring.c line 15
Quoted 4 lines
virt/kvm/dirty_ring.c new file +4
-0+{ +u32 size = kvm->dirty_ring_size; + + ring->dirty_gfns =vmalloc (size);So 1/2 a megabyte of kernel memory per VM that userspace locks up. Do we really have to though? Why not get a userspace pointer, write it with copy to user, and sidestep all this?
I'd say it won't be a big issue on locking 1/2M of host mem for a vm...
Also note that if dirty ring is enabled, I plan to evaporate the dirty_bitmap in the next post. The old kvm->dirty_bitmap takes $GUEST_MEM/32K*2 mem. E.g., for 64G guest it's 64G/32K*2=4M. If with dirty ring of 8 vcpus, that could be 64K*8=0.5M, which could be even less memory used.
Quoted 35 lines
Quoted 35 lines
virt/kvm/dirty_ring.c new file +34
-0+if (!ring->dirty_gfns) +return -ENOMEM ; +memset (ring->dirty_gfns,0 , size); + + ring->size = size /sizeof (struct kvm_dirty_gfn ); + ring->soft_limit = + (kvm->dirty_ring_size /sizeof (struct kvm_dirty_gfn )) - +kvm_dirty_ring_get_rsvd_entries (); + ring->dirty_index =0 ; + ring->reset_index =0 ; +spin_lock_init (&ring->lock); + +return 0 ; +} + +int kvm_dirty_ring_reset (struct kvm *kvm, +struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes) +{ +u32 cur_slot, next_slot; +u64 cur_offset, next_offset; +unsigned long mask; +u32 fetch; +int count =0 ; +struct kvm_dirty_gfn *entry; + + fetch =READ_ONCE (indexes->fetch_index); +if (fetch == ring->reset_index) +return 0 ; + + entry = &ring->dirty_gfns[ring->reset_index & (ring->size -1 )]; +/* +* The ring buffer is shared with userspace, which might mmap +* it and concurrently modify slot and offset. Userspace must
Quoted 8 lines
virt/kvm/dirty_ring.c line 52
Quoted 3 lines
virt/kvm/dirty_ring.c new file +3
-0+* not be trusted! READ_ONCE prevents the compiler from changing +* the values after they've been range-checked (the checks are +* in kvm_reset_dirty_gfn). What it doesn't is prevent speculative attacks. That's why things like copy from user have a speculation barrier. Instead of worrying about that, unless it's really critical, I think you'd do well do just use copy to/from user.
IMHO I would really hope these data be there without swapped out of memory, just like what we did with kvm->dirty_bitmap... it's on the hot path of mmu page fault, even we could be with mmu lock held if copy_to_user() page faulted. But indeed I've no experience on avoiding speculative attacks, suggestions would be greatly welcomed on that. In our case we do (index & (size - 1)), so is it still suffering from speculative attacks?
Quoted 5 lines
virt/kvm/dirty_ring.c line 54
Quoted 3 lines
virt/kvm/dirty_ring.c new file +2
-0+*/ +smp_read_barrier_depends ();What depends on what here? Looks suspicious ...
Hmm, I think maybe it can be removed because the entry pointer reference below should be an ordering constraint already?
Quoted 12 lines
virt/kvm/dirty_ring.c line 62
Quoted 9 lines
virt/kvm/dirty_ring.c new file +8
-0+ cur_slot =READ_ONCE (entry->slot); + cur_offset =READ_ONCE (entry->offset); + mask =1 ; + count++; + ring->reset_index++; +while (ring->reset_index != fetch) { + entry = &ring->dirty_gfns[ring->reset_index & (ring->size -1 )]; +smp_read_barrier_depends ();same concerns here
Quoted 9 lines
virt/kvm/dirty_ring.c line 69
Quoted 7 lines
virt/kvm/dirty_ring.c new file +7
-0+ next_slot =READ_ONCE (entry->slot); + next_offset =READ_ONCE (entry->offset); + ring->reset_index++; + count++; +/* +* Try to coalesce the reset operations when the guest is +* scanning pages in the same slot. what does guest scanning mean?
My wild guess is that it means when the guest is accessing the pages continuously so the dirty gfns are continuous too. Anyway I agree it's not clear, where I can try to rephrase.
Quoted 48 lines
Quoted 48 lines
virt/kvm/dirty_ring.c new file +47
-0+*/ +if (next_slot == cur_slot) { +int delta = next_offset - cur_offset; + +if (delta >=0 && delta <BITS_PER_LONG ) { + mask |=1ull << delta; +continue ; + } + +/* Backwards visit, careful about overflows! */ +if (delta > -BITS_PER_LONG && delta <0 && + (mask << -delta >> -delta) == mask) { + cur_offset = next_offset; + mask = (mask << -delta) |1 ; +continue ; + } + } +kvm_reset_dirty_gfn (kvm, cur_slot, cur_offset, mask); + cur_slot = next_slot; + cur_offset = next_offset; + mask =1 ; + } +kvm_reset_dirty_gfn (kvm, cur_slot, cur_offset, mask); + +return count; +} + +static inline u32 kvm_dirty_ring_used (struct kvm_dirty_ring *ring) +{ +return ring->dirty_index - ring->reset_index; +} + +bool kvm_dirty_ring_full (struct kvm_dirty_ring *ring) +{ +return kvm_dirty_ring_used (ring) >= ring->size; +} + +/* +* Returns: +* >0 if we should kick the vcpu out, +* =0 if the gfn pushed successfully, or, +* <0 if error (e.g. ring full) +*/ +int kvm_dirty_ring_push (struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes, +u32 slot,u64 offset,bool lock) +{
Quoted 8 lines
virt/kvm/dirty_ring.c line 121
Quoted 5 lines
virt/kvm/dirty_ring.c new file +5
-0+int ret; +struct kvm_dirty_gfn *entry; + +if (lock) +spin_lock (&ring->lock);what's the story around locking here? Why is it safe not to take the lock sometimes?
kvm_dirty_ring_push() will be with lock==true only when the per-vm ring is used. For per-vcpu ring, because that will only happen with the vcpu context, then we don't need locks (so kvm_dirty_ring_push() is called with lock==false).
Quoted 105 lines
Quoted 105 lines
virt/kvm/dirty_ring.c new file +35
-0+ +if (kvm_dirty_ring_full (ring)) { + ret = -EBUSY ; + goto out; + } + + entry = &ring->dirty_gfns[ring->dirty_index & (ring->size -1 )]; + entry->slot = slot; + entry->offset = offset; +smp_wmb (); + ring->dirty_index++; +WRITE_ONCE (indexes->avail_index, ring->dirty_index); + ret =kvm_dirty_ring_used (ring) >= ring->soft_limit; +pr_info ("%s: slot %u offset %llu used %u\n" , + __func__, slot, offset,kvm_dirty_ring_used (ring)); + +out: +if (lock) +spin_unlock (&ring->lock); + +return ret; +} + +struct page *kvm_dirty_ring_get_page (struct kvm_dirty_ring *ring,u32 i) +{ +return vmalloc_to_page ((void *)ring->dirty_gfns + i *PAGE_SIZE ); +} + +void kvm_dirty_ring_free (struct kvm_dirty_ring *ring) +{ +if (ring->dirty_gfns) { +vfree (ring->dirty_gfns); + ring->dirty_gfns =NULL ; + } +}virt/kvm/kvm_main.c +21
-1@@ -64,6 +64,8 @@#define CREATE_TRACE_POINTS #include <trace/events/kvm.h> +#include <linux/kvm_dirty_ring.h> +/* Worst case buffer size needed for holding an integer. */ #define ITOA_MAX_LEN 12@@ -149,6 +151,10 @@ static void mark_page_dirty_in_slot(struct kvm *kvm,struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, gfn_t gfn); +static void mark_page_dirty_in_ring (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *slot, +gfn_t gfn); __visible bool kvm_rebooting;EXPORT_SYMBOL_GPL (kvm_rebooting);@@ -359,11 +365,22 @@ int kvm_vcpu_init(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned id) vcpu->preempted = false; vcpu->ready = false; +if (kvm->dirty_ring_size) { + r =kvm_dirty_ring_alloc (vcpu->kvm, &vcpu->dirty_ring); +if (r) { + kvm->dirty_ring_size =0 ; + goto fail_free_run; + } + } + r =kvm_arch_vcpu_init (vcpu);if (r <0 ) - goto fail_free_run; + goto fail_free_ring;return 0 ; +fail_free_ring: +if (kvm->dirty_ring_size) +kvm_dirty_ring_free (&vcpu->dirty_ring); fail_free_run:free_page ((unsigned long )vcpu->run); fail:@@ -381,6 +398,8 @@ void kvm_vcpu_uninit(struct kvm_vcpu *vcpu)put_pid (rcu_dereference_protected (vcpu->pid,1 ));kvm_arch_vcpu_uninit (vcpu);free_page ((unsigned long )vcpu->run); +if (vcpu->kvm->dirty_ring_size) +kvm_dirty_ring_free (&vcpu->dirty_ring); }EXPORT_SYMBOL_GPL (kvm_vcpu_uninit);@@ -690,6 +709,7 @@ static struct kvm *kvm_create_vm(unsigned long type)struct kvm *kvm =kvm_arch_alloc_vm ();int r = -ENOMEM ;int i; +struct page *page;if (!kvm)return ERR_PTR (-ENOMEM );@@ -705,6 +725,14 @@ static struct kvm *kvm_create_vm(unsigned long type)BUILD_BUG_ON (KVM_MEM_SLOTS_NUM >SHRT_MAX );
Quoted 9 lines
virt/kvm/kvm_main.c static struct kvm *kvm_create_vm(unsigned long type) · line 733
Quoted 6 lines
virt/kvm/kvm_main.c +6
-0+ page =alloc_page (GFP_KERNEL | __GFP_ZERO); +if (!page) { + r = -ENOMEM ; + goto out_err_alloc_page; + } + kvm->vm_run =page_address (page);So 4K with just 8 bytes used. Not as bad as 1/2Mbyte for the ring but still. What is wrong with just a pointer and calling
put_user?
I want to make it the start point for sharing fields between user/kernel per-vm. Just like kvm_run for per-vcpu.
IMHO it'll be awkward if we always introduce a new interface just to take a pointer of the userspace buffer and cache it... I'd say so far I like the design of kvm_run and alike because it's efficient, easy to use, and easy for extensions.
Quoted 106 lines
Quoted 106 lines
virt/kvm/kvm_main.c +56
-1+BUILD_BUG_ON (sizeof (struct kvm_vm_run ) >PAGE_SIZE ); +if (init_srcu_struct (&kvm->srcu)) goto out_err_no_srcu;if (init_srcu_struct (&kvm->irq_srcu))@@ -775,6 +803,9 @@ static struct kvm *kvm_create_vm(unsigned long type) out_err_no_irq_srcu:cleanup_srcu_struct (&kvm->srcu); out_err_no_srcu: +free_page ((unsigned long )page); + kvm->vm_run =NULL ; +out_err_alloc_page:kvm_arch_free_vm (kvm);mmdrop (current->mm);return ERR_PTR (r);@@ -800,6 +831,15 @@ static void kvm_destroy_vm(struct kvm *kvm)int i;struct mm_struct *mm = kvm->mm; +if (kvm->dirty_ring_size) { +kvm_dirty_ring_free (&kvm->vm_dirty_ring); + } + +if (kvm->vm_run) { +free_page ((unsigned long )kvm->vm_run); + kvm->vm_run =NULL ; + } +kvm_uevent_notify_change (KVM_EVENT_DESTROY_VM , kvm);kvm_destroy_vm_debugfs (kvm);kvm_arch_sync_events (kvm);@@ -2301,7 +2341,7 @@ static void mark_page_dirty_in_slot(struct kvm *kvm, {if (memslot && memslot->dirty_bitmap) {unsigned long rel_gfn = gfn - memslot->base_gfn; - +mark_page_dirty_in_ring (kvm, vcpu, memslot, gfn);set_bit_le (rel_gfn, memslot->dirty_bitmap); } }@@ -2649,6 +2689,13 @@ void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode) }EXPORT_SYMBOL_GPL (kvm_vcpu_on_spin); +static bool kvm_fault_in_dirty_ring (struct kvm *kvm,struct vm_fault *vmf) +{ +return (vmf->pgoff >=KVM_DIRTY_LOG_PAGE_OFFSET ) && + (vmf->pgoff <KVM_DIRTY_LOG_PAGE_OFFSET + + kvm->dirty_ring_size /PAGE_SIZE ); +} +static vm_fault_t kvm_vcpu_fault (struct vm_fault *vmf) {struct kvm_vcpu *vcpu = vmf->vma->vm_file->private_data;@@ -2664,6 +2711,10 @@ static vm_fault_t kvm_vcpu_fault(struct vm_fault *vmf) else if (vmf->pgoff == KVM_COALESCED_MMIO_PAGE_OFFSET) page =virt_to_page (vcpu->kvm->coalesced_mmio_ring);#endif + else if (kvm_fault_in_dirty_ring(vcpu->kvm, vmf)) + page =kvm_dirty_ring_get_page ( + &vcpu->dirty_ring, + vmf->pgoff -KVM_DIRTY_LOG_PAGE_OFFSET ); else return kvm_arch_vcpu_fault(vcpu, vmf);get_page (page);@@ -3259,12 +3310,162 @@ static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg)#endif case KVM_CAP_NR_MEMSLOTS :return KVM_USER_MEM_SLOTS ; +case KVM_CAP_DIRTY_LOG_RING : +/* Version will be zero if arch didn't implement it */ +return KVM_DIRTY_RING_VERSION ;default :break ; }return kvm_vm_ioctl_check_extension (kvm, arg); } +static void mark_page_dirty_in_ring (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *slot, +gfn_t gfn) +{ +u32 as_id =0 ; +u64 offset; +int ret; +struct kvm_dirty_ring *ring; +struct kvm_dirty_ring_indexes *indexes; +bool is_vm_ring; + +if (!kvm->dirty_ring_size) +return ; + + offset = gfn - slot->base_gfn; + +if (vcpu) { + as_id =kvm_arch_vcpu_memslots_id (vcpu); + }else { + as_id =0 ; + vcpu =kvm_get_running_vcpu (); + } + +if (vcpu) { + ring = &vcpu->dirty_ring; + indexes = &vcpu->run->vcpu_ring_indexes;
Quoted 8 lines
virt/kvm/kvm_main.c static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) · line 3353
Quoted 5 lines
virt/kvm/kvm_main.c +5
-0+ is_vm_ring = false; + }else { +/* +* Put onto per vm ring because no vcpu context. Kick +* vcpu0 if ring is full. What about tasks on vcpu 0? Do guests realize it's a bad idea to put critical tasks there, they will be penalized disproportionally?
Reasonable question. So far we can't avoid it because vcpu exit is the event mechanism to say "hey please collect dirty bits". Maybe someway is better than this, but I'll need to rethink all these over...
Quoted 63 lines
Quoted 63 lines
virt/kvm/kvm_main.c +62
-0+*/ + vcpu = kvm->vcpus[0 ]; + ring = &kvm->vm_dirty_ring; + indexes = &kvm->vm_run->vm_ring_indexes; + is_vm_ring = true; + } + + ret =kvm_dirty_ring_push (ring, indexes, + (as_id <<16 )|slot->id, offset, + is_vm_ring); +if (ret <0 ) { +if (is_vm_ring) +pr_warn_once ("vcpu %d dirty log overflow\n" , + vcpu->vcpu_id); +else +pr_warn_once ("per-vm dirty log overflow\n" ); +return ; + } + +if (ret) +kvm_make_request (KVM_REQ_DIRTY_RING_FULL , vcpu); +} + +void kvm_reset_dirty_gfn (struct kvm *kvm,u32 slot,u64 offset,u64 mask) +{ +struct kvm_memory_slot *memslot; +int as_id, id; + + as_id = slot >>16 ; + id = (u16 )slot; +if (as_id >=KVM_ADDRESS_SPACE_NUM || id >=KVM_USER_MEM_SLOTS ) +return ; + + memslot =id_to_memslot (__kvm_memslots (kvm, as_id), id); +if (offset >= memslot->npages) +return ; + +spin_lock (&kvm->mmu_lock); +/* FIXME: we should use a single AND operation, but there is no +* applicable atomic API. +*/ +while (mask) { +clear_bit_le (offset +__ffs (mask), memslot->dirty_bitmap); + mask &= mask -1 ; + } + +kvm_arch_mmu_enable_log_dirty_pt_masked (kvm, memslot, offset, mask); +spin_unlock (&kvm->mmu_lock); +} + +static int kvm_vm_ioctl_enable_dirty_log_ring (struct kvm *kvm,u32 size) +{ +int r; + +/* the size should be power of 2 */ +if (!size || (size & (size -1 ))) +return -EINVAL ; + +/* Should be bigger to keep the reserved entries, or a page */ +if (size <kvm_dirty_ring_get_rsvd_entries () * +sizeof (struct kvm_dirty_gfn ) || size <PAGE_SIZE ) +return -EINVAL ;
Quoted 8 lines
virt/kvm/kvm_main.c static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) · line 3419
Quoted 4 lines
virt/kvm/kvm_main.c +4
-0+ +if (size >KVM_DIRTY_RING_MAX_ENTRIES * +sizeof (struct kvm_dirty_gfn )) +return -E2BIG ;
KVM_DIRTY_RING_MAX_ENTRIESis not part of UAPI.
So how does userspace know what's legal?
Do you expect it to just try?
Yep that's what I thought. :)
Please grep E2BIG in QEMU repo target/i386/kvm.c... won't be hard to do imho..
Quoted 2 lines
More likely it will just copy the number from kernel and can never ever make it smaller.
Not sure, but for sure I can probably move KVM_DIRTY_RING_MAX_ENTRIES to uapi too.
Thanks,
Signature · 1 line
Peter Xu
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 255 lines · Peter Xu
Quoted 55 lines · Michael S. Tsirkin
Quoted 54 lines · Peter Xu
This patch is heavily based on previous work from Lei Cao <[email protected]> and Paolo Bonzini <[email protected]>. [1]
KVM currently uses large bitmaps to track dirty memory. These bitmaps are copied to userspace when userspace queries KVM for its dirty page information. The use of bitmaps is mostly sufficient for live migration, as large parts of memory are be dirtied from one log-dirty pass to another. However, in a checkpointing system, the number of dirty pages is small and in fact it is often bounded---the VM is paused when it has dirtied a pre-defined number of pages. Traversing a large, sparsely populated bitmap to find set bits is time-consuming, as is copying the bitmap to user-space.
A similar issue will be there for live migration when the guest memory is huge while the page dirty procedure is trivial. In that case for each dirty sync we need to pull the whole dirty bitmap to userspace and analyse every bit even if it's mostly zeros.
The preferred data structure for above scenarios is a dense list of guest frame numbers (GFN). This patch series stores the dirty list in kernel memory that can be memory mapped into userspace to allow speedy harvesting.
We defined two new data structures:
struct
kvm_dirty_ring;
structkvm_dirty_ring_indexes;Firstly,
kvm_dirty_ringis defined to represent a ring of dirty pages. When dirty tracking is enabled, we can push dirty gfn onto the ring.Secondly,
kvm_dirty_ring_indexesis defined to represent the user/kernel interface of each ring. Currently it contains two
indexes: (1)avail_indexrepresents where we should push our next PFN (written by kernel), while (2)fetch_indexrepresents where the userspace should fetch the next dirty PFN (written by userspace).One complete ring is composed by one
kvm_dirty_ringplus its correspondingkvm_dirty_ring_indexes.Currently, we have N+1 rings for each VM of N vcpus:
- for each vcpu, we have 1 per-vcpu dirty ring,
- for each vm, we have 1 per-vm dirty ringPlease refer to the documentation update in this patch for more details.
Note that this patch implements the core logic of dirty ring buffer. It's still disabled for all archs for now. Also, we'll address some of the other issues in follow up patches before it's firstly enabled on x86.
Quoted 8 lines
Quoted 5 lines
[1] https://patchwork.kernel.org/patch/10471409/
Signed-off-by Lei Cao <[email protected]>Signed-off-by Paolo Bonzini <[email protected]>Signed-off-by Peter Xu <[email protected]>Thanks, that's interesting.
Hi, Michael,
Thanks for reading the series.
Quoted 43 lines
Quoted 43 lines
--- Documentation/virt/kvm/api.txt | 109 +++++++++++++++ arch/x86/kvm/Makefile | 3 +- include/linux/kvm_dirty_ring.h | 67 +++++++++ include/linux/kvm_host.h | 33 +++++ include/linux/kvm_types.h | 1 + include/uapi/linux/kvm.h | 36 +++++ virt/kvm/dirty_ring.c | 156 +++++++++++++++++++++ virt/kvm/kvm_main.c | 240 ++++++++++++++++++++++++++++++++- 8 files changed, 642 insertions(+), 3 deletions(-) create mode 100644 include/linux/kvm_dirty_ring.h create mode 100644 virt/kvm/dirty_ring.cDocumentation/virt/kvm/api.txt +13
-0@@ -231,6 +231,7 @@ Based on their initialization different VMs may have different capabilities. It is thus encouraged to use the vm ioctl to query for capabilities (available with KVM_CAP_CHECK_EXTENSION_VM on the vm fd) + 4.5 KVM_GET_VCPU_MMAP_SIZE Capability: basic@@ -243,6 +244,18 @@ The KVM_RUN ioctl (cf.) communicates with userspace via a shared memory region. This ioctl returns the size of that region. See the KVM_RUN documentation for details. +Besides the size of the KVM_RUN communication region, other areas of +the VCPU file descriptor can be mmap-ed, including: + +- if KVM_CAP_COALESCED_MMIO is available, a page at + KVM_COALESCED_MMIO_PAGE_OFFSET * PAGE_SIZE; for historical reasons, + this page is included in the result of KVM_GET_VCPU_MMAP_SIZE. + KVM_CAP_COALESCED_MMIO is not documented yet. + +- if KVM_CAP_DIRTY_LOG_RING is available, a number of pages at + KVM_DIRTY_LOG_PAGE_OFFSET * PAGE_SIZE. For more information on + KVM_CAP_DIRTY_LOG_RING, see section 8.3. +Quoted 5 lines
Documentation/virt/kvm/api.txt The KVM_RUN ioctl (cf.) communicates with userspace via a shared · line 261
Quoted 2 lines
Documentation/virt/kvm/api.txt +0
-04.6 KVM_SET_MEMORY_REGION
PAGE_SIZEbeing which value? It's not always trivial for userspace to know what's thePAGE_SIZEfor the kernel ...I thought it can be easily fetched from
getpagesize()orsysconf(PAGE_SIZE)? Especially considering that the document should be for kvm userspace, I'd say it should be common that a hypervisor process will need to know this probably in other tons of places.. no?Quoted 49 lines
Quoted 36 lines
Documentation/virt/kvm/api.txt +16
-0@@ -5358,6 +5371,7 @@ CPU when the exception is taken. If this virtual SError is taken to EL1 using AArch64, this value will be reported in the ISS field of ESR_ELx. See KVM_CAP_VCPU_EVENTS for more details. + 8.20 KVM_CAP_HYPERV_SEND_IPI Architectures: x86@@ -5365,6 +5379,7 @@ Architectures: x86 This capability indicates that KVM supports paravirtualized Hyper-V IPI send hypercalls: HvCallSendSyntheticClusterIpi, HvCallSendSyntheticClusterIpiEx. + 8.21 KVM_CAP_HYPERV_DIRECT_TLBFLUSH Architecture: x86@@ -5378,3 +5393,97 @@ handling by KVM (as some KVM hypercall may be mistakenly treated as TLB flush hypercalls by Hyper-V) so userspace should disable KVM identification in CPUID and only exposes Hyper-V identification. In this case, guest thinks it's running on Hyper-V and only use Hyper-V hypercalls. + +8.22 KVM_CAP_DIRTY_LOG_RING + +Architectures: x86 +Parameters: args[0] - size of the dirty log ring + +KVM is capable of tracking dirty memory using ring buffers that are +mmaped into userspace; there is one dirty ring per vcpu and one global +ring per vm. + +One dirty ring has the following two major structures: + +struct kvm_dirty_ring { + u16 dirty_index;Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5419
Quoted 10 lines
Documentation/virt/kvm/api.txt +10
-0+ u16 reset_index; + u32 size; + u32 soft_limit; + spinlock_t lock; + struct kvm_dirty_gfn *dirty_gfns; +}; + +struct kvm_dirty_ring_indexes { + __u32 avail_index; /* set by kernel */ + __u32 fetch_index; /* set by userspace */Sticking these next to each other seems to guarantee cache conflicts.
Quoted 8 lines
Avail/Fetch seems to mimic Virtio's avail/used exactly. I am not saying you must reuse the code really, but I think you should take a hard look at e.g. the virtio packed ring structure. We spent a bunch of time optimizing it for cache utilization. It seems kernel is the driver, making entries available, and userspace the device, using them. Again let's not develop a thread about this, but I think this is something to consider and discuss in future versions of the patches.
I think I completely understand your concern. We should avoid wasting time on those are already there. I'm just afraid that it'll took even more time to use virtio for this use case while at last we don't really get much benefit out of it (e.g. most of the virtio features are not used).
Yeh let's not develop a thread for this topic - I will read more on virtio before my next post to see whether there's any chance we can share anything with virtio ring.
Quoted 9 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5424
Quoted 7 lines
Documentation/virt/kvm/api.txt +5
-0+}; + +While for each of the dirty entry it's defined as: + +struct kvm_dirty_gfn {What does GFN stand for?
It's guest frame number, iiuc. I'm not the one who named this, but that's what I understand..
Quoted 8 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5428
Quoted 5 lines
Documentation/virt/kvm/api.txt +4
-0+ __u32 pad; + __u32 slot; /* as_id | slot_id */ + __u64 offset; +};offset of what? a 4K page right? Seems like a waste e.g. for hugetlbfs... How about replacing pad with size instead?
As Paolo explained, it's the page frame number of the guest. IIUC even for hugetlbfs we track dirty bits in 4k size.
Quoted 8 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5432
Quoted 5 lines
Documentation/virt/kvm/api.txt +4
-0+ +The fields in kvm_dirty_ring will be only internal to KVM itself, +while the fields in kvm_dirty_ring_indexes will be exposed to +userspace to be either read or written.I'm not sure what you are trying to say here.
kvm_dirty_gfnseems to be part of UAPI.It was talking about
kvm_dirty_ring, which is kvm internal and not exposed to uapi. Whilekvm_dirty_gfnis exposed to the users.Quoted 14 lines
Quoted 14 lines
Documentation/virt/kvm/api.txt +13
-0+ +The two indices in the ring buffer are free running counters. + +In pseudocode, processing the ring buffer looks like this: + + idx = load-acquire(&ring->fetch_index); + while (idx != ring->avail_index) { + struct kvm_dirty_gfn *entry; + entry = &ring->dirty_gfns[idx & (size - 1)]; + ... + + idx++; + }Quoted 8 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB
Quoted 6 lines
Documentation/virt/kvm/api.txt +6
-0+ ring->fetch_index = idx; + +Userspace calls KVM_ENABLE_CAP ioctl right after KVM_CREATE_VM ioctl +to enable this capability for the new guest and set the size of the +rings. It is only allowed before creating any vCPU, and the size of +the ring must be a power of two.All these seem like arbitrary limitations to me.
The dependency of vcpu is partly because we need to create per-vcpu ring, so it's easier that we don't allow it to change after that.
Quoted 1 line
Quoted 6 lines
Quoted 4 lines
Sizing the ring correctly might prove to be a challenge.
Thus I think there's value in resizing the rings
without destroying VCPU.Do you have an example on when we could use this feature?
So e.g. start with a small ring, and if you see stalls too often increase it? Otherwise I don't see how does one decide on ring size.
Quoted 9 lines
My wild
guess is that even if we try hard to allow resizing (assuming that won't bring more bugs, but I hightly doubt...), people may not use it at all.The major scenario here is that kvm userspace will be collecting the dirty bits quickly, so the ring should not really get full easily. Then the ring size does not really matter much either, as long as it is bigger than some specific value to avoid vmexits due to full.
Exactly but I don't see how you are going to find that value unless it's auto-tuning dynamically.
Quoted 9 lines
How about we start with the simple that we don't allow it to change? We can do that when the requirement comes.
Quoted 4 lines
Also, power of two just saves a branch here and there, but wastes lots of memory. Just wrap the index around to 0 and then users can select any size?
Same as above to postpone until we need it?
It's to save memory, don't we always need to do that?
Quoted 56 lines
Quoted 51 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5454
Quoted 7 lines
Documentation/virt/kvm/api.txt +3
-0The larger the ring buffer, the less +likely the ring is full and the VM is forced to exit to userspace. The +optimal size depends on the workload, but it is recommended that it be +at least 64 KiB (4096 entries).OTOH larger buffers put lots of pressure on the system cache.
Quoted 25 lines
Documentation/virt/kvm/api.txt +25
-0+ +After the capability is enabled, userspace can mmap the global ring +buffer (kvm_dirty_gfn[], offset KVM_DIRTY_LOG_PAGE_OFFSET) and the +indexes (kvm_dirty_ring_indexes, offset 0) from the VM file +descriptor. The per-vcpu dirty ring instead is mmapped when the vcpu +is created, similar to the kvm_run struct (kvm_dirty_ring_indexes +locates inside kvm_run, while kvm_dirty_gfn[] at offset +KVM_DIRTY_LOG_PAGE_OFFSET). + +Just like for dirty page bitmaps, the buffer tracks writes to +all user memory regions for which the KVM_MEM_LOG_DIRTY_PAGES flag was +set in KVM_SET_USER_MEMORY_REGION. Once a memory region is registered +with the flag set, userspace can start harvesting dirty pages from the +ring buffer. + +To harvest the dirty pages, userspace accesses the mmaped ring buffer +to read the dirty GFNs up to avail_index, and sets the fetch_index +accordingly. This can be done when the guest is running or paused, +and dirty pages need not be collected all at once. After processing +one or more entries in the ring buffer, userspace calls the VM ioctl +KVM_RESET_DIRTY_RINGS to notify the kernel that it has updated +fetch_index and to mark those pages clean. Therefore, the ioctl +must be called *before* reading the content of the dirty pages. + +However, there is a major difference comparing to theDocumentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5489
Quoted 10 lines
Documentation/virt/kvm/api.txt +10
-0+KVM_GET_DIRTY_LOG interface in that when reading the dirty ring from +userspace it's still possible that the kernel has not yet flushed the +hardware dirty buffers into the kernel buffer. To achieve that, one +needs to kick the vcpu out for a hardware buffer flush (vmexit). + +If one of the ring buffers is full, the guest will exit to userspace +with the exit reason set to KVM_EXIT_DIRTY_LOG_FULL, and the +KVM_RUN ioctl will return -EINTR. Once that happens, userspace +should pause all the vcpus, then harvest all the dirty pages and +rearm the dirty traps. It can unpause the guest after that.This last item means that the performance impact of the feature is really hard to predict. Can improve some workloads drastically. Or can slow some down.
Quoted 4 lines
One solution could be to actually allow using this together with the existing bitmap. Userspace can then decide whether it wants to block VCPU on ring full, or just record ring full condition and recover by bitmap scanning.
Quoted 7 lines
That's true, but again allowing mixture use of the two might bring extra complexity as well (especially when after adding
KVM_CLEAR_DIRTY_LOG).My understanding of this is that normally we do only want either one of them depending on the major workload and the configuration of the guest.
And again how does one know which to enable? No one has the time to fine-tune gazillion parameters.
Quoted 5 lines
It's not trivial to try to provide a one-for-all solution. So
again I would hope we can start from easy, then we extend when we have better ideas on how to leverage the two interfaces when the ideas really come, and then we can justify whether it's worth it to work on that complexity.
It's less *coding* work to build a simple thing but it need much more *testing*.
IMHO a huge amount of benchmarking has to happen if you just want to set this loose on users as default with these kind of limitations. We need to be sure that even though in theory it can be very bad, in practice it's actually good.
If it's auto-tuning then it's a much easier sell to upstream even if there's a chance of some regressions.
Quoted 159 lines
Quoted 150 lines
Quoted 150 lines
arch/x86/kvm/Makefile +2
-1@@ -5,7 +5,8 @@ ccflags-y += -Iarch/x86/kvm KVM := ../../../virt/kvm kvm-y += $(KVM)/kvm_main.o $(KVM)/coalesced_mmio.o \ - $(KVM)/eventfd.o $(KVM)/irqchip.o $(KVM)/vfio.o + $(KVM)/eventfd.o $(KVM)/irqchip.o $(KVM)/vfio.o \ + $(KVM)/dirty_ring.o kvm-$(CONFIG_KVM_ASYNC_PF) += $(KVM)/async_pf.o kvm-y += x86.o emulate.o i8259.o irq.o lapic.o \include/linux/kvm_dirty_ring.h new file +67
-0@@ -0,0 +1,67 @@ +#ifndef KVM_DIRTY_RING_H +#define KVM_DIRTY_RING_H + +/* +* struct kvm_dirty_ring is defined in include/uapi/linux/kvm.h. +* +* dirty_ring: shared with userspace via mmap. It is the compact list +* that holds the dirty pages. +* dirty_index: free running counter that points to the next slot in +* dirty_ring->dirty_gfns where a new dirty page should go. +* reset_index: free running counter that points to the next dirty page +* in dirty_ring->dirty_gfns for which dirty trap needs to +* be reenabled +* size: size of the compact list, dirty_ring->dirty_gfns +* soft_limit: when the number of dirty pages in the list reaches this +* limit, vcpu that owns this ring should exit to userspace +* to allow userspace to harvest all the dirty pages +* lock: protects dirty_ring, only in use if this is the global +* ring +* +* The number of dirty pages in the ring is calculated by, +* dirty_index - reset_index +* +* kernel increments dirty_ring->indices.avail_index after dirty index +* is incremented. When userspace harvests the dirty pages, it increments +* dirty_ring->indices.fetch_index up to dirty_ring->indices.avail_index. +* When kernel reenables dirty traps for the dirty pages, it increments +* reset_index up to dirty_ring->indices.fetch_index. +* +*/ +struct kvm_dirty_ring { +u32 dirty_index; +u32 reset_index; +u32 size; +u32 soft_limit; +spinlock_t lock; +struct kvm_dirty_gfn *dirty_gfns; +}; + +u32 kvm_dirty_ring_get_rsvd_entries (void ); +int kvm_dirty_ring_alloc (struct kvm *kvm,struct kvm_dirty_ring *ring); + +/* +* called with kvm->slots_lock held, returns the number of +* processed pages. +*/ +int kvm_dirty_ring_reset (struct kvm *kvm, +struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes); + +/* +* returns 0: successfully pushed +* 1: successfully pushed, soft limit reached, +* vcpu should exit to userspace +* -EBUSY: unable to push, dirty ring full. +*/ +int kvm_dirty_ring_push (struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes, +u32 slot,u64 offset,bool lock); + +/* for use in vm_operations_struct */ +struct page *kvm_dirty_ring_get_page (struct kvm_dirty_ring *ring,u32 i); + +void kvm_dirty_ring_free (struct kvm_dirty_ring *ring); +bool kvm_dirty_ring_full (struct kvm_dirty_ring *ring); + +#endif include/linux/kvm_host.h +18
-0@@ -34,6 +34,7 @@#include <linux/kvm_types.h> #include <asm/kvm_host.h> +#include <linux/kvm_dirty_ring.h> #ifndef KVM_MAX_VCPU_ID #define KVM_MAX_VCPU_ID KVM_MAX_VCPUS@@ -146,6 +147,7 @@ static inline bool is_error_page(struct page *page)#define KVM_REQ_MMU_RELOAD (1 | KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)#define KVM_REQ_PENDING_TIMER 2#define KVM_REQ_UNHALT 3 +#define KVM_REQ_DIRTY_RING_FULL 4#define KVM_REQUEST_ARCH_BASE 8#define KVM_ARCH_REQ_FLAGS (nr, flags) ({ \@@ -321,6 +323,7 @@ struct kvm_vcpu {bool ready;struct kvm_vcpu_arch arch;struct dentry *debugfs_dentry; +struct kvm_dirty_ring dirty_ring; };static inline int kvm_vcpu_exiting_guest_mode (struct kvm_vcpu *vcpu)@@ -501,6 +504,10 @@ struct kvm {struct srcu_struct srcu;struct srcu_struct irq_srcu;pid_t userspace_pid; +/* Data structure to be exported by mmap(kvm->fd, 0) */ +struct kvm_vm_run *vm_run; +u32 dirty_ring_size; +struct kvm_dirty_ring vm_dirty_ring; };#define kvm_err (fmt, ...) \@@ -832,6 +839,8 @@ void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,gfn_t gfn_offset, unsigned long mask); +void kvm_reset_dirty_gfn (struct kvm *kvm,u32 slot,u64 offset,u64 mask); +int kvm_vm_ioctl_get_dirty_log (struct kvm *kvm,struct kvm_dirty_log *log); int kvm_vm_ioctl_clear_dirty_log(struct kvm *kvm,@@ -1411,4 +1420,28 @@ int kvm_vm_create_worker_thread(struct kvm *kvm, kvm_vm_thread_fn_t thread_fn, uintptr_t data, const char *name, struct task_struct **thread_ptr); +/* +* This defines how many reserved entries we want to keep before we +* kick the vcpu to the userspace to avoid dirty ring full. This +* value can be tuned to higher if e.g. PML is enabled on the host. +*/ +#define KVM_DIRTY_RING_RSVD_ENTRIES 64 + +/* Max number of entries allowed for each kvm dirty ring */ +#define KVM_DIRTY_RING_MAX_ENTRIES 65536Quoted 8 lines
include/linux/kvm_host.h int kvm_vm_create_worker_thread(struct kvm *kvm, kvm_vm_thread_fn_t thread_fn,
Quoted 5 lines
include/linux/kvm_host.h +5
-0+ +/* +* Arch needs to define these macro after implementing the dirty ring +* feature. KVM_DIRTY_LOG_PAGE_OFFSET should be defined as the +* starting page offset of the dirty ring structures, Confused. Offset where? You set a default for everyone - where does arch want to override it?
Quoted 2 lines
If arch defines
KVM_DIRTY_LOG_PAGE_OFFSETthen below will be a no-op, please see [1] on #ifndef.
So which arches need to override it? Why do you say they should?
Quoted 23 lines
Quoted 6 lines
Quoted 6 lines
while
+ *KVM_DIRTY_RING_VERSIONshould be defined as >=1. By default, this
+ * feature is off on all archs.
+ */
+#ifndefKVM_DIRTY_LOG_PAGE_OFFSET[1]
Quoted 10 lines
include/linux/kvm_host.h int kvm_vm_create_worker_thread(struct kvm *kvm, kvm_vm_thread_fn_t thread_fn, · line 1445
Quoted 5 lines
include/linux/kvm_host.h +5
-0+#define KVM_DIRTY_LOG_PAGE_OFFSET 0 +#endif +#ifndef KVM_DIRTY_RING_VERSION +#define KVM_DIRTY_RING_VERSION 0 +#endif One way versioning, with no bits and negotiation will make it hard to change down the road.
what's wrong with existing KVM capabilities that you feel there's a need for dedicated versioning for this?Frankly speaking I don't even think it'll change in the near future.. :)
Quoted 8 lines
Yeh kvm versioning could work too. Here we can also return a zero just like the most of the caps (or
KVM_DIRTY_LOG_PAGE_OFFSETas in the original patchset, but it's really helpless either because it's defined in uapi), but I just don't see how it helps... So I returned a version number just in case we'd like to change the layout some day and when we don't want to bother introducing another cap bit for the same feature (likeKVM_CAP_MANUAL_DIRTY_LOG_PROTECTandKVM_CAP_MANUAL_DIRTY_LOG_PROTECT2).
I guess it's up to Paolo but really I don't see the point. You can add a version later when it means something ...
Quoted 121 lines
Quoted 112 lines
Quoted 112 lines
include/linux/kvm_host.h +1
-0+#endif include/linux/kvm_types.h +1
-0@@ -11,6 +11,7 @@ struct kvm_irq_routing_table;struct kvm_memory_slot ;struct kvm_one_reg ;struct kvm_run ; +struct kvm_vm_run ;struct kvm_userspace_memory_region ;struct kvm_vcpu ;struct kvm_vcpu_init ;include/uapi/linux/kvm.h +36
-0@@ -236,6 +236,7 @@ struct kvm_hyperv_exit {#define KVM_EXIT_IOAPIC_EOI 26#define KVM_EXIT_HYPERV 27#define KVM_EXIT_ARM_NISV 28 +#define KVM_EXIT_DIRTY_RING_FULL 29/* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */ @@ -247,6 +248,11 @@ struct kvm_hyperv_exit {/* Encounter unexpected vm-exit reason */ #define KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON 4 +struct kvm_dirty_ring_indexes { +__u32 avail_index;/* set by kernel */ +__u32 fetch_index;/* set by userspace */ +}; +/* for KVM_RUN, returned by mmap(vcpu_fd, offset=0) */ struct kvm_run {/* in */ @@ -421,6 +427,13 @@ struct kvm_run {struct kvm_sync_regs regs;char padding[SYNC_REGS_SIZE_BYTES ]; } s; + +struct kvm_dirty_ring_indexes vcpu_ring_indexes; +}; + +/* Returned by mmap(kvm->fd, offset=0) */ +struct kvm_vm_run { +struct kvm_dirty_ring_indexes vm_ring_indexes; };/* for KVM_REGISTER_COALESCED_MMIO / KVM_UNREGISTER_COALESCED_MMIO */ @@ -1009,6 +1022,7 @@ struct kvm_ppc_resize_hpt {#define KVM_CAP_PPC_GUEST_DEBUG_SSTEP 176#define KVM_CAP_ARM_NISV_TO_USER 177#define KVM_CAP_ARM_INJECT_EXT_DABT 178 +#define KVM_CAP_DIRTY_LOG_RING 179#ifdef KVM_CAP_IRQ_ROUTING @@ -1472,6 +1486,9 @@ struct kvm_enc_region {/* Available with KVM_CAP_ARM_SVE */ #define KVM_ARM_VCPU_FINALIZE _IOW(KVMIO, 0xc2, int) +/* Available with KVM_CAP_DIRTY_LOG_RING */ +#define KVM_RESET_DIRTY_RINGS _IO(KVMIO, 0xc3) +/* Secure Encrypted Virtualization command */ enum sev_cmd_id {/* Guest initialization commands */ @@ -1622,4 +1639,23 @@ struct kvm_hyperv_eventfd {#define KVM_HYPERV_CONN_ID_MASK 0x00ffffff#define KVM_HYPERV_EVENTFD_DEASSIGN (1 << 0) +/* +* The following are the requirements for supporting dirty log ring +* (by enabling KVM_DIRTY_LOG_PAGE_OFFSET). +* +* 1. Memory accesses by KVM should call kvm_vcpu_write_* instead +* of kvm_write_* so that the global dirty ring is not filled up +* too quickly. +* 2. kvm_arch_mmu_enable_log_dirty_pt_masked should be defined for +* enabling dirty logging. +* 3. There should not be a separate step to synchronize hardware +* dirty bitmap with KVM's. +*/ + +struct kvm_dirty_gfn { +__u32 pad; +__u32 slot; +__u64 offset; +}; +#endif /* __LINUX_KVM_H */ virt/kvm/dirty_ring.c new file +11
-0@@ -0,0 +1,156 @@ +#include <linux/kvm_host.h> +#include <linux/kvm.h> +#include <linux/vmalloc.h> +#include <linux/kvm_dirty_ring.h> + +u32 kvm_dirty_ring_get_rsvd_entries (void ) +{ +return KVM_DIRTY_RING_RSVD_ENTRIES +kvm_cpu_dirty_log_size (); +} + +int kvm_dirty_ring_alloc (struct kvm *kvm,struct kvm_dirty_ring *ring)Quoted 8 lines
virt/kvm/dirty_ring.c line 15
Quoted 4 lines
virt/kvm/dirty_ring.c new file +4
-0+{ +u32 size = kvm->dirty_ring_size; + + ring->dirty_gfns =vmalloc (size);So 1/2 a megabyte of kernel memory per VM that userspace locks up. Do we really have to though? Why not get a userspace pointer, write it with copy to user, and sidestep all this?
Quoted 7 lines
I'd say it won't be a big issue on locking 1/2M of host mem for a vm...
Also note that if dirty ring is enabled, I plan to evaporate thedirty_bitmapin the next post. The oldkvm->dirty_bitmaptakes $GUEST_MEM/32K*2 mem. E.g., for 64G guest it's 64G/32K*2=4M. If with dirty ring of 8 vcpus, that could be 64K*8=0.5M, which could be even less memory used.
Right - I think Avi described the bitmap in kernel memory as one of design mistakes. Why repeat that with the new design?
Quoted 44 lines
Quoted 35 lines
Quoted 35 lines
virt/kvm/dirty_ring.c new file +34
-0+if (!ring->dirty_gfns) +return -ENOMEM ; +memset (ring->dirty_gfns,0 , size); + + ring->size = size /sizeof (struct kvm_dirty_gfn ); + ring->soft_limit = + (kvm->dirty_ring_size /sizeof (struct kvm_dirty_gfn )) - +kvm_dirty_ring_get_rsvd_entries (); + ring->dirty_index =0 ; + ring->reset_index =0 ; +spin_lock_init (&ring->lock); + +return 0 ; +} + +int kvm_dirty_ring_reset (struct kvm *kvm, +struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes) +{ +u32 cur_slot, next_slot; +u64 cur_offset, next_offset; +unsigned long mask; +u32 fetch; +int count =0 ; +struct kvm_dirty_gfn *entry; + + fetch =READ_ONCE (indexes->fetch_index); +if (fetch == ring->reset_index) +return 0 ; + + entry = &ring->dirty_gfns[ring->reset_index & (ring->size -1 )]; +/* +* The ring buffer is shared with userspace, which might mmap +* it and concurrently modify slot and offset. Userspace must Quoted 8 lines
virt/kvm/dirty_ring.c line 52
Quoted 3 lines
virt/kvm/dirty_ring.c new file +3
-0+* not be trusted! READ_ONCE prevents the compiler from changing +* the values after they've been range-checked (the checks are +* in kvm_reset_dirty_gfn). What it doesn't is prevent speculative attacks. That's why things like copy from user have a speculation barrier. Instead of worrying about that, unless it's really critical, I think you'd do well do just use copy to/from user.
Quoted 7 lines
IMHO I would really hope these data be there without swapped out of memory, just like what we did with
kvm->dirty_bitmap... it's on the hot path of mmu page fault, even we could be with mmu lock held ifcopy_to_user()page faulted. But indeed I've no experience on avoiding speculative attacks, suggestions would be greatly welcomed on that. In our case we do (index & (size - 1)), so is it still suffering from speculative attacks?
I don't say I understand everything in depth.
Just reacting to this:
READ_ONCE prevents the compiler from changing the values after they've been range-checked (the checks are in kvm_reset_dirty_gfn)
so any range checks you do can be attacked.
And the safest way to avoid the attacks is to do what most kernel does and use copy from/to user when you talk to userspace. Avoid annoying things like bypassing SMAP too.
Quoted 212 lines
Quoted 5 lines
virt/kvm/dirty_ring.c line 54
Quoted 3 lines
virt/kvm/dirty_ring.c new file +2
-0+*/ +smp_read_barrier_depends ();What depends on what here? Looks suspicious ...
Hmm, I think maybe it can be removed because the entry pointer reference below should be an ordering constraint already?
Quoted 12 lines
virt/kvm/dirty_ring.c line 62
Quoted 9 lines
virt/kvm/dirty_ring.c new file +8
-0+ cur_slot =READ_ONCE (entry->slot); + cur_offset =READ_ONCE (entry->offset); + mask =1 ; + count++; + ring->reset_index++; +while (ring->reset_index != fetch) { + entry = &ring->dirty_gfns[ring->reset_index & (ring->size -1 )]; +smp_read_barrier_depends ();same concerns here
Quoted 9 lines
virt/kvm/dirty_ring.c line 69
Quoted 7 lines
virt/kvm/dirty_ring.c new file +7
-0+ next_slot =READ_ONCE (entry->slot); + next_offset =READ_ONCE (entry->offset); + ring->reset_index++; + count++; +/* +* Try to coalesce the reset operations when the guest is +* scanning pages in the same slot. what does guest scanning mean?
My wild guess is that it means when the guest is accessing the pages continuously so the dirty gfns are continuous too. Anyway I agree it's not clear, where I can try to rephrase.
Quoted 48 lines
Quoted 48 lines
virt/kvm/dirty_ring.c new file +47
-0+*/ +if (next_slot == cur_slot) { +int delta = next_offset - cur_offset; + +if (delta >=0 && delta <BITS_PER_LONG ) { + mask |=1ull << delta; +continue ; + } + +/* Backwards visit, careful about overflows! */ +if (delta > -BITS_PER_LONG && delta <0 && + (mask << -delta >> -delta) == mask) { + cur_offset = next_offset; + mask = (mask << -delta) |1 ; +continue ; + } + } +kvm_reset_dirty_gfn (kvm, cur_slot, cur_offset, mask); + cur_slot = next_slot; + cur_offset = next_offset; + mask =1 ; + } +kvm_reset_dirty_gfn (kvm, cur_slot, cur_offset, mask); + +return count; +} + +static inline u32 kvm_dirty_ring_used (struct kvm_dirty_ring *ring) +{ +return ring->dirty_index - ring->reset_index; +} + +bool kvm_dirty_ring_full (struct kvm_dirty_ring *ring) +{ +return kvm_dirty_ring_used (ring) >= ring->size; +} + +/* +* Returns: +* >0 if we should kick the vcpu out, +* =0 if the gfn pushed successfully, or, +* <0 if error (e.g. ring full) +*/ +int kvm_dirty_ring_push (struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes, +u32 slot,u64 offset,bool lock) +{Quoted 8 lines
virt/kvm/dirty_ring.c line 121
Quoted 5 lines
virt/kvm/dirty_ring.c new file +5
-0+int ret; +struct kvm_dirty_gfn *entry; + +if (lock) +spin_lock (&ring->lock);what's the story around locking here? Why is it safe not to take the lock sometimes?
kvm_dirty_ring_push()will be with lock==true only when the per-vm ring is used. For per-vcpu ring, because that will only happen with the vcpu context, then we don't need locks (sokvm_dirty_ring_push()is called with lock==false).Quoted 105 lines
Quoted 105 lines
virt/kvm/dirty_ring.c new file +35
-0+ +if (kvm_dirty_ring_full (ring)) { + ret = -EBUSY ; + goto out; + } + + entry = &ring->dirty_gfns[ring->dirty_index & (ring->size -1 )]; + entry->slot = slot; + entry->offset = offset; +smp_wmb (); + ring->dirty_index++; +WRITE_ONCE (indexes->avail_index, ring->dirty_index); + ret =kvm_dirty_ring_used (ring) >= ring->soft_limit; +pr_info ("%s: slot %u offset %llu used %u\n" , + __func__, slot, offset,kvm_dirty_ring_used (ring)); + +out: +if (lock) +spin_unlock (&ring->lock); + +return ret; +} + +struct page *kvm_dirty_ring_get_page (struct kvm_dirty_ring *ring,u32 i) +{ +return vmalloc_to_page ((void *)ring->dirty_gfns + i *PAGE_SIZE ); +} + +void kvm_dirty_ring_free (struct kvm_dirty_ring *ring) +{ +if (ring->dirty_gfns) { +vfree (ring->dirty_gfns); + ring->dirty_gfns =NULL ; + } +}virt/kvm/kvm_main.c +21
-1@@ -64,6 +64,8 @@#define CREATE_TRACE_POINTS #include <trace/events/kvm.h> +#include <linux/kvm_dirty_ring.h> +/* Worst case buffer size needed for holding an integer. */ #define ITOA_MAX_LEN 12@@ -149,6 +151,10 @@ static void mark_page_dirty_in_slot(struct kvm *kvm,struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, gfn_t gfn); +static void mark_page_dirty_in_ring (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *slot, +gfn_t gfn); __visible bool kvm_rebooting;EXPORT_SYMBOL_GPL (kvm_rebooting);@@ -359,11 +365,22 @@ int kvm_vcpu_init(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned id) vcpu->preempted = false; vcpu->ready = false; +if (kvm->dirty_ring_size) { + r =kvm_dirty_ring_alloc (vcpu->kvm, &vcpu->dirty_ring); +if (r) { + kvm->dirty_ring_size =0 ; + goto fail_free_run; + } + } + r =kvm_arch_vcpu_init (vcpu);if (r <0 ) - goto fail_free_run; + goto fail_free_ring;return 0 ; +fail_free_ring: +if (kvm->dirty_ring_size) +kvm_dirty_ring_free (&vcpu->dirty_ring); fail_free_run:free_page ((unsigned long )vcpu->run); fail:@@ -381,6 +398,8 @@ void kvm_vcpu_uninit(struct kvm_vcpu *vcpu)put_pid (rcu_dereference_protected (vcpu->pid,1 ));kvm_arch_vcpu_uninit (vcpu);free_page ((unsigned long )vcpu->run); +if (vcpu->kvm->dirty_ring_size) +kvm_dirty_ring_free (&vcpu->dirty_ring); }EXPORT_SYMBOL_GPL (kvm_vcpu_uninit);@@ -690,6 +709,7 @@ static struct kvm *kvm_create_vm(unsigned long type)struct kvm *kvm =kvm_arch_alloc_vm ();int r = -ENOMEM ;int i; +struct page *page;if (!kvm)return ERR_PTR (-ENOMEM );@@ -705,6 +725,14 @@ static struct kvm *kvm_create_vm(unsigned long type)BUILD_BUG_ON (KVM_MEM_SLOTS_NUM >SHRT_MAX );Quoted 9 lines
virt/kvm/kvm_main.c static struct kvm *kvm_create_vm(unsigned long type) · line 733
Quoted 6 lines
virt/kvm/kvm_main.c +6
-0+ page =alloc_page (GFP_KERNEL | __GFP_ZERO); +if (!page) { + r = -ENOMEM ; + goto out_err_alloc_page; + } + kvm->vm_run =page_address (page);So 4K with just 8 bytes used. Not as bad as 1/2Mbyte for the ring but still. What is wrong with just a pointer and calling
put_user?
Quoted 2 lines
I want to make it the start point for sharing fields between user/kernel per-vm. Just like
kvm_runfor per-vcpu.
And why is doing that without get/put user a good idea? If nothing else this bypasses SMAP, exploits can pass data from userspace to kernel through that.
Quoted 4 lines
IMHO it'll be awkward if we always introduce a new interface just to take a pointer of the userspace buffer and cache it... I'd say so far I like the design of
kvm_runand alike because it's efficient, easy to use, and easy for extensions.
Well kvm run at least isn't accessed when kernel is processing it. And the structure there is dead simple, not a tricky lockless ring with indices and things.
Again I might be wrong, eventually it's up to kvm maintainers. But really there's a standard thing all drivers do to talk to userspace, and if there's no special reason to do otherwise I would do exactly it.
Quoted 115 lines
Quoted 106 lines
Quoted 106 lines
virt/kvm/kvm_main.c +56
-1+BUILD_BUG_ON (sizeof (struct kvm_vm_run ) >PAGE_SIZE ); +if (init_srcu_struct (&kvm->srcu)) goto out_err_no_srcu;if (init_srcu_struct (&kvm->irq_srcu))@@ -775,6 +803,9 @@ static struct kvm *kvm_create_vm(unsigned long type) out_err_no_irq_srcu:cleanup_srcu_struct (&kvm->srcu); out_err_no_srcu: +free_page ((unsigned long )page); + kvm->vm_run =NULL ; +out_err_alloc_page:kvm_arch_free_vm (kvm);mmdrop (current->mm);return ERR_PTR (r);@@ -800,6 +831,15 @@ static void kvm_destroy_vm(struct kvm *kvm)int i;struct mm_struct *mm = kvm->mm; +if (kvm->dirty_ring_size) { +kvm_dirty_ring_free (&kvm->vm_dirty_ring); + } + +if (kvm->vm_run) { +free_page ((unsigned long )kvm->vm_run); + kvm->vm_run =NULL ; + } +kvm_uevent_notify_change (KVM_EVENT_DESTROY_VM , kvm);kvm_destroy_vm_debugfs (kvm);kvm_arch_sync_events (kvm);@@ -2301,7 +2341,7 @@ static void mark_page_dirty_in_slot(struct kvm *kvm, {if (memslot && memslot->dirty_bitmap) {unsigned long rel_gfn = gfn - memslot->base_gfn; - +mark_page_dirty_in_ring (kvm, vcpu, memslot, gfn);set_bit_le (rel_gfn, memslot->dirty_bitmap); } }@@ -2649,6 +2689,13 @@ void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode) }EXPORT_SYMBOL_GPL (kvm_vcpu_on_spin); +static bool kvm_fault_in_dirty_ring (struct kvm *kvm,struct vm_fault *vmf) +{ +return (vmf->pgoff >=KVM_DIRTY_LOG_PAGE_OFFSET ) && + (vmf->pgoff <KVM_DIRTY_LOG_PAGE_OFFSET + + kvm->dirty_ring_size /PAGE_SIZE ); +} +static vm_fault_t kvm_vcpu_fault (struct vm_fault *vmf) {struct kvm_vcpu *vcpu = vmf->vma->vm_file->private_data;@@ -2664,6 +2711,10 @@ static vm_fault_t kvm_vcpu_fault(struct vm_fault *vmf) else if (vmf->pgoff == KVM_COALESCED_MMIO_PAGE_OFFSET) page =virt_to_page (vcpu->kvm->coalesced_mmio_ring);#endif + else if (kvm_fault_in_dirty_ring(vcpu->kvm, vmf)) + page =kvm_dirty_ring_get_page ( + &vcpu->dirty_ring, + vmf->pgoff -KVM_DIRTY_LOG_PAGE_OFFSET ); else return kvm_arch_vcpu_fault(vcpu, vmf);get_page (page);@@ -3259,12 +3310,162 @@ static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg)#endif case KVM_CAP_NR_MEMSLOTS :return KVM_USER_MEM_SLOTS ; +case KVM_CAP_DIRTY_LOG_RING : +/* Version will be zero if arch didn't implement it */ +return KVM_DIRTY_RING_VERSION ;default :break ; }return kvm_vm_ioctl_check_extension (kvm, arg); } +static void mark_page_dirty_in_ring (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *slot, +gfn_t gfn) +{ +u32 as_id =0 ; +u64 offset; +int ret; +struct kvm_dirty_ring *ring; +struct kvm_dirty_ring_indexes *indexes; +bool is_vm_ring; + +if (!kvm->dirty_ring_size) +return ; + + offset = gfn - slot->base_gfn; + +if (vcpu) { + as_id =kvm_arch_vcpu_memslots_id (vcpu); + }else { + as_id =0 ; + vcpu =kvm_get_running_vcpu (); + } + +if (vcpu) { + ring = &vcpu->dirty_ring; + indexes = &vcpu->run->vcpu_ring_indexes;Quoted 8 lines
virt/kvm/kvm_main.c static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) · line 3353
Quoted 5 lines
virt/kvm/kvm_main.c +5
-0+ is_vm_ring = false; + }else { +/* +* Put onto per vm ring because no vcpu context. Kick +* vcpu0 if ring is full. What about tasks on vcpu 0? Do guests realize it's a bad idea to put critical tasks there, they will be penalized disproportionally?
Quoted 4 lines
Reasonable question. So far we can't avoid it because vcpu exit is the event mechanism to say "hey please collect dirty bits". Maybe someway is better than this, but I'll need to rethink all these over...
Maybe signal an eventfd, and let userspace worry about deciding what to do.
Quoted 72 lines
Quoted 63 lines
Quoted 63 lines
virt/kvm/kvm_main.c +62
-0+*/ + vcpu = kvm->vcpus[0 ]; + ring = &kvm->vm_dirty_ring; + indexes = &kvm->vm_run->vm_ring_indexes; + is_vm_ring = true; + } + + ret =kvm_dirty_ring_push (ring, indexes, + (as_id <<16 )|slot->id, offset, + is_vm_ring); +if (ret <0 ) { +if (is_vm_ring) +pr_warn_once ("vcpu %d dirty log overflow\n" , + vcpu->vcpu_id); +else +pr_warn_once ("per-vm dirty log overflow\n" ); +return ; + } + +if (ret) +kvm_make_request (KVM_REQ_DIRTY_RING_FULL , vcpu); +} + +void kvm_reset_dirty_gfn (struct kvm *kvm,u32 slot,u64 offset,u64 mask) +{ +struct kvm_memory_slot *memslot; +int as_id, id; + + as_id = slot >>16 ; + id = (u16 )slot; +if (as_id >=KVM_ADDRESS_SPACE_NUM || id >=KVM_USER_MEM_SLOTS ) +return ; + + memslot =id_to_memslot (__kvm_memslots (kvm, as_id), id); +if (offset >= memslot->npages) +return ; + +spin_lock (&kvm->mmu_lock); +/* FIXME: we should use a single AND operation, but there is no +* applicable atomic API. +*/ +while (mask) { +clear_bit_le (offset +__ffs (mask), memslot->dirty_bitmap); + mask &= mask -1 ; + } + +kvm_arch_mmu_enable_log_dirty_pt_masked (kvm, memslot, offset, mask); +spin_unlock (&kvm->mmu_lock); +} + +static int kvm_vm_ioctl_enable_dirty_log_ring (struct kvm *kvm,u32 size) +{ +int r; + +/* the size should be power of 2 */ +if (!size || (size & (size -1 ))) +return -EINVAL ; + +/* Should be bigger to keep the reserved entries, or a page */ +if (size <kvm_dirty_ring_get_rsvd_entries () * +sizeof (struct kvm_dirty_gfn ) || size <PAGE_SIZE ) +return -EINVAL ;Quoted 8 lines
virt/kvm/kvm_main.c static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) · line 3419
Quoted 4 lines
virt/kvm/kvm_main.c +4
-0+ +if (size >KVM_DIRTY_RING_MAX_ENTRIES * +sizeof (struct kvm_dirty_gfn )) +return -E2BIG ;
KVM_DIRTY_RING_MAX_ENTRIESis not part of UAPI.
So how does userspace know what's legal?
Do you expect it to just try?
Quoted 4 lines
Yep that's what I thought. :)
Please grep E2BIG in QEMU repo
target/i386/kvm.c... won't be hard to do imho..
I don't see anything except just failing. Do we really have something trying to find a working value? What would even be a reasonable range? Start from UINT_MAX and work down? In which increments? This is just a ton of overhead for what could have been a simple query.
Quoted 7 lines
Quoted 2 lines
More likely it will just copy the number from kernel and can never ever make it smaller.
Not sure, but for sure I can probably move
KVM_DIRTY_RING_MAX_ENTRIESto uapi too.Thanks,
Won't help as you can't change it ever then. You need it runtime discoverable.
Or again, keep it in userspace memory and then you don't really care what size it is.
Quoted 2 lines
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 4 lines · Michael S. Tsirkin
Quoted 4 lines
Quoted 4 lines
All these seem like arbitrary limitations to me.
Sizing the ring correctly might prove to be a challenge.
Quoted 8 lines
Quoted 4 lines
Quoted 2 lines
Thus I think there's value in resizing the rings
without destroying VCPU.Do you have an example on when we could use this feature?
So e.g. start with a small ring, and if you see stalls too often increase it? Otherwise I don't see how does one decide on ring size.
If you see stalls often, it means the guest is dirtying memory very fast. Harvesting the ring puts back pressure on the guest, you may prefer a smaller ring size to avoid a bufferbloat-like situation.
Note that having a larger ring is better, even though it does incur a memory cost, because it means the migration thread will be able to reap the ring buffer asynchronously with no vmexits.
With smaller ring sizes the cost of flushing the TLB when resetting the rings goes up, but the initial bulk copy phase _will_ have vmexits and then having to reap more dirty rings becomes more expensive and introduces some jitter. So it will require some experimentation to find an optimal value.
Anyway if in the future we go for resizable rings, KVM_ENABLE_CAP can be passed the largest desired size and then another ioctl can be introduced to set the mask for indices.
Quoted 7 lines
Quoted 5 lines
Quoted 3 lines
Also, power of two just saves a branch here and there, but wastes lots of memory. Just wrap the index around to 0 and then users can select any size?
Same as above to postpone until we need it?
It's to save memory, don't we always need to do that?
Does it really save that much memory? Would it really be so beneficial to choose 12K entries rather than 8K or 16K in the ring?
Quoted 6 lines
Quoted 3 lines
My understanding of this is that normally we do only want either one of them depending on the major workload and the configuration of the guest.
And again how does one know which to enable? No one has the time to fine-tune gazillion parameters.
Hopefully we can always use just the ring buffer.
Quoted 6 lines
IMHO a huge amount of benchmarking has to happen if you just want to set this loose on users as default with these kind of limitations. We need to be sure that even though in theory it can be very bad, in practice it's actually good.
If it's auto-tuning then it's a much easier sell to upstream even if there's a chance of some regressions.
Auto-tuning is not a silver bullet, it requires just as much benchmarking to make sure that it doesn't oscillate crazily and that it actually outperforms a simple fixed size.
Quoted 11 lines
Quoted 8 lines
Yeh kvm versioning could work too. Here we can also return a zero just like the most of the caps (or
KVM_DIRTY_LOG_PAGE_OFFSETas in the original patchset, but it's really helpless either because it's defined in uapi), but I just don't see how it helps... So I returned a version number just in case we'd like to change the layout some day and when we don't want to bother introducing another cap bit for the same feature (likeKVM_CAP_MANUAL_DIRTY_LOG_PROTECTandKVM_CAP_MANUAL_DIRTY_LOG_PROTECT2).I guess it's up to Paolo but really I don't see the point. You can add a version later when it means something ...
Yeah, we can return the maximum size of the ring buffer, too.
Quoted 10 lines
Quoted 7 lines
I'd say it won't be a big issue on locking 1/2M of host mem for a vm...
Also note that if dirty ring is enabled, I plan to evaporate thedirty_bitmapin the next post. The oldkvm->dirty_bitmaptakes $GUEST_MEM/32K*2 mem. E.g., for 64G guest it's 64G/32K*2=4M. If with dirty ring of 8 vcpus, that could be 64K*8=0.5M, which could be even less memory used.Right - I think Avi described the bitmap in kernel memory as one of design mistakes. Why repeat that with the new design?
Do you have a source for that? At least the dirty bitmap has to be accessed from atomic context so it seems unlikely that it can be moved to user memory.
The dirty ring could use user memory indeed, but it would be much harder to set up (multiple ioctls for each ring? what to do if userspace forgets one? etc.). The mmap API is easier to use.
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
virt/kvm/dirty_ring.c new file +4
-0+ entry = &ring->dirty_gfns[ring->reset_index & (ring->size -1 )]; +/* +* The ring buffer is shared with userspace, which might mmap +* it and concurrently modify slot and offset. Userspace must
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
virt/kvm/dirty_ring.c line 52
Quoted 3 lines
virt/kvm/dirty_ring.c new file +3
-0+* not be trusted! READ_ONCE prevents the compiler from changing +* the values after they've been range-checked (the checks are +* in kvm_reset_dirty_gfn). What it doesn't is prevent speculative attacks. That's why things like copy from user have a speculation barrier. Instead of worrying about that, unless it's really critical, I think you'd do well do just use copy to/from user.
An unconditional speculation barrier (lfence) is also expensive. We already have macros to add speculation checks with array_index_nospec at the right places, for example __kvm_memslots. We should add an array_index_nospec to id_to_memslot as well. I'll send a patch for that.
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
What depends on what here? Looks suspicious ...
Hmm, I think maybe it can be removed because the entry pointer reference below should be an ordering constraint already?
entry->xxx depends on ring->reset_index.
Quoted 7 lines
Quoted 7 lines
Quoted 2 lines
what's the story around locking here? Why is it safe not to take the lock sometimes?
kvm_dirty_ring_push()will be with lock==true only when the per-vm ring is used. For per-vcpu ring, because that will only happen with the vcpu context, then we don't need locks (sokvm_dirty_ring_push()is called with lock==false).
FWIW this will be done much more nicely in v2.
Quoted 10 lines
Quoted 10 lines
Quoted 9 lines
virt/kvm/kvm_main.c static struct kvm *kvm_create_vm(unsigned long type) · line 733
Quoted 6 lines
virt/kvm/kvm_main.c +6
-0+ page =alloc_page (GFP_KERNEL | __GFP_ZERO); +if (!page) { + r = -ENOMEM ; + goto out_err_alloc_page; + } + kvm->vm_run =page_address (page);So 4K with just 8 bytes used. Not as bad as 1/2Mbyte for the ring but still. What is wrong with just a pointer and calling
put_user?
Quoted 2 lines
Quoted 2 lines
I want to make it the start point for sharing fields between user/kernel per-vm. Just like
kvm_runfor per-vcpu.
This page is actually not needed at all. Userspace can just map at KVM_DIRTY_LOG_PAGE_OFFSET, the indices reside there. You can drop kvm_vm_run completely.
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines
virt/kvm/kvm_main.c static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) · line 3353
Quoted 4 lines
virt/kvm/kvm_main.c +4
-0+ }else { +/* +* Put onto per vm ring because no vcpu context. Kick +* vcpu0 if ring is full. What about tasks on vcpu 0? Do guests realize it's a bad idea to put critical tasks there, they will be penalized disproportionally?
Quoted 7 lines
Quoted 4 lines
Reasonable question. So far we can't avoid it because vcpu exit is the event mechanism to say "hey please collect dirty bits". Maybe someway is better than this, but I'll need to rethink all these over...
Maybe signal an eventfd, and let userspace worry about deciding what to do.
This has to be done synchronously. But the vm ring should be used very rarely (it's for things like kvmclock updates that write to guest memory outside a vCPU), possibly a handful of times in the whole run of the VM.
Quoted 5 lines
Quoted 5 lines
Quoted 3 lines
KVM_DIRTY_RING_MAX_ENTRIESis not part of UAPI.
So how does userspace know what's legal?
Do you expect it to just try?Yep that's what I thought. :)
We should return it for KVM_CHECK_EXTENSION.
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 8 lines · Paolo Bonzini
Quoted 8 lines
Quoted 7 lines
I'd say it won't be a big issue on locking 1/2M of host mem for a vm...
Also note that if dirty ring is enabled, I plan to evaporate thedirty_bitmapin the next post. The oldkvm->dirty_bitmaptakes $GUEST_MEM/32K*2 mem. E.g., for 64G guest it's 64G/32K*2=4M. If with dirty ring of 8 vcpus, that could be 64K*8=0.5M, which could be even less memory used.
Quoted 4 lines
Quoted 2 lines
Right - I think Avi described the bitmap in kernel memory as one of design mistakes. Why repeat that with the new design?
Do you have a source for that?
Nope, it was a private talk.
Quoted 3 lines
At least the dirty bitmap has to be
accessed from atomic context so it seems unlikely that it can be moved to user memory.
Why is that? We could surely do it from VCPU context?
Quoted 3 lines
The dirty ring could use user memory indeed, but it would be much harder to set up (multiple ioctls for each ring? what to do if userspace forgets one? etc.).
Why multiple ioctls? If you do like virtio packed ring you just need the base and the size.
Signature · 1 line
MST
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 14 lines · Michael S. Tsirkin
Quoted 8 lines · Paolo Bonzini
Quoted 8 lines
Quoted 7 lines
I'd say it won't be a big issue on locking 1/2M of host mem for a vm...
Also note that if dirty ring is enabled, I plan to evaporate thedirty_bitmapin the next post. The oldkvm->dirty_bitmaptakes $GUEST_MEM/32K*2 mem. E.g., for 64G guest it's 64G/32K*2=4M. If with dirty ring of 8 vcpus, that could be 64K*8=0.5M, which could be even less memory used.Quoted 4 lines
Quoted 2 lines
Right - I think Avi described the bitmap in kernel memory as one of design mistakes. Why repeat that with the new design?
Do you have a source for that?
Quoted 7 lines
Nope, it was a private talk.
Quoted 3 lines
At least the dirty bitmap has to be
accessed from atomic context so it seems unlikely that it can be moved to user memory.Why is that? We could surely do it from VCPU context?
Spinlock is taken.
Quoted 6 lines
Quoted 3 lines
The dirty ring could use user memory indeed, but it would be much harder to set up (multiple ioctls for each ring? what to do if userspace forgets one? etc.).
Why multiple ioctls? If you do like virtio packed ring you just need the base and the size.
You have multiple rings, so multiple invocations of one ioctl.
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 17 lines · Paolo Bonzini
Quoted 16 lines · Michael S. Tsirkin
Quoted 8 lines · Paolo Bonzini
Quoted 8 lines
Quoted 7 lines
I'd say it won't be a big issue on locking 1/2M of host mem for a vm...
Also note that if dirty ring is enabled, I plan to evaporate thedirty_bitmapin the next post. The oldkvm->dirty_bitmaptakes $GUEST_MEM/32K*2 mem. E.g., for 64G guest it's 64G/32K*2=4M. If with dirty ring of 8 vcpus, that could be 64K*8=0.5M, which could be even less memory used.Quoted 4 lines
Quoted 2 lines
Right - I think Avi described the bitmap in kernel memory as one of design mistakes. Why repeat that with the new design?
Do you have a source for that?
Nope, it was a private talk.
Quoted 7 lines
Quoted 5 lines
Quoted 3 lines
At least the dirty bitmap has to be
accessed from atomic context so it seems unlikely that it can be moved to user memory.Why is that? We could surely do it from VCPU context?
Spinlock is taken.
Right, that's an implementation detail though isn't it?
Quoted 10 lines
Quoted 6 lines
Quoted 3 lines
The dirty ring could use user memory indeed, but it would be much harder to set up (multiple ioctls for each ring? what to do if userspace forgets one? etc.).
Why multiple ioctls? If you do like virtio packed ring you just need the base and the size.
You have multiple rings, so multiple invocations of one ioctl.
Paolo
Oh. So when you said "multiple ioctls for each ring" - I guess you
meant: "multiple ioctls - one for each ring"?
And it's true, but then it allows supporting things like resize in a clean way without any effort in the kernel. You get a new ring address - you switch to that one.
Signature · 1 line
MST
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
In reply to Paolo BonziniQuoted 6 lines · Paolo Bonzini
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
What depends on what here? Looks suspicious ...
Hmm, I think maybe it can be removed because the entry pointer reference below should be an ordering constraint already?
entry->xxxdepends onring->reset_index.
Yes that's true, but...
entry = &ring->dirty_gfns[ring->reset_index & (ring->size - 1)];
/* barrier? */
next_slot = READ_ONCE(entry->slot);
next_offset = READ_ONCE(entry->offset);... I think entry->xxx depends on entry first, then entry depends on reset_index. So it seems fine because all things have a dependency?
Quoted 21 lines
Quoted 8 lines
Quoted 8 lines
Quoted 3 lines
what's the story around locking here? Why is it safe not to take the lock sometimes?
kvm_dirty_ring_push()will be with lock==true only when the per-vm ring is used. For per-vcpu ring, because that will only happen with the vcpu context, then we don't need locks (sokvm_dirty_ring_push()is called with lock==false).FWIW this will be done much more nicely in v2.
Quoted 10 lines
Quoted 10 lines
Quoted 9 lines
virt/kvm/kvm_main.c static struct kvm *kvm_create_vm(unsigned long type) · line 733
Quoted 6 lines
virt/kvm/kvm_main.c +6
-0+ page =alloc_page (GFP_KERNEL | __GFP_ZERO); +if (!page) { + r = -ENOMEM ; + goto out_err_alloc_page; + } + kvm->vm_run =page_address (page);So 4K with just 8 bytes used. Not as bad as 1/2Mbyte for the ring but still. What is wrong with just a pointer and calling
put_user?
Quoted 6 lines
Quoted 2 lines
Quoted 2 lines
I want to make it the start point for sharing fields between user/kernel per-vm. Just like
kvm_runfor per-vcpu.This page is actually not needed at all. Userspace can just map at
KVM_DIRTY_LOG_PAGE_OFFSET, the indices reside there. You can dropkvm_vm_runcompletely.
I changed it because otherwise we use one entry of the padding, and all the rest of paddings are a waste of memory because we can never really use the padding as new fields only for the 1st entry which overlaps with the indices. IMHO that could even waste more than 4k.
(for now we only "waste" 4K for per-vm, kvm_run is already mapped so
no waste there, not to say potentially I still think we can use the kvm_vm_run in the future)
Quoted 14 lines
Quoted 14 lines
Quoted 13 lines
Quoted 8 lines
virt/kvm/kvm_main.c static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) · line 3353
Quoted 5 lines
virt/kvm/kvm_main.c +4
-0+ }else { +/* +* Put onto per vm ring because no vcpu context. Kick +* vcpu0 if ring is full. What about tasks on vcpu 0? Do guests realize it's a bad idea to put critical tasks there, they will be penalized disproportionally?
Reasonable question. So far we can't avoid it because vcpu exit is the event mechanism to say "hey please collect dirty bits". Maybe someway is better than this, but I'll need to rethink all these over...
Quoted 6 lines
Quoted 2 lines
Maybe signal an eventfd, and let userspace worry about deciding what to do.
This has to be done synchronously. But the vm ring should be used very rarely (it's for things like kvmclock updates that write to guest memory outside a vCPU), possibly a handful of times in the whole run of the VM.
I've summarized a list of callers who might dirty guest memory in the other thread, it seems to me that even the kvm clock is using per-vcpu contexts.
Quoted 8 lines
Quoted 6 lines
Quoted 6 lines
Quoted 4 lines
KVM_DIRTY_RING_MAX_ENTRIESis not part of UAPI.
So how does userspace know what's legal?
Do you expect it to just try?Yep that's what I thought. :)
We should return it for
KVM_CHECK_EXTENSION.
OK. I'll drop the versioning.
Signature · 1 line
Peter Xu
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 10 lines · Peter Xu
Quoted 6 lines · Paolo Bonzini
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
What depends on what here? Looks suspicious ...
Hmm, I think maybe it can be removed because the entry pointer reference below should be an ordering constraint already?
entry->xxxdepends onring->reset_index.Yes that's true, but...
Quoted 7 lines
entry = &ring->dirty_gfns[ring->reset_index & (ring->size - 1)]; /* barrier? */ next_slot = READ_ONCE(entry->slot); next_offset = READ_ONCE(entry->offset);... I think
entry->xxxdepends on entry first, then entry depends onreset_index. So it seems fine because all things have a dependency?
Is reset_index changed from another thread then? If yes then you want to read reset_index with READ_ONCE. That includes a dependency barrier.
Quoted 75 lines
Quoted 21 lines
Quoted 8 lines
Quoted 8 lines
Quoted 3 lines
what's the story around locking here? Why is it safe not to take the lock sometimes?
kvm_dirty_ring_push()will be with lock==true only when the per-vm ring is used. For per-vcpu ring, because that will only happen with the vcpu context, then we don't need locks (sokvm_dirty_ring_push()is called with lock==false).FWIW this will be done much more nicely in v2.
Quoted 10 lines
Quoted 10 lines
Quoted 9 lines
virt/kvm/kvm_main.c static struct kvm *kvm_create_vm(unsigned long type) · line 733
Quoted 6 lines
virt/kvm/kvm_main.c +6
-0+ page =alloc_page (GFP_KERNEL | __GFP_ZERO); +if (!page) { + r = -ENOMEM ; + goto out_err_alloc_page; + } + kvm->vm_run =page_address (page);So 4K with just 8 bytes used. Not as bad as 1/2Mbyte for the ring but still. What is wrong with just a pointer and calling
put_user?Quoted 6 lines
Quoted 2 lines
Quoted 2 lines
I want to make it the start point for sharing fields between user/kernel per-vm. Just like
kvm_runfor per-vcpu.This page is actually not needed at all. Userspace can just map at
KVM_DIRTY_LOG_PAGE_OFFSET, the indices reside there. You can dropkvm_vm_runcompletely.I changed it because otherwise we use one entry of the padding, and all the rest of paddings are a waste of memory because we can never really use the padding as new fields only for the 1st entry which overlaps with the indices. IMHO that could even waste more than 4k.
(for now we only "waste" 4K for per-vm,
kvm_runis already mapped so
no waste there, not to say potentially I still think we can use thekvm_vm_runin the future)Quoted 14 lines
Quoted 14 lines
Quoted 13 lines
Quoted 8 lines
virt/kvm/kvm_main.c static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) · line 3353
Quoted 5 lines
virt/kvm/kvm_main.c +4
-0+ }else { +/* +* Put onto per vm ring because no vcpu context. Kick +* vcpu0 if ring is full. What about tasks on vcpu 0? Do guests realize it's a bad idea to put critical tasks there, they will be penalized disproportionally?
Reasonable question. So far we can't avoid it because vcpu exit is the event mechanism to say "hey please collect dirty bits". Maybe someway is better than this, but I'll need to rethink all these over...
Quoted 6 lines
Quoted 2 lines
Maybe signal an eventfd, and let userspace worry about deciding what to do.
This has to be done synchronously. But the vm ring should be used very rarely (it's for things like kvmclock updates that write to guest memory outside a vCPU), possibly a handful of times in the whole run of the VM.
I've summarized a list of callers who might dirty guest memory in the other thread, it seems to me that even the kvm clock is using per-vcpu contexts.
Quoted 8 lines
Quoted 6 lines
Quoted 6 lines
Quoted 4 lines
KVM_DIRTY_RING_MAX_ENTRIESis not part of UAPI.
So how does userspace know what's legal?
Do you expect it to just try?Yep that's what I thought. :)
We should return it for
KVM_CHECK_EXTENSION.OK. I'll drop the versioning.
Signature · 1 line
Peter Xu
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 16 lines · Michael S. Tsirkin
Quoted 15 lines · Peter Xu
Quoted 6 lines · Paolo Bonzini
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
What depends on what here? Looks suspicious ...
Hmm, I think maybe it can be removed because the entry pointer reference below should be an ordering constraint already?
entry->xxxdepends onring->reset_index.Yes that's true, but...
entry = &ring->dirty_gfns[ring->reset_index & (ring->size - 1)]; /* barrier? */ next_slot = READ_ONCE(entry->slot); next_offset = READ_ONCE(entry->offset);
Quoted 6 lines
Quoted 2 lines
... I think
entry->xxxdepends on entry first, then entry depends onreset_index. So it seems fine because all things have a dependency?Is
reset_indexchanged from another thread then? If yes then you want to readreset_indexwithREAD_ONCE. That includes a dependency barrier.
There're a few readers, but only this function will change it (kvm_dirty_ring_reset). Thanks,
Signature · 1 line
Peter Xu
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 20 lines · Peter Xu
Quoted 19 lines · Michael S. Tsirkin
Quoted 17 lines · Peter Xu
Quoted 6 lines · Paolo Bonzini
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
What depends on what here? Looks suspicious ...
Hmm, I think maybe it can be removed because the entry pointer reference below should be an ordering constraint already?
entry->xxxdepends onring->reset_index.Yes that's true, but...
entry = &ring->dirty_gfns[ring->reset_index & (ring->size - 1)]; /* barrier? */ next_slot = READ_ONCE(entry->slot); next_offset = READ_ONCE(entry->offset);... I think
entry->xxxdepends on entry first, then entry depends onreset_index. So it seems fine because all things have a dependency?
Quoted 6 lines
Quoted 3 lines
Is
reset_indexchanged from another thread then? If yes then you want to readreset_indexwithREAD_ONCE. That includes a dependency barrier.There're a few readers, but only this function will change it (
kvm_dirty_ring_reset). Thanks,
Then you don't need any barriers in this function. readers need at least READ_ONCE.
Quoted 2 lines
Signature · 1 line
Peter Xu
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 25 lines · Michael S. Tsirkin
Quoted 24 lines · Peter Xu
Quoted 22 lines · Michael S. Tsirkin
Quoted 10 lines · Peter Xu
Quoted 6 lines · Paolo Bonzini
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
What depends on what here? Looks suspicious ...
Hmm, I think maybe it can be removed because the entry pointer reference below should be an ordering constraint already?
entry->xxxdepends onring->reset_index.Yes that's true, but...
Quoted 7 lines
entry = &ring->dirty_gfns[ring->reset_index & (ring->size - 1)]; /* barrier? */ next_slot = READ_ONCE(entry->slot); next_offset = READ_ONCE(entry->offset);... I think
entry->xxxdepends on entry first, then entry depends onreset_index. So it seems fine because all things have a dependency?Is
reset_indexchanged from another thread then? If yes then you want to readreset_indexwithREAD_ONCE. That includes a dependency barrier.
Quoted 5 lines
Quoted 2 lines
There're a few readers, but only this function will change it (
kvm_dirty_ring_reset). Thanks,Then you don't need any barriers in this function. readers need at least
READ_ONCE.
In our case even an old reset_index should not matter much here imho because the worst case is we read an old reset so we stop pushing to a ring when it's just being reset and at the same time it's soft-full (so an extra user exit even race happened). But I agree it's clearer to READ_ONCE() on readers. Thanks!
Signature · 1 line
Peter Xu
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
In reply to Peter XuQuoted 8 lines · Peter Xu
This patch is heavily based on previous work from Lei Cao <[email protected]> and Paolo Bonzini <[email protected]>. [1]
KVM currently uses large bitmaps to track dirty memory. These bitmaps are copied to userspace when userspace queries KVM for its dirty page information. The use of bitmaps is mostly sufficient for live migration, as large parts of memory are be dirtied from one log-dirty pass to another.
That statement sort of concerns me. If large parts of memory are dirtied, won't this cause the rings to fill up quickly enough to cause a lot of churn between user-space and kernel?
See a possible suggestion to address that below.
Quoted 78 lines
However, in a checkpointing system, the number of
dirty pages is small and in fact it is often bounded---the VM is paused when it has dirtied a pre-defined number of pages. Traversing a large, sparsely populated bitmap to find set bits is time-consuming, as is copying the bitmap to user-space.A similar issue will be there for live migration when the guest memory is huge while the page dirty procedure is trivial. In that case for each dirty sync we need to pull the whole dirty bitmap to userspace and analyse every bit even if it's mostly zeros.
The preferred data structure for above scenarios is a dense list of guest frame numbers (GFN). This patch series stores the dirty list in kernel memory that can be memory mapped into userspace to allow speedy harvesting.
We defined two new data structures:
struct
kvm_dirty_ring;
structkvm_dirty_ring_indexes;Firstly,
kvm_dirty_ringis defined to represent a ring of dirty pages. When dirty tracking is enabled, we can push dirty gfn onto the ring.Secondly,
kvm_dirty_ring_indexesis defined to represent the user/kernel interface of each ring. Currently it contains two
indexes: (1)avail_indexrepresents where we should push our next PFN (written by kernel), while (2)fetch_indexrepresents where the userspace should fetch the next dirty PFN (written by userspace).One complete ring is composed by one
kvm_dirty_ringplus its correspondingkvm_dirty_ring_indexes.Currently, we have N+1 rings for each VM of N vcpus:
- for each vcpu, we have 1 per-vcpu dirty ring,
- for each vm, we have 1 per-vm dirty ringPlease refer to the documentation update in this patch for more details.
Note that this patch implements the core logic of dirty ring buffer. It's still disabled for all archs for now. Also, we'll address some of the other issues in follow up patches before it's firstly enabled on x86.
[1] https://patchwork.kernel.org/patch/10471409/
Signed-off-by Lei Cao <[email protected]>Signed-off-by Paolo Bonzini <[email protected]>Signed-off-by Peter Xu <[email protected]>--- Documentation/virt/kvm/api.txt | 109 +++++++++++++++ arch/x86/kvm/Makefile | 3 +- include/linux/kvm_dirty_ring.h | 67 +++++++++ include/linux/kvm_host.h | 33 +++++ include/linux/kvm_types.h | 1 + include/uapi/linux/kvm.h | 36 +++++ virt/kvm/dirty_ring.c | 156 +++++++++++++++++++++ virt/kvm/kvm_main.c | 240 ++++++++++++++++++++++++++++++++- 8 files changed, 642 insertions(+), 3 deletions(-) create mode 100644 include/linux/kvm_dirty_ring.h create mode 100644 virt/kvm/dirty_ring.cDocumentation/virt/kvm/api.txt +1
-0@@ -231,6 +231,7 @@ Based on their initialization different VMs may have different capabilities. It is thus encouraged to use the vm ioctl to query for capabilities (available with KVM_CAP_CHECK_EXTENSION_VM on the vm fd) + 4.5 KVM_GET_VCPU_MMAP_SIZE Capability: basic
Documentation/virt/kvm/api.txt The KVM_RUN ioctl (cf.) communicates with userspace via a shared · line 253
Quoted 10 lines
Documentation/virt/kvm/api.txt +7
-0@@ -243,6 +244,18 @@ The KVM_RUN ioctl (cf.) communicates with userspace via a shared memory region. This ioctl returns the size of that region. See the KVM_RUN documentation for details. +Besides the size of the KVM_RUN communication region, other areas of +the VCPU file descriptor can be mmap-ed, including: + +- if KVM_CAP_COALESCED_MMIO is available, a page at + KVM_COALESCED_MMIO_PAGE_OFFSET * PAGE_SIZE; for historical reasons, + this page is included in the result of KVM_GET_VCPU_MMAP_SIZE. + KVM_CAP_COALESCED_MMIO is not documented yet.
Does the above really belong to this patch?
Quoted 33 lines
Documentation/virt/kvm/api.txt +12
-0+ +- if KVM_CAP_DIRTY_LOG_RING is available, a number of pages at + KVM_DIRTY_LOG_PAGE_OFFSET * PAGE_SIZE. For more information on + KVM_CAP_DIRTY_LOG_RING, see section 8.3. + 4.6 KVM_SET_MEMORY_REGION@@ -5358,6 +5371,7 @@ CPU when the exception is taken. If this virtual SError is taken to EL1 using AArch64, this value will be reported in the ISS field of ESR_ELx. See KVM_CAP_VCPU_EVENTS for more details. + 8.20 KVM_CAP_HYPERV_SEND_IPI Architectures: x86@@ -5365,6 +5379,7 @@ Architectures: x86 This capability indicates that KVM supports paravirtualized Hyper-V IPI send hypercalls: HvCallSendSyntheticClusterIpi, HvCallSendSyntheticClusterIpiEx. + 8.21 KVM_CAP_HYPERV_DIRECT_TLBFLUSH Architecture: x86@@ -5378,3 +5393,97 @@ handling by KVM (as some KVM hypercall may be mistakenly treated as TLB flush hypercalls by Hyper-V) so userspace should disable KVM identification in CPUID and only exposes Hyper-V identification. In this case, guest thinks it's running on Hyper-V and only use Hyper-V hypercalls. + +8.22 KVM_CAP_DIRTY_LOG_RING + +Architectures: x86 +Parameters: args[0] - size of the dirty log ring
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5410
Quoted 10 lines
Documentation/virt/kvm/api.txt +10
-0+ +KVM is capable of tracking dirty memory using ring buffers that are +mmaped into userspace; there is one dirty ring per vcpu and one global +ring per vm. + +One dirty ring has the following two major structures: + +struct kvm_dirty_ring { + u16 dirty_index; + u16 reset_index;
What is the benefit of using u16 for that? That means with 4K pages, you can share at most 256M of dirty memory each time? That seems low to me, especially since it's sufficient to touch one byte in a page to dirty it.
Actually, this is not consistent with the definition in the code ;-) So I'll assume it's actually u32.
Quoted 8 lines
Documentation/virt/kvm/api.txt +8
-0+ u32 size; + u32 soft_limit; + spinlock_t lock; + struct kvm_dirty_gfn *dirty_gfns; +}; + +struct kvm_dirty_ring_indexes { + __u32 avail_index; /* set by kernel */
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5428
Quoted 10 lines
Documentation/virt/kvm/api.txt +10
-0+ __u32 fetch_index; /* set by userspace */ +}; + +While for each of the dirty entry it's defined as: + +struct kvm_dirty_gfn { + __u32 pad; + __u32 slot; /* as_id | slot_id */ + __u64 offset; +};
Like other have suggested, I think we might used "pad" to store size information to be able to dirty large pages more efficiently.
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5432
Quoted 4 lines
Documentation/virt/kvm/api.txt +4
-0+ +The fields in kvm_dirty_ring will be only internal to KVM itself, +while the fields in kvm_dirty_ring_indexes will be exposed to +userspace to be either read or written.
The sentence above is confusing when contrasted with the "set by kernel" comment above.
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5434
Quoted 2 lines
Documentation/virt/kvm/api.txt +2
-0+ +The two indices in the ring buffer are free running counters.
Nit: this patch uses both "indices" and "indexes". Both are correct, but it would be nice to be consistent.
Quoted 10 lines
Documentation/virt/kvm/api.txt +10
-0+ +In pseudocode, processing the ring buffer looks like this: + + idx = load-acquire(&ring->fetch_index); + while (idx != ring->avail_index) { + struct kvm_dirty_gfn *entry; + entry = &ring->dirty_gfns[idx & (size - 1)]; + ... + + idx++;
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5454
Quoted 10 lines
Documentation/virt/kvm/api.txt +10
-0+ } + ring->fetch_index = idx; + +Userspace calls KVM_ENABLE_CAP ioctl right after KVM_CREATE_VM ioctl +to enable this capability for the new guest and set the size of the +rings. It is only allowed before creating any vCPU, and the size of +the ring must be a power of two. The larger the ring buffer, the less +likely the ring is full and the VM is forced to exit to userspace. The +optimal size depends on the workload, but it is recommended that it be +at least 64 KiB (4096 entries).
Is there anything in the design that would preclude resizing the ring buffer at a later time? Presumably, you'd want a large ring while you are doing things like migrations, but it's mostly useless when you are not monitoring memory. So it would be nice to be able to call KVM_ENABLE_CAP at any time to adjust the size.
As I read the current code, one of the issue would be the mapping of the rings in case of a later extension where we added something beyond the rings. But I'm not sure that's a big deal at the moment.
Quoted 20 lines
Documentation/virt/kvm/api.txt +20
-0+ +After the capability is enabled, userspace can mmap the global ring +buffer (kvm_dirty_gfn[], offset KVM_DIRTY_LOG_PAGE_OFFSET) and the +indexes (kvm_dirty_ring_indexes, offset 0) from the VM file +descriptor. The per-vcpu dirty ring instead is mmapped when the vcpu +is created, similar to the kvm_run struct (kvm_dirty_ring_indexes +locates inside kvm_run, while kvm_dirty_gfn[] at offset +KVM_DIRTY_LOG_PAGE_OFFSET). + +Just like for dirty page bitmaps, the buffer tracks writes to +all user memory regions for which the KVM_MEM_LOG_DIRTY_PAGES flag was +set in KVM_SET_USER_MEMORY_REGION. Once a memory region is registered +with the flag set, userspace can start harvesting dirty pages from the +ring buffer. + +To harvest the dirty pages, userspace accesses the mmaped ring buffer +to read the dirty GFNs up to avail_index, and sets the fetch_index +accordingly. This can be done when the guest is running or paused, +and dirty pages need not be collected all at once. After processing +one or more entries in the ring buffer, userspace calls the VM ioctl
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5483
Quoted 10 lines
Documentation/virt/kvm/api.txt +9
-0+KVM_RESET_DIRTY_RINGS to notify the kernel that it has updated +fetch_index and to mark those pages clean. Therefore, the ioctl +must be called *before* reading the content of the dirty pages. + +However, there is a major difference comparing to the +KVM_GET_DIRTY_LOG interface in that when reading the dirty ring from +userspace it's still possible that the kernel has not yet flushed the +hardware dirty buffers into the kernel buffer. To achieve that, one +needs to kick the vcpu out for a hardware buffer flush (vmexit).
When you refer to "buffers", are you referring to the cache lines that contain the ring buffers, or to something else?
I'm a bit confused by this sentence. I think that you mean that a VCPU may still be running while you read its ring buffer, in which case the values in the ring buffer are not necessarily in memory yet, so not visible to a different CPU. But I wonder if you can't make this requirement to cause a vmexit unnecessary by carefully ordering the writes, to make sure that the fetch_index is updated only after the corresponding ring entries have been written to memory,
In other words, as seen by user-space, you would not care that the ring entries have not been flushed as long as the fetch_index itself is guaranteed to still be behind the not-flushed-yet entries.
(I would know how to do that on a different architecture, not sure for x86)
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5489
Quoted 6 lines
Documentation/virt/kvm/api.txt +6
-0+ +If one of the ring buffers is full, the guest will exit to userspace +with the exit reason set to KVM_EXIT_DIRTY_LOG_FULL, and the +KVM_RUN ioctl will return -EINTR. Once that happens, userspace +should pause all the vcpus, then harvest all the dirty pages and +rearm the dirty traps. It can unpause the guest after that.
Except for the condition above, why is it necessary to pause other VCPUs than the one being harvested?
Quoted 29 lines
arch/x86/kvm/Makefile +2
-1@@ -5,7 +5,8 @@ ccflags-y += -Iarch/x86/kvm KVM := ../../../virt/kvm kvm-y += $(KVM)/kvm_main.o $(KVM)/coalesced_mmio.o \ - $(KVM)/eventfd.o $(KVM)/irqchip.o $(KVM)/vfio.o + $(KVM)/eventfd.o $(KVM)/irqchip.o $(KVM)/vfio.o \ + $(KVM)/dirty_ring.o kvm-$(CONFIG_KVM_ASYNC_PF) += $(KVM)/async_pf.o kvm-y += x86.o emulate.o i8259.o irq.o lapic.o \include/linux/kvm_dirty_ring.h new file +9
-0@@ -0,0 +1,67 @@ +#ifndef KVM_DIRTY_RING_H +#define KVM_DIRTY_RING_H + +/* +* struct kvm_dirty_ring is defined in include/uapi/linux/kvm.h. +* +* dirty_ring: shared with userspace via mmap. It is the compact list +* that holds the dirty pages. +* dirty_index: free running counter that points to the next slot in
include/linux/kvm_dirty_ring.h line 19
Quoted 10 lines
include/linux/kvm_dirty_ring.h new file +10
-0+* dirty_ring->dirty_gfns where a new dirty page should go. +* reset_index: free running counter that points to the next dirty page +* in dirty_ring->dirty_gfns for which dirty trap needs to +* be reenabled +* size: size of the compact list, dirty_ring->dirty_gfns +* soft_limit: when the number of dirty pages in the list reaches this +* limit, vcpu that owns this ring should exit to userspace +* to allow userspace to harvest all the dirty pages +* lock: protects dirty_ring, only in use if this is the global +* ring
If that's not used for vcpu rings, maybe move it out of kvm_dirty_ring?
include/linux/kvm_dirty_ring.h line 22
Quoted 3 lines
include/linux/kvm_dirty_ring.h new file +3
-0+* +* The number of dirty pages in the ring is calculated by, +* dirty_index - reset_index
Nit: the code calls it "used" (in kvm_dirty_ring_used). Maybe find an unambiguous terminology. What about "posted", as in
The number of posted dirty pages, i.e. the number of dirty pages in the ring, is calculated as dirty_index - reset_index by function kvm_dirty_ring_posted
(Replace "posted" by any adjective of your liking)
include/linux/kvm_dirty_ring.h line 28
Quoted 6 lines
include/linux/kvm_dirty_ring.h new file +6
-0+* +* kernel increments dirty_ring->indices.avail_index after dirty index +* is incremented. When userspace harvests the dirty pages, it increments +* dirty_ring->indices.fetch_index up to dirty_ring->indices.avail_index. +* When kernel reenables dirty traps for the dirty pages, it increments +* reset_index up to dirty_ring->indices.fetch_index.
Userspace should not be trusted to be doing this, see below.
Quoted 24 lines
include/linux/kvm_dirty_ring.h new file +24
-0+* +*/ +struct kvm_dirty_ring { +u32 dirty_index; +u32 reset_index; +u32 size; +u32 soft_limit; +spinlock_t lock; +struct kvm_dirty_gfn *dirty_gfns; +}; + +u32 kvm_dirty_ring_get_rsvd_entries (void ); +int kvm_dirty_ring_alloc (struct kvm *kvm,struct kvm_dirty_ring *ring); + +/* +* called with kvm->slots_lock held, returns the number of +* processed pages. +*/ +int kvm_dirty_ring_reset (struct kvm *kvm, +struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes); + +/* +* returns 0: successfully pushed
include/linux/kvm_dirty_ring.h line 62
Quoted 10 lines
include/linux/kvm_dirty_ring.h new file +10
-0+* 1: successfully pushed, soft limit reached, +* vcpu should exit to userspace +* -EBUSY: unable to push, dirty ring full. +*/ +int kvm_dirty_ring_push (struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes, +u32 slot,u64 offset,bool lock); + +/* for use in vm_operations_struct */ +struct page *kvm_dirty_ring_get_page (struct kvm_dirty_ring *ring,u32 i);
Not very clear what 'i' means, seems to be a page offset based on call sites?
Quoted 34 lines
include/linux/kvm_dirty_ring.h new file +5
-0+ +void kvm_dirty_ring_free (struct kvm_dirty_ring *ring); +bool kvm_dirty_ring_full (struct kvm_dirty_ring *ring); + +#endif include/linux/kvm_host.h +3
-0@@ -34,6 +34,7 @@#include <linux/kvm_types.h> #include <asm/kvm_host.h> +#include <linux/kvm_dirty_ring.h> #ifndef KVM_MAX_VCPU_ID #define KVM_MAX_VCPU_ID KVM_MAX_VCPUS@@ -146,6 +147,7 @@ static inline bool is_error_page(struct page *page)#define KVM_REQ_MMU_RELOAD (1 | KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)#define KVM_REQ_PENDING_TIMER 2#define KVM_REQ_UNHALT 3 +#define KVM_REQ_DIRTY_RING_FULL 4#define KVM_REQUEST_ARCH_BASE 8#define KVM_ARCH_REQ_FLAGS (nr, flags) ({ \@@ -321,6 +323,7 @@ struct kvm_vcpu {bool ready;struct kvm_vcpu_arch arch;struct dentry *debugfs_dentry; +struct kvm_dirty_ring dirty_ring; };static inline int kvm_vcpu_exiting_guest_mode (struct kvm_vcpu *vcpu)
include/linux/kvm_host.h struct kvm { · line 510
Quoted 7 lines
include/linux/kvm_host.h +4
-0@@ -501,6 +504,10 @@ struct kvm {struct srcu_struct srcu;struct srcu_struct irq_srcu;pid_t userspace_pid; +/* Data structure to be exported by mmap(kvm->fd, 0) */ +struct kvm_vm_run *vm_run; +u32 dirty_ring_size; +struct kvm_dirty_ring vm_dirty_ring;
If you remove the lock from struct kvm_dirty_ring, you could just put it there.
Quoted 152 lines
include/linux/kvm_host.h +26
-0};#define kvm_err (fmt, ...) \@@ -832,6 +839,8 @@ void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,gfn_t gfn_offset, unsigned long mask); +void kvm_reset_dirty_gfn (struct kvm *kvm,u32 slot,u64 offset,u64 mask); +int kvm_vm_ioctl_get_dirty_log (struct kvm *kvm,struct kvm_dirty_log *log); int kvm_vm_ioctl_clear_dirty_log(struct kvm *kvm,@@ -1411,4 +1420,28 @@ int kvm_vm_create_worker_thread(struct kvm *kvm, kvm_vm_thread_fn_t thread_fn, uintptr_t data, const char *name, struct task_struct **thread_ptr); +/* +* This defines how many reserved entries we want to keep before we +* kick the vcpu to the userspace to avoid dirty ring full. This +* value can be tuned to higher if e.g. PML is enabled on the host. +*/ +#define KVM_DIRTY_RING_RSVD_ENTRIES 64 + +/* Max number of entries allowed for each kvm dirty ring */ +#define KVM_DIRTY_RING_MAX_ENTRIES 65536 + +/* +* Arch needs to define these macro after implementing the dirty ring +* feature. KVM_DIRTY_LOG_PAGE_OFFSET should be defined as the +* starting page offset of the dirty ring structures, while +* KVM_DIRTY_RING_VERSION should be defined as >=1. By default, this +* feature is off on all archs. +*/ +#ifndef KVM_DIRTY_LOG_PAGE_OFFSET +#define KVM_DIRTY_LOG_PAGE_OFFSET 0 +#endif +#ifndef KVM_DIRTY_RING_VERSION +#define KVM_DIRTY_RING_VERSION 0 +#endif +#endif include/linux/kvm_types.h +1
-0@@ -11,6 +11,7 @@ struct kvm_irq_routing_table;struct kvm_memory_slot ;struct kvm_one_reg ;struct kvm_run ; +struct kvm_vm_run ;struct kvm_userspace_memory_region ;struct kvm_vcpu ;struct kvm_vcpu_init ;include/uapi/linux/kvm.h +36
-0@@ -236,6 +236,7 @@ struct kvm_hyperv_exit {#define KVM_EXIT_IOAPIC_EOI 26#define KVM_EXIT_HYPERV 27#define KVM_EXIT_ARM_NISV 28 +#define KVM_EXIT_DIRTY_RING_FULL 29/* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */ @@ -247,6 +248,11 @@ struct kvm_hyperv_exit {/* Encounter unexpected vm-exit reason */ #define KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON 4 +struct kvm_dirty_ring_indexes { +__u32 avail_index;/* set by kernel */ +__u32 fetch_index;/* set by userspace */ +}; +/* for KVM_RUN, returned by mmap(vcpu_fd, offset=0) */ struct kvm_run {/* in */ @@ -421,6 +427,13 @@ struct kvm_run {struct kvm_sync_regs regs;char padding[SYNC_REGS_SIZE_BYTES ]; } s; + +struct kvm_dirty_ring_indexes vcpu_ring_indexes; +}; + +/* Returned by mmap(kvm->fd, offset=0) */ +struct kvm_vm_run { +struct kvm_dirty_ring_indexes vm_ring_indexes; };/* for KVM_REGISTER_COALESCED_MMIO / KVM_UNREGISTER_COALESCED_MMIO */ @@ -1009,6 +1022,7 @@ struct kvm_ppc_resize_hpt {#define KVM_CAP_PPC_GUEST_DEBUG_SSTEP 176#define KVM_CAP_ARM_NISV_TO_USER 177#define KVM_CAP_ARM_INJECT_EXT_DABT 178 +#define KVM_CAP_DIRTY_LOG_RING 179#ifdef KVM_CAP_IRQ_ROUTING @@ -1472,6 +1486,9 @@ struct kvm_enc_region {/* Available with KVM_CAP_ARM_SVE */ #define KVM_ARM_VCPU_FINALIZE _IOW(KVMIO, 0xc2, int) +/* Available with KVM_CAP_DIRTY_LOG_RING */ +#define KVM_RESET_DIRTY_RINGS _IO(KVMIO, 0xc3) +/* Secure Encrypted Virtualization command */ enum sev_cmd_id {/* Guest initialization commands */ @@ -1622,4 +1639,23 @@ struct kvm_hyperv_eventfd {#define KVM_HYPERV_CONN_ID_MASK 0x00ffffff#define KVM_HYPERV_EVENTFD_DEASSIGN (1 << 0) +/* +* The following are the requirements for supporting dirty log ring +* (by enabling KVM_DIRTY_LOG_PAGE_OFFSET). +* +* 1. Memory accesses by KVM should call kvm_vcpu_write_* instead +* of kvm_write_* so that the global dirty ring is not filled up +* too quickly. +* 2. kvm_arch_mmu_enable_log_dirty_pt_masked should be defined for +* enabling dirty logging. +* 3. There should not be a separate step to synchronize hardware +* dirty bitmap with KVM's. +*/ + +struct kvm_dirty_gfn { +__u32 pad; +__u32 slot; +__u64 offset; +}; +#endif /* __LINUX_KVM_H */ virt/kvm/dirty_ring.c new file +13
-0@@ -0,0 +1,156 @@ +#include <linux/kvm_host.h> +#include <linux/kvm.h> +#include <linux/vmalloc.h> +#include <linux/kvm_dirty_ring.h> + +u32 kvm_dirty_ring_get_rsvd_entries (void ) +{ +return KVM_DIRTY_RING_RSVD_ENTRIES +kvm_cpu_dirty_log_size (); +} + +int kvm_dirty_ring_alloc (struct kvm *kvm,struct kvm_dirty_ring *ring) +{ +u32 size = kvm->dirty_ring_size;
virt/kvm/dirty_ring.c line 23
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+ + ring->dirty_gfns =vmalloc (size); +if (!ring->dirty_gfns) +return -ENOMEM ; +memset (ring->dirty_gfns,0 , size); + + ring->size = size /sizeof (struct kvm_dirty_gfn ); + ring->soft_limit = + (kvm->dirty_ring_size /sizeof (struct kvm_dirty_gfn )) - +kvm_dirty_ring_get_rsvd_entries ();
Minor, but what about
ring->soft_limit = ring->size - kvm_dirty_ring_get_rsvd_entries();
Quoted 9 lines
virt/kvm/dirty_ring.c new file +9
-0+ ring->dirty_index =0 ; + ring->reset_index =0 ; +spin_lock_init (&ring->lock); + +return 0 ; +} + +int kvm_dirty_ring_reset(struct kvm *kvm, + struct kvm_dirty_ring *ring,
virt/kvm/dirty_ring.c line 42
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+ struct kvm_dirty_ring_indexes *indexes) +{ +u32 cur_slot, next_slot; +u64 cur_offset, next_offset; +unsigned long mask; +u32 fetch; +int count =0 ; +struct kvm_dirty_gfn *entry; + + fetch =READ_ONCE (indexes->fetch_index);
If I understand correctly, if a malicious user-space writes ring->reset_index + 1 into fetch_index, the loop below will execute 4 billion times.
virt/kvm/dirty_ring.c line 44
Quoted 2 lines
virt/kvm/dirty_ring.c new file +2
-0+if (fetch == ring->reset_index) +return 0 ;
To protect against scenario above, I would have something like:
if (fetch - ring->reset_index >= ring->size) return -EINVAL;
Quoted 18 lines
virt/kvm/dirty_ring.c new file +18
-0+ + entry = &ring->dirty_gfns[ring->reset_index & (ring->size -1 )]; +/* +* The ring buffer is shared with userspace, which might mmap +* it and concurrently modify slot and offset. Userspace must +* not be trusted! READ_ONCE prevents the compiler from changing +* the values after they've been range-checked (the checks are +* in kvm_reset_dirty_gfn). +*/ +smp_read_barrier_depends (); + cur_slot =READ_ONCE (entry->slot); + cur_offset =READ_ONCE (entry->offset); + mask =1 ; + count++; + ring->reset_index++; +while (ring->reset_index != fetch) { + entry = &ring->dirty_gfns[ring->reset_index & (ring->size -1 )]; +smp_read_barrier_depends ();
virt/kvm/dirty_ring.c line 72
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+ next_slot =READ_ONCE (entry->slot); + next_offset =READ_ONCE (entry->offset); + ring->reset_index++; + count++; +/* +* Try to coalesce the reset operations when the guest is +* scanning pages in the same slot. +*/ +if (next_slot == cur_slot) { +int delta = next_offset - cur_offset;
Since you diff two u64, shouldn't that be an i64 rather than int?
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+ +if (delta >=0 && delta <BITS_PER_LONG ) { + mask |=1ull << delta; +continue ; + } + +/* Backwards visit, careful about overflows! */ +if (delta > -BITS_PER_LONG && delta <0 && + (mask << -delta >> -delta) == mask) { + cur_offset = next_offset;
virt/kvm/dirty_ring.c line 92
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+ mask = (mask << -delta) |1 ; +continue ; + } + } +kvm_reset_dirty_gfn (kvm, cur_slot, cur_offset, mask); + cur_slot = next_slot; + cur_offset = next_offset; + mask =1 ; + } +kvm_reset_dirty_gfn (kvm, cur_slot, cur_offset, mask);
So if you did not coalesce the last one, you call kvm_reset_dirty_gfn twice? Something smells weird about this loop ;-) I have a gut feeling that it could be done in a single while loop combined with the entry test, but I may be wrong.
Quoted 13 lines
virt/kvm/dirty_ring.c new file +13
-0+ +return count; +} + +static inline u32 kvm_dirty_ring_used (struct kvm_dirty_ring *ring) +{ +return ring->dirty_index - ring->reset_index; +} + +bool kvm_dirty_ring_full (struct kvm_dirty_ring *ring) +{ +return kvm_dirty_ring_used (ring) >= ring->size; +}
virt/kvm/dirty_ring.c line 115
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+ +/* +* Returns: +* >0 if we should kick the vcpu out, +* =0 if the gfn pushed successfully, or, +* <0 if error (e.g. ring full) +*/ +int kvm_dirty_ring_push (struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes, +u32 slot,u64 offset,bool lock)
Obviously, if you go with the suggestion to have a "lock" only in struct kvm, then you'd have to pass a lock ptr instead of a bool.
Quoted 8 lines
virt/kvm/dirty_ring.c new file +8
-0+{ +int ret; +struct kvm_dirty_gfn *entry; + +if (lock) +spin_lock (&ring->lock); + +if (kvm_dirty_ring_full (ring)) {
virt/kvm/dirty_ring.c line 133
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+ ret = -EBUSY ; + goto out; + } + + entry = &ring->dirty_gfns[ring->dirty_index & (ring->size -1 )]; + entry->slot = slot; + entry->offset = offset; +smp_wmb (); + ring->dirty_index++; +WRITE_ONCE (indexes->avail_index, ring->dirty_index);
Following up on comment about having to vmexit other VCPUs above:
If you have a write barrier for the entry, and then a write once for the index, isn't that sufficient to ensure that another CPU will pick up the right values in the right order?
virt/kvm/dirty_ring.c line 145
Quoted 12 lines
virt/kvm/dirty_ring.c new file +12
-0+ ret =kvm_dirty_ring_used (ring) >= ring->soft_limit; +pr_info ("%s: slot %u offset %llu used %u\n" , + __func__, slot, offset,kvm_dirty_ring_used (ring)); + +out: +if (lock) +spin_unlock (&ring->lock); + +return ret; +} + +struct page *kvm_dirty_ring_get_page (struct kvm_dirty_ring *ring,u32 i)
Still don't like 'i' :-)
(Stopped my review here for lack of time, decided to share what I had so far)
Quoted 363 lines
virt/kvm/dirty_ring.c new file +11
-0+{ +return vmalloc_to_page ((void *)ring->dirty_gfns + i *PAGE_SIZE ); +} + +void kvm_dirty_ring_free (struct kvm_dirty_ring *ring) +{ +if (ring->dirty_gfns) { +vfree (ring->dirty_gfns); + ring->dirty_gfns =NULL ; + } +}virt/kvm/kvm_main.c +234
-2@@ -64,6 +64,8 @@#define CREATE_TRACE_POINTS #include <trace/events/kvm.h> +#include <linux/kvm_dirty_ring.h> +/* Worst case buffer size needed for holding an integer. */ #define ITOA_MAX_LEN 12@@ -149,6 +151,10 @@ static void mark_page_dirty_in_slot(struct kvm *kvm,struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, gfn_t gfn); +static void mark_page_dirty_in_ring (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *slot, +gfn_t gfn); __visible bool kvm_rebooting;EXPORT_SYMBOL_GPL (kvm_rebooting);@@ -359,11 +365,22 @@ int kvm_vcpu_init(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned id) vcpu->preempted = false; vcpu->ready = false; +if (kvm->dirty_ring_size) { + r =kvm_dirty_ring_alloc (vcpu->kvm, &vcpu->dirty_ring); +if (r) { + kvm->dirty_ring_size =0 ; + goto fail_free_run; + } + } + r =kvm_arch_vcpu_init (vcpu);if (r <0 ) - goto fail_free_run; + goto fail_free_ring;return 0 ; +fail_free_ring: +if (kvm->dirty_ring_size) +kvm_dirty_ring_free (&vcpu->dirty_ring); fail_free_run:free_page ((unsigned long )vcpu->run); fail:@@ -381,6 +398,8 @@ void kvm_vcpu_uninit(struct kvm_vcpu *vcpu)put_pid (rcu_dereference_protected (vcpu->pid,1 ));kvm_arch_vcpu_uninit (vcpu);free_page ((unsigned long )vcpu->run); +if (vcpu->kvm->dirty_ring_size) +kvm_dirty_ring_free (&vcpu->dirty_ring); }EXPORT_SYMBOL_GPL (kvm_vcpu_uninit);@@ -690,6 +709,7 @@ static struct kvm *kvm_create_vm(unsigned long type)struct kvm *kvm =kvm_arch_alloc_vm ();int r = -ENOMEM ;int i; +struct page *page;if (!kvm)return ERR_PTR (-ENOMEM );@@ -705,6 +725,14 @@ static struct kvm *kvm_create_vm(unsigned long type)BUILD_BUG_ON (KVM_MEM_SLOTS_NUM >SHRT_MAX ); + page =alloc_page (GFP_KERNEL | __GFP_ZERO); +if (!page) { + r = -ENOMEM ; + goto out_err_alloc_page; + } + kvm->vm_run =page_address (page); +BUILD_BUG_ON (sizeof (struct kvm_vm_run ) >PAGE_SIZE ); +if (init_srcu_struct (&kvm->srcu)) goto out_err_no_srcu;if (init_srcu_struct (&kvm->irq_srcu))@@ -775,6 +803,9 @@ static struct kvm *kvm_create_vm(unsigned long type) out_err_no_irq_srcu:cleanup_srcu_struct (&kvm->srcu); out_err_no_srcu: +free_page ((unsigned long )page); + kvm->vm_run =NULL ; +out_err_alloc_page:kvm_arch_free_vm (kvm);mmdrop (current->mm);return ERR_PTR (r);@@ -800,6 +831,15 @@ static void kvm_destroy_vm(struct kvm *kvm)int i;struct mm_struct *mm = kvm->mm; +if (kvm->dirty_ring_size) { +kvm_dirty_ring_free (&kvm->vm_dirty_ring); + } + +if (kvm->vm_run) { +free_page ((unsigned long )kvm->vm_run); + kvm->vm_run =NULL ; + } +kvm_uevent_notify_change (KVM_EVENT_DESTROY_VM , kvm);kvm_destroy_vm_debugfs (kvm);kvm_arch_sync_events (kvm);@@ -2301,7 +2341,7 @@ static void mark_page_dirty_in_slot(struct kvm *kvm, {if (memslot && memslot->dirty_bitmap) {unsigned long rel_gfn = gfn - memslot->base_gfn; - +mark_page_dirty_in_ring (kvm, vcpu, memslot, gfn);set_bit_le (rel_gfn, memslot->dirty_bitmap); } }@@ -2649,6 +2689,13 @@ void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode) }EXPORT_SYMBOL_GPL (kvm_vcpu_on_spin); +static bool kvm_fault_in_dirty_ring (struct kvm *kvm,struct vm_fault *vmf) +{ +return (vmf->pgoff >=KVM_DIRTY_LOG_PAGE_OFFSET ) && + (vmf->pgoff <KVM_DIRTY_LOG_PAGE_OFFSET + + kvm->dirty_ring_size /PAGE_SIZE ); +} +static vm_fault_t kvm_vcpu_fault (struct vm_fault *vmf) {struct kvm_vcpu *vcpu = vmf->vma->vm_file->private_data;@@ -2664,6 +2711,10 @@ static vm_fault_t kvm_vcpu_fault(struct vm_fault *vmf) else if (vmf->pgoff == KVM_COALESCED_MMIO_PAGE_OFFSET) page =virt_to_page (vcpu->kvm->coalesced_mmio_ring);#endif + else if (kvm_fault_in_dirty_ring(vcpu->kvm, vmf)) + page =kvm_dirty_ring_get_page ( + &vcpu->dirty_ring, + vmf->pgoff -KVM_DIRTY_LOG_PAGE_OFFSET ); else return kvm_arch_vcpu_fault(vcpu, vmf);get_page (page);@@ -3259,12 +3310,162 @@ static long kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg)#endif case KVM_CAP_NR_MEMSLOTS :return KVM_USER_MEM_SLOTS ; +case KVM_CAP_DIRTY_LOG_RING : +/* Version will be zero if arch didn't implement it */ +return KVM_DIRTY_RING_VERSION ;default :break ; }return kvm_vm_ioctl_check_extension (kvm, arg); } +static void mark_page_dirty_in_ring (struct kvm *kvm, +struct kvm_vcpu *vcpu, +struct kvm_memory_slot *slot, +gfn_t gfn) +{ +u32 as_id =0 ; +u64 offset; +int ret; +struct kvm_dirty_ring *ring; +struct kvm_dirty_ring_indexes *indexes; +bool is_vm_ring; + +if (!kvm->dirty_ring_size) +return ; + + offset = gfn - slot->base_gfn; + +if (vcpu) { + as_id =kvm_arch_vcpu_memslots_id (vcpu); + }else { + as_id =0 ; + vcpu =kvm_get_running_vcpu (); + } + +if (vcpu) { + ring = &vcpu->dirty_ring; + indexes = &vcpu->run->vcpu_ring_indexes; + is_vm_ring = false; + }else { +/* +* Put onto per vm ring because no vcpu context. Kick +* vcpu0 if ring is full. +*/ + vcpu = kvm->vcpus[0 ]; + ring = &kvm->vm_dirty_ring; + indexes = &kvm->vm_run->vm_ring_indexes; + is_vm_ring = true; + } + + ret =kvm_dirty_ring_push (ring, indexes, + (as_id <<16 )|slot->id, offset, + is_vm_ring); +if (ret <0 ) { +if (is_vm_ring) +pr_warn_once ("vcpu %d dirty log overflow\n" , + vcpu->vcpu_id); +else +pr_warn_once ("per-vm dirty log overflow\n" ); +return ; + } + +if (ret) +kvm_make_request (KVM_REQ_DIRTY_RING_FULL , vcpu); +} + +void kvm_reset_dirty_gfn (struct kvm *kvm,u32 slot,u64 offset,u64 mask) +{ +struct kvm_memory_slot *memslot; +int as_id, id; + + as_id = slot >>16 ; + id = (u16 )slot; +if (as_id >=KVM_ADDRESS_SPACE_NUM || id >=KVM_USER_MEM_SLOTS ) +return ; + + memslot =id_to_memslot (__kvm_memslots (kvm, as_id), id); +if (offset >= memslot->npages) +return ; + +spin_lock (&kvm->mmu_lock); +/* FIXME: we should use a single AND operation, but there is no +* applicable atomic API. +*/ +while (mask) { +clear_bit_le (offset +__ffs (mask), memslot->dirty_bitmap); + mask &= mask -1 ; + } + +kvm_arch_mmu_enable_log_dirty_pt_masked (kvm, memslot, offset, mask); +spin_unlock (&kvm->mmu_lock); +} + +static int kvm_vm_ioctl_enable_dirty_log_ring (struct kvm *kvm,u32 size) +{ +int r; + +/* the size should be power of 2 */ +if (!size || (size & (size -1 ))) +return -EINVAL ; + +/* Should be bigger to keep the reserved entries, or a page */ +if (size <kvm_dirty_ring_get_rsvd_entries () * +sizeof (struct kvm_dirty_gfn ) || size <PAGE_SIZE ) +return -EINVAL ; + +if (size >KVM_DIRTY_RING_MAX_ENTRIES * +sizeof (struct kvm_dirty_gfn )) +return -E2BIG ; + +/* We only allow it to set once */ +if (kvm->dirty_ring_size) +return -EINVAL ; + +mutex_lock (&kvm->lock); + +if (kvm->created_vcpus) { +/* We don't allow to change this value after vcpu created */ + r = -EINVAL ; + }else { + kvm->dirty_ring_size = size; + r =kvm_dirty_ring_alloc (kvm, &kvm->vm_dirty_ring); +if (r) { +/* Unset dirty ring */ + kvm->dirty_ring_size =0 ; + } + } + +mutex_unlock (&kvm->lock); +return r; +} + +static int kvm_vm_ioctl_reset_dirty_pages (struct kvm *kvm) +{ +int i; +struct kvm_vcpu *vcpu; +int cleared =0 ; + +if (!kvm->dirty_ring_size) +return -EINVAL ; + +mutex_lock (&kvm->slots_lock); + + cleared +=kvm_dirty_ring_reset (kvm, &kvm->vm_dirty_ring, + &kvm->vm_run->vm_ring_indexes); + +kvm_for_each_vcpu (i, vcpu, kvm) + cleared +=kvm_dirty_ring_reset (vcpu->kvm, &vcpu->dirty_ring, + &vcpu->run->vcpu_ring_indexes); + +mutex_unlock (&kvm->slots_lock); + +if (cleared) +kvm_flush_remote_tlbs (kvm); + +return cleared; +} +int __attribute__((weak))kvm_vm_ioctl_enable_cap (struct kvm *kvm,struct kvm_enable_cap *cap) {@@ -3282,6 +3483,8 @@ static int kvm_vm_ioctl_enable_cap_generic(struct kvm *kvm, kvm->manual_dirty_log_protect = cap->args[0 ];return 0 ;#endif +case KVM_CAP_DIRTY_LOG_RING : +return kvm_vm_ioctl_enable_dirty_log_ring (kvm, cap->args[0 ]);default :return kvm_vm_ioctl_enable_cap (kvm, cap); }@@ -3469,6 +3672,9 @@ static long kvm_vm_ioctl(struct file *filp,case KVM_CHECK_EXTENSION : r =kvm_vm_ioctl_check_extension_generic (kvm, arg);break ; +case KVM_RESET_DIRTY_RINGS : + r =kvm_vm_ioctl_reset_dirty_pages (kvm); +break ;default : r =kvm_arch_vm_ioctl (filp, ioctl, arg); }@@ -3517,9 +3723,39 @@ static long kvm_vm_compat_ioctl(struct file *filp, }#endif +static vm_fault_t kvm_vm_fault (struct vm_fault *vmf) +{ +struct kvm *kvm = vmf->vma->vm_file->private_data; +struct page *page =NULL ; + +if (vmf->pgoff ==0 ) + page =virt_to_page (kvm->vm_run); +else if (kvm_fault_in_dirty_ring (kvm, vmf)) + page =kvm_dirty_ring_get_page ( + &kvm->vm_dirty_ring, + vmf->pgoff -KVM_DIRTY_LOG_PAGE_OFFSET ); +else +return VM_FAULT_SIGBUS ; + +get_page (page); + vmf->page = page; +return 0 ; +} + +static const struct vm_operations_struct kvm_vm_vm_ops = { + .fault = kvm_vm_fault, +}; + +static int kvm_vm_mmap (struct file *file,struct vm_area_struct *vma) +{ + vma->vm_ops = &kvm_vm_vm_ops;
virt/kvm/kvm_main.c static long kvm_vm_compat_ioctl(struct file *filp, · line 3761
Quoted 10 lines
virt/kvm/kvm_main.c +4
-0+return 0 ; +} +static struct file_operations kvm_vm_fops = { .release = kvm_vm_release, .unlocked_ioctl = kvm_vm_ioctl, + .mmap = kvm_vm_mmap, .llseek = noop_llseek,KVM_COMPAT (kvm_vm_compat_ioctl), };
--
Cheers,
Christophe de Dinechin (IRC c3d)
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 11 lines · Christophe de Dinechin
Quoted 8 lines · Peter Xu
This patch is heavily based on previous work from Lei Cao <[email protected]> and Paolo Bonzini <[email protected]>. [1]
KVM currently uses large bitmaps to track dirty memory. These bitmaps are copied to userspace when userspace queries KVM for its dirty page information. The use of bitmaps is mostly sufficient for live migration, as large parts of memory are be dirtied from one log-dirty pass to another.
Quoted 3 lines
That statement sort of concerns me. If large parts of memory are dirtied, won't this cause the rings to fill up quickly enough to cause a lot of churn between user-space and kernel?
We have cpu-throttle in the QEMU to explicitly provide some "churns" just to slow the vcpu down. If dirtying is heavy during migrations then we might prefer some churns.. Also, this should not replace the old dirty_bitmap, but it should be a new interface only. Even if we want to switch this as default we'll definitely still keep the old interface when the user wants it in some scenarios.
Quoted 84 lines
See a possible suggestion to address that below.
Quoted 81 lines
However, in a checkpointing system, the number of
dirty pages is small and in fact it is often bounded---the VM is paused when it has dirtied a pre-defined number of pages. Traversing a large, sparsely populated bitmap to find set bits is time-consuming, as is copying the bitmap to user-space.A similar issue will be there for live migration when the guest memory is huge while the page dirty procedure is trivial. In that case for each dirty sync we need to pull the whole dirty bitmap to userspace and analyse every bit even if it's mostly zeros.
The preferred data structure for above scenarios is a dense list of guest frame numbers (GFN). This patch series stores the dirty list in kernel memory that can be memory mapped into userspace to allow speedy harvesting.
We defined two new data structures:
struct
kvm_dirty_ring;
structkvm_dirty_ring_indexes;Firstly,
kvm_dirty_ringis defined to represent a ring of dirty pages. When dirty tracking is enabled, we can push dirty gfn onto the ring.Secondly,
kvm_dirty_ring_indexesis defined to represent the user/kernel interface of each ring. Currently it contains two
indexes: (1)avail_indexrepresents where we should push our next PFN (written by kernel), while (2)fetch_indexrepresents where the userspace should fetch the next dirty PFN (written by userspace).One complete ring is composed by one
kvm_dirty_ringplus its correspondingkvm_dirty_ring_indexes.Currently, we have N+1 rings for each VM of N vcpus:
- for each vcpu, we have 1 per-vcpu dirty ring,
- for each vm, we have 1 per-vm dirty ringPlease refer to the documentation update in this patch for more details.
Note that this patch implements the core logic of dirty ring buffer. It's still disabled for all archs for now. Also, we'll address some of the other issues in follow up patches before it's firstly enabled on x86.
[1] https://patchwork.kernel.org/patch/10471409/
Signed-off-by Lei Cao <[email protected]>Signed-off-by Paolo Bonzini <[email protected]>Signed-off-by Peter Xu <[email protected]>--- Documentation/virt/kvm/api.txt | 109 +++++++++++++++ arch/x86/kvm/Makefile | 3 +- include/linux/kvm_dirty_ring.h | 67 +++++++++ include/linux/kvm_host.h | 33 +++++ include/linux/kvm_types.h | 1 + include/uapi/linux/kvm.h | 36 +++++ virt/kvm/dirty_ring.c | 156 +++++++++++++++++++++ virt/kvm/kvm_main.c | 240 ++++++++++++++++++++++++++++++++- 8 files changed, 642 insertions(+), 3 deletions(-) create mode 100644 include/linux/kvm_dirty_ring.h create mode 100644 virt/kvm/dirty_ring.cDocumentation/virt/kvm/api.txt +1
-0@@ -231,6 +231,7 @@ Based on their initialization different VMs may have different capabilities. It is thus encouraged to use the vm ioctl to query for capabilities (available with KVM_CAP_CHECK_EXTENSION_VM on the vm fd) + 4.5 KVM_GET_VCPU_MMAP_SIZE Capability: basic@@ -243,6 +244,18 @@ The KVM_RUN ioctl (cf.) communicates with userspace via a shared memory region. This ioctl returns the size of that region. See the KVM_RUN documentation for details.
Quoted 9 lines
Documentation/virt/kvm/api.txt The KVM_RUN ioctl (cf.) communicates with userspace via a shared · line 253
Quoted 7 lines
Documentation/virt/kvm/api.txt +7
-0+Besides the size of the KVM_RUN communication region, other areas of +the VCPU file descriptor can be mmap-ed, including: + +- if KVM_CAP_COALESCED_MMIO is available, a page at + KVM_COALESCED_MMIO_PAGE_OFFSET * PAGE_SIZE; for historical reasons, + this page is included in the result of KVM_GET_VCPU_MMAP_SIZE. + KVM_CAP_COALESCED_MMIO is not documented yet.Does the above really belong to this patch?
Probably not.. But sure I can move that out in my next post.
Quoted 49 lines
Quoted 34 lines
Documentation/virt/kvm/api.txt +12
-0+ +- if KVM_CAP_DIRTY_LOG_RING is available, a number of pages at + KVM_DIRTY_LOG_PAGE_OFFSET * PAGE_SIZE. For more information on + KVM_CAP_DIRTY_LOG_RING, see section 8.3. + 4.6 KVM_SET_MEMORY_REGION@@ -5358,6 +5371,7 @@ CPU when the exception is taken. If this virtual SError is taken to EL1 using AArch64, this value will be reported in the ISS field of ESR_ELx. See KVM_CAP_VCPU_EVENTS for more details. + 8.20 KVM_CAP_HYPERV_SEND_IPI Architectures: x86@@ -5365,6 +5379,7 @@ Architectures: x86 This capability indicates that KVM supports paravirtualized Hyper-V IPI send hypercalls: HvCallSendSyntheticClusterIpi, HvCallSendSyntheticClusterIpiEx. + 8.21 KVM_CAP_HYPERV_DIRECT_TLBFLUSH Architecture: x86@@ -5378,3 +5393,97 @@ handling by KVM (as some KVM hypercall may be mistakenly treated as TLB flush hypercalls by Hyper-V) so userspace should disable KVM identification in CPUID and only exposes Hyper-V identification. In this case, guest thinks it's running on Hyper-V and only use Hyper-V hypercalls. + +8.22 KVM_CAP_DIRTY_LOG_RING + +Architectures: x86 +Parameters: args[0] - size of the dirty log ringDocumentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5410
Quoted 10 lines
Documentation/virt/kvm/api.txt +10
-0+ +KVM is capable of tracking dirty memory using ring buffers that are +mmaped into userspace; there is one dirty ring per vcpu and one global +ring per vm. + +One dirty ring has the following two major structures: + +struct kvm_dirty_ring { + u16 dirty_index; + u16 reset_index;What is the benefit of using u16 for that? That means with 4K pages, you can share at most 256M of dirty memory each time? That seems low to me, especially since it's sufficient to touch one byte in a page to dirty it.
Quoted 2 lines
Actually, this is not consistent with the definition in the code ;-) So I'll assume it's actually u32.
Yes it's u32 now. Actually I believe at least Paolo would prefer u16 more. :)
I think even u16 would be mostly enough (if you see, the maximum allowed value currently is 64K entries only, not a big one). Again, the thing is that the userspace should be collecting the dirty bits, so the ring shouldn't reach full easily. Even if it does, we should probably let it stop for a while as explained above. It'll be inefficient only if we set it to a too-small value, imho.
Quoted 14 lines
Quoted 14 lines
Documentation/virt/kvm/api.txt +13
-0+ u32 size; + u32 soft_limit; + spinlock_t lock; + struct kvm_dirty_gfn *dirty_gfns; +}; + +struct kvm_dirty_ring_indexes { + __u32 avail_index; /* set by kernel */ + __u32 fetch_index; /* set by userspace */ +}; + +While for each of the dirty entry it's defined as: +
Quoted 8 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5428
Quoted 5 lines
Documentation/virt/kvm/api.txt +5
-0+struct kvm_dirty_gfn { + __u32 pad; + __u32 slot; /* as_id | slot_id */ + __u64 offset; +};Like other have suggested, I think we might used "pad" to store size information to be able to dirty large pages more efficiently.
As explained in the other thread, KVM should only trap dirty bits in 4K granularity, never in huge page sizes.
Quoted 8 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5432
Quoted 5 lines
Documentation/virt/kvm/api.txt +4
-0+ +The fields in kvm_dirty_ring will be only internal to KVM itself, +while the fields in kvm_dirty_ring_indexes will be exposed to +userspace to be either read or written.The sentence above is confusing when contrasted with the "set by kernel" comment above.
Maybe "kvm_dirty_ring_indexes will be exposed to both KVM and userspace" to be clearer?
"set by kernel" means kernel will write to it, then the userspace will still need to read from it.
Quoted 6 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5434
Quoted 3 lines
Documentation/virt/kvm/api.txt +2
-0+ +The two indices in the ring buffer are free running counters.Nit: this patch uses both "indices" and "indexes". Both are correct, but it would be nice to be consistent.
I'll respect the original patch to change everything into indices.
Quoted 19 lines
Quoted 19 lines
Documentation/virt/kvm/api.txt +18
-0+ +In pseudocode, processing the ring buffer looks like this: + + idx = load-acquire(&ring->fetch_index); + while (idx != ring->avail_index) { + struct kvm_dirty_gfn *entry; + entry = &ring->dirty_gfns[idx & (size - 1)]; + ... + + idx++; + } + ring->fetch_index = idx; + +Userspace calls KVM_ENABLE_CAP ioctl right after KVM_CREATE_VM ioctl +to enable this capability for the new guest and set the size of the +rings. It is only allowed before creating any vCPU, and the size of +the ring must be a power of two. The larger the ring buffer, the less +likely the ring is full and the VM is forced to exit to userspace. The
Quoted 8 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5454
Quoted 2 lines
Documentation/virt/kvm/api.txt +2
-0+optimal size depends on the workload, but it is recommended that it be +at least 64 KiB (4096 entries).Is there anything in the design that would preclude resizing the ring buffer at a later time? Presumably, you'd want a large ring while you are doing things like migrations, but it's mostly useless when you are not monitoring memory. So it would be nice to be able to call
KVM_ENABLE_CAPat any time to adjust the size.
It'll be scary to me to have it be adjusted at any time... Even during pushing dirty gfns onto the ring? We need to handle all these complexities...
IMHO it really does not help that much to have such a feature, or I'd appreciate we can allow to start from simple.
Quoted 4 lines
As I read the current code, one of the issue would be the mapping of the rings in case of a later extension where we added something beyond the rings. But I'm not sure that's a big deal at the moment.
I think we must define something to be sure that the ring mapped pages will be limited, so we can still extend things. IMHO that's why I introduced the maximum allowed ring size. That limits this.
Quoted 43 lines
Quoted 21 lines
Documentation/virt/kvm/api.txt +20
-0+ +After the capability is enabled, userspace can mmap the global ring +buffer (kvm_dirty_gfn[], offset KVM_DIRTY_LOG_PAGE_OFFSET) and the +indexes (kvm_dirty_ring_indexes, offset 0) from the VM file +descriptor. The per-vcpu dirty ring instead is mmapped when the vcpu +is created, similar to the kvm_run struct (kvm_dirty_ring_indexes +locates inside kvm_run, while kvm_dirty_gfn[] at offset +KVM_DIRTY_LOG_PAGE_OFFSET). + +Just like for dirty page bitmaps, the buffer tracks writes to +all user memory regions for which the KVM_MEM_LOG_DIRTY_PAGES flag was +set in KVM_SET_USER_MEMORY_REGION. Once a memory region is registered +with the flag set, userspace can start harvesting dirty pages from the +ring buffer. + +To harvest the dirty pages, userspace accesses the mmaped ring buffer +to read the dirty GFNs up to avail_index, and sets the fetch_index +accordingly. This can be done when the guest is running or paused, +and dirty pages need not be collected all at once. After processing +one or more entries in the ring buffer, userspace calls the VM ioctlDocumentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5483
Quoted 10 lines
Documentation/virt/kvm/api.txt +9
-0+KVM_RESET_DIRTY_RINGS to notify the kernel that it has updated +fetch_index and to mark those pages clean. Therefore, the ioctl +must be called *before* reading the content of the dirty pages. + +However, there is a major difference comparing to the +KVM_GET_DIRTY_LOG interface in that when reading the dirty ring from +userspace it's still possible that the kernel has not yet flushed the +hardware dirty buffers into the kernel buffer. To achieve that, one +needs to kick the vcpu out for a hardware buffer flush (vmexit).When you refer to "buffers", are you referring to the cache lines that contain the ring buffers, or to something else?
I'm a bit confused by this sentence. I think that you mean that a VCPU may still be running while you read its ring buffer, in which case the values in the ring buffer are not necessarily in memory yet, so not visible to a different CPU. But I wonder if you can't make this requirement to cause a vmexit unnecessary by carefully ordering the writes, to make sure that the
fetch_indexis updated only after the corresponding ring entries have been written to memory,
Quoted 5 lines
In other words, as seen by user-space, you would not care that the ring entries have not been flushed as long as the
fetch_indexitself is guaranteed to still be behind the not-flushed-yet entries.(I would know how to do that on a different architecture, not sure for x86)
Sorry for not being clear, but.. Do you mean the "hardware dirty buffers"? For Intel, it could be PML. Vmexits guarantee that even PML buffers will be flushed to the dirty rings. Nothing about cache lines.
I used "hardware dirty buffer" only because this document is for KVM in general, while PML is only one way to do such buffering. I can add "(for example, PML)" to make it clearer if you like.
Quoted 10 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5489
Quoted 7 lines
Documentation/virt/kvm/api.txt +6
-0+ +If one of the ring buffers is full, the guest will exit to userspace +with the exit reason set to KVM_EXIT_DIRTY_LOG_FULL, and the +KVM_RUN ioctl will return -EINTR. Once that happens, userspace +should pause all the vcpus, then harvest all the dirty pages and +rearm the dirty traps. It can unpause the guest after that.Except for the condition above, why is it necessary to pause other VCPUs than the one being harvested?
This is a good question. Paolo could correct me if I'm wrong.
Firstly I think this should rarely happen if the userspace is collecting the dirty bits from time to time. If it happens, we'll need to call KVM_RESET_DIRTY_RINGS to reset all the rings. Then the question actually becomes to: Whether we'd like to have per-vcpu KVM_RESET_DIRTY_RINGS?
So the answer is that it could be an overkill to do so. The important thing here is no matter what KVM_RESET_DIRTY_RINGS will need to change the page tables and kick all VCPUs for TLB flushings. If we must do it, we'd better do it as rare as possible. When we're with per-vcpu ring resets, we'll do N*N vcpu kicks for the bad case (N kicks per vcpu ring reset, and we've probably got N vcpus). While if we stick to the simple per-vm reset, it'll anyway kick all vcpus for tlb flushing after all, then maybe it's easier to collect all of them altogether and reset them altogether.
Quoted 35 lines
Quoted 35 lines
arch/x86/kvm/Makefile +2
-1@@ -5,7 +5,8 @@ ccflags-y += -Iarch/x86/kvm KVM := ../../../virt/kvm kvm-y += $(KVM)/kvm_main.o $(KVM)/coalesced_mmio.o \ - $(KVM)/eventfd.o $(KVM)/irqchip.o $(KVM)/vfio.o + $(KVM)/eventfd.o $(KVM)/irqchip.o $(KVM)/vfio.o \ + $(KVM)/dirty_ring.o kvm-$(CONFIG_KVM_ASYNC_PF) += $(KVM)/async_pf.o kvm-y += x86.o emulate.o i8259.o irq.o lapic.o \include/linux/kvm_dirty_ring.h new file +13
-0@@ -0,0 +1,67 @@ +#ifndef KVM_DIRTY_RING_H +#define KVM_DIRTY_RING_H + +/* +* struct kvm_dirty_ring is defined in include/uapi/linux/kvm.h. +* +* dirty_ring: shared with userspace via mmap. It is the compact list +* that holds the dirty pages. +* dirty_index: free running counter that points to the next slot in +* dirty_ring->dirty_gfns where a new dirty page should go. +* reset_index: free running counter that points to the next dirty page +* in dirty_ring->dirty_gfns for which dirty trap needs to +* be reenabled
Quoted 8 lines
include/linux/kvm_dirty_ring.h line 19
Quoted 6 lines
include/linux/kvm_dirty_ring.h new file +6
-0+* size: size of the compact list, dirty_ring->dirty_gfns +* soft_limit: when the number of dirty pages in the list reaches this +* limit, vcpu that owns this ring should exit to userspace +* to allow userspace to harvest all the dirty pages +* lock: protects dirty_ring, only in use if this is the global +* ring If that's not used for vcpu rings, maybe move it out of
kvm_dirty_ring?
Yeah we can.
Quoted 5 lines
include/linux/kvm_dirty_ring.h line 22
Quoted 4 lines
include/linux/kvm_dirty_ring.h new file +3
-0+* +* The number of dirty pages in the ring is calculated by, +* dirty_index - reset_index
Quoted 8 lines
Nit: the code calls it "used" (in
kvm_dirty_ring_used). Maybe find an unambiguous terminology. What about "posted", as inThe number of posted dirty pages, i.e. the number of dirty pages in the ring, is calculated as
dirty_index-reset_indexby functionkvm_dirty_ring_posted(Replace "posted" by any adjective of your liking)
Sure.
(Or maybe I'll just try to remove these lines to avoid introducing any
terminology as long as it's not very necessary... and after all similar things will be mentioned in the documents, and the code itself)
Quoted 39 lines
include/linux/kvm_dirty_ring.h line 28
Quoted 7 lines
include/linux/kvm_dirty_ring.h new file +6
-0+* +* kernel increments dirty_ring->indices.avail_index after dirty index +* is incremented. When userspace harvests the dirty pages, it increments +* dirty_ring->indices.fetch_index up to dirty_ring->indices.avail_index. +* When kernel reenables dirty traps for the dirty pages, it increments +* reset_index up to dirty_ring->indices.fetch_index. Userspace should not be trusted to be doing this, see below.
Quoted 28 lines
include/linux/kvm_dirty_ring.h new file +28
-0+* +*/ +struct kvm_dirty_ring { +u32 dirty_index; +u32 reset_index; +u32 size; +u32 soft_limit; +spinlock_t lock; +struct kvm_dirty_gfn *dirty_gfns; +}; + +u32 kvm_dirty_ring_get_rsvd_entries (void ); +int kvm_dirty_ring_alloc (struct kvm *kvm,struct kvm_dirty_ring *ring); + +/* +* called with kvm->slots_lock held, returns the number of +* processed pages. +*/ +int kvm_dirty_ring_reset (struct kvm *kvm, +struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes); + +/* +* returns 0: successfully pushed +* 1: successfully pushed, soft limit reached, +* vcpu should exit to userspace +* -EBUSY: unable to push, dirty ring full. +*/
Quoted 8 lines
include/linux/kvm_dirty_ring.h line 62
Quoted 6 lines
include/linux/kvm_dirty_ring.h new file +6
-0+int kvm_dirty_ring_push (struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes, +u32 slot,u64 offset,bool lock); + +/* for use in vm_operations_struct */ +struct page *kvm_dirty_ring_get_page (struct kvm_dirty_ring *ring,u32 i);Not very clear what 'i' means, seems to be a page offset based on call sites?
I'll rename it to "offset".
Quoted 33 lines
Quoted 33 lines
include/linux/kvm_dirty_ring.h new file +5
-0+ +void kvm_dirty_ring_free (struct kvm_dirty_ring *ring); +bool kvm_dirty_ring_full (struct kvm_dirty_ring *ring); + +#endif include/linux/kvm_host.h +3
-0@@ -34,6 +34,7 @@#include <linux/kvm_types.h> #include <asm/kvm_host.h> +#include <linux/kvm_dirty_ring.h> #ifndef KVM_MAX_VCPU_ID #define KVM_MAX_VCPU_ID KVM_MAX_VCPUS@@ -146,6 +147,7 @@ static inline bool is_error_page(struct page *page)#define KVM_REQ_MMU_RELOAD (1 | KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)#define KVM_REQ_PENDING_TIMER 2#define KVM_REQ_UNHALT 3 +#define KVM_REQ_DIRTY_RING_FULL 4#define KVM_REQUEST_ARCH_BASE 8#define KVM_ARCH_REQ_FLAGS (nr, flags) ({ \@@ -321,6 +323,7 @@ struct kvm_vcpu {bool ready;struct kvm_vcpu_arch arch;struct dentry *debugfs_dentry; +struct kvm_dirty_ring dirty_ring; };
Quoted 11 lines
include/linux/kvm_host.h struct kvm { · line 510
Quoted 9 lines
include/linux/kvm_host.h +4
-0static inline int kvm_vcpu_exiting_guest_mode (struct kvm_vcpu *vcpu)@@ -501,6 +504,10 @@ struct kvm {struct srcu_struct srcu;struct srcu_struct irq_srcu;pid_t userspace_pid; +/* Data structure to be exported by mmap(kvm->fd, 0) */ +struct kvm_vm_run *vm_run; +u32 dirty_ring_size; +struct kvm_dirty_ring vm_dirty_ring;If you remove the lock from struct
kvm_dirty_ring, you could just put it there.
Ok.
Quoted 166 lines
Quoted 153 lines
include/linux/kvm_host.h +26
-0};#define kvm_err (fmt, ...) \@@ -832,6 +839,8 @@ void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,gfn_t gfn_offset, unsigned long mask); +void kvm_reset_dirty_gfn (struct kvm *kvm,u32 slot,u64 offset,u64 mask); +int kvm_vm_ioctl_get_dirty_log (struct kvm *kvm,struct kvm_dirty_log *log); int kvm_vm_ioctl_clear_dirty_log(struct kvm *kvm,@@ -1411,4 +1420,28 @@ int kvm_vm_create_worker_thread(struct kvm *kvm, kvm_vm_thread_fn_t thread_fn, uintptr_t data, const char *name, struct task_struct **thread_ptr); +/* +* This defines how many reserved entries we want to keep before we +* kick the vcpu to the userspace to avoid dirty ring full. This +* value can be tuned to higher if e.g. PML is enabled on the host. +*/ +#define KVM_DIRTY_RING_RSVD_ENTRIES 64 + +/* Max number of entries allowed for each kvm dirty ring */ +#define KVM_DIRTY_RING_MAX_ENTRIES 65536 + +/* +* Arch needs to define these macro after implementing the dirty ring +* feature. KVM_DIRTY_LOG_PAGE_OFFSET should be defined as the +* starting page offset of the dirty ring structures, while +* KVM_DIRTY_RING_VERSION should be defined as >=1. By default, this +* feature is off on all archs. +*/ +#ifndef KVM_DIRTY_LOG_PAGE_OFFSET +#define KVM_DIRTY_LOG_PAGE_OFFSET 0 +#endif +#ifndef KVM_DIRTY_RING_VERSION +#define KVM_DIRTY_RING_VERSION 0 +#endif +#endif include/linux/kvm_types.h +1
-0@@ -11,6 +11,7 @@ struct kvm_irq_routing_table;struct kvm_memory_slot ;struct kvm_one_reg ;struct kvm_run ; +struct kvm_vm_run ;struct kvm_userspace_memory_region ;struct kvm_vcpu ;struct kvm_vcpu_init ;include/uapi/linux/kvm.h +36
-0@@ -236,6 +236,7 @@ struct kvm_hyperv_exit {#define KVM_EXIT_IOAPIC_EOI 26#define KVM_EXIT_HYPERV 27#define KVM_EXIT_ARM_NISV 28 +#define KVM_EXIT_DIRTY_RING_FULL 29/* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */ @@ -247,6 +248,11 @@ struct kvm_hyperv_exit {/* Encounter unexpected vm-exit reason */ #define KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON 4 +struct kvm_dirty_ring_indexes { +__u32 avail_index;/* set by kernel */ +__u32 fetch_index;/* set by userspace */ +}; +/* for KVM_RUN, returned by mmap(vcpu_fd, offset=0) */ struct kvm_run {/* in */ @@ -421,6 +427,13 @@ struct kvm_run {struct kvm_sync_regs regs;char padding[SYNC_REGS_SIZE_BYTES ]; } s; + +struct kvm_dirty_ring_indexes vcpu_ring_indexes; +}; + +/* Returned by mmap(kvm->fd, offset=0) */ +struct kvm_vm_run { +struct kvm_dirty_ring_indexes vm_ring_indexes; };/* for KVM_REGISTER_COALESCED_MMIO / KVM_UNREGISTER_COALESCED_MMIO */ @@ -1009,6 +1022,7 @@ struct kvm_ppc_resize_hpt {#define KVM_CAP_PPC_GUEST_DEBUG_SSTEP 176#define KVM_CAP_ARM_NISV_TO_USER 177#define KVM_CAP_ARM_INJECT_EXT_DABT 178 +#define KVM_CAP_DIRTY_LOG_RING 179#ifdef KVM_CAP_IRQ_ROUTING @@ -1472,6 +1486,9 @@ struct kvm_enc_region {/* Available with KVM_CAP_ARM_SVE */ #define KVM_ARM_VCPU_FINALIZE _IOW(KVMIO, 0xc2, int) +/* Available with KVM_CAP_DIRTY_LOG_RING */ +#define KVM_RESET_DIRTY_RINGS _IO(KVMIO, 0xc3) +/* Secure Encrypted Virtualization command */ enum sev_cmd_id {/* Guest initialization commands */ @@ -1622,4 +1639,23 @@ struct kvm_hyperv_eventfd {#define KVM_HYPERV_CONN_ID_MASK 0x00ffffff#define KVM_HYPERV_EVENTFD_DEASSIGN (1 << 0) +/* +* The following are the requirements for supporting dirty log ring +* (by enabling KVM_DIRTY_LOG_PAGE_OFFSET). +* +* 1. Memory accesses by KVM should call kvm_vcpu_write_* instead +* of kvm_write_* so that the global dirty ring is not filled up +* too quickly. +* 2. kvm_arch_mmu_enable_log_dirty_pt_masked should be defined for +* enabling dirty logging. +* 3. There should not be a separate step to synchronize hardware +* dirty bitmap with KVM's. +*/ + +struct kvm_dirty_gfn { +__u32 pad; +__u32 slot; +__u64 offset; +}; +#endif /* __LINUX_KVM_H */ virt/kvm/dirty_ring.c new file +13
-0@@ -0,0 +1,156 @@ +#include <linux/kvm_host.h> +#include <linux/kvm.h> +#include <linux/vmalloc.h> +#include <linux/kvm_dirty_ring.h> + +u32 kvm_dirty_ring_get_rsvd_entries (void ) +{ +return KVM_DIRTY_RING_RSVD_ENTRIES +kvm_cpu_dirty_log_size (); +} + +int kvm_dirty_ring_alloc (struct kvm *kvm,struct kvm_dirty_ring *ring) +{ +u32 size = kvm->dirty_ring_size;virt/kvm/dirty_ring.c line 23
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+ + ring->dirty_gfns =vmalloc (size); +if (!ring->dirty_gfns) +return -ENOMEM ; +memset (ring->dirty_gfns,0 , size); + + ring->size = size /sizeof (struct kvm_dirty_gfn ); + ring->soft_limit = + (kvm->dirty_ring_size /sizeof (struct kvm_dirty_gfn )) - +kvm_dirty_ring_get_rsvd_entries ();Minor, but what about
Quoted 1 line
ring->soft_limit = ring->size - kvm_dirty_ring_get_rsvd_entries();
Yeah it's better.
Quoted 27 lines
Quoted 11 lines
virt/kvm/dirty_ring.c new file +9
-0+ ring->dirty_index =0 ; + ring->reset_index =0 ; +spin_lock_init (&ring->lock); + +return 0 ; +} + +int kvm_dirty_ring_reset(struct kvm *kvm, + struct kvm_dirty_ring *ring,virt/kvm/dirty_ring.c line 42
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+ struct kvm_dirty_ring_indexes *indexes) +{ +u32 cur_slot, next_slot; +u64 cur_offset, next_offset; +unsigned long mask; +u32 fetch; +int count =0 ; +struct kvm_dirty_gfn *entry; + + fetch =READ_ONCE (indexes->fetch_index);If I understand correctly, if a malicious user-space writes
ring->reset_index+ 1 intofetch_index, the loop below will execute 4 billion times.
Quoted 7 lines
virt/kvm/dirty_ring.c line 44
Quoted 2 lines
virt/kvm/dirty_ring.c new file +2
-0+if (fetch == ring->reset_index) +return 0 ;To protect against scenario above, I would have something like:
if (fetch - ring->reset_index >= ring->size) return -EINVAL;
Good point... Actually I've got this in my latest branch already, but still thanks for noticing this!
Quoted 16 lines
Quoted 6 lines
virt/kvm/dirty_ring.c new file +5
-0+ + entry = &ring->dirty_gfns[ring->reset_index & (ring->size -1 )]; +/* +* The ring buffer is shared with userspace, which might mmap +* it and concurrently modify slot and offset. Userspace must virt/kvm/dirty_ring.c line 59
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+* not be trusted! READ_ONCE prevents the compiler from changing +* the values after they've been range-checked (the checks are +* in kvm_reset_dirty_gfn). +*/ +smp_read_barrier_depends (); + cur_slot =READ_ONCE (entry->slot); + cur_offset =READ_ONCE (entry->offset); + mask =1 ; + count++; + ring->reset_index++;
[1]
Quoted 7 lines
Quoted 7 lines
virt/kvm/dirty_ring.c new file +7
-0+while (ring->reset_index != fetch) { + entry = &ring->dirty_gfns[ring->reset_index & (ring->size -1 )]; +smp_read_barrier_depends (); + next_slot =READ_ONCE (entry->slot); + next_offset =READ_ONCE (entry->offset); + ring->reset_index++; + count++;
Quoted 8 lines
virt/kvm/dirty_ring.c line 72
Quoted 6 lines
virt/kvm/dirty_ring.c new file +6
-0+/* +* Try to coalesce the reset operations when the guest is +* scanning pages in the same slot. +*/ +if (next_slot == cur_slot) { +int delta = next_offset - cur_offset;Since you diff two u64, shouldn't that be an i64 rather than int?
I found there's no i64, so I'm using "long long".
Quoted 18 lines
Quoted 18 lines
virt/kvm/dirty_ring.c new file +17
-0+ +if (delta >=0 && delta <BITS_PER_LONG ) { + mask |=1ull << delta; +continue ; + } + +/* Backwards visit, careful about overflows! */ +if (delta > -BITS_PER_LONG && delta <0 && + (mask << -delta >> -delta) == mask) { + cur_offset = next_offset; + mask = (mask << -delta) |1 ; +continue ; + } + } +kvm_reset_dirty_gfn (kvm, cur_slot, cur_offset, mask); + cur_slot = next_slot; + cur_offset = next_offset;
Quoted 8 lines
virt/kvm/dirty_ring.c line 87
Quoted 3 lines
virt/kvm/dirty_ring.c new file +3
-0+ mask =1 ; + } +kvm_reset_dirty_gfn (kvm, cur_slot, cur_offset, mask);So if you did not coalesce the last one, you call
kvm_reset_dirty_gfntwice? Something smells weird about this loop ;-) I have a gut feeling that it could be done in a single while loop combined with the entry test, but I may be wrong.
It should be easy to save a few lines at [1] by introducing a boolean "first_round". I don't see it easy to avoid the kvm_reset_dirty_gfn() call at the end though...
Quoted 20 lines
Quoted 20 lines
virt/kvm/dirty_ring.c new file +18
-0+ +return count; +} + +static inline u32 kvm_dirty_ring_used (struct kvm_dirty_ring *ring) +{ +return ring->dirty_index - ring->reset_index; +} + +bool kvm_dirty_ring_full (struct kvm_dirty_ring *ring) +{ +return kvm_dirty_ring_used (ring) >= ring->size; +} + +/* +* Returns: +* >0 if we should kick the vcpu out, +* =0 if the gfn pushed successfully, or,
Quoted 8 lines
virt/kvm/dirty_ring.c line 115
Quoted 5 lines
virt/kvm/dirty_ring.c new file +5
-0+* <0 if error (e.g. ring full) +*/ +int kvm_dirty_ring_push (struct kvm_dirty_ring *ring, +struct kvm_dirty_ring_indexes *indexes, +u32 slot,u64 offset,bool lock)Obviously, if you go with the suggestion to have a "lock" only in struct kvm, then you'd have to pass a lock ptr instead of a bool.
Paolo got a better solution on that. That "lock" will be dropped.
Quoted 16 lines
Quoted 16 lines
virt/kvm/dirty_ring.c new file +15
-0+{ +int ret; +struct kvm_dirty_gfn *entry; + +if (lock) +spin_lock (&ring->lock); + +if (kvm_dirty_ring_full (ring)) { + ret = -EBUSY ; + goto out; + } + + entry = &ring->dirty_gfns[ring->dirty_index & (ring->size -1 )]; + entry->slot = slot; + entry->offset = offset;
Quoted 8 lines
virt/kvm/dirty_ring.c line 133
Quoted 3 lines
virt/kvm/dirty_ring.c new file +3
-0+smp_wmb (); + ring->dirty_index++; +WRITE_ONCE (indexes->avail_index, ring->dirty_index);Following up on comment about having to vmexit other VCPUs above:
If you have a write barrier for the entry, and then a write once for the index, isn't that sufficient to ensure that another CPU will pick up the right values in the right order?
I think so. I've replied above on the RESET issue.
Quoted 18 lines
Quoted 4 lines
virt/kvm/dirty_ring.c new file +2
-0+ ret =kvm_dirty_ring_used (ring) >= ring->soft_limit; + pr_info("%s: slot %u offset %llu used %u\n",virt/kvm/dirty_ring.c line 145
Quoted 10 lines
virt/kvm/dirty_ring.c new file +10
-0+ __func__, slot, offset, kvm_dirty_ring_used(ring)); + +out: +if (lock) +spin_unlock (&ring->lock); + +return ret; +} + +struct page *kvm_dirty_ring_get_page (struct kvm_dirty_ring *ring,u32 i)Still don't like 'i' :-)
Quoted 1 line
(Stopped my review here for lack of time, decided to share what I had so far)
Thanks for your comments!
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 8 lines · Peter Xu
Quoted 6 lines
What is the benefit of using u16 for that? That means with 4K pages, you can share at most 256M of dirty memory each time? That seems low to me, especially since it's sufficient to touch one byte in a page to dirty it.
Actually, this is not consistent with the definition in the code ;-) So I'll assume it's actually u32.
Yes it's u32 now. Actually I believe at least Paolo would prefer u16 more. :)
It has to be u16, because it overlaps the padding of the first entry.
Paolo
Quoted 7 lines
I think even u16 would be mostly enough (if you see, the maximum allowed value currently is 64K entries only, not a big one). Again, the thing is that the userspace should be collecting the dirty bits, so the ring shouldn't reach full easily. Even if it does, we should probably let it stop for a while as explained above. It'll be inefficient only if we set it to a too-small value, imho.
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 11 lines · Paolo Bonzini
Quoted 8 lines · Peter Xu
Quoted 6 lines
What is the benefit of using u16 for that? That means with 4K pages, you can share at most 256M of dirty memory each time? That seems low to me, especially since it's sufficient to touch one byte in a page to dirty it.
Actually, this is not consistent with the definition in the code ;-) So I'll assume it's actually u32.
Yes it's u32 now. Actually I believe at least Paolo would prefer u16 more. :)
It has to be u16, because it overlaps the padding of the first entry.
Hmm, could you explain?
Note that here what Christophe commented is on dirty_index, reset_index of "struct kvm_dirty_ring", so imho it could really be anything we want as long as it can store a u32 (which is the size of the elements in kvm_dirty_ring_indexes).
If you were instead talking about the previous union definition of "struct kvm_dirty_gfns" rather than "struct kvm_dirty_ring", iiuc I've moved those indices out of it and defined kvm_dirty_ring_indexes which we expose via kvm_run, so we don't have that limitation as well any more?
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 20 lines · Peter Xu
Quoted 11 lines · Paolo Bonzini
Quoted 8 lines · Peter Xu
Quoted 6 lines
What is the benefit of using u16 for that? That means with 4K pages, you can share at most 256M of dirty memory each time? That seems low to me, especially since it's sufficient to touch one byte in a page to dirty it.
Actually, this is not consistent with the definition in the code ;-) So I'll assume it's actually u32.
Yes it's u32 now. Actually I believe at least Paolo would prefer u16 more. :)
It has to be u16, because it overlaps the padding of the first entry.
Hmm, could you explain?
Note that here what Christophe commented is on
dirty_index,reset_indexof "structkvm_dirty_ring", so imho it could really be anything we want as long as it can store a u32 (which is the size of the elements inkvm_dirty_ring_indexes).
Quoted 5 lines
If you were instead talking about the previous union definition of "struct
kvm_dirty_gfns" rather than "structkvm_dirty_ring", iiuc I've moved those indices out of it and definedkvm_dirty_ring_indexeswhich we expose viakvm_run, so we don't have that limitation as well any more?
Yeah, I meant that since the size has (had) to be u16 in the union, it need not be bigger in kvm_dirty_ring.
I don't think having more than 2^16 entries in the *per-CPU* ring buffer makes sense; lagging in recording dirty memory by more than 256 MiB per CPU would mean a large pause later on resetting the ring buffers (your KVM_CLEAR_DIRTY_LOG patches found the sweet spot to be around 1 GiB for the whole system).
So I liked the union, but if you removed it you might as well align the producer and consumer indices to 64 bytes so that they are in separate cache lines.
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 27 lines · Paolo Bonzini
Quoted 20 lines · Peter Xu
Quoted 11 lines · Paolo Bonzini
Quoted 8 lines · Peter Xu
Quoted 6 lines
What is the benefit of using u16 for that? That means with 4K pages, you can share at most 256M of dirty memory each time? That seems low to me, especially since it's sufficient to touch one byte in a page to dirty it.
Actually, this is not consistent with the definition in the code ;-) So I'll assume it's actually u32.
Yes it's u32 now. Actually I believe at least Paolo would prefer u16 more. :)
It has to be u16, because it overlaps the padding of the first entry.
Hmm, could you explain?
Note that here what Christophe commented is on
dirty_index,reset_indexof "structkvm_dirty_ring", so imho it could really be anything we want as long as it can store a u32 (which is the size of the elements inkvm_dirty_ring_indexes).Quoted 5 lines
If you were instead talking about the previous union definition of "struct
kvm_dirty_gfns" rather than "structkvm_dirty_ring", iiuc I've moved those indices out of it and definedkvm_dirty_ring_indexeswhich we expose viakvm_run, so we don't have that limitation as well any more?
Quoted 8 lines
Yeah, I meant that since the size has (had) to be u16 in the union, it need not be bigger in
kvm_dirty_ring.I don't think having more than 2^16 entries in the *per-CPU* ring buffer makes sense; lagging in recording dirty memory by more than 256 MiB per CPU would mean a large pause later on resetting the ring buffers (your
KVM_CLEAR_DIRTY_LOGpatches found the sweet spot to be around 1 GiB for the whole system).
That's right, 1G could probably be a "common flavor" for guests in that case.
Though I wanted to use u64 only because I wanted to prepare even better for future potential changes as long as it won't hurt much. Here I'm just afraid 16bit might not be big enough for this 64bit world, at the meantime I'd confess some of the requirement could be really unimaginable before we know it.. I'm trying to forge one here:
what if the customer wants to handle 4G burst dirtying workload during a migration (besides the burst IOs, mostly idle guests), while the customer also want good responsiveness during the burst dirtying? In that case even if we use 256MiB ring we'll still need to freqently pause for the harvesting, but actually this case really suites for a 8G ring size.
My example could be nonsense actually, just to show that if we can extend something to u64 from u16 without paying much, then why not. :-)
Quoted 4 lines
So I liked the union, but if you removed it you might as well align the producer and consumer indices to 64 bytes so that they are in separate cache lines.
Yeh that I can do. Thanks,
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 37 lines · Peter Xu
Quoted 27 lines · Paolo Bonzini
Quoted 20 lines · Peter Xu
Quoted 11 lines · Paolo Bonzini
Quoted 8 lines · Peter Xu
Quoted 6 lines
What is the benefit of using u16 for that? That means with 4K pages, you can share at most 256M of dirty memory each time? That seems low to me, especially since it's sufficient to touch one byte in a page to dirty it.
Actually, this is not consistent with the definition in the code ;-) So I'll assume it's actually u32.
Yes it's u32 now. Actually I believe at least Paolo would prefer u16 more. :)
It has to be u16, because it overlaps the padding of the first entry.
Hmm, could you explain?
Note that here what Christophe commented is on
dirty_index,reset_indexof "structkvm_dirty_ring", so imho it could really be anything we want as long as it can store a u32 (which is the size of the elements inkvm_dirty_ring_indexes).Quoted 5 lines
If you were instead talking about the previous union definition of "struct
kvm_dirty_gfns" rather than "structkvm_dirty_ring", iiuc I've moved those indices out of it and definedkvm_dirty_ring_indexeswhich we expose viakvm_run, so we don't have that limitation as well any more?Quoted 8 lines
Yeah, I meant that since the size has (had) to be u16 in the union, it need not be bigger in
kvm_dirty_ring.I don't think having more than 2^16 entries in the *per-CPU* ring buffer makes sense; lagging in recording dirty memory by more than 256 MiB per CPU would mean a large pause later on resetting the ring buffers (your
KVM_CLEAR_DIRTY_LOGpatches found the sweet spot to be around 1 GiB for the whole system).
Quoted 5 lines
That's right, 1G could probably be a "common flavor" for guests in that case.
Though I wanted to use u64 only because I wanted to prepare even better for future potential changes as long as it won't hurt much.
No u64, please. u32 I can agree with, 16-bit *should* be enough but it is a bit tight, so let's make it 32-bit if we drop the union idea.
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 2 lines · Paolo Bonzini
No u64, please. u32 I can agree with, 16-bit *should* be enough but it is a bit tight, so let's make it 32-bit if we drop the union idea.
Sure.
Signature · 1 line
Peter Xu
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
In reply to Paolo BonziniQuoted 9 lines · Paolo Bonzini
Quoted 4 lines · Peter Xu
Quoted 4 lines
What is the benefit of using u16 for that? That means with 4K pages, you can share at most 256M of dirty memory each time? That seems low to me, especially since it's sufficient to touch one byte in a page to dirty it.
Quoted 6 lines
Quoted 4 lines
Quoted 2 lines
Actually, this is not consistent with the definition in the code ;-) So I'll assume it's actually u32.
Yes it's u32 now. Actually I believe at least Paolo would prefer u16 more. :)
It has to be u16, because it overlaps the padding of the first entry.
Wow, now that’s subtle.
That definitely needs a union with the padding to make this explicit.
(My guess is you do that to page-align the whole thing and avoid adding a page just for the counters)
Quoted 11 lines
Paolo
Quoted 7 lines
I think even u16 would be mostly enough (if you see, the maximum allowed value currently is 64K entries only, not a big one). Again, the thing is that the userspace should be collecting the dirty bits, so the ring shouldn't reach full easily. Even if it does, we should probably let it stop for a while as explained above. It'll be inefficient only if we set it to a too-small value, imho.
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 14 lines · Christophe de Dinechin
Quoted 14 lines · Paolo Bonzini
Quoted 8 lines · Peter Xu
Quoted 6 lines
What is the benefit of using u16 for that? That means with 4K pages, you can share at most 256M of dirty memory each time? That seems low to me, especially since it's sufficient to touch one byte in a page to dirty it.
Actually, this is not consistent with the definition in the code ;-) So I'll assume it's actually u32.
Yes it's u32 now. Actually I believe at least Paolo would prefer u16 more. :)
Quoted 8 lines
Quoted 1 line
It has to be u16, because it overlaps the padding of the first entry.
Wow, now that’s subtle.
That definitely needs a union with the padding to make this explicit.
(My guess is you do that to page-align the whole thing and avoid adding a page just for the counters)
Yes, that was the idea but Peter decided to scrap it. :)
Paolo
Quoted 12 lines
Quoted 11 lines
Paolo
Quoted 7 lines
I think even u16 would be mostly enough (if you see, the maximum allowed value currently is 64K entries only, not a big one). Again, the thing is that the userspace should be collecting the dirty bits, so the ring shouldn't reach full easily. Even if it does, we should probably let it stop for a while as explained above. It'll be inefficient only if we set it to a too-small value, imho.
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 15 lines · Paolo Bonzini
Quoted 14 lines · Christophe de Dinechin
Quoted 14 lines · Paolo Bonzini
Quoted 8 lines · Peter Xu
Quoted 6 lines
What is the benefit of using u16 for that? That means with 4K pages, you can share at most 256M of dirty memory each time? That seems low to me, especially since it's sufficient to touch one byte in a page to dirty it.
Actually, this is not consistent with the definition in the code ;-) So I'll assume it's actually u32.
Yes it's u32 now. Actually I believe at least Paolo would prefer u16 more. :)
Quoted 8 lines
Quoted 8 lines
Quoted 1 line
It has to be u16, because it overlaps the padding of the first entry.
Wow, now that’s subtle.
That definitely needs a union with the padding to make this explicit.
(My guess is you do that to page-align the whole thing and avoid adding a page just for the counters)
(Just to make sure this is clear... Paolo was talking about the
previous version. This version does not have this limitation because we don't have that union definition any more)
Quoted 2 lines
Yes, that was the idea but Peter decided to scrap it. :)
There's still time to persuade me to going back to it. :)
(Though, yes I still like current solution... if we can get rid of the
only kvmgt ugliness, we can even throw away the per-vm ring with its "extra" 4k page. Then I suppose it'll be even harder to persuade me :)
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 5 lines · Peter Xu
There's still time to persuade me to going back to it. :)
(Though, yes I still like current solution... if we can get rid of the
only kvmgt ugliness, we can even throw away the per-vm ring with its "extra" 4k page. Then I suppose it'll be even harder to persuade me :)
Actually that's what convinced me in the first place, so let's absolutely get rid of both the per-VM ring and the union. Kevin and Alex have answered and everybody seems to agree.
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 10 lines · Paolo Bonzini
Quoted 5 lines · Peter Xu
There's still time to persuade me to going back to it. :)
(Though, yes I still like current solution... if we can get rid of the
only kvmgt ugliness, we can even throw away the per-vm ring with its "extra" 4k page. Then I suppose it'll be even harder to persuade me :)Actually that's what convinced me in the first place, so let's absolutely get rid of both the per-VM ring and the union. Kevin and Alex have answered and everybody seems to agree.
Yeah that'd be perfect.
However I just noticed something... Note that we still didn't read into non-x86 archs, I think it's the same question as when I asked whether we can unify the kvm[_vcpu]_write() interfaces and you'd like me to read the non-x86 archs - I think it's time I read them, because it's still possible that non-x86 archs will still need the per-vm ring... then that could be another problem if we want to at last spread the dirty ring idea outside of x86.
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 8 lines · Peter Xu
However I just noticed something... Note that we still didn't read into non-x86 archs, I think it's the same question as when I asked whether we can unify the kvm[_vcpu]_write() interfaces and you'd like me to read the non-x86 archs - I think it's time I read them, because it's still possible that non-x86 archs will still need the per-vm ring... then that could be another problem if we want to at last spread the dirty ring idea outside of x86.
We can take a look, but I think based on x86 experience it's okay if we restrict dirty ring to arches that do no VM-wide accesses.
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 10 lines · Paolo Bonzini
Quoted 8 lines · Peter Xu
However I just noticed something... Note that we still didn't read into non-x86 archs, I think it's the same question as when I asked whether we can unify the kvm[_vcpu]_write() interfaces and you'd like me to read the non-x86 archs - I think it's time I read them, because it's still possible that non-x86 archs will still need the per-vm ring... then that could be another problem if we want to at last spread the dirty ring idea outside of x86.
Quoted 2 lines
We can take a look, but I think based on x86 experience it's okay if we restrict dirty ring to arches that do no VM-wide accesses.
Here it is - a quick update on callers of mark_page_dirty_in_slot(). The same reverse trace, but ignoring all common and x86 code path (which I covered in the other thread):
==================================
mark_page_dirty_in_slot (non-x86)
mark_page_dirty
kvm_write_guest_page
kvm_write_guest
kvm_write_guest_lock
vgic_its_save_ite [?]
vgic_its_save_dte [?]
vgic_its_save_cte [?]
vgic_its_save_collection_table [?]
vgic_v3_lpi_sync_pending_status [?]
vgic_v3_save_pending_tables [?]
kvmppc_rtas_hcall [&]
kvmppc_st [&]
access_guest [&]
put_guest_lc [&]
write_guest_lc [&]
write_guest_abs [&]
mark_page_dirty
_kvm_mips_map_page_fast [&]
kvm_mips_map_page [&]
kvmppc_mmu_map_page [&]
kvmppc_copy_guest
kvmppc_h_page_init [&]
kvmppc_xive_native_vcpu_eq_sync [&]
adapter_indicators_set [?] (from kvm_set_irq)
kvm_s390_sync_dirty_log [?]
unpin_guest_page
unpin_blocks [&]
unpin_scb [&]Cases with [*]: should not matter much
[&]: should be able to change to per-vcpu context [?]: uncertain...
==================================
This time we've got 8 leaves with "[?]".
I'm starting with these:
vgic_its_save_ite [?]
vgic_its_save_dte [?]
vgic_its_save_cte [?]
vgic_its_save_collection_table [?]
vgic_v3_lpi_sync_pending_status [?]
vgic_v3_save_pending_tables [?]These come from ARM specific ioctls like KVM_DEV_ARM_ITS_SAVE_TABLES, KVM_DEV_ARM_ITS_RESTORE_TABLES, KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES. IIUC ARM needed these to allow proper migration which indeed does not have a vcpu context.
(Though I'm a bit curious why ARM didn't simply migrate these
information explicitly from userspace, instead it seems to me that ARM guests will dump something into guest ram and then tries to recover from that which seems to be a bit weird)
Then it's this:
adapter_indicators_set [?]
This is s390 specific, which should come from kvm_set_irq. I'm not sure whether we can remove the mark_page_dirty() call of this, if it's applied from another kernel structure (which should be migrated properly IIUC). But I might be completely wrong.
kvm_s390_sync_dirty_log [?]
This is also s390 specific, should be collecting from the hardware PGSTE_UC_BIT bit. No vcpu context for sure.
(I'd be glad too if anyone could hint me why x86 cannot use page table
dirty bits for dirty tracking, if there's short answer...)
I think my conclusion so far...
- for s390 I don't think we even need this dirty ring buffer thing, because I think hardware trackings should be more efficient, then we don't need to care much on that either from design-wise of dirty ring,
- for ARM, those no-vcpu-context dirty tracking probably needs to be considered, but hopefully that's a very special path so it rarely happen. The bad thing is I didn't dig how many pages will be dirtied when ARM guest starts to dump all these things so it could be a burst... If it is, then there's risk to trigger the ring full condition (which we wanted to avoid..)
I'm CCing Eric for ARM, Conny&David for s390, just in case there're further inputs.
Thanks,
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 86 lines · Peter Xu
Quoted 10 lines · Paolo Bonzini
Quoted 8 lines · Peter Xu
However I just noticed something... Note that we still didn't read into non-x86 archs, I think it's the same question as when I asked whether we can unify the kvm[_vcpu]_write() interfaces and you'd like me to read the non-x86 archs - I think it's time I read them, because it's still possible that non-x86 archs will still need the per-vm ring... then that could be another problem if we want to at last spread the dirty ring idea outside of x86.
Quoted 2 lines
We can take a look, but I think based on x86 experience it's okay if we restrict dirty ring to arches that do no VM-wide accesses.
Here it is - a quick update on callers of
mark_page_dirty_in_slot(). The same reverse trace, but ignoring all common and x86 code path (which I covered in the other thread):==================================
mark_page_dirty_in_slot (non-x86) mark_page_dirty kvm_write_guest_page kvm_write_guest kvm_write_guest_lock vgic_its_save_ite [?] vgic_its_save_dte [?] vgic_its_save_cte [?] vgic_its_save_collection_table [?] vgic_v3_lpi_sync_pending_status [?] vgic_v3_save_pending_tables [?] kvmppc_rtas_hcall [&] kvmppc_st [&] access_guest [&] put_guest_lc [&] write_guest_lc [&] write_guest_abs [&] mark_page_dirty _kvm_mips_map_page_fast [&] kvm_mips_map_page [&] kvmppc_mmu_map_page [&] kvmppc_copy_guest kvmppc_h_page_init [&] kvmppc_xive_native_vcpu_eq_sync [&] adapter_indicators_set [?] (from kvm_set_irq) kvm_s390_sync_dirty_log [?] unpin_guest_page unpin_blocks [&] unpin_scb [&]Cases with [*]: should not matter much
[&]: should be able to change to per-vcpu context [?]: uncertain...==================================
This time we've got 8 leaves with "[?]".
I'm starting with these:
vgic_its_save_ite [?] vgic_its_save_dte [?] vgic_its_save_cte [?] vgic_its_save_collection_table [?] vgic_v3_lpi_sync_pending_status [?] vgic_v3_save_pending_tables [?]These come from ARM specific ioctls like
KVM_DEV_ARM_ITS_SAVE_TABLES,KVM_DEV_ARM_ITS_RESTORE_TABLES,KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES. IIUC ARM needed these to allow proper migration which indeed does not have a vcpu context.(Though I'm a bit curious why ARM didn't simply migrate these
information explicitly from userspace, instead it seems to me that ARM guests will dump something into guest ram and then tries to recover from that which seems to be a bit weird)Then it's this:
adapter_indicators_set[?]This is s390 specific, which should come from
kvm_set_irq. I'm not sure whether we can remove themark_page_dirty()call of this, if it's applied from another kernel structure (which should be migrated properly IIUC). But I might be completely wrong.
Quoted 7 lines
kvm_s390_sync_dirty_log [?]This is also s390 specific, should be collecting from the hardware
PGSTE_UC_BITbit. No vcpu context for sure.(I'd be glad too if anyone could hint me why x86 cannot use page table
dirty bits for dirty tracking, if there's short answer...)
With PML it is. Without PML, however, it would be much slower to synchronize the dirty bitmap from KVM to userspace (one atomic operation per page instead of one per 64 pages) and even impossible to have the dirty ring.
Quoted 6 lines
I think my conclusion so far...
- for s390 I don't think we even need this dirty ring buffer thing, because I think hardware trackings should be more efficient, then we don't need to care much on that either from design-wise of dirty ring,
I would be surprised if it's more efficient without something like PML, but anyway the gist is correct---without write protection-based dirty page logging, s390 cannot use the dirty page ring buffer.
Quoted 6 lines
- for ARM, those no-vcpu-context dirty tracking probably needs to be considered, but hopefully that's a very special path so it rarely happen. The bad thing is I didn't dig how many pages will be dirtied when ARM guest starts to dump all these things so it could be a burst... If it is, then there's risk to trigger the ring full condition (which we wanted to avoid..)
It says all vCPU locks must be held, so it could just use any vCPU. I am not sure what's the upper limit on the number of entries, or even whether userspace could just dirty those pages itself, or perhaps whether there could be a different ioctl that gets the pages into userspace memory (and then if needed userspace can copy them into guest memory, I don't know why it is designed like that).
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 92 lines · Paolo Bonzini
Quoted 91 lines · Peter Xu
Quoted 10 lines · Paolo Bonzini
Quoted 8 lines · Peter Xu
However I just noticed something... Note that we still didn't read into non-x86 archs, I think it's the same question as when I asked whether we can unify the kvm[_vcpu]_write() interfaces and you'd like me to read the non-x86 archs - I think it's time I read them, because it's still possible that non-x86 archs will still need the per-vm ring... then that could be another problem if we want to at last spread the dirty ring idea outside of x86.
Quoted 2 lines
We can take a look, but I think based on x86 experience it's okay if we restrict dirty ring to arches that do no VM-wide accesses.
Here it is - a quick update on callers of
mark_page_dirty_in_slot(). The same reverse trace, but ignoring all common and x86 code path (which I covered in the other thread):==================================
mark_page_dirty_in_slot (non-x86) mark_page_dirty kvm_write_guest_page kvm_write_guest kvm_write_guest_lock vgic_its_save_ite [?] vgic_its_save_dte [?] vgic_its_save_cte [?] vgic_its_save_collection_table [?] vgic_v3_lpi_sync_pending_status [?] vgic_v3_save_pending_tables [?] kvmppc_rtas_hcall [&] kvmppc_st [&] access_guest [&] put_guest_lc [&] write_guest_lc [&] write_guest_abs [&] mark_page_dirty _kvm_mips_map_page_fast [&] kvm_mips_map_page [&] kvmppc_mmu_map_page [&] kvmppc_copy_guest kvmppc_h_page_init [&] kvmppc_xive_native_vcpu_eq_sync [&] adapter_indicators_set [?] (from kvm_set_irq) kvm_s390_sync_dirty_log [?] unpin_guest_page unpin_blocks [&] unpin_scb [&]Cases with [*]: should not matter much
[&]: should be able to change to per-vcpu context [?]: uncertain...==================================
This time we've got 8 leaves with "[?]".
I'm starting with these:
vgic_its_save_ite [?] vgic_its_save_dte [?] vgic_its_save_cte [?] vgic_its_save_collection_table [?] vgic_v3_lpi_sync_pending_status [?] vgic_v3_save_pending_tables [?]These come from ARM specific ioctls like
KVM_DEV_ARM_ITS_SAVE_TABLES,KVM_DEV_ARM_ITS_RESTORE_TABLES,KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES. IIUC ARM needed these to allow proper migration which indeed does not have a vcpu context.(Though I'm a bit curious why ARM didn't simply migrate these
information explicitly from userspace, instead it seems to me that ARM guests will dump something into guest ram and then tries to recover from that which seems to be a bit weird)Then it's this:
adapter_indicators_set[?]This is s390 specific, which should come from
kvm_set_irq. I'm not sure whether we can remove themark_page_dirty()call of this, if it's applied from another kernel structure (which should be migrated properly IIUC). But I might be completely wrong.kvm_s390_sync_dirty_log [?]This is also s390 specific, should be collecting from the hardware
PGSTE_UC_BITbit. No vcpu context for sure.
Quoted 7 lines
Quoted 2 lines
(I'd be glad too if anyone could hint me why x86 cannot use page table
dirty bits for dirty tracking, if there's short answer...)With PML it is. Without PML, however, it would be much slower to synchronize the dirty bitmap from KVM to userspace (one atomic operation per page instead of one per 64 pages) and even impossible to have the dirty ring.
Indeed, however I think it'll be faster for hardware to mark page as dirty. So could it be a tradeoff on whether we want the "collection" to be faster or "marking page dirty" to be faster? IMHO "marking page dirty" could be even more important sometimes because that affects guest responsiveness (blocks vcpu execution), while the collection procedure can happen in parrallel with that.
Quoted 19 lines
Quoted 7 lines
I think my conclusion so far...
- for s390 I don't think we even need this dirty ring buffer thing, because I think hardware trackings should be more efficient, then we don't need to care much on that either from design-wise of dirty ring,
I would be surprised if it's more efficient without something like PML, but anyway the gist is correct---without write protection-based dirty page logging, s390 cannot use the dirty page ring buffer.
Quoted 6 lines
- for ARM, those no-vcpu-context dirty tracking probably needs to be considered, but hopefully that's a very special path so it rarely happen. The bad thing is I didn't dig how many pages will be dirtied when ARM guest starts to dump all these things so it could be a burst... If it is, then there's risk to trigger the ring full condition (which we wanted to avoid..)
Quoted 6 lines
It says all vCPU locks must be held, so it could just use any vCPU. I am not sure what's the upper limit on the number of entries, or even whether userspace could just dirty those pages itself, or perhaps whether there could be a different ioctl that gets the pages into userspace memory (and then if needed userspace can copy them into guest memory, I don't know why it is designed like that).
Yeah that's true. I'll see whether Eric has more update on these...
Thanks,
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerRe: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
Quoted 11 lines · Peter Xu
Quoted 4 lines
With PML it is. Without PML, however, it would be much slower to synchronize the dirty bitmap from KVM to userspace (one atomic operation per page instead of one per 64 pages) and even impossible to have the dirty ring.
Indeed, however I think it'll be faster for hardware to mark page as dirty. So could it be a tradeoff on whether we want the "collection" to be faster or "marking page dirty" to be faster? IMHO "marking page dirty" could be even more important sometimes because that affects guest responsiveness (blocks vcpu execution), while the collection procedure can happen in parrallel with that.
The problem is that the marking page dirty will be many many times slower, because you don't have this
if (!dirty_bitmap[i])
continue;and instead you have to scan the whole of the page tables even if a handful of bits are set (reading 4K of memory for every 2M of guest RAM). This can be quite bad for the TLB too. It is certainly possible that it turns out to be faster but I would be quite surprised and, with PML, that is more or less moot.
Thanks,
Paolo
Re: [PATCH RFC 04/15] KVM: Implement ring-based dirty memory tracking
In reply to Peter XuQuoted 9 lines · Peter Xu
Quoted 8 lines
Documentation/virt/kvm/api.txt handling by KVM (as some KVM hypercall may be mistakenly treated as TLB · line 5489
Quoted 5 lines
Documentation/virt/kvm/api.txt +5
-0+If one of the ring buffers is full, the guest will exit to userspace +with the exit reason set to KVM_EXIT_DIRTY_LOG_FULL, and the +KVM_RUN ioctl will return -EINTR. Once that happens, userspace +should pause all the vcpus, then harvest all the dirty pages and +rearm the dirty traps. It can unpause the guest after that.Except for the condition above, why is it necessary to pause other VCPUs than the one being harvested?
Quoted 7 lines
This is a good question. Paolo could correct me if I'm wrong.
Firstly I think this should rarely happen if the userspace is collecting the dirty bits from time to time. If it happens, we'll need to call
KVM_RESET_DIRTY_RINGSto reset all the rings. Then the question actually becomes to: Whether we'd like to have per-vcpuKVM_RESET_DIRTY_RINGS?
Hmm when I'm rethinking this, I could have errornously deduced something from Christophe's question. Christophe was asking about why kicking other vcpus, while it does not mean that the RESET will need to do per-vcpu.
So now I tend to agree here with Christophe that I can't find a reason why we need to kick all vcpus out. Even if we need to do tlb flushing for all vcpus when RESET, we can simply collect all the rings before sending the RESET, then it's not really a reason to explicitly kick them from userspace. So I plan to remove this sentence in the next version (which is only a document update).
Signature · 1 line
Peter Xu
RFC PATCH 5/15 KVM: Make dirty ring exclusive to dirty bitmap log
In reply to Peter XuThere's no good reason to use both the dirty bitmap logging and the new dirty ring buffer to track dirty bits. We should be able to even support both of them at the same time, but it could complicate things which could actually help little. Let's simply make it the rule before we enable dirty ring on any arch, that we don't allow these two interfaces to be used together.
The big world switch would be KVM_CAP_DIRTY_LOG_RING capability enablement. That's where we'll switch from the default dirty logging way to the dirty ring way. As long as kvm->dirty_ring_size is setup correctly, we'll once and for all switch to the dirty ring buffer mode for the current virtual machine.
--- Documentation/virt/kvm/api.txt | 7 +++++++ virt/kvm/kvm_main.c | 12 ++++++++++++ 2 files changed, 19 insertions(+)
Documentation/virt/kvm/api.txt +7 -0
@@ -5487,3 +5487,10 @@ with the exit reason set to KVM_EXIT_DIRTY_LOG_FULL, and the KVM_RUN ioctl will return -EINTR. Once that happens, userspace should pause all the vcpus, then harvest all the dirty pages and rearm the dirty traps. It can unpause the guest after that. + +NOTE: the KVM_CAP_DIRTY_LOG_RING capability and the new ioctl +KVM_RESET_DIRTY_RINGS are exclusive to the existing KVM_GET_DIRTY_LOG +interface. After enabling KVM_CAP_DIRTY_LOG_RING with an acceptable +dirty ring size, the virtual machine will switch to the dirty ring +tracking mode, and KVM_GET_DIRTY_LOG, KVM_CLEAR_DIRTY_LOG ioctls will +stop working.
virt/kvm/kvm_main.c +12 -0
@@ -1236,6 +1236,10 @@ int kvm_get_dirty_log(struct kvm *kvm,unsigned long n;unsigned long any =0 ; +/* Dirty ring tracking is exclusive to dirty log tracking */ +if (kvm->dirty_ring_size) +return -EINVAL ; + as_id = log->slot >>16 ; id = (u16 )log->slot;if (as_id >=KVM_ADDRESS_SPACE_NUM || id >=KVM_USER_MEM_SLOTS )
@@ -1293,6 +1297,10 @@ int kvm_get_dirty_log_protect(struct kvm *kvm,unsigned long *dirty_bitmap;unsigned long *dirty_bitmap_buffer; +/* Dirty ring tracking is exclusive to dirty log tracking */ +if (kvm->dirty_ring_size) +return -EINVAL ; + as_id = log->slot >>16 ; id = (u16 )log->slot;if (as_id >=KVM_ADDRESS_SPACE_NUM || id >=KVM_USER_MEM_SLOTS )
@@ -1364,6 +1372,10 @@ int kvm_clear_dirty_log_protect(struct kvm *kvm,unsigned long *dirty_bitmap;unsigned long *dirty_bitmap_buffer; +/* Dirty ring tracking is exclusive to dirty log tracking */ +if (kvm->dirty_ring_size) +return -EINVAL ; + as_id = log->slot >>16 ; id = (u16 )log->slot;if (as_id >=KVM_ADDRESS_SPACE_NUM || id >=KVM_USER_MEM_SLOTS )
Signature · 1 line
2.21.0
RFC PATCH 6/15 KVM: Introduce dirty ring wait queue
In reply to Peter XuWhen the dirty ring is completely full, right now we throw an error message and drop the dirty bit.
A better approach could be that we put the thread onto a waitqueue and retry after another KVM_RESET_DIRTY_RINGS.
We should still allow the process to be killed, so handle it explicitly.
--- include/linux/kvm_host.h | 1 + virt/kvm/kvm_main.c | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-)
include/linux/kvm_host.h +1 -0
@@ -508,6 +508,7 @@ struct kvm {struct kvm_vm_run *vm_run;u32 dirty_ring_size;struct kvm_dirty_ring vm_dirty_ring; +wait_queue_head_t dirty_ring_waitqueue; };#define kvm_err (fmt, ...) \
virt/kvm/kvm_main.c +16 -6
@@ -722,6 +722,7 @@ static struct kvm *kvm_create_vm(unsigned long type)mutex_init (&kvm->irq_lock);mutex_init (&kvm->slots_lock);INIT_LIST_HEAD (&kvm->devices); +init_waitqueue_head (&kvm->dirty_ring_waitqueue);BUILD_BUG_ON (KVM_MEM_SLOTS_NUM >SHRT_MAX );
@@ -3370,16 +3371,23 @@ static void mark_page_dirty_in_ring(struct kvm *kvm, is_vm_ring = true; } +retry: ret =kvm_dirty_ring_push (ring, indexes, (as_id <<16 )|slot->id, offset, is_vm_ring);if (ret <0 ) { -if (is_vm_ring) -pr_warn_once ("vcpu %d dirty log overflow\n" , - vcpu->vcpu_id); -else -pr_warn_once ("per-vm dirty log overflow\n" ); -return ; +/* +* Ring is full, put us onto per-vm waitqueue and wait +* for another KVM_RESET_DIRTY_RINGS to retry +*/ +wait_event_killable (kvm->dirty_ring_waitqueue, + !kvm_dirty_ring_full (ring)); + +/* If we're killed, no worry on lossing dirty bits! */ +if (fatal_signal_pending (current)) +return ; + + goto retry; }if (ret)
@@ -3475,6 +3483,8 @@ static int kvm_vm_ioctl_reset_dirty_pages(struct kvm *kvm)if (cleared)kvm_flush_remote_tlbs (kvm); +wake_up_all (&kvm->dirty_ring_waitqueue); +return cleared; }
Signature · 1 line
2.21.0
RFC PATCH 7/15 KVM: X86: Implement ring-based dirty memory tracking
In reply to Peter XuFrom: "Cao, Lei" <[email protected]>
Add new KVM exit reason KVM_EXIT_DIRTY_LOG_FULL and connect KVM_REQ_DIRTY_LOG_FULL to it.
[peterx: rebase, return 0 instead of -EINTR for user exits,
emul_insn before exit to userspace]
--- arch/x86/include/asm/kvm_host.h | 5 +++++ arch/x86/include/uapi/asm/kvm.h | 1 + arch/x86/kvm/mmu/mmu.c | 6 ++++++ arch/x86/kvm/vmx/vmx.c | 7 +++++++ arch/x86/kvm/x86.c | 12 ++++++++++++ 5 files changed, 31 insertions(+)
arch/x86/include/asm/kvm_host.h +5 -0
@@ -49,6 +49,8 @@#define KVM_IRQCHIP_NUM_PINS KVM_IOAPIC_NUM_PINS +#define KVM_DIRTY_RING_VERSION 1 +/* x86-specific vcpu->requests bit members */ #define KVM_REQ_MIGRATE_TIMER KVM_ARCH_REQ(0)#define KVM_REQ_REPORT_TPR_ACCESS KVM_ARCH_REQ(1)
@@ -1176,6 +1178,7 @@ struct kvm_x86_ops {struct kvm_memory_slot *slot, gfn_t offset, unsigned long mask);int (*write_log_dirty)(struct kvm_vcpu *vcpu); +int (*cpu_dirty_log_size)(void );/* pmu operations of sub-arch */ const struct kvm_pmu_ops *pmu_ops;
@@ -1661,4 +1664,6 @@ static inline int kvm_cpu_get_apicid(int mps_cpu)#define GET_SMSTATE (type, buf, offset) \ (*(type *)((buf) + (offset) - 0x7e00)) +int kvm_cpu_dirty_log_size (void ); +#endif /* _ASM_X86_KVM_HOST_H */
arch/x86/include/uapi/asm/kvm.h +1 -0
@@ -12,6 +12,7 @@#define KVM_PIO_PAGE_OFFSET 1#define KVM_COALESCED_MMIO_PAGE_OFFSET 2 +#define KVM_DIRTY_LOG_PAGE_OFFSET 64#define DE_VECTOR 0#define DB_VECTOR 1
arch/x86/kvm/mmu/mmu.c +6 -0
@@ -1818,7 +1818,13 @@ int kvm_arch_write_log_dirty(struct kvm_vcpu *vcpu) {if (kvm_x86_ops->write_log_dirty)return kvm_x86_ops->write_log_dirty (vcpu); +return 0 ; +} +int kvm_cpu_dirty_log_size (void ) +{ +if (kvm_x86_ops->cpu_dirty_log_size) +return kvm_x86_ops->cpu_dirty_log_size ();return 0 ; }
arch/x86/kvm/vmx/vmx.c +7 -0
@@ -7710,6 +7710,7 @@ static __init int hardware_setup(void) kvm_x86_ops->slot_disable_log_dirty =NULL ; kvm_x86_ops->flush_log_dirty =NULL ; kvm_x86_ops->enable_log_dirty_pt_masked =NULL ; + kvm_x86_ops->cpu_dirty_log_size =NULL ; }if (!cpu_has_vmx_preemption_timer ())
@@ -7774,6 +7775,11 @@ static __exit void hardware_unsetup(void)free_kvm_area (); } +static int vmx_cpu_dirty_log_size (void ) +{ +return enable_pml ?PML_ENTITY_NUM :0 ; +} +static struct kvm_x86_ops vmx_x86_ops__ro_after_init = { .cpu_has_kvm_support = cpu_has_kvm_support, .disabled_by_bios = vmx_disabled_by_bios,
@@ -7896,6 +7902,7 @@ static struct kvm_x86_ops vmx_x86_ops __ro_after_init = { .flush_log_dirty = vmx_flush_log_dirty, .enable_log_dirty_pt_masked = vmx_enable_log_dirty_pt_masked, .write_log_dirty = vmx_write_pml_buffer, + .cpu_dirty_log_size = vmx_cpu_dirty_log_size, .pre_block = vmx_pre_block, .post_block = vmx_post_block,
arch/x86/kvm/x86.c +12 -0
@@ -8094,6 +8094,18 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu)*/ if (kvm_check_request (KVM_REQ_HV_STIMER , vcpu))kvm_hv_process_stimers (vcpu); + +if (kvm_check_request (KVM_REQ_DIRTY_RING_FULL , vcpu)) { + vcpu->run->exit_reason =KVM_EXIT_DIRTY_RING_FULL ; +/* +* If this is requested, it means that we've +* marked the dirty bit in the dirty ring BUT +* we've not written the date. Do it now. +*/ + r =kvm_emulate_instruction (vcpu,0 ); + r = r >=0 ?0 : r; + goto out; + } }if (kvm_check_request (KVM_REQ_EVENT , vcpu) || req_int_win) {
Signature · 1 line
2.21.0
RFC PATCH 8/15 KVM: selftests: Always clear dirty bitmap after iteration
In reply to Peter XuWe don't clear the dirty bitmap before because KVM_GET_DIRTY_LOG will clear it for us before copying the dirty log onto it. However we'd still better to clear it explicitly instead of assuming the kernel will always do it for us.
More importantly, in the upcoming dirty ring tests we'll start to fetch dirty pages from a ring buffer, so no one is going to clear the dirty bitmap for us.
--- tools/testing/selftests/kvm/dirty_log_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
tools/testing/selftests/kvm/dirty_log_test.c +1 -1
@@ -197,7 +197,7 @@ static void vm_dirty_log_verify(unsigned long *bmap) page); } -if (test_bit_le (page, bmap)) { +if (test_and_clear_bit_le (page, bmap)) { host_dirty_count++;/* * If the bit is set, the value written onto
Signature · 1 line
2.21.0
RFC PATCH 9/15 KVM: selftests: Sync uapi/linux/kvm.h to tools/
In reply to Peter XuThis will be needed to extend the kvm selftest program.
--- tools/include/uapi/linux/kvm.h | 47 ++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+)
tools/include/uapi/linux/kvm.h +47 -0
@@ -235,6 +235,8 @@ struct kvm_hyperv_exit {#define KVM_EXIT_S390_STSI 25#define KVM_EXIT_IOAPIC_EOI 26#define KVM_EXIT_HYPERV 27 +#define KVM_EXIT_ARM_NISV 28 +#define KVM_EXIT_DIRTY_RING_FULL 29/* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */
@@ -246,6 +248,11 @@ struct kvm_hyperv_exit {/* Encounter unexpected vm-exit reason */ #define KVM_INTERNAL_ERROR_UNEXPECTED_EXIT_REASON 4 +struct kvm_dirty_ring_indexes { +__u32 avail_index;/* set by kernel */ +__u32 fetch_index;/* set by userspace */ +}; +/* for KVM_RUN, returned by mmap(vcpu_fd, offset=0) */ struct kvm_run {/* in */
@@ -394,6 +401,11 @@ struct kvm_run { } eoi;/* KVM_EXIT_HYPERV */ struct kvm_hyperv_exit hyperv; +/* KVM_EXIT_ARM_NISV */ +struct { +__u64 esr_iss; +__u64 fault_ipa; + } arm_nisv;/* Fix the size of the union. */ char padding[256 ]; };
@@ -415,6 +427,13 @@ struct kvm_run {struct kvm_sync_regs regs;char padding[SYNC_REGS_SIZE_BYTES ]; } s; + +struct kvm_dirty_ring_indexes vcpu_ring_indexes; +}; + +/* Returned by mmap(kvm->fd, offset=0) */ +struct kvm_vm_run { +struct kvm_dirty_ring_indexes vm_ring_indexes; };/* for KVM_REGISTER_COALESCED_MMIO / KVM_UNREGISTER_COALESCED_MMIO */
@@ -1000,6 +1019,10 @@ struct kvm_ppc_resize_hpt {#define KVM_CAP_PMU_EVENT_FILTER 173#define KVM_CAP_ARM_IRQ_LINE_LAYOUT_2 174#define KVM_CAP_HYPERV_DIRECT_TLBFLUSH 175 +#define KVM_CAP_PPC_GUEST_DEBUG_SSTEP 176 +#define KVM_CAP_ARM_NISV_TO_USER 177 +#define KVM_CAP_ARM_INJECT_EXT_DABT 178 +#define KVM_CAP_DIRTY_LOG_RING 179#ifdef KVM_CAP_IRQ_ROUTING
@@ -1227,6 +1250,8 @@ enum kvm_device_type {#define KVM_DEV_TYPE_ARM_VGIC_ITS KVM_DEV_TYPE_ARM_VGIC_ITS KVM_DEV_TYPE_XIVE,#define KVM_DEV_TYPE_XIVE KVM_DEV_TYPE_XIVE + KVM_DEV_TYPE_ARM_PV_TIME, +#define KVM_DEV_TYPE_ARM_PV_TIME KVM_DEV_TYPE_ARM_PV_TIME KVM_DEV_TYPE_MAX, };
@@ -1461,6 +1486,9 @@ struct kvm_enc_region {/* Available with KVM_CAP_ARM_SVE */ #define KVM_ARM_VCPU_FINALIZE _IOW(KVMIO, 0xc2, int) +/* Available with KVM_CAP_DIRTY_LOG_RING */ +#define KVM_RESET_DIRTY_RINGS _IO(KVMIO, 0xc3) +/* Secure Encrypted Virtualization command */ enum sev_cmd_id {/* Guest initialization commands */
@@ -1611,4 +1639,23 @@ struct kvm_hyperv_eventfd {#define KVM_HYPERV_CONN_ID_MASK 0x00ffffff#define KVM_HYPERV_EVENTFD_DEASSIGN (1 << 0) +/* +* The following are the requirements for supporting dirty log ring +* (by enabling KVM_DIRTY_LOG_PAGE_OFFSET). +* +* 1. Memory accesses by KVM should call kvm_vcpu_write_* instead +* of kvm_write_* so that the global dirty ring is not filled up +* too quickly. +* 2. kvm_arch_mmu_enable_log_dirty_pt_masked should be defined for +* enabling dirty logging. +* 3. There should not be a separate step to synchronize hardware +* dirty bitmap with KVM's. +*/ + +struct kvm_dirty_gfn { +__u32 pad; +__u32 slot; +__u64 offset; +}; +#endif /* __LINUX_KVM_H */
Signature · 1 line
2.21.0
RFC PATCH 10/15 KVM: selftests: Use a single binary for dirty/clear log test
In reply to Peter XuRemove the clear_dirty_log test, instead merge it into the existing dirty_log_test. It should be cleaner to use this single binary to do both tests, also it's a preparation for the upcoming dirty ring test.
The default test will still be the dirty_log test. To run the clear dirty log test, we need to specify "-M clear-log".
--- tools/testing/selftests/kvm/Makefile | 2 - .../selftests/kvm/clear_dirty_log_test.c | 2 - tools/testing/selftests/kvm/dirty_log_test.c | 131 +++++++++++++++--- 3 files changed, 110 insertions(+), 25 deletions(-) delete mode 100644 tools/testing/selftests/kvm/clear_dirty_log_test.c
tools/testing/selftests/kvm/Makefile +0 -2
@@ -26,11 +26,9 @@ TEST_GEN_PROGS_x86_64 += x86_64/vmx_dirty_log_test TEST_GEN_PROGS_x86_64 += x86_64/vmx_set_nested_state_test TEST_GEN_PROGS_x86_64 += x86_64/vmx_tsc_adjust_test TEST_GEN_PROGS_x86_64 += x86_64/xss_msr_test -TEST_GEN_PROGS_x86_64 += clear_dirty_log_test TEST_GEN_PROGS_x86_64 += dirty_log_test TEST_GEN_PROGS_x86_64 += kvm_create_max_vcpus -TEST_GEN_PROGS_aarch64 += clear_dirty_log_test TEST_GEN_PROGS_aarch64 += dirty_log_test TEST_GEN_PROGS_aarch64 += kvm_create_max_vcpus
tools/testing/selftests/kvm/clear_dirty_log_test.c deleted +0 -2
@@ -1,2 +0,0 @@ -#define USE_CLEAR_DIRTY_LOG -#include "dirty_log_test.c"
tools/testing/selftests/kvm/dirty_log_test.c +110 -21
185 lines not on this page · raw message
Signature · 1 line
2.21.0
RFC PATCH 11/15 KVM: selftests: Introduce after_vcpu_run hook for dirty log test
In reply to Peter XuProvide a hook for the checks after vcpu_run() completes. Preparation for the dirty ring test because we'll need to take care of another exit reason.
Since at it, drop the pages_count because after all we have a better summary right now with statistics, and clean it up a bit.
--- tools/testing/selftests/kvm/dirty_log_test.c | 39 ++++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-)
tools/testing/selftests/kvm/dirty_log_test.c +23 -16
@@ -168,6 +168,15 @@ static void clear_log_collect_dirty_pages(struct kvm_vm *vm, int slot,kvm_vm_clear_dirty_log (vm, slot, bitmap,0 , num_pages); } +static void default_after_vcpu_run (struct kvm_vm *vm) +{ +struct kvm_run *run =vcpu_state (vm,VCPU_ID ); + +TEST_ASSERT (get_ucall (vm,VCPU_ID ,NULL ) ==UCALL_SYNC , +"Invalid guest sync status: exit_reason=%s\n" , +exit_reason_str (run->exit_reason)); +} +struct log_mode {const char *name;/* Hook when the vm creation is done (before vcpu creation) */
@@ -175,16 +184,20 @@ struct log_mode {/* Hook to collect the dirty pages into the bitmap provided */ void (*collect_dirty_pages) (struct kvm_vm *vm,int slot,void *bitmap,uint32_t num_pages); +/* Hook to call when after each vcpu run */ +void (*after_vcpu_run)(struct kvm_vm *vm); } log_modes[LOG_MODE_NUM] = { { .name = "dirty-log", .create_vm_done = NULL, .collect_dirty_pages = dirty_log_collect_dirty_pages, + .after_vcpu_run = default_after_vcpu_run, }, { .name = "clear-log", .create_vm_done = clear_log_create_vm_done, .collect_dirty_pages = clear_log_collect_dirty_pages, + .after_vcpu_run = default_after_vcpu_run, }, };
@@ -224,6 +237,14 @@ static void log_mode_collect_dirty_pages(struct kvm_vm *vm, int slot, mode->collect_dirty_pages (vm, slot, bitmap, num_pages); } +static void log_mode_after_vcpu_run (struct kvm_vm *vm) +{ +struct log_mode *mode = &log_modes[host_log_mode]; + +if (mode->after_vcpu_run) + mode->after_vcpu_run (vm); +} +static void generate_random_array (uint64_t *guest_array,uint64_t size) {uint64_t i;
@@ -237,31 +258,17 @@ static void *vcpu_worker(void *data)int ret;struct kvm_vm *vm = data;uint64_t *guest_array; -uint64_t pages_count =0 ; -struct kvm_run *run; - - run =vcpu_state (vm,VCPU_ID ); guest_array =addr_gva2hva (vm, (vm_vaddr_t )random_array); -generate_random_array (guest_array,TEST_PAGES_PER_LOOP );while (!READ_ONCE (host_quit)) { +generate_random_array (guest_array,TEST_PAGES_PER_LOOP );/* Let the guest dirty the random pages */ ret =_vcpu_run (vm,VCPU_ID );TEST_ASSERT (ret ==0 ,"vcpu_run failed: %d\n" , ret); -if (get_ucall (vm,VCPU_ID ,NULL ) ==UCALL_SYNC ) { - pages_count +=TEST_PAGES_PER_LOOP ; -generate_random_array (guest_array,TEST_PAGES_PER_LOOP ); - }else { -TEST_ASSERT (false, -"Invalid guest sync status: " -"exit_reason=%s\n" , -exit_reason_str (run->exit_reason)); - } +log_mode_after_vcpu_run (vm); } -DEBUG ("Dirtied %" PRIu64" pages\n" , pages_count); -return NULL ; }
Signature · 1 line
2.21.0
RFC PATCH 12/15 KVM: selftests: Add dirty ring buffer test
In reply to Peter XuAdd the initial dirty ring buffer test.
The current test implements the userspace dirty ring collection, by only reaping the dirty ring when the ring is full.
So it's still running asynchronously like this:
vcpu main thread
1. vcpu dirties pages
2. vcpu gets dirty ring full
(userspace exit)
3. main thread waits until full (so hardware buffers flushed)
4. main thread collects
5. main thread continues vcpu
6. vcpu continues, goes back to 1
We can't directly collects dirty bits during vcpu execution because otherwise we can't guarantee the hardware dirty bits were flushed when we collect and we're very strict on the dirty bits so otherwise we can fail the future verify procedure. A follow up patch will make this test to support async just like the existing dirty log test, by adding a vcpu kick mechanism.
--- tools/testing/selftests/kvm/dirty_log_test.c | 148 ++++++++++++++++++ .../testing/selftests/kvm/include/kvm_util.h | 5 + tools/testing/selftests/kvm/lib/kvm_util.c | 95 +++++++++++ .../selftests/kvm/lib/kvm_util_internal.h | 5 + 4 files changed, 253 insertions(+)
tools/testing/selftests/kvm/dirty_log_test.c +148 -0
@@ -12,8 +12,10 @@#include <unistd.h> #include <time.h> #include <pthread.h> +#include <semaphore.h> #include <linux/bitmap.h> #include <linux/bitops.h> +#include <asm/barrier.h> #include "test_util.h" #include "kvm_util.h"
@@ -57,6 +59,8 @@# define test_and_clear_bit_le test_and_clear_bit#endif +#define TEST_DIRTY_RING_COUNT 1024 +/* * Guest/Host shared variables. Ensure addr_gva2hva() and/or * sync_global_to/from_guest() are used when accessing from
@@ -128,6 +132,10 @@ static uint64_t host_dirty_count;static uint64_t host_clear_count;static uint64_t host_track_next_count; +/* Whether dirty ring reset is requested, or finished */ +static sem_t dirty_ring_vcpu_stop; +static sem_t dirty_ring_vcpu_cont; +enum log_mode_t {/* Only use KVM_GET_DIRTY_LOG for logging */ LOG_MODE_DIRTY_LOG =0 ,
@@ -135,6 +143,9 @@ enum log_mode_t {/* Use both KVM_[GET|CLEAR]_DIRTY_LOG for logging */ LOG_MODE_CLERA_LOG =1 , +/* Use dirty ring for logging */ +LOG_MODE_DIRTY_RING =2 , + LOG_MODE_NUM, };
@@ -177,6 +188,123 @@ static void default_after_vcpu_run(struct kvm_vm *vm) exit_reason_str(run->exit_reason)); } +static void dirty_ring_create_vm_done (struct kvm_vm *vm) +{ +/* +* Switch to dirty ring mode after VM creation but before any +* of the vcpu creation. +*/ +vm_enable_dirty_ring (vm,TEST_DIRTY_RING_COUNT * +sizeof (struct kvm_dirty_gfn )); +} + +static uint32_t dirty_ring_collect_one (struct kvm_dirty_gfn *dirty_gfns, +struct kvm_dirty_ring_indexes *indexes, +int slot,void *bitmap, +uint32_t num_pages,int index) +{ +struct kvm_dirty_gfn *cur; +uint32_t avail, fetch, count =0 ; + +/* +* We should keep it somewhere, but to be simple we read +* fetch_index too. +*/ + fetch =READ_ONCE (indexes->fetch_index); + avail =READ_ONCE (indexes->avail_index); + +/* Make sure we read valid entries always */ +rmb (); + +DEBUG ("ring %d: fetch: 0x%x, avail: 0x%x\n" , index, fetch, avail); + +while (fetch != avail) { + cur = &dirty_gfns[fetch % test_dirty_ring_count]; +TEST_ASSERT (cur->pad ==0 ,"Padding is non-zero: 0x%x" , cur->pad); +TEST_ASSERT (cur->slot == slot,"Slot number didn't match: " +"%u != %u" , cur->slot, slot); +TEST_ASSERT (cur->offset < num_pages,"Offset overflow: " +"0x%llx >= 0x%llx" , cur->offset, num_pages); +//DEBUG("slot %d offset %llu\n", cur->slot, cur->offset); +test_and_set_bit (cur->offset, bitmap); + fetch++; + count++; + } +WRITE_ONCE (indexes->fetch_index, fetch); + +return count; +} + +static void dirty_ring_collect_dirty_pages (struct kvm_vm *vm,int slot, +void *bitmap,uint32_t num_pages) +{ +/* We only have one vcpu */ +struct kvm_run *state =vcpu_state (vm,VCPU_ID ); +struct kvm_vm_run *vm_run =vm_state (vm); +uint32_t count =0 , cleared; + +/* +* Before fetching the dirty pages, we need a vmexit of the +* worker vcpu to make sure the hardware dirty buffers were +* flushed. This is not needed for dirty-log/clear-log tests +* because get dirty log will natually do so. +* +* For now we do it in the simple way - we simply wait until +* the vcpu uses up the soft dirty ring, then it'll always +* do a vmexit to make sure that PML buffers will be flushed. +* In real hypervisors, we probably need a vcpu kick or to +* stop the vcpus (before the final sync) to make sure we'll +* get all the existing dirty PFNs even cached in hardware. +*/ +sem_wait (&dirty_ring_vcpu_stop); + + count +=dirty_ring_collect_one (kvm_map_dirty_ring (vm), + &vm_run->vm_ring_indexes, + slot, bitmap, num_pages,-1 ); + +/* Only have one vcpu */ + count +=dirty_ring_collect_one (vcpu_map_dirty_ring (vm,VCPU_ID ), + &state->vcpu_ring_indexes, + slot, bitmap, num_pages,VCPU_ID ); + + cleared =kvm_vm_reset_dirty_ring (vm); + +/* Cleared pages should be the same as collected */ +TEST_ASSERT (cleared == count,"Reset dirty pages (%u) mismatch " +"with collected (%u)" , cleared, count); + +DEBUG ("Notifying vcpu to continue\n" ); +sem_post (&dirty_ring_vcpu_cont); + +DEBUG ("Iteration %ld collected %u pages\n" , iteration, count); +} + +static void dirty_ring_after_vcpu_run (struct kvm_vm *vm) +{ +struct kvm_run *run =vcpu_state (vm,VCPU_ID ); + +/* A ucall-sync or ring-full event is allowed */ +if (get_ucall (vm,VCPU_ID ,NULL ) ==UCALL_SYNC ) { +/* We should allow this to continue */ + ; + }else if (run->exit_reason ==KVM_EXIT_DIRTY_RING_FULL ) { +sem_post (&dirty_ring_vcpu_stop); +DEBUG ("vcpu stops because dirty ring full...\n" ); +sem_wait (&dirty_ring_vcpu_cont); +DEBUG ("vcpu continues now.\n" ); + }else { +TEST_ASSERT (false,"Invalid guest sync status: " +"exit_reason=%s\n" , +exit_reason_str (run->exit_reason)); + } +} + +static void dirty_ring_before_vcpu_join (void ) +{ +/* Kick another round of vcpu just to make sure it will quit */ +sem_post (&dirty_ring_vcpu_cont); +} +struct log_mode {const char *name;/* Hook when the vm creation is done (before vcpu creation) */
@@ -186,6 +314,7 @@ struct log_mode { void *bitmap, uint32_t num_pages);/* Hook to call when after each vcpu run */ void (*after_vcpu_run)(struct kvm_vm *vm); +void (*before_vcpu_join) (void ); } log_modes[LOG_MODE_NUM] = { { .name = "dirty-log",
@@ -199,6 +328,13 @@ struct log_mode { .collect_dirty_pages = clear_log_collect_dirty_pages, .after_vcpu_run = default_after_vcpu_run, }, + { + .name = "dirty-ring", + .create_vm_done = dirty_ring_create_vm_done, + .collect_dirty_pages = dirty_ring_collect_dirty_pages, + .before_vcpu_join = dirty_ring_before_vcpu_join, + .after_vcpu_run = dirty_ring_after_vcpu_run, + }, };/*
@@ -245,6 +381,14 @@ static void log_mode_after_vcpu_run(struct kvm_vm *vm) mode->after_vcpu_run (vm); } +static void log_mode_before_vcpu_join (void ) +{ +struct log_mode *mode = &log_modes[host_log_mode]; + +if (mode->before_vcpu_join) + mode->before_vcpu_join (); +} +static void generate_random_array (uint64_t *guest_array,uint64_t size) {uint64_t i;
@@ -460,6 +604,7 @@ static void run_test(enum vm_guest_mode mode, unsigned long iterations,/* Tell the vcpu thread to quit */ host_quit = true; +log_mode_before_vcpu_join ();pthread_join (vcpu_thread,NULL ); DEBUG("Total bits checked: dirty (%"PRIu64"), clear (%"PRIu64"), "
@@ -524,6 +669,9 @@ int main(int argc, char *argv[])unsigned int host_ipa_limit;#endif +sem_init (&dirty_ring_vcpu_stop,0 ,0 ); +sem_init (&dirty_ring_vcpu_cont,0 ,0 ); +#ifdef __x86_64__vm_guest_mode_params_init (VM_MODE_PXXV48_4K , true, true);#endif
tools/testing/selftests/kvm/include/kvm_util.h +5 -0
@@ -67,6 +67,7 @@ enum vm_mem_backing_src_type {int kvm_check_cap (long cap);int vm_enable_cap (struct kvm_vm *vm,struct kvm_enable_cap *cap); +void vm_enable_dirty_ring (struct kvm_vm *vm,uint32_t ring_size);struct kvm_vm *vm_create (enum vm_guest_mode mode,uint64_t phy_pages,int perm);struct kvm_vm *_vm_create (enum vm_guest_mode mode,uint64_t phy_pages,int perm);
@@ -76,6 +77,7 @@ void kvm_vm_release(struct kvm_vm *vmp);void kvm_vm_get_dirty_log (struct kvm_vm *vm,int slot,void *log);void kvm_vm_clear_dirty_log (struct kvm_vm *vm,int slot,void *log,uint64_t first_page,uint32_t num_pages); +uint32_t kvm_vm_reset_dirty_ring (struct kvm_vm *vm);int kvm_memcmp_hva_gva (void *hva,struct kvm_vm *vm,const vm_vaddr_t gva,size_t len);
@@ -111,6 +113,7 @@ vm_paddr_t addr_hva2gpa(struct kvm_vm *vm, void *hva);vm_paddr_t addr_gva2gpa (struct kvm_vm *vm,vm_vaddr_t gva);struct kvm_run *vcpu_state (struct kvm_vm *vm,uint32_t vcpuid); +struct kvm_vm_run *vm_state (struct kvm_vm *vm);void vcpu_run (struct kvm_vm *vm,uint32_t vcpuid);int _vcpu_run (struct kvm_vm *vm,uint32_t vcpuid);void vcpu_run_complete_io (struct kvm_vm *vm,uint32_t vcpuid);
@@ -137,6 +140,8 @@ void vcpu_nested_state_get(struct kvm_vm *vm, uint32_t vcpuid,int vcpu_nested_state_set (struct kvm_vm *vm,uint32_t vcpuid,struct kvm_nested_state *state,bool ignore_error);#endif +void *vcpu_map_dirty_ring (struct kvm_vm *vm,uint32_t vcpuid); +void *kvm_map_dirty_ring (struct kvm_vm *vm);const char *exit_reason_str (unsigned int exit_reason);
tools/testing/selftests/kvm/lib/kvm_util.c +95 -0
137 lines not on this page · raw message
tools/testing/selftests/kvm/lib/kvm_util_internal.h +5 -0
@@ -39,6 +39,8 @@ struct vcpu {uint32_t id;int fd;struct kvm_run *state; +struct kvm_dirty_gfn *dirty_gfns; +uint32_t dirty_gfns_count; };struct kvm_vm {
@@ -61,6 +63,9 @@ struct kvm_vm {vm_paddr_t pgd;vm_vaddr_t gdt;vm_vaddr_t tss; +uint32_t dirty_ring_size; +struct kvm_vm_run *vm_run; +struct kvm_dirty_gfn *vm_dirty_gfns; };struct vcpu *vcpu_find (struct kvm_vm *vm,uint32_t vcpuid);
Signature · 1 line
2.21.0
RFC PATCH 13/15 KVM: selftests: Let dirty_log_test async for dirty ring test
In reply to Peter XuPreviously the dirty ring test was working in synchronous way, because only with a vmexit (with that it was the ring full event) we'll know the hardware dirty bits will be flushed to the dirty ring.
With this patch we first introduced the vcpu kick mechanism by using SIGUSR1, meanwhile we can have a guarantee of vmexit and also the flushing of hardware dirty bits. With all these, we can keep the vcpu dirty work asynchronous of the whole collection procedure now.
Further increase the dirty ring size to current maximum to make sure we torture more on the no-ring-full case, which should be the major scenario when the hypervisors like QEMU would like to use this feature.
--- tools/testing/selftests/kvm/dirty_log_test.c | 74 ++++++++++++------- .../testing/selftests/kvm/include/kvm_util.h | 1 + tools/testing/selftests/kvm/lib/kvm_util.c | 8 ++ 3 files changed, 57 insertions(+), 26 deletions(-)
tools/testing/selftests/kvm/dirty_log_test.c +48 -26
@@ -13,6 +13,9 @@#include <time.h> #include <pthread.h> #include <semaphore.h> +#include <sys/types.h> +#include <signal.h> +#include <errno.h> #include <linux/bitmap.h> #include <linux/bitops.h> #include <asm/barrier.h>
@@ -59,7 +62,9 @@# define test_and_clear_bit_le test_and_clear_bit#endif -#define TEST_DIRTY_RING_COUNT 1024 +#define TEST_DIRTY_RING_COUNT 65536 + +#define SIG_IPI SIGUSR1/* * Guest/Host shared variables. Ensure addr_gva2hva() and/or
@@ -151,6 +156,20 @@ enum log_mode_t {/* Mode of logging. Default is LOG_MODE_DIRTY_LOG */ static enum log_mode_t host_log_mode; +pthread_t vcpu_thread; + +/* Only way to pass this to the signal handler */ +struct kvm_vm *current_vm; + +static void vcpu_sig_handler (int sig) +{ +TEST_ASSERT (sig ==SIG_IPI ,"unknown signal: %d" , sig); +} + +static void vcpu_kick (void ) +{ +pthread_kill (vcpu_thread,SIG_IPI ); +}static void clear_log_create_vm_done (struct kvm_vm *vm) {
@@ -179,10 +198,13 @@ static void clear_log_collect_dirty_pages(struct kvm_vm *vm, int slot,kvm_vm_clear_dirty_log (vm, slot, bitmap,0 , num_pages); } -static void default_after_vcpu_run (struct kvm_vm *vm) +static void default_after_vcpu_run (struct kvm_vm *vm,int ret,int err) {struct kvm_run *run =vcpu_state (vm,VCPU_ID ); +TEST_ASSERT (ret ==0 || (ret ==-1 && err ==EINTR ), +"vcpu run failed: errno=%d" , err); +TEST_ASSERT (get_ucall (vm,VCPU_ID ,NULL ) ==UCALL_SYNC ,"Invalid guest sync status: exit_reason=%s\n" ,exit_reason_str (run->exit_reason));
@@ -244,19 +266,15 @@ static void dirty_ring_collect_dirty_pages(struct kvm_vm *vm, int slot,uint32_t count =0 , cleared;/* -* Before fetching the dirty pages, we need a vmexit of the -* worker vcpu to make sure the hardware dirty buffers were -* flushed. This is not needed for dirty-log/clear-log tests -* because get dirty log will natually do so. -* -* For now we do it in the simple way - we simply wait until -* the vcpu uses up the soft dirty ring, then it'll always -* do a vmexit to make sure that PML buffers will be flushed. -* In real hypervisors, we probably need a vcpu kick or to -* stop the vcpus (before the final sync) to make sure we'll -* get all the existing dirty PFNs even cached in hardware. +* These steps will make sure hardware buffer flushed to dirty +* ring. Now with the vcpu kick mechanism we can keep the +* vcpu running even during collecting dirty bits without ring +* full. */ +vcpu_kick ();sem_wait (&dirty_ring_vcpu_stop); +DEBUG ("Notifying vcpu to continue\n" ); +sem_post (&dirty_ring_vcpu_cont); count += dirty_ring_collect_one(kvm_map_dirty_ring(vm), &vm_run->vm_ring_indexes,
@@ -273,13 +291,10 @@ static void dirty_ring_collect_dirty_pages(struct kvm_vm *vm, int slot,TEST_ASSERT (cleared == count,"Reset dirty pages (%u) mismatch " "with collected (%u)" , cleared, count); -DEBUG ("Notifying vcpu to continue\n" ); -sem_post (&dirty_ring_vcpu_cont); -DEBUG ("Iteration %ld collected %u pages\n" , iteration, count); } -static void dirty_ring_after_vcpu_run (struct kvm_vm *vm) +static void dirty_ring_after_vcpu_run (struct kvm_vm *vm,int ret,int err) {struct kvm_run *run =vcpu_state (vm,VCPU_ID );
@@ -287,9 +302,11 @@ static void dirty_ring_after_vcpu_run(struct kvm_vm *vm)if (get_ucall (vm,VCPU_ID ,NULL ) ==UCALL_SYNC ) {/* We should allow this to continue */ ; - }else if (run->exit_reason ==KVM_EXIT_DIRTY_RING_FULL ) { + }else if (run->exit_reason ==KVM_EXIT_DIRTY_RING_FULL || + (ret ==-1 && err ==EINTR )) { +/* Either ring full, or we're probably kicked out */ sem_post (&dirty_ring_vcpu_stop); -DEBUG ("vcpu stops because dirty ring full...\n" ); +DEBUG ("vcpu stops because dirty ring full or kicked...\n" );sem_wait (&dirty_ring_vcpu_cont);DEBUG ("vcpu continues now.\n" ); }else {
@@ -313,7 +330,7 @@ struct log_mode {void (*collect_dirty_pages) (struct kvm_vm *vm,int slot,void *bitmap,uint32_t num_pages);/* Hook to call when after each vcpu run */ -void (*after_vcpu_run)(struct kvm_vm *vm); +void (*after_vcpu_run)(struct kvm_vm *vm,int ret,int err);void (*before_vcpu_join) (void ); } log_modes[LOG_MODE_NUM] = { {
@@ -373,12 +390,12 @@ static void log_mode_collect_dirty_pages(struct kvm_vm *vm, int slot, mode->collect_dirty_pages (vm, slot, bitmap, num_pages); } -static void log_mode_after_vcpu_run (struct kvm_vm *vm) +static void log_mode_after_vcpu_run (struct kvm_vm *vm,int ret,int err) {struct log_mode *mode = &log_modes[host_log_mode];if (mode->after_vcpu_run) - mode->after_vcpu_run (vm); + mode->after_vcpu_run (vm, ret, err); }static void log_mode_before_vcpu_join (void )
@@ -402,15 +419,21 @@ static void *vcpu_worker(void *data)int ret;struct kvm_vm *vm = data;uint64_t *guest_array; +struct sigaction sigact; + + current_vm = vm; +memset (&sigact,0 ,sizeof (sigact)); + sigact.sa_handler = vcpu_sig_handler; +sigaction (SIG_IPI , &sigact,NULL ); guest_array =addr_gva2hva (vm, (vm_vaddr_t )random_array);while (!READ_ONCE (host_quit)) { +/* Clear any existing kick signals */ generate_random_array (guest_array,TEST_PAGES_PER_LOOP );/* Let the guest dirty the random pages */ - ret =_vcpu_run (vm,VCPU_ID ); -TEST_ASSERT (ret ==0 ,"vcpu_run failed: %d\n" , ret); -log_mode_after_vcpu_run (vm); + ret =__vcpu_run (vm,VCPU_ID ); +log_mode_after_vcpu_run (vm, ret, errno); }return NULL ;
@@ -506,7 +529,6 @@ static struct kvm_vm *create_vm(enum vm_guest_mode mode, uint32_t vcpuid,static void run_test (enum vm_guest_mode mode,unsigned long iterations,unsigned long interval,uint64_t phys_offset) { -pthread_t vcpu_thread;struct kvm_vm *vm;unsigned long *bmap;
tools/testing/selftests/kvm/include/kvm_util.h +1 -0
@@ -116,6 +116,7 @@ struct kvm_run *vcpu_state(struct kvm_vm *vm, uint32_t vcpuid);struct kvm_vm_run *vm_state (struct kvm_vm *vm);void vcpu_run (struct kvm_vm *vm,uint32_t vcpuid);int _vcpu_run (struct kvm_vm *vm,uint32_t vcpuid); +int __vcpu_run (struct kvm_vm *vm,uint32_t vcpuid);void vcpu_run_complete_io (struct kvm_vm *vm,uint32_t vcpuid);void vcpu_set_mp_state (struct kvm_vm *vm,uint32_t vcpuid,struct kvm_mp_state *mp_state);
tools/testing/selftests/kvm/lib/kvm_util.c +8 -0
@@ -1209,6 +1209,14 @@ int _vcpu_run(struct kvm_vm *vm, uint32_t vcpuid)return rc; } +int __vcpu_run (struct kvm_vm *vm,uint32_t vcpuid) +{ +struct vcpu *vcpu =vcpu_find (vm, vcpuid); + +TEST_ASSERT (vcpu !=NULL ,"vcpu not found, vcpuid: %u" , vcpuid); +return ioctl (vcpu->fd,KVM_RUN ,NULL ); +} +void vcpu_run_complete_io (struct kvm_vm *vm,uint32_t vcpuid) {struct vcpu *vcpu =vcpu_find (vm, vcpuid);
Signature · 1 line
2.21.0
RFC PATCH 14/15 KVM: selftests: Add "-c" parameter to dirty log test
In reply to Peter XuIt's only used to override the existing dirty ring size/count. If with a bigger ring count, we test async of dirty ring. If with a smaller ring count, we test ring full code path.
It has no use for non-dirty-ring tests.
--- tools/testing/selftests/kvm/dirty_log_test.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-)
tools/testing/selftests/kvm/dirty_log_test.c +9 -2
@@ -157,6 +157,7 @@ enum log_mode_t {/* Mode of logging. Default is LOG_MODE_DIRTY_LOG */ static enum log_mode_t host_log_mode;pthread_t vcpu_thread; +static uint32_t test_dirty_ring_count =TEST_DIRTY_RING_COUNT ;/* Only way to pass this to the signal handler */ struct kvm_vm *current_vm;
@@ -216,7 +217,7 @@ static void dirty_ring_create_vm_done(struct kvm_vm *vm)* Switch to dirty ring mode after VM creation but before any * of the vcpu creation. */ -vm_enable_dirty_ring (vm,TEST_DIRTY_RING_COUNT * +vm_enable_dirty_ring (vm, test_dirty_ring_count *sizeof (struct kvm_dirty_gfn )); }
@@ -658,6 +659,9 @@ static void help(char *name)printf ("usage: %s [-h] [-i iterations] [-I interval] " "[-p offset] [-m mode]\n" , name);puts ("" ); +printf (" -c: specify dirty ring size, in number of entries\n" ); +printf (" (only useful for dirty-ring test; default: %" PRIu32")\n" , +TEST_DIRTY_RING_COUNT );printf (" -i: specify iteration counts (default: %" PRIu64")\n" ,TEST_HOST_LOOP_N ); printf(" -I: specify interval in ms (default: %"PRIu64" ms)\n",
@@ -713,8 +717,11 @@ int main(int argc, char *argv[])vm_guest_mode_params_init (VM_MODE_P40V48_4K , true, true);#endif -while ((opt =getopt (argc, argv,"hi:I:p:m:M:" )) !=-1 ) { +while ((opt =getopt (argc, argv,"c:hi:I:p:m:M:" )) !=-1 ) {switch (opt) { +case 'c' : + test_dirty_ring_count =strtol (optarg,NULL ,10 ); +break ;case 'i' : iterations =strtol (optarg,NULL ,10 );break ;
Signature · 1 line
2.21.0
RFC PATCH 15/15 KVM: selftests: Test dirty ring waitqueue
In reply to Peter XuThis is a bit tricky, but should still be reasonable.
Firstly we introduce a totally new dirty log test type, because we need to force vcpu to go into a blocked state by dead loop on vcpu_run even if it wants to quit to userspace.
Here the tricky part is we need to read the procfs to make sure the vcpu thread is TASK_UNINTERRUPTIBLE.
After that, we reset the ring and the reset should kick the vcpu again by moving out of that state.
--- tools/testing/selftests/kvm/dirty_log_test.c | 101 +++++++++++++++++++ 1 file changed, 101 insertions(+)
tools/testing/selftests/kvm/dirty_log_test.c +101 -0
@@ -16,6 +16,7 @@#include <sys/types.h> #include <signal.h> #include <errno.h> +#include <sys/syscall.h> #include <linux/bitmap.h> #include <linux/bitops.h> #include <asm/barrier.h>
@@ -151,12 +152,16 @@ enum log_mode_t {/* Use dirty ring for logging */ LOG_MODE_DIRTY_RING =2 , +/* Dirty ring test but tailored for the waitqueue */ +LOG_MODE_DIRTY_RING_WP =3 , + LOG_MODE_NUM, };/* Mode of logging. Default is LOG_MODE_DIRTY_LOG */ static enum log_mode_t host_log_mode;pthread_t vcpu_thread; +pid_t vcpu_thread_tid;static uint32_t test_dirty_ring_count =TEST_DIRTY_RING_COUNT ;/* Only way to pass this to the signal handler */
@@ -221,6 +226,18 @@ static void dirty_ring_create_vm_done(struct kvm_vm *vm) sizeof(struct kvm_dirty_gfn)); } +static void dirty_ring_wq_create_vm_done (struct kvm_vm *vm) +{ +/* +* Force to use a relatively small ring size, so easier to get +* full. Better bigger than PML size, hence 1024. +*/ + test_dirty_ring_count =1024 ; +DEBUG ("Forcing ring size: %u\n" , test_dirty_ring_count); +vm_enable_dirty_ring (vm, test_dirty_ring_count * +sizeof (struct kvm_dirty_gfn )); +} + static uint32_t dirty_ring_collect_one(struct kvm_dirty_gfn *dirty_gfns, struct kvm_dirty_ring_indexes *indexes, int slot, void *bitmap,
@@ -295,6 +312,81 @@ static void dirty_ring_collect_dirty_pages(struct kvm_vm *vm, int slot,DEBUG ("Iteration %ld collected %u pages\n" , iteration, count); } +/* +* Return 'D' for uninterruptible, 'R' for running, 'S' for +* interruptible, etc. +*/ +static char read_tid_status_char (unsigned int tid) +{ +int fd, ret, line =0 ; +char buf[128 ], *c; + +snprintf (buf,sizeof (buf) -1 ,"/proc/%u/status" , tid); + fd =open (buf,O_RDONLY ); +TEST_ASSERT (fd >=0 ,"open status file failed: %s" , buf); + ret =read (fd, buf,sizeof (buf) -1 ); +TEST_ASSERT (ret >0 ,"read status file failed: %d, %d" , ret, errno); +close (fd); + +/* Skip 2 lines */ +for (c = buf; c < buf +sizeof (buf) && line <2 ; c++) { +if (*c =='\n' ) { + line++; +continue ; + } + } + +/* Skip "Status: " */ +while (*c !=':' ) c++; + c++; +while (*c ==' ' ) c++; + c++; + +return *c; +} + +static void dirty_ring_wq_collect_dirty_pages (struct kvm_vm *vm,int slot, +void *bitmap,uint32_t num_pages) +{ +uint32_t count = test_dirty_ring_count; +struct kvm_run *state =vcpu_state (vm,VCPU_ID ); +struct kvm_dirty_ring_indexes *indexes = &state->vcpu_ring_indexes; +uint32_t avail; + +while (count--) { +/* +* Force vcpu to run enough time to make sure we +* trigger the ring full case +*/ +sem_post (&dirty_ring_vcpu_cont); + } + +/* Make sure it's stuck */ +TEST_ASSERT (vcpu_thread_tid,"TID not inited" ); +/* +* Wait for /proc/pid/status "Status:" changes to "D". "D" +* stands for "D (disk sleep)", TASK_UNINTERRUPTIBLE +*/ +while (read_tid_status_char (vcpu_thread_tid) !='D' ) { +usleep (1000 ); + } +DEBUG ("Now VCPU thread dirty ring full\n" ); + + avail =READ_ONCE (indexes->avail_index); +/* Assuming we've consumed all */ +WRITE_ONCE (indexes->fetch_index, avail); + +kvm_vm_reset_dirty_ring (vm); + +/* Wait for it to be awake */ +while (read_tid_status_char (vcpu_thread_tid) =='D' ) { +usleep (1000 ); + } +DEBUG ("VCPU Thread is successfully waked up\n" ); + +exit (0 ); +} +static void dirty_ring_after_vcpu_run (struct kvm_vm *vm,int ret,int err) {struct kvm_run *run =vcpu_state (vm,VCPU_ID );
@@ -353,6 +445,12 @@ struct log_mode { .before_vcpu_join = dirty_ring_before_vcpu_join, .after_vcpu_run = dirty_ring_after_vcpu_run, }, + { + .name = "dirty-ring-wait-queue", + .create_vm_done = dirty_ring_wq_create_vm_done, + .collect_dirty_pages = dirty_ring_wq_collect_dirty_pages, + .after_vcpu_run = dirty_ring_after_vcpu_run, + }, };/*
@@ -422,6 +520,9 @@ static void *vcpu_worker(void *data)uint64_t *guest_array;struct sigaction sigact; + vcpu_thread_tid =syscall (SYS_gettid); +printf ("VCPU Thread ID: %u\n" , vcpu_thread_tid); + current_vm = vm;memset (&sigact,0 ,sizeof (sigact)); sigact.sa_handler = vcpu_sig_handler;
Signature · 1 line
2.21.0
Paolo Bonzini
maintainerHi Peter,
thanks for the RFC! Just a couple comments before I look at the series (for which I don't expect many surprises).
Quoted 14 lines · Peter Xu
I marked this series as RFC because I'm at least uncertain on this change of
vcpu_enter_guest():if (kvm_check_request(KVM_REQ_DIRTY_RING_FULL, vcpu)) { vcpu->run->exit_reason = KVM_EXIT_DIRTY_RING_FULL; /* * If this is requested, it means that we've * marked the dirty bit in the dirty ring BUT * we've not written the date. Do it now. */ r = kvm_emulate_instruction(vcpu, 0); r = r >= 0 ? 0 : r; goto out; }
This is not needed, it will just be a false negative (dirty page that actually isn't dirty). The dirty bit will be cleared when userspace resets the ring buffer; then the instruction will be executed again and mark the page dirty again. Since ring full is not a common condition, it's not a big deal.
Quoted 29 lines
I did a
kvm_emulate_instruction()when dirty ring reaches softlimit and want to exit to userspace, however I'm not really sure whether there could have any side effect. I'd appreciate any comment of above, or anything else.Tests
===========I wanted to continue work on the QEMU part, but after I noticed that the interface might still prone to change, I posted this series first. However to make sure it's at least working, I've provided unit tests together with the series. The unit tests should be able to test the series in at least three major paths:
(1) ./dirty_log_test -M dirty-ring
This tests async ring operations: this should be the major work mode for the dirty ring interface, say, when the kernel is queuing more data, the userspace is collecting too. Ring can hardly reaches full when working like this, because in most cases the collection could be fast.
(2) ./dirty_log_test -M dirty-ring -c 1024
This set the ring size to be very small so that ring soft-full always triggers (soft-full is a soft limit of the ring state, when the dirty ring reaches the soft limit it'll do a userspace exit and let the userspace to collect the data).
Quoted 7 lines
(3) ./dirty_log_test -M dirty-ring-wait-queue
This sololy test the extreme case where ring is full. When the ring is completely full, the thread (no matter vcpu or not) will be put onto a per-vm waitqueue, and
KVM_RESET_DIRTY_RINGSwill wake the threads up (assuming until which the ring will not be full any more).
One question about this testcase: why does the task get into uninterruptible wait?
Paolo
Quoted 54 lines
Thanks,
Cao, Lei (2):
KVM: Add kvm/vcpu argument tomark_dirty_page_in_slot
KVM: X86: Implement ring-based dirty memory trackingPaolo Bonzini (1):
KVM: Move running VCPU from ARM to common codePeter Xu (12):
KVM: Add build-time error check onkvm_runsize
KVM: Implement ring-based dirty memory tracking
KVM: Make dirty ring exclusive to dirty bitmap log
KVM: Introduce dirty ring wait queue
KVM: selftests: Always clear dirty bitmap after iteration
KVM: selftests: Syncuapi/linux/kvm.htotools/
KVM: selftests: Use a single binary for dirty/clear log test
KVM: selftests: Introduceafter_vcpu_runhook for dirty log test
KVM: selftests: Add dirty ring buffer test
KVM: selftests: Letdirty_log_testasync for dirty ring test
KVM: selftests: Add "-c" parameter to dirty log test
KVM: selftests: Test dirty ring waitqueueDocumentation/virt/kvm/api.txt | 116 +++++ arch/arm/include/asm/kvm_host.h | 2 - arch/arm64/include/asm/kvm_host.h | 2 - arch/x86/include/asm/kvm_host.h | 5 + arch/x86/include/uapi/asm/kvm.h | 1 + arch/x86/kvm/Makefile | 3 +- arch/x86/kvm/mmu/mmu.c | 6 + arch/x86/kvm/vmx/vmx.c | 7 + arch/x86/kvm/x86.c | 12 + include/linux/kvm_dirty_ring.h | 67 +++ include/linux/kvm_host.h | 37 ++ include/linux/kvm_types.h | 1 + include/uapi/linux/kvm.h | 36 ++ tools/include/uapi/linux/kvm.h | 47 ++ tools/testing/selftests/kvm/Makefile | 2 - .../selftests/kvm/clear_dirty_log_test.c | 2 - tools/testing/selftests/kvm/dirty_log_test.c | 452 ++++++++++++++++-- .../testing/selftests/kvm/include/kvm_util.h | 6 + tools/testing/selftests/kvm/lib/kvm_util.c | 103 ++++ .../selftests/kvm/lib/kvm_util_internal.h | 5 + virt/kvm/arm/arm.c | 29 -- virt/kvm/arm/perf.c | 6 +- virt/kvm/arm/vgic/vgic-mmio.c | 15 +- virt/kvm/dirty_ring.c | 156 ++++++ virt/kvm/kvm_main.c | 315 +++++++++++- 25 files changed, 1329 insertions(+), 104 deletions(-) create mode 100644 include/linux/kvm_dirty_ring.h delete mode 100644 tools/testing/selftests/kvm/clear_dirty_log_test.c create mode 100644 virt/kvm/dirty_ring.c
Quoted 21 lines · Paolo Bonzini
Hi Peter,
thanks for the RFC! Just a couple comments before I look at the series (for which I don't expect many surprises).
Quoted 14 lines · Peter Xu
I marked this series as RFC because I'm at least uncertain on this change of
vcpu_enter_guest():if (kvm_check_request(KVM_REQ_DIRTY_RING_FULL, vcpu)) { vcpu->run->exit_reason = KVM_EXIT_DIRTY_RING_FULL; /* * If this is requested, it means that we've * marked the dirty bit in the dirty ring BUT * we've not written the date. Do it now. */ r = kvm_emulate_instruction(vcpu, 0); r = r >= 0 ? 0 : r; goto out; }
Quoted 5 lines
This is not needed, it will just be a false negative (dirty page that actually isn't dirty). The dirty bit will be cleared when userspace resets the ring buffer; then the instruction will be executed again and mark the page dirty again. Since ring full is not a common condition, it's not a big deal.
Actually I added this only because it failed one of the unit tests when verifying the dirty bits.. But now after a second thought, I probably agree with you that we can change the userspace too to fix this.
I think the steps of the failed test case could be simplified into something like this (assuming the QEMU migration context, might be easier to understand):
1. page P has data P1
2. vcpu writes to page P, with date P2
3. vmexit (P is still with data P1)
4. mark P as dirty, ring full, user exit
5. collect dirty bit P, migrate P with data P1
6. vcpu run due to some reason, P was written with P2, user exit again (because ring is already reaching soft limit)
7. do KVM_RESET_DIRTY_RINGS
8. never write to P again
Then P will be P1 always on destination, while it'll be P2 on source.
I think maybe that's why we need to be very sure that when userspace exits happens (soft limit reached), we need to kick all the vcpus out, and more importantly we must _not_ let them run again before the KVM_RESET_DIRTY_PAGES otherwise we might face the data corrupt. I'm not sure whether we should mention this in the document to let the userspace to be sure of the issue.
On the other side, I tried to remove the emulate_instruction() above and fixed the test case, though I found that the last address before user exit is not really written again after the next vmenter right after KVM_RESET_DIRTY_RINGS, so the dirty bit was truly lost... I'm pasting some traces below (I added some tracepoints too, I think I'll just keep them for v2):
...
dirty_log_test-29003 [001] 184503.384328: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.384329: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.384329: kvm_page_fault: address 7fc036d000 error_code 582 dirty_log_test-29003 [001] 184503.384331: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.384332: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.384332: kvm_page_fault: address 7fc036d000 error_code 582 dirty_log_test-29003 [001] 184503.384332: kvm_dirty_ring_push: ring 1: dirty 0x37f reset 0x1c0 slot 1 offset 0x37e ret 0 (used 447) dirty_log_test-29003 [001] 184503.384333: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.384334: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.384334: kvm_page_fault: address 7fc036e000 error_code 582 dirty_log_test-29003 [001] 184503.384336: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.384336: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.384336: kvm_page_fault: address 7fc036e000 error_code 582 dirty_log_test-29003 [001] 184503.384337: kvm_dirty_ring_push: ring 1: dirty 0x380 reset 0x1c0 slot 1 offset 0x37f ret 1 (used 448) dirty_log_test-29003 [001] 184503.384337: kvm_dirty_ring_exit: vcpu 1 dirty_log_test-29003 [001] 184503.384338: kvm_fpu: unload dirty_log_test-29003 [001] 184503.384340: kvm_userspace_exit: reason 0x1d (29) dirty_log_test-29000 [006] 184503.505103: kvm_dirty_ring_reset: ring 1: dirty 0x380 reset 0x380 (used 0) dirty_log_test-29003 [001] 184503.505184: kvm_fpu: load dirty_log_test-29003 [001] 184503.505187: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.505193: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.505194: kvm_page_fault: address 7fc036f000 error_code 582 <-------- [1] dirty_log_test-29003 [001] 184503.505206: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.505207: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.505207: kvm_page_fault: address 7fc036f000 error_code 582 dirty_log_test-29003 [001] 184503.505226: kvm_dirty_ring_push: ring 1: dirty 0x381 reset 0x380 slot 1 offset 0x380 ret 0 (used 1) dirty_log_test-29003 [001] 184503.505226: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.505227: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.505228: kvm_page_fault: address 7fc0370000 error_code 582 dirty_log_test-29003 [001] 184503.505231: kvm_entry: vcpu 1
...
The test was trying to continuously write to pages, from above log starting from 7fc036d000. The reason 0x1d (29) is the new dirty ring full exit reason.
So far I'm still unsure of two things:
1. Why for each page we faulted twice rather than once. Take the example of page at 7fc036e000 above, the first fault didn't trigger the marking dirty path, while only until the 2nd ept violation did we trigger kvm_dirty_ring_push.
2. Why we didn't get the last page written again after kvm_userspace_exit (last page was 7fc036e000, and the test failed because 7fc036e000 detected change however dirty bit unset). In this case the first write after KVM_RESET_DIRTY_RINGS is the line pointed by [1], I thought it should be a rewritten of page 7fc036e000 because when the user exit happens logically the write should not happen yet and eip should keep. However at [1] it's already writting to a new page.
I'll continue to dig tomorrow, or quick answers will be greatly welcomed too. :)
Quoted 32 lines
Quoted 32 lines
I did a
kvm_emulate_instruction()when dirty ring reaches softlimit and want to exit to userspace, however I'm not really sure whether there could have any side effect. I'd appreciate any comment of above, or anything else.Tests
===========I wanted to continue work on the QEMU part, but after I noticed that the interface might still prone to change, I posted this series first. However to make sure it's at least working, I've provided unit tests together with the series. The unit tests should be able to test the series in at least three major paths:
(1) ./dirty_log_test -M dirty-ring
This tests async ring operations: this should be the major work mode for the dirty ring interface, say, when the kernel is queuing more data, the userspace is collecting too. Ring can hardly reaches full when working like this, because in most cases the collection could be fast.
(2) ./dirty_log_test -M dirty-ring -c 1024
This set the ring size to be very small so that ring soft-full always triggers (soft-full is a soft limit of the ring state, when the dirty ring reaches the soft limit it'll do a userspace exit and let the userspace to collect the data).
(3) ./dirty_log_test -M dirty-ring-wait-queue
Quoted 8 lines
Quoted 5 lines
This sololy test the extreme case where ring is full. When the ring is completely full, the thread (no matter vcpu or not) will be put onto a per-vm waitqueue, and
KVM_RESET_DIRTY_RINGSwill wake the threads up (assuming until which the ring will not be full any more).One question about this testcase: why does the task get into uninterruptible wait?
Because I'm using wait_event_killable() to wait when ring is completely full. I thought we should be strict there because it's after all rare (even more rare than the soft-limit reached), and with that we will never have a change to lose a dirty bit accidentally. Or do you think we should still respond to non fatal signals due to some reason even during that wait period?
Thanks,
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerQuoted 10 lines · Peter Xu
Quoted 5 lines
This is not needed, it will just be a false negative (dirty page that actually isn't dirty). The dirty bit will be cleared when userspace resets the ring buffer; then the instruction will be executed again and mark the page dirty again. Since ring full is not a common condition, it's not a big deal.
Actually I added this only because it failed one of the unit tests when verifying the dirty bits.. But now after a second thought, I probably agree with you that we can change the userspace too to fix this.
I think there is already a similar case in dirty_log_test when a page is dirty but we called KVM_GET_DIRTY_LOG just before it got written to.
Quoted 4 lines
I think the steps of the failed test case could be simplified into something like this (assuming the QEMU migration context, might be easier to understand):
Quoted 8 lines
1. page P has data P1
2. vcpu writes to page P, with date P2
3. vmexit (P is still with data P1)
4. mark P as dirty, ring full, user exit
5. collect dirty bit P, migrate P with data P1
6. vcpu run due to some reason, P was written with P2, user exit again (because ring is already reaching soft limit)
7. doKVM_RESET_DIRTY_RINGS
Migration should only be done after KVM_RESET_DIRTY_RINGS (think of KVM_RESET_DIRTY_RINGS as the equivalent of KVM_CLEAR_DIRTY_LOG).
Quoted 36 lines
dirty_log_test-29003 [001] 184503.384328: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.384329: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.384329: kvm_page_fault: address 7fc036d000 error_code 582 dirty_log_test-29003 [001] 184503.384331: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.384332: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.384332: kvm_page_fault: address 7fc036d000 error_code 582 dirty_log_test-29003 [001] 184503.384332: kvm_dirty_ring_push: ring 1: dirty 0x37f reset 0x1c0 slot 1 offset 0x37e ret 0 (used 447) dirty_log_test-29003 [001] 184503.384333: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.384334: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.384334: kvm_page_fault: address 7fc036e000 error_code 582 dirty_log_test-29003 [001] 184503.384336: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.384336: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.384336: kvm_page_fault: address 7fc036e000 error_code 582 dirty_log_test-29003 [001] 184503.384337: kvm_dirty_ring_push: ring 1: dirty 0x380 reset 0x1c0 slot 1 offset 0x37f ret 1 (used 448) dirty_log_test-29003 [001] 184503.384337: kvm_dirty_ring_exit: vcpu 1 dirty_log_test-29003 [001] 184503.384338: kvm_fpu: unload dirty_log_test-29003 [001] 184503.384340: kvm_userspace_exit: reason 0x1d (29) dirty_log_test-29000 [006] 184503.505103: kvm_dirty_ring_reset: ring 1: dirty 0x380 reset 0x380 (used 0) dirty_log_test-29003 [001] 184503.505184: kvm_fpu: load dirty_log_test-29003 [001] 184503.505187: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.505193: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.505194: kvm_page_fault: address 7fc036f000 error_code 582 <-------- [1] dirty_log_test-29003 [001] 184503.505206: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.505207: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.505207: kvm_page_fault: address 7fc036f000 error_code 582 dirty_log_test-29003 [001] 184503.505226: kvm_dirty_ring_push: ring 1: dirty 0x381 reset 0x380 slot 1 offset 0x380 ret 0 (used 1) dirty_log_test-29003 [001] 184503.505226: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.505227: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.505228: kvm_page_fault: address 7fc0370000 error_code 582 dirty_log_test-29003 [001] 184503.505231: kvm_entry: vcpu 1...
The test was trying to continuously write to pages, from above log starting from
7fc036d000. The reason0x1d(29) is the new dirty ring full exit reason.
Quoted 6 lines
So far I'm still unsure of two things:
1. Why for each page we faulted twice rather than once. Take the example of page at
7fc036e000above, the first fault didn't trigger the marking dirty path, while only until the 2nd ept violation did we triggerkvm_dirty_ring_push.
Not sure about that. Try enabling kvmmmu tracepoints too, it will tell you more of the path that was taken while processing the EPT violation.
If your machine has PML, what you're seeing is likely not-present violation, not dirty-protect violation. Try disabling pml and see if the trace changes.
Quoted 8 lines
2. Why we didn't get the last page written again after
kvm_userspace_exit(last page was7fc036e000, and the test failed because7fc036e000detected change however dirty bit unset). In this case the first write afterKVM_RESET_DIRTY_RINGSis the line pointed by [1], I thought it should be a rewritten of page7fc036e000because when the user exit happens logically the write should not happen yet and eip should keep. However at [1] it's already writting to a new page.
IIUC you should get, with PML enabled:
- guest writes to page
- PML marks dirty bit, causes vmexit
- host copies PML log to ring, causes userspace exit
- userspace calls KVM_RESET_DIRTY_RINGS
- host marks page as clean
- userspace calls KVM_RUN
- guest writes again to page
but the page won't be in the ring until after another vmexit happens. Therefore, it's okay to reap the pages in the ring asynchronously, but there must be a synchronization point in the testcase sooner or later, where all CPUs are kicked out of KVM_RUN. This synchronization point corresponds to the migration downtime.
Thanks,
Paolo
Quoted 7 lines · Paolo Bonzini
Quoted 6 lines · Peter Xu
Quoted 5 lines
This is not needed, it will just be a false negative (dirty page that actually isn't dirty). The dirty bit will be cleared when userspace resets the ring buffer; then the instruction will be executed again and mark the page dirty again. Since ring full is not a common condition, it's not a big deal.
Quoted 7 lines
Quoted 4 lines
Actually I added this only because it failed one of the unit tests when verifying the dirty bits.. But now after a second thought, I probably agree with you that we can change the userspace too to fix this.
I think there is already a similar case in
dirty_log_testwhen a page is dirty but we calledKVM_GET_DIRTY_LOGjust before it got written to.
If you mean the host_bmap_track (in dirty_log_test.c), that should be a reversed version of this race (that's where the data is written, while we didn't see the dirty bit set). But yes I think I can probably use the same bitmap to fix the test case, because in both cases what we want to do is to make sure "the dirty bit of this page should be set in next round".
Quoted 14 lines
Quoted 5 lines
I think the steps of the failed test case could be simplified into something like this (assuming the QEMU migration context, might be easier to understand):
Quoted 8 lines
1. page P has data P1
2. vcpu writes to page P, with date P2
3. vmexit (P is still with data P1)
4. mark P as dirty, ring full, user exit
5. collect dirty bit P, migrate P with data P1
6. vcpu run due to some reason, P was written with P2, user exit again (because ring is already reaching soft limit)
7. doKVM_RESET_DIRTY_RINGS
Quoted 2 lines
Migration should only be done after
KVM_RESET_DIRTY_RINGS(think ofKVM_RESET_DIRTY_RINGSas the equivalent ofKVM_CLEAR_DIRTY_LOG).
Totally agree for migration. It's probably just that the test case needs fixing.
Quoted 39 lines
Quoted 39 lines
dirty_log_test-29003 [001] 184503.384328: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.384329: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.384329: kvm_page_fault: address 7fc036d000 error_code 582 dirty_log_test-29003 [001] 184503.384331: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.384332: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.384332: kvm_page_fault: address 7fc036d000 error_code 582 dirty_log_test-29003 [001] 184503.384332: kvm_dirty_ring_push: ring 1: dirty 0x37f reset 0x1c0 slot 1 offset 0x37e ret 0 (used 447) dirty_log_test-29003 [001] 184503.384333: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.384334: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.384334: kvm_page_fault: address 7fc036e000 error_code 582 dirty_log_test-29003 [001] 184503.384336: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.384336: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.384336: kvm_page_fault: address 7fc036e000 error_code 582 dirty_log_test-29003 [001] 184503.384337: kvm_dirty_ring_push: ring 1: dirty 0x380 reset 0x1c0 slot 1 offset 0x37f ret 1 (used 448) dirty_log_test-29003 [001] 184503.384337: kvm_dirty_ring_exit: vcpu 1 dirty_log_test-29003 [001] 184503.384338: kvm_fpu: unload dirty_log_test-29003 [001] 184503.384340: kvm_userspace_exit: reason 0x1d (29) dirty_log_test-29000 [006] 184503.505103: kvm_dirty_ring_reset: ring 1: dirty 0x380 reset 0x380 (used 0) dirty_log_test-29003 [001] 184503.505184: kvm_fpu: load dirty_log_test-29003 [001] 184503.505187: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.505193: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.505194: kvm_page_fault: address 7fc036f000 error_code 582 <-------- [1] dirty_log_test-29003 [001] 184503.505206: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.505207: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.505207: kvm_page_fault: address 7fc036f000 error_code 582 dirty_log_test-29003 [001] 184503.505226: kvm_dirty_ring_push: ring 1: dirty 0x381 reset 0x380 slot 1 offset 0x380 ret 0 (used 1) dirty_log_test-29003 [001] 184503.505226: kvm_entry: vcpu 1 dirty_log_test-29003 [001] 184503.505227: kvm_exit: reason EPT_VIOLATION rip 0x40359f info 582 0 dirty_log_test-29003 [001] 184503.505228: kvm_page_fault: address 7fc0370000 error_code 582 dirty_log_test-29003 [001] 184503.505231: kvm_entry: vcpu 1...
The test was trying to continuously write to pages, from above log starting from
7fc036d000. The reason0x1d(29) is the new dirty ring full exit reason.So far I'm still unsure of two things:
Quoted 7 lines
Quoted 4 lines
1. Why for each page we faulted twice rather than once. Take the example of page at
7fc036e000above, the first fault didn't trigger the marking dirty path, while only until the 2nd ept violation did we triggerkvm_dirty_ring_push.Not sure about that. Try enabling kvmmmu tracepoints too, it will tell you more of the path that was taken while processing the EPT violation.
These new tracepoints are extremely useful (which I didn't notice before).
So here's the final culprit...
void kvm_reset_dirty_gfn(struct kvm *kvm, u32 slot, u64 offset, u64 mask)
{
...
spin_lock(&kvm->mmu_lock);
/* FIXME: we should use a single AND operation, but there is no
* applicable atomic API.
*/
while (mask) {
clear_bit_le(offset + __ffs(mask), memslot->dirty_bitmap);
mask &= mask - 1;
}
kvm_arch_mmu_enable_log_dirty_pt_masked(kvm, memslot, offset, mask);
spin_unlock(&kvm->mmu_lock);
}The mask is cleared before reachingkvm_arch_mmu_enable_log_dirty_pt_masked()..
The funny thing is that I did have a few more patches to even skip allocate the dirty_bitmap when dirty ring is enabled (hence in that tree I removed this while loop too, so that has no such problem). However I dropped those patches when I posted the RFC because I don't think it's mature, and the selftest didn't complain about that either.. Though, I do plan to redo that in v2 if you don't disagree. The major question would be whether the dirty_bitmap could still be for any use if dirty ring is enabled.
Quoted 24 lines
If your machine has PML, what you're seeing is likely not-present violation, not dirty-protect violation. Try disabling pml and see if the trace changes.
Quoted 8 lines
2. Why we didn't get the last page written again after
kvm_userspace_exit(last page was7fc036e000, and the test failed because7fc036e000detected change however dirty bit unset). In this case the first write afterKVM_RESET_DIRTY_RINGSis the line pointed by [1], I thought it should be a rewritten of page7fc036e000because when the user exit happens logically the write should not happen yet and eip should keep. However at [1] it's already writting to a new page.IIUC you should get, with PML enabled:
- guest writes to page
- PML marks dirty bit, causes vmexit
- host copies PML log to ring, causes userspace exit
- userspace callsKVM_RESET_DIRTY_RINGS
- host marks page as clean
- userspace callsKVM_RUN
- guest writes again to page
Quoted 5 lines
but the page won't be in the ring until after another vmexit happens. Therefore, it's okay to reap the pages in the ring asynchronously, but there must be a synchronization point in the testcase sooner or later, where all CPUs are kicked out of
KVM_RUN. This synchronization point corresponds to the migration downtime.
Yep, currently in the test case I used the same signal trick to kick the vcpu out to make sure PML buffers are flushed during the vmexit, before the main thread starts to collect dirty bits.
Thanks,
Signature · 1 line
Peter Xu
Paolo Bonzini
maintainerQuoted 5 lines · Peter Xu
Quoted 2 lines
Try enabling kvmmmu tracepoints too, it will tell
you more of the path that was taken while processing the EPT violation.These new tracepoints are extremely useful (which I didn't notice before).
Yes, they are!
Quoted 18 lines
So here's the final culprit...
void
kvm_reset_dirty_gfn(struct kvm *kvm, u32 slot, u64 offset, u64 mask){ ... spin_lock(&kvm->mmu_lock); /* FIXME: we should use a single AND operation, but there is no * applicable atomic API. */ while (mask) { clear_bit_le(offset + __ffs(mask), memslot->dirty_bitmap); mask &= mask - 1; } kvm_arch_mmu_enable_log_dirty_pt_masked(kvm, memslot, offset, mask); spin_unlock(&kvm->mmu_lock); }
Quoted 2 lines
The mask is cleared before reaching
kvm_arch_mmu_enable_log_dirty_pt_masked()..
I'm not sure why that results in two vmexits? (clearing before kvm_arch_mmu_enable_log_dirty_pt_masked is also what KVM_{GET,CLEAR}_DIRTY_LOG does).
Quoted 8 lines
The funny thing is that I did have a few more patches to even skip allocate the
dirty_bitmapwhen dirty ring is enabled (hence in that tree I removed this while loop too, so that has no such problem). However I dropped those patches when I posted the RFC because I don't think it's mature, and the selftest didn't complain about that either.. Though, I do plan to redo that in v2 if you don't disagree. The major question would be whether thedirty_bitmapcould still be for any use if dirty ring is enabled.
Userspace may want a dirty bitmap in addition to a list (for example:
list for migration, bitmap for framebuffer update), but it can also do a pass over the dirty rings in order to update an internal bitmap.
So I think it make sense to make it either one or the other.
Paolo
Quoted 8 lines · Paolo Bonzini
Quoted 5 lines · Peter Xu
Quoted 2 lines
Try enabling kvmmmu tracepoints too, it will tell
you more of the path that was taken while processing the EPT violation.These new tracepoints are extremely useful (which I didn't notice before).
Yes, they are!
(I forgot to say thanks for teaching me that! :)
Quoted 19 lines
Quoted 19 lines
So here's the final culprit...
void
kvm_reset_dirty_gfn(struct kvm *kvm, u32 slot, u64 offset, u64 mask){ ... spin_lock(&kvm->mmu_lock); /* FIXME: we should use a single AND operation, but there is no * applicable atomic API. */ while (mask) { clear_bit_le(offset + __ffs(mask), memslot->dirty_bitmap); mask &= mask - 1; } kvm_arch_mmu_enable_log_dirty_pt_masked(kvm, memslot, offset, mask); spin_unlock(&kvm->mmu_lock); }
Quoted 6 lines
Quoted 2 lines
The mask is cleared before reaching
kvm_arch_mmu_enable_log_dirty_pt_masked()..I'm not sure why that results in two vmexits? (clearing before
kvm_arch_mmu_enable_log_dirty_pt_maskedis also what KVM_{GET,CLEAR}_DIRTY_LOG does).
Sorry my fault to be not clear on this.
The kvm_arch_mmu_enable_log_dirty_pt_masked() only explains why the same page is not written again after the ring-full userspace exit (which triggered the real dirty bit missing), and that's because the write bit is not removed during KVM_RESET_DIRTY_RINGS so the next vmenter will directly write to the previous page without vmexit.
The two vmexits is another story - I tracked it is retried because mmu_notifier_seq has changed, hence it goes through this path:
if (mmu_notifier_retry(vcpu->kvm, mmu_seq)) goto out_unlock;
It's because when try_async_pf(), we will do a writable page fault, which probably triggers both the invalidate_range_end and change_pte notifiers. A reference trace when EPT enabled:
kvm_mmu_notifier_change_pte+1
__mmu_notifier_change_pte+82
wp_page_copy+1907
do_wp_page+478
__handle_mm_fault+3395
handle_mm_fault+196
__get_user_pages+681
get_user_pages_unlocked+172
__gfn_to_pfn_memslot+290
try_async_pf+141
tdp_page_fault+326
kvm_mmu_page_fault+115
kvm_arch_vcpu_ioctl_run+2675
kvm_vcpu_ioctl+536
do_vfs_ioctl+1029
ksys_ioctl+94
__x64_sys_ioctl+22
do_syscall_64+91I'm not sure whether that's ideal, but it makes sense to me.
Quoted 10 lines
Quoted 9 lines
The funny thing is that I did have a few more patches to even skip allocate the
dirty_bitmapwhen dirty ring is enabled (hence in that tree I removed this while loop too, so that has no such problem). However I dropped those patches when I posted the RFC because I don't think it's mature, and the selftest didn't complain about that either.. Though, I do plan to redo that in v2 if you don't disagree. The major question would be whether thedirty_bitmapcould still be for any use if dirty ring is enabled.
Quoted 5 lines
Userspace may want a dirty bitmap in addition to a list (for example:
list for migration, bitmap for framebuffer update), but it can also do a pass over the dirty rings in order to update an internal bitmap.So I think it make sense to make it either one or the other.
Ok, then I'll do.
Thanks,
Signature · 1 line
Peter Xu
Quoted 21 lines · Paolo Bonzini
Hi Peter,
thanks for the RFC! Just a couple comments before I look at the series (for which I don't expect many surprises).
Quoted 14 lines · Peter Xu
I marked this series as RFC because I'm at least uncertain on this change of
vcpu_enter_guest():if (kvm_check_request(KVM_REQ_DIRTY_RING_FULL, vcpu)) { vcpu->run->exit_reason = KVM_EXIT_DIRTY_RING_FULL; /* * If this is requested, it means that we've * marked the dirty bit in the dirty ring BUT * we've not written the date. Do it now. */ r = kvm_emulate_instruction(vcpu, 0); r = r >= 0 ? 0 : r; goto out; }
Quoted 5 lines
This is not needed, it will just be a false negative (dirty page that actually isn't dirty). The dirty bit will be cleared when userspace resets the ring buffer; then the instruction will be executed again and mark the page dirty again. Since ring full is not a common condition, it's not a big deal.
Side topic, KVM_REQ_DIRTY_RING_FULL is misnamed, it's set when a ring goes above its soft limit, not when the ring is actually full. It took quite a bit of digging to figure out whether or not PML was broken...
Quoted 28 lines · Sean Christopherson
Quoted 21 lines · Paolo Bonzini
Hi Peter,
thanks for the RFC! Just a couple comments before I look at the series (for which I don't expect many surprises).
Quoted 14 lines · Peter Xu
I marked this series as RFC because I'm at least uncertain on this change of
vcpu_enter_guest():if (kvm_check_request(KVM_REQ_DIRTY_RING_FULL, vcpu)) { vcpu->run->exit_reason = KVM_EXIT_DIRTY_RING_FULL; /* * If this is requested, it means that we've * marked the dirty bit in the dirty ring BUT * we've not written the date. Do it now. */ r = kvm_emulate_instruction(vcpu, 0); r = r >= 0 ? 0 : r; goto out; }Quoted 5 lines
This is not needed, it will just be a false negative (dirty page that actually isn't dirty). The dirty bit will be cleared when userspace resets the ring buffer; then the instruction will be executed again and mark the page dirty again. Since ring full is not a common condition, it's not a big deal.
Quoted 3 lines
Side topic,
KVM_REQ_DIRTY_RING_FULLis misnamed, it's set when a ring goes above its soft limit, not when the ring is actually full. It took quite a bit of digging to figure out whether or not PML was broken...
Yeah it's indeed a bit confusing.
Do you like KVM_REQ_DIRTY_RING_COLLECT? Pair with KVM_EXIT_DIRTY_RING_COLLECT. Or, suggestions?
Signature · 1 line
Peter Xu
Quoted 24 lines · Peter Xu
Branch is here:https://github.com/xzpeter/linux/tree/kvm-dirty-ring
Overview
============This is a continued work from Lei Cao<[email protected]> and Paolo on the KVM dirty ring interface. To make it simple, I'll still start with version 1 as RFC.
The new dirty ring interface is another way to collect dirty pages for the virtual machine, but it is different from the existing dirty logging interface in a few ways, majorly:
- Data format: The dirty data was in a ring format rather than a bitmap format, so the size of data to sync for dirty logging does not depend on the size of guest memory any more, but speed of dirtying. Also, the dirty ring is per-vcpu (currently plus another per-vm ring, so total ring number is N+1), while the dirty bitmap is per-vm.
- Data copy: The sync of dirty pages does not need data copy any more, but instead the ring is shared between the userspace and kernel by page sharings (
mmap()on either the vm fd or vcpu fd)
Quoted 7 lines
- Interface: Instead of using the old
KVM_GET_DIRTY_LOG,KVM_CLEAR_DIRTY_LOGinterfaces, the new ring uses a new interface calledKVM_RESET_DIRTY_RINGSwhen we want to reset the collected dirty pages to protected mode again (works likeKVM_CLEAR_DIRTY_LOG, but ring based)And more.
Looks really interesting, I wonder if we can make this as a library then we can reuse it for vhost.
Thanks
Quoted 32 lines · Jason Wang
Quoted 30 lines · Peter Xu
Branch is here:https://github.com/xzpeter/linux/tree/kvm-dirty-ring
Overview
============This is a continued work from Lei Cao<[email protected]> and Paolo on the KVM dirty ring interface. To make it simple, I'll still start with version 1 as RFC.
The new dirty ring interface is another way to collect dirty pages for the virtual machine, but it is different from the existing dirty logging interface in a few ways, majorly:
- Data format: The dirty data was in a ring format rather than a bitmap format, so the size of data to sync for dirty logging does not depend on the size of guest memory any more, but speed of dirtying. Also, the dirty ring is per-vcpu (currently plus another per-vm ring, so total ring number is N+1), while the dirty bitmap is per-vm.
- Data copy: The sync of dirty pages does not need data copy any more, but instead the ring is shared between the userspace and kernel by page sharings (
mmap()on either the vm fd or vcpu fd)- Interface: Instead of using the old
KVM_GET_DIRTY_LOG,KVM_CLEAR_DIRTY_LOGinterfaces, the new ring uses a new interface calledKVM_RESET_DIRTY_RINGSwhen we want to reset the collected dirty pages to protected mode again (works likeKVM_CLEAR_DIRTY_LOG, but ring based)
Quoted 5 lines
Quoted 1 line
And more.
Looks really interesting, I wonder if we can make this as a library then we can reuse it for vhost.
So iiuc this ring will majorly for (1) data exchange between kernel and user, and (2) shared memory. I think from that pov yeh it should work even for vhost.
It shouldn't be hard to refactor the interfaces to avoid kvm elements, however I'm not sure how to do that best. Maybe like irqbypass and put it into virt/lib/ as a standlone module? Would it worth it?
Paolo, what's your take?
Signature · 1 line
Peter Xu
Quoted 36 lines · Peter Xu
Quoted 31 lines · Jason Wang
Quoted 30 lines · Peter Xu
Branch is here:https://github.com/xzpeter/linux/tree/kvm-dirty-ring
Overview
============This is a continued work from Lei Cao<[email protected]> and Paolo on the KVM dirty ring interface. To make it simple, I'll still start with version 1 as RFC.
The new dirty ring interface is another way to collect dirty pages for the virtual machine, but it is different from the existing dirty logging interface in a few ways, majorly:
- Data format: The dirty data was in a ring format rather than a bitmap format, so the size of data to sync for dirty logging does not depend on the size of guest memory any more, but speed of dirtying. Also, the dirty ring is per-vcpu (currently plus another per-vm ring, so total ring number is N+1), while the dirty bitmap is per-vm.
- Data copy: The sync of dirty pages does not need data copy any more, but instead the ring is shared between the userspace and kernel by page sharings (
mmap()on either the vm fd or vcpu fd)- Interface: Instead of using the old
KVM_GET_DIRTY_LOG,KVM_CLEAR_DIRTY_LOGinterfaces, the new ring uses a new interface calledKVM_RESET_DIRTY_RINGSwhen we want to reset the collected dirty pages to protected mode again (works likeKVM_CLEAR_DIRTY_LOG, but ring based)Quoted 4 lines
Quoted 1 line
And more.
Looks really interesting, I wonder if we can make this as a library then we can reuse it for vhost.
Quoted 7 lines
So iiuc this ring will majorly for (1) data exchange between kernel and user, and (2) shared memory. I think from that pov yeh it should work even for vhost.
It shouldn't be hard to refactor the interfaces to avoid kvm elements, however I'm not sure how to do that best. Maybe like irqbypass and put it into
virt/lib/as a standlone module? Would it worth it?
Maybe, and it looks to me some dirty pages reporting API for VFIO is proposed in the same time. It will be helpful to unify them (or at least leave a chance for other users).
Thanks
Quoted 3 lines
Paolo, what's your take?
Quoted 13 lines · Peter Xu
Branch is here: https://github.com/xzpeter/linux/tree/kvm-dirty-ring
Overview
============This is a continued work from Lei Cao <[email protected]> and Paolo on the KVM dirty ring interface. To make it simple, I'll still start with version 1 as RFC.
The new dirty ring interface is another way to collect dirty pages for the virtual machine, but it is different from the existing dirty logging interface in a few ways, majorly:
Quoted 6 lines
- Data format: The dirty data was in a ring format rather than a bitmap format, so the size of data to sync for dirty logging does not depend on the size of guest memory any more, but speed of dirtying. Also, the dirty ring is per-vcpu (currently plus another per-vm ring, so total ring number is N+1), while the dirty bitmap is per-vm.
I like Sean's suggestion to fetch rings when dirtying. That could reduce the number of dirty rings to examine.
Also, as is, this means that the same gfn may be present in multiple rings, right?
Quoted 21 lines
- Data copy: The sync of dirty pages does not need data copy any more, but instead the ring is shared between the userspace and kernel by page sharings (
mmap()on either the vm fd or vcpu fd)- Interface: Instead of using the old
KVM_GET_DIRTY_LOG,KVM_CLEAR_DIRTY_LOGinterfaces, the new ring uses a new interface calledKVM_RESET_DIRTY_RINGSwhen we want to reset the collected dirty pages to protected mode again (works likeKVM_CLEAR_DIRTY_LOG, but ring based)And more.
I would appreciate if the reviewers can start with patch "KVM:
Implement ring-based dirty memory tracking", especially the document update part for the big picture. Then I'll avoid copying into most of them into cover letter again.I marked this series as RFC because I'm at least uncertain on this change of
vcpu_enter_guest():
Quoted 6 lines
if (
kvm_check_request(KVM_REQ_DIRTY_RING_FULL, vcpu)) {
vcpu->run->exit_reason=KVM_EXIT_DIRTY_RING_FULL; /*
* If this is requested, it means that we've
* marked the dirty bit in the dirty ring BUT
* we've not written the date. Do it now.
not written the "data" ?
Quoted 35 lines
*/ r = kvm_emulate_instruction(vcpu, 0); r = r >= 0 ? 0 : r; goto out; }I did a
kvm_emulate_instruction()when dirty ring reaches softlimit and want to exit to userspace, however I'm not really sure whether there could have any side effect. I'd appreciate any comment of above, or anything else.Tests
===========I wanted to continue work on the QEMU part, but after I noticed that the interface might still prone to change, I posted this series first. However to make sure it's at least working, I've provided unit tests together with the series. The unit tests should be able to test the series in at least three major paths:
(1) ./dirty_log_test -M dirty-ring
This tests async ring operations: this should be the major work mode for the dirty ring interface, say, when the kernel is queuing more data, the userspace is collecting too. Ring can hardly reaches full when working like this, because in most cases the collection could be fast.
(2) ./dirty_log_test -M dirty-ring -c 1024
This set the ring size to be very small so that ring soft-full always triggers (soft-full is a soft limit of the ring state, when the dirty ring reaches the soft limit it'll do a userspace exit and let the userspace to collect the data).
Quoted 7 lines
(3) ./dirty_log_test -M dirty-ring-wait-queue
This sololy test the extreme case where ring is full. When the ring is completely full, the thread (no matter vcpu or not) will be put onto a per-vm waitqueue, and
KVM_RESET_DIRTY_RINGSwill wake the threads up (assuming until which the ring will not be full any more).
Am I correct assuming that guest memory can be dirtied by DMA operations? Should
Not being that familiar with the current implementation of dirty page tracking, I wonder who marks the pages dirty in that case, and when? If the VM ring is used for I/O threads, isn't it possible that a large DMA could dirty a sufficiently large number of GFNs to overflow the associated ring? Does this case need a separate way to queue the dirtying I/O thread?
Quoted 45 lines
Thanks,
Cao, Lei (2):
KVM: Add kvm/vcpu argument tomark_dirty_page_in_slot
KVM: X86: Implement ring-based dirty memory trackingPaolo Bonzini (1):
KVM: Move running VCPU from ARM to common codePeter Xu (12):
KVM: Add build-time error check onkvm_runsize
KVM: Implement ring-based dirty memory tracking
KVM: Make dirty ring exclusive to dirty bitmap log
KVM: Introduce dirty ring wait queue
KVM: selftests: Always clear dirty bitmap after iteration
KVM: selftests: Syncuapi/linux/kvm.htotools/
KVM: selftests: Use a single binary for dirty/clear log test
KVM: selftests: Introduceafter_vcpu_runhook for dirty log test
KVM: selftests: Add dirty ring buffer test
KVM: selftests: Letdirty_log_testasync for dirty ring test
KVM: selftests: Add "-c" parameter to dirty log test
KVM: selftests: Test dirty ring waitqueueDocumentation/virt/kvm/api.txt | 116 +++++ arch/arm/include/asm/kvm_host.h | 2 - arch/arm64/include/asm/kvm_host.h | 2 - arch/x86/include/asm/kvm_host.h | 5 + arch/x86/include/uapi/asm/kvm.h | 1 + arch/x86/kvm/Makefile | 3 +- arch/x86/kvm/mmu/mmu.c | 6 + arch/x86/kvm/vmx/vmx.c | 7 + arch/x86/kvm/x86.c | 12 + include/linux/kvm_dirty_ring.h | 67 +++ include/linux/kvm_host.h | 37 ++ include/linux/kvm_types.h | 1 + include/uapi/linux/kvm.h | 36 ++ tools/include/uapi/linux/kvm.h | 47 ++ tools/testing/selftests/kvm/Makefile | 2 - .../selftests/kvm/clear_dirty_log_test.c | 2 - tools/testing/selftests/kvm/dirty_log_test.c | 452 ++++++++++++++++-- .../testing/selftests/kvm/include/kvm_util.h | 6 + tools/testing/selftests/kvm/lib/kvm_util.c | 103 ++++ .../selftests/kvm/lib/kvm_util_internal.h | 5 + virt/kvm/arm/arm.c | 29 --
Quoted 8 lines
virt/kvm/arm/perf.c | 6 +- virt/kvm/arm/vgic/vgic-mmio.c | 15 +- virt/kvm/dirty_ring.c | 156 ++++++ virt/kvm/kvm_main.c | 315 +++++++++++- 25 files changed, 1329 insertions(+), 104 deletions(-) create mode 100644 include/linux/kvm_dirty_ring.h delete mode 100644 tools/testing/selftests/kvm/clear_dirty_log_test.c create mode 100644 virt/kvm/dirty_ring.c
--
Cheers,
Christophe de Dinechin (IRC c3d)
Paolo Bonzini
maintainerQuoted 23 lines · Christophe de Dinechin
Quoted 13 lines · Peter Xu
Branch is here: https://github.com/xzpeter/linux/tree/kvm-dirty-ring
Overview
============This is a continued work from Lei Cao <[email protected]> and Paolo on the KVM dirty ring interface. To make it simple, I'll still start with version 1 as RFC.
The new dirty ring interface is another way to collect dirty pages for the virtual machine, but it is different from the existing dirty logging interface in a few ways, majorly:
Quoted 6 lines
- Data format: The dirty data was in a ring format rather than a bitmap format, so the size of data to sync for dirty logging does not depend on the size of guest memory any more, but speed of dirtying. Also, the dirty ring is per-vcpu (currently plus another per-vm ring, so total ring number is N+1), while the dirty bitmap is per-vm.
Quoted 2 lines
I like Sean's suggestion to fetch rings when dirtying. That could reduce the number of dirty rings to examine.
What do you mean by "fetch rings"?
Quoted 2 lines
Also, as is, this means that the same gfn may be present in multiple rings, right?
I think the actual marking of a page as dirty is protected by a spinlock but I will defer to Peter on this.
Paolo
Quoted 142 lines
Quoted 21 lines
- Data copy: The sync of dirty pages does not need data copy any more, but instead the ring is shared between the userspace and kernel by page sharings (
mmap()on either the vm fd or vcpu fd)- Interface: Instead of using the old
KVM_GET_DIRTY_LOG,KVM_CLEAR_DIRTY_LOGinterfaces, the new ring uses a new interface calledKVM_RESET_DIRTY_RINGSwhen we want to reset the collected dirty pages to protected mode again (works likeKVM_CLEAR_DIRTY_LOG, but ring based)And more.
I would appreciate if the reviewers can start with patch "KVM:
Implement ring-based dirty memory tracking", especially the document update part for the big picture. Then I'll avoid copying into most of them into cover letter again.I marked this series as RFC because I'm at least uncertain on this change of
vcpu_enter_guest():Quoted 6 lines
if (
kvm_check_request(KVM_REQ_DIRTY_RING_FULL, vcpu)) {
vcpu->run->exit_reason=KVM_EXIT_DIRTY_RING_FULL; /*
* If this is requested, it means that we've
* marked the dirty bit in the dirty ring BUT
* we've not written the date. Do it now.not written the "data" ?
Quoted 35 lines
*/ r = kvm_emulate_instruction(vcpu, 0); r = r >= 0 ? 0 : r; goto out; }I did a
kvm_emulate_instruction()when dirty ring reaches softlimit and want to exit to userspace, however I'm not really sure whether there could have any side effect. I'd appreciate any comment of above, or anything else.Tests
===========I wanted to continue work on the QEMU part, but after I noticed that the interface might still prone to change, I posted this series first. However to make sure it's at least working, I've provided unit tests together with the series. The unit tests should be able to test the series in at least three major paths:
(1) ./dirty_log_test -M dirty-ring
This tests async ring operations: this should be the major work mode for the dirty ring interface, say, when the kernel is queuing more data, the userspace is collecting too. Ring can hardly reaches full when working like this, because in most cases the collection could be fast.
(2) ./dirty_log_test -M dirty-ring -c 1024
This set the ring size to be very small so that ring soft-full always triggers (soft-full is a soft limit of the ring state, when the dirty ring reaches the soft limit it'll do a userspace exit and let the userspace to collect the data).
Quoted 7 lines
(3) ./dirty_log_test -M dirty-ring-wait-queue
This sololy test the extreme case where ring is full. When the ring is completely full, the thread (no matter vcpu or not) will be put onto a per-vm waitqueue, and
KVM_RESET_DIRTY_RINGSwill wake the threads up (assuming until which the ring will not be full any more).Am I correct assuming that guest memory can be dirtied by DMA operations? Should
Not being that familiar with the current implementation of dirty page tracking, I wonder who marks the pages dirty in that case, and when? If the VM ring is used for I/O threads, isn't it possible that a large DMA could dirty a sufficiently large number of GFNs to overflow the associated ring? Does this case need a separate way to queue the dirtying I/O thread?
Quoted 45 lines
Thanks,
Cao, Lei (2):
KVM: Add kvm/vcpu argument tomark_dirty_page_in_slot
KVM: X86: Implement ring-based dirty memory trackingPaolo Bonzini (1):
KVM: Move running VCPU from ARM to common codePeter Xu (12):
KVM: Add build-time error check onkvm_runsize
KVM: Implement ring-based dirty memory tracking
KVM: Make dirty ring exclusive to dirty bitmap log
KVM: Introduce dirty ring wait queue
KVM: selftests: Always clear dirty bitmap after iteration
KVM: selftests: Syncuapi/linux/kvm.htotools/
KVM: selftests: Use a single binary for dirty/clear log test
KVM: selftests: Introduceafter_vcpu_runhook for dirty log test
KVM: selftests: Add dirty ring buffer test
KVM: selftests: Letdirty_log_testasync for dirty ring test
KVM: selftests: Add "-c" parameter to dirty log test
KVM: selftests: Test dirty ring waitqueueDocumentation/virt/kvm/api.txt | 116 +++++ arch/arm/include/asm/kvm_host.h | 2 - arch/arm64/include/asm/kvm_host.h | 2 - arch/x86/include/asm/kvm_host.h | 5 + arch/x86/include/uapi/asm/kvm.h | 1 + arch/x86/kvm/Makefile | 3 +- arch/x86/kvm/mmu/mmu.c | 6 + arch/x86/kvm/vmx/vmx.c | 7 + arch/x86/kvm/x86.c | 12 + include/linux/kvm_dirty_ring.h | 67 +++ include/linux/kvm_host.h | 37 ++ include/linux/kvm_types.h | 1 + include/uapi/linux/kvm.h | 36 ++ tools/include/uapi/linux/kvm.h | 47 ++ tools/testing/selftests/kvm/Makefile | 2 - .../selftests/kvm/clear_dirty_log_test.c | 2 - tools/testing/selftests/kvm/dirty_log_test.c | 452 ++++++++++++++++-- .../testing/selftests/kvm/include/kvm_util.h | 6 + tools/testing/selftests/kvm/lib/kvm_util.c | 103 ++++ .../selftests/kvm/lib/kvm_util_internal.h | 5 + virt/kvm/arm/arm.c | 29 --Quoted 8 lines
virt/kvm/arm/perf.c | 6 +- virt/kvm/arm/vgic/vgic-mmio.c | 15 +- virt/kvm/dirty_ring.c | 156 ++++++ virt/kvm/kvm_main.c | 315 +++++++++++- 25 files changed, 1329 insertions(+), 104 deletions(-) create mode 100644 include/linux/kvm_dirty_ring.h delete mode 100644 tools/testing/selftests/kvm/clear_dirty_log_test.c create mode 100644 virt/kvm/dirty_ring.c--
Cheers,
Christophe de Dinechin (IRC c3d)
Quoted 24 lines · Paolo Bonzini
Quoted 23 lines · Christophe de Dinechin
Quoted 19 lines · Peter Xu
Branch is here: https://github.com/xzpeter/linux/tree/kvm-dirty-ring
Overview
============This is a continued work from Lei Cao <[email protected]> and Paolo on the KVM dirty ring interface. To make it simple, I'll still start with version 1 as RFC.
The new dirty ring interface is another way to collect dirty pages for the virtual machine, but it is different from the existing dirty logging interface in a few ways, majorly:
- Data format: The dirty data was in a ring format rather than a bitmap format, so the size of data to sync for dirty logging does not depend on the size of guest memory any more, but speed of dirtying. Also, the dirty ring is per-vcpu (currently plus another per-vm ring, so total ring number is N+1), while the dirty bitmap is per-vm.
Quoted 4 lines
Quoted 2 lines
I like Sean's suggestion to fetch rings when dirtying. That could reduce the number of dirty rings to examine.
What do you mean by "fetch rings"?
I'd wildly guess Christophe means something like we create a ring pool and we try to find a ring to push the dirty gfn when it comes.
OK, should I count it as another vote to Sean's? :)
I agree, but imho larger number of rings won't really be a problem as long as it's still per-vcpu (after all we have a vcpu number limitation which is harder to break...). To me what Sean's suggestion attracted me most is that the interface is cleaner, that we don't need to expose the ring in two places any more. At the meantime, I won't care too much on perf issue here because after all it's dirty logging. If perf is critial, then I think I'll certainly choose per-vcpu ring without doubt even if it complicates the interface because it'll certainly help on some conditional lockless.
Quoted 6 lines
Quoted 3 lines
Also, as is, this means that the same gfn may be present in multiple rings, right?
I think the actual marking of a page as dirty is protected by a spinlock but I will defer to Peter on this.
In most cases imho we should be with the mmu lock iiuc because the general mmu page fault will take it. However I think there're special cases:
- when the spte was popped already and just write protected, then it's very possible we can go via the quick page fault path (fast_page_fault()). That is lockless (no mmu lock taken).
- when there's no vcpu context, we'll use the per-vm ring. Though per-vm ring is locked (per-vcpu ring is not!), I don't see how it would protect two callers to insert two identical gfns sequentially.. Also it can happen between per-vm and per-vcpu ring as well.
So I think gfn duplication could happen, but it should be rare. Even if it happens, it won't hurt much because the 2nd/3rd/... dirty bit of the same gfn will simply be skipped by userspace when harvesting.
Quoted 32 lines
Paolo
Quoted 29 lines
Quoted 21 lines
- Data copy: The sync of dirty pages does not need data copy any more, but instead the ring is shared between the userspace and kernel by page sharings (
mmap()on either the vm fd or vcpu fd)- Interface: Instead of using the old
KVM_GET_DIRTY_LOG,KVM_CLEAR_DIRTY_LOGinterfaces, the new ring uses a new interface calledKVM_RESET_DIRTY_RINGSwhen we want to reset the collected dirty pages to protected mode again (works likeKVM_CLEAR_DIRTY_LOG, but ring based)And more.
I would appreciate if the reviewers can start with patch "KVM:
Implement ring-based dirty memory tracking", especially the document update part for the big picture. Then I'll avoid copying into most of them into cover letter again.I marked this series as RFC because I'm at least uncertain on this change of
vcpu_enter_guest():Quoted 6 lines
if (
kvm_check_request(KVM_REQ_DIRTY_RING_FULL, vcpu)) {
vcpu->run->exit_reason=KVM_EXIT_DIRTY_RING_FULL; /*
* If this is requested, it means that we've
* marked the dirty bit in the dirty ring BUT
* we've not written the date. Do it now.not written the "data" ?
Yep, though I'll drop these lines altogether so we'll be fine.. :)
Thanks,
Signature · 1 line
Peter Xu