[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH v3 11/18] x86/hvm: introduce per-vCPU mapcache for vCPU-PT domains



On Wed, Oct 07, 2026 at 11:40:44AM +0100, George Dunlap wrote:
> From: Roger Pau Monné <roger.pau@xxxxxxxxxx>
> 
> Add a struct mapcache to struct mapcache_vcpu for the per-vCPU
> accounting. Since MAPCACHE_VCPU_ENTRIES is 16, its inuse and garbage
> bitmaps are a word each: declare them inline in struct mapcache_vcpu,
> rather than mapping them in the per-domain area as the domain-wide
> bitmaps are.
> 
> A per-vCPU mapcache is only ever used by its own vCPU, and so by one
> pCPU at a time. We can therefore skip both the lock and the TLB-epoch
> tracking the domain-wide mapcache needs; a wrap still flushes the
> local TLB.
> 
> struct mapcache_vcpu grows from 136 to 176 bytes, and struct vcpu from
> 3008 to 3072 bytes (x86_64, debug build), within its page.
> 
> Unlike a domain-wide mapcache, a per-vCPU one cannot, by design,
> borrow slots from other vCPUs: MAPCACHE_VCPU_ENTRIES (16) is the most
> single-page mappings such a vCPU can hold at once, interrupt handlers'
> included, and one more hits the BUG_ON() in map_domain_page(). The PoD
> zero-page sweep held up to 16 on top of its callers' until "x86/PoD: map
> one page at a time in p2m_pod_zero_check()".

Is it really the best approach to keep the same limit?  As you note,
with the mapcache being per-vcpu the limit of 16 active mappings is
going to be strictly enforce, which it wasn't before.

The per-vcpu mapcache area is a whole L1 page-directory (it cannot be
smaller), so that's 512 entries per-vcpu, of which we limit ourselves
to 16.

Can we consider using bigger per-vcpu mapcaches, maybe 512 is overkill
for the bitmap sizes, but using greater than 16 will likely avoid
frequent TLB flushes to re-use the entries.  Maybe 64 entries, so the
bitmap is still a single unsigned long on 64bit arches.  Note that
declare bitmap will already use at least an unsigned long for the
bitmap itself.

> 
> mapcache_current_vcpu()'s lazy-context-switch handling
> (sync_local_execstate() plus a pgtable_vcpu re-read) now applies to
> any vCPU with a mapcache: when the idle vCPU runs lazily on an HVM
> vCPU's monitor table, the full switch happens before the idle context
> uses a mapcache.
> 
> In release builds map_domain_page() still tries the directmap first,
> for any vCPU; debug builds exercise the new mapcache.
> 
> Signed-off-by: Roger Pau Monné <roger.pau@xxxxxxxxxx>
> Assisted-by: Claude Code:claude-fable-5, Claude Code:claude-opus-4-8, Claude 
> Code:claude-opus-5-5
> Signed-off-by: George Dunlap <gwd@xxxxxxxxxxxxxx>
> ---
> Changes in v3:
> - HVM only, and per-vCPU from the start: the HVM enablement of "x86:
>   Initialize the mapcache for PV, HVM, and idle domains" (from the
>   directmap removal series) is folded in, without the idle domain and
>   without a domain-wide HVM mapcache.
> - Retitle (was: "x86/mm: introduce a per-vCPU mapcache when using ASI").
> - Say which second unmaps the not-present check in unmap_domain_page()
>   catches: those of an entry the first unmap zapped.
> - State the budget of single-page mappings a vCPU can hold at once.
> - The mapcache accounting refactor, the move of struct mapcache_vcpu
>   to struct arch_vcpu and the not-present check in unmap_domain_page()
>   are split out, for review, into the three patches before this one.
> ---
>  xen/arch/x86/domain_page.c        | 70 ++++++++++++++++++++++---------
>  xen/arch/x86/include/asm/domain.h |  6 +++
>  2 files changed, 57 insertions(+), 19 deletions(-)
> 
> diff --git a/xen/arch/x86/domain_page.c b/xen/arch/x86/domain_page.c
> index 708df90a00..0500bb9dba 100644
> --- a/xen/arch/x86/domain_page.c
> +++ b/xen/arch/x86/domain_page.c
> @@ -20,12 +20,13 @@
>  
>  /*
>   * Whether v has a mapcache: PV vCPUs (whose domain-wide mapcache may still 
> be
> - * disabled, see mapcache_domain_init()).  HVM vCPUs reach everything through
> - * the directmap, which covers all physical address space for them.
> + * disabled, see mapcache_domain_init()) and the vCPUs of an HVM domain using
> + * per-vCPU page-tables.  Other HVM vCPUs reach everything through the
> + * directmap, which covers all physical address space for them.
>   */
>  static bool has_mapcache(const struct vcpu *v)
>  {
> -    return is_pv_vcpu(v);
> +    return is_pv_vcpu(v) || v->domain->arch.vcpu_pt;
>  }
>  
>  static inline struct vcpu *mapcache_current_vcpu(void)
> @@ -38,10 +39,10 @@ static inline struct vcpu *mapcache_current_vcpu(void)
>          return NULL;
>  
>      /*
> -     * If we are in a lazy context-switch state from a PV vCPU do a full 
> switch
> -     * to the idle vCPU now, otherwise an incoming FLUSH_VCPU_STATE IPI would
> -     * change the page tables under our feet an invalidate any in-use 
> mapcache
> -     * entries.
> +     * If we are in a lazy context-switch state from a vCPU with a mapcache 
> do
> +     * a full switch to the idle vCPU now, otherwise an incoming
> +     * FLUSH_VCPU_STATE IPI would change the page tables under our feet an
> +     * invalidate any in-use mapcache entries.
>       */
>      if ( unlikely(this_cpu(curr_vcpu) != curr) )
>      {
> @@ -61,14 +62,21 @@ static inline struct vcpu *mapcache_current_vcpu(void)
>  }
>  
>  /*
> - * The accounting for v's mapcache: the domain's, with *dcache set so the
> - * caller takes the domain-wide lock and TLB-epoch machinery into account.
> + * The accounting for v's mapcache: its own for a vCPU-PT domain, the 
> domain's
> + * otherwise, in which case *dcache is set so the caller takes the 
> domain-wide
> + * lock and TLB-epoch machinery into account.
>   */
>  static struct mapcache *vcpu_mapcache(struct vcpu *v,
>                                        struct mapcache_domain **dcache)
>  {
>      struct domain *d = v->domain;
>  
> +    if ( d->arch.vcpu_pt )
> +    {
> +        *dcache = NULL;
> +        return &v->arch.mapcache.cache;
> +    }
> +
>      *dcache = &d->arch.pv.mapcache;
>      return &(*dcache)->cache;
>  }
> @@ -77,11 +85,15 @@ static struct mapcache *vcpu_mapcache(struct vcpu *v,
>   * The domain-wide mapcache's lock and TLB-epoch machinery, taken around
>   * allocating a slot: the domain's vCPUs share the mapcache's slots, and each
>   * catches up with a wrap another one caused, flushing its TLB if it has not
> - * been flushed since.
> + * been flushed since.  A per-vCPU mapcache (no dcache) needs neither: only 
> its
> + * own vCPU, and so one pCPU at a time, uses it.
>   */
>  static void mapcache_lock(struct mapcache_domain *dcache,
>                            struct mapcache_vcpu *vcache)
>  {
> +    if ( !dcache )
> +        return;
> +
>      spin_lock(&dcache->lock);
>  
>      /* Has some other CPU caused a wrap? We must flush if so. */
> @@ -100,13 +112,17 @@ static void mapcache_lock(struct mapcache_domain 
> *dcache,
>  static void mapcache_new_epoch(struct mapcache_domain *dcache,
>                                 struct mapcache_vcpu *vcache)
>  {
> +    if ( !dcache )
> +        return;
> +
>      vcache->shadow_epoch = ++dcache->epoch;
>      dcache->tlbflush_timestamp = tlbflush_current_time();
>  }
>  
>  static void mapcache_unlock(struct mapcache_domain *dcache)
>  {
> -    spin_unlock(&dcache->lock);
> +    if ( dcache )
> +        spin_unlock(&dcache->lock);
>  }
>  
>  #define mapcache_l2_entry(e) ((e) >> PAGETABLE_ORDER)
> @@ -191,7 +207,12 @@ void *map_domain_page(mfn_t mfn)
>          }
>          BUG_ON(idx >= cache->entries);
>  
> -        /* /Second/, flush TLBs. */
> +        /*
> +         * /Second/, flush TLBs.  A per-vCPU mapcache needs a local flush
> +         * only: its entries are never _PAGE_GLOBAL, and this vCPU's
> +         * page-tables can only have been loaded on another pCPU via
> +         * switch_cr3_cr4(), which flushes non-global mappings.
> +         */

Shouldn't the above comment be inside of mapcache_new_epoch()?  It's a
bit disconnected from the code here, as apparently there's no
different handling here between per-domain and per-vcpu mapcaches.

>          perfc_incr(domain_page_tlb_flush);
>          flush_tlb_local();
>          mapcache_new_epoch(dcache, vcache);
> @@ -313,11 +334,19 @@ int mapcache_vcpu_init(struct vcpu *v)
>          return 0;
>  
>      cache = vcpu_mapcache(v, &dcache);
> -    if ( !cache->inuse )
> +    if ( !dcache )
> +    {
> +        /* Per-vCPU bitmaps are a word each: they live in the vCPU itself. */
> +        cache->inuse = v->arch.mapcache.inuse;
> +        cache->garbage = v->arch.mapcache.garbage;

Well, the size of the bitmap is set to 16 bits, but due to the usage of
DECLARE_BITMAP() the underlying type is unsigned long, so it's
effectively using 64 bits (a quad).

Thanks, Roger.



 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.