@@ -1047,6 +1047,10 @@ isolate_migratepages_block(struct compact_control *cc, unsigned long low_pfn,if(!mapping&&(folio_ref_count(folio)-1)>folio_mapcount(folio))gotoisolate_fail_put;+/* The mapping truly isn't movable. */+if(mapping&&mapping_unmovable(mapping))+gotoisolate_fail_put;+
I doubt that it is safe to dereference mapping here. I believe the folio
can be truncated from under us and the mapping freed with the inode.
The folio has to be locked to dereference mapping safely (given that the
mapping is still tied to the folio).
There's even a comment to that effect later on in the function:
/*
* Only pages without mappings or that have a
* ->migrate_folio callback are possible to migrate
* without blocking. However, we can be racing with
* truncation so it's necessary to lock the page
* to stabilise the mapping as truncation holds
* the page lock until after the page is removed
* from the page cache.
*/
(that could be reworded to make it clear how dangerous dereferencing
->mapping is without the lock ... and it does need to be changed to say
"folio lock" instead of "page lock", so ...)
How does this look?
/*
* Only folios without mappings or that have
* a ->migrate_folio callback are possible to
* migrate without blocking. However, we can
* be racing with truncation, which can free
* the mapping. Truncation holds the folio lock
* until after the folio is removed from the page
* cache so holding it ourselves is sufficient.
*/
Maybe better to check if caller provided a buffer to get the max_order:
if (max_order)
*max_order = compound_order(compound_head(page));
This is what the previous version did (restrictedmem_get_page),
so that callers who only want to get a pfn don't need to define
an unused "order" param.
Maybe better to check if caller provided a buffer to get the max_order:
if (max_order)
*max_order = compound_order(compound_head(page));
This is what the previous version did (restrictedmem_get_page),
so that callers who only want to get a pfn don't need to define
an unused "order" param.
My preference would be to require @max_order. I can kinda sorta see why a generic
implementation (restrictedmem) would make the param optional, but with gmem being
KVM-internal I think it makes sense to require the param. Even if pKVM doesn't
_currently_ need/want the order of the backing allocation, presumably that's because
hugepage support is still on the TODO list, not because pKVM fundamentally doesn't
need to know the order of the backing allocation.
From: Sean Christopherson <seanjc@google.com> Date: 2023-07-25 18:05:38
On Fri, Jul 21, 2023, Xu Yilun wrote:
On 2023-07-21 at 14:26:11 +0800, Yan Zhao wrote:
quoted
On Tue, Jul 18, 2023 at 04:44:44PM -0700, Sean Christopherson wrote:
May I know why KVM now needs to register to callback .change_pte()?
I can see the original purpose is to "setting a pte in the shadow page
table directly, instead of flushing the shadow page table entry and then
getting vmexit to set it"[1].
IIUC, KVM is expected to directly make the new pte present for new
pages in this callback, like for COW.
Yes.
quoted
As also commented in kvm_mmu_notifier_change_pte(), .change_pte() must be
surrounded by .invalidate_range_{start,end}().
While kvm_mmu_notifier_invalidate_range_start() has called kvm_unmap_gfn_range()
to zap all leaf SPTEs, and page fault path will not install new SPTEs
successfully before kvm_mmu_notifier_invalidate_range_end(),
kvm_set_spte_gfn() should not be able to find any shadow present leaf entries to
update PFN.
I also failed to figure out how the kvm_set_spte_gfn() could pass
several !is_shadow_present_pte(iter.old_spte) check then write the new
pte.
It can't. .change_pte() has been dead code on x86 for 10+ years at this point,
and if my assessment from a few years back still holds true, it's dead code on
all architectures.
The only reason I haven't formally proposed dropping the hook is that I don't want
to risk the patch backfiring, i.e. I don't want to prompt someone to care enough
to try and fix it.
commit c13fda237f08a388ba8a0849785045944bf39834
Author: Sean Christopherson [off-list ref]
Date: Fri Apr 2 02:56:49 2021 +0200
KVM: Assert that notifier count is elevated in .change_pte()
In KVM's .change_pte() notification callback, replace the notifier
sequence bump with a WARN_ON assertion that the notifier count is
elevated. An elevated count provides stricter protections than bumping
the sequence, and the sequence is guarnateed to be bumped before the
count hits zero.
When .change_pte() was added by commit 828502d30073 ("ksm: add
mmu_notifier set_pte_at_notify()"), bumping the sequence was necessary
as .change_pte() would be invoked without any surrounding notifications.
However, since commit 6bdb913f0a70 ("mm: wrap calls to set_pte_at_notify
with invalidate_range_start and invalidate_range_end"), all calls to
.change_pte() are guaranteed to be surrounded by start() and end(), and
so are guaranteed to run with an elevated notifier count.
Note, wrapping .change_pte() with .invalidate_range_{start,end}() is a
bug of sorts, as invalidating the secondary MMU's (KVM's) PTE defeats
the purpose of .change_pte(). Every arch's kvm_set_spte_hva() assumes
.change_pte() is called when the relevant SPTE is present in KVM's MMU,
as the original goal was to accelerate Kernel Samepage Merging (KSM) by
updating KVM's SPTEs without requiring a VM-Exit (due to invalidating
the SPTE). I.e. it means that .change_pte() is effectively dead code
on _all_ architectures.
x86 and MIPS are clearcut nops if the old SPTE is not-present, and that
is guaranteed due to the prior invalidation. PPC simply unmaps the SPTE,
which again should be a nop due to the invalidation. arm64 is a bit
murky, but it's also likely a nop because kvm_pgtable_stage2_map() is
called without a cache pointer, which means it will map an entry if and
only if an existing PTE was found.
For now, take advantage of the bug to simplify future consolidation of
KVMs's MMU notifier code. Doing so will not greatly complicate fixing
.change_pte(), assuming it's even worth fixing. .change_pte() has been
broken for 8+ years and no one has complained. Even if there are
KSM+KVM users that care deeply about its performance, the benefits of
avoiding VM-Exits via .change_pte() need to be reevaluated to justify
the added complexity and testing burden. Ripping out .change_pte()
entirely would be a lot easier.
Maybe better to check if caller provided a buffer to get the max_order:
if (max_order)
*max_order = compound_order(compound_head(page));
This is what the previous version did (restrictedmem_get_page), so
that callers who only want to get a pfn don't need to define an unused
"order" param.
My preference would be to require @max_order. I can kinda sorta see why a
generic implementation (restrictedmem) would make the param optional, but
with gmem being KVM-internal I think it makes sense to require the param.
Even if pKVM doesn't _currently_ need/want the order of the backing
allocation, presumably that's because hugepage support is still on the TODO
list, not because pKVM fundamentally doesn't need to know the order of the
backing allocation.
Another usage is live migration. The migration flow works with 4KB pages only,
and we only need to get the pfn from the given gfn. "order" doesn't seem to be
useful for this case.
From: Nikunj A. Dadhania <hidden> Date: 2023-07-26 11:20:48
Hi Sean,
On 7/24/2023 10:30 PM, Sean Christopherson wrote:
On Mon, Jul 24, 2023, Nikunj A. Dadhania wrote:
quoted
On 7/19/2023 5:14 AM, Sean Christopherson wrote:
quoted
This is the next iteration of implementing fd-based (instead of vma-based)
memory for KVM guests. If you want the full background of why we are doing
this, please go read the v10 cover letter[1].
The biggest change from v10 is to implement the backing storage in KVM
itself, and expose it via a KVM ioctl() instead of a "generic" sycall.
See link[2] for details on why we pivoted to a KVM-specific approach.
Key word is "biggest". Relative to v10, there are many big changes.
Highlights below (I can't remember everything that got changed at
this point).
Tagged RFC as there are a lot of empty changelogs, and a lot of missing
documentation. And ideally, we'll have even more tests before merging.
There are also several gaps/opens (to be discussed in tomorrow's PUCK).
As per our discussion on the PUCK call, here are the memory/NUMA accounting
related observations that I had while working on SNP guest secure page migration:
* gmem allocations are currently treated as file page allocations
accounted to the kernel and not to the QEMU process.
We need to level set on terminology: these are all *stats*, not accounting. That
distinction matters because we have wiggle room on stats, e.g. we can probably get
away with just about any definition of how guest_memfd memory impacts stats, so
long as the information that is surfaced to userspace is useful and expected.
But we absolutely need to get accounting correct, specifically the allocations
need to be correctly accounted in memcg. And unless I'm missing something,
nothing in here shows anything related to memcg.
I tried out memcg after creating a separate cgroup for the qemu process. Guest
memory is accounted in memcg.
$ egrep -w "file|file_thp|unevictable" memory.stat
file 42978775040
file_thp 42949672960
unevictable 42953588736
NUMA allocations are coming from right nodes as set by the numactl.
$ egrep -w "file|file_thp|unevictable" memory.numa_stat
file N0=0 N1=20480 N2=21489377280 N3=21489377280
file_thp N0=0 N1=0 N2=21472739328 N3=21476933632
unevictable N0=0 N1=0 N2=21474697216 N3=21478891520
quoted
Starting an SNP guest with 40G memory with memory interleave between
Node2 and Node3
$ numactl -i 2,3 ./bootg_snp.sh
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
242179 root 20 0 40.4g 99580 51676 S 78.0 0.0 0:56.58 qemu-system-x86
-> Incorrect process resident memory and shared memory is reported
I don't know that I would call these "incorrect". Shared memory definitely is
correct, because by definition guest_memfd isn't shared. RSS is less clear cut;
gmem memory is resident in RAM, but if we show gmem in RSS then we'll end up with
scenarios where RSS > VIRT, which will be quite confusing for unaware users (I'm
assuming the 40g of VIRT here comes from QEMU mapping the shared half of gmem
memslots).
I am not sure why will RSS exceed the VIRT, it should be at max 40G (assuming all the
memory is private)
As per my experiments with a hack below. MM_FILEPAGES does get accounted to RSS/SHR in top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4339 root 20 0 40.4g 40.1g 40.1g S 76.7 16.0 0:13.83 qemu-system-x86
@@ -91,6 +91,10 @@ static struct folio *kvm_gmem_get_folio(struct file *file, pgoff_t index)clear_highpage(folio_page(folio,i));}+/* Account only once for the first time */+if(!folio_test_dirty(folio))+add_mm_counter(current->mm,MM_FILEPAGES,folio_nr_pages(folio));+folio_mark_accessed(folio);folio_mark_dirty(folio);folio_mark_uptodate(folio);
We can update the rss_stat appropriately to get correct reporting in userspace.
quoted
Accounting of the memory happens in the host page fault handler path,
but for private guest pages we will never hit that.
* NUMA allocation does use the process mempolicy for appropriate node
allocation (Node2 and Node3), but they again do not get attributed to
the QEMU process
Every 1.0s: sudo numastat -m -p qemu-system-x86 | egrep -i "qemu|PID|Node|Filepage" gomati: Mon Jul 24 11:51:34 2023
Per-node process memory usage (in MBs)
PID Node 0 Node 1 Node 2 Node 3 Total
242179 (qemu-system-x86) 21.14 1.61 39.44 39.38 101.57
Per-node system memory usage (in MBs):
Node 0 Node 1 Node 2 Node 3 Total
FilePages 2475.63 2395.83 23999.46 23373.22 52244.14
* Most of the memory accounting relies on the VMAs and as private-fd of
gmem doesn't have a VMA(and that was the design goal), user-space fails
to attribute the memory appropriately to the process.
/proc/<qemu pid>/numa_maps
7f528be00000 interleave:2-3 file=/memfd:memory-backend-memfd-shared\040(deleted) anon=1070 dirty=1070 mapped=1987 mapmax=256 active=1956 N2=582 N3=1405 kernelpagesize_kB=4
7f5c90200000 interleave:2-3 file=/memfd:rom-backend-memfd-shared\040(deleted)
7f5c90400000 interleave:2-3 file=/memfd:rom-backend-memfd-shared\040(deleted) dirty=32 active=0 N2=32 kernelpagesize_kB=4
7f5c90800000 interleave:2-3 file=/memfd:rom-backend-memfd-shared\040(deleted) dirty=892 active=0 N2=512 N3=380 kernelpagesize_kB=4
/proc/<qemu pid>/smaps
7f528be00000-7f5c8be00000 rw-p 00000000 00:01 26629 /memfd:memory-backend-memfd-shared (deleted)
7f5c90200000-7f5c90220000 rw-s 00000000 00:01 44033 /memfd:rom-backend-memfd-shared (deleted)
7f5c90400000-7f5c90420000 rw-s 00000000 00:01 44032 /memfd:rom-backend-memfd-shared (deleted)
7f5c90800000-7f5c90b7c000 rw-s 00000000 00:01 1025 /memfd:rom-backend-memfd-shared (deleted)
This is all expected, and IMO correct. There are no userspace mappings, and so
not accounting anything is working as intended.
Doesn't sound that correct, if 10 SNP guests are running each using 10GB, how would we know who is using 100GB of memory?
quoted
* QEMU based NUMA bindings will not work. Memory backend uses mbind()
to set the policy for a particular virtual memory range but gmem
private-FD does not have a virtual memory range visible in the host.
Yes, adding a generic fbind() is the way to solve silve.
@@ -1047,6 +1047,10 @@ isolate_migratepages_block(struct compact_control *cc, unsigned long low_pfn,if(!mapping&&(folio_ref_count(folio)-1)>folio_mapcount(folio))gotoisolate_fail_put;+/* The mapping truly isn't movable. */+if(mapping&&mapping_unmovable(mapping))+gotoisolate_fail_put;+
I doubt that it is safe to dereference mapping here. I believe the folio
can be truncated from under us and the mapping freed with the inode.
The folio has to be locked to dereference mapping safely (given that the
mapping is still tied to the folio).
There's even a comment to that effect later on in the function:
/*
* Only pages without mappings or that have a
* ->migrate_folio callback are possible to migrate
* without blocking. However, we can be racing with
* truncation so it's necessary to lock the page
* to stabilise the mapping as truncation holds
* the page lock until after the page is removed
* from the page cache.
*/
(that could be reworded to make it clear how dangerous dereferencing
->mapping is without the lock ... and it does need to be changed to say
"folio lock" instead of "page lock", so ...)
How does this look?
/*
* Only folios without mappings or that have
* a ->migrate_folio callback are possible to
* migrate without blocking. However, we can
* be racing with truncation, which can free
* the mapping. Truncation holds the folio lock
* until after the folio is removed from the page
* cache so holding it ourselves is sufficient.
*/
Looks good to me.
--
Kiryl Shutsemau / Kirill A. Shutemov
From: Sean Christopherson <seanjc@google.com> Date: 2023-07-26 14:25:09
On Wed, Jul 26, 2023, Nikunj A. Dadhania wrote:
Hi Sean,
On 7/24/2023 10:30 PM, Sean Christopherson wrote:
quoted
quoted
Starting an SNP guest with 40G memory with memory interleave between
Node2 and Node3
$ numactl -i 2,3 ./bootg_snp.sh
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
242179 root 20 0 40.4g 99580 51676 S 78.0 0.0 0:56.58 qemu-system-x86
-> Incorrect process resident memory and shared memory is reported
I don't know that I would call these "incorrect". Shared memory definitely is
correct, because by definition guest_memfd isn't shared. RSS is less clear cut;
gmem memory is resident in RAM, but if we show gmem in RSS then we'll end up with
scenarios where RSS > VIRT, which will be quite confusing for unaware users (I'm
assuming the 40g of VIRT here comes from QEMU mapping the shared half of gmem
memslots).
I am not sure why will RSS exceed the VIRT, it should be at max 40G (assuming all the
memory is private)
And also assuming that (a) userspace mmap()'d the shared side of things 1:1 with
private memory and (b) that the shared mappings have not been populated. Those
assumptions will mostly probably hold true for QEMU, but kernel correctness
shouldn't depend on assumptions about one specific userspace application.
This is all expected, and IMO correct. There are no userspace mappings, and so
not accounting anything is working as intended.
Doesn't sound that correct, if 10 SNP guests are running each using 10GB, how
would we know who is using 100GB of memory?
It's correct with respect to what the interfaces show, which is how much memory
is *mapped* into userspace.
As I said (or at least tried to say) in my first reply, I am not against exposing
memory usage to userspace via stats, only that it's not obvious to me that the
existing VMA-based stats are the most appropriate way to surface this information.
Is it better to make the destruction in reverse order from the creation?
Yeah. It _shoudn't_ matter, but there's no reason not keep things tidy and
consistent.
To put xa_destroy(&kvm->mem_attr_array) after cleanup_srcu_struct(&kvm->srcu),
or put xa_init(&kvm->mem_attr_array) after init_srcu_struct(&kvm->irq_srcu).
The former, because init_srcu_struct() can fail (allocates memory), whereas
xa_init() is a "pure" initialization routine.
+static int kvm_vm_ioctl_set_mem_attributes(struct kvm *kvm,
+ struct kvm_memory_attributes *attrs)
+{
+ gfn_t start, end;
+
+ /* flags is currently not used. */
+ if (attrs->flags)
+ return -EINVAL;
+ if (attrs->attributes & ~kvm_supported_mem_attributes(kvm))
+ return -EINVAL;
+ if (attrs->size == 0 || attrs->address + attrs->size < attrs->address)
+ return -EINVAL;
+ if (!PAGE_ALIGNED(attrs->address) || !PAGE_ALIGNED(attrs->size))
+ return -EINVAL;
+
+ start = attrs->address >> PAGE_SHIFT;
+ end = (attrs->address + attrs->size - 1 + PAGE_SIZE) >> PAGE_SHIFT;
As the attrs->address/size are both garanteed to be non-zero, non-wrap
and page aligned in prevous check. Is it OK to simplify the calculation,
like:
end = (attrs->address + attrs->size) >> PAGE_SHIFT;
Yes, that should work.
Chao, am I missing something? Or did we just end up with unnecessarly convoluted
code as things evolved?
quoted
+
+ if (WARN_ON_ONCE(start == end))
+ return -EINVAL;
Also, is this check possible to be hit? Maybe remove it?
It should be impossible to, hence the WARN. I added the check for two reasons:
(1) to help document that end is exclusive, and (2) to guard against future bugs.
Should this be:
#define GUEST_MEMORY_KVM_MAGIC
or KVM_GUEST_MEMORY_KVM_MAGIC?
BALLOON_KVM_MAGIC is KVM-specific few lines above.
---
Originally, I was planning to use the generic guest memfd infrastructure
to support Gunyah hypervisor, however I see that's probably not going to
be possible now that the guest memfd implementation is KVM-specific. I
think this is good for both KVM and Gunyah as there will be some Gunyah
specifics and some KVM specifics in each of implementation, as you
mentioned in the previous series.
I'll go through series over next week or so and I'll try to find how
much similar Gunyah guest mem fd implementation would be and we can see
if it's better to pull whatever that ends up being into a common
implementation? We could also agree to have completely divergent fd
implementations like we do for the UAPI. Thoughts?
Thanks,
Elliot
<snip>
Should this be:
#define GUEST_MEMORY_KVM_MAGIC
or KVM_GUEST_MEMORY_KVM_MAGIC?
BALLOON_KVM_MAGIC is KVM-specific few lines above.
Ah, good point. My preference would be either KVM_GUEST_MEMORY_MAGIC or
KVM_GUEST_MEMFD_MAGIC. Though hopefully we don't actually need a dedicated
filesystem, I _think_ it's unnecessary if we don't try to support userspace
mounts.
---
Originally, I was planning to use the generic guest memfd infrastructure to
support Gunyah hypervisor, however I see that's probably not going to be
possible now that the guest memfd implementation is KVM-specific. I think
this is good for both KVM and Gunyah as there will be some Gunyah specifics
and some KVM specifics in each of implementation, as you mentioned in the
previous series.
Yeah, that's where my headspace is at too. Sharing the actual uAPI, and even
internal APIs to some extent, doesn't save all that much, e.g. wiring up an ioctl()
is the easy part. Whereas I strongly suspect each hypervisor use case will want
different semantics for the uAPI.
I'll go through series over next week or so and I'll try to find how much
similar Gunyah guest mem fd implementation would be and we can see if it's
better to pull whatever that ends up being into a common implementation?
That would be awesome!
We could also agree to have completely divergent fd implementations like we
do for the UAPI. Thoughts?
I'd like to avoid _completely_ divergent implementations, e.g. the majority of
kvm_gmem_allocate() and __kvm_gmem_create() isn't KVM specific. I think there
would be value in sharing the core allocation logic, even if the other details
are different. Especially if we fully commit to not supporting migration or
swap, and decide to use xarray directly to manage folios instead of bouncing
through the filemap APIs.
Thanks!
I think these should be static assertions near the definition of the
structs. However another possibility is to remove 'raw' and just assign the
whole union.
Duh, and use a named union. I think when I first proposed this I forgot that
a single value would be passed between kvm_hva_range *and* kvm_gfn_range, and so
created an anonymous union without thinking about the impliciations.
A named union is _much_ cleaner. I'll post a complete version of the below
snippet as a standalone non-RFC patch.
On 2023-07-26 at 08:59:53 -0700, Sean Christopherson wrote:
On Mon, Jul 24, 2023, Xu Yilun wrote:
quoted
On 2023-07-18 at 16:44:51 -0700, Sean Christopherson wrote:
quoted
+ if (WARN_ON_ONCE(start == end))
+ return -EINVAL;
Also, is this check possible to be hit? Maybe remove it?
It should be impossible to, hence the WARN. I added the check for two reasons:
(1) to help document that end is exclusive, and (2) to guard against future bugs.
This is all expected, and IMO correct. There are no userspace mappings, and so
not accounting anything is working as intended.
Doesn't sound that correct, if 10 SNP guests are running each using 10GB, how
would we know who is using 100GB of memory?
It's correct with respect to what the interfaces show, which is how much memory
is *mapped* into userspace.
As I said (or at least tried to say) in my first reply, I am not against exposing
memory usage to userspace via stats, only that it's not obvious to me that the
existing VMA-based stats are the most appropriate way to surface this information.
Right, then should we think in the line of creating a VM IOCTL for querying current memory
usage for guest-memfd ?
We could use memcg for statistics, but then memory cgroup can be disabled and so memcg
isn't really a dependable option.
Do you have some ideas on how to expose the memory usage to the user space other than
VMA-based stats ?
Regards,
Nikunj
@@ -5134,6 +5167,16 @@ static long kvm_vm_ioctl(struct file *filp, case KVM_GET_STATS_FD: r = kvm_vm_ioctl_get_stats_fd(kvm); break;+ case KVM_CREATE_GUEST_MEMFD: {+ struct kvm_create_guest_memfd guest_memfd;++ r = -EFAULT;+ if (copy_from_user(&guest_memfd, argp, sizeof(guest_memfd)))+ goto out;++ r = kvm_gmem_create(kvm, &guest_memfd);+ break;+ }
I'm thinking line of sight here, by having this as a vm ioctl (rather
than a system iocl), would it complicate making it possible in the
future to share/donate memory between VMs?
Cheers,
/fuad
From: Sean Christopherson <seanjc@google.com> Date: 2023-07-27 17:13:32
On Thu, Jul 27, 2023, Fuad Tabba wrote:
Hi Sean,
<snip>
...
quoted
@@ -5134,6 +5167,16 @@ static long kvm_vm_ioctl(struct file *filp, case KVM_GET_STATS_FD: r = kvm_vm_ioctl_get_stats_fd(kvm); break;+ case KVM_CREATE_GUEST_MEMFD: {+ struct kvm_create_guest_memfd guest_memfd;++ r = -EFAULT;+ if (copy_from_user(&guest_memfd, argp, sizeof(guest_memfd)))+ goto out;++ r = kvm_gmem_create(kvm, &guest_memfd);+ break;+ }
I'm thinking line of sight here, by having this as a vm ioctl (rather
than a system iocl), would it complicate making it possible in the
future to share/donate memory between VMs?
Maybe, but I hope not?
There would still be a primary owner of the memory, i.e. the memory would still
need to be allocated in the context of a specific VM. And the primary owner should
be able to restrict privileges, e.g. allow a different VM to read but not write
memory.
My current thinking is to (a) tie the lifetime of the backing pages to the inode,
i.e. allow allocations to outlive the original VM, and (b) create a new file each
time memory is shared/donated with a different VM (or other entity in the kernel).
That should make it fairly straightforward to provide different permissions, e.g.
track them per-file, and I think should also avoid the need to change the memslot
binding logic since each VM would have it's own view/bindings.
Copy+pasting a relevant snippet from a lengthier response in a different thread[*]:
Conceptually, I think KVM should to bind to the file. The inode is effectively
the raw underlying physical storage, while the file is the VM's view of that
storage.
Practically, I think that gives us a clean, intuitive way to handle intra-host
migration. Rather than transfer ownership of the file, instantiate a new file
for the target VM, using the gmem inode from the source VM, i.e. create a hard
link. That'd probably require new uAPI, but I don't think that will be hugely
problematic. KVM would need to ensure the new VM's guest_memfd can't be mapped
until KVM_CAP_VM_MOVE_ENC_CONTEXT_FROM (which would also need to verify the
memslots/bindings are identical), but that should be easy enough to enforce.
That way, a VM, its memslots, and its SPTEs are tied to the file, while allowing
the memory and the *contents* of memory to outlive the VM, i.e. be effectively
transfered to the new target VM. And we'll maintain the invariant that each
guest_memfd is bound 1:1 with a single VM.
As above, that should also help us draw the line between mapping memory into a
VM (file), and freeing/reclaiming the memory (inode).
There will be extra complexity/overhead as we'll have to play nice with the
possibility of multiple files per inode, e.g. to zap mappings across all files
when punching a hole, but the extra complexity is quite small, e.g. we can use
address_space.private_list to keep track of the guest_memfd instances associated
with the inode.
Setting aside TDX and SNP for the moment, as it's not clear how they'll support
memory that is "private" but shared between multiple VMs, I think per-VM files
would work well for sharing gmem between two VMs. E.g. would allow a give page
to be bound to a different gfn for each VM, would allow having different permissions
for each file (e.g. to allow fallocate() only from the original owner).
[*] https://lore.kernel.org/all/ZLGiEfJZTyl7M8mS@google.com
@@ -95,6 +95,16 @@ struct kvm_userspace_memory_region {__u64userspace_addr;/* start of the userspace allocated memory */};+/* for KVM_SET_USER_MEMORY_REGION2 */+structkvm_userspace_memory_region2{+__u32slot;+__u32flags;+__u64guest_phys_addr;+__u64memory_size;+__u64userspace_addr;+__u64pad[16];
Should we replace that pad[16] with:
__u64 size;
where 'size' is the size of the structure as seen by userspace? This is
used in other UAPIs (see struct sched_attr for example) and is a bit
more robust for future extensions (e.g. an 'old' kernel can correctly
reject a newer version of the struct with additional fields it doesn't
know about if that makes sense, etc).
@@ -1047,6 +1047,10 @@ isolate_migratepages_block(struct compact_control *cc, unsigned long low_pfn,if(!mapping&&(folio_ref_count(folio)-1)>folio_mapcount(folio))gotoisolate_fail_put;+/* The mapping truly isn't movable. */+if(mapping&&mapping_unmovable(mapping))+gotoisolate_fail_put;+
I doubt that it is safe to dereference mapping here. I believe the folio
can be truncated from under us and the mapping freed with the inode.
The folio has to be locked to dereference mapping safely (given that the
mapping is still tied to the folio).
There's even a comment to that effect later on in the function:
Hmm, well spotted. But it wouldn't be so great if we now had to lock every
inspected page (and not just dirty pages), just to check the AS_ bit.
But I wonder if this is leftover from previous versions. Are the guest pages
even PageLRU currently? (and should they be, given how they can't be swapped
out or anything?) If not, isolate_migratepages_block will skip them anyway.
/*
* Only pages without mappings or that have a
* ->migrate_folio callback are possible to migrate
* without blocking. However, we can be racing with
* truncation so it's necessary to lock the page
* to stabilise the mapping as truncation holds
* the page lock until after the page is removed
* from the page cache.
*/
(that could be reworded to make it clear how dangerous dereferencing
->mapping is without the lock ... and it does need to be changed to say
"folio lock" instead of "page lock", so ...)
How does this look?
/*
* Only folios without mappings or that have
* a ->migrate_folio callback are possible to
* migrate without blocking. However, we can
* be racing with truncation, which can free
* the mapping. Truncation holds the folio lock
* until after the folio is removed from the page
* cache so holding it ourselves is sufficient.
*/
From: Paolo Bonzini <pbonzini@redhat.com> Date: 2023-07-28 16:14:25
On 7/28/23 18:02, Vlastimil Babka wrote:
quoted
There's even a comment to that effect later on in the function:
Hmm, well spotted. But it wouldn't be so great if we now had to lock every
inspected page (and not just dirty pages), just to check the AS_ bit.
But I wonder if this is leftover from previous versions. Are the guest pages
even PageLRU currently? (and should they be, given how they can't be swapped
out or anything?) If not, isolate_migratepages_block will skip them anyway.
No, they're not (migration or even swap-out is not excluded for the
future, but for now it's left for future work.
Paolo
@@ -95,6 +95,16 @@ struct kvm_userspace_memory_region {__u64userspace_addr;/* start of the userspace allocated memory */};+/* for KVM_SET_USER_MEMORY_REGION2 */+structkvm_userspace_memory_region2{+__u32slot;+__u32flags;+__u64guest_phys_addr;+__u64memory_size;+__u64userspace_addr;+__u64pad[16];
Should we replace that pad[16] with:
__u64 size;
where 'size' is the size of the structure as seen by userspace? This is
used in other UAPIs (see struct sched_attr for example) and is a bit
more robust for future extensions (e.g. an 'old' kernel can correctly
reject a newer version of the struct with additional fields it doesn't
know about if that makes sense, etc).
"flags" serves that purpose, i.e. allows userspace to opt-in to having KVM actually
consume what is currently just padding.
The padding is there mainly to simplify kernel/KVM code, e.g. the number of bytes
that KVM needs to copy in is static.
But now that I think more on this, I don't know why we didn't just unconditionally
bump the size of kvm_userspace_memory_region. We tried to play games with unions
and overlays, but that was a mess[*].
KVM would need to do multiple uaccess reads, but that's not a big deal. Am I
missing something, or did past us just get too clever and miss the obvious solution?
[*] https://lkml.kernel.org/r/Y7xrtf9FCuYRYm1q%40google.com
@@ -95,6 +95,16 @@ struct kvm_userspace_memory_region {__u64userspace_addr;/* start of the userspace allocated memory */};+/* for KVM_SET_USER_MEMORY_REGION2 */+structkvm_userspace_memory_region2{+__u32slot;+__u32flags;+__u64guest_phys_addr;+__u64memory_size;+__u64userspace_addr;+__u64pad[16];
Should we replace that pad[16] with:
__u64 size;
where 'size' is the size of the structure as seen by userspace? This is
used in other UAPIs (see struct sched_attr for example) and is a bit
more robust for future extensions (e.g. an 'old' kernel can correctly
reject a newer version of the struct with additional fields it doesn't
know about if that makes sense, etc).
"flags" serves that purpose, i.e. allows userspace to opt-in to having KVM actually
consume what is currently just padding.
Sure, I've just grown to dislike static padding of that type -- it ends
up being either a waste a space, or is too small, while the 'superior'
alternative (having a 'size' member) doesn't cost much and avoids those
problems.
But no strong opinion really, this struct really shouldn't grow much,
so I'm sure that'll be fine in practice.
The padding is there mainly to simplify kernel/KVM code, e.g. the number of bytes
that KVM needs to copy in is static.
But now that I think more on this, I don't know why we didn't just unconditionally
bump the size of kvm_userspace_memory_region. We tried to play games with unions
and overlays, but that was a mess[*].
KVM would need to do multiple uaccess reads, but that's not a big deal. Am I
missing something, or did past us just get too clever and miss the obvious solution?
[*] https://lkml.kernel.org/r/Y7xrtf9FCuYRYm1q%40google.com
Right, so the first uaccess would get_user() the flags, based on that
we'd figure out the size of the struct, copy_from_user() what we need,
and then sanity check the flags are the same from both reads, or
something along those lines?
That doesn't sound too complicated to me, and as long as every extension
to the struct does come with a new flag I can't immediately see what
would go wrong.
Hi Sean,
On Thu, Jul 27, 2023 at 6:13 PM Sean Christopherson [off-list ref] wrote:
On Thu, Jul 27, 2023, Fuad Tabba wrote:
quoted
Hi Sean,
<snip>
...
quoted
@@ -5134,6 +5167,16 @@ static long kvm_vm_ioctl(struct file *filp, case KVM_GET_STATS_FD: r = kvm_vm_ioctl_get_stats_fd(kvm); break;+ case KVM_CREATE_GUEST_MEMFD: {+ struct kvm_create_guest_memfd guest_memfd;++ r = -EFAULT;+ if (copy_from_user(&guest_memfd, argp, sizeof(guest_memfd)))+ goto out;++ r = kvm_gmem_create(kvm, &guest_memfd);+ break;+ }
I'm thinking line of sight here, by having this as a vm ioctl (rather
than a system iocl), would it complicate making it possible in the
future to share/donate memory between VMs?
Maybe, but I hope not?
There would still be a primary owner of the memory, i.e. the memory would still
need to be allocated in the context of a specific VM. And the primary owner should
be able to restrict privileges, e.g. allow a different VM to read but not write
memory.
My current thinking is to (a) tie the lifetime of the backing pages to the inode,
i.e. allow allocations to outlive the original VM, and (b) create a new file each
time memory is shared/donated with a different VM (or other entity in the kernel).
That should make it fairly straightforward to provide different permissions, e.g.
track them per-file, and I think should also avoid the need to change the memslot
binding logic since each VM would have it's own view/bindings.
Copy+pasting a relevant snippet from a lengthier response in a different thread[*]:
Conceptually, I think KVM should to bind to the file. The inode is effectively
the raw underlying physical storage, while the file is the VM's view of that
storage.
I'm not aware of any implementation of sharing memory between VMs in
KVM before (afaik, since there was no need for one). The following is
me thinking out loud, rather than any strong opinions on my part.
If an allocation can outlive the original VM, then why associate it
with that (or a) VM to begin with? Wouldn't it be more flexible if it
were a system-level construct, which is effectively what it was in
previous iterations of this? This doesn't rule out binding to the
file, and keeping the inode as the underlying physical storage.
The binding of a VM to a guestmem object could happen implicitly with
KVM_SET_USER_MEMORY_REGION2, or we could have a new ioctl specifically
for handling binding.
Cheers,
/fuad
Practically, I think that gives us a clean, intuitive way to handle intra-host
migration. Rather than transfer ownership of the file, instantiate a new file
for the target VM, using the gmem inode from the source VM, i.e. create a hard
link. That'd probably require new uAPI, but I don't think that will be hugely
problematic. KVM would need to ensure the new VM's guest_memfd can't be mapped
until KVM_CAP_VM_MOVE_ENC_CONTEXT_FROM (which would also need to verify the
memslots/bindings are identical), but that should be easy enough to enforce.
That way, a VM, its memslots, and its SPTEs are tied to the file, while allowing
the memory and the *contents* of memory to outlive the VM, i.e. be effectively
transfered to the new target VM. And we'll maintain the invariant that each
guest_memfd is bound 1:1 with a single VM.
As above, that should also help us draw the line between mapping memory into a
VM (file), and freeing/reclaiming the memory (inode).
There will be extra complexity/overhead as we'll have to play nice with the
possibility of multiple files per inode, e.g. to zap mappings across all files
when punching a hole, but the extra complexity is quite small, e.g. we can use
address_space.private_list to keep track of the guest_memfd instances associated
with the inode.
Setting aside TDX and SNP for the moment, as it's not clear how they'll support
memory that is "private" but shared between multiple VMs, I think per-VM files
would work well for sharing gmem between two VMs. E.g. would allow a give page
to be bound to a different gfn for each VM, would allow having different permissions
for each file (e.g. to allow fallocate() only from the original owner).
[*] https://lore.kernel.org/all/ZLGiEfJZTyl7M8mS@google.com
From: Paolo Bonzini <pbonzini@redhat.com> Date: 2023-07-31 15:59:04
On 7/29/23 02:03, Sean Christopherson wrote:
KVM would need to do multiple uaccess reads, but that's not a big
deal. Am I missing something, or did past us just get too clever and
miss the obvious solution?
You would have to introduce struct kvm_userspace_memory_region2 anyway,
though not a new ioctl, for two reasons:
1) the current size of the struct is part of the userspace API via the
KVM_SET_USER_MEMORY_REGION #define, so introducing a new struct is the
easiest way to preserve this
2) the struct can (at least theoretically) enter the ABI of a shared
library, and such mismatches are really hard to detect and resolve. So
it's better to add the padding to a new struct, and keep struct
kvm_userspace_memory_region backwards-compatible.
As to whether we should introduce a new ioctl: doing so makes
KVM_SET_USER_MEMORY_REGION's detection of bad flags a bit more robust;
it's not like we cannot introduce new flags at all, of course, but
having out-of-bounds reads as a side effect of new flags is a bit nasty.
Protecting programs from their own bugs gets into diminishing returns
very quickly, but introducing a new ioctl can make exploits a bit harder
when struct kvm_userspace_memory_region is on the stack and adjacent to
an attacker-controlled location.
Paolo
Maybe better to check if caller provided a buffer to get the max_order:
if (max_order)
*max_order = compound_order(compound_head(page));
This is what the previous version did (restrictedmem_get_page),
so that callers who only want to get a pfn don't need to define
an unused "order" param.
My preference would be to require @max_order. I can kinda sorta see why a generic
implementation (restrictedmem) would make the param optional, but with gmem being
KVM-internal I think it makes sense to require the param. Even if pKVM doesn't
_currently_ need/want the order of the backing allocation, presumably that's because
hugepage support is still on the TODO list, not because pKVM fundamentally doesn't
need to know the order of the backing allocation.
You're right that with huge pages pKVM will eventually need to know
the order of the backing allocation, but there is at least one use
case where it doesn't, which I ran into in the previous ports as well
as this one. In pKVM (and in possibly other implementations), the host
needs to access (shared) guest memory that isn't mapped. For that,
I've used kvm_*_get_pfn(), only requiring the pfn, so get the page via
pfn_to_page().
Although it's not that big, my preference would be for max_order to be optional.
Thanks!
/fuad
On Tue, Jul 18, 2023 at 04:44:51PM -0700,
Sean Christopherson [off-list ref] wrote:
quoted hunk
From: Chao Peng <redacted>
In confidential computing usages, whether a page is private or shared is
necessary information for KVM to perform operations like page fault
handling, page zapping etc. There are other potential use cases for
per-page memory attributes, e.g. to make memory read-only (or no-exec,
or exec-only, etc.) without having to modify memslots.
Introduce two ioctls (advertised by KVM_CAP_MEMORY_ATTRIBUTES) to allow
userspace to operate on the per-page memory attributes.
- KVM_SET_MEMORY_ATTRIBUTES to set the per-page memory attributes to
a guest memory range.
- KVM_GET_SUPPORTED_MEMORY_ATTRIBUTES to return the KVM supported
memory attributes.
Use an xarray to store the per-page attributes internally, with a naive,
not fully optimized implementation, i.e. prioritize correctness over
performance for the initial implementation.
Because setting memory attributes is roughly analogous to mprotect() on
memory that is mapped into the guest, zap existing mappings prior to
updating the memory attributes. Opportunistically provide an arch hook
for the post-set path (needed to complete invalidation anyways) in
anticipation of x86 needing the hook to update metadata related to
determining whether or not a given gfn can be backed with various sizes
of hugepages.
It's possible that future usages may not require an invalidation, e.g.
if KVM ends up supporting RWX protections and userspace grants _more_
protections, but again opt for simplicity and punt optimizations to
if/when they are needed.
Suggested-by: Sean Christopherson <seanjc@google.com>
Link: https://lore.kernel.org/all/Y2WB48kD0J4VGynX@google.com
Cc: Fuad Tabba <redacted>
Signed-off-by: Chao Peng <redacted>
Co-developed-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
Documentation/virt/kvm/api.rst | 60 ++++++++++++
include/linux/kvm_host.h | 14 +++
include/uapi/linux/kvm.h | 14 +++
virt/kvm/Kconfig | 4 +
virt/kvm/kvm_main.c | 170 +++++++++++++++++++++++++++++++++
5 files changed, 262 insertions(+)
@@ -6068,6 +6068,56 @@ writes to the CNTVCT_EL0 and CNTPCT_EL0 registers using the SET_ONE_REG interface. No error will be returned, but the resulting offset will not be applied.+4.139 KVM_GET_SUPPORTED_MEMORY_ATTRIBUTES+-----------------------------------------++:Capability: KVM_CAP_MEMORY_ATTRIBUTES+:Architectures: x86+:Type: vm ioctl+:Parameters: u64 memory attributes bitmask(out)+:Returns: 0 on success, <0 on error++Returns supported memory attributes bitmask. Supported memory attributes will+have the corresponding bits set in u64 memory attributes bitmask.++The following memory attributes are defined::++ #define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)++4.140 KVM_SET_MEMORY_ATTRIBUTES+-----------------------------------------++:Capability: KVM_CAP_MEMORY_ATTRIBUTES+:Architectures: x86+:Type: vm ioctl+:Parameters: struct kvm_memory_attributes(in/out)+:Returns: 0 on success, <0 on error++Sets memory attributes for pages in a guest memory range. Parameters are+specified via the following structure::++ struct kvm_memory_attributes {+ __u64 address;+ __u64 size;+ __u64 attributes;+ __u64 flags;+ };++The user sets the per-page memory attributes to a guest memory range indicated+by address/size, and in return KVM adjusts address and size to reflect the+actual pages of the memory range have been successfully set to the attributes.+If the call returns 0, "address" is updated to the last successful address + 1+and "size" is updated to the remaining address size that has not been set+successfully. The user should check the return value as well as the size to+decide if the operation succeeded for the whole range or not. The user may want+to retry the operation with the returned address/size if the previous range was+partially successful.++Both address and size should be page aligned and the supported attributes can be+retrieved with KVM_GET_SUPPORTED_MEMORY_ATTRIBUTES.++The "flags" field may be used for future extensions and should be set to 0s.+5. The kvm_run structure ========================
@@ -8494,6 +8544,16 @@ block sizes is exposed in KVM_CAP_ARM_SUPPORTED_BLOCK_SIZES as a 64-bit bitmap (each bit describing a block size). The default value is 0, to disable the eager page splitting.+8.41 KVM_CAP_MEMORY_ATTRIBUTES+------------------------------++:Capability: KVM_CAP_MEMORY_ATTRIBUTES+:Architectures: x86+:Type: vm++This capability indicates KVM supports per-page memory attributes and ioctls+KVM_GET_SUPPORTED_MEMORY_ATTRIBUTES/KVM_SET_MEMORY_ATTRIBUTES are available.+9. Known KVM API problems =========================
@@ -2301,4 +2305,14 @@ static inline void kvm_account_pgtable_pages(void *virt, int nr)/* Max number of entries allowed for each kvm dirty ring */#define KVM_DIRTY_RING_MAX_ENTRIES 65536+#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES+staticinlineunsignedlongkvm_get_memory_attributes(structkvm*kvm,gfn_tgfn)+{+returnxa_to_value(xa_load(&kvm->mem_attr_array,gfn));+}++boolkvm_arch_post_set_memory_attributes(structkvm*kvm,+structkvm_gfn_range*range);+#endif /* CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES */+#endif
Hi Sean,
On 7/24/2023 10:30 PM, Sean Christopherson wrote:
quoted
On Mon, Jul 24, 2023, Nikunj A. Dadhania wrote:
quoted
On 7/19/2023 5:14 AM, Sean Christopherson wrote:
quoted
This is the next iteration of implementing fd-based (instead of vma-based)
memory for KVM guests. If you want the full background of why we are doing
this, please go read the v10 cover letter[1].
The biggest change from v10 is to implement the backing storage in KVM
itself, and expose it via a KVM ioctl() instead of a "generic" sycall.
See link[2] for details on why we pivoted to a KVM-specific approach.
Key word is "biggest". Relative to v10, there are many big changes.
Highlights below (I can't remember everything that got changed at
this point).
Tagged RFC as there are a lot of empty changelogs, and a lot of missing
documentation. And ideally, we'll have even more tests before merging.
There are also several gaps/opens (to be discussed in tomorrow's PUCK).
As per our discussion on the PUCK call, here are the memory/NUMA accounting
related observations that I had while working on SNP guest secure page migration:
* gmem allocations are currently treated as file page allocations
accounted to the kernel and not to the QEMU process.
We need to level set on terminology: these are all *stats*, not accounting. That
distinction matters because we have wiggle room on stats, e.g. we can probably get
away with just about any definition of how guest_memfd memory impacts stats, so
long as the information that is surfaced to userspace is useful and expected.
But we absolutely need to get accounting correct, specifically the allocations
need to be correctly accounted in memcg. And unless I'm missing something,
nothing in here shows anything related to memcg.
I tried out memcg after creating a separate cgroup for the qemu process. Guest
memory is accounted in memcg.
$ egrep -w "file|file_thp|unevictable" memory.stat
file 42978775040
file_thp 42949672960
unevictable 42953588736
NUMA allocations are coming from right nodes as set by the numactl.
$ egrep -w "file|file_thp|unevictable" memory.numa_stat
file N0=0 N1=20480 N2=21489377280 N3=21489377280
file_thp N0=0 N1=0 N2=21472739328 N3=21476933632
unevictable N0=0 N1=0 N2=21474697216 N3=21478891520
quoted
quoted
Starting an SNP guest with 40G memory with memory interleave between
Node2 and Node3
$ numactl -i 2,3 ./bootg_snp.sh
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
242179 root 20 0 40.4g 99580 51676 S 78.0 0.0 0:56.58 qemu-system-x86
-> Incorrect process resident memory and shared memory is reported
I don't know that I would call these "incorrect". Shared memory definitely is
correct, because by definition guest_memfd isn't shared. RSS is less clear cut;
gmem memory is resident in RAM, but if we show gmem in RSS then we'll end up with
scenarios where RSS > VIRT, which will be quite confusing for unaware users (I'm
assuming the 40g of VIRT here comes from QEMU mapping the shared half of gmem
memslots).
I am not sure why will RSS exceed the VIRT, it should be at max 40G (assuming all the
memory is private)
As per my experiments with a hack below. MM_FILEPAGES does get accounted to RSS/SHR in top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4339 root 20 0 40.4g 40.1g 40.1g S 76.7 16.0 0:13.83 qemu-system-x86
@@ -91,6 +91,10 @@ static struct folio *kvm_gmem_get_folio(struct file *file, pgoff_t index)clear_highpage(folio_page(folio,i));}+/* Account only once for the first time */+if(!folio_test_dirty(folio))+add_mm_counter(current->mm,MM_FILEPAGES,folio_nr_pages(folio));
I think this alone would cause "Bad rss-counter" messages when the process
exits, because there's no corresponding decrement when page tables are torn
down. We would probably have to instantiate the page tables (i.e. with
PROT_NONE so userspace can't really do accesses through them) for this to
work properly.
So then it wouldn't technically be "unmapped private memory" anymore, but
effectively still would be. Maybe there would be more benefits, like the
mbind() working. But where would the PROT_NONE page tables be instantiated
if there's no page fault? During the ioctl? And is perhaps too much (CPU)
work for little benefit? Maybe, but we could say it makes things simpler and
can be optimized later?
Anyway IMHO it would be really great if the memory usage was attributable
the usual way without new IOCTLs or something. Each time some memory appears
"unaccounted" somewhere, it causes confusion.
+
folio_mark_accessed(folio);
folio_mark_dirty(folio);
folio_mark_uptodate(folio);
We can update the rss_stat appropriately to get correct reporting in userspace.
quoted
quoted
Accounting of the memory happens in the host page fault handler path,
but for private guest pages we will never hit that.
* NUMA allocation does use the process mempolicy for appropriate node
allocation (Node2 and Node3), but they again do not get attributed to
the QEMU process
Every 1.0s: sudo numastat -m -p qemu-system-x86 | egrep -i "qemu|PID|Node|Filepage" gomati: Mon Jul 24 11:51:34 2023
Per-node process memory usage (in MBs)
PID Node 0 Node 1 Node 2 Node 3 Total
242179 (qemu-system-x86) 21.14 1.61 39.44 39.38 101.57
Per-node system memory usage (in MBs):
Node 0 Node 1 Node 2 Node 3 Total
FilePages 2475.63 2395.83 23999.46 23373.22 52244.14
* Most of the memory accounting relies on the VMAs and as private-fd of
gmem doesn't have a VMA(and that was the design goal), user-space fails
to attribute the memory appropriately to the process.
/proc/<qemu pid>/numa_maps
7f528be00000 interleave:2-3 file=/memfd:memory-backend-memfd-shared\040(deleted) anon=1070 dirty=1070 mapped=1987 mapmax=256 active=1956 N2=582 N3=1405 kernelpagesize_kB=4
7f5c90200000 interleave:2-3 file=/memfd:rom-backend-memfd-shared\040(deleted)
7f5c90400000 interleave:2-3 file=/memfd:rom-backend-memfd-shared\040(deleted) dirty=32 active=0 N2=32 kernelpagesize_kB=4
7f5c90800000 interleave:2-3 file=/memfd:rom-backend-memfd-shared\040(deleted) dirty=892 active=0 N2=512 N3=380 kernelpagesize_kB=4
/proc/<qemu pid>/smaps
7f528be00000-7f5c8be00000 rw-p 00000000 00:01 26629 /memfd:memory-backend-memfd-shared (deleted)
7f5c90200000-7f5c90220000 rw-s 00000000 00:01 44033 /memfd:rom-backend-memfd-shared (deleted)
7f5c90400000-7f5c90420000 rw-s 00000000 00:01 44032 /memfd:rom-backend-memfd-shared (deleted)
7f5c90800000-7f5c90b7c000 rw-s 00000000 00:01 1025 /memfd:rom-backend-memfd-shared (deleted)
This is all expected, and IMO correct. There are no userspace mappings, and so
not accounting anything is working as intended.
Doesn't sound that correct, if 10 SNP guests are running each using 10GB, how would we know who is using 100GB of memory?
quoted
quoted
* QEMU based NUMA bindings will not work. Memory backend uses mbind()
to set the policy for a particular virtual memory range but gmem
private-FD does not have a virtual memory range visible in the host.
Yes, adding a generic fbind() is the way to solve silve.
From: Chao Peng <redacted>
In confidential computing usages, whether a page is private or shared is
necessary information for KVM to perform operations like page fault
handling, page zapping etc. There are other potential use cases for
per-page memory attributes, e.g. to make memory read-only (or no-exec,
or exec-only, etc.) without having to modify memslots.
Introduce two ioctls (advertised by KVM_CAP_MEMORY_ATTRIBUTES) to allow
userspace to operate on the per-page memory attributes.
- KVM_SET_MEMORY_ATTRIBUTES to set the per-page memory attributes to
a guest memory range.
- KVM_GET_SUPPORTED_MEMORY_ATTRIBUTES to return the KVM supported
memory attributes.
Use an xarray to store the per-page attributes internally, with a naive,
not fully optimized implementation, i.e. prioritize correctness over
performance for the initial implementation.
Because setting memory attributes is roughly analogous to mprotect() on
memory that is mapped into the guest, zap existing mappings prior to
updating the memory attributes. Opportunistically provide an arch hook
for the post-set path (needed to complete invalidation anyways) in
s/anyways/anyway
quoted hunk
anticipation of x86 needing the hook to update metadata related to
determining whether or not a given gfn can be backed with various sizes
of hugepages.
It's possible that future usages may not require an invalidation, e.g.
if KVM ends up supporting RWX protections and userspace grants _more_
protections, but again opt for simplicity and punt optimizations to
if/when they are needed.
Suggested-by: Sean Christopherson <seanjc@google.com>
Link: https://lore.kernel.org/all/Y2WB48kD0J4VGynX@google.com
Cc: Fuad Tabba <redacted>
Signed-off-by: Chao Peng <redacted>
Co-developed-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
Documentation/virt/kvm/api.rst | 60 ++++++++++++
include/linux/kvm_host.h | 14 +++
include/uapi/linux/kvm.h | 14 +++
virt/kvm/Kconfig | 4 +
virt/kvm/kvm_main.c | 170 +++++++++++++++++++++++++++++++++
5 files changed, 262 insertions(+)
@@ -6068,6 +6068,56 @@ writes to the CNTVCT_EL0 and CNTPCT_EL0 registers using the SET_ONE_REG interface. No error will be returned, but the resulting offset will not be applied.+4.139 KVM_GET_SUPPORTED_MEMORY_ATTRIBUTES+-----------------------------------------++:Capability: KVM_CAP_MEMORY_ATTRIBUTES+:Architectures: x86+:Type: vm ioctl+:Parameters: u64 memory attributes bitmask(out)+:Returns: 0 on success, <0 on error++Returns supported memory attributes bitmask. Supported memory attributes will+have the corresponding bits set in u64 memory attributes bitmask.++The following memory attributes are defined::++ #define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)++4.140 KVM_SET_MEMORY_ATTRIBUTES+-----------------------------------------++:Capability: KVM_CAP_MEMORY_ATTRIBUTES+:Architectures: x86+:Type: vm ioctl+:Parameters: struct kvm_memory_attributes(in/out)+:Returns: 0 on success, <0 on error++Sets memory attributes for pages in a guest memory range. Parameters are+specified via the following structure::++ struct kvm_memory_attributes {+ __u64 address;+ __u64 size;+ __u64 attributes;+ __u64 flags;+ };++The user sets the per-page memory attributes to a guest memory range indicated+by address/size, and in return KVM adjusts address and size to reflect the+actual pages of the memory range have been successfully set to the attributes.+If the call returns 0, "address" is updated to the last successful address + 1+and "size" is updated to the remaining address size that has not been set+successfully. The user should check the return value as well as the size to+decide if the operation succeeded for the whole range or not. The user may want+to retry the operation with the returned address/size if the previous range was+partially successful.++Both address and size should be page aligned and the supported attributes can be+retrieved with KVM_GET_SUPPORTED_MEMORY_ATTRIBUTES.++The "flags" field may be used for future extensions and should be set to 0s.+5. The kvm_run structure ========================
@@ -8494,6 +8544,16 @@ block sizes is exposed in KVM_CAP_ARM_SUPPORTED_BLOCK_SIZES as a 64-bit bitmap (each bit describing a block size). The default value is 0, to disable the eager page splitting.+8.41 KVM_CAP_MEMORY_ATTRIBUTES+------------------------------++:Capability: KVM_CAP_MEMORY_ATTRIBUTES+:Architectures: x86+:Type: vm++This capability indicates KVM supports per-page memory attributes and ioctls+KVM_GET_SUPPORTED_MEMORY_ATTRIBUTES/KVM_SET_MEMORY_ATTRIBUTES are available.+9. Known KVM API problems =========================
@@ -2301,4 +2305,14 @@ static inline void kvm_account_pgtable_pages(void *virt, int nr)/* Max number of entries allowed for each kvm dirty ring */#define KVM_DIRTY_RING_MAX_ENTRIES 65536+#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES+staticinlineunsignedlongkvm_get_memory_attributes(structkvm*kvm,gfn_tgfn)+{+returnxa_to_value(xa_load(&kvm->mem_attr_array,gfn));+}++boolkvm_arch_post_set_memory_attributes(structkvm*kvm,+structkvm_gfn_range*range);+#endif /* CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES */+#endif
Why attributes of value 0 is considered not a value? Is it because 0 is
not a valid value when RWX is considered in the future?
+
+ mutex_lock(&kvm->slots_lock);
+
+ /*
+ * Reserve memory ahead of time to avoid having to deal with failures
+ * partway through setting the new attributes.
+ */
+ for (i = start; i < end; i++) {
+ r = xa_reserve(&kvm->mem_attr_array, i, GFP_KERNEL_ACCOUNT);
+ if (r)
+ goto out_unlock;
+ }
+
+ kvm_handle_gfn_range(kvm, &unmap_range);
+
+ for (i = start; i < end; i++) {
+ r = xa_err(xa_store(&kvm->mem_attr_array, i, entry,
+ GFP_KERNEL_ACCOUNT));
+ KVM_BUG_ON(r, kvm);
+ }
+
+ kvm_handle_gfn_range(kvm, &post_set_range);
+
+out_unlock:
+ mutex_unlock(&kvm->slots_lock);
+
+ return r;
+}
+static int kvm_vm_ioctl_set_mem_attributes(struct kvm *kvm,
+ struct kvm_memory_attributes *attrs)
+{
+ gfn_t start, end;
+
+ /* flags is currently not used. */
+ if (attrs->flags)
+ return -EINVAL;
+ if (attrs->attributes & ~kvm_supported_mem_attributes(kvm))
+ return -EINVAL;
+ if (attrs->size == 0 || attrs->address + attrs->size < attrs->address)
+ return -EINVAL;
+ if (!PAGE_ALIGNED(attrs->address) || !PAGE_ALIGNED(attrs->size))
+ return -EINVAL;
+
+ start = attrs->address >> PAGE_SHIFT;
+ end = (attrs->address + attrs->size - 1 + PAGE_SIZE) >> PAGE_SHIFT;
No need to handle the alignment again since both address and size are
page aligned.
quoted hunk
+
+ if (WARN_ON_ONCE(start == end))
+ return -EINVAL;
+
+ /*
+ * xarray tracks data using "unsigned long", and as a result so does
+ * KVM. For simplicity, supports generic attributes only on 64-bit
+ * architectures.
+ */
+ BUILD_BUG_ON(sizeof(attrs->attributes) != sizeof(unsigned long));
+
+ return kvm_vm_set_mem_attributes(kvm, attrs->attributes, start, end);
+}
+#endif /* CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES */
+
struct kvm_memory_slot *gfn_to_memslot(struct kvm *kvm, gfn_t gfn)
{
return __gfn_to_memslot(kvm_memslots(kvm), gfn);
@@ -4521,6 +4667,9 @@ static int kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) #ifdef CONFIG_HAVE_KVM_MSI case KVM_CAP_SIGNAL_MSI: #endif+#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES+ case KVM_CAP_MEMORY_ATTRIBUTES:+#endif #ifdef CONFIG_HAVE_KVM_IRQFD case KVM_CAP_IRQFD: #endif
@@ -4937,6 +5086,27 @@ static long kvm_vm_ioctl(struct file *filp, break; } #endif /* CONFIG_HAVE_KVM_IRQ_ROUTING */+#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES+ case KVM_GET_SUPPORTED_MEMORY_ATTRIBUTES: {+ u64 attrs = kvm_supported_mem_attributes(kvm);++ r = -EFAULT;+ if (copy_to_user(argp, &attrs, sizeof(attrs)))+ goto out;+ r = 0;+ break;+ }+ case KVM_SET_MEMORY_ATTRIBUTES: {+ struct kvm_memory_attributes attrs;++ r = -EFAULT;+ if (copy_from_user(&attrs, argp, sizeof(attrs)))+ goto out;++ r = kvm_vm_ioctl_set_mem_attributes(kvm, &attrs);+ break;
Both the changelog and the document added mention that the address and
size of attrs will be updated to
"reflect the actual pages of the memory range have been successfully set
to the attributes", but it doesn't.
Why attributes of value 0 is considered not a value? Is it because 0 is not
a valid value when RWX is considered in the future?
0 values don't require an entry in the xarray, i.e. don't need to be stored and
so don't consume memory. The potential conflict with a RWX=0 entry has already
been noted, but we'll cross that bridge when we get to it, e.g. KVM can easily
support RWX=0 by using an internal "valid" flag.
Both the changelog and the document added mention that the address and size
of attrs will be updated to
"reflect the actual pages of the memory range have been successfully set to
the attributes", but it doesn't.
Yeah, on the todo list, all of the changelogs are horribly stale.
+ return NULL;
+
+ /*
+ * Use the up-to-date flag to track whether or not the memory has been
+ * zeroed before being handed off to the guest. There is no backing
+ * storage for the memory, so the folio will remain up-to-date until
+ * it's removed.
+ *
+ * TODO: Skip clearing pages when trusted firmware will do it when
+ * assigning memory to the guest.
+ */
+ if (!folio_test_uptodate(folio)) {
+ unsigned long nr_pages = folio_nr_pages(folio);
+ unsigned long i;
+
+ for (i = 0; i < nr_pages; i++)
+ clear_highpage(folio_page(folio, i));
+
+ folio_mark_uptodate(folio);
+ }
+
+ /*
+ * Ignore accessed, referenced, and dirty flags. The memory is
+ * unevictable and there is no storage to write back to.
+ */
+ return folio;
+}
[...]
+
+static long kvm_gmem_allocate(struct inode *inode, loff_t offset, loff_t len)
+{
+ struct address_space *mapping = inode->i_mapping;
+ pgoff_t start, index, end;
+ int r;
+
+ /* Dedicated guest is immutable by default. */
+ if (offset + len > i_size_read(inode))
+ return -EINVAL;
+
+ filemap_invalidate_lock_shared(mapping);
+
+ start = offset >> PAGE_SHIFT;
+ end = (offset + len) >> PAGE_SHIFT;
+
+ r = 0;
+ for (index = start; index < end; ) {
+ struct folio *folio;
+
+ if (signal_pending(current)) {
+ r = -EINTR;
+ break;
+ }
+
+ folio = kvm_gmem_get_folio(inode, index);
+ if (!folio) {
+ r = -ENOMEM;
+ break;
+ }
+
+ index = folio_next_index(folio);
+
+ folio_unlock(folio);
+ folio_put(folio);
May be a dumb question, why we get the folio and then put it immediately?
Will it make the folio be released back to the page allocator?
+
+ /* 64-bit only, wrapping the index should be impossible. */
+ if (WARN_ON_ONCE(!index))
+ break;
+
+ cond_resched();
+ }
+
+ filemap_invalidate_unlock_shared(mapping);
+
+ return r;
+}
+
[...]
+
+int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
+ unsigned int fd, loff_t offset)
+{
+ loff_t size = slot->npages << PAGE_SHIFT;
+ unsigned long start, end, flags;
+ struct kvm_gmem *gmem;
+ struct inode *inode;
+ struct file *file;
+
+ BUILD_BUG_ON(sizeof(gfn_t) != sizeof(slot->gmem.pgoff));
+
+ file = fget(fd);
+ if (!file)
+ return -EINVAL;
+
+ if (file->f_op != &kvm_gmem_fops)
+ goto err;
+
+ gmem = file->private_data;
+ if (gmem->kvm != kvm)
+ goto err;
+
+ inode = file_inode(file);
+ flags = (unsigned long)inode->i_private;
+
+ /*
+ * For simplicity, require the offset into the file and the size of the
+ * memslot to be aligned to the largest possible page size used to back
+ * the file (same as the size of the file itself).
+ */
+ if (!kvm_gmem_is_valid_size(offset, flags) ||
+ !kvm_gmem_is_valid_size(size, flags))
+ goto err;
+
+ if (offset + size > i_size_read(inode))
+ goto err;
+
+ filemap_invalidate_lock(inode->i_mapping);
+
+ start = offset >> PAGE_SHIFT;
+ end = start + slot->npages;
+
+ if (!xa_empty(&gmem->bindings) &&
+ xa_find(&gmem->bindings, &start, end - 1, XA_PRESENT)) {
+ filemap_invalidate_unlock(inode->i_mapping);
+ goto err;
+ }
+
+ /*
+ * No synchronize_rcu() needed, any in-flight readers are guaranteed to
+ * be see either a NULL file or this new file, no need for them to go
+ * away.
+ */
+ rcu_assign_pointer(slot->gmem.file, file);
+ slot->gmem.pgoff = start;
+
+ xa_store_range(&gmem->bindings, start, end - 1, slot, GFP_KERNEL);
+ filemap_invalidate_unlock(inode->i_mapping);
+
+ /*
+ * Drop the reference to the file, even on success. The file pins KVM,
+ * not the other way 'round. Active bindings are invalidated if the
an extra ', or maybe around?
+ * file is closed before memslots are destroyed.
+ */
+ fput(file);
+ return 0;
+
+err:
+ fput(file);
+ return -EINVAL;
+}
+
@@ -1047,6 +1047,10 @@ isolate_migratepages_block(struct compact_control *cc, unsigned long low_pfn,if(!mapping&&(folio_ref_count(folio)-1)>folio_mapcount(folio))gotoisolate_fail_put;+/* The mapping truly isn't movable. */+if(mapping&&mapping_unmovable(mapping))+gotoisolate_fail_put;+
I doubt that it is safe to dereference mapping here. I believe the folio
can be truncated from under us and the mapping freed with the inode.
The folio has to be locked to dereference mapping safely (given that the
mapping is still tied to the folio).
There's even a comment to that effect later on in the function:
/*
* Only pages without mappings or that have a
* ->migrate_folio callback are possible to migrate
* without blocking. However, we can be racing with
* truncation so it's necessary to lock the page
* to stabilise the mapping as truncation holds
* the page lock until after the page is removed
* from the page cache.
*/
(that could be reworded to make it clear how dangerous dereferencing
->mapping is without the lock ... and it does need to be changed to say
"folio lock" instead of "page lock", so ...)
How does this look?
/*
* Only folios without mappings or that have
* a ->migrate_folio callback are possible to
* migrate without blocking. However, we can
* be racing with truncation, which can free
* the mapping. Truncation holds the folio lock
* until after the folio is removed from the page
* cache so holding it ourselves is sufficient.
*/
I think it should be always allowed. The outcome would just be "never have
a hugepage" if thp is not enabled in the kernel.
I don't have a strong preference. My thinking was that userspace would probably
rather have an explicit error, as opposed to silently running with a misconfigured
setup.
Considering that is how madvise(MADV_HUGEPAGE) behaves, your patch is
good. I disagree but consistency is better.
Paolo