munmap() vs unmap_mapping_range()
Subsystems (9)
Peter Zijlstra
maintainercover munmap() vs unmap_mapping_range()
Hi,
These 4 patches implement the suggestion by Linus.
The first patch nukes all architecture implementations of tlb_{start,end}_vma() by adding two additional MMU_GATHER config knobs.
DaveM, please clarify the sparc64 case, it seems a little odd to have flush_cache_range() but to explicitly not call it at unmap time. It would be nice to remove this one special case.
The second patch isn't strictly needed for this series but since I was looking at all this, I figured C-SKY didn't make a whole lot of sense. Guo, please have a look.
The third patch cleans up the #ifdeffery and provides a single tlb_{start,end}_vma() implementation for everyone.
The fourth, and final, patch does the force TLB-flush on VM_PFNMAP thing.
I've had the first three patches exposed to the robots and they've (so far) not reported build (or other) fail.
Peter Zijlstra
maintainerPATCH 1/4 mmu_gather: Remove per arch tlb_{start,end}_vma()
Scattered across the archs are 3 basic forms of tlb_{start,end}_vma(). Provide two new MMU_GATHER_knobs to enumerate them and remove the per arch tlb_{start,end}_vma() implementations.
- MMU_GATHER_NO_FLUSH_CACHE indicates the arch has flush_cache_range() but does *NOT* want to call it for each VMA.
- MMU_GATHER_MERGE_VMAS indicates the arch wants to merge the invalidate across multiple VMAs if possible.
With these it is possible to capture the three forms:
1) empty stubs;
select MMU_GATHER_NO_FLUSH_CACHE and MMU_GATHER_MERGE_VMAS
2) start: flush_cache_range(), end: empty;
select MMU_GATHER_MERGE_VMAS
3) start: flush_cache_range(), end: flush_tlb_range(); default
Obviously, if the architecture does not have flush_cache_range() then it also doesn't need to select MMU_GATHER_NO_FLUSH_CACHE.
--- arch/Kconfig | 7 +++++++ arch/csky/include/asm/tlb.h | 13 ------------- arch/loongarch/Kconfig | 1 + arch/loongarch/include/asm/tlb.h | 10 ---------- arch/powerpc/Kconfig | 1 + arch/powerpc/include/asm/tlb.h | 2 -- arch/s390/Kconfig | 1 + arch/s390/include/asm/tlb.h | 3 --- arch/sparc/Kconfig | 2 ++ arch/sparc/include/asm/tlb_64.h | 2 -- arch/x86/Kconfig | 1 + arch/x86/include/asm/tlb.h | 3 --- include/asm-generic/tlb.h | 21 +++++++++++++++++++-- 13 files changed, 32 insertions(+), 35 deletions(-)
arch/Kconfig +7 -0
@@ -438,6 +438,13 @@ config MMU_GATHER_PAGE_SIZE config MMU_GATHER_NO_RANGE bool + select MMU_GATHER_MERGE_VMAS + +config MMU_GATHER_NO_FLUSH_CACHE + bool + +config MMU_GATHER_MERGE_VMAS + bool config MMU_GATHER_NO_GATHER bool
arch/csky/include/asm/tlb.h +0 -13
@@ -4,19 +4,6 @@#define __ASM_CSKY_TLB_H#include <asm/cacheflush.h> - -#define tlb_start_vma (tlb, vma) \ - do { \ - if (!(tlb)->fullmm) \ - flush_cache_range(vma, (vma)->vm_start, (vma)->vm_end); \ - } while (0) - -#define tlb_end_vma (tlb, vma) \ - do { \ - if (!(tlb)->fullmm) \ - flush_tlb_range(vma, (vma)->vm_start, (vma)->vm_end); \ - } while (0) -#define tlb_flush (tlb) flush_tlb_mm((tlb)->mm)#include <asm-generic/tlb.h>
arch/loongarch/Kconfig +1 -0
@@ -112,6 +112,7 @@ config LOONGARCH select TRACE_IRQFLAGS_SUPPORT select USE_PERCPU_NUMA_NODE_ID select ZONE_DMA32 + select MMU_GATHER_MERGE_VMAS if MMU config 32BIT bool
arch/loongarch/include/asm/tlb.h +0 -10
@@ -137,16 +137,6 @@ static inline void invtlb_all(u32 op, u3 ); } -/* -* LoongArch doesn't need any special per-pte or per-vma handling, except -* we need to flush cache for area to be unmapped. -*/ -#define tlb_start_vma (tlb, vma) \ - do { \ - if (!(tlb)->fullmm) \ - flush_cache_range(vma, vma->vm_start, vma->vm_end); \ - } while (0) -#define tlb_end_vma (tlb, vma) do { } while (0)#define __tlb_remove_tlb_entry (tlb, ptep, address) do { } while (0)static void tlb_flush (struct mmu_gather *tlb);
arch/powerpc/Kconfig +1 -0
@@ -256,6 +256,7 @@ config PPC select IRQ_FORCED_THREADING select MMU_GATHER_PAGE_SIZE select MMU_GATHER_RCU_TABLE_FREE + select MMU_GATHER_MERGE_VMAS select MODULES_USE_ELF_RELA select NEED_DMA_MAP_STATE if PPC64 || NOT_COHERENT_CACHE select NEED_PER_CPU_EMBED_FIRST_CHUNK if PPC64
arch/powerpc/include/asm/tlb.h +0 -2
@@ -19,8 +19,6 @@#include <linux/pagemap.h> -#define tlb_start_vma (tlb, vma) do { } while (0) -#define tlb_end_vma (tlb, vma) do { } while (0)#define __tlb_remove_tlb_entry __tlb_remove_tlb_entry#define tlb_flush tlb_flush
arch/s390/Kconfig +1 -0
@@ -204,6 +204,7 @@ config S390 select IOMMU_SUPPORT if PCI select MMU_GATHER_NO_GATHER select MMU_GATHER_RCU_TABLE_FREE + select MMU_GATHER_MERGE_VMAS select MODULES_USE_ELF_RELA select NEED_DMA_MAP_STATE if PCI select NEED_SG_DMA_LENGTH if PCI
arch/s390/include/asm/tlb.h +0 -3
@@ -27,9 +27,6 @@ static inline void tlb_flush(struct mmu_static inline bool __tlb_remove_page_size (struct mmu_gather *tlb,struct page *page,int page_size); -#define tlb_start_vma (tlb, vma) do { } while (0) -#define tlb_end_vma (tlb, vma) do { } while (0) -#define tlb_flush tlb_flush#define pte_free_tlb pte_free_tlb#define pmd_free_tlb pmd_free_tlb
arch/sparc/Kconfig +2 -0
@@ -67,6 +67,8 @@ config SPARC64 select HAVE_KRETPROBES select HAVE_KPROBES select MMU_GATHER_RCU_TABLE_FREE if SMP + select MMU_GATHER_MERGE_VMAS + select MMU_GATHER_NO_FLUSH_CACHE select HAVE_ARCH_TRANSPARENT_HUGEPAGE select HAVE_DYNAMIC_FTRACE select HAVE_FTRACE_MCOUNT_RECORD
arch/sparc/include/asm/tlb_64.h +0 -2
@@ -22,8 +22,6 @@ void smp_flush_tlb_mm(struct mm_struct *void __flush_tlb_pending (unsigned long ,unsigned long ,unsigned long *);void flush_tlb_pending (void ); -#define tlb_start_vma (tlb, vma) do { } while (0) -#define tlb_end_vma (tlb, vma) do { } while (0)#define tlb_flush (tlb) flush_tlb_pending()/*
arch/x86/Kconfig +1 -0
@@ -245,6 +245,7 @@ config X86 select HAVE_PERF_REGS select HAVE_PERF_USER_STACK_DUMP select MMU_GATHER_RCU_TABLE_FREE if PARAVIRT + select MMU_GATHER_MERGE_VMAS select HAVE_POSIX_CPU_TIMERS_TASK_WORK select HAVE_REGS_AND_STACK_ACCESS_API select HAVE_RELIABLE_STACKTRACE if UNWINDER_ORC || STACK_VALIDATION
arch/x86/include/asm/tlb.h +0 -3
@@ -2,9 +2,6 @@#ifndef _ASM_X86_TLB_H#define _ASM_X86_TLB_H -#define tlb_start_vma (tlb, vma) do { } while (0) -#define tlb_end_vma (tlb, vma) do { } while (0) -#define tlb_flush tlb_flushstatic inline void tlb_flush (struct mmu_gather *tlb);
include/asm-generic/tlb.h +19 -2
@@ -158,9 +158,24 @@* Useful if your architecture doesn't use IPIs for remote TLB invalidates * and therefore doesn't naturally serialize with software page-table walkers. * +* MMU_GATHER_NO_FLUSH_CACHE +* +* Indicates the architecture has flush_cache_range() but it needs *NOT* be called +* before unmapping a VMA. +* +* NOTE: strictly speaking we shouldn't have this knob and instead rely on +* flush_cache_range() being a NOP, except Sparc64 seems to be +* different here. +* +* MMU_GATHER_MERGE_VMAS +* +* Indicates the architecture wants to merge ranges over VMAs; typical when +* multiple range invalidates are more expensive than a full invalidate. +* * MMU_GATHER_NO_RANGE * -* Use this if your architecture lacks an efficient flush_tlb_range(). +* Use this if your architecture lacks an efficient flush_tlb_range(). This +* option implies MMU_GATHER_MERGE_VMAS above. * * MMU_GATHER_NO_GATHER *
@@ -493,14 +508,16 @@ static inline void tlb_start_vma(structreturn ;tlb_update_vma_flags (tlb, vma); +#ifndef CONFIG_MMU_GATHER_NO_FLUSH_CACHE flush_cache_range (vma, vma->vm_start, vma->vm_end); +#endif }#endif #ifndef tlb_end_vmastatic inline void tlb_end_vma (struct mmu_gather *tlb,struct vm_area_struct *vma) { -if (tlb->fullmm) +if (tlb->fullmm ||IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS ))return ;/*
Re: [PATCH 1/4] mmu_gather: Remove per arch tlb_{start,end}_vma()
Quoted 44 lines · Peter Zijlstra
Scattered across the archs are 3 basic forms of tlb_{start,end}_vma(). Provide two new
MMU_GATHER_knobsto enumerate them and remove the per arch tlb_{start,end}_vma() implementations.-
MMU_GATHER_NO_FLUSH_CACHEindicates the arch hasflush_cache_range()but does *NOT* want to call it for each VMA.-
MMU_GATHER_MERGE_VMASindicates the arch wants to merge the invalidate across multiple VMAs if possible.With these it is possible to capture the three forms:
1) empty stubs;
selectMMU_GATHER_NO_FLUSH_CACHEandMMU_GATHER_MERGE_VMAS2) start:
flush_cache_range(), end: empty;
selectMMU_GATHER_MERGE_VMAS3) start:
flush_cache_range(), end:flush_tlb_range(); defaultObviously, if the architecture does not have
flush_cache_range()then it also doesn't need to selectMMU_GATHER_NO_FLUSH_CACHE.Signed-off-by Peter Zijlstra (Intel) <[email protected]>--- arch/Kconfig | 7 +++++++ arch/csky/include/asm/tlb.h | 13 ------------- arch/loongarch/Kconfig | 1 + arch/loongarch/include/asm/tlb.h | 10 ---------- arch/powerpc/Kconfig | 1 + arch/powerpc/include/asm/tlb.h | 2 -- arch/s390/Kconfig | 1 + arch/s390/include/asm/tlb.h | 3 --- arch/sparc/Kconfig | 2 ++ arch/sparc/include/asm/tlb_64.h | 2 -- arch/x86/Kconfig | 1 + arch/x86/include/asm/tlb.h | 3 --- include/asm-generic/tlb.h | 21 +++++++++++++++++++-- 13 files changed, 32 insertions(+), 35 deletions(-)
arch/Kconfig config MMU_GATHER_PAGE_SIZE · line 444
Quoted 7 lines
arch/Kconfig +4
-0@@ -438,6 +438,13 @@ config MMU_GATHER_PAGE_SIZE config MMU_GATHER_NO_RANGE bool + select MMU_GATHER_MERGE_VMAS + +config MMU_GATHER_NO_FLUSH_CACHE + bool
If this is really a sparc-special and we don't necessarily want it to proliferate, then maybe:
default y
depends on SPARC
would keep it confined?
But I don't mind either way and the important bits of the patch look good:
Thanks,
Will
Peter Zijlstra
maintainerPATCH 2/4 csky/tlb: Remove tlb_flush() define
In reply to Peter ZijlstraThe previous patch removed the tlb_flush_end() implementation which used tlb_flush_range(). This means:
- csky did double invalidates, a range invalidate per vma and a full invalidate at the end
- csky actually has range invalidates and as such the generic tlb_flush implementation is more efficient for it.
--- arch/csky/include/asm/tlb.h | 2 -- 1 file changed, 2 deletions(-)
arch/csky/include/asm/tlb.h +0 -2
@@ -4,8 +4,6 @@#define __ASM_CSKY_TLB_H#include <asm/cacheflush.h> -#define tlb_flush (tlb) flush_tlb_mm((tlb)->mm) -#include <asm-generic/tlb.h> #endif /* __ASM_CSKY_TLB_H */
Re: [PATCH 2/4] csky/tlb: Remove tlb_flush() define
Quoted 17 lines · Peter Zijlstra
The previous patch removed the
tlb_flush_end()implementation which usedtlb_flush_range(). This means:- csky did double invalidates, a range invalidate per vma and a full invalidate at the end
- csky actually has range invalidates and as such the generic
tlb_flushimplementation is more efficient for it.Signed-off-by Peter Zijlstra (Intel) <[email protected]>--- arch/csky/include/asm/tlb.h | 2 -- 1 file changed, 2 deletions(-)
arch/csky/include/asm/tlb.h line 7
Quoted 4 lines
arch/csky/include/asm/tlb.h +0
-1@@ -4,8 +4,6 @@#define __ASM_CSKY_TLB_H#include <asm/cacheflush.h> -#define tlb_flush (tlb) flush_tlb_mm((tlb)->mm)
Looks right to me, and the generic code handles the fullmm case when !CONFIG_MMU_GATHER_NO_RANGE so:
Will
Peter Zijlstra
maintainerPATCH 3/4 mmu_gather: Let there be one tlb_{start,end}_vma() implementation
In reply to Peter ZijlstraNow that architectures are no longer allowed to override tlb_{start,end}_vma() re-arrange code so that there is only one implementation for each of these functions.
This much simplifies trying to figure out what they actually do.
--- include/asm-generic/tlb.h | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-)
include/asm-generic/tlb.h +2 -13
@@ -346,8 +346,8 @@ static inline void __tlb_reset_range(str#ifdef CONFIG_MMU_GATHER_NO_RANGE -#if defined(tlb_flush) || defined(tlb_start_vma) || defined(tlb_end_vma) -#error MMU_GATHER_NO_RANGE relies on default tlb_flush(), tlb_start_vma() and tlb_end_vma() +#if defined(tlb_flush) +#error MMU_GATHER_NO_RANGE relies on default tlb_flush()#endif /*
@@ -367,17 +367,10 @@ static inline void tlb_flush(struct mmu_static inline void tlb_update_vma_flags (struct mmu_gather *tlb,struct vm_area_struct *vma) { } -#define tlb_end_vma tlb_end_vma -static inline void tlb_end_vma (struct mmu_gather *tlb,struct vm_area_struct *vma) { } -#else /* CONFIG_MMU_GATHER_NO_RANGE */ #ifndef tlb_flush -#if defined(tlb_start_vma) || defined(tlb_end_vma) -#error Default tlb_flush() relies on default tlb_start_vma() and tlb_end_vma() -#endif -/* * When an architecture does not provide its own tlb_flush() implementation * but does have a reasonably efficient flush_vma_range() implementation
@@ -498,7 +491,6 @@ static inline unsigned long tlb_get_unma* case where we're doing a full MM flush. When we're doing a munmap, * the vmas are adjusted to only cover the region to be torn down. */ -#ifndef tlb_start_vmastatic inline void tlb_start_vma (struct mmu_gather *tlb,struct vm_area_struct *vma) {if (tlb->fullmm)
@@ -509,9 +501,7 @@ static inline void tlb_start_vma(structflush_cache_range (vma, vma->vm_start, vma->vm_end);#endif } -#endif -#ifndef tlb_end_vmastatic inline void tlb_end_vma (struct mmu_gather *tlb,struct vm_area_struct *vma) {if (tlb->fullmm ||IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS ))
@@ -525,7 +515,6 @@ static inline void tlb_end_vma(struct mm*/ tlb_flush_mmu_tlbonly (tlb); } -#endif /* * tlb_flush_{pte|pmd|pud|p4d}_range() adjust the tlb->start and tlb->end,
Re: [PATCH 3/4] mmu_gather: Let there be one tlb_{start,end}_vma() implementation
Quoted 61 lines · Peter Zijlstra
Now that architectures are no longer allowed to override tlb_{start,end}_vma() re-arrange code so that there is only one implementation for each of these functions.
This much simplifies trying to figure out what they actually do.
Signed-off-by Peter Zijlstra (Intel) <[email protected]>--- include/asm-generic/tlb.h | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-)include/asm-generic/tlb.h +2
-12@@ -346,8 +346,8 @@ static inline void __tlb_reset_range(str#ifdef CONFIG_MMU_GATHER_NO_RANGE -#if defined(tlb_flush) || defined(tlb_start_vma) || defined(tlb_end_vma) -#error MMU_GATHER_NO_RANGE relies on default tlb_flush(), tlb_start_vma() and tlb_end_vma() +#if defined(tlb_flush) +#error MMU_GATHER_NO_RANGE relies on default tlb_flush()#endif /* @@ -367,17 +367,10 @@ static inline void tlb_flush(struct mmu_static inline void tlb_update_vma_flags (struct mmu_gather *tlb,struct vm_area_struct *vma) { } -#define tlb_end_vma tlb_end_vma -static inline void tlb_end_vma (struct mmu_gather *tlb,struct vm_area_struct *vma) { } -#else /* CONFIG_MMU_GATHER_NO_RANGE */ #ifndef tlb_flush -#if defined(tlb_start_vma) || defined(tlb_end_vma) -#error Default tlb_flush() relies on default tlb_start_vma() and tlb_end_vma() -#endif -/* * When an architecture does not provide its own tlb_flush() implementation * but does have a reasonably efficient flush_vma_range() implementation @@ -498,7 +491,6 @@ static inline unsigned long tlb_get_unma* case where we're doing a full MM flush. When we're doing a munmap, * the vmas are adjusted to only cover the region to be torn down. */ -#ifndef tlb_start_vmastatic inline void tlb_start_vma (struct mmu_gather *tlb,struct vm_area_struct *vma) {if (tlb->fullmm)@@ -509,9 +501,7 @@ static inline void tlb_start_vma(structflush_cache_range (vma, vma->vm_start, vma->vm_end);#endif } -#endif -#ifndef tlb_end_vmastatic inline void tlb_end_vma (struct mmu_gather *tlb,struct vm_area_struct *vma) {if (tlb->fullmm ||IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS ))
include/asm-generic/tlb.h static inline void tlb_end_vma(struct mm · line 528
Quoted 4 lines
include/asm-generic/tlb.h +0
-1@@ -525,7 +515,6 @@ static inline void tlb_end_vma(struct mm*/ tlb_flush_mmu_tlbonly (tlb); } -#endif
Much nicer:
Will
Peter Zijlstra
maintainerPATCH 4/4 mmu_gather: Force tlb-flush VM_PFNMAP vmas
In reply to Peter ZijlstraJann reported a race between munmap() and unmap_mapping_range(), where unmap_mapping_range() will no-op once unmap_vmas() has unlinked the VMA; however munmap() will not yet have invalidated the TLBs.
Therefore unmap_mapping_range() will complete while there are still (stale) TLB entries for the specified range.
Mitigate this by force flushing TLBs for VM_PFNMAP ranges.
--- include/asm-generic/tlb.h | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-)
include/asm-generic/tlb.h +17 -16
@@ -303,6 +303,7 @@ struct mmu_gather {*/ unsigned int vma_exec : 1; unsigned int vma_huge : 1; + unsigned int vma_pfn : 1;unsigned int batch_count;
@@ -373,7 +374,6 @@ tlb_update_vma_flags(struct mmu_gather *#else /* CONFIG_MMU_GATHER_NO_RANGE */ #ifndef tlb_flush -/* * When an architecture does not provide its own tlb_flush() implementation * but does have a reasonably efficient flush_vma_range() implementation
@@ -393,6 +393,9 @@ static inline void tlb_flush(struct mmu_flush_tlb_range (&vma, tlb->start, tlb->end); } } +#endif + +#endif /* CONFIG_MMU_GATHER_NO_RANGE */ static inline void tlb_update_vma_flags (struct mmu_gather *tlb,struct vm_area_struct *vma)
@@ -410,17 +413,9 @@ tlb_update_vma_flags(struct mmu_gather **/ tlb->vma_huge =is_vm_hugetlb_page (vma); tlb->vma_exec = !!(vma->vm_flags &VM_EXEC ); + tlb->vma_pfn = !!(vma->vm_flags &VM_PFNMAP ); } -#else - -static inline void -tlb_update_vma_flags (struct mmu_gather *tlb,struct vm_area_struct *vma) { } - -#endif - -#endif /* CONFIG_MMU_GATHER_NO_RANGE */ -static inline void tlb_flush_mmu_tlbonly (struct mmu_gather *tlb) {/*
@@ -507,16 +502,22 @@ static inline void tlb_start_vma(structstatic inline void tlb_end_vma (struct mmu_gather *tlb,struct vm_area_struct *vma) { -if (tlb->fullmm ||IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS )) +if (tlb->fullmm)return ;/* -* Do a TLB flush and reset the range at VMA boundaries; this avoids -* the ranges growing with the unused space between consecutive VMAs, -* but also the mmu_gather::vma_* flags from tlb_start_vma() rely on -* this. +* VM_PFNMAP is more fragile because the core mm will not track the +* page mapcount -- there might not be page-frames for these PFNs after +* all. Force flush TLBs for such ranges to avoid munmap() vs +* unmap_mapping_range() races. */ -tlb_flush_mmu_tlbonly (tlb); +if (tlb->vma_pfn || !IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS )) { +/* +* Do a TLB flush and reset the range at VMA boundaries; this avoids +* the ranges growing with the unused space between consecutive VMAs. +*/ +tlb_flush_mmu_tlbonly (tlb); + } }/*
Re: [PATCH 4/4] mmu_gather: Force tlb-flush VM_PFNMAP vmas
Quoted 77 lines · Peter Zijlstra
Jann reported a race between
munmap()andunmap_mapping_range(), whereunmap_mapping_range()will no-op onceunmap_vmas()has unlinked the VMA; howevermunmap()will not yet have invalidated the TLBs.Therefore
unmap_mapping_range()will complete while there are still (stale) TLB entries for the specified range.Mitigate this by force flushing TLBs for
VM_PFNMAPranges.Signed-off-by Peter Zijlstra (Intel) <[email protected]>--- include/asm-generic/tlb.h | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-)include/asm-generic/tlb.h +9
-15@@ -303,6 +303,7 @@ struct mmu_gather {*/ unsigned int vma_exec : 1; unsigned int vma_huge : 1; + unsigned int vma_pfn : 1;unsigned int batch_count;@@ -373,7 +374,6 @@ tlb_update_vma_flags(struct mmu_gather *#else /* CONFIG_MMU_GATHER_NO_RANGE */ #ifndef tlb_flush -/* * When an architecture does not provide its own tlb_flush() implementation * but does have a reasonably efficient flush_vma_range() implementation @@ -393,6 +393,9 @@ static inline void tlb_flush(struct mmu_flush_tlb_range (&vma, tlb->start, tlb->end); } } +#endif + +#endif /* CONFIG_MMU_GATHER_NO_RANGE */ static inline void tlb_update_vma_flags (struct mmu_gather *tlb,struct vm_area_struct *vma)@@ -410,17 +413,9 @@ tlb_update_vma_flags(struct mmu_gather **/ tlb->vma_huge =is_vm_hugetlb_page (vma); tlb->vma_exec = !!(vma->vm_flags &VM_EXEC ); + tlb->vma_pfn = !!(vma->vm_flags &VM_PFNMAP ); } -#else - -static inline void -tlb_update_vma_flags (struct mmu_gather *tlb,struct vm_area_struct *vma) { } - -#endif - -#endif /* CONFIG_MMU_GATHER_NO_RANGE */ -static inline void tlb_flush_mmu_tlbonly (struct mmu_gather *tlb) {/* @@ -507,16 +502,22 @@ static inline void tlb_start_vma(structstatic inline void tlb_end_vma (struct mmu_gather *tlb,struct vm_area_struct *vma) { -if (tlb->fullmm ||IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS )) +if (tlb->fullmm)return ;/* -* Do a TLB flush and reset the range at VMA boundaries; this avoids -* the ranges growing with the unused space between consecutive VMAs, -* but also the mmu_gather::vma_* flags from tlb_start_vma() rely on -* this. +* VM_PFNMAP is more fragile because the core mm will not track the +* page mapcount -- there might not be page-frames for these PFNs after +* all. Force flush TLBs for such ranges to avoid munmap() vs
include/asm-generic/tlb.h static inline void tlb_start_vma(struct · line 520
Quoted 10 lines
include/asm-generic/tlb.h +8
-1+* unmap_mapping_range() races. */ -tlb_flush_mmu_tlbonly (tlb); +if (tlb->vma_pfn || !IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS )) { +/* +* Do a TLB flush and reset the range at VMA boundaries; this avoids +* the ranges growing with the unused space between consecutive VMAs. +*/ +tlb_flush_mmu_tlbonly (tlb); + }
We already have the vma here, so I'm not sure how much the new 'vma_pfn' field really buys us over checking the 'vm_flags', but perhaps that's cleanup for another day.
Will
Peter Zijlstra
maintainerRe: [PATCH 4/4] mmu_gather: Force tlb-flush VM_PFNMAP vmas
Quoted 24 lines · Will Deacon
Quoted 22 lines · Peter Zijlstra
(quoted diff) +8
-6@@ -507,16 +502,22 @@ static inline void tlb_start_vma(struct static inline void tlb_end_vma(struct mmu_gather *tlb, struct vm_area_struct *vma) { - if (tlb->fullmm || IS_ENABLED(CONFIG_MMU_GATHER_MERGE_VMAS)) + if (tlb->fullmm) return; /* - * Do a TLB flush and reset the range at VMA boundaries; this avoids - * the ranges growing with the unused space between consecutive VMAs, - * but also the mmu_gather::vma_* flags from tlb_start_vma() rely on - * this. + * VM_PFNMAP is more fragile because the core mm will not track the + * page mapcount -- there might not be page-frames for these PFNs after + * all. Force flush TLBs for such ranges to avoid munmap() vs + * unmap_mapping_range() races. */ - tlb_flush_mmu_tlbonly(tlb); + if (tlb->vma_pfn || !IS_ENABLED(CONFIG_MMU_GATHER_MERGE_VMAS)) { + /* + * Do a TLB flush and reset the range at VMA boundaries; this avoids
Quoted 8 lines
Review static inline void tlb_start_vma(struct · line 520
Quoted 4 lines
(quoted diff) +4
-0+ * the ranges growing with the unused space between consecutive VMAs. + */ + tlb_flush_mmu_tlbonly(tlb); + }We already have the vma here, so I'm not sure how much the new '
vma_pfn' field really buys us over checking the 'vm_flags', but perhaps that's cleanup for another day.
Duh, that's just me being daft again. For some raisin I was convinced (and failed to check) that we only had the vma at start.
I can easily respin this to not need the extra variable.
How's this then?
---
Subject: mmu_gather: Force tlb-flush VM_PFNMAP vmas
From: Peter Zijlstra <[email protected]>
Date: Thu Jul 7 11:51:16 CEST 2022
Jann reported a race between munmap() and unmap_mapping_range(), where unmap_mapping_range() will no-op once unmap_vmas() has unlinked the VMA; however munmap() will not yet have invalidated the TLBs.
Therefore unmap_mapping_range() will complete while there are still (stale) TLB entries for the specified range.
Mitigate this by force flushing TLBs for VM_PFNMAP ranges.
--- include/asm-generic/tlb.h | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-)
include/asm-generic/tlb.h +12 -6
@@ -507,16 +507,22 @@ static inline void tlb_start_vma(structstatic inline void tlb_end_vma (struct mmu_gather *tlb,struct vm_area_struct *vma) { -if (tlb->fullmm ||IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS )) +if (tlb->fullmm)return ;/* -* Do a TLB flush and reset the range at VMA boundaries; this avoids -* the ranges growing with the unused space between consecutive VMAs, -* but also the mmu_gather::vma_* flags from tlb_start_vma() rely on -* this. +* VM_PFNMAP is more fragile because the core mm will not track the +* page mapcount -- there might not be page-frames for these PFNs after +* all. Force flush TLBs for such ranges to avoid munmap() vs +* unmap_mapping_range() races. */ -tlb_flush_mmu_tlbonly (tlb); +if ((vma->vm_flags &VM_PFNMAP ) || !IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS )) { +/* +* Do a TLB flush and reset the range at VMA boundaries; this avoids +* the ranges growing with the unused space between consecutive VMAs. +*/ +tlb_flush_mmu_tlbonly (tlb); + } }/*
Re: [PATCH 4/4] mmu_gather: Force tlb-flush VM_PFNMAP vmas
In reply to Peter ZijlstraQuoted 43 lines · Peter Zijlstra
Jann reported a race between
munmap()andunmap_mapping_range(), whereunmap_mapping_range()will no-op onceunmap_vmas()has unlinked the VMA; howevermunmap()will not yet have invalidated the TLBs.Therefore
unmap_mapping_range()will complete while there are still (stale) TLB entries for the specified range.Mitigate this by force flushing TLBs for
VM_PFNMAPranges.Signed-off-by Peter Zijlstra (Intel) <[email protected]>--- include/asm-generic/tlb.h | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-)include/asm-generic/tlb.h +4
-1@@ -303,6 +303,7 @@ struct mmu_gather {*/ unsigned int vma_exec : 1; unsigned int vma_huge : 1; + unsigned int vma_pfn : 1;unsigned int batch_count;@@ -373,7 +374,6 @@ tlb_update_vma_flags(struct mmu_gather *#else /* CONFIG_MMU_GATHER_NO_RANGE */ #ifndef tlb_flush -/* * When an architecture does not provide its own tlb_flush() implementation * but does have a reasonably efficient flush_vma_range() implementation @@ -393,6 +393,9 @@ static inline void tlb_flush(struct mmu_flush_tlb_range (&vma, tlb->start, tlb->end); } } +#endif + +#endif /* CONFIG_MMU_GATHER_NO_RANGE */ static inline void tlb_update_vma_flags (struct mmu_gather *tlb,struct vm_area_struct *vma)
include/asm-generic/tlb.h tlb_update_vma_flags(struct mmu_gather * · line 416
Quoted 4 lines
include/asm-generic/tlb.h +1
-0@@ -410,17 +413,9 @@ tlb_update_vma_flags(struct mmu_gather **/ tlb->vma_huge =is_vm_hugetlb_page (vma); tlb->vma_exec = !!(vma->vm_flags &VM_EXEC ); + tlb->vma_pfn = !!(vma->vm_flags &VM_PFNMAP );
We should probably handle VM_MIXEDMAP the same way as VM_PFNMAP here, I think? Conceptually I think the same issue can happen with device-owned pages that aren't managed by the kernel's page allocator, and for those, VM_MIXEDMAP is the same as VM_PFNMAP.
Quoted 15 lines
include/asm-generic/tlb.h +0
-9} -#else - -static inline void -tlb_update_vma_flags (struct mmu_gather *tlb,struct vm_area_struct *vma) { } - -#endif - -#endif /* CONFIG_MMU_GATHER_NO_RANGE */ -static inline void tlb_flush_mmu_tlbonly (struct mmu_gather *tlb) {/*
include/asm-generic/tlb.h static inline void tlb_start_vma(struct · line 506
Quoted 6 lines
include/asm-generic/tlb.h +1
-1@@ -507,16 +502,22 @@ static inline void tlb_start_vma(structstatic inline void tlb_end_vma (struct mmu_gather *tlb,struct vm_area_struct *vma) { -if (tlb->fullmm ||IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS )) +if (tlb->fullmm)return ;
Is this correct, or would there still be a race between MM teardown (which sets ->fullmm, see exit_mmap()->tlb_gather_mmu_fullmm()) and unmap_mapping_range()? My understanding is that ->fullmm only guarantees a flush at tlb_finish_mmu(), but here we're trying to ensure a flush before unlink_file_vma().
include/asm-generic/tlb.h static inline void tlb_start_vma(struct · line 512
Quoted 9 lines
include/asm-generic/tlb.h +4
-4/* -* Do a TLB flush and reset the range at VMA boundaries; this avoids -* the ranges growing with the unused space between consecutive VMAs, -* but also the mmu_gather::vma_* flags from tlb_start_vma() rely on -* this. +* VM_PFNMAP is more fragile because the core mm will not track the +* page mapcount -- there might not be page-frames for these PFNs after +* all. Force flush TLBs for such ranges to avoid munmap() vs +* unmap_mapping_range() races.
Maybe add: "We do *not* guarantee that after munmap() has passed through tlb_end_vma(), there are no more stale TLB entries for this VMA; there could be a parallel PTE-zapping operation that has zapped PTEs before we looked at them but hasn't done the corresponding TLB flush yet. However, such a parallel zap can't be done through the mm_struct (we've unlinked the VMA), so it would have to be done under the ->i_mmap_sem in read mode, which we synchronize against in unlink_file_vma()."
I'm not convinced it's particularly nice to do a flush in tlb_end_vma() when we can't make guarantees about the TLB state wrt parallel invalidations, and when we only really care about having a flush between unmap_vmas() and free_pgtables(), but I guess it works?
Quoted 14 lines
include/asm-generic/tlb.h +7
-1*/ -tlb_flush_mmu_tlbonly (tlb); +if (tlb->vma_pfn || !IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS )) { +/* +* Do a TLB flush and reset the range at VMA boundaries; this avoids +* the ranges growing with the unused space between consecutive VMAs. +*/ +tlb_flush_mmu_tlbonly (tlb); + } }/*
Peter Zijlstra
maintainerRe: [PATCH 4/4] mmu_gather: Force tlb-flush VM_PFNMAP vmas
Quoted 41 lines · Jann Horn
Quoted 40 lines · Peter Zijlstra
Jann reported a race between
munmap()andunmap_mapping_range(), whereunmap_mapping_range()will no-op onceunmap_vmas()has unlinked the VMA; howevermunmap()will not yet have invalidated the TLBs.Therefore
unmap_mapping_range()will complete while there are still (stale) TLB entries for the specified range.Mitigate this by force flushing TLBs for
VM_PFNMAPranges.Signed-off-by Peter Zijlstra (Intel) <[email protected]>--- include/asm-generic/tlb.h | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-)include/asm-generic/tlb.h +4
-1@@ -303,6 +303,7 @@ struct mmu_gather {*/ unsigned int vma_exec : 1; unsigned int vma_huge : 1; + unsigned int vma_pfn : 1;unsigned int batch_count;@@ -373,7 +374,6 @@ tlb_update_vma_flags(struct mmu_gather *#else /* CONFIG_MMU_GATHER_NO_RANGE */ #ifndef tlb_flush -/* * When an architecture does not provide its own tlb_flush() implementation * but does have a reasonably efficient flush_vma_range() implementation @@ -393,6 +393,9 @@ static inline void tlb_flush(struct mmu_flush_tlb_range (&vma, tlb->start, tlb->end); } } +#endif + +#endif /* CONFIG_MMU_GATHER_NO_RANGE */
Quoted 12 lines
include/asm-generic/tlb.h tlb_update_vma_flags(struct mmu_gather * · line 416
Quoted 7 lines
include/asm-generic/tlb.h +1
-0static inline void tlb_update_vma_flags (struct mmu_gather *tlb,struct vm_area_struct *vma)@@ -410,17 +413,9 @@ tlb_update_vma_flags(struct mmu_gather **/ tlb->vma_huge =is_vm_hugetlb_page (vma); tlb->vma_exec = !!(vma->vm_flags &VM_EXEC ); + tlb->vma_pfn = !!(vma->vm_flags &VM_PFNMAP );We should probably handle
VM_MIXEDMAPthe same way asVM_PFNMAPhere, I think? Conceptually I think the same issue can happen with device-owned pages that aren't managed by the kernel's page allocator, and for those,VM_MIXEDMAPis the same asVM_PFNMAP.
Hmm, yeah, that seems to make sense.
Quoted 16 lines
Quoted 16 lines
include/asm-generic/tlb.h +0
-9} -#else - -static inline void -tlb_update_vma_flags (struct mmu_gather *tlb,struct vm_area_struct *vma) { } - -#endif - -#endif /* CONFIG_MMU_GATHER_NO_RANGE */ -static inline void tlb_flush_mmu_tlbonly (struct mmu_gather *tlb) {/* @@ -507,16 +502,22 @@ static inline void tlb_start_vma(struct
Quoted 11 lines
include/asm-generic/tlb.h static inline void tlb_start_vma(struct · line 506
Quoted 5 lines
include/asm-generic/tlb.h +1
-1static inline void tlb_end_vma (struct mmu_gather *tlb,struct vm_area_struct *vma) { -if (tlb->fullmm ||IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS )) +if (tlb->fullmm)return ;Is this correct, or would there still be a race between MM teardown (which sets ->fullmm, see exit_mmap()->tlb_gather_mmu_fullmm()) and
unmap_mapping_range()? My understanding is that ->fullmm only guarantees a flush attlb_finish_mmu(), but here we're trying to ensure a flush beforeunlink_file_vma().
fullmm is when the last user of the mm goes away, there should not be any races on the address space at that time. Also see the comment with tlb_gather_mmu_fullmm() and its users.
Quoted 11 lines
include/asm-generic/tlb.h static inline void tlb_start_vma(struct · line 511
Quoted 9 lines
include/asm-generic/tlb.h +4
-4/* -* Do a TLB flush and reset the range at VMA boundaries; this avoids -* the ranges growing with the unused space between consecutive VMAs, -* but also the mmu_gather::vma_* flags from tlb_start_vma() rely on -* this. +* VM_PFNMAP is more fragile because the core mm will not track the +* page mapcount -- there might not be page-frames for these PFNs after +* all. Force flush TLBs for such ranges to avoid munmap() vs +* unmap_mapping_range() races. Maybe add: "We do *not* guarantee that after
munmap()has passed
^ otherwise?
Quoted 7 lines
through
tlb_end_vma(), there are no more stale TLB entries for this VMA; there could be a parallel PTE-zapping operation that has zapped PTEs before we looked at them but hasn't done the corresponding TLB flush yet. However, such a parallel zap can't be done through themm_struct(we've unlinked the VMA), so it would have to be done under the ->i_mmap_sem in read mode, which we synchronize against inunlink_file_vma()."
Done.
Quoted 4 lines
I'm not convinced it's particularly nice to do a flush in
tlb_end_vma()when we can't make guarantees about the TLB state wrt parallel invalidations, and when we only really care about having a flush betweenunmap_vmas()andfree_pgtables(), but I guess it works?
Yeah, none of this is pretty. I despise this whole parallel invalidation stuff with a passion, we've had ever so many bugs because of that :-(
We could add another mmu_gather callback and place it between unmap_vmas() and free_pgtables(), but it's a much larger patch and I'm not entirely sure it's worth the complexity.
OTOH having a callback between freeing pages and freeing page-tables might not be the worst idea. Let me ponder that for a bit.
Meanwhile; updated patch below.
---
Subject: mmu_gather: Force TLB-flush VM_PFNMAP|VM_MIXEDMAP vmas
From: Peter Zijlstra <[email protected]>
Date: Thu Jul 7 11:51:16 CEST 2022
Jann reported a race between munmap() and unmap_mapping_range(), where unmap_mapping_range() will no-op once unmap_vmas() has unlinked the VMA; however munmap() will not yet have invalidated the TLBs.
Therefore unmap_mapping_range() will complete while there are still (stale) TLB entries for the specified range.
Mitigate this by force flushing TLBs for VM_PFNMAP ranges.
--- include/asm-generic/tlb.h | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-)
include/asm-generic/tlb.h +22 -6
@@ -507,16 +507,32 @@ static inline void tlb_start_vma(structstatic inline void tlb_end_vma (struct mmu_gather *tlb,struct vm_area_struct *vma) { -if (tlb->fullmm ||IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS )) +if (tlb->fullmm)return ;/* -* Do a TLB flush and reset the range at VMA boundaries; this avoids -* the ranges growing with the unused space between consecutive VMAs, -* but also the mmu_gather::vma_* flags from tlb_start_vma() rely on -* this. +* VM_PFNMAP|VM_MIXEDMAP is more fragile because the core mm will not +* track the page mapcount -- there might not be page-frames for these +* PFNs after all. Force flush TLBs for such ranges to avoid munmap() +* vs unmap_mapping_range() races. +* +* We do *NOT* guarantee that after munmap() has passed through +* tlb_end_vma(), there are no more stale TLB entries for this VMA; +* there could be a parallel PTE-zapping operation that has zapped PTEs +* before we looked at them but hasn't done the corresponding TLB flush +* yet. However, such a parallel zap can't be done through the +* mm_struct (we've unliked the VMA), so it would have to be done under +* the ->i_mmap_sem in read move, which we synchronize against in +* unlink_file_vma(). */ -tlb_flush_mmu_tlbonly (tlb); +if ((vma->vm_flags & (VM_PFNMAP |VM_MIXEDMAP )) || + !IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS )) { +/* +* Do a TLB flush and reset the range at VMA boundaries; this avoids +* the ranges growing with the unused space between consecutive VMAs. +*/ +tlb_flush_mmu_tlbonly (tlb); + } }/*
Re: [PATCH 4/4] mmu_gather: Force tlb-flush VM_PFNMAP vmas
Quoted 16 lines · Peter Zijlstra
Quoted 14 lines · Jann Horn
include/asm-generic/tlb.h static inline void tlb_start_vma(struct · line 506
Quoted 7 lines · Peter Zijlstra
include/asm-generic/tlb.h +1
-1@@ -507,16 +502,22 @@ static inline void tlb_start_vma(structstatic inline void tlb_end_vma (struct mmu_gather *tlb,struct vm_area_struct *vma) { -if (tlb->fullmm ||IS_ENABLED (CONFIG_MMU_GATHER_MERGE_VMAS )) +if (tlb->fullmm)return ;Is this correct, or would there still be a race between MM teardown (which sets ->fullmm, see exit_mmap()->tlb_gather_mmu_fullmm()) and
unmap_mapping_range()? My understanding is that ->fullmm only guarantees a flush attlb_finish_mmu(), but here we're trying to ensure a flush beforeunlink_file_vma().
Quoted 1 line
fullmm is when the last user of the mm goes away, there should not be
(FWIW, there also seems to be an error path in write_ldt -> free_ldt_pgtables -> tlb_gather_mmu_fullmm where ->fullmm can be set for a TLB shootdown in a live process, but that's irrelevant for this patch.)
Quoted 2 lines
any races on the address space at that time. Also see the comment with
tlb_gather_mmu_fullmm()and its users.
Ah, right, aside from the LDT weirdness, fullmm is only used in exit_mmap, and at that point there can be no more parallel access to the MM except for remote memory reaping (which is synchronized against using mmap_write_lock()) and rmap walks...
Quoted 8 lines
Subject:
mmu_gather: Force TLB-flush VM_PFNMAP|VM_MIXEDMAP vmas
From: Peter Zijlstra <[email protected]>
Date: Thu Jul 7 11:51:16 CEST 2022Jann reported a race between
munmap()andunmap_mapping_range(), whereunmap_mapping_range()will no-op onceunmap_vmas()has unlinked the VMA; howevermunmap()will not yet have invalidated the TLBs.
Quoted 7 lines
Therefore
unmap_mapping_range()will complete while there are still (stale) TLB entries for the specified range.Mitigate this by force flushing TLBs for
VM_PFNMAPranges.Reported-by Jann Horn <[email protected]>Signed-off-by Peter Zijlstra (Intel) <[email protected]>
Looks good to me.
Peter Zijlstra
maintainerQuoted 15 lines · Peter Zijlstra
Hi,
These 4 patches implement the suggestion by Linus.
The first patch nukes all architecture implementations of tlb_{start,end}_vma() by adding two additional
MMU_GATHERconfig knobs.DaveM, please clarify the sparc64 case, it seems a little odd to have
flush_cache_range()but to explicitly not call it at unmap time. It would be nice to remove this one special case.The second patch isn't strictly needed for this series but since I was looking at all this, I figured C-SKY didn't make a whole lot of sense. Guo, please have a look.
Quoted 8 lines
The third patch cleans up the #ifdeffery and provides a single tlb_{start,end}_vma() implementation for everyone.
The fourth, and final, patch does the force TLB-flush on
VM_PFNMAPthing.I've had the first three patches exposed to the robots and they've (so far) not reported build (or other) fail.
What are we doing with these patches? Andrew will you pick them up, or will I smuggle them in x86/mm or something ?
Quoted 3 lines · Peter Zijlstra
What are we doing with these patches? Andrew will you pick them up, or will I smuggle them in x86/mm or something ?
I'm picking up other random things right now anyway as people have emailed me, so I'll just do that with this series too.
Linus
Quoted 3 lines · Linus Torvalds
I'm picking up other random things right now anyway as people have emailed me, so I'll just do that with this series too.
.. and I took the suggestion from Jann to make that last patch also check VM_MIXEDMAP in addition to VM_PFNMAP.
Linus