mbox series

[v5,00/14] KVM: arm64: Parallel stage-2 fault handling

Message ID 20221107215644.1895162-1-oliver.upton@linux.dev (mailing list archive)
Headers show
Series KVM: arm64: Parallel stage-2 fault handling | expand

Message

Oliver Upton Nov. 7, 2022, 9:56 p.m. UTC
Presently KVM only takes a read lock for stage 2 faults if it believes
the fault can be fixed by relaxing permissions on a PTE (write unprotect
for dirty logging). Otherwise, stage 2 faults grab the write lock, which
predictably can pile up all the vCPUs in a sufficiently large VM.

Like the TDP MMU for x86, this series loosens the locking around
manipulations of the stage 2 page tables to allow parallel faults. RCU
and atomics are exploited to safely build/destroy the stage 2 page
tables in light of multiple software observers.

Patches 1-4 clean up the context associated with a page table walk / PTE
visit. This is helpful for:
 - Extending the context passed through for a visit
 - Building page table walkers that operate outside of a kvm_pgtable
   context (e.g. RCU callback)

Patches 5-7 clean up the stage-2 map walkers by calling a helper to tear
down removed tables. There is a small improvement here in that a broken
PTE is replaced more quickly, as page table teardown happens afterwards.

Patch 8 sprinkles in RCU to the page table walkers, punting the
teardown of removed tables to an RCU callback.

Patches 9-13 implement the meat of this series, extending the
'break-before-make' sequence with atomics to realize locking on PTEs.
Effectively a cmpxchg() is used to 'break' a PTE, thereby serializing
changes to a given PTE.

Finally, patch 14 flips the switch on all the new code and starts
grabbing the read side of the MMU lock for stage 2 faults.

Applies to 6.1-rc3. Tested with KVM selftests, kvm-unit-tests, and live
migrating a 24 vCPU, 96GB VM that was running a Debian install.
Confirmed all stage-2 table memory was freed by checking the
SecPageTables stat in meminfo.

Branch available at:

  https://github.com/oupton/linux kvm-arm64/parallel_mmu

benchmarked with dirty_log_perf_test, scaling from 1 to 48 vCPUs with
4GB of memory per vCPU backed by THP.

  ./dirty_log_perf_test -s anonymous_thp -m 2 -b 4G -v ${NR_VCPUS}

Time to dirty memory:

        +-------+----------+-------------------+
        | vCPUs | 6.1-rc3  | 6.1-rc3 + series  |
        +-------+----------+-------------------+
        |     1 | 0.87s    | 0.93s             |
        |     2 | 1.11s    | 1.16s             |
        |     4 | 2.39s    | 1.27s             |
        |     8 | 5.01s    | 1.39s             |
        |    16 | 8.89s    | 2.07s             |
        |    32 | 19.90s   | 4.45s             |
        |    48 | 32.10s   | 6.23s             |
        +-------+----------+-------------------+

It is also worth mentioning that the time to populate memory has
improved:

        +-------+----------+-------------------+
        | vCPUs | 6.1-rc3  | 6.1-rc3 + series  |
        +-------+----------+-------------------+
        |     1 | 0.21s    | 0.17s             |
        |     2 | 0.26s    | 0.23s             |
        |     4 | 0.39s    | 0.31s             |
        |     8 | 0.68s    | 0.39s             |
        |    16 | 1.26s    | 0.53s             |
        |    32 | 2.51s    | 1.04s             |
        |    48 | 3.94s    | 1.55s             |
        +-------+----------+-------------------+

v4 -> v5:
 - Fix an obvious leak of table memory (Ricardo)

v3 -> v4:
 - Fix some type conversion misses caught by sparse (test robot)
 - Squash RCU locking and RCU callback patches together into one (Sean)
 - Commit message nits (Sean)
 - Take a pointer to kvm_s2_mmu in stage2_try_break_pte(), in
   anticipation of eager page splitting (Ricardo)

v3: https://lore.kernel.org/kvmarm/20221027221752.1683510-1-oliver.upton@linux.dev/
v4: https://lore.kernel.org/kvmarm/20221103091140.1040433-1-oliver.upton@linux.dev/

Oliver Upton (14):
  KVM: arm64: Combine visitor arguments into a context structure
  KVM: arm64: Stash observed pte value in visitor context
  KVM: arm64: Pass mm_ops through the visitor context
  KVM: arm64: Don't pass kvm_pgtable through kvm_pgtable_walk_data
  KVM: arm64: Add a helper to tear down unlinked stage-2 subtrees
  KVM: arm64: Use an opaque type for pteps
  KVM: arm64: Tear down unlinked stage-2 subtree after break-before-make
  KVM: arm64: Protect stage-2 traversal with RCU
  KVM: arm64: Atomically update stage 2 leaf attributes in parallel
    walks
  KVM: arm64: Split init and set for table PTE
  KVM: arm64: Make block->table PTE changes parallel-aware
  KVM: arm64: Make leaf->leaf PTE changes parallel-aware
  KVM: arm64: Make table->block changes parallel-aware
  KVM: arm64: Handle stage-2 faults in parallel

 arch/arm64/include/asm/kvm_pgtable.h  |  92 +++-
 arch/arm64/kvm/hyp/nvhe/mem_protect.c |  21 +-
 arch/arm64/kvm/hyp/nvhe/setup.c       |  22 +-
 arch/arm64/kvm/hyp/pgtable.c          | 628 ++++++++++++++------------
 arch/arm64/kvm/mmu.c                  |  53 ++-
 5 files changed, 466 insertions(+), 350 deletions(-)


base-commit: 30a0b95b1335e12efef89dd78518ed3e4a71a763

Comments

Marc Zyngier Nov. 11, 2022, 3:47 p.m. UTC | #1
On Mon, 7 Nov 2022 21:56:30 +0000, Oliver Upton wrote:
> Presently KVM only takes a read lock for stage 2 faults if it believes
> the fault can be fixed by relaxing permissions on a PTE (write unprotect
> for dirty logging). Otherwise, stage 2 faults grab the write lock, which
> predictably can pile up all the vCPUs in a sufficiently large VM.
> 
> Like the TDP MMU for x86, this series loosens the locking around
> manipulations of the stage 2 page tables to allow parallel faults. RCU
> and atomics are exploited to safely build/destroy the stage 2 page
> tables in light of multiple software observers.
> 
> [...]

I've gone over this for quite a while, and while I'm still sh*t
scared about it, I've decided to let it simmer in -next for a bit.

If anything goes wrong or that someone spots something ugly,
it will be easy to simply drop the branch. For simple fixes, they
can go on top.

[01/14] KVM: arm64: Combine visitor arguments into a context structure
        commit: dfc7a7769ab7f2a2f629c673717ef1fa7b63aa42
[02/14] KVM: arm64: Stash observed pte value in visitor context
        commit: 83844a2317ecad935f6735abd854e4bf3f757040
[03/14] KVM: arm64: Pass mm_ops through the visitor context
        commit: 2a611c7f87f26cca405da63a57f06d0e4dc14240
[04/14] KVM: arm64: Don't pass kvm_pgtable through kvm_pgtable_walk_data
        commit: fa002e8e79b3f980455ba585c1f47b26680de5b9
[05/14] KVM: arm64: Add a helper to tear down unlinked stage-2 subtrees
        commit: 8e94e1252cc054bb31fd3e9a15235cd831970ec1
[06/14] KVM: arm64: Use an opaque type for pteps
        commit: 6b91b8f95cadd3441c056182daf9024475ac4a91
[07/14] KVM: arm64: Tear down unlinked stage-2 subtree after break-before-make
        commit: 5c359cca1faf6d7671537fe1c240e8668467864d
[08/14] KVM: arm64: Protect stage-2 traversal with RCU
        commit: c3119ae45dfb6038ca458ab5ba7a9fba2810845b
[09/14] KVM: arm64: Atomically update stage 2 leaf attributes in parallel walks
        commit: ca5de2448c3b4c018fe3d6223df8b59068be1cd7
[10/14] KVM: arm64: Split init and set for table PTE
        commit: 331aa3a0547d1c794587e0df374d13b16645e832
[11/14] KVM: arm64: Make block->table PTE changes parallel-aware
        commit: 0ab12f3574db6cb432917a667f9392a88e8f0dfc
[12/14] KVM: arm64: Make leaf->leaf PTE changes parallel-aware
        commit: 946fbfdf336b811479e024136c7cabc00157b6b9
[13/14] KVM: arm64: Make table->block changes parallel-aware
        commit: af87fc03cfdf6893011df419588d27acdfb9c197
[14/14] KVM: arm64: Handle stage-2 faults in parallel
        commit: 1577cb5823cefdff4416f272a88143ee933d97f5

Fingers crossed,

	M.