Skip to contentSkip to conversation

TLB and PTE coherency during munmap

PATCH New Max Filippov 8 participants 36 messages May 26, 2013 – Jun 5, 2013

Subsystems (7)
lore
Share & tools
lore
#0 lore raw

Hello arch and mm people.

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

I've noticed that with xtensa arch when I added a test before returning to userspace checking that TLB contents agrees with page tables of the current mm. This check reliably fires with the LTP test mtest05 that maps, unmaps and accesses memory from multiple threads.

Is there anything wrong in my description, maybe something specific to my arch, or this issue really exists?

I've also noticed that there are a lot of arches with default implementations of the involved functions, does that mean that any/all of them have this issue too?

Signature · 2 lines
Thanks.
-- Max
#1 lore raw

Hello arch and mm people.

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

I've noticed that with xtensa arch when I added a test before returning to userspace checking that TLB contents agrees with page tables of the current mm. This check reliably fires with the LTP test mtest05 that maps, unmaps and accesses memory from multiple threads.

Is there anything wrong in my description, maybe something specific to my arch, or this issue really exists?

I've also noticed that there are a lot of arches with default implementations of the involved functions, does that mean that any/all of them have this issue too?

Signature · 2 lines
Thanks.
-- Max
#2 lore raw
Quoted 23 lines · Max Filippov

Hello arch and mm people.

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

Quoted 7 lines

I've noticed that with xtensa arch when I added a test before returning to userspace checking that TLB contents agrees with page tables of the current mm. This check reliably fires with the LTP test mtest05 that maps, unmaps and accesses memory from multiple threads.

Is there anything wrong in my description, maybe something specific to my arch, or this issue really exists?

Hi,

I've made similar checking function for MIPS (because qemu is my only choice and it simulates MIPS TLB) and ran my tests on mips-malta machine in qemu. With MIPS I can also see this issue. I hope I did it right, the patch at the bottom is for the reference. The test I run and the diagnostic output are as follows:

# ./runltp -p -q -T 100 -s mtest05
...
mmstress    0  TINFO  :  test2: Test case tests the race condition between simultaneous write faults in the same address space.
[  439.010000] 14: 70d68000: 03178000/00000000
mmstress    2  TPASS  :  TEST 2 Passed
...
mmstress    0  TINFO  :  test2: Test case tests the race condition between simultaneous write faults in the same address space.
[  947.390000] 10: 6f9d2000: 03639000/00000000
[  947.390000] 10: 6f9d3000: 03638000/00000000
mmstress    2  TPASS  :  TEST 2 Passed
...
mmstress    0  TINFO  :  test1: Test case tests the race condition between simultaneous read faults in the same address space.
[ 1922.680000] 10: 68e12000: 03b59000/00000000
[ 1922.680000] 10: 68e13000: 03b58000/00000000
mmstress    1  TPASS  :  TEST 1 Passed
...

To me it looks like the cond_resched in the zap_pmd_range is the root cause of this issue (let alone SMP case for now). It was introduced in the commit

commit 97a894136f29802da19a15541de3c019e1ca147e

Author: Peter Zijlstra <[email protected]>
Date:   Tue May 24 17:12:04 2011 -0700

mm: Remove i_mmap_lock lockbreak

Peter, Kamezawa, other reviewers of that commit, could you please comment?

------8<------
From 29324d01c0c95b11fbfe509d9b0eae42cab6d380 Mon Sep 17 00:00:00 2001
From: Max Filippov <[email protected]>
Date: Tue, 28 May 2013 08:06:03 +0400
Subject: [PATCH] mips: check TLB sanity on return to userspace

---
 arch/mips/kernel/entry.S |    1 +
 arch/mips/lib/dump_tlb.c |   81 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 82 insertions(+), 0 deletions(-)
arch/mips/kernel/entry.S +1 -0
index e578685..82881ac 100644
@@ -49,6 +49,7 @@ resume_userspace:
 	local_irq_disable		# make sure we dont miss an
 					# interrupt setting need_resched
 					# between sampling and return
+	jal	check_tlb_all
 	LONG_L	a2, TI_FLAGS($28)	# current->work
 	andi	t0, a2, _TIF_WORK_MASK	# (ignoring syscall_trace)
 	bnez	t0, work_pending
arch/mips/lib/dump_tlb.c +81 -0

90 lines not on this page · raw message

Signature · 6 lines
1.7.7.6


-- 
Thanks.
-- Max
maintainer
#3 lore raw
Quoted 43 lines · Max Filippov
Quoted 23 lines · Max Filippov

Hello arch and mm people.

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

Quoted 7 lines

I've noticed that with xtensa arch when I added a test before returning to userspace checking that TLB contents agrees with page tables of the current mm. This check reliably fires with the LTP test mtest05 that maps, unmaps and accesses memory from multiple threads.

Is there anything wrong in my description, maybe something specific to my arch, or this issue really exists?

Hi,

I've made similar checking function for MIPS (because qemu is my only choice and it simulates MIPS TLB) and ran my tests on mips-malta machine in qemu. With MIPS I can also see this issue. I hope I did it right, the patch at the bottom is for the reference. The test I run and the diagnostic output are as follows:

To me it looks like the cond_resched in the zap_pmd_range is the root cause of this issue (let alone SMP case for now). It was introduced in the commit

Quoted 7 lines

commit 97a894136f29802da19a15541de3c019e1ca147e

Author: Peter Zijlstra <[email protected]>
Date:   Tue May 24 17:12:04 2011 -0700

mm: Remove i_mmap_lock lockbreak

Peter, Kamezawa, other reviewers of that commit, could you please comment?

Are you all running UP systems? I suppose the preemptible muck invalidated the assumption that UP systems are 'easy'.

If you make tlb_fast_mode() return an unconditional false, does it all work again?

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#4 lore raw
Quoted 50 lines · Peter Zijlstra
Quoted 49 lines · Max Filippov
Quoted 23 lines · Max Filippov

Hello arch and mm people.

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

Quoted 7 lines

I've noticed that with xtensa arch when I added a test before returning to userspace checking that TLB contents agrees with page tables of the current mm. This check reliably fires with the LTP test mtest05 that maps, unmaps and accesses memory from multiple threads.

Is there anything wrong in my description, maybe something specific to my arch, or this issue really exists?

Hi,

I've made similar checking function for MIPS (because qemu is my only choice and it simulates MIPS TLB) and ran my tests on mips-malta machine in qemu. With MIPS I can also see this issue. I hope I did it right, the patch at the bottom is for the reference. The test I run and the diagnostic output are as follows:

To me it looks like the cond_resched in the zap_pmd_range is the root cause of this issue (let alone SMP case for now). It was introduced in the commit

commit 97a894136f29802da19a15541de3c019e1ca147e

Author: Peter Zijlstra <[email protected]>
Date:   Tue May 24 17:12:04 2011 -0700

mm: Remove i_mmap_lock lockbreak

Quoted 8 lines
Quoted 1 line

Peter, Kamezawa, other reviewers of that commit, could you please comment?

Are you all running UP systems? I suppose the preemptible muck invalidated the assumption that UP systems are 'easy'.

If you make tlb_fast_mode() return an unconditional false, does it all work again?

It seems tlb_fast_mode() only affects the page free batching and won't affect the TLB flush themselves unless ofcourse the batching runs out of space.

FWIW, prior to your commit d16dfc550f5326 "mm: mmu_gather rework" tlb_finish_mmu() right before the need_resced() which would have handled the current situation. My proposal - please see my earlier email in thread is to reuse the force_flush logic in zap_pte_range() to do this.

-Vineet

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

maintainer
#5 lore raw
Quoted 2 lines · Vineet Gupta

It seems tlb_fast_mode() only affects the page free batching and won't affect the TLB flush themselves unless ofcourse the batching runs out of space.

It does, it will keep the pages around until after a flush. So you'll no longer free pages before having done a TLB flush.

Quoted 4 lines

FWIW, prior to your commit d16dfc550f5326 "mm: mmu_gather rework" tlb_finish_mmu() right before the need_resced() which would have handled the current situation. My proposal - please see my earlier email in thread is to reuse the force_flush logic in zap_pte_range() to do this.

I don't have earlier emails as my lkml folder is somewhat broken atm :/

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

maintainer
#6 lore raw
In reply to Vineet Gupta

What about something like this?

---
 include/asm-generic/tlb.h | 11 ++++++++++-
 mm/memory.c               | 17 ++++++++++++++++-
 2 files changed, 26 insertions(+), 2 deletions(-)
include/asm-generic/tlb.h +10 -1
index b1b1fa6..651b1cf 100644
@@ -116,6 +116,7 @@ struct mmu_gather {
 
 static inline int tlb_fast_mode(struct mmu_gather *tlb)
 {
+#ifndef CONFIG_PREEMPT
 #ifdef CONFIG_SMP
 	return tlb->fast_mode;
 #else
@@ -124,7 +125,15 @@ static inline int tlb_fast_mode(struct mmu_gather *tlb)
 	 * and page free order so much..
 	 */
 	return 1;
-#endif
+#endif /* CONFIG_SMP */
+#else  /* CONFIG_PREEMPT */
+	/*
+	 * Since mmu_gather is preemptible, preemptible kernels are like SMP
+	 * kernels, we must batch to make sure we invalidate TLBs before we
+	 * free the pages.
+	 */
+	return 0;
+#endif /* CONFIG_PREEMPT */
 }
 
 void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, bool fullmm);
mm/memory.c +16 -1
index 6dc1882..e915af2 100644
@@ -384,6 +384,21 @@ void tlb_remove_table(struct mmu_gather *tlb, void *table)
 
 #endif /* CONFIG_HAVE_RCU_TABLE_FREE */
 
+static inline void cond_resched_tlb(struct mmu_gather *tlb)
+{
+#ifndef CONFIG_PREEMPT
+	/*
+	 * For full preempt kernels we must do regular batching like
+	 * SMP, see tlb_fast_mode(). For !PREEMPT we can 'cheat' and
+	 * do a flush before our voluntary 'yield'.
+	 */
+	if (need_resched()) {
+		tlb_flush_mmu(tlb);
+		cond_resched();
+	}
+#endif
+}
+
 /*
  * If a p?d_bad entry is found while walking page tables, report
  * the error, before resetting entry to p?d_none.  Usually (but
@@ -1264,7 +1279,7 @@ static inline unsigned long zap_pmd_range(struct mmu_gather *tlb,
 			goto next;
 		next = zap_pte_range(tlb, vma, pmd, addr, next, details);
 next:
-		cond_resched();
+		cond_resched_tlb(tlb);
 	} while (pmd++, addr = next, addr != end);
 
 	return addr;

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#7 lore raw
Quoted 10 lines · Peter Zijlstra
mm/memory.c +4 -0
@@ -384,6 +384,21 @@ void tlb_remove_table(struct mmu_gather *tlb, void *table)
 
 #endif /* CONFIG_HAVE_RCU_TABLE_FREE */
 
+static inline void cond_resched_tlb(struct mmu_gather *tlb)
+{
+#ifndef CONFIG_PREEMPT
+       /*

mm/memory.c void tlb_remove_table(struct mmu_gather *tlb, void *table) · line 400

Quoted 10 lines
mm/memory.c +10 -0
+        * For full preempt kernels we must do regular batching like
+        * SMP, see tlb_fast_mode(). For !PREEMPT we can 'cheat' and
+        * do a flush before our voluntary 'yield'.
+        */
+       if (need_resched()) {
+               tlb_flush_mmu(tlb);
+               cond_resched();
+       }
+#endif
+}

Does it matter that in the CONFIG_PREEMPT case, you no longer call cond_resched()? I guess we can just rely on the kernel full preemption to reschedule as needed.

--
Catalin

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

maintainer
#8 lore raw
Quoted 17 lines · Catalin Marinas
Quoted 16 lines · Peter Zijlstra
mm/memory.c +10 -0
@@ -384,6 +384,21 @@ void tlb_remove_table(struct mmu_gather *tlb, void *table)
 
 #endif /* CONFIG_HAVE_RCU_TABLE_FREE */
 
+static inline void cond_resched_tlb(struct mmu_gather *tlb)
+{
+#ifndef CONFIG_PREEMPT
+       /*
+        * For full preempt kernels we must do regular batching like
+        * SMP, see tlb_fast_mode(). For !PREEMPT we can 'cheat' and
+        * do a flush before our voluntary 'yield'.
+        */
+       if (need_resched()) {
+               tlb_flush_mmu(tlb);
Quoted 8 lines

mm/memory.c void tlb_remove_table(struct mmu_gather *tlb, void *table) · line 400

Quoted 4 lines
mm/memory.c +4 -0
+               cond_resched();
+       }
+#endif
+}

Does it matter that in the CONFIG_PREEMPT case, you no longer call cond_resched()? I guess we can just rely on the kernel full preemption to reschedule as needed.

Exactly, the preempt_enable from the spin_unlock in pte_unmap_unlock() will most likely immediately trigger a preemption.

And since we do full batching for PREEMPT doing extra flushes would be detrimental for performance -- however unlikely it is we'll still see the need_resched() there.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#9 lore raw
In reply to Peter Zijlstra
Quoted 24 lines · Peter Zijlstra

What about something like this?

---
 include/asm-generic/tlb.h | 11 ++++++++++-
 mm/memory.c               | 17 ++++++++++++++++-
 2 files changed, 26 insertions(+), 2 deletions(-)
include/asm-generic/tlb.h +1 -1
index b1b1fa6..651b1cf 100644
@@ -116,6 +116,7 @@ struct mmu_gather {
 
 static inline int tlb_fast_mode(struct mmu_gather *tlb)
 {
+#ifndef CONFIG_PREEMPT
 #ifdef CONFIG_SMP
 	return tlb->fast_mode;
 #else
@@ -124,7 +125,15 @@ static inline int tlb_fast_mode(struct mmu_gather *tlb)
 	 * and page free order so much..
 	 */
 	return 1;
-#endif

include/asm-generic/tlb.h static inline int tlb_fast_mode(struct mmu_gather *tlb) · line 137

Quoted 10 lines
include/asm-generic/tlb.h +9 -0
+#endif /* CONFIG_SMP */
+#else  /* CONFIG_PREEMPT */
+	/*
+	 * Since mmu_gather is preemptible, preemptible kernels are like SMP
+	 * kernels, we must batch to make sure we invalidate TLBs before we
+	 * free the pages.
+	 */
+	return 0;
+#endif /* CONFIG_PREEMPT */
 }

So this adds the page batching logic to small/simpler UP systems - but it's necessary evil :-(

Quoted 8 lines
include/asm-generic/tlb.h +0 -0
 void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, bool fullmm);
mm/memory.c +0 -0
index 6dc1882..e915af2 100644
@@ -384,6 +384,21 @@ void tlb_remove_table(struct mmu_gather *tlb, void *table)
 
 #endif /* CONFIG_HAVE_RCU_TABLE_FREE */

mm/memory.c void tlb_remove_table(struct mmu_gather *tlb, void *table) · line 395

Quoted 10 lines
mm/memory.c +9 -0
 
+static inline void cond_resched_tlb(struct mmu_gather *tlb)
+{
+#ifndef CONFIG_PREEMPT
+	/*
+	 * For full preempt kernels we must do regular batching like
+	 * SMP, see tlb_fast_mode(). For !PREEMPT we can 'cheat' and
+	 * do a flush before our voluntary 'yield'.
+	 */
+	if (need_resched()) {

This is really neat: w/o this check, a @fullmm flush (exit/execve) would have suffered multiple full TLB flushes in the loop, now you do that only if a scheduling was needed - meaning only in the case when we have the potential race condition which Max was seeing. Cool !

Quoted 10 lines
mm/memory.c +6 -0
+		tlb_flush_mmu(tlb);
+		cond_resched();
+	}
+#endif
+}
+
 /*
  * If a p?d_bad entry is found while walking page tables, report
  * the error, before resetting entry to p?d_none.  Usually (but

mm/memory.c static inline unsigned long zap_pmd_range(struct mmu_gather *tlb, · line 1285

Quoted 8 lines
mm/memory.c +1 -1
@@ -1264,7 +1279,7 @@ static inline unsigned long zap_pmd_range(struct mmu_gather *tlb,
 			goto next;
 		next = zap_pte_range(tlb, vma, pmd, addr, next, details);
 next:
-		cond_resched();
+		cond_resched_tlb(tlb);
 	} while (pmd++, addr = next, addr != end);
 
 	return addr;

BTW, since we are on the topic, it seems that we are missing tlb_fast_mode() in one spot - unless it is tied to rcu table free stuff.

-------------->
From: Vineet Gupta <[email protected]>
Date: Thu, 30 May 2013 10:25:30 +0530
Subject: [PATCH] mm: tlb_fast_mode check missing in tlb_finish_mmu()

---
 mm/memory.c |    3 +++
 1 file changed, 3 insertions(+)
mm/memory.c +0 -0
index d9d5fd9..569ffe1 100644
@@ -269,6 +269,9 @@ void tlb_finish_mmu(struct mmu_gather *tlb, unsigned long

start, unsigned long e

     /* keep the page table cache within bounds */
     check_pgt_cache();

+ if (tlb_fast_mode(tlb))
+ return;
+

     for (batch = tlb->local.next; batch; batch = next) {
         next = batch->next;
         free_pages((unsigned long)batch, 0);
Signature · 1 line
1.7.10.4
maintainer
#10 lore raw
Quoted 30 lines · Vineet Gupta
Quoted 29 lines · Peter Zijlstra

What about something like this?

---
 include/asm-generic/tlb.h | 11 ++++++++++-
 mm/memory.c               | 17 ++++++++++++++++-
 2 files changed, 26 insertions(+), 2 deletions(-)
include/asm-generic/tlb.h +6 -1
index b1b1fa6..651b1cf 100644
@@ -116,6 +116,7 @@ struct mmu_gather {
 
 static inline int tlb_fast_mode(struct mmu_gather *tlb)
 {
+#ifndef CONFIG_PREEMPT
 #ifdef CONFIG_SMP
 	return tlb->fast_mode;
 #else
@@ -124,7 +125,15 @@ static inline int tlb_fast_mode(struct mmu_gather *tlb)
 	 * and page free order so much..
 	 */
 	return 1;
-#endif
+#endif /* CONFIG_SMP */
+#else  /* CONFIG_PREEMPT */
+	/*
+	 * Since mmu_gather is preemptible, preemptible kernels are like SMP
+	 * kernels, we must batch to make sure we invalidate TLBs before we
Quoted 8 lines

include/asm-generic/tlb.h static inline int tlb_fast_mode(struct mmu_gather *tlb) · line 137

Quoted 5 lines
include/asm-generic/tlb.h +4 -0
+	 * free the pages.
+	 */
+	return 0;
+#endif /* CONFIG_PREEMPT */
 }

So this adds the page batching logic to small/simpler UP systems - but it's necessary evil :-(

Well, only if you have CONFIG_PREEMPT=y, at which point UP isn't really _that_ simply anymore really.

Quoted 9 lines
Quoted 9 lines
include/asm-generic/tlb.h +0 -0
 void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, bool fullmm);
mm/memory.c +0 -0
index 6dc1882..e915af2 100644
@@ -384,6 +384,21 @@ void tlb_remove_table(struct mmu_gather *tlb, void *table)
 
 #endif /* CONFIG_HAVE_RCU_TABLE_FREE */
 
Quoted 14 lines

mm/memory.c void tlb_remove_table(struct mmu_gather *tlb, void *table) · line 395

Quoted 9 lines
mm/memory.c +9 -0
+static inline void cond_resched_tlb(struct mmu_gather *tlb)
+{
+#ifndef CONFIG_PREEMPT
+	/*
+	 * For full preempt kernels we must do regular batching like
+	 * SMP, see tlb_fast_mode(). For !PREEMPT we can 'cheat' and
+	 * do a flush before our voluntary 'yield'.
+	 */
+	if (need_resched()) {

This is really neat: w/o this check, a @fullmm flush (exit/execve) would have suffered multiple full TLB flushes in the loop, now you do that only if a scheduling was needed - meaning only in the case when we have the potential race condition which Max was seeing. Cool !

Glad you like it ;-)

Quoted 41 lines
Quoted 10 lines
mm/memory.c +6 -0
+		tlb_flush_mmu(tlb);
+		cond_resched();
+	}
+#endif
+}
+
 /*
  * If a p?d_bad entry is found while walking page tables, report
  * the error, before resetting entry to p?d_none.  Usually (but

mm/memory.c static inline unsigned long zap_pmd_range(struct mmu_gather *tlb, · line 1285

Quoted 8 lines
mm/memory.c +1 -1
@@ -1264,7 +1279,7 @@ static inline unsigned long zap_pmd_range(struct mmu_gather *tlb,
 			goto next;
 		next = zap_pte_range(tlb, vma, pmd, addr, next, details);
 next:
-		cond_resched();
+		cond_resched_tlb(tlb);
 	} while (pmd++, addr = next, addr != end);
 
 	return addr;

BTW, since we are on the topic, it seems that we are missing tlb_fast_mode() in one spot - unless it is tied to rcu table free stuff.

-------------->
From: Vineet Gupta <[email protected]>
Date: Thu, 30 May 2013 10:25:30 +0530
Subject: [PATCH] mm: tlb_fast_mode check missing in tlb_finish_mmu()

---
 mm/memory.c |    3 +++
 1 file changed, 3 insertions(+)
mm/memory.c +0 -0
index d9d5fd9..569ffe1 100644
@@ -269,6 +269,9 @@ void tlb_finish_mmu(struct mmu_gather *tlb, unsigned long

start, unsigned long e

     /* keep the page table cache within bounds */
     check_pgt_cache();

mm/memory.c

Quoted 6 lines
mm/memory.c +3 -0
+    if (tlb_fast_mode(tlb))
+        return;
+
     for (batch = tlb->local.next; batch; batch = next) {
         next = batch->next;
         free_pages((unsigned long)batch, 0);

Yes I think that is possible. It would shrink the code a little when fast_mode was unconditionally 1 -- ie. simple UP ;-).

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#11 lore raw
Quoted 25 lines · Peter Zijlstra
Quoted 24 lines · Vineet Gupta

On 05/29/2013 11:21 PM, Peter Zijlstra wrote:

BTW, since we are on the topic, it seems that we are missing tlb_fast_mode() in one spot - unless it is tied to rcu table free stuff.

-------------->
From: Vineet Gupta <[email protected]>
Date: Thu, 30 May 2013 10:25:30 +0530
Subject: [PATCH] mm: tlb_fast_mode check missing in tlb_finish_mmu()

---
 mm/memory.c |    3 +++
 1 file changed, 3 insertions(+)
mm/memory.c +0 -0
index d9d5fd9..569ffe1 100644
@@ -269,6 +269,9 @@ void tlb_finish_mmu(struct mmu_gather *tlb, unsigned long

start, unsigned long e

     /* keep the page table cache within bounds */
     check_pgt_cache();
Quoted 8 lines

mm/memory.c

Quoted 6 lines
mm/memory.c +3 -0
+    if (tlb_fast_mode(tlb))
+        return;
+
     for (batch = tlb->local.next; batch; batch = next) {
         next = batch->next;
         free_pages((unsigned long)batch, 0);

Yes I think that is possible. It would shrink the code a little when fast_mode was unconditionally 1 -- ie. simple UP ;-).

Exactly ! Can you please revert with a Reviewed-by/Acked-by so I can formally send it over to linux-mm list.

Thx,
-Vineet

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

maintainer Acked-by
#12 lore raw
Quoted 35 lines · Vineet Gupta
Quoted 25 lines · Peter Zijlstra
Quoted 24 lines · Vineet Gupta

On 05/29/2013 11:21 PM, Peter Zijlstra wrote:

BTW, since we are on the topic, it seems that we are missing tlb_fast_mode() in one spot - unless it is tied to rcu table free stuff.

-------------->
From: Vineet Gupta <[email protected]>
Date: Thu, 30 May 2013 10:25:30 +0530
Subject: [PATCH] mm: tlb_fast_mode check missing in tlb_finish_mmu()

---
 mm/memory.c |    3 +++
 1 file changed, 3 insertions(+)
mm/memory.c +0 -0
index d9d5fd9..569ffe1 100644
@@ -269,6 +269,9 @@ void tlb_finish_mmu(struct mmu_gather *tlb, unsigned long

start, unsigned long e

     /* keep the page table cache within bounds */
     check_pgt_cache();
Quoted 8 lines

mm/memory.c

Quoted 6 lines
mm/memory.c +3 -0
+    if (tlb_fast_mode(tlb))
+        return;
+
     for (batch = tlb->local.next; batch; batch = next) {
         next = batch->next;
         free_pages((unsigned long)batch, 0);

Yes I think that is possible. It would shrink the code a little when fast_mode was unconditionally 1 -- ie. simple UP ;-).

Quoted 2 lines

Exactly ! Can you please revert with a Reviewed-by/Acked-by so I can formally send it over to linux-mm list.

s/revert/reply/?

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#13 lore raw
In reply to Peter Zijlstra

Hi Peter,

Quoted 1 line · Peter Zijlstra

What about something like this?

With that patch I still get mtest05 firing my TLB/PTE incoherency check in the UP PREEMPT_VOLUNTARY configuration. This happens after zap_pte_range completion in the end of unmap_region because of rescheduling called in the following call chain:

unmap_region
free_pgtables
unlink_anon_vmas
lock_anon_vma_root
down_write
might_sleep
might_resched
_cond_resched

Quoted 72 lines
---
 include/asm-generic/tlb.h | 11 ++++++++++-
 mm/memory.c               | 17 ++++++++++++++++-
 2 files changed, 26 insertions(+), 2 deletions(-)
include/asm-generic/tlb.h +10 -1
index b1b1fa6..651b1cf 100644
@@ -116,6 +116,7 @@ struct mmu_gather {
 
 static inline int tlb_fast_mode(struct mmu_gather *tlb)
 {
+#ifndef CONFIG_PREEMPT
 #ifdef CONFIG_SMP
        return tlb->fast_mode;
 #else
@@ -124,7 +125,15 @@ static inline int tlb_fast_mode(struct mmu_gather *tlb)
         * and page free order so much..
         */
        return 1;
-#endif
+#endif /* CONFIG_SMP */
+#else  /* CONFIG_PREEMPT */
+       /*
+        * Since mmu_gather is preemptible, preemptible kernels are like SMP
+        * kernels, we must batch to make sure we invalidate TLBs before we
+        * free the pages.
+        */
+       return 0;
+#endif /* CONFIG_PREEMPT */
 }
 
 void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, bool fullmm);
mm/memory.c +16 -1
index 6dc1882..e915af2 100644
@@ -384,6 +384,21 @@ void tlb_remove_table(struct mmu_gather *tlb, void *table)
 
 #endif /* CONFIG_HAVE_RCU_TABLE_FREE */
 
+static inline void cond_resched_tlb(struct mmu_gather *tlb)
+{
+#ifndef CONFIG_PREEMPT
+       /*
+        * For full preempt kernels we must do regular batching like
+        * SMP, see tlb_fast_mode(). For !PREEMPT we can 'cheat' and
+        * do a flush before our voluntary 'yield'.
+        */
+       if (need_resched()) {
+               tlb_flush_mmu(tlb);
+               cond_resched();
+       }
+#endif
+}
+
 /*
  * If a p?d_bad entry is found while walking page tables, report
  * the error, before resetting entry to p?d_none.  Usually (but
@@ -1264,7 +1279,7 @@ static inline unsigned long zap_pmd_range(struct mmu_gather *tlb,
                        goto next;
                next = zap_pte_range(tlb, vma, pmd, addr, next, details);
 next:
-               cond_resched();
+               cond_resched_tlb(tlb);
        } while (pmd++, addr = next, addr != end);
 
        return addr;
Signature · 2 lines
Thanks.
-- Max
maintainer
#14 lore raw
Quoted 10 lines · Max Filippov

Hi Peter,

Quoted 1 line · Peter Zijlstra

What about something like this?

With that patch I still get mtest05 firing my TLB/PTE incoherency check in the UP PREEMPT_VOLUNTARY configuration. This happens after zap_pte_range completion in the end of unmap_region because of rescheduling called in the following call chain:

Quoted 9 lines

unmap_region
free_pgtables
unlink_anon_vmas
lock_anon_vma_root
down_write
might_sleep
might_resched
_cond_resched

Hurm, yeah. Catching all regular blocking primitives and making it maintainable is going to be a problem :/

I suppose the easiest thing is simply killing fast_mode for now/

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

maintainer
#15 lore raw
In reply to Max Filippov
Quoted 9 lines · Max Filippov

Hi Peter,

Quoted 1 line · Peter Zijlstra

What about something like this?

With that patch I still get mtest05 firing my TLB/PTE incoherency check in the UP PREEMPT_VOLUNTARY configuration. This happens after zap_pte_range completion in the end of unmap_region because of rescheduling called in the following call chain:

OK, so there two options; completely kill off fast-mode or something like the below where we add magic to the scheduler :/

I'm aware people might object to something like the below -- but since its a possibility I thought we ought to at least mention it.

For those new to the thread; the problem is that since the introduction of preemptible mmu_gather the traditional UP fast-mode is broken. Fast-mode is where we free the pages first and flush TLBs later. This is not a problem if there's no concurrency, but obviously if you can preempt there now is.

I think I prefer completely killing off fast-mode esp. since UP seems to go the way of the Dodo and it does away with an exception in the mmu_gather code.

Anyway; opinions? Linus, Thomas, Ingo?

---
nclude/asm-generic/tlb.h | 19 +++++++++++++++----

 include/linux/sched.h     |  1 +
 kernel/sched/core.c       |  9 +++++++++
 mm/memory.c               |  3 +++
 4 files changed, 28 insertions(+), 4 deletions(-)
include/asm-generic/tlb.h +15 -4
index b1b1fa6..8d84154 100644
@@ -116,15 +116,26 @@ struct mmu_gather {
 
 static inline int tlb_fast_mode(struct mmu_gather *tlb)
 {
+#ifdef CONFIG_PREEMPT
+	/*
+	 * We don't want to add to the schedule fast path for preemptible
+	 * kernels; disable fast mode unconditionally.
+	 */
+	return 0;
+#endif
+
 #ifdef CONFIG_SMP
+	/*
+	 * We can only use fast mode if there's a single CPU online;
+	 * otherwise SMP might trip over stale TLB entries.
+	 */
 	return tlb->fast_mode;
-#else
+#endif
+
 	/*
-	 * For UP we don't need to worry about TLB flush
-	 * and page free order so much..
+	 * Non-preempt UP can do fast mode unconditionally.
 	 */
 	return 1;
-#endif
 }
 
 void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, bool fullmm);
include/linux/sched.h +1 -0
index 178a8d9..3dc6930 100644
@@ -1416,6 +1416,7 @@ struct task_struct {
 	unsigned int	sequential_io;
 	unsigned int	sequential_io_avg;
 #endif
+	struct mmu_gather *tlb;
 };
 
 /* Future-safe accessor for struct task_struct's cpus_allowed. */
kernel/sched/core.c +9 -0
index 36f85be..6829b78 100644
@@ -2374,6 +2374,15 @@ static void __sched __schedule(void)
 	struct rq *rq;
 	int cpu;
 
+#ifndef CONFIG_PREEMPT
+	/*
+	 * We always force batched mmu_gather for preemptible kernels in order
+	 * to minimize scheduling delays. See tlb_fast_mode().
+	 */
+	if (current->tlb && tlb_fast_mode(current->tlb))
+		tlb_flush_mmu(current->tlb);
+#endif
+
 need_resched:
 	preempt_disable();
 	cpu = smp_processor_id();
mm/memory.c +3 -0
index d7d54a1..8925578 100644
@@ -230,6 +230,7 @@ void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, bool fullmm)
 #ifdef CONFIG_HAVE_RCU_TABLE_FREE
 	tlb->batch = NULL;
 #endif
+	current->tlb = tlb;
 }
 
 void tlb_flush_mmu(struct mmu_gather *tlb)
@@ -274,6 +275,8 @@ void tlb_finish_mmu(struct mmu_gather *tlb, unsigned long start, unsigned long e
 		free_pages((unsigned long)batch, 0);
 	}
 	tlb->local.next = NULL;
+
+	current->tlb = NULL;
 }
 
 /* __tlb_remove_page

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#16 lore raw
Quoted 23 lines · Peter Zijlstra
Quoted 9 lines · Max Filippov

Hi Peter,

Quoted 1 line · Peter Zijlstra

What about something like this?

With that patch I still get mtest05 firing my TLB/PTE incoherency check in the UP PREEMPT_VOLUNTARY configuration. This happens after zap_pte_range completion in the end of unmap_region because of rescheduling called in the following call chain:

OK, so there two options; completely kill off fast-mode or something like the below where we add magic to the scheduler :/

I'm aware people might object to something like the below -- but since its a possibility I thought we ought to at least mention it.

For those new to the thread; the problem is that since the introduction of preemptible mmu_gather the traditional UP fast-mode is broken. Fast-mode is where we free the pages first and flush TLBs later. This is not a problem if there's no concurrency, but obviously if you can preempt there now is.

Quoted 5 lines

I think I prefer completely killing off fast-mode esp. since UP seems to go the way of the Dodo and it does away with an exception in the mmu_gather code.

Anyway; opinions? Linus, Thomas, Ingo?

Since UP kernels have not been packaged up by major distros for years, and since the live-patching of SMP kernels (the SMP alternative-instructions patching machinery) does away with a big chunk of the SMP cost, I guess UP kernels are slowly becoming like TINY_RCU: interesting but not really a primary design goal?

( Another reason for reducing SMP vs. UP complexity in this area would be
the fact that we had a few bad regressions lately - the TLB code is not getting simpler, and bugs are getting discovered and fixed slower. )

At least that's the x86 perspective. ARM might still see it differently?

Thanks,

Ingo

#17 lore raw
Quoted 38 lines · Ingo Molnar
Quoted 23 lines · Peter Zijlstra
Quoted 9 lines · Max Filippov

Hi Peter,

Quoted 1 line · Peter Zijlstra

What about something like this?

With that patch I still get mtest05 firing my TLB/PTE incoherency check in the UP PREEMPT_VOLUNTARY configuration. This happens after zap_pte_range completion in the end of unmap_region because of rescheduling called in the following call chain:

OK, so there two options; completely kill off fast-mode or something like the below where we add magic to the scheduler :/

I'm aware people might object to something like the below -- but since its a possibility I thought we ought to at least mention it.

For those new to the thread; the problem is that since the introduction of preemptible mmu_gather the traditional UP fast-mode is broken. Fast-mode is where we free the pages first and flush TLBs later. This is not a problem if there's no concurrency, but obviously if you can preempt there now is.

Quoted 5 lines

I think I prefer completely killing off fast-mode esp. since UP seems to go the way of the Dodo and it does away with an exception in the mmu_gather code.

Anyway; opinions? Linus, Thomas, Ingo?

Since UP kernels have not been packaged up by major distros for years, and since the live-patching of SMP kernels (the SMP alternative-instructions patching machinery) does away with a big chunk of the SMP cost, I guess UP kernels are slowly becoming like TINY_RCU: interesting but not really a primary design goal?

Quoted 5 lines

( Another reason for reducing SMP vs. UP complexity in this area would be
the fact that we had a few bad regressions lately - the TLB code is not getting simpler, and bugs are getting discovered and fixed slower. )

At least that's the x86 perspective. ARM might still see it differently?

On ARM there is a lot of ongoing work on single zImage for multiple SoCs and this implies SMP kernels. There is an SMP_ON_UP feature which does run-time code patching to optimise the UP case in a few places.

Regarding tlb_fast_mode(), the ARM-specific implementation is always 0 on ARMv7 even if UP because of speculative TLB loads (the MMU could pretty much act as a separate processor).

Catalin

maintainer
#18 lore raw
Quoted 7 lines · Catalin Marinas

On ARM there is a lot of ongoing work on single zImage for multiple SoCs and this implies SMP kernels. There is an SMP_ON_UP feature which does run-time code patching to optimise the UP case in a few places.

Regarding tlb_fast_mode(), the ARM-specific implementation is always 0 on ARMv7 even if UP because of speculative TLB loads (the MMU could pretty much act as a separate processor).

Oh right.. I should really refresh the mmu_gather unification patches so all archs are using the generic code.

/me ups on todo list.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#19 lore raw
Quoted 11 lines · Peter Zijlstra
Quoted 7 lines · Catalin Marinas

On ARM there is a lot of ongoing work on single zImage for multiple SoCs and this implies SMP kernels. There is an SMP_ON_UP feature which does run-time code patching to optimise the UP case in a few places.

Regarding tlb_fast_mode(), the ARM-specific implementation is always 0 on ARMv7 even if UP because of speculative TLB loads (the MMU could pretty much act as a separate processor).

Oh right.. I should really refresh the mmu_gather unification patches so all archs are using the generic code.

That would be great! I'll help with testing/review (or if it needs anything else) from an arm/arm64 perspective.

Thanks.

Signature · 1 line
Catalin
maintainer
#20 lore raw
In reply to Peter Zijlstra
Quoted 22 lines · Peter Zijlstra
Quoted 9 lines · Max Filippov

Hi Peter,

Quoted 1 line · Peter Zijlstra

What about something like this?

With that patch I still get mtest05 firing my TLB/PTE incoherency check in the UP PREEMPT_VOLUNTARY configuration. This happens after zap_pte_range completion in the end of unmap_region because of rescheduling called in the following call chain:

OK, so there two options; completely kill off fast-mode or something like the below where we add magic to the scheduler :/

I'm aware people might object to something like the below -- but since its a possibility I thought we ought to at least mention it.

For those new to the thread; the problem is that since the introduction of preemptible mmu_gather the traditional UP fast-mode is broken. Fast-mode is where we free the pages first and flush TLBs later. This is not a problem if there's no concurrency, but obviously if you can preempt there now is.

Quoted 4 lines

I think I prefer completely killing off fast-mode esp. since UP seems to go the way of the Dodo and it does away with an exception in the mmu_gather code.

Anyway; opinions? Linus, Thomas, Ingo?

And here's the patch that makes fast mode go *poof*..

---
 arch/arm/include/asm/tlb.h  | 27 ++++-----------------------
 arch/ia64/include/asm/tlb.h | 41 ++++++++---------------------------------
 include/asm-generic/tlb.h   | 17 +----------------
 mm/memory.c                 |  9 ---------
 4 files changed, 13 insertions(+), 81 deletions(-)
arch/arm/include/asm/tlb.h +4 -23
index 99a1951..bdf2b84 100644
@@ -33,18 +33,6 @@
 #include <asm/pgalloc.h>
 #include <asm/tlbflush.h>
 
-/*
- * We need to delay page freeing for SMP as other CPUs can access pages
- * which have been removed but not yet had their TLB entries invalidated.
- * Also, as ARMv7 speculative prefetch can drag new entries into the TLB,
- * we need to apply this same delaying tactic to ensure correct operation.
- */
-#if defined(CONFIG_SMP) || defined(CONFIG_CPU_32v7)
-#define tlb_fast_mode(tlb)	0
-#else
-#define tlb_fast_mode(tlb)	1
-#endif
-
 #define MMU_GATHER_BUNDLE	8
 
 /*
@@ -112,12 +100,10 @@ static inline void __tlb_alloc_page(struct mmu_gather *tlb)
 static inline void tlb_flush_mmu(struct mmu_gather *tlb)
 {
 	tlb_flush(tlb);
-	if (!tlb_fast_mode(tlb)) {
-		free_pages_and_swap_cache(tlb->pages, tlb->nr);
-		tlb->nr = 0;
-		if (tlb->pages == tlb->local)
-			__tlb_alloc_page(tlb);
-	}
+	free_pages_and_swap_cache(tlb->pages, tlb->nr);
+	tlb->nr = 0;
+	if (tlb->pages == tlb->local)
+		__tlb_alloc_page(tlb);
 }
 
 static inline void
@@ -178,11 +164,6 @@ tlb_end_vma(struct mmu_gather *tlb, struct vm_area_struct *vma)
 
 static inline int __tlb_remove_page(struct mmu_gather *tlb, struct page *page)
 {
-	if (tlb_fast_mode(tlb)) {
-		free_page_and_swap_cache(page);
-		return 1; /* avoid calling tlb_flush_mmu */
-	}
-
 	tlb->pages[tlb->nr++] = page;
 	VM_BUG_ON(tlb->nr > tlb->max);
 	return tlb->max - tlb->nr;
arch/ia64/include/asm/tlb.h +8 -33

77 lines not on this page · raw message

include/asm-generic/tlb.h +1 -16
index b1b1fa6..13821c3 100644
@@ -97,11 +97,9 @@ struct mmu_gather {
 	unsigned long		start;
 	unsigned long		end;
 	unsigned int		need_flush : 1,	/* Did free PTEs */
-				fast_mode  : 1; /* No batching   */
-
 	/* we are in the middle of an operation to clear
 	 * a full mm and can make some optimizations */
-	unsigned int		fullmm : 1,
+				fullmm : 1,
 	/* we have performed an operation which
 	 * requires a complete flush of the tlb */
 				need_flush_all : 1;
@@ -114,19 +112,6 @@ struct mmu_gather {
 
 #define HAVE_GENERIC_MMU_GATHER
 
-static inline int tlb_fast_mode(struct mmu_gather *tlb)
-{
-#ifdef CONFIG_SMP
-	return tlb->fast_mode;
-#else
-	/*
-	 * For UP we don't need to worry about TLB flush
-	 * and page free order so much..
-	 */
-	return 1;
-#endif
-}
-
 void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, bool fullmm);
 void tlb_flush_mmu(struct mmu_gather *tlb);
 void tlb_finish_mmu(struct mmu_gather *tlb, unsigned long start,
mm/memory.c +0 -9
index d7d54a1..95d0cce 100644
@@ -220,7 +220,6 @@ void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, bool fullmm)
 	tlb->start	= -1UL;
 	tlb->end	= 0;
 	tlb->need_flush = 0;
-	tlb->fast_mode  = (num_possible_cpus() == 1);
 	tlb->local.next = NULL;
 	tlb->local.nr   = 0;
 	tlb->local.max  = ARRAY_SIZE(tlb->__pages);
@@ -244,9 +243,6 @@ void tlb_flush_mmu(struct mmu_gather *tlb)
 	tlb_table_flush(tlb);
 #endif
 
-	if (tlb_fast_mode(tlb))
-		return;
-
 	for (batch = &tlb->local; batch; batch = batch->next) {
 		free_pages_and_swap_cache(batch->pages, batch->nr);
 		batch->nr = 0;
@@ -288,11 +284,6 @@ int __tlb_remove_page(struct mmu_gather *tlb, struct page *page)
 
 	VM_BUG_ON(!tlb->need_flush);
 
-	if (tlb_fast_mode(tlb)) {
-		free_page_and_swap_cache(page);
-		return 1; /* avoid calling tlb_flush_mmu() */
-	}
-
 	batch = tlb->active;
 	batch->pages[batch->nr++] = page;
 	if (batch->nr == batch->max) {

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

maintainer
#22 lore raw

PATCH 1/1 arch, mm: Remove tlb_fast_mode()

Quoted 8 lines · Linus Torvalds
Quoted 2 lines · Peter Zijlstra

And here's the patch that makes fast mode go *poof*..

Let's just do this. Mind sending a patch with proper changelog and sign-off?

Sure, as an extra I even compile tested it! ;-) (including arm and ia64).

---
Subject: arch, mm: Remove tlb_fast_mode()
From: Peter Zijlstra <[email protected]>

Since the introduction of preemptible mmu_gather TLB fast mode has been broken. TLB fast mode relies on there being absolutely no concurrency; it frees pages first and invalidates TLBs later.

However now we can get concurrency and stuff goes *bang*.

This patch removes all tlb_fast_mode() code; it was found the better option vs trying to patch the hole by entangling tlb invalidation with the scheduler.

---
 arch/arm/include/asm/tlb.h  |   27 ++++-----------------------
 arch/ia64/include/asm/tlb.h |   41 ++++++++---------------------------------
 include/asm-generic/tlb.h   |   17 +----------------
 mm/memory.c                 |    9 ---------
 4 files changed, 13 insertions(+), 81 deletions(-)
arch/arm/include/asm/tlb.h +4 -23
@@ -33,18 +33,6 @@
 #include <asm/pgalloc.h>
 #include <asm/tlbflush.h>
 
-/*
- * We need to delay page freeing for SMP as other CPUs can access pages
- * which have been removed but not yet had their TLB entries invalidated.
- * Also, as ARMv7 speculative prefetch can drag new entries into the TLB,
- * we need to apply this same delaying tactic to ensure correct operation.
- */
-#if defined(CONFIG_SMP) || defined(CONFIG_CPU_32v7)
-#define tlb_fast_mode(tlb)	0
-#else
-#define tlb_fast_mode(tlb)	1
-#endif
-
 #define MMU_GATHER_BUNDLE	8
 
 /*
@@ -112,12 +100,10 @@ static inline void __tlb_alloc_page(stru
 static inline void tlb_flush_mmu(struct mmu_gather *tlb)
 {
 	tlb_flush(tlb);
-	if (!tlb_fast_mode(tlb)) {
-		free_pages_and_swap_cache(tlb->pages, tlb->nr);
-		tlb->nr = 0;
-		if (tlb->pages == tlb->local)
-			__tlb_alloc_page(tlb);
-	}
+	free_pages_and_swap_cache(tlb->pages, tlb->nr);
+	tlb->nr = 0;
+	if (tlb->pages == tlb->local)
+		__tlb_alloc_page(tlb);
 }
 
 static inline void
@@ -178,11 +164,6 @@ tlb_end_vma(struct mmu_gather *tlb, stru
 
 static inline int __tlb_remove_page(struct mmu_gather *tlb, struct page *page)
 {
-	if (tlb_fast_mode(tlb)) {
-		free_page_and_swap_cache(page);
-		return 1; /* avoid calling tlb_flush_mmu */
-	}
-
 	tlb->pages[tlb->nr++] = page;
 	VM_BUG_ON(tlb->nr > tlb->max);
 	return tlb->max - tlb->nr;
arch/ia64/include/asm/tlb.h +8 -33

77 lines not on this page · raw message

include/asm-generic/tlb.h +1 -16
@@ -97,11 +97,9 @@ struct mmu_gather {
 	unsigned long		start;
 	unsigned long		end;
 	unsigned int		need_flush : 1,	/* Did free PTEs */
-				fast_mode  : 1; /* No batching   */
-
 	/* we are in the middle of an operation to clear
 	 * a full mm and can make some optimizations */
-	unsigned int		fullmm : 1,
+				fullmm : 1,
 	/* we have performed an operation which
 	 * requires a complete flush of the tlb */
 				need_flush_all : 1;
@@ -114,19 +112,6 @@ struct mmu_gather {
 
 #define HAVE_GENERIC_MMU_GATHER
 
-static inline int tlb_fast_mode(struct mmu_gather *tlb)
-{
-#ifdef CONFIG_SMP
-	return tlb->fast_mode;
-#else
-	/*
-	 * For UP we don't need to worry about TLB flush
-	 * and page free order so much..
-	 */
-	return 1;
-#endif
-}
-
 void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, bool fullmm);
 void tlb_flush_mmu(struct mmu_gather *tlb);
 void tlb_finish_mmu(struct mmu_gather *tlb, unsigned long start,
mm/memory.c +0 -9
@@ -220,7 +220,6 @@ void tlb_gather_mmu(struct mmu_gather *t
 	tlb->start	= -1UL;
 	tlb->end	= 0;
 	tlb->need_flush = 0;
-	tlb->fast_mode  = (num_possible_cpus() == 1);
 	tlb->local.next = NULL;
 	tlb->local.nr   = 0;
 	tlb->local.max  = ARRAY_SIZE(tlb->__pages);
@@ -244,9 +243,6 @@ void tlb_flush_mmu(struct mmu_gather *tl
 	tlb_table_flush(tlb);
 #endif
 
-	if (tlb_fast_mode(tlb))
-		return;
-
 	for (batch = &tlb->local; batch; batch = batch->next) {
 		free_pages_and_swap_cache(batch->pages, batch->nr);
 		batch->nr = 0;
@@ -288,11 +284,6 @@ int __tlb_remove_page(struct mmu_gather
 
 	VM_BUG_ON(!tlb->need_flush);
 
-	if (tlb_fast_mode(tlb)) {
-		free_page_and_swap_cache(page);
-		return 1; /* avoid calling tlb_flush_mmu() */
-	}
-
 	batch = tlb->active;
 	batch->pages[batch->nr++] = page;
 	if (batch->nr == batch->max) {

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#23 lore raw
In reply to Peter Zijlstra
Quoted 48 lines · Peter Zijlstra
Quoted 47 lines · Max Filippov
Quoted 23 lines · Max Filippov

Hello arch and mm people.

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

Quoted 7 lines

I've noticed that with xtensa arch when I added a test before returning to userspace checking that TLB contents agrees with page tables of the current mm. This check reliably fires with the LTP test mtest05 that maps, unmaps and accesses memory from multiple threads.

Is there anything wrong in my description, maybe something specific to my arch, or this issue really exists?

Hi,

I've made similar checking function for MIPS (because qemu is my only choice and it simulates MIPS TLB) and ran my tests on mips-malta machine in qemu. With MIPS I can also see this issue. I hope I did it right, the patch at the bottom is for the reference. The test I run and the diagnostic output are as follows:

To me it looks like the cond_resched in the zap_pmd_range is the root cause of this issue (let alone SMP case for now). It was introduced in the commit

commit 97a894136f29802da19a15541de3c019e1ca147e

Author: Peter Zijlstra <[email protected]>
Date:   Tue May 24 17:12:04 2011 -0700
Quoted 5 lines
Quoted 3 lines

mm: Remove i_mmap_lock lockbreak

Peter, Kamezawa, other reviewers of that commit, could you please comment?

Are you all running UP systems? I suppose the preemptible muck

Yes, xtensa SMP support is in the middle of forward porting/cleanup process.

Quoted 4 lines

invalidated the assumption that UP systems are 'easy'.

If you make tlb_fast_mode() return an unconditional false, does it all work again?

Actually the only thing that was visibly broken is the test for TLB/PTE coherency,
but that incoherency appears to be intentional (and the test is too simple for that).
Unconditionally returning false from tlb_fast_mode() doesn't fix that test, but AFAICS it fixes underlying page freeing.

Signature · 8 lines
Thanks.
-- Max

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#24 lore raw
In reply to Max Filippov
Quoted 16 lines · Max Filippov

Hello arch and mm people.

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

Quoted 6 lines

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

The idea behind the lazy MMU subsystem is that it does not need to flush the TLB all the time and allow one to do PTE manipulations in a "batch mode". Meaning there are stray entries - and one has to be diligient about not using them.

Here is the relvant comment from the Linux header:

/*
* A facility to provide lazy MMU batching. This allows PTE updates and
* page invalidations to be delayed until a call to leave lazy MMU mode
* is issued. Some architectures may benefit from doing this, and it is
* beneficial for both shadow and direct mode hypervisors, which may batch
* the PTE updates which happen during this window. Note that using this
* interface requires that read hazards be removed from the code. A read
* hazard could result in the direct mode hypervisor case, since the actual
* write to the page tables may not yet have taken place, so reads though
* a raw PTE pointer after it has been modified are not guaranteed to be
* up to date. This mode can only be entered and left under the protection of
* the page table locks for all page tables which may be modified. In the UP
* case, this is required so that preemption is disabled, and in the SMP case,
* it must synchronize the delayed page table writes properly on other CPUs. */

This means that eventually when arch_leave_lazy_mmu_mode or arch_flush_lazy_mmu_mode is called, the PTE updates _should_ be flushed (aka, TLB flush if needed on the altered PTE entries).

Quoted 6 lines

I've noticed that with xtensa arch when I added a test before returning to userspace checking that TLB contents agrees with page tables of the current mm. This check reliably fires with the LTP test mtest05 that maps, unmaps and accesses memory from multiple threads.

Quoted 17 lines

Is there anything wrong in my description, maybe something specific to my arch, or this issue really exists?

I've also noticed that there are a lot of arches with default implementations of the involved functions, does that mean that any/all of them have this issue too?

Signature · 8 lines
Thanks.
-- Max

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>

#25 lore raw
Quoted 24 lines · Konrad Rzeszutek Wilk
Quoted 16 lines · Max Filippov

Hello arch and mm people.

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

Quoted 6 lines

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

Quoted 3 lines

The idea behind the lazy MMU subsystem is that it does not need to flush the TLB all the time and allow one to do PTE manipulations in a "batch mode". Meaning there are stray entries - and one has to be diligient about not using them.

Yes, I got it, IOW TLB entries must either be flushed before userspace can see them, or the underlying pages must not be freed.

Quoted 18 lines

Here is the relvant comment from the Linux header:

/*
* A facility to provide lazy MMU batching. This allows PTE updates and
* page invalidations to be delayed until a call to leave lazy MMU mode
* is issued. Some architectures may benefit from doing this, and it is
* beneficial for both shadow and direct mode hypervisors, which may batch
* the PTE updates which happen during this window. Note that using this
* interface requires that read hazards be removed from the code. A read
* hazard could result in the direct mode hypervisor case, since the actual
* write to the page tables may not yet have taken place, so reads though
* a raw PTE pointer after it has been modified are not guaranteed to be
* up to date. This mode can only be entered and left under the protection of
* the page table locks for all page tables which may be modified. In the UP
* case, this is required so that preemption is disabled, and in the SMP case,
* it must synchronize the delayed page table writes properly on other CPUs. */

Quoted 3 lines

This means that eventually when arch_leave_lazy_mmu_mode or arch_flush_lazy_mmu_mode is called, the PTE updates _should_ be flushed (aka, TLB flush if needed on the altered PTE entries).

Should (: But I only see powerpc, sparc and x86 defining __HAVE_ARCH_ENTER_LAZY_MMU_MODE, so this does not apply to all remaining arches.

Signature · 2 lines
Thanks.
-- Max
#26 lore raw
In reply to Max Filippov
Quoted 16 lines · Max Filippov

Hello arch and mm people.

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

Quoted 3 lines

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages,

The page is not freed at that time (at least not for the generic mmu_gather implementation). It is stored into mmu_gather and then freed along with the tlb flush in tlb_flush_mmu.
[...]

Signature · 8 lines
Michal Hocko
SUSE Labs

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#27 lore raw
Quoted 15 lines · Michal Hocko
Quoted 14 lines · Max Filippov

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

Quoted 7 lines
Quoted 3 lines

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages,

The page is not freed at that time (at least not for the generic mmu_gather implementation). It is stored into mmu_gather and then freed along with the tlb flush in tlb_flush_mmu.

Actually for the UP case, the page gets freed in __tlb_remove_page() since tlb_fast_mode() is 1.

--
Catalin

#28 lore raw
In reply to Max Filippov

Max,

Quoted 4 lines · Max Filippov

Hello arch and mm people.

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

If it happens, this would be a bug. It means that a process can access a physical page that has been allocated to something else, possibly kernel data.

Quoted 11 lines

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

Quoted 6 lines

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

It looks to me like cond_resched() here introduces a possible bug but it depends on the actual arch code, especially the __tlb_remove_tlb_entry() function. On ARM we record the range in tlb_remove_tlb_entry() and queue the pages to be removed in __tlb_remove_page(). It pretty much acts like tlb_fast_mode() == 0 even for the UP case (which is also needed for hardware speculative TLB loads). The tlb_finish_mmu() takes care of whatever pages are left to be freed.

With a dummy __tlb_remove_tlb_entry() and tlb_fast_mode() == 1, cond_resched() in zap_pmd_range() would cause problems.

I think possible workarounds:

1. tlb_fast_mode() always returning 0.
2. add a tlb_flush_mmu(tlb) before cond_resched() in zap_pmd_range().
3. implement __tlb_remove_tlb_entry() on xtensa to always flush the tlb (which is probably costly).
4. drop the cond_resched() (not sure about preemptible kernels though).

I would vote for 1 but let's see what the mm people say.

--
Catalin

#29 lore raw

Hi Catalin,

Quoted 39 lines · Catalin Marinas

Max,

Quoted 4 lines · Max Filippov

Hello arch and mm people.

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

If it happens, this would be a bug. It means that a process can access a physical page that has been allocated to something else, possibly kernel data.

Quoted 11 lines

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

Quoted 6 lines

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

It looks to me like cond_resched() here introduces a possible bug but it depends on the actual arch code, especially the __tlb_remove_tlb_entry() function. On ARM we record the range in tlb_remove_tlb_entry() and queue the pages to be removed in __tlb_remove_page(). It pretty much acts like tlb_fast_mode() == 0 even for the UP case (which is also needed for hardware speculative TLB loads). The tlb_finish_mmu() takes care of whatever pages are left to be freed.

Quoted 2 lines

With a dummy __tlb_remove_tlb_entry() and tlb_fast_mode() == 1, cond_resched() in zap_pmd_range() would cause problems.

So, looks like most architectures in the UP configuration should have this issue (unless they flush TLB in the switch_mm, even when switching to the same mm):

tlb_remove_tlb_entry __tlb_remove_tlb_entry __tlb_remove_page __HAVE_ARCH_ENTER_LAZY_MMU_MODE
non-default non-trivial non-default defined
alpha
arc
arm yes yes
arm64 yes yes
avr32
blackfin
c6x
cris
frv
h8300
hexagon
ia64 yes yes yes
Kconfig
m32r
m68k
metag
microblaze
mips
mn10300
openrisc
parisc
powerpc yes yes
s390 yes yes (a)
score
sh yes yes (a)
sparc yes
tile
um yes yes yes
unicore32
x86 yes
xtensa

(a) __tlb_remove_page == free_page_and_swap_cache

Quoted 9 lines

I think possible workarounds:

1. tlb_fast_mode() always returning 0.
2. add a tlb_flush_mmu(tlb) before cond_resched() in zap_pmd_range().
3. implement __tlb_remove_tlb_entry() on xtensa to always flush the tlb (which is probably costly).
4. drop the cond_resched() (not sure about preemptible kernels though).

I would vote for 1 but let's see what the mm people say.

Signature · 2 lines
Thanks.
-- Max
#30 lore raw
Quoted 36 lines · Max Filippov
Quoted 35 lines · Catalin Marinas
Quoted 2 lines · Max Filippov

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

If it happens, this would be a bug. It means that a process can access a physical page that has been allocated to something else, possibly kernel data.

Quoted 11 lines

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

Quoted 6 lines

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

It looks to me like cond_resched() here introduces a possible bug but it depends on the actual arch code, especially the __tlb_remove_tlb_entry() function. On ARM we record the range in tlb_remove_tlb_entry() and queue the pages to be removed in __tlb_remove_page(). It pretty much acts like tlb_fast_mode() == 0 even for the UP case (which is also needed for hardware speculative TLB loads). The tlb_finish_mmu() takes care of whatever pages are left to be freed.

Quoted 6 lines
Quoted 2 lines

With a dummy __tlb_remove_tlb_entry() and tlb_fast_mode() == 1, cond_resched() in zap_pmd_range() would cause problems.

So, looks like most architectures in the UP configuration should have this issue (unless they flush TLB in the switch_mm, even when switching to the same mm):

switch_mm() wouldn't be called if switching to the same mm. You could do it in switch_to() but it's not efficient (or before returning to user space on the same processor).

Do you happen to have a user-space test for this? Something like one thread does an mmap(), writes some poison value, munmap(). The other thread keeps checking the poison value while trapping and ignoring any SIGSEGV. If it's working correctly, the second thread should either get a SIGSEGV or read the poison value.

Signature · 7 lines
Catalin

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#31 lore raw
Quoted 37 lines · Catalin Marinas
Quoted 36 lines · Max Filippov
Quoted 35 lines · Catalin Marinas
Quoted 2 lines · Max Filippov

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

If it happens, this would be a bug. It means that a process can access a physical page that has been allocated to something else, possibly kernel data.

Quoted 11 lines

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

Quoted 6 lines

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

It looks to me like cond_resched() here introduces a possible bug but it depends on the actual arch code, especially the __tlb_remove_tlb_entry() function. On ARM we record the range in tlb_remove_tlb_entry() and queue the pages to be removed in __tlb_remove_page(). It pretty much acts like tlb_fast_mode() == 0 even for the UP case (which is also needed for hardware speculative TLB loads). The tlb_finish_mmu() takes care of whatever pages are left to be freed.

Quoted 8 lines
Quoted 6 lines
Quoted 2 lines

With a dummy __tlb_remove_tlb_entry() and tlb_fast_mode() == 1, cond_resched() in zap_pmd_range() would cause problems.

So, looks like most architectures in the UP configuration should have this issue (unless they flush TLB in the switch_mm, even when switching to the same mm):

switch_mm() wouldn't be called if switching to the same mm. You could do

Hmm... Strange, but as far as I can tell from the context_switch it would.

Quoted 4 lines

it in switch_to() but it's not efficient (or before returning to user space on the same processor).

Do you happen to have a user-space test for this? Something like one

I only had mtest05 from LTP that triggered TLB/PTE inconsistency, but not anything that would really try to peek at the freed page. I can make such test though.

Quoted 4 lines

thread does an mmap(), writes some poison value, munmap(). The other thread keeps checking the poison value while trapping and ignoring any SIGSEGV. If it's working correctly, the second thread should either get a SIGSEGV or read the poison value.

Signature · 8 lines
Thanks.
-- Max

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#32 lore raw
Quoted 42 lines · Max Filippov
Quoted 40 lines · Catalin Marinas
Quoted 39 lines · Max Filippov
Quoted 37 lines · Catalin Marinas
Quoted 2 lines · Max Filippov

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

If it happens, this would be a bug. It means that a process can access a physical page that has been allocated to something else, possibly kernel data.

Quoted 11 lines

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

Quoted 6 lines

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

It looks to me like cond_resched() here introduces a possible bug but it depends on the actual arch code, especially the __tlb_remove_tlb_entry() function. On ARM we record the range in tlb_remove_tlb_entry() and queue the pages to be removed in __tlb_remove_page(). It pretty much acts like tlb_fast_mode() == 0 even for the UP case (which is also needed for hardware speculative TLB loads). The tlb_finish_mmu() takes care of whatever pages are left to be freed.

With a dummy __tlb_remove_tlb_entry() and tlb_fast_mode() == 1, cond_resched() in zap_pmd_range() would cause problems.

Quoted 7 lines
Quoted 5 lines
Quoted 3 lines

So, looks like most architectures in the UP configuration should have this issue (unless they flush TLB in the switch_mm, even when switching to the same mm):

switch_mm() wouldn't be called if switching to the same mm. You could do

Hmm... Strange, but as far as I can tell from the context_switch it would.

You are right. On ARM we have a prev != next check but that's in the switch_mm() implementation.

Signature · 7 lines
Catalin

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#33 lore raw
In reply to Max Filippov
Quoted 50 lines · Max Filippov
Quoted 37 lines · Catalin Marinas
Quoted 36 lines · Max Filippov
Quoted 35 lines · Catalin Marinas
Quoted 2 lines · Max Filippov

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

If it happens, this would be a bug. It means that a process can access a physical page that has been allocated to something else, possibly kernel data.

Quoted 11 lines

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

Quoted 6 lines

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

It looks to me like cond_resched() here introduces a possible bug but it depends on the actual arch code, especially the __tlb_remove_tlb_entry() function. On ARM we record the range in tlb_remove_tlb_entry() and queue the pages to be removed in __tlb_remove_page(). It pretty much acts like tlb_fast_mode() == 0 even for the UP case (which is also needed for hardware speculative TLB loads). The tlb_finish_mmu() takes care of whatever pages are left to be freed.

Quoted 8 lines
Quoted 6 lines
Quoted 2 lines

With a dummy __tlb_remove_tlb_entry() and tlb_fast_mode() == 1, cond_resched() in zap_pmd_range() would cause problems.

So, looks like most architectures in the UP configuration should have this issue (unless they flush TLB in the switch_mm, even when switching to the same mm):

switch_mm() wouldn't be called if switching to the same mm. You could do

Hmm... Strange, but as far as I can tell from the context_switch it would.

Quoted 13 lines
Quoted 4 lines

it in switch_to() but it's not efficient (or before returning to user space on the same processor).

Do you happen to have a user-space test for this? Something like one

I only had mtest05 from LTP that triggered TLB/PTE inconsistency, but not anything that would really try to peek at the freed page. I can make such test though.

Quoted 4 lines

thread does an mmap(), writes some poison value, munmap(). The other thread keeps checking the poison value while trapping and ignoring any SIGSEGV. If it's working correctly, the second thread should either get a SIGSEGV or read the poison value.

I've made a number of such tests and had them running for a couple of days. Checking thread never read anything other than poison value so far.

Signature · 8 lines
Thanks.
-- Max

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#34 lore raw
In reply to Catalin Marinas
Quoted 39 lines · Catalin Marinas

Max,

Quoted 4 lines · Max Filippov

Hello arch and mm people.

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

If it happens, this would be a bug. It means that a process can access a physical page that has been allocated to something else, possibly kernel data.

Quoted 11 lines

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

Quoted 6 lines

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

It looks to me like cond_resched() here introduces a possible bug but it depends on the actual arch code, especially the __tlb_remove_tlb_entry() function. On ARM we record the range in tlb_remove_tlb_entry() and queue the pages to be removed in __tlb_remove_page(). It pretty much acts like tlb_fast_mode() == 0 even for the UP case (which is also needed for hardware speculative TLB loads). The tlb_finish_mmu() takes care of whatever pages are left to be freed.

Quoted 6 lines

With a dummy __tlb_remove_tlb_entry() and tlb_fast_mode() == 1, cond_resched() in zap_pmd_range() would cause problems.

I think possible workarounds:

1. tlb_fast_mode() always returning 0.

This might add needless page free batching logic so not very lucrative.

Quoted 1 line

2. add a tlb_flush_mmu(tlb) before cond_resched() in zap_pmd_range().

For !fullmm flushes it might be no-op on some arches (atleast on ARC) as we use tlb_end_vma() to do TLB range flush. And flushing the entire TLB would be excessive though.

Actually zap_pte_range() already has logic to flush the TLB range (if batching runs out of space). Can we re-use that infrastructure to make sure zap_pte_range() does it's share of TLB flushing before returning and going into cond_resched().

However with that, we need to prevent tlb_end_vma()/tlb_finish_mmu() from duplicating the range flush - which can be done by clearing tlb->need_flush.

Now simplistically this will cause even the fullmm flushes (simple ASID increment on ARC/ARM..) to become TLB walks to flush the individual entries so we can do this for only for !fullmm, assuming that cond_resched() can potentially cause an exit'ing task's thread to be scheduled in and reuse the entries.

Let me go off cook a patch to see if this might work.

-Vineet

#35 lore raw
In reply to Catalin Marinas

[Resending with fixed linux-mm address]

Quoted 39 lines · Catalin Marinas

Max,

Quoted 4 lines · Max Filippov

Hello arch and mm people.

Is it intentional that threads of a process that invoked munmap syscall can see TLB entries pointing to already freed pages, or it is a bug?

If it happens, this would be a bug. It means that a process can access a physical page that has been allocated to something else, possibly kernel data.

Quoted 11 lines

I'm talking about zap_pmd_range and zap_pte_range:

zap_pmd_range
zap_pte_range
arch_enter_lazy_mmu_mode
ptep_get_and_clear_full
tlb_remove_tlb_entry
__tlb_remove_page
arch_leave_lazy_mmu_mode
cond_resched

Quoted 6 lines

With the default arch_{enter,leave}_lazy_mmu_mode, tlb_remove_tlb_entry and __tlb_remove_page there is a loop in the zap_pte_range that clears PTEs and frees corresponding pages, but doesn't flush TLB, and surrounding loop in the zap_pmd_range that calls cond_resched. If a thread of the same process gets scheduled then it is able to see TLB entries pointing to already freed physical pages.

It looks to me like cond_resched() here introduces a possible bug but it depends on the actual arch code, especially the __tlb_remove_tlb_entry() function. On ARM we record the range in tlb_remove_tlb_entry() and queue the pages to be removed in __tlb_remove_page(). It pretty much acts like tlb_fast_mode() == 0 even for the UP case (which is also needed for hardware speculative TLB loads). The tlb_finish_mmu() takes care of whatever pages are left to be freed.

Quoted 6 lines

With a dummy __tlb_remove_tlb_entry() and tlb_fast_mode() == 1, cond_resched() in zap_pmd_range() would cause problems.

I think possible workarounds:

1. tlb_fast_mode() always returning 0.

This might add needless page free batching logic so not very lucrative.

Quoted 1 line

2. add a tlb_flush_mmu(tlb) before cond_resched() in zap_pmd_range().

For !fullmm flushes it might be no-op on some arches (atleast on ARC) as we use tlb_end_vma() to do TLB range flush. And flushing the entire TLB would be excessive though.

Actually zap_pte_range() already has logic to flush the TLB range (if batching runs out of space). Can we re-use that infrastructure to make sure zap_pte_range() does it's share of TLB flushing before returning and going into cond_resched().

However with that, we need to prevent tlb_end_vma()/tlb_finish_mmu() from duplicating the range flush - which can be done by clearing tlb->need_flush.

Now simplistically this will cause even the fullmm flushes (simple ASID increment on ARC/ARM..) to become TLB walks to flush the individual entries so we can do this for only for !fullmm, assuming that cond_resched() can potentially cause an exit'ing task's thread to be scheduled in and reuse the entries.

Let me go off cook a patch to see if this might work.

-Vineet

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>