skip redundant TLB sync IPIs
Subsystems (11)
Lance Yang
reviewerPATCH v2 1/3 mm/tlb: allow architectures to skip redundant TLB sync IPIs
In reply to Lance YangFrom: Lance Yang <[email protected]>
When unsharing hugetlb PMD page tables, we currently send two IPIs: one for TLB invalidation, and another to synchronize with concurrent GUP-fast walkers.
However, if the TLB flush already reaches all CPUs, the second IPI is redundant. GUP-fast runs with IRQs disabled, so when the TLB flush IPI completes, any concurrent GUP-fast must have finished.
Add tlb_table_flush_implies_ipi_broadcast() to let architectures indicate their TLB flush provides full synchronization, enabling the redundant IPI to be skipped.
--- include/asm-generic/tlb.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
include/asm-generic/tlb.h +14 -0
@@ -261,6 +261,20 @@ static inline void tlb_remove_table_sync_one(void) { }#endif /* CONFIG_MMU_GATHER_RCU_TABLE_FREE */ +/* +* Architectures can override if their TLB flush already broadcasts IPIs to all +* CPUs when freeing or unsharing page tables. +* +* Return true only when the flush guarantees: +* - IPIs reach all CPUs with potentially stale paging-structure cache entries +* - Synchronization with IRQ-disabled code like GUP-fast +*/ +#ifndef tlb_table_flush_implies_ipi_broadcast +static inline bool tlb_table_flush_implies_ipi_broadcast (void ) +{ +return false; +} +#endif #ifndef CONFIG_MMU_GATHER_NO_GATHER /*
Signature · 1 line
2.49.0
Lance Yang
reviewerPATCH v2 2/3 x86/mm: implement redundant IPI elimination for page table operations
In reply to Lance YangFrom: Lance Yang <[email protected]>
Add a callback function flush_tlb_multi_implies_ipi_broadcast to pv_mmu_ops to explicitly track whether flush_tlb_multi IPIs provide sufficient synchronization for GUP-fast when freeing or unsharing page tables.
Pass both freed_tables and unshared_tables to flush_tlb_mm_range() to ensure lazy-TLB CPUs receive IPIs and flush their paging-structure caches:
flush_tlb_mm_range(..., freed_tables || unshared_tables);
--- arch/x86/include/asm/paravirt_types.h | 6 ++++++ arch/x86/include/asm/tlb.h | 19 ++++++++++++++++++- arch/x86/kernel/paravirt.c | 10 ++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-)
arch/x86/include/asm/paravirt_types.h +6 -0
@@ -133,6 +133,12 @@ struct pv_mmu_ops {void (*flush_tlb_multi)(const struct cpumask *cpus,const struct flush_tlb_info *info); +/* +* Indicates whether flush_tlb_multi IPIs provide sufficient +* synchronization for GUP-fast when freeing or unsharing page tables. +*/ +bool (*flush_tlb_multi_implies_ipi_broadcast)(void ); +/* Hook for intercepting the destruction of an mm_struct. */ void (*exit_mmap)(struct mm_struct *mm);void (*notify_page_enc_status_changed)(unsigned long pfn,int npages,bool enc);
arch/x86/include/asm/tlb.h +18 -1
@@ -5,10 +5,26 @@#define tlb_flush tlb_flushstatic inline void tlb_flush (struct mmu_gather *tlb); +#define tlb_table_flush_implies_ipi_broadcast tlb_table_flush_implies_ipi_broadcast +static inline bool tlb_table_flush_implies_ipi_broadcast (void ); +#include <asm-generic/tlb.h> #include <linux/kernel.h> #include <vdso/bits.h> #include <vdso/page.h> +#include <asm/paravirt.h> + +static inline bool tlb_table_flush_implies_ipi_broadcast (void ) +{ +#ifdef CONFIG_PARAVIRT +if (pv_ops.mmu.flush_tlb_multi_implies_ipi_broadcast) +return pv_ops.mmu.flush_tlb_multi_implies_ipi_broadcast (); + +return false; +#else +return !cpu_feature_enabled (X86_FEATURE_INVLPGB ); +#endif +}static inline void tlb_flush (struct mmu_gather *tlb) {
@@ -20,7 +36,8 @@ static inline void tlb_flush(struct mmu_gather *tlb) end = tlb->end; } -flush_tlb_mm_range (tlb->mm, start, end, stride_shift, tlb->freed_tables); +flush_tlb_mm_range (tlb->mm, start, end, stride_shift, + tlb->freed_tables || tlb->unshared_tables); }static inline void invlpg (unsigned long addr)
arch/x86/kernel/paravirt.c +10 -0
@@ -60,6 +60,15 @@ void __init native_pv_lock_init(void)static_branch_enable (&virt_spin_lock_key); } +static bool native_flush_tlb_multi_implies_ipi_broadcast (void ) +{ +/* Paravirt may use hypercalls that don't send real IPIs. */ +if (pv_ops.mmu.flush_tlb_multi != native_flush_tlb_multi) +return false; + +return !cpu_feature_enabled (X86_FEATURE_INVLPGB ); +} +struct static_key paravirt_steal_enabled;struct static_key paravirt_steal_rq_enabled;
@@ -173,6 +182,7 @@ struct paravirt_patch_template pv_ops = { .mmu.flush_tlb_kernel = native_flush_tlb_global, .mmu.flush_tlb_one_user = native_flush_tlb_one_user, .mmu.flush_tlb_multi = native_flush_tlb_multi, + .mmu.flush_tlb_multi_implies_ipi_broadcast = native_flush_tlb_multi_implies_ipi_broadcast, .mmu.exit_mmap = paravirt_nop, .mmu.notify_page_enc_status_changed = paravirt_nop,
Signature · 1 line
2.49.0
Lance Yang
reviewerPATCH v2 3/3 mm: embed TLB flush IPI check in tlb_remove_table_sync_one()
In reply to Lance YangFrom: Lance Yang <[email protected]>
Embed the tlb_table_flush_implies_ipi_broadcast() check directly inside tlb_remove_table_sync_one() instead of requiring every caller to check it explicitly. This relies on callers to do the right thing: flush with freed_tables=true or unshared_tables=true beforehand.
All existing callers satisfy this requirement:
1. mm/khugepaged.c:1188 (collapse_huge_page):
pmdp_collapse_flush(vma, address, pmd)
-> flush_tlb_range(vma, address, address + HPAGE_PMD_SIZE)
-> flush_tlb_mm_range(mm, ..., freed_tables = true)
-> flush_tlb_multi(mm_cpumask(mm), info) So freed_tables=true before calling tlb_remove_table_sync_one().
2. include/asm-generic/tlb.h:861 (tlb_flush_unshared_tables):
tlb_flush_mmu_tlbonly(tlb)
-> tlb_flush(tlb)
-> flush_tlb_mm_range(mm, ..., unshared_tables = true)
-> flush_tlb_multi(mm_cpumask(mm), info) unshared_tables=true (equivalent to freed_tables for sending IPIs).
3. mm/mmu_gather.c:341 (__tlb_remove_table_one):
When we can't allocate a batch page in tlb_remove_table(), we do:
tlb_table_invalidate(tlb)
-> tlb_flush_mmu_tlbonly(tlb)
-> flush_tlb_mm_range(mm, ..., freed_tables = true)
-> flush_tlb_multi(mm_cpumask(mm), info) Then:
tlb_remove_table_one(table)
-> __tlb_remove_table_one(table) // if !CONFIG_PT_RECLAIM
-> tlb_remove_table_sync_one()
freed_tables=true, and this should work too.
Why is tlb->freed_tables guaranteed? Because callers like pte_free_tlb() (via free_pte_range) set freed_tables=true before calling __pte_free_tlb(), which then calls tlb_remove_table(). We cannot free page tables without freed_tables=true.
Note that tlb_remove_table_sync_one() was a NOP on bare metal x86 (CONFIG_MMU_GATHER_RCU_TABLE_FREE=n) before commit a37259732a7d ("x86/mm: Make MMU_GATHER_RCU_TABLE_FREE unconditional").
4-5. mm/khugepaged.c:1683,1819 (pmdp_get_lockless_sync macro):
Same as #1. These also use pmdp_collapse_flush() beforehand.
--- mm/mmu_gather.c | 4 ++++ 1 file changed, 4 insertions(+)
mm/mmu_gather.c +4 -0
@@ -276,6 +276,10 @@ static void tlb_remove_table_smp_sync(void *arg)void tlb_remove_table_sync_one (void ) { +/* Skip the IPI if the TLB flush already synchronized with other CPUs. */ +if (tlb_table_flush_implies_ipi_broadcast ()) +return ; +/* * This isn't an RCU grace period and hence the page-tables cannot be * assumed to be actually RCU-freed.
Signature · 1 line
2.49.0
Lance Yang
reviewerRe: [PATCH v2 1/3] mm/tlb: allow architectures to skip redundant TLB sync IPIs
In reply to Lance YangConfusingly, I'm still unable to send the cover letter, but there's no error message. I'll post it here instead ...
Lance Yang
reviewerv2 cover skip redundant TLB sync IPIs
From: Lance Yang <[email protected]>
Hi all,
When unsharing hugetlb PMD page tables or collapsing pages in khugepaged, we send two IPIs: one for TLB invalidation, and another to synchronize with concurrent GUP-fast walkers. However, if the TLB flush already reaches all CPUs, the second IPI is redundant. GUP-fast runs with IRQs disabled, so when the TLB flush IPI completes, any concurrent GUP-fast must have finished.
This series introduces a way for architectures to indicate their TLB flush already provides full synchronization, allowing the redundant IPI to be skipped. For now, the optimization is implemented for x86 first and applied to all page table operations that free or unshare tables.
David Hildenbrand did the initial implementation. I built on his work and relied on off-list discussions to push it further - thanks a lot David!
v1 -> v2:
- Fix cover letter encoding to resolve send-email issues. Apologies for any email flood caused by the failed send attempts :(
RFC -> v1:
- Use a callback function in pv_mmu_ops instead of comparing function pointers (per David)
- Embed the check directly in tlb_remove_table_sync_one() instead of requiring every caller to check explicitly (per David)
- Move tlb_table_flush_implies_ipi_broadcast() outside of CONFIG_MMU_GATHER_RCU_TABLE_FREE to fix build error on architectures that don't enable this config.
https://lore.kernel.org/oe-kbuild-all/[email protected]/
- https://lore.kernel.org/linux-mm/[email protected]/
Lance Yang (3):
mm/tlb: allow architectures to skip redundant TLB sync IPIs x86/mm: implement redundant IPI elimination for page table operations
mm: embed TLB flush IPI check in tlb_remove_table_sync_one()
arch/x86/include/asm/paravirt_types.h | 6 ++++++ arch/x86/include/asm/tlb.h | 19 ++++++++++++++++++- arch/x86/kernel/paravirt.c | 10 ++++++++++ include/asm-generic/tlb.h | 14 ++++++++++++++ mm/mmu_gather.c | 4 ++++ 5 files changed, 52 insertions(+), 1 deletion(-)
Signature · 1 line
2.49.0
Lance Yang
reviewerv2 cover skip redundant TLB sync IPIs
From: Lance Yang <[email protected]>
Hi all,
When unsharing hugetlb PMD page tables or collapsing pages in khugepaged, we send two IPIs: one for TLB invalidation, and another to synchronize with concurrent GUP-fast walkers. However, if the TLB flush already reaches all CPUs, the second IPI is redundant. GUP-fast runs with IRQs disabled, so when the TLB flush IPI completes, any concurrent GUP-fast must have finished.
This series introduces a way for architectures to indicate their TLB flush already provides full synchronization, allowing the redundant IPI to be skipped. For now, the optimization is implemented for x86 first and applied to all page table operations that free or unshare tables.
David Hildenbrand did the initial implementation. I built on his work and relied on off-list discussions to push it further - thanks a lot David!
v1 -> v2:
- Fix cover letter encoding to resolve send-email issues. Apologies for any email flood caused by the failed send attempts :(
RFC -> v1:
- Use a callback function in pv_mmu_ops instead of comparing function pointers (per David)
- Embed the check directly in tlb_remove_table_sync_one() instead of requiring every caller to check explicitly (per David)
- Move tlb_table_flush_implies_ipi_broadcast() outside of CONFIG_MMU_GATHER_RCU_TABLE_FREE to fix build error on architectures that don't enable this config.
https://lore.kernel.org/oe-kbuild-all/[email protected]/
- https://lore.kernel.org/linux-mm/[email protected]/
Lance Yang (3):
mm/tlb: allow architectures to skip redundant TLB sync IPIs x86/mm: implement redundant IPI elimination for page table operations
mm: embed TLB flush IPI check in tlb_remove_table_sync_one()
arch/x86/include/asm/paravirt_types.h | 6 ++++++ arch/x86/include/asm/tlb.h | 19 ++++++++++++++++++- arch/x86/kernel/paravirt.c | 10 ++++++++++ include/asm-generic/tlb.h | 14 ++++++++++++++ mm/mmu_gather.c | 4 ++++ 5 files changed, 52 insertions(+), 1 deletion(-)
Signature · 1 line
2.49.0
David Hildenbrand (Red Hat)
maintainerRe: [PATCH v2 1/3] mm/tlb: allow architectures to skip redundant TLB sync IPIs
In reply to Lance YangQuoted 35 lines · Lance Yang
From: Lance Yang <[email protected]>
When unsharing hugetlb PMD page tables, we currently send two IPIs: one for TLB invalidation, and another to synchronize with concurrent GUP-fast walkers.
However, if the TLB flush already reaches all CPUs, the second IPI is redundant. GUP-fast runs with IRQs disabled, so when the TLB flush IPI completes, any concurrent GUP-fast must have finished.
Add
tlb_table_flush_implies_ipi_broadcast()to let architectures indicate their TLB flush provides full synchronization, enabling the redundant IPI to be skipped.Suggested-by David Hildenbrand (Red Hat) <[email protected]>Signed-off-by Lance Yang <[email protected]>--- include/asm-generic/tlb.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+)include/asm-generic/tlb.h +7
-0@@ -261,6 +261,20 @@ static inline void tlb_remove_table_sync_one(void) { }#endif /* CONFIG_MMU_GATHER_RCU_TABLE_FREE */ +/* +* Architectures can override if their TLB flush already broadcasts IPIs to all +* CPUs when freeing or unsharing page tables. +* +* Return true only when the flush guarantees: +* - IPIs reach all CPUs with potentially stale paging-structure cache entries +* - Synchronization with IRQ-disabled code like GUP-fast
include/asm-generic/tlb.h static inline void tlb_remove_table_sync_one(void) { } · line 280
Quoted 10 lines
include/asm-generic/tlb.h +7
-0+*/ +#ifndef tlb_table_flush_implies_ipi_broadcast +static inline bool tlb_table_flush_implies_ipi_broadcast (void ) +{ +return false; +} +#endif #ifndef CONFIG_MMU_GATHER_NO_GATHER /*
This should likely get squashed into patch #3. Patch #1 itself does not add a lot of value to be had separately.
So best to squash both and have them as #1, to then implement it in #2 for x86.
Signature · 3 lines
Cheers David
David Hildenbrand (Red Hat)
maintainerRe: [PATCH v2 3/3] mm: embed TLB flush IPI check in tlb_remove_table_sync_one()
In reply to Lance YangQuoted 52 lines · Lance Yang
From: Lance Yang <[email protected]>
Embed the
tlb_table_flush_implies_ipi_broadcast()check directly insidetlb_remove_table_sync_one()instead of requiring every caller to check it explicitly. This relies on callers to do the right thing: flush with freed_tables=true or unshared_tables=true beforehand.All existing callers satisfy this requirement:
1.
mm/khugepaged.c:1188(collapse_huge_page):pmdp_collapse_flush(vma, address, pmd) -> flush_tlb_range(vma, address, address + HPAGE_PMD_SIZE) -> flush_tlb_mm_range(mm, ..., freed_tables = true) -> flush_tlb_multi(mm_cpumask(mm), info) So freed_tables=true before calling tlb_remove_table_sync_one().2.
include/asm-generic/tlb.h:861(tlb_flush_unshared_tables):tlb_flush_mmu_tlbonly(tlb) -> tlb_flush(tlb) -> flush_tlb_mm_range(mm, ..., unshared_tables = true) -> flush_tlb_multi(mm_cpumask(mm), info) unshared_tables=true (equivalent to freed_tables for sending IPIs).3.
mm/mmu_gather.c:341(__tlb_remove_table_one):When we can't allocate a batch page in
tlb_remove_table(), we do:
tlb_table_invalidate(tlb)
->tlb_flush_mmu_tlbonly(tlb)
->flush_tlb_mm_range(mm, ..., freed_tables = true)
->flush_tlb_multi(mm_cpumask(mm), info)Then:
tlb_remove_table_one(table)
->__tlb_remove_table_one(table)// if !CONFIG_PT_RECLAIM
->tlb_remove_table_sync_one()freed_tables=true, and this should work too.
Why is
tlb->freed_tablesguaranteed? Because callers likepte_free_tlb()(viafree_pte_range) set freed_tables=true before calling__pte_free_tlb(), which then callstlb_remove_table(). We cannot free page tables without freed_tables=true.Note that
tlb_remove_table_sync_one()was a NOP on bare metal x86 (CONFIG_MMU_GATHER_RCU_TABLE_FREE=n) before commita37259732a7d("x86/mm: MakeMMU_GATHER_RCU_TABLE_FREEunconditional").
Quoted 6 lines
4-5. mm/khugepaged.c:1683,1819 (
pmdp_get_lockless_syncmacro):Same as #1. These also use
pmdp_collapse_flush()beforehand.Suggested-by David Hildenbrand (Red Hat) <[email protected]>Signed-off-by Lance Yang <[email protected]>
LGTM. I think we should document that somewhere. Can we add some kerneldoc for tlb_remove_table_sync_one() where we document that it doesn't to any sync if a previous TLB flush when removing/unsharing page tables would have already performed an IPI?
Quoted 19 lines
--- mm/mmu_gather.c | 4 ++++ 1 file changed, 4 insertions(+)mm/mmu_gather.c +4
-0@@ -276,6 +276,10 @@ static void tlb_remove_table_smp_sync(void *arg)void tlb_remove_table_sync_one (void ) { +/* Skip the IPI if the TLB flush already synchronized with other CPUs. */ +if (tlb_table_flush_implies_ipi_broadcast ()) +return ; +/* * This isn't an RCU grace period and hence the page-tables cannot be * assumed to be actually RCU-freed.
Signature · 3 lines
Cheers David
Lance Yang
reviewerRe: [PATCH v2 1/3] mm/tlb: allow architectures to skip redundant TLB sync IPIs
In reply to David Hildenbrand (Red Hat)Quoted 50 lines · David Hildenbrand (Red Hat)
Quoted 43 lines · Lance Yang
From: Lance Yang <[email protected]>
When unsharing hugetlb PMD page tables, we currently send two IPIs: one for TLB invalidation, and another to synchronize with concurrent GUP-fast walkers.
However, if the TLB flush already reaches all CPUs, the second IPI is redundant. GUP-fast runs with IRQs disabled, so when the TLB flush IPI completes, any concurrent GUP-fast must have finished.
Add
tlb_table_flush_implies_ipi_broadcast()to let architectures indicate their TLB flush provides full synchronization, enabling the redundant IPI to be skipped.Suggested-by David Hildenbrand (Red Hat) <[email protected]>Signed-off-by Lance Yang <[email protected]>---
include/asm-generic/tlb.h| 14 ++++++++++++++1 file changed, 14 insertions(+)include/asm-generic/tlb.h +2
-0@@ -261,6 +261,20 @@ static inline voidtlb_remove_table_sync_one (void) { } #endif /* CONFIG_MMU_GATHER_RCU_TABLE_FREE */ +/* +* Architectures can override if their TLB flush already broadcasts IPIs to all
+ * CPUs when freeing or unsharing page tables.
+ *
+ * Return true only when the flush guarantees:
+ * - IPIs reach all CPUs with potentially stale paging-structure cache entries
+ * - Synchronization with IRQ-disabled code like GUP-fast
+ */include/asm-generic/tlb.h +6
-0+#ifndef tlb_table_flush_implies_ipi_broadcast +static inline bool tlb_table_flush_implies_ipi_broadcast (void ) +{ + return false; +} +#endif Quoted 2 lines
#ifndef
CONFIG_MMU_GATHER_NO_GATHER
/*This should likely get squashed into patch #3. Patch #1 itself does not add a lot of value to be had separately.
Quoted 1 line
So best to squash both and have them as #1, to then implement it in #2 for x86.
Sounds good, will do! Squashing #1 and #3 together, keeping the x86 implementation as #2 ;)
Cheers,
Lance
Lance Yang
reviewerRe: [PATCH v2 3/3] mm: embed TLB flush IPI check in tlb_remove_table_sync_one()
In reply to David Hildenbrand (Red Hat)Quoted 53 lines · David Hildenbrand (Red Hat)
Quoted 52 lines · Lance Yang
From: Lance Yang <[email protected]>
Embed the
tlb_table_flush_implies_ipi_broadcast()check directly insidetlb_remove_table_sync_one()instead of requiring every caller to check it explicitly. This relies on callers to do the right thing: flush with freed_tables=true or unshared_tables=true beforehand.All existing callers satisfy this requirement:
1.
mm/khugepaged.c:1188(collapse_huge_page):
pmdp_collapse_flush(vma, address, pmd)
->flush_tlb_range(vma, address, address + HPAGE_PMD_SIZE)->flush_tlb_mm_range(mm, ..., freed_tables = true)->flush_tlb_multi(mm_cpumask(mm), info)So freed_tables=true before calling
tlb_remove_table_sync_one().2.
include/asm-generic/tlb.h:861(tlb_flush_unshared_tables):
tlb_flush_mmu_tlbonly(tlb)
->tlb_flush(tlb)
->flush_tlb_mm_range(mm, ..., unshared_tables = true)->flush_tlb_multi(mm_cpumask(mm), info)unshared_tables=true (equivalent to
freed_tablesfor sending IPIs).3.
mm/mmu_gather.c:341(__tlb_remove_table_one):When we can't allocate a batch page in
tlb_remove_table(), we do:
tlb_table_invalidate(tlb)
->tlb_flush_mmu_tlbonly(tlb)
->flush_tlb_mm_range(mm, ..., freed_tables = true)->flush_tlb_multi(mm_cpumask(mm), info)Then:
tlb_remove_table_one(table)
->__tlb_remove_table_one(table)// if !CONFIG_PT_RECLAIM ->tlb_remove_table_sync_one()freed_tables=true, and this should work too.
Why is
tlb->freed_tablesguaranteed? Because callers likepte_free_tlb()(viafree_pte_range) set freed_tables=true before calling__pte_free_tlb(), which then callstlb_remove_table(). We cannot free page tables without freed_tables=true.Note that
tlb_remove_table_sync_one()was a NOP on bare metal x86 (CONFIG_MMU_GATHER_RCU_TABLE_FREE=n) before commita37259732a7d("x86/mm: MakeMMU_GATHER_RCU_TABLE_FREEunconditional").
Quoted 8 lines
Quoted 6 lines
4-5. mm/khugepaged.c:1683,1819 (
pmdp_get_lockless_syncmacro):Same as #1. These also use
pmdp_collapse_flush()beforehand.Suggested-by David Hildenbrand (Red Hat) <[email protected]>Signed-off-by Lance Yang <[email protected]>LGTM. I think we should document that somewhere. Can we add some
Thanks!
Quoted 1 line
kerneldoc for
tlb_remove_table_sync_one()where we document that it doesn't to any sync if a previous TLB flush when removing/unsharing page tables would have already performed an IPI?
Fair point. Would something like this work?
---8<---
mm/mmu_gather.c +14 -0
@@ -274,6 +274,20 @@ static void tlb_remove_table_smp_sync(void *arg)/* Simply deliver the interrupt */ } +/** +* tlb_remove_table_sync_one - Send IPI to synchronize page table operations +* +* Sends an IPI to all CPUs to synchronize when freeing or unsharing page +* tables (e.g., to ensure concurrent GUP-fast walkers have completed). +* +* If a previous TLB flush (when removing/unsharing page tables) already +* broadcast IPIs to all CPUs, the redundant IPI is skipped. The optimization +* relies on architectures implementing tlb_table_flush_implies_ipi_broadcast() +* to indicate when their TLB flush provides sufficient synchronization. +* +* Note that callers must ensure that a TLB flush with freed_tables=true or +* unshared_tables=true has been performed before calling. +*/ void tlb_remove_table_sync_one (void ) {/* Skip the IPI if the TLB flush already synchronized with other CPUs. */
---
Cheers,
Lance
Quoted 22 lines
Quoted 20 lines
---
mm/mmu_gather.c| 4 ++++1 file changed, 4 insertions(+)mm/mmu_gather.c +1
-0@@ -276,6 +276,10 @@ static void tlb_remove_table_smp_sync(void *arg) void tlb_remove_table_sync_one(void) { + /* Skip the IPI if the TLB flush already synchronized with otherCPUs. */
+ if (tlb_table_flush_implies_ipi_broadcast())
+ return;
+
/*
* This isn't an RCU grace period and hence the page-tables cannot be
* assumed to be actually RCU-freed.
On 12/29/25 06:52, Lance Yang wrote:
...
Quoted 4 lines
This series introduces a way for architectures to indicate their TLB flush already provides full synchronization, allowing the redundant IPI to be skipped. For now, the optimization is implemented for x86 first and applied to all page table operations that free or unshare tables.
I really don't like all the complexity here. Even on x86, there are three or more ways of deriving this. Having the pv_ops check the value of another pv op is also a bit unsettling.
That said, complexity can be worth it with sufficient demonstrated gains. But:
Quoted 3 lines
When unsharing hugetlb PMD page tables or collapsing pages in khugepaged, we send two IPIs: one for TLB invalidation, and another to synchronize with concurrent GUP-fast walkers.
Those aren't exactly hot paths. khugepaged is fundamentally rate limited. I don't think unsharing hugetlb PMD page tables just is all that common either.
What kind of end user benefit is there to justify the complexity?
David Hildenbrand (Red Hat)
maintainerQuoted 10 lines · Dave Hansen
On 12/29/25 06:52, Lance Yang wrote:
...Quoted 4 lines
This series introduces a way for architectures to indicate their TLB flush already provides full synchronization, allowing the redundant IPI to be skipped. For now, the optimization is implemented for x86 first and applied to all page table operations that free or unshare tables.
I really don't like all the complexity here. Even on x86, there are three or more ways of deriving this. Having the
pv_opscheck the value of another pv op is also a bit unsettling.
Right. What I actually meant is that we simply have a property "bool flush_tlb_multi_implies_ipi_broadcast" that we set only to true from the initialization code.
Without comparing the pv_ops.
That should reduce the complexity quite a bit IMHO.
But maybe you have an even better way on how to indicate support, in a very simple way.
Quoted 11 lines
That said, complexity can be worth it with sufficient demonstrated gains. But:
Quoted 3 lines
When unsharing hugetlb PMD page tables or collapsing pages in khugepaged, we send two IPIs: one for TLB invalidation, and another to synchronize with concurrent GUP-fast walkers.
Those aren't exactly hot paths. khugepaged is fundamentally rate limited. I don't think unsharing hugetlb PMD page tables just is all that common either.
Given that the added IPIs during unsharing broke Oracle DBs rather badly [1], I think this is actually a case worth optimizing.
I'd assume that the impact can be measured on a many-core/many-socket system with an adjusted reproducer of [1]. The impact will not be as big as what [1] fixed (we reduced the tlb_remove_table_sync_one() invocations quite drastically).
After all, tlb_remove_table_sync_one() sends an IPI to *all* CPUs in the system, not just the ones in the MM CPU mask, which is rather bad on systems with a lot of CPUs. Of course, this way we can only optimize on systems that actually send IPIs during TLB flushes.
For other systems, it will be more tricky to avoid these broadcast IPIs.
(I have the faint recollection that the IPI broadcast through tlb_remove_table_sync_one() is a problem when called from __tlb_remove_table_one() on RT systems ...)
[1] https://lkml.kernel.org/r/[email protected]
Signature · 3 lines
Cheers David
Quoted 14 lines · David Hildenbrand (Red Hat)
Quoted 9 lines · Dave Hansen
On 12/29/25 06:52, Lance Yang wrote:
...Quoted 6 lines
This series introduces a way for architectures to indicate their TLB flush
already provides full synchronization, allowing the redundant IPI to be skipped. For now, the optimization is implemented for x86 first and applied
to all page table operations that free or unshare tables.Quoted 3 lines
I really don't like all the complexity here. Even on x86, there are three or more ways of deriving this. Having the
pv_opscheck the value of another pv op is also a bit unsettling.
Quoted 7 lines
Right. What I actually meant is that we simply have a property "bool
flush_tlb_multi_implies_ipi_broadcast" that we set only to true from the initialization code.Without comparing the
pv_ops.That should reduce the complexity quite a bit IMHO.
Yeah, that sounds promising.
Quoted 2 lines
But maybe you have an even better way on how to indicate support, in a very simple way.
Rather than having some kind of explicit support enumeration, the other idea I had would be to actually track the state about what needs to get flushed somewhere. For instance, even CPUs with enabled INVLPGB support still use IPIs sometimes. That makes the tlb_table_flush_implies_ipi_broadcast() check a bit imperfect as is because it will for the extra sync IPI even when INVLPGB isn't being used for an mm.
First, we already save some semblance of support for doing different flushes when freeing page tables mmu_gather->freed_tables. But, the call sites in question here are for a single flush and don't use mmu_gathers.
The other pretty straightforward thing to do would be to add something to mm->context that indicates that page tables need to be freed but there might still be wild gup walkers out there that need an IPI. It would get set when the page tables are modified and cleared at all the sites where an IPIs are sent.
Quoted 14 lines
Quoted 11 lines
That said, complexity can be worth it with sufficient demonstrated gains. But:
Quoted 4 lines
When unsharing hugetlb PMD page tables or collapsing pages in khugepaged,
we send two IPIs: one for TLB invalidation, and another to synchronize with concurrent GUP-fast walkers.Those aren't exactly hot paths. khugepaged is fundamentally rate limited. I don't think unsharing hugetlb PMD page tables just is all that common either.
Given that the added IPIs during unsharing broke Oracle DBs rather badly [1], I think this is actually a case worth optimizing.
...
Quoted 1 line
Gah, that's good context, thanks.
Are there any tests out there that might catch these this case better? It might be something good to have 0day watch for.
Lance Yang
reviewerQuoted 19 lines · Dave Hansen
Quoted 18 lines · David Hildenbrand (Red Hat)
Quoted 9 lines · Dave Hansen
On 12/29/25 06:52, Lance Yang wrote:
...Quoted 6 lines
This series introduces a way for architectures to indicate their TLB flush
already provides full synchronization, allowing the redundant IPI to be skipped. For now, the optimization is implemented for x86 first and applied
to all page table operations that free or unshare tables.Quoted 3 lines
I really don't like all the complexity here. Even on x86, there are three or more ways of deriving this. Having the
pv_opscheck the value of another pv op is also a bit unsettling.Right. What I actually meant is that we simply have a property "bool
flush_tlb_multi_implies_ipi_broadcast" that we set only to true from the initialization code.
Quoted 5 lines
Quoted 3 lines
Without comparing the
pv_ops.That should reduce the complexity quite a bit IMHO.
Yeah, that sounds promising.
Thanks a lot for taking the time to review!
Yeah, I simplified things to just a bool property set during init (no pv_ops comparison at runtime) as follows:
---8<---
arch/x86/include/asm/paravirt.h +5 -0
@@ -698,6 +698,7 @@ static __always_inline unsigned long arch_local_irq_save(void)extern void default_banner (void );void native_pv_lock_init (void )__init ; +void setup_pv_tlb_flush_ipi_broadcast (void )__init ;#else /* __ASSEMBLER__ */
@@ -727,6 +728,10 @@ void native_pv_lock_init(void) __init;static inline void native_pv_lock_init (void ) { } + +static inline void setup_pv_tlb_flush_ipi_broadcast (void ) +{ +}#endif #endif /* !CONFIG_PARAVIRT */
arch/x86/include/asm/paravirt_types.h +6 -0
@@ -133,6 +133,12 @@ struct pv_mmu_ops {void (*flush_tlb_multi)(const struct cpumask *cpus,const struct flush_tlb_info *info); +/* +* Indicates whether flush_tlb_multi IPIs provide sufficient +* synchronization for GUP-fast when freeing or unsharing page tables. +*/ +bool flush_tlb_multi_implies_ipi_broadcast; +/* Hook for intercepting the destruction of an mm_struct. */ void (*exit_mmap)(struct mm_struct *mm); void (*notify_page_enc_status_changed)(unsigned long pfn, int npages,
bool enc);
arch/x86/include/asm/tlb.h +15 -1
@@ -5,10 +5,23 @@#define tlb_flush tlb_flushstatic inline void tlb_flush (struct mmu_gather *tlb); +#define tlb_table_flush_implies_ipi_broadcast tlb_table_flush_implies_ipi_broadcast +static inline bool tlb_table_flush_implies_ipi_broadcast (void ); +#include <asm-generic/tlb.h> #include <linux/kernel.h> #include <vdso/bits.h> #include <vdso/page.h> +#include <asm/paravirt.h> + +static inline bool tlb_table_flush_implies_ipi_broadcast (void ) +{ +#ifdef CONFIG_PARAVIRT +return pv_ops.mmu.flush_tlb_multi_implies_ipi_broadcast; +#else +return !cpu_feature_enabled (X86_FEATURE_INVLPGB ); +#endif +}static inline void tlb_flush (struct mmu_gather *tlb) {
@@ -20,7 +33,8 @@ static inline void tlb_flush(struct mmu_gather *tlb) end = tlb->end; } -flush_tlb_mm_range (tlb->mm, start, end, stride_shift, tlb->freed_tables); +flush_tlb_mm_range (tlb->mm, start, end, stride_shift, + tlb->freed_tables || tlb->unshared_tables); }static inline void invlpg (unsigned long addr)
arch/x86/kernel/paravirt.c +18 -0
@@ -60,6 +60,23 @@ void __init native_pv_lock_init(void)static_branch_enable (&virt_spin_lock_key); } +void __init setup_pv_tlb_flush_ipi_broadcast (void ) +{ +/* +* For native TLB flush, if we don't have INVLPGB, we use IPI-based +* flushing which sends real IPIs to all CPUs. This provides sufficient +* synchronization for GUP-fast. +* +* For paravirt (e.g., KVM, Xen, HyperV), hypercalls may not send real +* IPIs, so we keep the default value of false. Only set to true when +* using native flush_tlb_multi without INVLPGB. +*/ +if (pv_ops.mmu.flush_tlb_multi == native_flush_tlb_multi && + !cpu_feature_enabled (X86_FEATURE_INVLPGB )) + pv_ops.mmu.flush_tlb_multi_implies_ipi_broadcast = true; +} + +struct static_key paravirt_steal_enabled;struct static_key paravirt_steal_rq_enabled;
@@ -173,6 +190,7 @@ struct paravirt_patch_template pv_ops = { .mmu.flush_tlb_kernel = native_flush_tlb_global, .mmu.flush_tlb_one_user = native_flush_tlb_one_user, .mmu.flush_tlb_multi = native_flush_tlb_multi, + .mmu.flush_tlb_multi_implies_ipi_broadcast = false, .mmu.exit_mmap = paravirt_nop, .mmu.notify_page_enc_status_changed = paravirt_nop,
arch/x86/kernel/setup.c +2 -0
@@ -51,6 +51,7 @@#include <asm/olpc_ofw.h> #include <asm/pci-direct.h> #include <asm/prom.h> +#include <asm/paravirt.h> #include <asm/proto.h> #include <asm/realmode.h> #include <asm/thermal.h>
@@ -1257,6 +1258,7 @@ void __init setup_arch(char **cmdline_p)io_apic_init_mappings (); x86_init.hyper.guest_late_init (); +setup_pv_tlb_flush_ipi_broadcast ();e820__reserve_resources ();e820__register_nosave_regions (max_pfn);
---
Quoted 16 lines
Quoted 3 lines
But maybe you have an even better way on how to indicate support, in a very simple way.
Rather than having some kind of explicit support enumeration, the other idea I had would be to actually track the state about what needs to get flushed somewhere. For instance, even CPUs with enabled INVLPGB support still use IPIs sometimes. That makes the
tlb_table_flush_implies_ipi_broadcast()check a bit imperfect as is because it will for the extra sync IPI even when INVLPGB isn't being used for an mm.First, we already save some semblance of support for doing different flushes when freeing page tables
mmu_gather->freed_tables. But, the call sites in question here are for a single flush and don't usemmu_gathers.
Quoted 5 lines
The other pretty straightforward thing to do would be to add something to
mm->contextthat indicates that page tables need to be freed but there might still be wild gup walkers out there that need an IPI. It would get set when the page tables are modified and cleared at all the sites where an IPIs are sent.
Thanks for the suggestion! The mm->context tracking idea makes a lot of sense - it would handle those mixed INVLPGB/IPI cases much better :)
Maybe we could do that as a follow-up. I'd like to keep things simple for now, so we just add a bool property to skip redundant TLB sync IPIs on systems without INVLPGB support.
Then we could add the mm->context (or something similar) tracking later to handle things more precisely.
Anyway, I'm open to going straight to the mm->context approach as well and happy to do that instead :D
Thanks,
Lance
Quoted 23 lines
Quoted 16 lines
Quoted 5 lines
That said, complexity can be worth it with sufficient demonstrated gains. But:
Quoted 8 lines
Quoted 4 lines
When unsharing hugetlb PMD page tables or collapsing pages in khugepaged,
we send two IPIs: one for TLB invalidation, and another to synchronize with concurrent GUP-fast walkers.Those aren't exactly hot paths. khugepaged is fundamentally rate limited. I don't think unsharing hugetlb PMD page tables just is all that common either.
Given that the added IPIs during unsharing broke Oracle DBs rather badly [1], I think this is actually a case worth optimizing.
...
Quoted 1 line
Gah, that's good context, thanks.
Are there any tests out there that might catch these this case better? It might be something good to have 0day watch for.
On 1/3/26 00:39, Lance Yang wrote:
...
Quoted 3 lines
Maybe we could do that as a follow-up. I'd like to keep things simple for now, so we just add a bool property to skip redundant TLB sync IPIs on systems without INVLPGB support.
It's not just INVLPGB support. Take a look at hyperv_flush_tlb_multi(), for instance. It can eventually land back in native_flush_tlb_multi(), but would also "fail" the pv_ops check in all cases.
It's not that Hyper-V performance is super important, it just that the semantics of the chosen approach here are rather complicated.
Quoted 5 lines
Then we could add the
mm->context(or something similar) tracking later to handle things more precisely.Anyway, I'm open to going straight to the
mm->contextapproach as well and happy to do that instead :D
I'd really like to see what an mm->context approach looks like before we go forward with what is being proposed here.
Is there some kind of hurry to get this done immediately?
Lance Yang
reviewerQuoted 9 lines · Dave Hansen
On 1/3/26 00:39, Lance Yang wrote:
...Quoted 3 lines
Maybe we could do that as a follow-up. I'd like to keep things simple for now, so we just add a bool property to skip redundant TLB sync IPIs on systems without INVLPGB support.
It's not just INVLPGB support. Take a look at
hyperv_flush_tlb_multi(), for instance. It can eventually land back innative_flush_tlb_multi(), but would also "fail" thepv_opscheck in all cases.
Thanks for pointing that out!
Quoted 3 lines
It's not that Hyper-V performance is super important, it just that the semantics of the chosen approach here are rather complicated.
Yep, got it ;)
Quoted 9 lines
Quoted 6 lines
Then we could add the
mm->context(or something similar) tracking later to handle things more precisely.Anyway, I'm open to going straight to the
mm->contextapproach as well and happy to do that instead :DI'd really like to see what an
mm->contextapproach looks like before we go forward with what is being proposed here.
Actually, I went ahead and tried a simialr approach using tlb_gather to track IPI sends dynamically/precisely.
Seems simpler than the mm->context approach because:
1) IIUC, mm->context tracking would need proper synchronization (CAS, handling concurrent flushes, etc.) which adds more complexity :)
2) With tlb_gather we already have the right context at the right time - we just pass the tlb pointer through flush_tlb_mm_range() and set a flag when IPIs are actually sent.
The first one adds a tlb_flush_sent_ipi flag to mmu_gather and wires it through flush_tlb_mm_range(). When we call flush_tlb_multi(), we set the flag. Then tlb_gather_remove_table_sync_one() checks it and skips the IPI if it's set.
---8<---
When unsharing hugetlb PMD page tables, we currently send two IPIs: one for TLB invalidation, and another to synchronize with concurrent GUP-fast walkers via tlb_remove_table_sync_one().
However, if the TLB flush already sent IPIs to all CPUs (when freed_tables or unshared_tables is true), the second IPI is redundant. GUP-fast runs with IRQs disabled, so when the TLB flush IPI completes, any concurrent GUP-fast must have finished.
Add a tlb_flush_sent_ipi flag to struct mmu_gather to track whether IPIs were actually sent.
Introduce tlb_gather_remove_table_sync_one() which checks tlb_flush_sent_ipi and skips the IPI if redundant.
--- arch/x86/include/asm/tlb.h | 3 ++- arch/x86/include/asm/tlbflush.h | 8 ++++---- arch/x86/kernel/alternative.c | 2 +- arch/x86/kernel/ldt.c | 2 +- arch/x86/mm/tlb.c | 6 ++++-- include/asm-generic/tlb.h | 14 +++++++++----- mm/mmu_gather.c | 24 ++++++++++++++++++------ 7 files changed, 39 insertions(+), 20 deletions(-)
arch/x86/include/asm/tlb.h +2 -1
@@ -20,7 +20,8 @@ static inline void tlb_flush(struct mmu_gather *tlb) end = tlb->end; } -flush_tlb_mm_range (tlb->mm, start, end, stride_shift, tlb->freed_tables); +flush_tlb_mm_range (tlb->mm, start, end, stride_shift, + tlb->freed_tables || tlb->unshared_tables, tlb); }static inline void invlpg (unsigned long addr)
arch/x86/include/asm/tlbflush.h +3 -3
@@ -305,23 +305,23 @@ static inline bool mm_in_asid_transition(struct mm_struct *mm) { return false; }#endif #define flush_tlb_mm (mm) \ - flush_tlb_mm_range(mm, 0UL, TLB_FLUSH_ALL, 0UL, true) + flush_tlb_mm_range(mm, 0UL, TLB_FLUSH_ALL, 0UL, true, NULL)#define flush_tlb_range (vma, start, end) \ flush_tlb_mm_range((vma)->vm_mm, start, end, \ ((vma)->vm_flags & VM_HUGETLB) \ ? huge_page_shift(hstate_vma(vma)) \ - : PAGE_SHIFT, true) + : PAGE_SHIFT, true, NULL)extern void flush_tlb_all (void );extern void flush_tlb_mm_range (struct mm_struct *mm,unsigned long start,unsigned long end,unsigned int stride_shift, -bool freed_tables); +bool freed_tables,struct mmu_gather *tlb); extern void flush_tlb_kernel_range(unsigned long start, unsigned long
end);
static inline void flush_tlb_page(struct vm_area_struct *vma, unsigned
long a)
{
- flush_tlb_mm_range(vma->vm_mm, a, a + PAGE_SIZE, PAGE_SHIFT, false);
+ flush_tlb_mm_range(vma->vm_mm, a, a + PAGE_SIZE, PAGE_SHIFT, false, NULL);
}
static inline bool arch_tlbbatch_should_defer(struct mm_struct *mm)
arch/x86/kernel/alternative.c +1 -1
@@ -2572,7 +2572,7 @@ static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t l*/ flush_tlb_mm_range (text_poke_mm, text_poke_mm_addr, text_poke_mm_addr + (cross_page_boundary ?2 :1 ) *PAGE_SIZE , -PAGE_SHIFT , false); +PAGE_SHIFT , false,NULL );if (func == text_poke_memcpy) {/*
arch/x86/kernel/ldt.c +1 -1
@@ -374,7 +374,7 @@ static void unmap_ldt_struct(struct mm_struct *mm, struct ldt_struct *ldt) } va = (unsigned long )ldt_slot_va (ldt->slot); -flush_tlb_mm_range (mm, va, va + nr_pages *PAGE_SIZE ,PAGE_SHIFT , false); +flush_tlb_mm_range (mm, va, va + nr_pages *PAGE_SIZE ,PAGE_SHIFT , false,NULL ); }#else /* !CONFIG_MITIGATION_PAGE_TABLE_ISOLATION */
arch/x86/mm/tlb.c +4 -2
@@ -1447,8 +1447,8 @@ static void put_flush_tlb_info(void) }void flush_tlb_mm_range (struct mm_struct *mm,unsigned long start, -unsigned long end,unsigned int stride_shift, -bool freed_tables) +unsigned long end,unsigned int stride_shift, +bool freed_tables,struct mmu_gather *tlb) {struct flush_tlb_info *info;int cpu =get_cpu ();
@@ -1471,6 +1471,8 @@ void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, info->trim_cpumask =should_trim_cpumask (mm);flush_tlb_multi (mm_cpumask (mm), info);consider_global_asid (mm); +if (tlb && freed_tables) + tlb->tlb_flush_sent_ipi = true; }else if (mm ==this_cpu_read (cpu_tlbstate.loaded_mm)) {lockdep_assert_irqs_enabled ();local_irq_disable ();
include/asm-generic/tlb.h +9 -5
@@ -249,6 +249,7 @@ static inline void tlb_remove_table(struct mmu_gather *tlb, void *table)#define tlb_needs_table_invalidate () (true)#endif +void tlb_gather_remove_table_sync_one (struct mmu_gather *tlb);void tlb_remove_table_sync_one (void );#else
@@ -257,6 +258,7 @@ void tlb_remove_table_sync_one(void);#error tlb_needs_table_invalidate() requires MMU_GATHER_RCU_TABLE_FREE#endif +static inline void tlb_gather_remove_table_sync_one (struct mmu_gather *tlb) { }static inline void tlb_remove_table_sync_one (void ) { }#endif /* CONFIG_MMU_GATHER_RCU_TABLE_FREE */
@@ -379,6 +381,12 @@ struct mmu_gather {*/ unsigned int fully_unshared_tables : 1; +/* +* Did the TLB flush for freed/unshared tables send IPIs to all CPUs? +* If true, we can skip the redundant IPI in tlb_remove_table_sync_one(). +*/ + unsigned int tlb_flush_sent_ipi : 1; +unsigned int batch_count;#ifndef CONFIG_MMU_GATHER_NO_GATHER
@@ -834,13 +842,9 @@ static inline void tlb_flush_unshared_tables(struct mmu_gather *tlb)* * We only perform this when we are the last sharer of a page table, * as the IPI will reach all CPUs: any GUP-fast. -* -* Note that on configs where tlb_remove_table_sync_one() is a NOP, -* the expectation is that the tlb_flush_mmu_tlbonly() would have issued -* required IPIs already for us. */ if (tlb->fully_unshared_tables) { -tlb_remove_table_sync_one (); +tlb_gather_remove_table_sync_one (tlb); tlb->fully_unshared_tables = false; } }
mm/mmu_gather.c +18 -6
@@ -274,8 +274,14 @@ static void tlb_remove_table_smp_sync(void *arg)/* Simply deliver the interrupt */ } -void tlb_remove_table_sync_one (void ) +void tlb_gather_remove_table_sync_one (struct mmu_gather *tlb) { +/* Skip the IPI if the TLB flush already synchronized with other CPUs */ +if (tlb && tlb->tlb_flush_sent_ipi) { + tlb->tlb_flush_sent_ipi = false; +return ; + } +/* * This isn't an RCU grace period and hence the page-tables cannot be * assumed to be actually RCU-freed.
@@ -286,6 +292,11 @@ void tlb_remove_table_sync_one(void)smp_call_function (tlb_remove_table_smp_sync,NULL ,1 ); } +void tlb_remove_table_sync_one (void ) +{ +tlb_gather_remove_table_sync_one (NULL ); +} +static void tlb_remove_table_rcu (struct rcu_head *head) {__tlb_remove_table_free (container_of(head ,struct mmu_table_batch ,rcu ));
@@ -337,16 +348,16 @@ static inline void __tlb_remove_table_one(void *table) call_rcu(&ptdesc->pt_rcu_head, __tlb_remove_table_one_rcu); }#else -static inline void __tlb_remove_table_one (void *table) +static inline void __tlb_remove_table_one (void *table,struct mmu_gather *tlb) { -tlb_remove_table_sync_one (); +tlb_gather_remove_table_sync_one (tlb);__tlb_remove_table (table); }#endif /* CONFIG_PT_RECLAIM */ -static void tlb_remove_table_one (void *table) +static void tlb_remove_table_one (void *table,struct mmu_gather *tlb) { -__tlb_remove_table_one (table); +__tlb_remove_table_one (table, tlb); }static void tlb_table_flush (struct mmu_gather *tlb)
@@ -368,7 +379,7 @@ void tlb_remove_table(struct mmu_gather *tlb, void *table) *batch = (struct mmu_table_batch *)__get_free_page (GFP_NOWAIT );if (*batch ==NULL ) {tlb_table_invalidate (tlb); -tlb_remove_table_one (table); +tlb_remove_table_one (table, tlb);return ; } (*batch)->nr =0 ;
@@ -428,6 +439,7 @@ static void __tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, tlb->vma_pfn =0 ; tlb->fully_unshared_tables =0 ; + tlb->tlb_flush_sent_ipi =0 ;__tlb_reset_range (tlb);inc_tlb_flush_pending (tlb->mm); }
---
The second one optimizes khugepaged by using mmu_gather to track IPI sends. This makes the approach work across all paths ;)
---8<---pmdp_collapse_flush() may already send IPIs to flush TLBs, and then callers send another IPI via tlb_remove_table_sync_one() or pmdp_get_lockless_sync() to synchronize with concurrent GUP-fast walkers.
However, since GUP-fast runs with IRQs disabled, the TLB flush IPI already provides the necessary synchronization. We can avoid the redundant second IPI.
Introduce pmdp_collapse_flush_sync() which combines flush and sync:
- For architectures using the generic pmdp_collapse_flush() implementation (e.g., x86): Use mmu_gather to track IPI sends. If the TLB flush sent an IPI, tlb_gather_remove_table_sync_one() will skip the redundant one.
- For architectures with custom pmdp_collapse_flush() (s390, riscv, powerpc): Fall back to calling pmdp_collapse_flush() followed by tlb_remove_table_sync_one(). No behavior change.
Update khugepaged to use pmdp_collapse_flush_sync() instead of separate flush and sync calls. Remove the now-unused pmdp_get_lockless_sync() macro.
--- include/linux/pgtable.h | 13 +++++++++---- mm/khugepaged.c | 9 +++------ mm/pgtable-generic.c | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 10 deletions(-)
include/linux/pgtable.h +9 -4
@@ -755,7 +755,6 @@ static inline pmd_t pmdp_get_lockless(pmd_t *pmdp)return pmd; }#define pmdp_get_lockless pmdp_get_lockless -#define pmdp_get_lockless_sync () tlb_remove_table_sync_one()#endif /* CONFIG_PGTABLE_LEVELS > 2 */ #endif /* CONFIG_GUP_GET_PXX_LOW_HIGH */
@@ -774,9 +773,6 @@ static inline pmd_t pmdp_get_lockless(pmd_t *pmdp) {return pmdp_get (pmdp); } -static inline void pmdp_get_lockless_sync (void ) -{ -}#endif #ifdef CONFIG_TRANSPARENT_HUGEPAGE
@@ -1174,6 +1170,8 @@ static inline void pudp_set_wrprotect(struct mm_struct *mm,#ifdef CONFIG_TRANSPARENT_HUGEPAGE extern pmd_t pmdp_collapse_flush (struct vm_area_struct *vma,unsigned long address,pmd_t *pmdp); +extern pmd_t pmdp_collapse_flush_sync (struct vm_area_struct *vma, +unsigned long address,pmd_t *pmdp);#else static inline pmd_t pmdp_collapse_flush(struct vm_area_struct *vma, unsigned long address,
@@ -1182,6 +1180,13 @@ static inline pmd_t pmdp_collapse_flush(struct vm_area_struct *vma,BUILD_BUG ();return *pmdp; } +static inline pmd_t pmdp_collapse_flush_sync (struct vm_area_struct *vma, +unsigned long address, +pmd_t *pmdp) +{ +BUILD_BUG (); +return *pmdp; +}#define pmdp_collapse_flush pmdp_collapse_flush#endif /* CONFIG_TRANSPARENT_HUGEPAGE */ #endif
mm/khugepaged.c +3 -6
@@ -1177,10 +1177,9 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long a* Parallel GUP-fast is fine since GUP-fast will back off when * it detects PMD is changed. */ - _pmd =pmdp_collapse_flush (vma, address, pmd); + _pmd =pmdp_collapse_flush_sync (vma, address, pmd);spin_unlock (pmd_ptl);mmu_notifier_invalidate_range_end (&range); -tlb_remove_table_sync_one (); pte =pte_offset_map_lock (mm, &_pmd, address, &pte_ptl);if (pte) {
@@ -1663,8 +1662,7 @@ static enum scan_result try_collapse_pte_mapped_thp(struct mm_struct *mm, unsign } } } - pgt_pmd =pmdp_collapse_flush (vma, haddr, pmd); -pmdp_get_lockless_sync (); + pgt_pmd =pmdp_collapse_flush_sync (vma, haddr, pmd);pte_unmap_unlock (start_pte, ptl);if (ptl != pml)spin_unlock (pml);
@@ -1817,8 +1815,7 @@ static void retract_page_tables(struct address_space *mapping, pgoff_t pgoff)* races against the prior checks. */ if (likely (file_backed_vma_is_retractable (vma))) { - pgt_pmd =pmdp_collapse_flush (vma, addr, pmd); -pmdp_get_lockless_sync (); + pgt_pmd =pmdp_collapse_flush_sync (vma, addr, pmd); success = true; }
mm/pgtable-generic.c +34 -0
@@ -233,6 +233,40 @@ pmd_t pmdp_collapse_flush(struct vm_area_struct *vma, unsigned long address,flush_tlb_range (vma, address, address +HPAGE_PMD_SIZE );return pmd; } + +pmd_t pmdp_collapse_flush_sync (struct vm_area_struct *vma,unsigned long address, +pmd_t *pmdp) +{ +struct mmu_gather tlb; +pmd_t pmd; + +VM_BUG_ON (address & ~HPAGE_PMD_MASK ); +VM_BUG_ON (pmd_trans_huge (*pmdp)); + +tlb_gather_mmu (&tlb, vma->vm_mm); + pmd =pmdp_huge_get_and_clear (vma->vm_mm, address, pmdp); + +flush_tlb_mm_range (vma->vm_mm, address, address +HPAGE_PMD_SIZE , +PAGE_SHIFT , true, &tlb); + +/* +* Synchronize with GUP-fast. If the flush sent IPIs, skip the +* redundant sync IPI. +*/ +tlb_gather_remove_table_sync_one (&tlb); +tlb_finish_mmu (&tlb); +return pmd; +} +#else +pmd_t pmdp_collapse_flush_sync (struct vm_area_struct *vma,unsigned long address, +pmd_t *pmdp) +{ +pmd_t pmd; + + pmd =pmdp_collapse_flush (vma, address, pmdp); +tlb_remove_table_sync_one (); +return pmd; +}#endif /* arch define pte_free_defer in asm/pgalloc.h for its own
implementation */
---
Quoted 2 lines
Is there some kind of hurry to get this done immediately?
No rush at all - just wanted to explore what works best and keep things simpler as well ;)
What do you think?
Lance Yang
reviewerQuoted 175 lines · Lance Yang
Quoted 9 lines · Dave Hansen
On 1/3/26 00:39, Lance Yang wrote:
...Quoted 3 lines
Maybe we could do that as a follow-up. I'd like to keep things simple for now, so we just add a bool property to skip redundant TLB sync IPIs on systems without INVLPGB support.
It's not just INVLPGB support. Take a look at
hyperv_flush_tlb_multi(), for instance. It can eventually land back innative_flush_tlb_multi(), but would also "fail" thepv_opscheck in all cases.Thanks for pointing that out!
Quoted 3 lines
It's not that Hyper-V performance is super important, it just that the semantics of the chosen approach here are rather complicated.
Yep, got it ;)
Quoted 9 lines
Quoted 6 lines
Then we could add the
mm->context(or something similar) tracking later to handle things more precisely.Anyway, I'm open to going straight to the
mm->contextapproach as well and happy to do that instead :DI'd really like to see what an
mm->contextapproach looks like before we go forward with what is being proposed here.Actually, I went ahead and tried a simialr approach using
tlb_gatherto track IPI sends dynamically/precisely.Seems simpler than the
mm->contextapproach because:1) IIUC,
mm->contexttracking would need proper synchronization (CAS, handling concurrent flushes, etc.) which adds more complexity :)2) With
tlb_gatherwe already have the right context at the right time - we just pass the tlb pointer throughflush_tlb_mm_range()and set a flag when IPIs are actually sent.The first one adds a
tlb_flush_sent_ipiflag tommu_gatherand wires it throughflush_tlb_mm_range(). When we callflush_tlb_multi(), we set the flag. Thentlb_gather_remove_table_sync_one()checks it and skips the IPI if it's set.---8<---
When unsharing hugetlb PMD page tables, we currently send two IPIs: one for TLB invalidation, and another to synchronize with concurrent GUP-fast walkers viatlb_remove_table_sync_one().However, if the TLB flush already sent IPIs to all CPUs (when
freed_tablesorunshared_tablesis true), the second IPI is redundant. GUP-fast runs with IRQs disabled, so when the TLB flush IPI completes, any concurrent GUP-fast must have finished.Add a
tlb_flush_sent_ipiflag to structmmu_gatherto track whether IPIs were actually sent.Introduce
tlb_gather_remove_table_sync_one()which checkstlb_flush_sent_ipiand skips the IPI if redundant.Suggested-by David Hildenbrand <[email protected]>Suggested-by Dave Hansen <[email protected]>Signed-off-by Lance Yang <[email protected]>--- arch/x86/include/asm/tlb.h | 3 ++- arch/x86/include/asm/tlbflush.h | 8 ++++---- arch/x86/kernel/alternative.c | 2 +- arch/x86/kernel/ldt.c | 2 +- arch/x86/mm/tlb.c | 6 ++++-- include/asm-generic/tlb.h | 14 +++++++++----- mm/mmu_gather.c | 24 ++++++++++++++++++------ 7 files changed, 39 insertions(+), 20 deletions(-)arch/x86/include/asm/tlb.h +2
-1@@ -20,7 +20,8 @@ static inline void tlb_flush(struct mmu_gather *tlb) end = tlb->end; } - flush_tlb_mm_range(tlb->mm, start, end, stride_shift, tlb- >freed_tables); + flush_tlb_mm_range(tlb->mm, start, end, stride_shift, + tlb->freed_tables || tlb->unshared_tables, tlb); }static inline void invlpg(unsigned long addr)
arch/x86/include/asm/tlbflush.h +4
-4@@ -305,23 +305,23 @@ static inline bool mm_in_asid_transition(struct mm_struct *mm) { return false; } #endif #define flush_tlb_mm(mm) \ - flush_tlb_mm_range(mm, 0UL, TLB_FLUSH_ALL, 0UL, true) + flush_tlb_mm_range(mm, 0UL, TLB_FLUSH_ALL, 0UL, true, NULL) #define flush_tlb_range(vma, start, end) \ flush_tlb_mm_range((vma)->vm_mm, start, end, \ ((vma)->vm_flags & VM_HUGETLB) \ ? huge_page_shift(hstate_vma(vma)) \ - : PAGE_SHIFT, true) + : PAGE_SHIFT, true, NULL) extern void flush_tlb_all(void); extern void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, unsigned long end, unsigned int stride_shift, - bool freed_tables); + bool freed_tables, struct mmu_gather *tlb); extern void flush_tlb_kernel_range(unsigned long start, unsigned long end); static inline void flush_tlb_page(struct vm_area_struct *vma, unsignedlong a) { - flush_tlb_mm_range(vma->vm_mm, a, a + PAGE_SIZE, PAGE_SHIFT, false); + flush_tlb_mm_range(vma->vm_mm, a, a + PAGE_SIZE, PAGE_SHIFT, false,NULL);
}static inline bool
arch_tlbbatch_should_defer(struct mm_struct *mm)arch/x86/kernel/alternative.c +0
-0@@ -2572,7 +2572,7 @@ static void *__text_poke(text_poke_f func, void*addr, const void *src, size_t l */ flush_tlb_mm_range(text_poke_mm, text_poke_mm_addr,
text_poke_mm_addr+
(cross_page_boundary? 2 : 1) *PAGE_SIZE,
-PAGE_SHIFT, false);
+PAGE_SHIFT, false, NULL);if (func ==
text_poke_memcpy) {
/*arch/x86/kernel/ldt.c +1
-1@@ -374,7 +374,7 @@ static void unmap_ldt_struct(struct mm_struct *mm, struct ldt_struct *ldt) } va = (unsigned long)ldt_slot_va(ldt->slot); - flush_tlb_mm_range(mm, va, va + nr_pages * PAGE_SIZE, PAGE_SHIFT, false); + flush_tlb_mm_range(mm, va, va + nr_pages * PAGE_SIZE, PAGE_SHIFT, false,NULL );}
#else /* !CONFIG_MITIGATION_PAGE_TABLE_ISOLATION */
arch/x86/mm/tlb.c +2
-2@@ -1447,8 +1447,8 @@ static void put_flush_tlb_info(void) } void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, - unsigned long end, unsigned int stride_shift, - bool freed_tables) + unsigned long end, unsigned int stride_shift, + bool freed_tables, struct mmu_gather *tlb) { struct flush_tlb_info *info; int cpu = get_cpu();
arch/x86/mm/tlb.c void flush_tlb_mm_range(struct mm_struct *mm, · line 1476
Quoted 6 lines
arch/x86/mm/tlb.c +2
-0@@ -1471,6 +1471,8 @@ void flush_tlb_mm_range(struct mm_struct *mm,unsigned long start, info->trim_cpumask = should_trim_cpumask(mm); flush_tlb_multi(mm_cpumask(mm), info); consider_global_asid(mm); + if (tlb && freed_tables) + tlb->tlb_flush_sent_ipi = true;
Oops, I missed an important piece here! The sent_ipi flag needs to be set in native_flush_tlb_multi() when IPIs are actually sent :(
The missing part is as follows:
---8<---
arch/x86/hyperv/mmu.c +1 -1
@@ -17,7 +17,7 @@#define HV_TLB_FLUSH_UNIT (4096 * PAGE_SIZE)static u64 hyperv_flush_tlb_others_ex (const struct cpumask *cpus, -const struct flush_tlb_info *info); +struct flush_tlb_info *info);/* * Fills in gva_list starting from offset. Returns the number of items
added.
@@ -57,7 +57,7 @@ static bool cpu_is_lazy(int cpu)
}
static void hyperv_flush_tlb_multi(const struct cpumask *cpus,
- const struct flush_tlb_info *info)
+ struct flush_tlb_info *info)
{
int cpu, vcpu, gva_n, max_gvas;
struct hv_tlb_flush *flush;
@@ -170,7 +170,7 @@ static void hyperv_flush_tlb_multi(const struct cpumask *cpus,
}
static u64 hyperv_flush_tlb_others_ex(const struct cpumask *cpus,
- const struct flush_tlb_info *info)
+ struct flush_tlb_info *info)
{
int nr_bank = 0, max_gvas, gva_n;
struct hv_tlb_flush_ex *flush;
arch/x86/include/asm/paravirt.h +1 -1
@@ -68,7 +68,7 @@ void native_flush_tlb_local(void);void native_flush_tlb_global (void );void native_flush_tlb_one_user (unsigned long addr);void native_flush_tlb_multi (const struct cpumask *cpumask, -const struct flush_tlb_info *info); +struct flush_tlb_info *info);static inline void __flush_tlb_local (void ) {
arch/x86/include/asm/paravirt_types.h +1 -1
@@ -131,7 +131,7 @@ struct pv_mmu_ops {void (*flush_tlb_kernel)(void );void (*flush_tlb_one_user)(unsigned long addr);void (*flush_tlb_multi)(const struct cpumask *cpus, -const struct flush_tlb_info *info); +struct flush_tlb_info *info);/* Hook for intercepting the destruction of an mm_struct. */ void (*exit_mmap)(struct mm_struct *mm);
arch/x86/include/asm/tlbflush.h +2 -1
@@ -227,13 +227,14 @@ struct flush_tlb_info {u8 stride_shift;u8 freed_tables;u8 trim_cpumask; +u8 sent_ipi; };void flush_tlb_local (void );void flush_tlb_one_user (unsigned long addr);void flush_tlb_one_kernel (unsigned long addr);void flush_tlb_multi (const struct cpumask *cpumask, -const struct flush_tlb_info *info); +struct flush_tlb_info *info);static inline bool is_dyn_asid (u16 asid) {
arch/x86/include/asm/trace/hyperv.h +1 -1
@@ -10,7 +10,7 @@ TRACE_EVENT(hyperv_mmu_flush_tlb_multi, TP_PROTO(const struct cpumask *cpus, - const struct flush_tlb_info *info), + struct flush_tlb_info *info), TP_ARGS(cpus, info), TP_STRUCT__entry( __field(unsigned int, ncpus)
arch/x86/kernel/kvm.c +1 -1
@@ -649,7 +649,7 @@ static void kvm_smp_send_call_func_ipi(const struct cpumask *mask) }static void kvm_flush_tlb_multi (const struct cpumask *cpumask, -const struct flush_tlb_info *info) +struct flush_tlb_info *info) {u8 state;int cpu;
arch/x86/mm/tlb.c +7 -3
@@ -1345,7 +1345,7 @@ DEFINE_PER_CPU_SHARED_ALIGNED(struct tlb_state_shared, cpu_tlbstate_shared);EXPORT_PER_CPU_SYMBOL (cpu_tlbstate_shared); STATIC_NOPV void native_flush_tlb_multi(const struct cpumask *cpumask, -const struct flush_tlb_info *info) +struct flush_tlb_info *info) {/* * Do accounting and tracing. Note that there are (and have always been)
@@ -1374,10 +1374,13 @@ STATIC_NOPV void native_flush_tlb_multi(const struct cpumask *cpumask,else on_each_cpu_cond_mask (should_flush_tlb, flush_tlb_func, (void *)info,1 , cpumask); + +if (info->freed_tables) + info->sent_ipi =1 ; }void flush_tlb_multi (const struct cpumask *cpumask, -const struct flush_tlb_info *info) +struct flush_tlb_info *info) {__flush_tlb_multi (cpumask, info); }
@@ -1433,6 +1436,7 @@ static struct flush_tlb_info *get_flush_tlb_info(struct mm_struct *mm, info->new_tlb_gen = new_tlb_gen; info->initiating_cpu =smp_processor_id (); info->trim_cpumask =0 ; + info->sent_ipi =0 ;return info; }
@@ -1471,7 +1475,7 @@ void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start, info->trim_cpumask =should_trim_cpumask (mm);flush_tlb_multi (mm_cpumask (mm), info);consider_global_asid (mm); -if (tlb && freed_tables) +if (tlb && info->sent_ipi) tlb->tlb_flush_sent_ipi = true; }else if (mm ==this_cpu_read (cpu_tlbstate.loaded_mm)) {lockdep_assert_irqs_enabled ();
arch/x86/xen/mmu_pv.c +1 -1
@@ -1320,7 +1320,7 @@ static void xen_flush_tlb_one_user(unsigned long addr) }static void xen_flush_tlb_multi (const struct cpumask *cpus, -const struct flush_tlb_info *info) +struct flush_tlb_info *info) {struct {struct mmuext_op op;
---
Thanks,
Lance
Quoted 313 lines
arch/x86/mm/tlb.c +0
-0} else if (mm == this_cpu_read(cpu_tlbstate.loaded_mm)) { lockdep_assert_irqs_enabled();
local_irq_disable();include/asm-generic/tlb.h +1
-0@@ -249,6 +249,7 @@ static inline void tlb_remove_table(struct mmu_gather *tlb, void *table) #define tlb_needs_table_invalidate() (true) #endif +void tlb_gather_remove_table_sync_one (struct mmu_gather *tlb); void tlb_remove_table_sync_one(void);#else
include/asm-generic/tlb.h +1
-0@@ -257,6 +258,7 @@ void tlb_remove_table_sync_one(void); #error tlb_needs_table_invalidate() requires MMU_GATHER_RCU_TABLE_FREE #endif +static inline void tlb_gather_remove_table_sync_one (struct mmu_gather *tlb) { } static inline void tlb_remove_table_sync_one(void) { }#endif /*
CONFIG_MMU_GATHER_RCU_TABLE_FREE*/include/asm-generic/tlb.h +3
-0@@ -379,6 +381,12 @@ struct mmu_gather {*/ unsigned int fully_unshared_tables : 1; + /* +* Did the TLB flush for freed/unshared tables send IPIs to all CPUs? +* If true, we can skip the redundant IPI in
tlb_remove_table_sync_one().
+ */
+ unsigned inttlb_flush_sent_ipi: 1;include/asm-generic/tlb.h +1
-3+ unsigned int batch_count; #ifndef CONFIG_MMU_GATHER_NO_GATHER@@ -834,13 +842,9 @@ static inline void tlb_flush_unshared_tables(struct mmu_gather *tlb) * * We only perform this when we are the last sharer of a page table, * as the IPI will reach all CPUs: any GUP-fast. - * - * Note that on configs where tlb_remove_table_sync_one() is a NOP, - * the expectation is that the tlb_flush_mmu_tlbonly() would haveissued
- * required IPIs already for us.
*/
if (tlb->fully_unshared_tables) {
-tlb_remove_table_sync_one();
+tlb_gather_remove_table_sync_one(tlb);
tlb->fully_unshared_tables= false;
}
}mm/mmu_gather.c +2
-1@@ -274,8 +274,14 @@ static void tlb_remove_table_smp_sync(void *arg) /* Simply deliver the interrupt */ } -void tlb_remove_table_sync_one (void ) +void tlb_gather_remove_table_sync_one (struct mmu_gather *tlb) { + /* Skip the IPI if the TLB flush already synchronized with otherCPUs */
+ if (tlb &&tlb->tlb_flush_sent_ipi) {
+tlb->tlb_flush_sent_ipi= false;
+ return;
+ }mm/mmu_gather.c +6
-0+ /** This isn't an RCU grace period and hence the page-tables cannot be * assumed to be actually RCU-freed. @@ -286,6 +292,11 @@ void tlb_remove_table_sync_one(void) smp_call_function(tlb_remove_table_smp_sync, NULL, 1); } +void tlb_remove_table_sync_one (void ) +{ + tlb_gather_remove_table_sync_one(NULL); +} + static void tlb_remove_table_rcu(struct rcu_head *head) { __tlb_remove_table_free(container_of(head, struct mmu_table_batch,rcu));
mm/mmu_gather.c +4
-4@@ -337,16 +348,16 @@ static inline void __tlb_remove_table_one(void *table) call_rcu(&ptdesc->pt_rcu_head, __tlb_remove_table_one_rcu); } #else -static inline void __tlb_remove_table_one (void *table) +static inline void __tlb_remove_table_one (void *table,struct mmu_gather *tlb) { - tlb_remove_table_sync_one(); + tlb_gather_remove_table_sync_one(tlb); __tlb_remove_table(table); } #endif /* CONFIG_PT_RECLAIM */ -static void tlb_remove_table_one (void *table) +static void tlb_remove_table_one (void *table,struct mmu_gather *tlb) { - __tlb_remove_table_one(table); + __tlb_remove_table_one(table, tlb); }static void
tlb_table_flush(struct mmu_gather *tlb)mm/mmu_gather.c +1
-1@@ -368,7 +379,7 @@ void tlb_remove_table(struct mmu_gather *tlb, void *table) *batch = (struct mmu_table_batch *)__get_free_page(GFP_NOWAIT); if (*batch == NULL) { tlb_table_invalidate(tlb); - tlb_remove_table_one(table); + tlb_remove_table_one(table, tlb); return; }(*batch)->nr = 0;
mm/mmu_gather.c +1
-0@@ -428,6 +439,7 @@ static void __tlb_gather_mmu(struct mmu_gather *tlb,struct mm_struct *mm, tlb->vma_pfn = 0; tlb->fully_unshared_tables = 0; + tlb->tlb_flush_sent_ipi = 0; __tlb_reset_range(tlb); inc_tlb_flush_pending(tlb->mm);}
---The second one optimizes khugepaged by using
mmu_gatherto track IPI sends. This makes the approach work across all paths ;)---8<---
pmdp_collapse_flush()may already send IPIs to flush TLBs, and then callers send another IPI viatlb_remove_table_sync_one()orpmdp_get_lockless_sync()to synchronize with concurrent GUP-fast walkers.However, since GUP-fast runs with IRQs disabled, the TLB flush IPI already provides the necessary synchronization. We can avoid the redundant second IPI.
Introduce
pmdp_collapse_flush_sync()which combines flush and sync:- For architectures using the generic
pmdp_collapse_flush()implementation (e.g., x86): Usemmu_gatherto track IPI sends. If the TLB flush sent an IPI,tlb_gather_remove_table_sync_one()will skip the redundant one.- For architectures with custom
pmdp_collapse_flush()(s390, riscv, powerpc): Fall back to callingpmdp_collapse_flush()followed bytlb_remove_table_sync_one(). No behavior change.Update khugepaged to use
pmdp_collapse_flush_sync()instead of separate flush and sync calls. Remove the now-unusedpmdp_get_lockless_sync()macro.Suggested-by David Hildenbrand <[email protected]>Suggested-by Dave Hansen <[email protected]>Signed-off-by Lance Yang <[email protected]>--- include/linux/pgtable.h | 13 +++++++++---- mm/khugepaged.c | 9 +++------ mm/pgtable-generic.c | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 10 deletions(-)include/linux/pgtable.h +2
-4@@ -755,7 +755,6 @@ static inline pmd_t pmdp_get_lockless(pmd_t *pmdp) return pmd; } #define pmdp_get_lockless pmdp_get_lockless -#define pmdp_get_lockless_sync () tlb_remove_table_sync_one() #endif /* CONFIG_PGTABLE_LEVELS > 2 */ #endif /* CONFIG_GUP_GET_PXX_LOW_HIGH */@@ -774,9 +773,6 @@ static inline pmd_t pmdp_get_lockless(pmd_t *pmdp) { return pmdp_get(pmdp); } -static inline void pmdp_get_lockless_sync (void ) -{ -} #endif #ifdef CONFIG_TRANSPARENT_HUGEPAGE@@ -1174,6 +1170,8 @@ static inline void pudp_set_wrprotect(structmm_struct *mm, #ifdef CONFIG_TRANSPARENT_HUGEPAGE extern pmd_t pmdp_collapse_flush(struct vm_area_struct *vma, unsigned long address, pmd_t *pmdp); +extern pmd_t pmdp_collapse_flush_sync (struct vm_area_struct *vma, + unsigned long address, pmd_t *pmdp); #else static inline pmd_t pmdp_collapse_flush(struct vm_area_struct *vma,unsigned long address,
include/linux/pgtable.h +7
-0@@ -1182,6 +1180,13 @@ static inline pmd_t pmdp_collapse_flush(structvm_area_struct *vma, BUILD_BUG(); return *pmdp; } +static inline pmd_t pmdp_collapse_flush_sync (struct vm_area_struct *vma, + unsigned long address, + pmd_t *pmdp) +{ + BUILD_BUG(); + return *pmdp; +} #define pmdp_collapse_flush pmdp_collapse_flush #endif /* CONFIG_TRANSPARENT_HUGEPAGE */#endif
mm/khugepaged.c +0
-0@@ -1177,10 +1177,9 @@ static enum scan_result collapse_huge_page(struct
mm_struct*mm, unsigned long a
* Parallel GUP-fast is fine since GUP-fast will back off when
* it detects PMD is changed.
*/
-_pmd=pmdp_collapse_flush(vma, address, pmd);
+_pmd=pmdp_collapse_flush_sync(vma, address, pmd);
spin_unlock(pmd_ptl);mmu_notifier_invalidate_range_end(&range);
-tlb_remove_table_sync_one();pte =
pte_offset_map_lock(mm, &_pmd, address, &pte_ptl); if (pte) {mm/khugepaged.c +0
-0@@ -1663,8 +1662,7 @@ static enum scan_resulttry_collapse_pte_mapped_thp(struct
mm_struct*mm, unsign
}
}
}
-pgt_pmd=pmdp_collapse_flush(vma, haddr, pmd); -pmdp_get_lockless_sync();
+pgt_pmd=pmdp_collapse_flush_sync(vma, haddr, pmd);
pte_unmap_unlock(start_pte, ptl);
if (ptl != pml)
spin_unlock(pml);mm/khugepaged.c +1
-2@@ -1817,8 +1815,7 @@ static void retract_page_tables(structaddress_space *mapping, pgoff_t pgoff) * races against the prior checks. */ if (likely(file_backed_vma_is_retractable(vma))) { - pgt_pmd = pmdp_collapse_flush(vma, addr, pmd); - pmdp_get_lockless_sync(); + pgt_pmd = pmdp_collapse_flush_sync(vma, addr, pmd); success = true; }mm/pgtable-generic.c +34
-0@@ -233,6 +233,40 @@ pmd_t pmdp_collapse_flush(struct vm_area_struct *vma, unsigned long address, flush_tlb_range(vma, address, address + HPAGE_PMD_SIZE); return pmd; } + +pmd_t pmdp_collapse_flush_sync (struct vm_area_struct *vma,unsigned long address, + pmd_t *pmdp) +{ + struct mmu_gather tlb; + pmd_t pmd; + + VM_BUG_ON(address & ~HPAGE_PMD_MASK); + VM_BUG_ON(pmd_trans_huge(*pmdp)); + + tlb_gather_mmu(&tlb, vma->vm_mm); + pmd = pmdp_huge_get_and_clear(vma->vm_mm, address, pmdp); + + flush_tlb_mm_range(vma->vm_mm, address, address + HPAGE_PMD_SIZE, + PAGE_SHIFT, true, &tlb); + + /* + * Synchronize with GUP-fast. If the flush sent IPIs, skip the + * redundant sync IPI. + */ + tlb_gather_remove_table_sync_one(&tlb); + tlb_finish_mmu(&tlb); + return pmd; +} +#else +pmd_t pmdp_collapse_flush_sync (struct vm_area_struct *vma,unsigned long address, + pmd_t *pmdp) +{ + pmd_t pmd; + + pmd = pmdp_collapse_flush(vma, address, pmdp); + tlb_remove_table_sync_one(); + return pmd; +}#endif
/* arch define
pte_free_deferinasm/pgalloc.hfor its own
implementation */
---Quoted 2 lines
Is there some kind of hurry to get this done immediately?
No rush at all - just wanted to explore what works best and keep things simpler as well ;)
What do you think?