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

[PATCH v15 04/10] x86/shadow: reduce amount of work to do by shadow_unhook_mappings()


  • To: "xen-devel@xxxxxxxxxxxxxxxxxxxx" <xen-devel@xxxxxxxxxxxxxxxxxxxx>
  • From: Jan Beulich <jbeulich@xxxxxxxx>
  • Date: Tue, 28 Jul 2026 16:21:45 +0200
  • Authentication-results: eu.smtp.expurgate.cloud; dkim=pass header.s=google header.d=suse.com header.i="@suse.com" header.h="Content-Transfer-Encoding:Content-Type:In-Reply-To:Autocrypt:Content-Language:References:Cc:To:From:Subject:User-Agent:MIME-Version:Date:Message-ID"
  • Autocrypt: addr=jbeulich@xxxxxxxx; keydata= xsDiBFk3nEQRBADAEaSw6zC/EJkiwGPXbWtPxl2xCdSoeepS07jW8UgcHNurfHvUzogEq5xk hu507c3BarVjyWCJOylMNR98Yd8VqD9UfmX0Hb8/BrA+Hl6/DB/eqGptrf4BSRwcZQM32aZK 7Pj2XbGWIUrZrd70x1eAP9QE3P79Y2oLrsCgbZJfEwCgvz9JjGmQqQkRiTVzlZVCJYcyGGsD /0tbFCzD2h20ahe8rC1gbb3K3qk+LpBtvjBu1RY9drYk0NymiGbJWZgab6t1jM7sk2vuf0Py O9Hf9XBmK0uE9IgMaiCpc32XV9oASz6UJebwkX+zF2jG5I1BfnO9g7KlotcA/v5ClMjgo6Gl MDY4HxoSRu3i1cqqSDtVlt+AOVBJBACrZcnHAUSuCXBPy0jOlBhxPqRWv6ND4c9PH1xjQ3NP nxJuMBS8rnNg22uyfAgmBKNLpLgAGVRMZGaGoJObGf72s6TeIqKJo/LtggAS9qAUiuKVnygo 3wjfkS9A3DRO+SpU7JqWdsveeIQyeyEJ/8PTowmSQLakF+3fote9ybzd880fSmFuIEJldWxp Y2ggPGpiZXVsaWNoQHN1c2UuY29tPsJgBBMRAgAgBQJZN5xEAhsDBgsJCAcDAgQVAggDBBYC AwECHgECF4AACgkQoDSui/t3IH4J+wCfQ5jHdEjCRHj23O/5ttg9r9OIruwAn3103WUITZee e7Sbg12UgcQ5lv7SzsFNBFk3nEQQCACCuTjCjFOUdi5Nm244F+78kLghRcin/awv+IrTcIWF hUpSs1Y91iQQ7KItirz5uwCPlwejSJDQJLIS+QtJHaXDXeV6NI0Uef1hP20+y8qydDiVkv6l IreXjTb7DvksRgJNvCkWtYnlS3mYvQ9NzS9PhyALWbXnH6sIJd2O9lKS1Mrfq+y0IXCP10eS FFGg+Av3IQeFatkJAyju0PPthyTqxSI4lZYuJVPknzgaeuJv/2NccrPvmeDg6Coe7ZIeQ8Yj t0ARxu2xytAkkLCel1Lz1WLmwLstV30g80nkgZf/wr+/BXJW/oIvRlonUkxv+IbBM3dX2OV8 AmRv1ySWPTP7AAMFB/9PQK/VtlNUJvg8GXj9ootzrteGfVZVVT4XBJkfwBcpC/XcPzldjv+3 HYudvpdNK3lLujXeA5fLOH+Z/G9WBc5pFVSMocI71I8bT8lIAzreg0WvkWg5V2WZsUMlnDL9 mpwIGFhlbM3gfDMs7MPMu8YQRFVdUvtSpaAs8OFfGQ0ia3LGZcjA6Ik2+xcqscEJzNH+qh8V m5jjp28yZgaqTaRbg3M/+MTbMpicpZuqF4rnB0AQD12/3BNWDR6bmh+EkYSMcEIpQmBM51qM EKYTQGybRCjpnKHGOxG0rfFY1085mBDZCH5Kx0cl0HVJuQKC+dV2ZY5AqjcKwAxpE75MLFkr wkkEGBECAAkFAlk3nEQCGwwACgkQoDSui/t3IH7nnwCfcJWUDUFKdCsBH/E5d+0ZnMQi+G0A nAuWpQkjM1ASeQwSHEeAWPgskBQL
  • Cc: Andrew Cooper <andrew.cooper3@xxxxxxxxxx>, Tim Deegan <tim@xxxxxxx>
  • Delivery-date: Tue, 28 Jul 2026 14:21:50 +0000
  • List-id: Xen developer discussion <xen-devel.lists.xenproject.org>

When preemption is enabled for uses of shadow_blow_tables(), the
granularity of preemption checks in both of the present two passes can
be pretty coarse, as deep table hierarchies may need processing. Reduce
shadow page table depth up front by introducing a third (earlier) pass
processing first L2, then L3 tables by doing hash lookups instead of
tree traversal.

Signed-off-by: Jan Beulich <jbeulich@xxxxxxxx>
---
The way need for preemption is being checked for is crude, but the least
intrusive variant I could think of while still respecting hash_foreach()
not permitting further traversal after a removal from the hash.

I've deliberately chosen 0 as the "wildcard", as using INVALID_MFN looks
somewhat more fragile/risky to me. Of course we might consider
introducing WILDCARD_MFN, which then could be non-zero but still
distinct from INVALID_MFN.

I've further deliberately made hash_foreach() return "int", not "bool",
since at least transiently I was also playing with returning -ERESTART
from some of the callback functions.

We could avoid the hash walk for guests which never entered 64-bit mode.
That would require tracking the maximum shadow paging level that was
ever used (perhaps since the last [completed] shadow_blow_tables()) by a
domain. (This would similarly apply to future 5-level support, where we
could avoid the SHF_L4_ANY walk for guests never having entered 5-level
mode.)

The same may want (need) adding to _shadow_prealloc() and perhaps
elsewhere. However, for zapping entries from alive domains hash lookup
pulling most recently used entries to the front means most recently used
entries would then also be zapped first, which isn't very nice.
---
v14: Exclude 32-bit L2 types. Re-base.
v13: New.

--- a/xen/arch/x86/mm/shadow/common.c
+++ b/xen/arch/x86/mm/shadow/common.c
@@ -268,6 +268,32 @@ sh_validate_guest_entry(struct vcpu *v,
     return result;
 }
 
+typedef int (*hash_callback_t)(struct domain *d, mfn_t smfn, mfn_t other_mfn);
+
+#define HASH_CALLBACKS_CHECK(mask) \
+    BUILD_BUG_ON((mask) > (1U << ARRAY_SIZE(callbacks)) - 1)
+
+static int hash_foreach(struct domain *d,
+                        unsigned int callback_mask,
+                        const hash_callback_t callbacks[],
+                        mfn_t callback_mfn);
+
+/*
+ * Dispatch table for getting per-type functions: each level must
+ * be called with the function to remove a lower-level shadow.
+ */
+static const hash_callback_t remove_callbacks[SH_type_unused] = {
+#ifdef CONFIG_HVM
+    [SH_type_l2_32_shadow] = SHADOW_INTERNAL_NAME(sh_remove_l1_shadow, 2),
+    [SH_type_l2_pae_shadow] = SHADOW_INTERNAL_NAME(sh_remove_l1_shadow, 3),
+#endif
+    [SH_type_l2_64_shadow] = SHADOW_INTERNAL_NAME(sh_remove_l1_shadow, 4),
+#ifdef CONFIG_PV32
+    [SH_type_l2h_64_shadow] = SHADOW_INTERNAL_NAME(sh_remove_l1_shadow, 4),
+#endif
+    [SH_type_l3_64_shadow] = SHADOW_INTERNAL_NAME(sh_remove_l2_shadow, 4),
+    [SH_type_l4_64_shadow] = SHADOW_INTERNAL_NAME(sh_remove_l3_shadow, 4),
+};
 
 /**************************************************************************/
 /* Memory management for shadow pages. */
@@ -476,7 +502,35 @@ void shadow_blow_tables(struct domain *d
     if ( !d->vcpu[0] )
         return;
 
-    /* Pass one: unpin all pinned pages */
+    /* First pass: reduce page table depth */
+    if ( preempted )
+    {
+#define callbacks remove_callbacks
+        static const unsigned int masks[] = {
+            SHF_L2_ANY & ~(SHF_L2_32 | SHF_L2_PAE),
+            SHF_L3_ANY,
+#if CONFIG_PAGING_LEVELS > 4
+            SHF_L4_ANY,
+#endif
+        };
+
+        HASH_CALLBACKS_CHECK(SHF_page_type_mask & ~(SHF_L1_ANY | SHF_FL1_ANY));
+
+        for ( i = 0; i < ARRAY_SIZE(masks); ++i )
+        {
+            while ( hash_foreach(d, masks[i], callbacks, _mfn(0)) )
+            {
+                if ( general_preempt_check() )
+                {
+                    *preempted = true;
+                    return;
+                }
+            }
+        }
+#undef callbacks
+    }
+
+    /* Second pass: unpin all pinned pages */
     foreach_pinned_shadow(d, sp, t)
     {
         smfn = page_to_mfn(sp);
@@ -488,7 +542,7 @@ void shadow_blow_tables(struct domain *d
         }
     }
 
-    /* Second pass: unhook entries of in-use shadows */
+    /* Third pass: unhook entries of in-use shadows */
     for_each_vcpu(d, v)
         for ( i = 0; i < ARRAY_SIZE(v->arch.paging.shadow.shadow_table); i++ )
             if ( !pagetable_is_null(v->arch.paging.shadow.shadow_table[i]) )
@@ -1151,15 +1205,10 @@ bool shadow_hash_delete(struct domain *d
     return true;
 }
 
-typedef int (*hash_callback_t)(struct domain *d, mfn_t smfn, mfn_t other_mfn);
-
-#define HASH_CALLBACKS_CHECK(mask) \
-    BUILD_BUG_ON((mask) > (1U << ARRAY_SIZE(callbacks)) - 1)
-
-static void hash_foreach(struct domain *d,
-                         unsigned int callback_mask,
-                         const hash_callback_t callbacks[],
-                         mfn_t callback_mfn)
+static int hash_foreach(struct domain *d,
+                        unsigned int callback_mask,
+                        const hash_callback_t callbacks[],
+                        mfn_t callback_mfn)
 /* Walk the hash table looking at the types of the entries and
  * calling the appropriate callback function for each entry.
  * The mask determines which shadow types we call back for, and the array
@@ -1176,7 +1225,7 @@ static void hash_foreach(struct domain *
 
     /* Can be called via p2m code &c after shadow teardown. */
     if ( unlikely(!d->arch.paging.shadow.hash_table) )
-        return;
+        return 0;
 
     /* Say we're here, to stop hash-lookups reordering the chains */
     ASSERT(d->arch.paging.shadow.hash_walking == 0);
@@ -1201,6 +1250,8 @@ static void hash_foreach(struct domain *
         if ( done ) break;
     }
     d->arch.paging.shadow.hash_walking = 0;
+
+    return done;
 }
 
 
@@ -1640,21 +1691,6 @@ void sh_remove_shadows(struct domain *d,
     mfn_t smfn;
     unsigned char t;
 
-    /* Dispatch table for getting per-type functions: each level must
-     * be called with the function to remove a lower-level shadow. */
-    static const hash_callback_t callbacks[SH_type_unused] = {
-#ifdef CONFIG_HVM
-        [SH_type_l2_32_shadow] = SHADOW_INTERNAL_NAME(sh_remove_l1_shadow, 2),
-        [SH_type_l2_pae_shadow] = SHADOW_INTERNAL_NAME(sh_remove_l1_shadow, 3),
-#endif
-        [SH_type_l2_64_shadow] = SHADOW_INTERNAL_NAME(sh_remove_l1_shadow, 4),
-#ifdef CONFIG_PV32
-        [SH_type_l2h_64_shadow] = SHADOW_INTERNAL_NAME(sh_remove_l1_shadow, 4),
-#endif
-        [SH_type_l3_64_shadow] = SHADOW_INTERNAL_NAME(sh_remove_l2_shadow, 4),
-        [SH_type_l4_64_shadow] = SHADOW_INTERNAL_NAME(sh_remove_l3_shadow, 4),
-    };
-
     /* Another lookup table, for choosing which mask to use */
     static const unsigned int masks[SH_type_unused] = {
 #ifdef CONFIG_HVM
@@ -1689,6 +1725,8 @@ void sh_remove_shadows(struct domain *d,
     /* Search for this shadow in all appropriate shadows */
     perfc_incr(shadow_unshadow);
 
+#define callbacks remove_callbacks
+
     /*
      * Lower-level shadows need to be excised from upper-level shadows. This
      * call to hash_foreach() looks dangerous but is in fact OK: each call
@@ -1736,6 +1774,7 @@ void sh_remove_shadows(struct domain *d,
     DO_UNSHADOW(SH_type_l1_64_shadow);
 
 #undef DO_UNSHADOW
+#undef callbacks
 
     /* If that didn't catch the shadows, something is wrong */
     if ( !fast && all && (pg->count_info & PGC_shadowed_pt) )
--- a/xen/arch/x86/mm/shadow/multi.c
+++ b/xen/arch/x86/mm/shadow/multi.c
@@ -3644,10 +3644,12 @@ int cf_check sh_remove_l1_shadow(struct
 
     FOREACH_PRESENT_L2E(sl2mfn, sl2e, NULL, done, d,
     {
-        if ( mfn_eq(shadow_l2e_get_mfn(*sl2e), sl1mfn) )
+        mfn_t mfn = shadow_l2e_get_mfn(*sl2e);
+
+        if ( !mfn_x(sl1mfn) || mfn_eq(mfn, sl1mfn) )
         {
             shadow_set_l2e(d, sl2e, shadow_l2e_empty(), sl2mfn);
-            if ( mfn_to_page(sl1mfn)->u.sh.type == 0 )
+            if ( !mfn_to_page(mfn)->u.sh.type )
                 /* This breaks us cleanly out of the FOREACH macro */
                 done = 1;
         }
@@ -3664,10 +3666,12 @@ int cf_check sh_remove_l2_shadow(struct
 
     FOREACH_PRESENT_L3E(sl3mfn, sl3e, NULL, done,
     {
-        if ( mfn_eq(shadow_l3e_get_mfn(*sl3e), sl2mfn) )
+        mfn_t mfn = shadow_l3e_get_mfn(*sl3e);
+
+        if ( !mfn_x(sl2mfn) || mfn_eq(mfn, sl2mfn) )
         {
             shadow_set_l3e(d, sl3e, shadow_l3e_empty(), sl3mfn);
-            if ( mfn_to_page(sl2mfn)->u.sh.type == 0 )
+            if ( !mfn_to_page(mfn)->u.sh.type )
                 /* This breaks us cleanly out of the FOREACH macro */
                 done = 1;
         }
@@ -3683,10 +3687,12 @@ int cf_check sh_remove_l3_shadow(struct
 
     FOREACH_PRESENT_L4E(sl4mfn, sl4e, NULL, done, d,
     {
-        if ( mfn_eq(shadow_l4e_get_mfn(*sl4e), sl3mfn) )
+        mfn_t mfn = shadow_l4e_get_mfn(*sl4e);
+
+        if ( !mfn_x(sl3mfn) || mfn_eq(mfn, sl3mfn) )
         {
             shadow_set_l4e(d, sl4e, shadow_l4e_empty(), sl4mfn);
-            if ( mfn_to_page(sl3mfn)->u.sh.type == 0 )
+            if ( !mfn_to_page(mfn)->u.sh.type )
                 /* This breaks us cleanly out of the FOREACH macro */
                 done = 1;
         }
--- a/xen/arch/x86/mm/shadow/private.h
+++ b/xen/arch/x86/mm/shadow/private.h
@@ -282,6 +282,9 @@ static inline void sh_terminate_list(str
 
 #define SHF_L1_ANY  (SHF_L1_32|SHF_L1_PAE|SHF_L1_64)
 #define SHF_FL1_ANY (SHF_FL1_32|SHF_FL1_PAE|SHF_FL1_64)
+#define SHF_L2_ANY  (SHF_L2_32|SHF_L2_PAE|SHF_L2H_64|SHF_L2_64)
+#define SHF_L3_ANY  SHF_L3_64
+#define SHF_L4_ANY  SHF_L4_64
 
 #if (SHADOW_OPTIMIZATIONS & SHOPT_OUT_OF_SYNC)
 /* Marks a guest L1 page table which is shadowed but not write-protected.




 


Rackspace

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