[PATCH v15 10/10] x86/shadow: limit the number of pages which may be in use as shadows
Jan Beulich <[email protected]> Tue, 28 Jul 2026 16:24:47 +0200
| Newsgroups | gmane.comp.emulators.xen.devel |
|---|---|
| Message-ID | <[email protected]> |
In order to bound the amount of work a single invocation of shadow_unhook_mappings() may be doing all in one go, constrain the number of pages which may be in use as shadows. To achieve that, simply adjust the "success" exit condition of _shadow_prealloc(), thus forcing removal of shadows not only when we're short of memory. Note that, depending on workload, this may have a severe effect on performance, due to the potentially much larger rate of thrashed shadows. In the context of "x86/shadow: account for log-dirty mode when pre- allocating" it is relevant to note that we will be too strict in sh_prealloc_okay() when log-dirty mode is enabled: Only part of the pages considered are actually to become shadows. But I think accepting this is better than further complicating the logic. Requested-by: Roger Pau Monné <[email protected]> Signed-off-by: Jan Beulich <[email protected]> Acked-by: Tim Deegan <[email protected]> --- What exactly we want the upper bound to be is up for discussion. This may need to go together with an upper limit on the number of vCPU-s we deem supportable in a (shadow) guest. Really 32-bit HVM guests have 3 monitor tables. But I think that not accounting for that in sh_prealloc_okay() is acceptable. How to correctly do such accounting there would be unclear anyway, as we mean to only take domain properties into account, whereas mode dependent properties are per-vCPU. Backporting note: The placement of the setting of the new per-domain field relies on d->max_vcpus being set right at domain creation. Hence this will need to move elsewhere for 4.11 and older (perhaps into shadow_set_allocation()'s "if ( pages > 0 )" block, conditional upon the value still being zero and max_vcpus already set). Backporting note: 1d3668664df7 ("x86/shadow: restrict OOS allocation to when it's really needed") is a necessary prereq for the respective part of sh_prealloc_okay(). --- v14: Re-base over XSA-427 and new earlier patches. Restrict allowance for monitor tables to HVM. Restrict allowance for OOS to when that's actually in use. v13: Prevent underflow in sh_prealloc_okay(). Re-base. v12: Re-base past the XSA-410 series. v11: Account for monitor tables and OOS snapshots in sh_prealloc_okay(). Calculate the (default) maximum value once during domain initialization, into a new per-domain field. v10: Extend commit message. v9: New. --- a/xen/arch/x86/include/asm/domain.h +++ b/xen/arch/x86/include/asm/domain.h @@ -108,6 +108,9 @@ void init_hypercall_page(struct domain * struct shadow_domain { #ifdef CONFIG_SHADOW_PAGING unsigned int opt_flags; /* runtime tunable optimizations on/off */ + + unsigned int max_pages; /* limit on the number of shadows in use */ + struct page_list_head pinned_shadows; /* 1-to-1 map for use when HVM vcpus have paging disabled */ --- a/xen/arch/x86/mm/shadow/common.c +++ b/xen/arch/x86/mm/shadow/common.c @@ -96,6 +96,32 @@ int shadow_domain_init(struct domain *d) d->arch.paging.flush_tlb = shadow_flush_tlb; #endif + /* + * Figure out the default for the highest acceptable quantity of shadow + * memory. This is because we need to bound the amount of work potentially + * in need of doing by a single shadow_unhook_mappings() invocation. + */ + d->arch.paging.shadow.max_pages = 2048; + if ( d->max_vcpus > 8 ) + { + /* + * This is + * + * 128 * (max_vcpus + 8) + * max_vcpus * --------------------- + * max_vcpus + * + * suitably resolved, with the right side of the multiplication + * (when expressed as f(x)) satisfying + * f(8) = 256 + * lim f(x) = 128 + * x->∞ + * i.e. continuous with the simpler case above and converging to + * shadow_min_acceptable_pages() for large values. + */ + d->arch.paging.shadow.max_pages = 128 * (d->max_vcpus + 8); + } + return 0; } @@ -372,7 +398,7 @@ static inline void trace_shadow_prealloc } static bool sh_blow_tables(struct domain *d, unsigned int goal, - bool *preempted); + unsigned int type, bool *preempted); /* Make sure there are at least count pages of the order according to * type available in the shadow page pool. @@ -396,7 +422,7 @@ bool shadow_prealloc(struct domain *d, u ((SHF_L1_ANY | SHF_FL1_ANY) & (1u << type)) ) count += paging_logdirty_levels(); - ret = sh_blow_tables(d, count, NULL); + ret = sh_blow_tables(d, count, type, NULL); if ( !ret && (!d->is_shutting_down || d->shutdown_code != SHUTDOWN_crash) ) /* * Failing to allocate memory required for shadow usage can only result in @@ -408,6 +434,31 @@ bool shadow_prealloc(struct domain *d, u } /* + * Check that + * - there are enough free pages, + * - there aren't too many pages in use as shadows already when about to make + * a (set of) new shadow page(s). + */ +static bool sh_prealloc_okay(const struct domain *d, unsigned int goal, + unsigned int type) +{ + if ( d->arch.paging.free_pages < goal ) + return false; + + if ( type < SH_type_min_shadow || type > SH_type_max_shadow ) + return true; + + return d->arch.paging.total_pages - + (d->arch.paging.free_pages - goal) <= + d->arch.paging.shadow.max_pages + +#if (SHADOW_OPTIMIZATIONS & SHOPT_OUT_OF_SYNC) + (d->options & XEN_DOMCTL_CDF_oos_off ? 0 : SHADOW_OOS_PAGES) + +#endif + /* Allow for one monitor table per HVM vCPU. */ + paging_mode_external(d) * d->max_vcpus; +} + +/* * When @goal is zero: Deliberately free all the memory we can: This will * tear down all of this domain's shadows. * @@ -415,7 +466,7 @@ bool shadow_prealloc(struct domain *d, u * available in the shadow page pool. */ static bool sh_blow_tables(struct domain *d, unsigned int goal, - bool *preempted) + unsigned int type, bool *preempted) { struct page_info *sp, *t; struct vcpu *v; @@ -423,7 +474,7 @@ static bool sh_blow_tables(struct domain int i; unsigned int done = 0; - if ( goal && d->arch.paging.free_pages >= goal ) + if ( goal && sh_prealloc_okay(d, goal, type) ) return true; /* @@ -465,7 +516,7 @@ static bool sh_blow_tables(struct domain while ( hash_foreach(d, masks[i], callbacks, _mfn(0)) ) { /* See if that freed up enough space */ - if ( goal && d->arch.paging.free_pages >= goal ) + if ( goal && sh_prealloc_okay(d, goal, type) ) return true; if ( general_preempt_check() ) @@ -491,7 +542,7 @@ static bool sh_blow_tables(struct domain sh_unpin(d, smfn); /* See if that freed up enough space */ - if ( goal && d->arch.paging.free_pages >= goal ) + if ( goal && sh_prealloc_okay(d, goal, type) ) return true; if ( preempted && !(++done & 0xff) && general_preempt_check() ) @@ -519,7 +570,7 @@ static bool sh_blow_tables(struct domain 0); /* See if that freed up enough space */ - if ( goal && d->arch.paging.free_pages >= goal ) + if ( goal && sh_prealloc_okay(d, goal, type) ) { guest_flush_tlb_mask(d, d->dirty_cpumask); return true; @@ -569,7 +620,7 @@ static bool sh_blow_tables(struct domain * this domain's shadows */ void shadow_blow_tables(struct domain *d, bool *preempted) { - sh_blow_tables(d, 0, preempted); + sh_blow_tables(d, 0, SH_type_none, preempted); } void shadow_blow_tables_per_domain(struct domain *d) @@ -904,7 +955,7 @@ int shadow_set_allocation(struct domain else if ( d->arch.paging.total_pages > pages ) { /* Need to return memory to domheap */ - if ( !sh_blow_tables(d, 1, preempted) ) + if ( !sh_blow_tables(d, 1, SH_type_none, preempted) ) return preempted && *preempted ? 0 : -ENOMEM; sp = page_list_remove_head(&d->arch.paging.freelist);