From: Sean Christopherson <seanjc@google.com>
To: sashiko-reviews@lists.linux.dev
Cc: "Woodhouse, David" <dwmw@amazon.co.uk>, kvm@vger.kernel.org
Subject: Re: [PATCH v3 3/7] KVM: pfncache: Use RCU for readers instead of a rwlock
Date: Thu, 6 Aug 2026 09:53:31 -0700 [thread overview]
Message-ID: <anS8C4Cxqy6ehCo3@google.com> (raw)
In-Reply-To: <20260805203608.D3CA61F000E9@smtp.kernel.org>
On Wed, Aug 05, 2026, sashiko-bot@kernel.org wrote:
> Replace the per-cache rwlock with RCU for the read side.
I don't hate the idea, but I am very against using RCU. Unless it's "impossible",
e.g. because synchronize_srcu() allocates memory and breaks OOM kill, I would
strongly prefer to use SRCU, probably with a dedicated kvm->gpc_srcu, so that
synchronization doesn't need to wait on all CPUs in the system. The tail latencies
for synchronize_rcu() are horrendous, especially for many-CPU systems. If it
were only mmu_notifiers that got hit, it miiiight be acceptable, but since this
will affect vCPU tasks in the refresh() path as well, normal RCU is pretty much
a non-starter.
Even SRCU could be problematic: if synchronize_srcu_expedited() is forced to wait,
the wait time can easily get to 20+ milliseconds, which again is a non-starter for
things like steal-time updates and nVMX pages. Simply using a per-GPC SRCU would
be gross, as gfn_to_pfn_cache_invalidate_start() would become absurdly complex in
order to juggle gpc_lock with synchronize_srcu_expedited().
A somewhat crazy idea would be to have a per-VM gpc_srcu, *and* a per-GPC srcu.
Readers would take both, refresh() would sync gpc->srcu, and invalidation would
sync kvm->gpc_srcu. That way, refresh() wouldn't need to wait on concurrent
readers of *other* GPCs.
Actually, a better idea: use kvm->gpc_srcu to synchronize invalidations and
refresh() for GPCs that aren't tightly coupled to a vCPU, but for GPCs that are
*only* accessed by a single loaded vCPU, protect readers and refresh() with
vcpu->mutex. That way, single-vCPU GPCs wouldn't need to synchronize() on refresh(),
because by definition there can't be concurrent readers with refresh().
That would basically punt on optimizing most of the Xen GPCs, but that's probably
ok? Because the hot path GPCs, e.g. runstate_cache{,2}, are generally associated
1:1 with a vCPU, i.e. can avoid synchronizing on SRCU. The one GPC that I see
as being problematic is vcpu_info_cache, because it's accesses cross-vCPU and so
the owning vCPU would need to synchronize() on refresh(). But if you're ok with
potentially high tail latencies if the vcpu_info_cache page is migrated or
reclaimed, then I doubt anyone else will complain.
> > --- a/virt/kvm/pfncache.c
> > +++ b/virt/kvm/pfncache.c
> > @@ -26,35 +26,49 @@ void gfn_to_pfn_cache_invalidate_start(struct kvm *kvm, unsigned long start,
> > unsigned long end)
> > {
> > struct gfn_to_pfn_cache *gpc;
> > + bool cleared = false;
> >
> > spin_lock(&kvm->gpc_lock);
> > list_for_each_entry(gpc, &kvm->gpc_list, list) {
> > - read_lock_irq(&gpc->lock);
> > -
> > - /* Only a single page so no need to care about length */
> > - if (gpc->valid && !is_error_noslot_pfn(gpc->pfn) &&
> > + if (smp_load_acquire(&gpc->valid) &&
> > gpc->uhva >= start && gpc->uhva < end) {
> > - read_unlock_irq(&gpc->lock);
> > -
> > - /*
> > - * There is a small window here where the cache could
> > - * be modified, and invalidation would no longer be
> > - * necessary. Hence check again whether invalidation
> > - * is still necessary once the write lock has been
> > - * acquired.
> > - */
> > -
> > - write_lock_irq(&gpc->lock);
> > - if (gpc->valid && !is_error_noslot_pfn(gpc->pfn) &&
> > - gpc->uhva >= start && gpc->uhva < end)
> > - gpc->valid = false;
> > - write_unlock_irq(&gpc->lock);
> > - continue;
> > + WRITE_ONCE(gpc->valid, false);
> > + cleared = true;
> > }
> > -
> > - read_unlock_irq(&gpc->lock);
> > }
> > spin_unlock(&kvm->gpc_lock);
> > +
> > + /*
> > + * Readers may still be using the old mapping, having sampled
> > + * gpc->valid before it was cleared. Wait for them all to drain
> > + * before the caller proceeds to zap the page tables; like a TLB
> > + * shootdown, this guarantees no access via the stale mapping
> > + * once the invalidation completes.
> > + *
> > + * This wait is safe even on unblockable ranges (the OOM reaper):
> > + * the constraint there is not "no sleeping" but "no blocking on
> > + * anything which may itself depend on memory allocation to make
> > + * progress" (see the reasoning in commit 312364f3534c and its
> > + * discussion). An RCU grace period has no such dependency: GPC
> > + * readers never allocate, never take mmap_lock, and never sleep.
> > + */
> > + if (cleared)
> > + synchronize_rcu();
> > }
>
> [Severity: Critical]
> Could this conditional wait bypass the grace period and lead to a regression
> with physical pages being freed too early?
>
> If a concurrent mutator like __kvm_gpc_refresh transitions gpc->valid to
> false and is currently blocking on its own synchronize_rcu wait, the MMU
> notifier will see valid as false, skip the wait since cleared is false, and
> return immediately.
>
> This would allow the host memory subsystem to free the page while the
> original mutator is still waiting for active RCU readers to finish.
Ya, pretty sure, Sashiko is correct. The mmu_notifier rules require that all
mappings are removed before .invalidate_range_start() completes. Seems like the
easiest solution is to key off of gpc->active, not gpc->valid.
> [ ... ]
> > @@ -74,6 +88,22 @@ bool kvm_gpc_check(struct gfn_to_pfn_cache *gpc, unsigned long len)
> > {
> > struct kvm_memslots *slots = kvm_memslots(gpc->kvm);
> >
> > + RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
> > + "kvm_gpc_check() without RCU read lock");
> > +
> > + /*
> > + * Check valid *first*. The acquire pairs with the release-publish
> > + * in hva_to_pfn_retry(), so every field read below — and any use
> > + * of gpc->khva by the caller — is guaranteed to be from the
> > + * published generation, not a stale value reordered from before
> > + * the publish. The fields are then stable for the remainder of
> > + * the RCU read-side critical section, because every mutator
> > + * clears valid and waits a full grace period before changing
> > + * anything.
> > + */
> > + if (!smp_load_acquire(&gpc->valid))
> > + return false;
> > +
> > if (!gpc->active)
> > return false;
>
> [Severity: Medium]
> Should the read of gpc->generation use READ_ONCE?
+1, the {WRITE,READ}_ONCE() usage looks to be very inconsistent.
next prev parent reply other threads:[~2026-08-06 16:53 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-05 19:55 [PATCH v3 0/7] KVM: x86/xen: Fix Xen/GPC/PREEMPT_RT issues with rwlock_t Woodhouse, David
2026-08-05 19:55 ` [PATCH v3 1/7] KVM: pfncache: use a dedicated invalidation sequence for cache refresh Woodhouse, David
2026-08-05 19:55 ` [PATCH v3 2/7] mm/mmu_notifier: Remove non_block_start/end() from notifier invocation Woodhouse, David
2026-08-05 19:55 ` [PATCH v3 3/7] KVM: pfncache: Use RCU for readers instead of a rwlock Woodhouse, David
2026-08-05 20:36 ` sashiko-bot
2026-08-06 16:53 ` Sean Christopherson [this message]
2026-08-06 17:58 ` Woodhouse, David
2026-08-06 18:11 ` Sean Christopherson
2026-08-06 18:23 ` Woodhouse, David
2026-08-06 20:38 ` David Woodhouse
2026-08-06 21:52 ` Paul E. McKenney
2026-08-06 22:02 ` David Woodhouse
2026-08-05 19:55 ` [PATCH v3 4/7] KVM: x86/xen: Extract delivery of event to vCPU into a separate helper Woodhouse, David
2026-08-05 20:47 ` sashiko-bot
2026-08-05 22:35 ` David Woodhouse
2026-08-06 10:00 ` David Woodhouse
2026-08-06 14:32 ` David Woodhouse
2026-08-05 19:56 ` [PATCH v3 5/7] KVM: x86/xen: Explicitly tag "shared info" page as never being dirty tracked Woodhouse, David
2026-08-05 19:56 ` [PATCH v3 6/7] KVM: x86/xen: Don't dirty track "vCPU info" page Woodhouse, David
2026-08-05 19:56 ` [PATCH v3 7/7] KVM: x86: Use gfn_to_pfn_cache for steal time / preempted status Woodhouse, David
2026-08-05 21:15 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=anS8C4Cxqy6ehCo3@google.com \
--to=seanjc@google.com \
--cc=dwmw@amazon.co.uk \
--cc=kvm@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox