* [PATCH v2 0/3] KVM: x86/mmu: Repurpose MMU shrinker into page cache shrinker
@ 2024-10-04 19:55 Vipin Sharma
2024-10-04 19:55 ` [PATCH v2 1/3] KVM: x86/mmu: Change KVM mmu shrinker to no-op Vipin Sharma
` (2 more replies)
0 siblings, 3 replies; 11+ messages in thread
From: Vipin Sharma @ 2024-10-04 19:55 UTC (permalink / raw)
To: seanjc, pbonzini, dmatlack
Cc: zhi.wang.linux, weijiang.yang, mizhang, liangchen.linux, kvm,
linux-kernel, Vipin Sharma
This series is extracted out from the NUMA aware page table series[1].
MMU shrinker changes were in patches 1 to 9 in the old series.
This series is changing KVM MMU shrinker behaviour by emptying MMU page
caches which are used during page fault and MMU load operations. It also
incorporates feedback from the NUMA aware page table series[1] regarding
MMU shrinker.
KVM MMU shrinker has not been very effective in alleviating pain under
memory pressure. It frees up the pages actively being used which results
in VM degradation. VM will take fault and bring them again in page
tables. More discussions happened at [2]. Overall, consensus was to
reprupose it into the code which frees pages from KVM MMU page caches.
Recently [3], there was a discussion to disable shrinker for TDP MMU.
Revival of this series is result of that discussion.
There are two major differences from the old series.
1. There is no global accounting of cache pages. It is dynamically
calculated in mmu_shrink_count(). This has two effects; i) counting will
be inaccurate but code is much simpler, and ii) kvm_lock being used
here, this should be fine as mmu_shrink_scan() also holds the lock
for its operation.
2. Only empty mmu_shadow_page_cache and mmu_shadowed_info_cache. This
version doesn't empty split_shadow_page_cache as it is used only
during dirty logging operation and is one per VM unlike other two
which are per vCPU. I am not fully convinced that adding it is needed
as it will add the cost of adding one more mutex and synchronizing it
in shrinker. Also, if a VM is being dirty tracked most likely it will
be migrated (memory pressure might be the reason in the first place)
so better to not hinder migration effort and let vCPUs free up their
caches. If someone convinces me to add split cache as well then I can
send a separate patch to add that as well.
[1] https://lore.kernel.org/kvm/20230306224127.1689967-1-vipinsh@google.com/
[2] https://lore.kernel.org/lkml/Y45dldZnI6OIf+a5@google.com/
[3] https://lore.kernel.org/kvm/20240819214014.GA2313467.vipinsh@google.com/#t
Note to maintainers: scripts/checkpatch.pl is complaining about few
things on selftest (patch 3). These are just false positives.
1. Prefer strscpy() over strcpy().
This kernel API and not general libc userspace API.
2. Unnecessary whitespace before a quoted new line.
Just the way help string is written for selftests to align output of
help.
3. Prefer using '"%s...", __func__'
Because I wrote "help" word in the printf().
v2:
- Add a new selftest, mmu_shrinker_test.
v1: https://lore.kernel.org/kvm/20240913214316.1945951-1-vipinsh@google.com/
- No global counting of pages in cache. As this number might not remain
same between calls of mmu_shrink_count() and mmu_shrink_scan().
- Count cache pages in mmu_shrink_count(). KVM can tolerate inaccuracy
here.
- Empty mmu_shadow_page_cache and mmu_shadowed_info_cache only. Don't
empty split_shadow_page_cache.
v0: Patches 1-9 from NUMA aware page table series.
https://lore.kernel.org/kvm/20230306224127.1689967-1-vipinsh@google.com/
Vipin Sharma (3):
KVM: x86/mmu: Change KVM mmu shrinker to no-op
KVM: x86/mmu: Use MMU shrinker to shrink KVM MMU memory caches
KVM: selftests: Add a test to invoke MMU shrinker on KVM VMs
arch/x86/include/asm/kvm_host.h | 7 +-
arch/x86/kvm/mmu/mmu.c | 139 ++++-----
arch/x86/kvm/mmu/paging_tmpl.h | 14 +-
include/linux/kvm_host.h | 1 +
tools/testing/selftests/kvm/Makefile | 1 +
.../testing/selftests/kvm/include/test_util.h | 5 +
tools/testing/selftests/kvm/lib/test_util.c | 51 ++++
.../selftests/kvm/x86_64/mmu_shrinker_test.c | 269 ++++++++++++++++++
virt/kvm/kvm_main.c | 8 +-
9 files changed, 404 insertions(+), 91 deletions(-)
create mode 100644 tools/testing/selftests/kvm/x86_64/mmu_shrinker_test.c
base-commit: 12680d7b8ac4db2eba6237a21a93d2b0e78a52a6
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 1/3] KVM: x86/mmu: Change KVM mmu shrinker to no-op
2024-10-04 19:55 [PATCH v2 0/3] KVM: x86/mmu: Repurpose MMU shrinker into page cache shrinker Vipin Sharma
@ 2024-10-04 19:55 ` Vipin Sharma
2024-10-04 19:55 ` [PATCH v2 2/3] KVM: x86/mmu: Use MMU shrinker to shrink KVM MMU memory caches Vipin Sharma
2024-10-04 19:55 ` [PATCH v2 3/3] KVM: selftests: Add a test to invoke MMU shrinker on KVM VMs Vipin Sharma
2 siblings, 0 replies; 11+ messages in thread
From: Vipin Sharma @ 2024-10-04 19:55 UTC (permalink / raw)
To: seanjc, pbonzini, dmatlack
Cc: zhi.wang.linux, weijiang.yang, mizhang, liangchen.linux, kvm,
linux-kernel, Vipin Sharma
Remove global kvm_total_used_mmu_pages and page zapping flow from MMU
shrinker. Keep shrinker infrastructure in place to reuse in future
commits for freeing KVM page caches. Remove zapped_obsolete_pages list
from struct kvm_arch{} and use local list in kvm_zap_obsolete_pages()
since MMU shrinker is not using it anymore.
mmu_shrink_scan() is very disruptive to VMs. It picks the first VM in
the vm_list, zaps the oldest page which is most likely an upper level
SPTEs and most like to be reused. Prior to TDP MMU, this is even more
disruptive in nested VMs case, considering L1 SPTEs will be the oldest
even though most of the entries are for L2 SPTEs.
As discussed in
https://lore.kernel.org/lkml/Y45dldZnI6OIf+a5@google.com/ shrinker logic
has not be very useful in actually keeping VMs performant and reducing
memory usage.
Suggested-by: Sean Christopherson <seanjc@google.com>
Suggested-by: David Matlack <dmatlack@google.com>
Reviewed-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
arch/x86/include/asm/kvm_host.h | 1 -
arch/x86/kvm/mmu/mmu.c | 92 +++------------------------------
2 files changed, 8 insertions(+), 85 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index b0c0bc0ed813f..cbfe31bac6cf6 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1309,7 +1309,6 @@ struct kvm_arch {
bool pre_fault_allowed;
struct hlist_head mmu_page_hash[KVM_NUM_MMU_PAGES];
struct list_head active_mmu_pages;
- struct list_head zapped_obsolete_pages;
/*
* A list of kvm_mmu_page structs that, if zapped, could possibly be
* replaced by an NX huge page. A shadow page is on this list if its
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index d25c2b395116e..213e46b55dda2 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -179,7 +179,6 @@ struct kvm_shadow_walk_iterator {
static struct kmem_cache *pte_list_desc_cache;
struct kmem_cache *mmu_page_header_cache;
-static struct percpu_counter kvm_total_used_mmu_pages;
static void mmu_spte_set(u64 *sptep, u64 spte);
@@ -1651,27 +1650,15 @@ static void kvm_mmu_check_sptes_at_free(struct kvm_mmu_page *sp)
#endif
}
-/*
- * This value is the sum of all of the kvm instances's
- * kvm->arch.n_used_mmu_pages values. We need a global,
- * aggregate version in order to make the slab shrinker
- * faster
- */
-static inline void kvm_mod_used_mmu_pages(struct kvm *kvm, long nr)
-{
- kvm->arch.n_used_mmu_pages += nr;
- percpu_counter_add(&kvm_total_used_mmu_pages, nr);
-}
-
static void kvm_account_mmu_page(struct kvm *kvm, struct kvm_mmu_page *sp)
{
- kvm_mod_used_mmu_pages(kvm, +1);
+ kvm->arch.n_used_mmu_pages++;
kvm_account_pgtable_pages((void *)sp->spt, +1);
}
static void kvm_unaccount_mmu_page(struct kvm *kvm, struct kvm_mmu_page *sp)
{
- kvm_mod_used_mmu_pages(kvm, -1);
+ kvm->arch.n_used_mmu_pages--;
kvm_account_pgtable_pages((void *)sp->spt, -1);
}
@@ -6338,6 +6325,7 @@ static void kvm_zap_obsolete_pages(struct kvm *kvm)
{
struct kvm_mmu_page *sp, *node;
int nr_zapped, batch = 0;
+ LIST_HEAD(invalid_list);
bool unstable;
restart:
@@ -6371,7 +6359,7 @@ static void kvm_zap_obsolete_pages(struct kvm *kvm)
}
unstable = __kvm_mmu_prepare_zap_page(kvm, sp,
- &kvm->arch.zapped_obsolete_pages, &nr_zapped);
+ &invalid_list, &nr_zapped);
batch += nr_zapped;
if (unstable)
@@ -6387,7 +6375,7 @@ static void kvm_zap_obsolete_pages(struct kvm *kvm)
* kvm_mmu_load()), and the reload in the caller ensure no vCPUs are
* running with an obsolete MMU.
*/
- kvm_mmu_commit_zap_page(kvm, &kvm->arch.zapped_obsolete_pages);
+ kvm_mmu_commit_zap_page(kvm, &invalid_list);
}
/*
@@ -6450,16 +6438,10 @@ static void kvm_mmu_zap_all_fast(struct kvm *kvm)
kvm_tdp_mmu_zap_invalidated_roots(kvm);
}
-static bool kvm_has_zapped_obsolete_pages(struct kvm *kvm)
-{
- return unlikely(!list_empty_careful(&kvm->arch.zapped_obsolete_pages));
-}
-
void kvm_mmu_init_vm(struct kvm *kvm)
{
kvm->arch.shadow_mmio_value = shadow_mmio_value;
INIT_LIST_HEAD(&kvm->arch.active_mmu_pages);
- INIT_LIST_HEAD(&kvm->arch.zapped_obsolete_pages);
INIT_LIST_HEAD(&kvm->arch.possible_nx_huge_pages);
spin_lock_init(&kvm->arch.mmu_unsync_pages_lock);
@@ -7015,65 +6997,13 @@ void kvm_mmu_invalidate_mmio_sptes(struct kvm *kvm, u64 gen)
static unsigned long mmu_shrink_scan(struct shrinker *shrink,
struct shrink_control *sc)
{
- struct kvm *kvm;
- int nr_to_scan = sc->nr_to_scan;
- unsigned long freed = 0;
-
- mutex_lock(&kvm_lock);
-
- list_for_each_entry(kvm, &vm_list, vm_list) {
- int idx;
-
- /*
- * Never scan more than sc->nr_to_scan VM instances.
- * Will not hit this condition practically since we do not try
- * to shrink more than one VM and it is very unlikely to see
- * !n_used_mmu_pages so many times.
- */
- if (!nr_to_scan--)
- break;
- /*
- * n_used_mmu_pages is accessed without holding kvm->mmu_lock
- * here. We may skip a VM instance errorneosly, but we do not
- * want to shrink a VM that only started to populate its MMU
- * anyway.
- */
- if (!kvm->arch.n_used_mmu_pages &&
- !kvm_has_zapped_obsolete_pages(kvm))
- continue;
-
- idx = srcu_read_lock(&kvm->srcu);
- write_lock(&kvm->mmu_lock);
-
- if (kvm_has_zapped_obsolete_pages(kvm)) {
- kvm_mmu_commit_zap_page(kvm,
- &kvm->arch.zapped_obsolete_pages);
- goto unlock;
- }
-
- freed = kvm_mmu_zap_oldest_mmu_pages(kvm, sc->nr_to_scan);
-
-unlock:
- write_unlock(&kvm->mmu_lock);
- srcu_read_unlock(&kvm->srcu, idx);
-
- /*
- * unfair on small ones
- * per-vm shrinkers cry out
- * sadness comes quickly
- */
- list_move_tail(&kvm->vm_list, &vm_list);
- break;
- }
-
- mutex_unlock(&kvm_lock);
- return freed;
+ return SHRINK_STOP;
}
static unsigned long mmu_shrink_count(struct shrinker *shrink,
struct shrink_control *sc)
{
- return percpu_counter_read_positive(&kvm_total_used_mmu_pages);
+ return SHRINK_EMPTY;
}
static struct shrinker *mmu_shrinker;
@@ -7204,12 +7134,9 @@ int kvm_mmu_vendor_module_init(void)
if (!mmu_page_header_cache)
goto out;
- if (percpu_counter_init(&kvm_total_used_mmu_pages, 0, GFP_KERNEL))
- goto out;
-
mmu_shrinker = shrinker_alloc(0, "x86-mmu");
if (!mmu_shrinker)
- goto out_shrinker;
+ goto out;
mmu_shrinker->count_objects = mmu_shrink_count;
mmu_shrinker->scan_objects = mmu_shrink_scan;
@@ -7219,8 +7146,6 @@ int kvm_mmu_vendor_module_init(void)
return 0;
-out_shrinker:
- percpu_counter_destroy(&kvm_total_used_mmu_pages);
out:
mmu_destroy_caches();
return ret;
@@ -7237,7 +7162,6 @@ void kvm_mmu_destroy(struct kvm_vcpu *vcpu)
void kvm_mmu_vendor_module_exit(void)
{
mmu_destroy_caches();
- percpu_counter_destroy(&kvm_total_used_mmu_pages);
shrinker_free(mmu_shrinker);
}
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v2 2/3] KVM: x86/mmu: Use MMU shrinker to shrink KVM MMU memory caches
2024-10-04 19:55 [PATCH v2 0/3] KVM: x86/mmu: Repurpose MMU shrinker into page cache shrinker Vipin Sharma
2024-10-04 19:55 ` [PATCH v2 1/3] KVM: x86/mmu: Change KVM mmu shrinker to no-op Vipin Sharma
@ 2024-10-04 19:55 ` Vipin Sharma
2024-10-04 20:04 ` Vipin Sharma
2024-10-24 23:25 ` Sean Christopherson
2024-10-04 19:55 ` [PATCH v2 3/3] KVM: selftests: Add a test to invoke MMU shrinker on KVM VMs Vipin Sharma
2 siblings, 2 replies; 11+ messages in thread
From: Vipin Sharma @ 2024-10-04 19:55 UTC (permalink / raw)
To: seanjc, pbonzini, dmatlack
Cc: zhi.wang.linux, weijiang.yang, mizhang, liangchen.linux, kvm,
linux-kernel, Vipin Sharma
Use MMU shrinker to iterate through all the vCPUs of all the VMs and
free pages allocated in MMU memory caches. Protect cache allocation in
page fault and MMU load path from MMU shrinker by using a per vCPU
mutex. In MMU shrinker, move the iterated VM to the end of the VMs list
so that the pain of emptying cache spread among other VMs too.
The specific caches to empty are mmu_shadow_page_cache and
mmu_shadowed_info_cache as these caches store whole pages. Emptying them
will give more impact to shrinker compared to other caches like
mmu_pte_list_desc_cache{} and mmu_page_header_cache{}
Holding per vCPU mutex lock ensures that a vCPU doesn't get surprised
by finding its cache emptied after filling them up for page table
allocations during page fault handling and MMU load operation. Per vCPU
mutex also makes sure there is only race between MMU shrinker and all
other vCPUs. This should result in very less contention.
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
arch/x86/include/asm/kvm_host.h | 6 +++
arch/x86/kvm/mmu/mmu.c | 69 +++++++++++++++++++++++++++------
arch/x86/kvm/mmu/paging_tmpl.h | 14 ++++---
include/linux/kvm_host.h | 1 +
virt/kvm/kvm_main.c | 8 +++-
5 files changed, 81 insertions(+), 17 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index cbfe31bac6cf6..63eaf03111ebb 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -811,6 +811,12 @@ struct kvm_vcpu_arch {
*/
struct kvm_mmu *walk_mmu;
+ /*
+ * Protect cache from getting emptied in MMU shrinker while vCPU might
+ * use cache for fault handling or loading MMU. As this is a per vCPU
+ * lock, only contention might happen when MMU shrinker runs.
+ */
+ struct mutex mmu_memory_cache_lock;
struct kvm_mmu_memory_cache mmu_pte_list_desc_cache;
struct kvm_mmu_memory_cache mmu_shadow_page_cache;
struct kvm_mmu_memory_cache mmu_shadowed_info_cache;
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 213e46b55dda2..8e2935347615d 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -4524,29 +4524,33 @@ static int direct_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault
if (r != RET_PF_INVALID)
return r;
+ mutex_lock(&vcpu->arch.mmu_memory_cache_lock);
r = mmu_topup_memory_caches(vcpu, false);
if (r)
- return r;
+ goto out_mmu_memory_cache_unlock;
r = kvm_faultin_pfn(vcpu, fault, ACC_ALL);
if (r != RET_PF_CONTINUE)
- return r;
+ goto out_mmu_memory_cache_unlock;
r = RET_PF_RETRY;
write_lock(&vcpu->kvm->mmu_lock);
if (is_page_fault_stale(vcpu, fault))
- goto out_unlock;
+ goto out_mmu_unlock;
r = make_mmu_pages_available(vcpu);
if (r)
- goto out_unlock;
+ goto out_mmu_unlock;
r = direct_map(vcpu, fault);
-out_unlock:
+out_mmu_unlock:
write_unlock(&vcpu->kvm->mmu_lock);
kvm_release_pfn_clean(fault->pfn);
+out_mmu_memory_cache_unlock:
+ mutex_unlock(&vcpu->arch.mmu_memory_cache_lock);
+
return r;
}
@@ -4617,25 +4621,28 @@ static int kvm_tdp_mmu_page_fault(struct kvm_vcpu *vcpu,
if (r != RET_PF_INVALID)
return r;
+ mutex_lock(&vcpu->arch.mmu_memory_cache_lock);
r = mmu_topup_memory_caches(vcpu, false);
if (r)
- return r;
+ goto out_mmu_memory_cache_unlock;
r = kvm_faultin_pfn(vcpu, fault, ACC_ALL);
if (r != RET_PF_CONTINUE)
- return r;
+ goto out_mmu_memory_cache_unlock;
r = RET_PF_RETRY;
read_lock(&vcpu->kvm->mmu_lock);
if (is_page_fault_stale(vcpu, fault))
- goto out_unlock;
+ goto out_mmu_unlock;
r = kvm_tdp_mmu_map(vcpu, fault);
-out_unlock:
+out_mmu_unlock:
read_unlock(&vcpu->kvm->mmu_lock);
kvm_release_pfn_clean(fault->pfn);
+out_mmu_memory_cache_unlock:
+ mutex_unlock(&vcpu->arch.mmu_memory_cache_lock);
return r;
}
#endif
@@ -5691,6 +5698,7 @@ int kvm_mmu_load(struct kvm_vcpu *vcpu)
{
int r;
+ mutex_lock(&vcpu->arch.mmu_memory_cache_lock);
r = mmu_topup_memory_caches(vcpu, !vcpu->arch.mmu->root_role.direct);
if (r)
goto out;
@@ -5717,6 +5725,7 @@ int kvm_mmu_load(struct kvm_vcpu *vcpu)
*/
kvm_x86_call(flush_tlb_current)(vcpu);
out:
+ mutex_unlock(&vcpu->arch.mmu_memory_cache_lock);
return r;
}
@@ -6303,6 +6312,7 @@ int kvm_mmu_create(struct kvm_vcpu *vcpu)
if (!vcpu->arch.mmu_shadow_page_cache.init_value)
vcpu->arch.mmu_shadow_page_cache.gfp_zero = __GFP_ZERO;
+ mutex_init(&vcpu->arch.mmu_memory_cache_lock);
vcpu->arch.mmu = &vcpu->arch.root_mmu;
vcpu->arch.walk_mmu = &vcpu->arch.root_mmu;
@@ -6997,13 +7007,50 @@ void kvm_mmu_invalidate_mmio_sptes(struct kvm *kvm, u64 gen)
static unsigned long mmu_shrink_scan(struct shrinker *shrink,
struct shrink_control *sc)
{
- return SHRINK_STOP;
+ struct kvm *kvm, *next_kvm, *first_kvm = NULL;
+ unsigned long i, freed = 0;
+ struct kvm_vcpu *vcpu;
+
+ mutex_lock(&kvm_lock);
+ list_for_each_entry_safe(kvm, next_kvm, &vm_list, vm_list) {
+ if (!first_kvm)
+ first_kvm = kvm;
+ else if (first_kvm == kvm)
+ break;
+
+ list_move_tail(&kvm->vm_list, &vm_list);
+
+ kvm_for_each_vcpu(i, vcpu, kvm) {
+ if (!mutex_trylock(&vcpu->arch.mmu_memory_cache_lock))
+ continue;
+ freed += kvm_mmu_empty_memory_cache(&vcpu->arch.mmu_shadow_page_cache);
+ freed += kvm_mmu_empty_memory_cache(&vcpu->arch.mmu_shadowed_info_cache);
+ mutex_unlock(&vcpu->arch.mmu_memory_cache_lock);
+ if (freed >= sc->nr_to_scan)
+ goto out;
+ }
+ }
+out:
+ mutex_unlock(&kvm_lock);
+ return freed;
}
static unsigned long mmu_shrink_count(struct shrinker *shrink,
struct shrink_control *sc)
{
- return SHRINK_EMPTY;
+ unsigned long i, count = 0;
+ struct kvm_vcpu *vcpu;
+ struct kvm *kvm;
+
+ mutex_lock(&kvm_lock);
+ list_for_each_entry(kvm, &vm_list, vm_list) {
+ kvm_for_each_vcpu(i, vcpu, kvm) {
+ count += READ_ONCE(vcpu->arch.mmu_shadow_page_cache.nobjs);
+ count += READ_ONCE(vcpu->arch.mmu_shadowed_info_cache.nobjs);
+ }
+ }
+ mutex_unlock(&kvm_lock);
+ return !count ? SHRINK_EMPTY : count;
}
static struct shrinker *mmu_shrinker;
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index 405bd7ceee2a3..084a5c532078f 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -809,13 +809,14 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault
return RET_PF_EMULATE;
}
+ mutex_lock(&vcpu->arch.mmu_memory_cache_lock);
r = mmu_topup_memory_caches(vcpu, true);
if (r)
- return r;
+ goto out_mmu_memory_cache_unlock;
r = kvm_faultin_pfn(vcpu, fault, walker.pte_access);
if (r != RET_PF_CONTINUE)
- return r;
+ goto out_mmu_memory_cache_unlock;
/*
* Do not change pte_access if the pfn is a mmio page, otherwise
@@ -840,16 +841,19 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault
write_lock(&vcpu->kvm->mmu_lock);
if (is_page_fault_stale(vcpu, fault))
- goto out_unlock;
+ goto out_mmu_unlock;
r = make_mmu_pages_available(vcpu);
if (r)
- goto out_unlock;
+ goto out_mmu_unlock;
r = FNAME(fetch)(vcpu, fault, &walker);
-out_unlock:
+out_mmu_unlock:
write_unlock(&vcpu->kvm->mmu_lock);
kvm_release_pfn_clean(fault->pfn);
+out_mmu_memory_cache_unlock:
+ mutex_unlock(&vcpu->arch.mmu_memory_cache_lock);
+
return r;
}
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index b23c6d48392f7..288e503f14a0b 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -1446,6 +1446,7 @@ void kvm_flush_remote_tlbs_memslot(struct kvm *kvm,
int kvm_mmu_topup_memory_cache(struct kvm_mmu_memory_cache *mc, int min);
int __kvm_mmu_topup_memory_cache(struct kvm_mmu_memory_cache *mc, int capacity, int min);
int kvm_mmu_memory_cache_nr_free_objects(struct kvm_mmu_memory_cache *mc);
+int kvm_mmu_empty_memory_cache(struct kvm_mmu_memory_cache *mc);
void kvm_mmu_free_memory_cache(struct kvm_mmu_memory_cache *mc);
void *kvm_mmu_memory_cache_alloc(struct kvm_mmu_memory_cache *mc);
#endif
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index cb2b78e92910f..5d89ca218791b 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -451,15 +451,21 @@ int kvm_mmu_memory_cache_nr_free_objects(struct kvm_mmu_memory_cache *mc)
return mc->nobjs;
}
-void kvm_mmu_free_memory_cache(struct kvm_mmu_memory_cache *mc)
+int kvm_mmu_empty_memory_cache(struct kvm_mmu_memory_cache *mc)
{
+ int freed = mc->nobjs;
while (mc->nobjs) {
if (mc->kmem_cache)
kmem_cache_free(mc->kmem_cache, mc->objects[--mc->nobjs]);
else
free_page((unsigned long)mc->objects[--mc->nobjs]);
}
+ return freed;
+}
+void kvm_mmu_free_memory_cache(struct kvm_mmu_memory_cache *mc)
+{
+ kvm_mmu_empty_memory_cache(mc);
kvfree(mc->objects);
mc->objects = NULL;
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v2 3/3] KVM: selftests: Add a test to invoke MMU shrinker on KVM VMs
2024-10-04 19:55 [PATCH v2 0/3] KVM: x86/mmu: Repurpose MMU shrinker into page cache shrinker Vipin Sharma
2024-10-04 19:55 ` [PATCH v2 1/3] KVM: x86/mmu: Change KVM mmu shrinker to no-op Vipin Sharma
2024-10-04 19:55 ` [PATCH v2 2/3] KVM: x86/mmu: Use MMU shrinker to shrink KVM MMU memory caches Vipin Sharma
@ 2024-10-04 19:55 ` Vipin Sharma
2024-10-04 20:03 ` Vipin Sharma
2 siblings, 1 reply; 11+ messages in thread
From: Vipin Sharma @ 2024-10-04 19:55 UTC (permalink / raw)
To: seanjc, pbonzini, dmatlack
Cc: zhi.wang.linux, weijiang.yang, mizhang, liangchen.linux, kvm,
linux-kernel, Vipin Sharma
Add a test which invokes KVM MMU shrinker to free caches used by vCPUs in
KVM while a VM is running. Find the KVM MMU shrinker location in
shrinker_debugfs and invoke its scan file to fire the shrinker. Provide
options to specify number of vCPUs, memory accessed by each vCPU, number
of iterations of firing the shrinker scan and delay in milliseconds to
take a pause between two consecutive shrinker calls.
If shrinker_debugfs is not mounted then exit with soft skip.
Suggested-by: David Matlack <dmatlack@google.com>
Signed-off-by: Vipin Sharma <vipinsh@google.com>
---
tools/testing/selftests/kvm/Makefile | 1 +
.../testing/selftests/kvm/include/test_util.h | 5 +
tools/testing/selftests/kvm/lib/test_util.c | 51 ++++
.../selftests/kvm/x86_64/mmu_shrinker_test.c | 269 ++++++++++++++++++
4 files changed, 326 insertions(+)
create mode 100644 tools/testing/selftests/kvm/x86_64/mmu_shrinker_test.c
diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile
index 45cb70c048bb7..a0119e44a37f6 100644
--- a/tools/testing/selftests/kvm/Makefile
+++ b/tools/testing/selftests/kvm/Makefile
@@ -81,6 +81,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/hyperv_svm_test
TEST_GEN_PROGS_x86_64 += x86_64/hyperv_tlb_flush
TEST_GEN_PROGS_x86_64 += x86_64/kvm_clock_test
TEST_GEN_PROGS_x86_64 += x86_64/kvm_pv_test
+TEST_GEN_PROGS_x86_64 += x86_64/mmu_shrinker_test
TEST_GEN_PROGS_x86_64 += x86_64/monitor_mwait_test
TEST_GEN_PROGS_x86_64 += x86_64/nested_exceptions_test
TEST_GEN_PROGS_x86_64 += x86_64/platform_info_test
diff --git a/tools/testing/selftests/kvm/include/test_util.h b/tools/testing/selftests/kvm/include/test_util.h
index 3e473058849ff..7fb530fbc9ce3 100644
--- a/tools/testing/selftests/kvm/include/test_util.h
+++ b/tools/testing/selftests/kvm/include/test_util.h
@@ -218,4 +218,9 @@ char *strdup_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2), n
char *sys_get_cur_clocksource(void);
+int find_debugfs_root(char *debugfs_path, size_t size);
+int find_debugfs_subsystem_path(const char *subsystem,
+ char *debugfs_subsystem_path,
+ size_t max_path_size);
+
#endif /* SELFTEST_KVM_TEST_UTIL_H */
diff --git a/tools/testing/selftests/kvm/lib/test_util.c b/tools/testing/selftests/kvm/lib/test_util.c
index 8ed0b74ae8373..abd2837b5d69d 100644
--- a/tools/testing/selftests/kvm/lib/test_util.c
+++ b/tools/testing/selftests/kvm/lib/test_util.c
@@ -4,6 +4,7 @@
*
* Copyright (C) 2020, Google LLC.
*/
+#include <linux/limits.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
@@ -15,6 +16,8 @@
#include <sys/syscall.h>
#include <linux/mman.h>
#include "linux/kernel.h"
+#include <mntent.h>
+#include <string.h>
#include "test_util.h"
@@ -415,3 +418,51 @@ char *sys_get_cur_clocksource(void)
return clk_name;
}
+
+int find_debugfs_root(char *debugfs_path, size_t size)
+{
+ FILE *mtab = setmntent("/etc/mtab", "r");
+ struct mntent *mntent;
+ int r = -ENOENT;
+
+ if (!mtab)
+ return r;
+
+ while ((mntent = getmntent(mtab))) {
+ if (strcmp("debugfs", mntent->mnt_type))
+ continue;
+
+ if (strlen(mntent->mnt_dir) >= size) {
+ r = -EOVERFLOW;
+ } else {
+ strcpy(debugfs_path, mntent->mnt_dir);
+ r = 0;
+ }
+ break;
+ }
+
+ endmntent(mtab);
+ return r;
+}
+
+int find_debugfs_subsystem_path(const char *subsystem,
+ char *debugfs_subsystem_path,
+ size_t max_path_size)
+{
+ char debugfs_path[PATH_MAX];
+ int ret;
+
+ ret = find_debugfs_root(debugfs_path, PATH_MAX);
+ if (ret)
+ return ret;
+
+ /* Add extra 1 for separator "/". */
+ if (strlen(debugfs_path) + 1 + strlen(subsystem) >= max_path_size)
+ return -EOVERFLOW;
+
+ strcpy(debugfs_subsystem_path, debugfs_path);
+ strcat(debugfs_subsystem_path, "/");
+ strcat(debugfs_subsystem_path, subsystem);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/kvm/x86_64/mmu_shrinker_test.c b/tools/testing/selftests/kvm/x86_64/mmu_shrinker_test.c
new file mode 100644
index 0000000000000..81dd745bcebdb
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86_64/mmu_shrinker_test.c
@@ -0,0 +1,269 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MMU shrinker test
+ *
+ * Test MMU shrinker invocation on VMs. This test needs kernel built with
+ * shrinker debugfs and mounted. Generally that location is
+ * /sys/debug/kernel/shrinker.
+ *
+ * Test will keep adding and removing memslots while guest is accessing memory
+ * so that vCPUs will keep taking fault and filling up caches to process the
+ * page faults. It will also invoke shrinker after memslot changes which will
+ * race with vCPUs to empty caches.
+ *
+ * Copyright 2010 Google LLC
+ *
+ */
+
+#include "guest_modes.h"
+#include "kvm_util.h"
+#include "memstress.h"
+#include "test_util.h"
+#include "ucall_common.h"
+
+#include <dirent.h>
+#include <error.h>
+#include <fnmatch.h>
+#include <kselftest.h>
+#include <linux/limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define SHRINKER_DIR "shrinker"
+#define KVM_MMU_SHRINKER_PREFIX "x86-mmu-*"
+#define SHRINKER_SCAN_FILE "scan"
+#define DUMMY_MEMSLOT_INDEX 10
+#define DEFAULT_MMU_SHRINKER_ITERATIONS 5
+#define DEFAULT_MMU_SHRINKER_VCPUS 2
+#define DEFAULT_MMU_SHRINKER_DELAY_MS 100
+
+struct test_params {
+ uint64_t iterations;
+ uint64_t guest_percpu_mem_size;
+ int delay_ms;
+ int nr_vcpus;
+ char kvm_shrink_scan_file[PATH_MAX];
+};
+
+static int filter(const struct dirent *dir)
+{
+ return !fnmatch(KVM_MMU_SHRINKER_PREFIX, dir->d_name, 0);
+}
+
+static int find_kvm_shrink_scan_path(const char *shrinker_path,
+ char *kvm_shrinker_path, size_t size)
+{
+ struct dirent **dirs = NULL;
+ int ret = 0;
+ size_t len;
+ int n;
+
+ n = scandir(shrinker_path, &dirs, filter, NULL);
+ if (n == -1) {
+ return -errno;
+ } else if (n != 1) {
+ pr_info("Expected one x86-mmu shrinker but found %d\n", n);
+ ret = -ENOTSUP;
+ goto out;
+ }
+
+ len = strnlen(shrinker_path, PATH_MAX) +
+ 1 + /* For path separator '/' */
+ strnlen(dirs[0]->d_name, PATH_MAX) +
+ 1 + /* For path separator '/' */
+ strnlen(SHRINKER_SCAN_FILE, PATH_MAX);
+
+ if (len >= PATH_MAX) {
+ ret = -EOVERFLOW;
+ goto out;
+ }
+
+ strcpy(kvm_shrinker_path, shrinker_path);
+ strcat(kvm_shrinker_path, "/");
+ strcat(kvm_shrinker_path, dirs[0]->d_name);
+ strcat(kvm_shrinker_path, "/");
+ strcat(kvm_shrinker_path, SHRINKER_SCAN_FILE);
+
+out:
+ while (n > 0)
+ free(dirs[n--]);
+ free(dirs);
+ return ret;
+}
+
+static void find_and_validate_kvm_shrink_scan_file(char *kvm_mmu_shrink_scan_file, size_t size)
+{
+ char shrinker_path[PATH_MAX];
+ int ret;
+
+ ret = find_debugfs_subsystem_path(SHRINKER_DIR, shrinker_path, PATH_MAX);
+ if (ret == -ENOENT) {
+ pr_info("Cannot find debugfs, error (%d - %s). Skipping the test.\n",
+ -ret, strerror(-ret));
+ exit(KSFT_SKIP);
+ } else if (ret) {
+ exit(-ret);
+ }
+
+ ret = find_kvm_shrink_scan_path(shrinker_path, kvm_mmu_shrink_scan_file, size);
+ if (ret == -ENOENT) {
+ pr_info("Cannot find kvm shrinker debugfs path, error (%d - %s). Skipping the test.\n",
+ -ret, strerror(-ret));
+ exit(KSFT_SKIP);
+ } else if (ret) {
+ exit(-ret);
+ }
+
+ if (access(kvm_mmu_shrink_scan_file, W_OK))
+ exit(errno);
+
+ pr_info("Got KVM MMU shrink scan file at: %s\n",
+ kvm_mmu_shrink_scan_file);
+}
+
+static int invoke_kvm_mmu_shrinker_scan(struct kvm_vm *vm,
+ const char *kvm_shrink_scan_file,
+ uint64_t iterations, int delay_ms)
+{
+ uint64_t pages = 1;
+ uint64_t gpa;
+ FILE *shrinker_scan_fp;
+ struct timespec ts;
+ int i = 1;
+
+ ts.tv_sec = delay_ms / 1000;
+ ts.tv_nsec = (delay_ms - (ts.tv_sec * 1000)) * 1000000;
+
+ gpa = memstress_args.gpa - pages * vm->page_size;
+
+ shrinker_scan_fp = fopen(kvm_shrink_scan_file, "w");
+ if (!shrinker_scan_fp) {
+ pr_info("Not able to open KVM shrink scan file for writing\n");
+ return -errno;
+ }
+
+ while (iterations--) {
+ /* Adding and deleting memslots rebuilds the page table */
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, gpa,
+ DUMMY_MEMSLOT_INDEX, pages, 0);
+ vm_mem_region_delete(vm, DUMMY_MEMSLOT_INDEX);
+
+ pr_info("Iteration %d: Invoking shrinker.\n", i++);
+ fprintf(shrinker_scan_fp, "0 0 1000\n");
+ rewind(shrinker_scan_fp);
+
+ nanosleep(&ts, NULL);
+ }
+
+ fclose(shrinker_scan_fp);
+ return 0;
+}
+
+static void vcpu_worker(struct memstress_vcpu_args *vcpu_args)
+{
+ struct kvm_vcpu *vcpu = vcpu_args->vcpu;
+ struct kvm_run *run;
+ int ret;
+
+ run = vcpu->run;
+
+ /* Let the guest access its memory until a stop signal is received */
+ while (!READ_ONCE(memstress_args.stop_vcpus)) {
+ ret = _vcpu_run(vcpu);
+ TEST_ASSERT(ret == 0, "vcpu_run failed: %d", ret);
+
+ if (get_ucall(vcpu, NULL) == UCALL_SYNC)
+ continue;
+
+ TEST_ASSERT(false,
+ "Invalid guest sync status: exit_reason=%s\n",
+ exit_reason_str(run->exit_reason));
+ }
+}
+
+static void run_test(struct test_params *p)
+{
+ struct kvm_vm *vm;
+ int nr_vcpus = p->nr_vcpus;
+
+ pr_info("Creating the VM.\n");
+ vm = memstress_create_vm(VM_MODE_DEFAULT, p->nr_vcpus,
+ p->guest_percpu_mem_size,
+ /*slots =*/1, DEFAULT_VM_MEM_SRC,
+ /*partition_vcpu_memory_access=*/true);
+
+ memstress_start_vcpu_threads(p->nr_vcpus, vcpu_worker);
+
+ pr_info("Starting the test.\n");
+ invoke_kvm_mmu_shrinker_scan(vm, p->kvm_shrink_scan_file, p->iterations,
+ p->delay_ms);
+
+ pr_info("Test completed.\nStopping the VM.\n");
+ memstress_join_vcpu_threads(nr_vcpus);
+ memstress_destroy_vm(vm);
+}
+
+static void help(char *name)
+{
+ puts("");
+ printf("usage: %s [-b memory] [-d delay_usec] [-i iterations] [-h]\n"
+ " [-v vcpus] \n", name);
+ printf(" -b: specify the size of the memory region which should be\n"
+ " accessed by each vCPU. e.g. 10M or 3G. (Default: 1G)\n");
+ printf(" -d: add a delay between each iterations of firing MMU shrinker\n"
+ " scan in milliseconds. (Default: %dms).\n",
+ DEFAULT_MMU_SHRINKER_DELAY_MS);
+ printf(" -i: specify the number of iterations of firing MMU shrinker.\n"
+ " scan. (Default: %d)\n",
+ DEFAULT_MMU_SHRINKER_ITERATIONS);
+ printf(" -v: specify the number of vCPUs to run. (Default: %d)\n",
+ DEFAULT_MMU_SHRINKER_VCPUS);
+ printf(" -h: Print the help message.\n");
+ puts("");
+}
+
+int main(int argc, char *argv[])
+{
+ int max_vcpus = kvm_check_cap(KVM_CAP_MAX_VCPUS);
+ struct test_params p = {
+ .iterations = DEFAULT_MMU_SHRINKER_ITERATIONS,
+ .guest_percpu_mem_size = DEFAULT_PER_VCPU_MEM_SIZE,
+ .nr_vcpus = DEFAULT_MMU_SHRINKER_VCPUS,
+ .delay_ms = DEFAULT_MMU_SHRINKER_DELAY_MS,
+ };
+ int opt;
+
+ while ((opt = getopt(argc, argv, "b:d:i:v:")) != -1) {
+ switch (opt) {
+ case 'b':
+ p.guest_percpu_mem_size = parse_size(optarg);
+ break;
+ case 'd':
+ p.delay_ms = atoi_non_negative("Time gap between two MMU shrinker invocations in milliseconds",
+ optarg);
+ break;
+ case 'i':
+ p.iterations = atoi_positive("Number of iterations", optarg);
+ break;
+ case 'v':
+ p.nr_vcpus = atoi_positive("Number of vCPUs", optarg);
+ TEST_ASSERT(p.nr_vcpus <= max_vcpus,
+ "Invalid number of vcpus, must be between 1 and %d",
+ max_vcpus);
+ break;
+ case 'h':
+ help(argv[0]);
+ exit(EXIT_SUCCESS);
+ default:
+ help(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ find_and_validate_kvm_shrink_scan_file(p.kvm_shrink_scan_file, PATH_MAX);
+ run_test(&p);
+ return 0;
+}
--
2.47.0.rc0.187.ge670bccf7e-goog
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v2 3/3] KVM: selftests: Add a test to invoke MMU shrinker on KVM VMs
2024-10-04 19:55 ` [PATCH v2 3/3] KVM: selftests: Add a test to invoke MMU shrinker on KVM VMs Vipin Sharma
@ 2024-10-04 20:03 ` Vipin Sharma
0 siblings, 0 replies; 11+ messages in thread
From: Vipin Sharma @ 2024-10-04 20:03 UTC (permalink / raw)
To: seanjc, pbonzini, dmatlack
Cc: zhi.wang.linux, weijiang.yang, mizhang, liangchen.linux, kvm,
linux-kernel
On Fri, Oct 4, 2024 at 12:55 PM Vipin Sharma <vipinsh@google.com> wrote:
> diff --git a/tools/testing/selftests/kvm/x86_64/mmu_shrinker_test.c b/tools/testing/selftests/kvm/x86_64/mmu_shrinker_test.c
> new file mode 100644
> index 0000000000000..81dd745bcebdb
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/x86_64/mmu_shrinker_test.c
> @@ -0,0 +1,269 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * MMU shrinker test
> + *
> + * Test MMU shrinker invocation on VMs. This test needs kernel built with
> + * shrinker debugfs and mounted. Generally that location is
> + * /sys/debug/kernel/shrinker.
> + *
> + * Test will keep adding and removing memslots while guest is accessing memory
> + * so that vCPUs will keep taking fault and filling up caches to process the
> + * page faults. It will also invoke shrinker after memslot changes which will
> + * race with vCPUs to empty caches.
> + *
> + * Copyright 2010 Google LLC
Oops! This should have been 2024. I clearly live in the past.
Should I send v3 or can it be updated when this is merged?
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 2/3] KVM: x86/mmu: Use MMU shrinker to shrink KVM MMU memory caches
2024-10-04 19:55 ` [PATCH v2 2/3] KVM: x86/mmu: Use MMU shrinker to shrink KVM MMU memory caches Vipin Sharma
@ 2024-10-04 20:04 ` Vipin Sharma
2024-10-24 23:25 ` Sean Christopherson
1 sibling, 0 replies; 11+ messages in thread
From: Vipin Sharma @ 2024-10-04 20:04 UTC (permalink / raw)
To: seanjc, pbonzini, dmatlack
Cc: zhi.wang.linux, weijiang.yang, mizhang, liangchen.linux, kvm,
linux-kernel
On Fri, Oct 4, 2024 at 12:55 PM Vipin Sharma <vipinsh@google.com> wrote:
>
> Use MMU shrinker to iterate through all the vCPUs of all the VMs and
> free pages allocated in MMU memory caches. Protect cache allocation in
> page fault and MMU load path from MMU shrinker by using a per vCPU
> mutex. In MMU shrinker, move the iterated VM to the end of the VMs list
> so that the pain of emptying cache spread among other VMs too.
>
> The specific caches to empty are mmu_shadow_page_cache and
> mmu_shadowed_info_cache as these caches store whole pages. Emptying them
> will give more impact to shrinker compared to other caches like
> mmu_pte_list_desc_cache{} and mmu_page_header_cache{}
>
> Holding per vCPU mutex lock ensures that a vCPU doesn't get surprised
> by finding its cache emptied after filling them up for page table
> allocations during page fault handling and MMU load operation. Per vCPU
> mutex also makes sure there is only race between MMU shrinker and all
> other vCPUs. This should result in very less contention.
>
> Signed-off-by: Vipin Sharma <vipinsh@google.com>
I also meant to add
Suggested-by: Sean Christopherson <seanjc@google.com>
Suggested-by: David Matlack <dmatlack@google.com>
I can send v3 or please take it from v2.
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 2/3] KVM: x86/mmu: Use MMU shrinker to shrink KVM MMU memory caches
2024-10-04 19:55 ` [PATCH v2 2/3] KVM: x86/mmu: Use MMU shrinker to shrink KVM MMU memory caches Vipin Sharma
2024-10-04 20:04 ` Vipin Sharma
@ 2024-10-24 23:25 ` Sean Christopherson
2024-10-25 17:36 ` Vipin Sharma
1 sibling, 1 reply; 11+ messages in thread
From: Sean Christopherson @ 2024-10-24 23:25 UTC (permalink / raw)
To: Vipin Sharma
Cc: pbonzini, dmatlack, zhi.wang.linux, weijiang.yang, mizhang,
liangchen.linux, kvm, linux-kernel
On Fri, Oct 04, 2024, Vipin Sharma wrote:
> Use MMU shrinker to iterate through all the vCPUs of all the VMs and
> free pages allocated in MMU memory caches. Protect cache allocation in
> page fault and MMU load path from MMU shrinker by using a per vCPU
> mutex. In MMU shrinker, move the iterated VM to the end of the VMs list
> so that the pain of emptying cache spread among other VMs too.
>
> The specific caches to empty are mmu_shadow_page_cache and
> mmu_shadowed_info_cache as these caches store whole pages. Emptying them
> will give more impact to shrinker compared to other caches like
> mmu_pte_list_desc_cache{} and mmu_page_header_cache{}
>
> Holding per vCPU mutex lock ensures that a vCPU doesn't get surprised
> by finding its cache emptied after filling them up for page table
> allocations during page fault handling and MMU load operation. Per vCPU
> mutex also makes sure there is only race between MMU shrinker and all
> other vCPUs. This should result in very less contention.
>
> Signed-off-by: Vipin Sharma <vipinsh@google.com>
> ---
...
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> index 213e46b55dda2..8e2935347615d 100644
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
> @@ -4524,29 +4524,33 @@ static int direct_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault
> if (r != RET_PF_INVALID)
> return r;
>
> + mutex_lock(&vcpu->arch.mmu_memory_cache_lock);
> r = mmu_topup_memory_caches(vcpu, false);
> if (r)
> - return r;
> + goto out_mmu_memory_cache_unlock;
>
> r = kvm_faultin_pfn(vcpu, fault, ACC_ALL);
> if (r != RET_PF_CONTINUE)
> - return r;
> + goto out_mmu_memory_cache_unlock;
>
> r = RET_PF_RETRY;
> write_lock(&vcpu->kvm->mmu_lock);
>
> if (is_page_fault_stale(vcpu, fault))
> - goto out_unlock;
> + goto out_mmu_unlock;
>
> r = make_mmu_pages_available(vcpu);
> if (r)
> - goto out_unlock;
> + goto out_mmu_unlock;
>
> r = direct_map(vcpu, fault);
>
> -out_unlock:
> +out_mmu_unlock:
> write_unlock(&vcpu->kvm->mmu_lock);
> kvm_release_pfn_clean(fault->pfn);
> +out_mmu_memory_cache_unlock:
> + mutex_unlock(&vcpu->arch.mmu_memory_cache_lock);
I've been thinking about this patch on and off for the past few weeks, and every
time I come back to it I can't shake the feeling that we came up with a clever
solution for a problem that doesn't exist. I can't recall a single complaint
about KVM consuming an unreasonable amount of memory for page tables. In fact,
the only time I can think of where the code in question caused problems was when
I unintentionally inverted the iterator and zapped the newest SPs instead of the
oldest SPs.
So, I'm leaning more and more toward simply removing the shrinker integration.
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 2/3] KVM: x86/mmu: Use MMU shrinker to shrink KVM MMU memory caches
2024-10-24 23:25 ` Sean Christopherson
@ 2024-10-25 17:36 ` Vipin Sharma
2024-10-28 20:37 ` David Matlack
0 siblings, 1 reply; 11+ messages in thread
From: Vipin Sharma @ 2024-10-25 17:36 UTC (permalink / raw)
To: Sean Christopherson
Cc: pbonzini, dmatlack, zhi.wang.linux, weijiang.yang, mizhang,
liangchen.linux, kvm, linux-kernel
On Thu, Oct 24, 2024 at 4:25 PM Sean Christopherson <seanjc@google.com> wrote:
>
> On Fri, Oct 04, 2024, Vipin Sharma wrote:
> > +out_mmu_memory_cache_unlock:
> > + mutex_unlock(&vcpu->arch.mmu_memory_cache_lock);
>
> I've been thinking about this patch on and off for the past few weeks, and every
> time I come back to it I can't shake the feeling that we came up with a clever
> solution for a problem that doesn't exist. I can't recall a single complaint
> about KVM consuming an unreasonable amount of memory for page tables. In fact,
> the only time I can think of where the code in question caused problems was when
> I unintentionally inverted the iterator and zapped the newest SPs instead of the
> oldest SPs.
>
> So, I'm leaning more and more toward simply removing the shrinker integration.
One thing we can agree on is that we don't need MMU shrinker in its
current form because it is removing pages which are very well being
used by VM instead of shrinking its cache.
Regarding the current series, the biggest VM in GCE we can have 416
vCPUs, considering each thread can have 40 pages in its cache, total
cost gonna be around 65 MiB, doesn't seem much to me considering these
VMs have memory in TiB. Since caches in VMs are not unbounded, I think
it is fine to not have a MMU shrinker as its impact is miniscule in
KVM.
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 2/3] KVM: x86/mmu: Use MMU shrinker to shrink KVM MMU memory caches
2024-10-25 17:36 ` Vipin Sharma
@ 2024-10-28 20:37 ` David Matlack
2024-10-28 20:49 ` Sean Christopherson
0 siblings, 1 reply; 11+ messages in thread
From: David Matlack @ 2024-10-28 20:37 UTC (permalink / raw)
To: Vipin Sharma
Cc: Sean Christopherson, pbonzini, zhi.wang.linux, weijiang.yang,
mizhang, liangchen.linux, kvm, linux-kernel
On Fri, Oct 25, 2024 at 10:37 AM Vipin Sharma <vipinsh@google.com> wrote:
>
> On Thu, Oct 24, 2024 at 4:25 PM Sean Christopherson <seanjc@google.com> wrote:
> >
> > On Fri, Oct 04, 2024, Vipin Sharma wrote:
> > > +out_mmu_memory_cache_unlock:
> > > + mutex_unlock(&vcpu->arch.mmu_memory_cache_lock);
> >
> > I've been thinking about this patch on and off for the past few weeks, and every
> > time I come back to it I can't shake the feeling that we came up with a clever
> > solution for a problem that doesn't exist. I can't recall a single complaint
> > about KVM consuming an unreasonable amount of memory for page tables. In fact,
> > the only time I can think of where the code in question caused problems was when
> > I unintentionally inverted the iterator and zapped the newest SPs instead of the
> > oldest SPs.
> >
> > So, I'm leaning more and more toward simply removing the shrinker integration.
>
> One thing we can agree on is that we don't need MMU shrinker in its
> current form because it is removing pages which are very well being
> used by VM instead of shrinking its cache.
>
> Regarding the current series, the biggest VM in GCE we can have 416
> vCPUs, considering each thread can have 40 pages in its cache, total
> cost gonna be around 65 MiB, doesn't seem much to me considering these
> VMs have memory in TiB. Since caches in VMs are not unbounded, I think
> it is fine to not have a MMU shrinker as its impact is miniscule in
> KVM.
I have no objection to removing the shrinker entirely.
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 2/3] KVM: x86/mmu: Use MMU shrinker to shrink KVM MMU memory caches
2024-10-28 20:37 ` David Matlack
@ 2024-10-28 20:49 ` Sean Christopherson
2024-10-28 22:32 ` Vipin Sharma
0 siblings, 1 reply; 11+ messages in thread
From: Sean Christopherson @ 2024-10-28 20:49 UTC (permalink / raw)
To: David Matlack
Cc: Vipin Sharma, pbonzini, zhi.wang.linux, weijiang.yang, mizhang,
liangchen.linux, kvm, linux-kernel
On Mon, Oct 28, 2024, David Matlack wrote:
> On Fri, Oct 25, 2024 at 10:37 AM Vipin Sharma <vipinsh@google.com> wrote:
> >
> > On Thu, Oct 24, 2024 at 4:25 PM Sean Christopherson <seanjc@google.com> wrote:
> > >
> > > On Fri, Oct 04, 2024, Vipin Sharma wrote:
> > > > +out_mmu_memory_cache_unlock:
> > > > + mutex_unlock(&vcpu->arch.mmu_memory_cache_lock);
> > >
> > > I've been thinking about this patch on and off for the past few weeks, and every
> > > time I come back to it I can't shake the feeling that we came up with a clever
> > > solution for a problem that doesn't exist. I can't recall a single complaint
> > > about KVM consuming an unreasonable amount of memory for page tables. In fact,
> > > the only time I can think of where the code in question caused problems was when
> > > I unintentionally inverted the iterator and zapped the newest SPs instead of the
> > > oldest SPs.
> > >
> > > So, I'm leaning more and more toward simply removing the shrinker integration.
> >
> > One thing we can agree on is that we don't need MMU shrinker in its
> > current form because it is removing pages which are very well being
> > used by VM instead of shrinking its cache.
> >
> > Regarding the current series, the biggest VM in GCE we can have 416
> > vCPUs, considering each thread can have 40 pages in its cache, total
> > cost gonna be around 65 MiB, doesn't seem much to me considering these
> > VMs have memory in TiB. Since caches in VMs are not unbounded, I think
> > it is fine to not have a MMU shrinker as its impact is miniscule in
> > KVM.
>
> I have no objection to removing the shrinker entirely.
Let's do that. In the unlikely scenario someone comes along with a strong use
case for purging the vCPU caches, we can always resurrect this approach.
Vipin, can you send a v3?
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 2/3] KVM: x86/mmu: Use MMU shrinker to shrink KVM MMU memory caches
2024-10-28 20:49 ` Sean Christopherson
@ 2024-10-28 22:32 ` Vipin Sharma
0 siblings, 0 replies; 11+ messages in thread
From: Vipin Sharma @ 2024-10-28 22:32 UTC (permalink / raw)
To: Sean Christopherson
Cc: David Matlack, pbonzini, zhi.wang.linux, weijiang.yang, mizhang,
liangchen.linux, kvm, linux-kernel
On Mon, Oct 28, 2024 at 1:49 PM Sean Christopherson <seanjc@google.com> wrote:
>
> On Mon, Oct 28, 2024, David Matlack wrote:
> > On Fri, Oct 25, 2024 at 10:37 AM Vipin Sharma <vipinsh@google.com> wrote:
> > >
> > > On Thu, Oct 24, 2024 at 4:25 PM Sean Christopherson <seanjc@google.com> wrote:
> > > >
> > > > On Fri, Oct 04, 2024, Vipin Sharma wrote:
> > > > > +out_mmu_memory_cache_unlock:
> > > > > + mutex_unlock(&vcpu->arch.mmu_memory_cache_lock);
> > > >
> > > > I've been thinking about this patch on and off for the past few weeks, and every
> > > > time I come back to it I can't shake the feeling that we came up with a clever
> > > > solution for a problem that doesn't exist. I can't recall a single complaint
> > > > about KVM consuming an unreasonable amount of memory for page tables. In fact,
> > > > the only time I can think of where the code in question caused problems was when
> > > > I unintentionally inverted the iterator and zapped the newest SPs instead of the
> > > > oldest SPs.
> > > >
> > > > So, I'm leaning more and more toward simply removing the shrinker integration.
> > >
> > > One thing we can agree on is that we don't need MMU shrinker in its
> > > current form because it is removing pages which are very well being
> > > used by VM instead of shrinking its cache.
> > >
> > > Regarding the current series, the biggest VM in GCE we can have 416
> > > vCPUs, considering each thread can have 40 pages in its cache, total
> > > cost gonna be around 65 MiB, doesn't seem much to me considering these
> > > VMs have memory in TiB. Since caches in VMs are not unbounded, I think
> > > it is fine to not have a MMU shrinker as its impact is miniscule in
> > > KVM.
> >
> > I have no objection to removing the shrinker entirely.
>
> Let's do that. In the unlikely scenario someone comes along with a strong use
> case for purging the vCPU caches, we can always resurrect this approach.
>
> Vipin, can you send a v3?
Okay, I will send a v3.
Thanks
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2024-10-28 22:33 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-10-04 19:55 [PATCH v2 0/3] KVM: x86/mmu: Repurpose MMU shrinker into page cache shrinker Vipin Sharma
2024-10-04 19:55 ` [PATCH v2 1/3] KVM: x86/mmu: Change KVM mmu shrinker to no-op Vipin Sharma
2024-10-04 19:55 ` [PATCH v2 2/3] KVM: x86/mmu: Use MMU shrinker to shrink KVM MMU memory caches Vipin Sharma
2024-10-04 20:04 ` Vipin Sharma
2024-10-24 23:25 ` Sean Christopherson
2024-10-25 17:36 ` Vipin Sharma
2024-10-28 20:37 ` David Matlack
2024-10-28 20:49 ` Sean Christopherson
2024-10-28 22:32 ` Vipin Sharma
2024-10-04 19:55 ` [PATCH v2 3/3] KVM: selftests: Add a test to invoke MMU shrinker on KVM VMs Vipin Sharma
2024-10-04 20:03 ` Vipin Sharma
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox