Skip to contentSkip to conversation

mm: preemptibility -v2

PATCH In review Peter Zijlstra 15 participants 96 messages Apr 8, 2010 – May 25, 2010

Subsystems (19)
Memory management x86 VFS PowerPC +15 more subsystems
lore
Share & tools
lore
#0 lore raw

cover mm: preemptibility -v2

Hi,

This (still incomplete) patch-set makes part of the mm a lot more preemptible. It converts i_mmap_lock and anon_vma->lock to mutexes. On the way there it also makes mmu_gather preemptible.

The main motivation was making mm_take_all_locks() preemptible, since it appears people are nesting hundreds of spinlocks there.

The side-effects are that we can finally make mmu_gather preemptible, something which lots of people have wanted to do for a long time.

It also gets us anon_vma refcounting which seems to be wanted by KSM as well as Mel's compaction work.

This patch-set seems to build and boot on my x86_64 machines and even builds a kernel. I've also attempted powerpc and sparc, which I've compile tested with their respective defconfigs, remaining are (afaikt the rest uses the generic tlb bits):

- s390
- ia64
- arm
- superh
- um

From those, s390 and ia64 look 'interesting', arm and superh seem very similar and should be relatively easy (-rt has a patchlet for arm iirc).

What kind of performance tests would people have me run on this to satisfy their need for numbers? I've done a kernel build on x86_64 and if anything that was slightly faster with these patches, but it was well within the noise levels so it might be heat noise I'm looking at ;-)

#1 lore raw

PATCH 1/13 powerpc: Add rcu_read_lock() to gup_fast() implementation

The powerpc page table freeing relies on the fact that IRQs hold off an RCU grace period, this is currently true for all existing RCU implementations but is not an assumption Paul wants to support.

Therefore, also take the RCU read lock along with disabling IRQs to ensure the RCU grace period does at least cover these lookups.

Cc Nick Piggin <[email protected]>
Cc Benjamin Herrenschmidt <[email protected]>
---
 arch/powerpc/mm/gup.c |    3 +++
 1 file changed, 3 insertions(+)
arch/powerpc/mm/gup.c +3 -0
Index: linux-2.6/arch/powerpc/mm/gup.c
===================================================================
@@ -142,6 +142,7 @@ int get_user_pages_fast(unsigned long st
 	 * So long as we atomically load page table pointers versus teardown,
 	 * we can follow the address down to the the page and take a ref on it.
 	 */
+	rcu_read_lock();
 	local_irq_disable();
 
 	pgdp = pgd_offset(mm, addr);
@@ -162,6 +163,7 @@ int get_user_pages_fast(unsigned long st
 	} while (pgdp++, addr = next, addr != end);
 
 	local_irq_enable();
+	rcu_read_unlock();
 
 	VM_BUG_ON(nr != (end - start) >> PAGE_SHIFT);
 	return nr;
@@ -171,6 +173,7 @@ int get_user_pages_fast(unsigned long st
 
 slow:
 		local_irq_enable();
+		rcu_read_unlock();
 slow_irqon:
 		pr_devel("  slow path ! nr = %d\n", nr);
 
Reviewed-by on v1 1/13
#2 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 11 lines · Peter Zijlstra

The powerpc page table freeing relies on the fact that IRQs hold off an RCU grace period, this is currently true for all existing RCU implementations but is not an assumption Paul wants to support.

Therefore, also take the RCU read lock along with disabling IRQs to ensure the RCU grace period does at least cover these lookups.

#3 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

In reply to Peter Zijlstra

It's not wrong per se, but the entire powerpc memory management code does the IRQ disabling for its pagetable RCU code. So I think it would be better to do the whole thing in one go.

I don't think Paul will surprise-break powerpc :)

It's up to Ben really, though.

Quoted 44 lines · Peter Zijlstra

The powerpc page table freeing relies on the fact that IRQs hold off an RCU grace period, this is currently true for all existing RCU implementations but is not an assumption Paul wants to support.

Therefore, also take the RCU read lock along with disabling IRQs to ensure the RCU grace period does at least cover these lookups.

Cc Nick Piggin <[email protected]>
Cc Benjamin Herrenschmidt <[email protected]>
---
 arch/powerpc/mm/gup.c |    3 +++
 1 file changed, 3 insertions(+)

Index: linux-2.6/arch/powerpc/mm/gup.c
===================================================================

arch/powerpc/mm/gup.c +3 -0
@@ -142,6 +142,7 @@ int get_user_pages_fast(unsigned long st
 	 * So long as we atomically load page table pointers versus teardown,
 	 * we can follow the address down to the the page and take a ref on it.
 	 */
+	rcu_read_lock();
 	local_irq_disable();
 
 	pgdp = pgd_offset(mm, addr);
@@ -162,6 +163,7 @@ int get_user_pages_fast(unsigned long st
 	} while (pgdp++, addr = next, addr != end);
 
 	local_irq_enable();
+	rcu_read_unlock();
 
 	VM_BUG_ON(nr != (end - start) >> PAGE_SHIFT);
 	return nr;
@@ -171,6 +173,7 @@ int get_user_pages_fast(unsigned long st
 
 slow:
 		local_irq_enable();
+		rcu_read_unlock();
 slow_irqon:
 		pr_devel("  slow path ! nr = %d\n", nr);
 
#4 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

In reply to Peter Zijlstra
Quoted 7 lines · Peter Zijlstra

plain text document attachment (powerpc-gup_fast-rcu.patch) The powerpc page table freeing relies on the fact that IRQs hold off an RCU grace period, this is currently true for all existing RCU implementations but is not an assumption Paul wants to support.

Therefore, also take the RCU read lock along with disabling IRQs to ensure the RCU grace period does at least cover these lookups.

There's a few other places that need a similar fix then. The hash page code for example. All the C cases should end up calling the find_linux_pte() helper afaik, so we should be able to stick the lock in there (and the hugetlbfs variant, find_linux_pte_or_hugepte()).

However, we also have cases of tight asm code walking the page tables, such as the tlb miss handler on embedded processors. I don't see how I could do that there. IE. I only have a handful of registers to play with, no stack, etc...

So we might have to support the interrupt assumption, at least in some form, with those guys...

Cheers,
Ben.

Quoted 37 lines
Cc Nick Piggin <[email protected]>
Cc Benjamin Herrenschmidt <[email protected]>
---
 arch/powerpc/mm/gup.c |    3 +++
 1 file changed, 3 insertions(+)

Index: linux-2.6/arch/powerpc/mm/gup.c
===================================================================

arch/powerpc/mm/gup.c +3 -0
@@ -142,6 +142,7 @@ int get_user_pages_fast(unsigned long st
 	 * So long as we atomically load page table pointers versus teardown,
 	 * we can follow the address down to the the page and take a ref on it.
 	 */
+	rcu_read_lock();
 	local_irq_disable();
 
 	pgdp = pgd_offset(mm, addr);
@@ -162,6 +163,7 @@ int get_user_pages_fast(unsigned long st
 	} while (pgdp++, addr = next, addr != end);
 
 	local_irq_enable();
+	rcu_read_unlock();
 
 	VM_BUG_ON(nr != (end - start) >> PAGE_SHIFT);
 	return nr;
@@ -171,6 +173,7 @@ int get_user_pages_fast(unsigned long st
 
 slow:
 		local_irq_enable();
+		rcu_read_unlock();
 slow_irqon:
 		pr_devel("  slow path ! nr = %d\n", nr);
 
#5 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 14 lines · Benjamin Herrenschmidt
Quoted 7 lines · Peter Zijlstra

plain text document attachment (powerpc-gup_fast-rcu.patch) The powerpc page table freeing relies on the fact that IRQs hold off an RCU grace period, this is currently true for all existing RCU implementations but is not an assumption Paul wants to support.

Therefore, also take the RCU read lock along with disabling IRQs to ensure the RCU grace period does at least cover these lookups.

There's a few other places that need a similar fix then. The hash page code for example. All the C cases should end up calling the find_linux_pte() helper afaik, so we should be able to stick the lock in there (and the hugetlbfs variant, find_linux_pte_or_hugepte()).

Quoted 7 lines

However, we also have cases of tight asm code walking the page tables, such as the tlb miss handler on embedded processors. I don't see how I could do that there. IE. I only have a handful of registers to play with, no stack, etc...

So we might have to support the interrupt assumption, at least in some form, with those guys...

One way to make the interrupt assumption official is to use synchronize_sched() rather than synchronize_rcu().

Thanx, Paul

Quoted 47 lines

Cheers,
Ben.

Quoted 30 lines
Cc Nick Piggin <[email protected]>
Cc Benjamin Herrenschmidt <[email protected]>
---
 arch/powerpc/mm/gup.c |    3 +++
 1 file changed, 3 insertions(+)

Index: linux-2.6/arch/powerpc/mm/gup.c
===================================================================

arch/powerpc/mm/gup.c +2 -0
@@ -142,6 +142,7 @@ int get_user_pages_fast(unsigned long st
 	 * So long as we atomically load page table pointers versus teardown,
 	 * we can follow the address down to the the page and take a ref on it.
 	 */
+	rcu_read_lock();
 	local_irq_disable();
 
 	pgdp = pgd_offset(mm, addr);
@@ -162,6 +163,7 @@ int get_user_pages_fast(unsigned long st
 	} while (pgdp++, addr = next, addr != end);
 
 	local_irq_enable();
+	rcu_read_unlock();
 
 	VM_BUG_ON(nr != (end - start) >> PAGE_SHIFT);
 	return nr;

arch/powerpc/mm/gup.c int get_user_pages_fast(unsigned long st · line 179

Quoted 7 lines
arch/powerpc/mm/gup.c +1 -0
@@ -171,6 +173,7 @@ int get_user_pages_fast(unsigned long st
 
 slow:
 		local_irq_enable();
+		rcu_read_unlock();
 slow_irqon:
 		pr_devel("  slow path ! nr = %d\n", nr);
 

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

maintainer
#6 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 5 lines · Paul E. McKenney
Quoted 2 lines

So we might have to support the interrupt assumption, at least in some form, with those guys...

One way to make the interrupt assumption official is to use synchronize_sched() rather than synchronize_rcu().

Well, call_rcu_sched() then, because the current usage is to use call_rcu() to free the page directories.

Paul, here is a call_rcu_sched() available in kernel/rcutree.c, but am I right in reading that code that that would not be available for preemptible RCU?

#7 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 7 lines · Peter Zijlstra
Quoted 5 lines · Paul E. McKenney
Quoted 2 lines

So we might have to support the interrupt assumption, at least in some form, with those guys...

One way to make the interrupt assumption official is to use synchronize_sched() rather than synchronize_rcu().

Quoted 6 lines

Well, call_rcu_sched() then, because the current usage is to use call_rcu() to free the page directories.

Paul, here is a call_rcu_sched() available in kernel/rcutree.c, but am I right in reading that code that that would not be available for preemptible RCU?

Both call_rcu_sched() and call_rcu() are always there for you. ;-)

o If CONFIG_TREE_RCU (or CONFIG_TINY_RCU), they both have the same
implementation.

o If CONFIG_TREE_PREEMPT_RCU, call_rcu_sched() is preemptible and
call_rcu() is not.

Of course, with call_rcu_sched(), the corresponding RCU read-side critical sections are non-preemptible. Therefore, in CONFIG_PREEMPT_RT, these read-side critical sections must use raw spinlocks.

Can the code in question accommodate these restrictions?

Thanx, Paul

#8 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 7 lines · Paul E. McKenney

Of course, with call_rcu_sched(), the corresponding RCU read-side critical
sections are non-preemptible. Therefore, in CONFIG_PREEMPT_RT, these read-side critical sections must use raw spinlocks.

Can the code in question accommodate these restrictions?

What we protect against is always code that hard-disable IRQs (though there seem to be a bug in the hugepages code there...). Would that work ?

Cheers,
Ben.

#9 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 7 lines · Benjamin Herrenschmidt
Quoted 6 lines · Paul E. McKenney

Of course, with call_rcu_sched(), the corresponding RCU read-side critical
sections are non-preemptible. Therefore, in CONFIG_PREEMPT_RT, these read-side critical sections must use raw spinlocks.

Quoted 5 lines
Quoted 1 line

Can the code in question accommodate these restrictions?

What we protect against is always code that hard-disable IRQs (though there seem to be a bug in the hugepages code there...). Would that work ?

From the perspective of call_rcu_sched() and synchronize_sched(), the following things mark RCU-sched read-side critical sections:

1. rcu_read_lock_sched() and rcu_read_unlock_sched().

2. preempt_disable() and preempt_enable(), along with anything
else that disables preemption.

3. local_bh_disable() and local_bh_enable(), along with anything
else that disables bottom halves.

4. local_irq_disable() and local_irq_enable(), along wiht anything
else that disables hardirqs.

5. Handlers for NMIs.

So I believe that in this case call_rcu_sched() is your friend. ;-)

Thanx, Paul

#10 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 1 line · Paul E. McKenney

So I believe that in this case call_rcu_sched() is your friend. ;-)

Looks like it :-)

I'll cook up a patch changing my current call_rcu() to call_rcu_sched().

Cheers,
Ben.

maintainer
#11 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

In reply to Paul E. McKenney
Quoted 15 lines · Paul E. McKenney
Quoted 7 lines · Peter Zijlstra
Quoted 5 lines · Paul E. McKenney
Quoted 2 lines

So we might have to support the interrupt assumption, at least in some form, with those guys...

One way to make the interrupt assumption official is to use synchronize_sched() rather than synchronize_rcu().

Quoted 6 lines

Well, call_rcu_sched() then, because the current usage is to use call_rcu() to free the page directories.

Paul, here is a call_rcu_sched() available in kernel/rcutree.c, but am I right in reading that code that that would not be available for preemptible RCU?

Quoted 7 lines

Both call_rcu_sched() and call_rcu() are always there for you. ;-)

o If CONFIG_TREE_RCU (or CONFIG_TINY_RCU), they both have the same
implementation.

o If CONFIG_TREE_PREEMPT_RCU, call_rcu_sched() is preemptible and
call_rcu() is not.

(The reverse I suspect?)

Quoted 3 lines

Of course, with call_rcu_sched(), the corresponding RCU read-side critical sections are non-preemptible. Therefore, in CONFIG_PREEMPT_RT, these read-side critical sections must use raw spinlocks.

OK, so if we fully remove CONFIG_TREE_PREEMPT_RCU (defaulting to y), rename all the {call_rcu, rcu_read_lock, rcu_read_unlock, synchronize_rcu} functions to {*}_preempt and then add a new CONFIG_PREEMPT_RCU that simply maps {*} to either {*}_sched or {*}_preempt, we've basically got what I've been asking for for a while, no?

Quoted 1 line

Can the code in question accommodate these restrictions?

Yes, that should do just fine I think.

#12 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 18 lines · Peter Zijlstra
Quoted 17 lines · Paul E. McKenney
Quoted 7 lines · Peter Zijlstra
Quoted 5 lines · Paul E. McKenney
Quoted 2 lines

So we might have to support the interrupt assumption, at least in some form, with those guys...

One way to make the interrupt assumption official is to use synchronize_sched() rather than synchronize_rcu().

Quoted 6 lines

Well, call_rcu_sched() then, because the current usage is to use call_rcu() to free the page directories.

Paul, here is a call_rcu_sched() available in kernel/rcutree.c, but am I right in reading that code that that would not be available for preemptible RCU?

Both call_rcu_sched() and call_rcu() are always there for you. ;-)

Quoted 7 lines
Quoted 5 lines

o If CONFIG_TREE_RCU (or CONFIG_TINY_RCU), they both have the same
implementation.

o If CONFIG_TREE_PREEMPT_RCU, call_rcu_sched() is preemptible and
call_rcu() is not.

(The reverse I suspect?)

Indeed: If CONFIG_TREE_PREEMPT_RCU, call_rcu() is preemptible and call_rcu_sched() is not.

Quoted 10 lines
Quoted 3 lines

Of course, with call_rcu_sched(), the corresponding RCU read-side critical sections are non-preemptible. Therefore, in CONFIG_PREEMPT_RT, these read-side critical sections must use raw spinlocks.

OK, so if we fully remove CONFIG_TREE_PREEMPT_RCU (defaulting to y), rename all the {call_rcu, rcu_read_lock, rcu_read_unlock, synchronize_rcu} functions to {*}_preempt and then add a new CONFIG_PREEMPT_RCU that simply maps {*} to either {*}_sched or {*}_preempt, we've basically got what I've been asking for for a while, no?

What would rcu_read_lock_preempt() do in a !CONFIG_PREEMPT kernel?

Quoted 3 lines
Quoted 1 line

Can the code in question accommodate these restrictions?

Yes, that should do just fine I think.

Cool!!!

Thanx, Paul

maintainer
#13 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 4 lines · Paul E. McKenney
Quoted 4 lines
Quoted 3 lines

Of course, with call_rcu_sched(), the corresponding RCU read-side critical sections are non-preemptible. Therefore, in CONFIG_PREEMPT_RT, these read-side critical sections must use raw spinlocks.

Quoted 8 lines
Quoted 6 lines

OK, so if we fully remove CONFIG_TREE_PREEMPT_RCU (defaulting to y), rename all the {call_rcu, rcu_read_lock, rcu_read_unlock, synchronize_rcu} functions to {*}_preempt and then add a new CONFIG_PREEMPT_RCU that simply maps {*} to either {*}_sched or {*}_preempt, we've basically got what I've been asking for for a while, no?

What would rcu_read_lock_preempt() do in a !CONFIG_PREEMPT kernel?

Same as for a preempt one, since you'd have to be able to schedule() while holding it to be able to do things like mutex_lock().

#14 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 13 lines · Peter Zijlstra
Quoted 11 lines · Paul E. McKenney
Quoted 10 lines
Quoted 3 lines

Of course, with call_rcu_sched(), the corresponding RCU read-side critical sections are non-preemptible. Therefore, in CONFIG_PREEMPT_RT, these read-side critical sections must use raw spinlocks.

OK, so if we fully remove CONFIG_TREE_PREEMPT_RCU (defaulting to y), rename all the {call_rcu, rcu_read_lock, rcu_read_unlock, synchronize_rcu} functions to {*}_preempt and then add a new CONFIG_PREEMPT_RCU that simply maps {*} to either {*}_sched or {*}_preempt, we've basically got what I've been asking for for a while, no?

Quoted 4 lines
Quoted 1 line

What would rcu_read_lock_preempt() do in a !CONFIG_PREEMPT kernel?

Same as for a preempt one, since you'd have to be able to schedule() while holding it to be able to do things like mutex_lock().

So what you really want is something like rcu_read_lock_sleep() rather than rcu_read_lock_preempt(), right? The point is that you want to do more than merely preempt, given that it is legal to do general blocking while holding a mutex, correct?

Thanx, Paul

maintainer
#15 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 16 lines · Paul E. McKenney
Quoted 15 lines · Peter Zijlstra
Quoted 12 lines · Paul E. McKenney
Quoted 10 lines
Quoted 3 lines

Of course, with call_rcu_sched(), the corresponding RCU read-side critical sections are non-preemptible. Therefore, in CONFIG_PREEMPT_RT, these read-side critical sections must use raw spinlocks.

OK, so if we fully remove CONFIG_TREE_PREEMPT_RCU (defaulting to y), rename all the {call_rcu, rcu_read_lock, rcu_read_unlock, synchronize_rcu} functions to {*}_preempt and then add a new CONFIG_PREEMPT_RCU that simply maps {*} to either {*}_sched or {*}_preempt, we've basically got what I've been asking for for a while, no?

What would rcu_read_lock_preempt() do in a !CONFIG_PREEMPT kernel?

Quoted 7 lines
Quoted 2 lines

Same as for a preempt one, since you'd have to be able to schedule() while holding it to be able to do things like mutex_lock().

So what you really want is something like rcu_read_lock_sleep() rather than rcu_read_lock_preempt(), right? The point is that you want to do more than merely preempt, given that it is legal to do general blocking while holding a mutex, correct?

Right, but CONFIG_TREE_PREEMPT_RCU=y ends up being that. We could change the name to _sleep, but we've been calling it preemptible-rcu for a long while now.

#16 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 20 lines · Peter Zijlstra
Quoted 19 lines · Paul E. McKenney
Quoted 17 lines · Peter Zijlstra
Quoted 4 lines · Paul E. McKenney
Quoted 4 lines
Quoted 3 lines

Of course, with call_rcu_sched(), the corresponding RCU read-side critical sections are non-preemptible. Therefore, in CONFIG_PREEMPT_RT, these read-side critical sections must use raw spinlocks.

Quoted 8 lines
Quoted 6 lines

OK, so if we fully remove CONFIG_TREE_PREEMPT_RCU (defaulting to y), rename all the {call_rcu, rcu_read_lock, rcu_read_unlock, synchronize_rcu} functions to {*}_preempt and then add a new CONFIG_PREEMPT_RCU that simply maps {*} to either {*}_sched or {*}_preempt, we've basically got what I've been asking for for a while, no?

What would rcu_read_lock_preempt() do in a !CONFIG_PREEMPT kernel?

Same as for a preempt one, since you'd have to be able to schedule() while holding it to be able to do things like mutex_lock().

Quoted 8 lines
Quoted 4 lines

So what you really want is something like rcu_read_lock_sleep() rather than rcu_read_lock_preempt(), right? The point is that you want to do more than merely preempt, given that it is legal to do general blocking while holding a mutex, correct?

Right, but CONFIG_TREE_PREEMPT_RCU=y ends up being that. We could change the name to _sleep, but we've been calling it preemptible-rcu for a long while now.

It is actually not permitted to do general blocking in a preemptible RCU read-side critical section. Otherwise, someone is going to block waiting for a network packet that never comes, thus OOMing the system.

Thanx, Paul

maintainer
#17 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 26 lines · Paul E. McKenney
Quoted 25 lines · Peter Zijlstra
Quoted 23 lines · Paul E. McKenney
Quoted 13 lines · Peter Zijlstra
Quoted 11 lines · Paul E. McKenney
Quoted 10 lines
Quoted 3 lines

Of course, with call_rcu_sched(), the corresponding RCU read-side critical sections are non-preemptible. Therefore, in CONFIG_PREEMPT_RT, these read-side critical sections must use raw spinlocks.

OK, so if we fully remove CONFIG_TREE_PREEMPT_RCU (defaulting to y), rename all the {call_rcu, rcu_read_lock, rcu_read_unlock, synchronize_rcu} functions to {*}_preempt and then add a new CONFIG_PREEMPT_RCU that simply maps {*} to either {*}_sched or {*}_preempt, we've basically got what I've been asking for for a while, no?

Quoted 4 lines
Quoted 1 line

What would rcu_read_lock_preempt() do in a !CONFIG_PREEMPT kernel?

Same as for a preempt one, since you'd have to be able to schedule() while holding it to be able to do things like mutex_lock().

So what you really want is something like rcu_read_lock_sleep() rather than rcu_read_lock_preempt(), right? The point is that you want to do more than merely preempt, given that it is legal to do general blocking while holding a mutex, correct?

Quoted 7 lines
Quoted 3 lines

Right, but CONFIG_TREE_PREEMPT_RCU=y ends up being that. We could change the name to _sleep, but we've been calling it preemptible-rcu for a long while now.

It is actually not permitted to do general blocking in a preemptible RCU read-side critical section. Otherwise, someone is going to block waiting for a network packet that never comes, thus OOMing the system.

Sure, something that guarantees progress seems like a sensible restriction for any lock, and in particular RCU :-)

#18 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 31 lines · Peter Zijlstra
Quoted 30 lines · Paul E. McKenney
Quoted 28 lines · Peter Zijlstra
Quoted 16 lines · Paul E. McKenney
Quoted 15 lines · Peter Zijlstra
Quoted 12 lines · Paul E. McKenney
Quoted 10 lines
Quoted 3 lines

Of course, with call_rcu_sched(), the corresponding RCU read-side critical sections are non-preemptible. Therefore, in CONFIG_PREEMPT_RT, these read-side critical sections must use raw spinlocks.

OK, so if we fully remove CONFIG_TREE_PREEMPT_RCU (defaulting to y), rename all the {call_rcu, rcu_read_lock, rcu_read_unlock, synchronize_rcu} functions to {*}_preempt and then add a new CONFIG_PREEMPT_RCU that simply maps {*} to either {*}_sched or {*}_preempt, we've basically got what I've been asking for for a while, no?

What would rcu_read_lock_preempt() do in a !CONFIG_PREEMPT kernel?

Quoted 7 lines
Quoted 2 lines

Same as for a preempt one, since you'd have to be able to schedule() while holding it to be able to do things like mutex_lock().

So what you really want is something like rcu_read_lock_sleep() rather than rcu_read_lock_preempt(), right? The point is that you want to do more than merely preempt, given that it is legal to do general blocking while holding a mutex, correct?

Right, but CONFIG_TREE_PREEMPT_RCU=y ends up being that. We could change the name to _sleep, but we've been calling it preemptible-rcu for a long while now.

Quoted 6 lines
Quoted 3 lines

It is actually not permitted to do general blocking in a preemptible RCU read-side critical section. Otherwise, someone is going to block waiting for a network packet that never comes, thus OOMing the system.

Sure, something that guarantees progress seems like a sensible restriction for any lock, and in particular RCU :-)

Excellent point -- much of the issue really does center around forward-progress guarantees. In fact the Linux kernel has a number of locking primitives that require different degrees of forward-progress guarantee from the code in their respective critical sections:

o spin_lock_irqsave(): Critical sections must guarantee forward
progress against everything except NMI handlers.

o raw_spin_lock(): Critical sections must guarantee forward
progress against everything except IRQ (including softirq) and NMI handlers.

o spin_lock(): Critical sections must guarantee forward
progress against everything except IRQ (again including softirq) and NMI handlers and (given CONFIG_PREEMPT_RT) higher-priority realtime tasks.

o mutex_lock(): Critical sections need not guarantee
forward progress, as general blocking is permitted.

The other issue is the scope of the lock. The Linux kernel has the following:

o BKL: global scope.

o Everything else: scope defined by the use of the underlying
lock variable.

One of the many reasons that we are trying to get rid of BKL is because it combines global scope with relatively weak forward-progress guarantees.

So here is how the various RCU flavors stack up:

o rcu_read_lock_bh(): critical sections must guarantee forward
progress against everything except NMI handlers and IRQ handlers, but not against softirq handlers. Global in scope, so that violating the forward-progress guarantee risks OOMing the system.

o rcu_read_lock_sched(): critical sections must guarantee
forward progress against everything except NMI and IRQ handlers, including softirq handlers. Global in scope, so that violating the forward-progress guarantee risks OOMing the system.

o rcu_read_lock(): critical sections must guarantee forward
progress against everything except NMI handlers, IRQ handlers, softirq handlers, and (in CONFIG_PREEMPT_RT) higher-priority realtime tasks. Global in scope, so that violating the forward-progress guarantee risks OOMing the system.

o srcu_read_lock(): critical sections need not guarantee forward
progress, as general blocking is permitted. Scope is controlled by the use of the underlying srcu_struct structure.

As you say, one can block in rcu_read_lock() critical sections, but the only blocking that is really safe is blocking that is subject to priority inheritance. This prohibits mutexes, because although the mutexes themselves are subject to priority inheritance, the mutexes' critical sections might well not be.

So the easy response is "just use SRCU." Of course, SRCU has some disadvantages at the moment:

o The return value from srcu_read_lock() must be passed to
srcu_read_unlock(). I believe that I can fix this.

o There is no call_srcu(). I believe that I can fix this.

o SRCU uses a flat per-CPU counter scheme that is not particularly
scalable. I believe that I can fix this.

o SRCU's current implementation makes it almost impossible to
implement priority boosting. I believe that I can fix this.

o SRCU requires explicit initialization of the underlying
srcu_struct. Unfortunately, I don't see a reasonable way around this. Not yet, anyway.

So, is there anything else that you don't like about SRCU?

Thanx, Paul

maintainer
#19 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 3 lines · Paul E. McKenney

o mutex_lock(): Critical sections need not guarantee
forward progress, as general blocking is permitted.

Right, I would argue that they should guarantee fwd progress, but due to being able to schedule while holding them, its harder to enforce.

Anything that is waiting for uncertainty should do so without any locks held and simply re-acquire them once such an event does occur.

Quoted 11 lines

So the easy response is "just use SRCU." Of course, SRCU has some disadvantages at the moment:

o The return value from srcu_read_lock() must be passed to
srcu_read_unlock(). I believe that I can fix this.

o There is no call_srcu(). I believe that I can fix this.

o SRCU uses a flat per-CPU counter scheme that is not particularly
scalable. I believe that I can fix this.

Quoted 8 lines

o SRCU's current implementation makes it almost impossible to
implement priority boosting. I believe that I can fix this.

o SRCU requires explicit initialization of the underlying
srcu_struct. Unfortunately, I don't see a reasonable way around this. Not yet, anyway.

So, is there anything else that you don't like about SRCU?

No, I quite like SRCU when implemented as preemptible tree RCU, and I don't at all mind that last point, all dynamic things need some sort of init. All locks certainly have.

#20 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 9 lines · Peter Zijlstra
Quoted 3 lines · Paul E. McKenney

o mutex_lock(): Critical sections need not guarantee
forward progress, as general blocking is permitted.

Right, I would argue that they should guarantee fwd progress, but due to being able to schedule while holding them, its harder to enforce.

Anything that is waiting for uncertainty should do so without any locks held and simply re-acquire them once such an event does occur.

Agreed. But holding a small-scope mutex for (say) 60 seconds would not be a problem (at 120 seconds, you might start seeing softlockup messages). In contrast, holding off an RCU grace period for 60 seconds might well OOM the machine, especially a small embedded system with limited memory.

Quoted 18 lines
Quoted 18 lines

So the easy response is "just use SRCU." Of course, SRCU has some disadvantages at the moment:

o The return value from srcu_read_lock() must be passed to
srcu_read_unlock(). I believe that I can fix this.

o There is no call_srcu(). I believe that I can fix this.

o SRCU uses a flat per-CPU counter scheme that is not particularly
scalable. I believe that I can fix this.

o SRCU's current implementation makes it almost impossible to
implement priority boosting. I believe that I can fix this.

o SRCU requires explicit initialization of the underlying
srcu_struct. Unfortunately, I don't see a reasonable way around this. Not yet, anyway.

Quoted 5 lines
Quoted 1 line

So, is there anything else that you don't like about SRCU?

No, I quite like SRCU when implemented as preemptible tree RCU, and I don't at all mind that last point, all dynamic things need some sort of init. All locks certainly have.

Very good!!! I should clarify, though -- by "explicit initialization", I mean that there needs to be a run-time call to init_srcu_struct(). Unless there is some clever way to initialize an array of pointers to per-CPU structures at compile time. And, conversely, a way to initialize pointers in a per-CPU structure to point to possibly-different rcu_node structures.

Thanx, Paul

#21 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

In reply to Paul E. McKenney
Quoted 2 lines · Paul E. McKenney

o mutex_lock(): Critical sections need not guarantee
forward progress, as general blocking is permitted.

This isn't quite right. mutex critical sections must guarantee eventual forward progress against the class of other potential acquirers of the mutex otherwise the system will become either deadlocked or livelocked.

James

#22 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 7 lines · James Bottomley
Quoted 2 lines · Paul E. McKenney

o mutex_lock(): Critical sections need not guarantee
forward progress, as general blocking is permitted.

This isn't quite right. mutex critical sections must guarantee eventual forward progress against the class of other potential acquirers of the mutex otherwise the system will become either deadlocked or livelocked.

If I understand you correctly, you are saying that it is OK for a given critical section for a given mutex to fail to make forward progress if nothing else happens to acquire that mutex during that time. I would agree, at least I would if you were to further add that the soft-lockup checks permit an additional 120 seconds of failure to make forward progress even if something -is- attempting to acquire that mutex.

By my standards, 120 seconds is a reasonable approximation to infinity, hence my statement above.

So, would you agree with the following as a more precise statement?

o mutex_lock(): Critical sections need not guarantee
forward progress unless some other task is waiting on the mutex in question, in which case critical sections should complete in 120 seconds.

Thanx, Paul

#23 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 9 lines · Paul E. McKenney
Quoted 7 lines · James Bottomley
Quoted 2 lines · Paul E. McKenney

o mutex_lock(): Critical sections need not guarantee
forward progress, as general blocking is permitted.

This isn't quite right. mutex critical sections must guarantee eventual forward progress against the class of other potential acquirers of the mutex otherwise the system will become either deadlocked or livelocked.

Quoted 6 lines

If I understand you correctly, you are saying that it is OK for a given critical section for a given mutex to fail to make forward progress if nothing else happens to acquire that mutex during that time. I would agree, at least I would if you were to further add that the soft-lockup checks permit an additional 120 seconds of failure to make forward progress even if something -is- attempting to acquire that mutex.

Yes ... I was thinking of two specific cases: one is wrong programming of lock acquisition where the system deadlocks; the other is doing silly things like taking a mutex around an event loop instead of inside it so incoming events prevent forward progress and the system livelocks, but there are many other ways of producing deadlocks and livelocks. I just couldn't think of a concise way of saying all of that but I didn't want a statement about mutexes to give the impression that anything goes.

I've got to say that I also dislike seeing any form of sleep within a critical section because it's just asking for a nasty entangled deadlock which can be very hard to sort out. So I also didn't like the statement "general blocking is permitted"

Quoted 9 lines

By my standards, 120 seconds is a reasonable approximation to infinity, hence my statement above.

So, would you agree with the following as a more precise statement?

o mutex_lock(): Critical sections need not guarantee
forward progress unless some other task is waiting on the mutex in question, in which case critical sections should complete in 120 seconds.

Sounds fair.

James

#24 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

In reply to Paul E. McKenney
Quoted 6 lines · Paul E. McKenney
Quoted 1 line

So we might have to support the interrupt assumption, at least in

some

Quoted 1 line

form, with those guys...

One way to make the interrupt assumption official is to use synchronize_sched() rather than synchronize_rcu().

Ok, so I'm a bit of a RCU newbie as you may know :-) Right now, we use neither, we use call_rcu and we free the pages from the callback.

Ben.

#25 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 10 lines · Benjamin Herrenschmidt
Quoted 6 lines · Paul E. McKenney
Quoted 1 line

So we might have to support the interrupt assumption, at least in

some

Quoted 1 line

form, with those guys...

One way to make the interrupt assumption official is to use synchronize_sched() rather than synchronize_rcu().

Ok, so I'm a bit of a RCU newbie as you may know :-) Right now, we use neither, we use call_rcu and we free the pages from the callback.

BTW. you currently have an interesting page table freeing path where you usually free by RCU, but (occasionally) free by IPI. This means you need to disable both RCU and interrupts to walk page tables.

If you change it to always use RCU, then you wouldn't need to disable interrupts. Whether this actually matters anywhere in your mm code, I don't know (it's probably not terribly important for gup_fast). But rcu disable is always preferable for latency and performance.

#26 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 9 lines · Nick Piggin
Quoted 8 lines · Benjamin Herrenschmidt
Quoted 6 lines · Paul E. McKenney
Quoted 1 line

So we might have to support the interrupt assumption, at least in

some

Quoted 1 line

form, with those guys...

One way to make the interrupt assumption official is to use synchronize_sched() rather than synchronize_rcu().

Quoted 6 lines
Quoted 2 lines

Ok, so I'm a bit of a RCU newbie as you may know :-) Right now, we use neither, we use call_rcu and we free the pages from the callback.

BTW. you currently have an interesting page table freeing path where you usually free by RCU, but (occasionally) free by IPI. This means you need to disable both RCU and interrupts to walk page tables.

Well, the point is we use interrupts to synchronize. The fact that RCU used to do the job was an added benefit. I may need to switch to rcu _sched variants tho to keep that. The IPI case is a slow path in case we are out of memory and cannot allocate our page of RCU batch.

Quoted 4 lines

If you change it to always use RCU, then you wouldn't need to disable interrupts. Whether this actually matters anywhere in your mm code, I don't know (it's probably not terribly important for gup_fast). But rcu disable is always preferable for latency and performance.

Well, the main case is the hash miss and that always runs with IRQs off.

Cheers,
Ben.

Quoted 4 lines

--
To unsubscribe from this list: send the line "unsubscribe linux-arch" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#27 lore raw

Re: [PATCH 01/13] powerpc: Add rcu_read_lock() to gup_fast() implementation

Quoted 13 lines · Benjamin Herrenschmidt
Quoted 12 lines · Nick Piggin
Quoted 10 lines · Benjamin Herrenschmidt
Quoted 6 lines · Paul E. McKenney
Quoted 1 line

So we might have to support the interrupt assumption, at least in

some

Quoted 1 line

form, with those guys...

One way to make the interrupt assumption official is to use synchronize_sched() rather than synchronize_rcu().

Ok, so I'm a bit of a RCU newbie as you may know :-) Right now, we use neither, we use call_rcu and we free the pages from the callback.

Quoted 8 lines
Quoted 3 lines

BTW. you currently have an interesting page table freeing path where you usually free by RCU, but (occasionally) free by IPI. This means you need to disable both RCU and interrupts to walk page tables.

Well, the point is we use interrupts to synchronize. The fact that RCU used to do the job was an added benefit. I may need to switch to rcu _sched variants tho to keep that. The IPI case is a slow path in case we are out of memory and cannot allocate our page of RCU batch.

It is the slowpath but it forces all lookup paths to do irq disable too.

Quoted 6 lines
Quoted 4 lines

If you change it to always use RCU, then you wouldn't need to disable interrupts. Whether this actually matters anywhere in your mm code, I don't know (it's probably not terribly important for gup_fast). But rcu disable is always preferable for latency and performance.

Well, the main case is the hash miss and that always runs with IRQs off.

Probably not a big deal then.

#28 lore raw

PATCH 2/13 mm: Revalidate anon_vma in page_lock_anon_vma()

In reply to Peter Zijlstra

There is nothing preventing the anon_vma from being detached while we are spinning to acquire the lock. Most (all?) current users end up calling something like vma_address(page, vma) on it, which has a fairly good chance of weeding out wonky vmas.

However suppose the anon_vma got freed and re-used while we were waiting to acquire the lock, and the new anon_vma fits with the page->index (because that is the only thing vma_address() uses to determine if the page fits in a particular vma, we could end up traversing faulty anon_vma chains.

Close this hole for good by re-validating that page->mapping still holds the very same anon_vma pointer after we acquire the lock, if not be utterly paranoid and retry the whole operation (which will very likely bail, because it's unlikely the page got attached to a different anon_vma in the meantime).

Cc Hugh Dickins <[email protected]>
Cc Linus Torvalds <[email protected]>
---
 mm/rmap.c |    7 +++++++
 1 file changed, 7 insertions(+)
mm/rmap.c +7 -0
Index: linux-2.6/mm/rmap.c
===================================================================
@@ -294,6 +294,7 @@ struct anon_vma *page_lock_anon_vma(stru
 	unsigned long anon_mapping;
 
 	rcu_read_lock();
+again:
 	anon_mapping = (unsigned long) ACCESS_ONCE(page->mapping);
 	if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
 		goto out;
@@ -302,6 +303,12 @@ struct anon_vma *page_lock_anon_vma(stru
 
 	anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);
 	spin_lock(&anon_vma->lock);
+
+	if (page_rmapping(page) != anon_vma) {
+		spin_unlock(&anon_vma->lock);
+		goto again;
+	}
+
 	return anon_vma;
 out:
 	rcu_read_unlock();
Reviewed-by on v1 2/13
#29 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

Quoted 17 lines · Peter Zijlstra

There is nothing preventing the anon_vma from being detached while we are spinning to acquire the lock. Most (all?) current users end up calling something like vma_address(page, vma) on it, which has a fairly good chance of weeding out wonky vmas.

However suppose the anon_vma got freed and re-used while we were waiting to acquire the lock, and the new anon_vma fits with the page->index (because that is the only thing vma_address() uses to determine if the page fits in a particular vma, we could end up traversing faulty anon_vma chains.

Close this hole for good by re-validating that page->mapping still holds the very same anon_vma pointer after we acquire the lock, if not be utterly paranoid and retry the whole operation (which will very likely bail, because it's unlikely the page got attached to a different anon_vma in the meantime).

Quoted 3 lines
Cc Hugh Dickins<[email protected]>
Cc Linus Torvalds<[email protected]>
maintainer
#30 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

In reply to Peter Zijlstra
Quoted 2 lines · On Thu, 08 Apr 2010 21:17:39 +0200 Peter Zijlstra <[email protected]>…

There is nothing preventing the anon_vma from being detached while we are spinning to acquire the lock.

Well. The comment there clearly implies (or states) that RCU protection is used to "guard against races". If that's inaccurate or incomplete, can we please get it fixed?

The whole function makes be a bit queasy.

- Fails to explain why it pulls all these party tricks to read page->mapping a single time. What code path are we defending against here?

- Then checks page_mapped() without having any apparent defence against page_mapped() becoming untrue one nanosecond later.

- Checks page_mapped() inside the rcu_read_locked() section for inscrutable reasons.

Quoted 39 lines

Most (all?) current users end up
calling something like vma_address(page, vma) on it, which has a fairly good chance of weeding out wonky vmas.

However suppose the anon_vma got freed and re-used while we were waiting to acquire the lock, and the new anon_vma fits with the page->index (because that is the only thing vma_address() uses to determine if the page fits in a particular vma, we could end up traversing faulty anon_vma chains.

Close this hole for good by re-validating that page->mapping still holds the very same anon_vma pointer after we acquire the lock, if not be utterly paranoid and retry the whole operation (which will very likely bail, because it's unlikely the page got attached to a different anon_vma in the meantime).

Cc Hugh Dickins <[email protected]>
Cc Linus Torvalds <[email protected]>
---
 mm/rmap.c |    7 +++++++
 1 file changed, 7 insertions(+)

Index: linux-2.6/mm/rmap.c
===================================================================

mm/rmap.c +1 -0
@@ -294,6 +294,7 @@ struct anon_vma *page_lock_anon_vma(stru
 	unsigned long anon_mapping;
 
 	rcu_read_lock();
+again:
 	anon_mapping = (unsigned long) ACCESS_ONCE(page->mapping);
 	if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
 		goto out;
@@ -302,6 +303,12 @@ struct anon_vma *page_lock_anon_vma(stru
 
 	anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);

mm/rmap.c struct anon_vma *page_lock_anon_vma(stru · line 314

Quoted 10 lines
mm/rmap.c +6 -0
 	spin_lock(&anon_vma->lock);
+
+	if (page_rmapping(page) != anon_vma) {
+		spin_unlock(&anon_vma->lock);
+		goto again;
+	}
+
 	return anon_vma;
 out:
 	rcu_read_unlock();

A comment here explaining how this situation could come about would be helpful.

maintainer
#31 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

Quoted 9 lines · Andrew Morton
Quoted 2 lines · On Thu, 08 Apr 2010 21:17:39 +0200 Peter Zijlstra <[email protected]>…

There is nothing preventing the anon_vma from being detached while we are spinning to acquire the lock.

Well. The comment there clearly implies (or states) that RCU protection is used to "guard against races". If that's inaccurate or incomplete, can we please get it fixed?

Good point, goes together with that last comment you made.

Quoted 5 lines

The whole function makes be a bit queasy.

- Fails to explain why it pulls all these party tricks to read page->mapping a single time. What code path are we defending against here?

From what I understand we race with tear-down, anon_vma_unlock() takes anon_vma->lock, so holding the lock pins the anon_vma.

So what we do to acquire a stable anon_vma from a page * is to, while holding RCU read lock, very carefully read page->mapping, extract the anon_vma and acquire the lock.

Now, the RCU usage is a tad tricky here, anon_vma uses SLAB_DESTROY_BY_RCU, which means that the slab will be RCU freed, however not the objects allocated from it. This means that an anon_vma can be re-used directly after its gets freed, but the storage will remain valid for at least a grace period after the free.

So once we do have the lock we need to revalidate that we indeed got the anon_vma we throught we got.

So its:

  page->mapping = NULL;
  anon_vma_unlink();
    spin_lock()
    spin_unlock()
    kmem_cache_free(anon_vma);

VS

page_lock_anon_vma()'s trickery.

Quoted 5 lines

- Then checks page_mapped() without having any apparent defence against page_mapped() becoming untrue one nanosecond later.

- Checks page_mapped() inside the rcu_read_locked() section for inscrutable reasons.

Right, I think the page_mapped() stuff is just an early bail out.

#32 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

Hi

Quoted 7 lines
Quoted 5 lines

- Then checks page_mapped() without having any apparent defence against page_mapped() becoming untrue one nanosecond later.

- Checks page_mapped() inside the rcu_read_locked() section for inscrutable reasons.

Right, I think the page_mapped() stuff is just an early bail out.

FWIW, not only early bail out.
page_remove_rmap() don't have "page->mapping = NULL". page_remove_rmap's comment says

/*
* It would be tidy to reset the PageAnon mapping here,
* but that might overwrite a racing page_add_anon_rmap
* which increments mapcount after us but sets mapping
* before us: so leave the reset to free_hot_cold_page,
* and remember that it's only reliable while mapped.
* Leaving it set also helps swapoff to reinstate ptes
* faster for those pages still in swapcache. */

So, If following scenario happen, we can't dereference page->mapping (iow, can't call spin_lock(&anon_vma->lock)). It might be pointed invalid address.

CPU0 CPU1
===============================================================
page->mappings become -1
anon_vma_unlink()
-- grace period --

                                      page_lock_anon_vma
                                      page_mapped()
                                      spin_lock(&anon_vma->lock);

Of cource, this statement doesn't mean I'm against your patch at all. I like it.

#33 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

In reply to Peter Zijlstra

Hi, Peter.

Quoted 10 lines · Peter Zijlstra

There is nothing preventing the anon_vma from being detached while we are spinning to acquire the lock. Most (all?) current users end up calling something like vma_address(page, vma) on it, which has a fairly good chance of weeding out wonky vmas.

However suppose the anon_vma got freed and re-used while we were waiting to acquire the lock, and the new anon_vma fits with the page->index (because that is the only thing vma_address() uses to determine if the page fits in a particular vma, we could end up traversing faulty anon_vma chains.

We have second defense rule by page_check_address.
Before anon_vma is detached, pte of pages on the anon_vma should be zeroed. So can't page_check_address close the race?

Thanks for good trial for good feature.

Signature · 2 lines
Kind regards,
Minchan Kim
#34 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

In reply to Peter Zijlstra
Quoted 11 lines · Peter Zijlstra

There is nothing preventing the anon_vma from being detached while we are spinning to acquire the lock. Most (all?) current users end up calling something like vma_address(page, vma) on it, which has a fairly good chance of weeding out wonky vmas.

However suppose the anon_vma got freed and re-used while we were waiting to acquire the lock, and the new anon_vma fits with the page->index (because that is the only thing vma_address() uses to determine if the page fits in a particular vma, we could end up traversing faulty anon_vma chains.

Quoted 5 lines

Close this hole for good by re-validating that page->mapping still holds the very same anon_vma pointer after we acquire the lock, if not be utterly paranoid and retry the whole operation (which will very likely bail, because it's unlikely the page got attached to a different anon_vma in the meantime).

Hm, looks like a bugfix? How was this supposed to be safe?

Quoted 20 lines
Cc Hugh Dickins <[email protected]>
Cc Linus Torvalds <[email protected]>
---
 mm/rmap.c |    7 +++++++
 1 file changed, 7 insertions(+)

Index: linux-2.6/mm/rmap.c
===================================================================

mm/rmap.c +1 -0
@@ -294,6 +294,7 @@ struct anon_vma *page_lock_anon_vma(stru
 	unsigned long anon_mapping;
 
 	rcu_read_lock();
+again:
 	anon_mapping = (unsigned long) ACCESS_ONCE(page->mapping);
 	if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
 		goto out;

mm/rmap.c struct anon_vma *page_lock_anon_vma(stru · line 307

Quoted 5 lines
mm/rmap.c +2 -0
@@ -302,6 +303,12 @@ struct anon_vma *page_lock_anon_vma(stru
 
 	anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);
 	spin_lock(&anon_vma->lock);
+
+	if (page_rmapping(page) != anon_vma) {

very unlikely()?

Quoted 8 lines
mm/rmap.c +4 -0
+		spin_unlock(&anon_vma->lock);
+		goto again;
+	}
+
 	return anon_vma;
 out:
 	rcu_read_unlock();
#35 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

Quoted 12 lines · On Fri, 9 Apr 2010 13:16:41 +1000 Nick Piggin <[email protected]> wrote:
Quoted 11 lines · Peter Zijlstra

There is nothing preventing the anon_vma from being detached while we are spinning to acquire the lock. Most (all?) current users end up calling something like vma_address(page, vma) on it, which has a fairly good chance of weeding out wonky vmas.

However suppose the anon_vma got freed and re-used while we were waiting to acquire the lock, and the new anon_vma fits with the page->index (because that is the only thing vma_address() uses to determine if the page fits in a particular vma, we could end up traversing faulty anon_vma chains.

Quoted 8 lines
Quoted 5 lines

Close this hole for good by re-validating that page->mapping still holds the very same anon_vma pointer after we acquire the lock, if not be utterly paranoid and retry the whole operation (which will very likely bail, because it's unlikely the page got attached to a different anon_vma in the meantime).

Hm, looks like a bugfix? How was this supposed to be safe?

IIUC.

Before Rik's change to anon_vma, once page->mapping is set as anon_vma | 0x1, it's not modified until the page is freed.
After the patch, do_wp_page() overwrite page->mapping when it reuse existing page.

==
static int do_wp_page(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, pte_t *page_table, pmd_t *pmd, spinlock_t *ptl, pte_t orig_pte)
{
....

        if (PageAnon(old_page) && !PageKsm(old_page)) {
                if (!trylock_page(old_page)) {
                        page_cache_get(old_page);
....
                reuse = reuse_swap_page(old_page);
                if (reuse)
                        /*
                         * The page is all ours.  Move it to our anon_vma so
                         * the rmap code will not search our parent or siblings.
                         * Protected against the rmap code by the page lock.
                         */
                        page_move_anon_rmap(old_page, vma, address); ----(*)
}

===
(*) is new.

Then, this new check makes sense in the current kernel.

Quoted 22 lines
Quoted 22 lines
Cc Hugh Dickins <[email protected]>
Cc Linus Torvalds <[email protected]>
---
 mm/rmap.c |    7 +++++++
 1 file changed, 7 insertions(+)

Index: linux-2.6/mm/rmap.c
===================================================================

mm/rmap.c +1 -0
@@ -294,6 +294,7 @@ struct anon_vma *page_lock_anon_vma(stru
 	unsigned long anon_mapping;
 
 	rcu_read_lock();
+again:
 	anon_mapping = (unsigned long) ACCESS_ONCE(page->mapping);
 	if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
 		goto out;
@@ -302,6 +303,12 @@ struct anon_vma *page_lock_anon_vma(stru
 
Quoted 7 lines

mm/rmap.c struct anon_vma *page_lock_anon_vma(stru · line 307

Quoted 4 lines
mm/rmap.c +2 -0
 	anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);
 	spin_lock(&anon_vma->lock);
+
+	if (page_rmapping(page) != anon_vma) {

very unlikely()?

I think so.

Thanks,
-Kame

#36 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

Quoted 21 lines
Quoted 18 lines · On Fri, 9 Apr 2010 13:16:41 +1000 Nick Piggin <[email protected]> wrote:
Quoted 16 lines · Peter Zijlstra

There is nothing preventing the anon_vma from being detached while we are spinning to acquire the lock. Most (all?) current users end up calling something like vma_address(page, vma) on it, which has a fairly good chance of weeding out wonky vmas.

However suppose the anon_vma got freed and re-used while we were waiting to acquire the lock, and the new anon_vma fits with the page->index (because that is the only thing vma_address() uses to determine if the page fits in a particular vma, we could end up traversing faulty anon_vma chains.

Close this hole for good by re-validating that page->mapping still holds the very same anon_vma pointer after we acquire the lock, if not be utterly paranoid and retry the whole operation (which will very likely bail, because it's unlikely the page got attached to a different anon_vma in the meantime).

Quoted 8 lines
Quoted 2 lines

Hm, looks like a bugfix? How was this supposed to be safe?

IIUC.

Before Rik's change to anon_vma, once page->mapping is set as anon_vma | 0x1, it's not modified until the page is freed.
After the patch, do_wp_page() overwrite page->mapping when it reuse existing page.

Why?
IIUC. page->mapping dereference in page_lock_anon_vma() makes four story.

1. the anon_vma is valid

	-> do page_referenced_one(). 
2. the anon_vma is invalid and freed to buddy
	-> bail out by page_mapped(), no touch anon_vma
3. the anon_vma is kfreed, and not reused
	-> bail out by page_mapped()
4. the anon_vma is kfreed, but reused as another anon_vma
	-> bail out by page_check_address()

Now we have to consider 5th story.

5. the anon_vma is exchanged another anon_vma by do_wp_page.

	-> bail out by above bailing out stuff.

I agree peter's patch makes sense. but I don't think Rik's patch change locking rule.

Quoted 24 lines

==
static int do_wp_page(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, pte_t *page_table, pmd_t *pmd, spinlock_t *ptl, pte_t orig_pte)
{
....

        if (PageAnon(old_page) && !PageKsm(old_page)) {
                if (!trylock_page(old_page)) {
                        page_cache_get(old_page);
....
                reuse = reuse_swap_page(old_page);
                if (reuse)
                        /*
                         * The page is all ours.  Move it to our anon_vma so
                         * the rmap code will not search our parent or siblings.
                         * Protected against the rmap code by the page lock.
                         */
                        page_move_anon_rmap(old_page, vma, address); ----(*)
}

===
(*) is new.

Then, this new check makes sense in the current kernel.

#37 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

Quoted 42 lines · On Fri, 9 Apr 2010 15:34:33 +0900 (JST) KOSAKI Motohiro…
Quoted 21 lines
Quoted 18 lines · On Fri, 9 Apr 2010 13:16:41 +1000 Nick Piggin <[email protected]> wrote:
Quoted 16 lines · Peter Zijlstra

There is nothing preventing the anon_vma from being detached while we are spinning to acquire the lock. Most (all?) current users end up calling something like vma_address(page, vma) on it, which has a fairly good chance of weeding out wonky vmas.

However suppose the anon_vma got freed and re-used while we were waiting to acquire the lock, and the new anon_vma fits with the page->index (because that is the only thing vma_address() uses to determine if the page fits in a particular vma, we could end up traversing faulty anon_vma chains.

Close this hole for good by re-validating that page->mapping still holds the very same anon_vma pointer after we acquire the lock, if not be utterly paranoid and retry the whole operation (which will very likely bail, because it's unlikely the page got attached to a different anon_vma in the meantime).

Quoted 8 lines
Quoted 2 lines

Hm, looks like a bugfix? How was this supposed to be safe?

IIUC.

Before Rik's change to anon_vma, once page->mapping is set as anon_vma | 0x1, it's not modified until the page is freed.
After the patch, do_wp_page() overwrite page->mapping when it reuse existing page.

Why?
IIUC. page->mapping dereference in page_lock_anon_vma() makes four story.

1. the anon_vma is valid

	-> do page_referenced_one(). 
2. the anon_vma is invalid and freed to buddy
	-> bail out by page_mapped(), no touch anon_vma
3. the anon_vma is kfreed, and not reused
	-> bail out by page_mapped()
4. the anon_vma is kfreed, but reused as another anon_vma
	-> bail out by page_check_address()
Quoted 9 lines

Now we have to consider 5th story.

5. the anon_vma is exchanged another anon_vma by do_wp_page.

	-> bail out by above bailing out stuff.

I agree peter's patch makes sense. but I don't think Rik's patch change locking rule.

Hmm, I think following.

Assume a page is ANON and SwapCache, and it has only one reference. Consider it's read-only mapped and cause do_wp_page(). page_mapcount(page) == 1 here.

CPU0 CPU1

1. do_wp_page()
2. .....
3. replace anon_vma. anon_vma = lock_page_anon_vma()

So, lock_page_anon_vma() may have lock on wrong anon_vma, here.(mapcount=1)

4. modify pte to writable. do something...

After lock, in CPU1, a pte of estimated address by vma_address(vma, page) containes pfn of the page and page_check_address() will success.

I'm not sure how this is dangerouns.
But it's possible that CPU1 cannot notice there was anon_vma replacement. And modifies pte withoug holding anon vma's lock which the code believes it's holded.

Thanks,
-Kame

#38 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

Quoted 71 lines
Quoted 42 lines · On Fri, 9 Apr 2010 15:34:33 +0900 (JST) KOSAKI Motohiro…
Quoted 21 lines
Quoted 18 lines · On Fri, 9 Apr 2010 13:16:41 +1000 Nick Piggin <[email protected]> wrote:
Quoted 16 lines · Peter Zijlstra

There is nothing preventing the anon_vma from being detached while we are spinning to acquire the lock. Most (all?) current users end up calling something like vma_address(page, vma) on it, which has a fairly good chance of weeding out wonky vmas.

However suppose the anon_vma got freed and re-used while we were waiting to acquire the lock, and the new anon_vma fits with the page->index (because that is the only thing vma_address() uses to determine if the page fits in a particular vma, we could end up traversing faulty anon_vma chains.

Close this hole for good by re-validating that page->mapping still holds the very same anon_vma pointer after we acquire the lock, if not be utterly paranoid and retry the whole operation (which will very likely bail, because it's unlikely the page got attached to a different anon_vma in the meantime).

Quoted 8 lines
Quoted 2 lines

Hm, looks like a bugfix? How was this supposed to be safe?

IIUC.

Before Rik's change to anon_vma, once page->mapping is set as anon_vma | 0x1, it's not modified until the page is freed.
After the patch, do_wp_page() overwrite page->mapping when it reuse existing page.

Why?
IIUC. page->mapping dereference in page_lock_anon_vma() makes four story.

1. the anon_vma is valid

	-> do page_referenced_one(). 
2. the anon_vma is invalid and freed to buddy
	-> bail out by page_mapped(), no touch anon_vma
3. the anon_vma is kfreed, and not reused
	-> bail out by page_mapped()
4. the anon_vma is kfreed, but reused as another anon_vma
	-> bail out by page_check_address()
Quoted 9 lines

Now we have to consider 5th story.

5. the anon_vma is exchanged another anon_vma by do_wp_page.

	-> bail out by above bailing out stuff.

I agree peter's patch makes sense. but I don't think Rik's patch change locking rule.

Hmm, I think following.

Assume a page is ANON and SwapCache, and it has only one reference. Consider it's read-only mapped and cause do_wp_page(). page_mapcount(page) == 1 here.

CPU0 CPU1

1. do_wp_page()
2. .....
3. replace anon_vma. anon_vma = lock_page_anon_vma()

So, lock_page_anon_vma() may have lock on wrong anon_vma, here.(mapcount=1)

4. modify pte to writable. do something...

Quoted 7 lines

After lock, in CPU1, a pte of estimated address by vma_address(vma, page) containes pfn of the page and page_check_address() will success.

I'm not sure how this is dangerouns.
But it's possible that CPU1 cannot notice there was anon_vma replacement. And modifies pte withoug holding anon vma's lock which the code believes it's holded.

Hehe, page_referenced() already can take unstable VM_LOCKED value. So, In worst case we make false positive pageout, but it's not disaster. I think. Anyway "use after free" don't happen by this blutal code.

However, I think you pointed one good thing. before Rik patch, we don't have page->mapping reassignment. then, we didn't need rcu_dereference(). but now it can happen. so, I think rcu_dereference() is better.

Perhaps, I'm missing something.

mm/rmap.c +1 -1
index 8b088f0..b4a0b5b 100644
@@ -295,7 +295,7 @@ struct anon_vma *page_lock_anon_vma(struct page *page)
 	unsigned long anon_mapping;
 
 	rcu_read_lock();
-	anon_mapping = (unsigned long) ACCESS_ONCE(page->mapping);
+	anon_mapping = (unsigned long) rcu_dereference(page->mapping);
 	if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
 		goto out;
 	if (!page_mapped(page))
#39 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

Quoted 84 lines · On Fri, 9 Apr 2010 16:29:59 +0900 (JST) KOSAKI Motohiro…
Quoted 71 lines
Quoted 42 lines · On Fri, 9 Apr 2010 15:34:33 +0900 (JST) KOSAKI Motohiro…
Quoted 21 lines
Quoted 18 lines · On Fri, 9 Apr 2010 13:16:41 +1000 Nick Piggin <[email protected]> wrote:
Quoted 16 lines · Peter Zijlstra

There is nothing preventing the anon_vma from being detached while we are spinning to acquire the lock. Most (all?) current users end up calling something like vma_address(page, vma) on it, which has a fairly good chance of weeding out wonky vmas.

However suppose the anon_vma got freed and re-used while we were waiting to acquire the lock, and the new anon_vma fits with the page->index (because that is the only thing vma_address() uses to determine if the page fits in a particular vma, we could end up traversing faulty anon_vma chains.

Close this hole for good by re-validating that page->mapping still holds the very same anon_vma pointer after we acquire the lock, if not be utterly paranoid and retry the whole operation (which will very likely bail, because it's unlikely the page got attached to a different anon_vma in the meantime).

Quoted 8 lines
Quoted 2 lines

Hm, looks like a bugfix? How was this supposed to be safe?

IIUC.

Before Rik's change to anon_vma, once page->mapping is set as anon_vma | 0x1, it's not modified until the page is freed.
After the patch, do_wp_page() overwrite page->mapping when it reuse existing page.

Why?
IIUC. page->mapping dereference in page_lock_anon_vma() makes four story.

1. the anon_vma is valid

	-> do page_referenced_one(). 
2. the anon_vma is invalid and freed to buddy
	-> bail out by page_mapped(), no touch anon_vma
3. the anon_vma is kfreed, and not reused
	-> bail out by page_mapped()
4. the anon_vma is kfreed, but reused as another anon_vma
	-> bail out by page_check_address()
Quoted 9 lines

Now we have to consider 5th story.

5. the anon_vma is exchanged another anon_vma by do_wp_page.

	-> bail out by above bailing out stuff.

I agree peter's patch makes sense. but I don't think Rik's patch change locking rule.

Hmm, I think following.

Assume a page is ANON and SwapCache, and it has only one reference. Consider it's read-only mapped and cause do_wp_page(). page_mapcount(page) == 1 here.

CPU0 CPU1

1. do_wp_page()
2. .....
3. replace anon_vma. anon_vma = lock_page_anon_vma()

So, lock_page_anon_vma() may have lock on wrong anon_vma, here.(mapcount=1)

4. modify pte to writable. do something...

Quoted 7 lines

After lock, in CPU1, a pte of estimated address by vma_address(vma, page) containes pfn of the page and page_check_address() will success.

I'm not sure how this is dangerouns.
But it's possible that CPU1 cannot notice there was anon_vma replacement. And modifies pte withoug holding anon vma's lock which the code believes it's holded.

Hehe, page_referenced() already can take unstable VM_LOCKED value. So, In worst case we make false positive pageout, but it's not disaster. I think. Anyway "use after free" don't happen by this blutal code.

Quoted 6 lines

However, I think you pointed one good thing. before Rik patch, we don't have page->mapping reassignment. then, we didn't need rcu_dereference(). but now it can happen. so, I think rcu_dereference() is better.

Perhaps, I'm missing something.

Hmm. I wonder we can check "whether we lock valid anon_vma or not" only under pte_lock or lock_page().
==

	anon_vma = page_anon_vma();
	lock(anon_vma->lock);
	....
	page_check_address(page)
		....
		pte_lock();
		if (page_anon_vma(page) == anon_vma)
			# anon_vma replacement happens!
	unlock(anon_vma->lock);

==
So, rather than page_lock_anon_vma(), page_check_address() may have to check anon_vma replacement....But I cannot think of dangerous case which can cause panic for now. I may miss something...

Thanks,
-Kame

Quoted 19 lines
mm/rmap.c +1 -1
index 8b088f0..b4a0b5b 100644
@@ -295,7 +295,7 @@ struct anon_vma *page_lock_anon_vma(struct page *page)
 	unsigned long anon_mapping;
 
 	rcu_read_lock();
-	anon_mapping = (unsigned long) ACCESS_ONCE(page->mapping);
+	anon_mapping = (unsigned long) rcu_dereference(page->mapping);
 	if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
 		goto out;
 	if (!page_mapped(page))
#40 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

Quoted 51 lines · On Fri, 9 Apr 2010 16:57:03 +0900 KAMEZAWA Hiroyuki…
Quoted 29 lines · On Fri, 9 Apr 2010 16:29:59 +0900 (JST) KOSAKI Motohiro…
Quoted 16 lines

Hmm, I think following.

Assume a page is ANON and SwapCache, and it has only one reference. Consider it's read-only mapped and cause do_wp_page(). page_mapcount(page) == 1 here.

CPU0 CPU1

1. do_wp_page()
2. .....
3. replace anon_vma. anon_vma = lock_page_anon_vma()

So, lock_page_anon_vma() may have lock on wrong anon_vma, here.(mapcount=1)

4. modify pte to writable. do something...

Quoted 7 lines

After lock, in CPU1, a pte of estimated address by vma_address(vma, page) containes pfn of the page and page_check_address() will success.

I'm not sure how this is dangerouns.
But it's possible that CPU1 cannot notice there was anon_vma replacement. And modifies pte withoug holding anon vma's lock which the code believes it's holded.

Hehe, page_referenced() already can take unstable VM_LOCKED value. So, In worst case we make false positive pageout, but it's not disaster. I think. Anyway "use after free" don't happen by this blutal code.

Quoted 6 lines

However, I think you pointed one good thing. before Rik patch, we don't have page->mapping reassignment. then, we didn't need rcu_dereference(). but now it can happen. so, I think rcu_dereference() is better.

Perhaps, I'm missing something.

Hmm. I wonder we can check "whether we lock valid anon_vma or not" only under pte_lock or lock_page().
==

	anon_vma = page_anon_vma();
	lock(anon_vma->lock);
	....
	page_check_address(page)
		....
		pte_lock();
		if (page_anon_vma(page) == anon_vma)
			# anon_vma replacement happens!
	unlock(anon_vma->lock);
Quoted 5 lines

==
So, rather than page_lock_anon_vma(), page_check_address() may have to check anon_vma replacement....But I cannot think of dangerous case which can cause panic for now. I may miss something...

Ah...anon_vma replacemet occurs under lock_page() and pte_lock. Almost all callers of page_lock_anon_vma() holds lock_page(). So, I think this anon_vma replacement is not very serious.
Hmm...

Thanks,
-Kame

#41 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

Quoted 59 lines · On Fri, 9 Apr 2010 17:03:49 +0900 KAMEZAWA Hiroyuki…
Quoted 51 lines · On Fri, 9 Apr 2010 16:57:03 +0900 KAMEZAWA Hiroyuki…
Quoted 29 lines · On Fri, 9 Apr 2010 16:29:59 +0900 (JST) KOSAKI Motohiro…
Quoted 16 lines

Hmm, I think following.

Assume a page is ANON and SwapCache, and it has only one reference. Consider it's read-only mapped and cause do_wp_page(). page_mapcount(page) == 1 here.

CPU0 CPU1

1. do_wp_page()
2. .....
3. replace anon_vma. anon_vma = lock_page_anon_vma()

So, lock_page_anon_vma() may have lock on wrong anon_vma, here.(mapcount=1)

4. modify pte to writable. do something...

Quoted 7 lines

After lock, in CPU1, a pte of estimated address by vma_address(vma, page) containes pfn of the page and page_check_address() will success.

I'm not sure how this is dangerouns.
But it's possible that CPU1 cannot notice there was anon_vma replacement. And modifies pte withoug holding anon vma's lock which the code believes it's holded.

Hehe, page_referenced() already can take unstable VM_LOCKED value. So, In worst case we make false positive pageout, but it's not disaster. I think. Anyway "use after free" don't happen by this blutal code.

Quoted 6 lines

However, I think you pointed one good thing. before Rik patch, we don't have page->mapping reassignment. then, we didn't need rcu_dereference(). but now it can happen. so, I think rcu_dereference() is better.

Perhaps, I'm missing something.

Hmm. I wonder we can check "whether we lock valid anon_vma or not" only under pte_lock or lock_page().
==

	anon_vma = page_anon_vma();
	lock(anon_vma->lock);
	....
	page_check_address(page)
		....
		pte_lock();
		if (page_anon_vma(page) == anon_vma)
			# anon_vma replacement happens!
	unlock(anon_vma->lock);
Quoted 5 lines

==
So, rather than page_lock_anon_vma(), page_check_address() may have to check anon_vma replacement....But I cannot think of dangerous case which can cause panic for now. I may miss something...

Quoted 3 lines

Ah...anon_vma replacemet occurs under lock_page() and pte_lock. Almost all callers of page_lock_anon_vma() holds lock_page(). So, I think this anon_vma replacement is not very serious.

Sorry for short mails ;(

Note:vmscan.c::shrink_active_list()

	-> page_referenced()

doesn't take lock_page() and may see wrong anon_vma by replacement.

Don't we need lock_page() around ?

Thanks,
-Kame

#42 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

In reply to KOSAKI Motohiro

Hi, Kosaki.

Quoted 19 lines · KOSAKI Motohiro
Quoted 19 lines

Hmm, I think following.

Assume a page is ANON and SwapCache, and it has only one reference. Consider it's read-only mapped and cause do_wp_page(). page_mapcount(page) == 1 here.

    CPU0                          CPU1

1. do_wp_page()
2. .....
3. replace anon_vma.     anon_vma = lock_page_anon_vma()

So, lock_page_anon_vma() may have lock on wrong anon_vma, here.(mapcount=1)

4. modify pte to writable.        do something...

After lock, in CPU1, a pte of estimated address by vma_address(vma, page) containes pfn of the page and page_check_address() will success.

Quoted 8 lines
Quoted 4 lines

I'm not sure how this is dangerouns.
But it's possible that CPU1 cannot notice there was anon_vma replacement. And modifies pte withoug holding anon vma's lock which the code believes it's holded.

Hehe, page_referenced() already can take unstable VM_LOCKED value. So, In worst case we make false positive pageout, but it's not disaster.

OFF-TOPIC:

I think you pointed out good thing, too. :)

You mean although application call mlock of any vma, few pages on the vma can be swapout by race between mlock and reclaim?

Although it's not disaster, apparently it breaks API.
Man page
" mlock() and munlock()
mlock() locks pages in the address range starting at addr and
continuing for len bytes. All pages that contain a part of the specified address range are guaranteed to be resident in RAM when the call returns successfully; the pages are guaranteed to stay in RAM until later unlocked."

Do you have a plan to solve such problem?

And how about adding simple comment about that race in page_referenced_one? Could you send the patch?

Signature · 2 lines
Kind regards,
Minchan Kim
#43 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

Hi Minchan,

Quoted 16 lines

OFF-TOPIC:

I think you pointed out good thing, too. :)

You mean although application call mlock of any vma, few pages on the vma can be swapout by race between mlock and reclaim?

Although it's not disaster, apparently it breaks API.
Man page
" mlock() and munlock()
mlock() locks pages in the address range starting at addr and
continuing for len bytes. All pages that contain a part of the specified address range are guaranteed to be resident in RAM when the call returns successfully; the pages are guaranteed to stay in RAM until later unlocked."

Quoted 4 lines

Do you have a plan to solve such problem?

And how about adding simple comment about that race in page_referenced_one? Could you send the patch?

I'm surprising this mail. you were pushing much patch in this area. I believed you know all stuff ;)

My answer is, it don't need to fix, because it's not bug. The point is that this one is race issue. not "pageout after mlock" issue. If pageout and mlock occur at the exactly same time, the human can't observe which event occur in first. it's not API violation.

Thanks.

#44 lore raw

mlock and pageout race?

Hi, Kosaki.

I don't want to make noise due to off-topic.
So I open new thread.

Quoted 18 lines · KOSAKI Motohiro

Hi Minchan,

Quoted 16 lines

OFF-TOPIC:

I think you pointed out good thing, too. :)

You mean although application call mlock of any vma, few pages on the vma can be swapout by race between mlock and reclaim?

Although it's not disaster, apparently it breaks API.
Man page
" mlock() and munlock()
mlock() locks pages in the address range starting at addr and
continuing for len bytes. All pages that contain a part of the specified address range are guaranteed to be resident in RAM when the call returns successfully; the pages are guaranteed to stay in RAM until later unlocked."

Quoted 7 lines
Quoted 4 lines

Do you have a plan to solve such problem?

And how about adding simple comment about that race in page_referenced_one? Could you send the patch?

I'm surprising this mail. you were pushing much patch in this area. I believed you know all stuff ;)

If I disappoint you, sorry for that.
Still, there are many thing to study to me. :)

Quoted 5 lines

My answer is, it don't need to fix, because it's not bug. The point is that this one is race issue. not "pageout after mlock" issue. If pageout and mlock occur at the exactly same time, the human can't observe which event occur in first. it's not API violation.

If it might happen, it's obviously API violation, I think.

int main()

{
	mlock(any vma, CURRENT|FUTURE);
	system("cat /proc/self/smaps | grep "any vma");
	..
}

result :

08884000-088a5000 rw-p 00000000 00:00 0 [any vma]

Size:                  4 kB
Rss:                   4 kB

...
Swap: 4 kB
...

Apparently, user expected that "If I call mlock, there are whole pages of the vma in DRAM". But the result make him embarrassed :(

side note :
Of course, mlock's semantic is rather different with smaps's Swap. mlock's semantic just makes sure pages on DRAM after success of mlock call. it's not relate smap's swap entry.
Actually, smaps's swap entry cannot compare to mlock's semantic. Some page isn't on swap device yet but on swap cache and whole PTEs of page already have swap entry(ie, all unmapped). In such case, smap's Swap entry represent it with swap page. But with semantic of mlock, it's still on RAM so that it's okay.

I looked the code more detail.
Fortunately, the situation you said "page_referenced() already can take unstable VM_LOCKED value. So, In worst case we make false positive pageout, but it's not disaster" cannot happen, I think.

1)
mlock_fixup shrink_page_list

lock_page try_to_unmap

vma->vm_flags = VM_LOCKED
pte_lock
pte_present test
get_page
pte_unlock
pte_lock VM_LOCKED test fail pte_unlock never pageout
So, no problem.

2)
mlock_fixup shrink_page_list

lock_page try_to_unmap pte_lock VM_LOCKED test pass
vma->vm_flags = VM_LOCKED make pte to swap entry
pte_lock pte_unlock
pte_present test fail
pte_unlock
pageout
swapin by handle_mm_fault

So, no problem.

3)
mlock_fixup shrink_page_list

lock_page try_to_unmap pte_lock VM_LOCKED test pass
vma->vm_flags = VM_LOCKED make pte to swap entry
pte_lock pte_unlock
pte_present test fail
pte_unlock
cachehit in swapcache by handle_mm_fault
pageout is_page_cache_freeable fail
So, no problem, too.

I can't think the race situation you mentioned.
When 'false positive pageout' happens?
Could you elaborate on it?

Signature · 2 lines
Kind regards,
Minchan Kim
maintainer
#45 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

In reply to KOSAKI Motohiro
Quoted 15 lines · KOSAKI Motohiro
mm/rmap.c +1 -1
index 8b088f0..b4a0b5b 100644
@@ -295,7 +295,7 @@ struct anon_vma *page_lock_anon_vma(struct page
 *page)
        unsigned long anon_mapping;
 
        rcu_read_lock();
-       anon_mapping = (unsigned long) ACCESS_ONCE(page->mapping);
+       anon_mapping = (unsigned long) rcu_dereference(page->mapping);
        if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
                goto out;
Quoted 1 line
        if (!page_mapped(page)) 

Yes, I think this is indeed required.

I'll do a new version of the patch that includes the comment updates requested by Andrew.

maintainer
#46 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

Quoted 20 lines · On Fri, 09 Apr 2010 10:44:53 +0200 Peter Zijlstra <[email protected]> wrote:
Quoted 15 lines · KOSAKI Motohiro
mm/rmap.c +1 -1
index 8b088f0..b4a0b5b 100644
@@ -295,7 +295,7 @@ struct anon_vma *page_lock_anon_vma(struct page
 *page)
        unsigned long anon_mapping;
 
        rcu_read_lock();
-       anon_mapping = (unsigned long) ACCESS_ONCE(page->mapping);
+       anon_mapping = (unsigned long) rcu_dereference(page->mapping);
        if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
                goto out;
Quoted 1 line
        if (!page_mapped(page)) 

Yes, I think this is indeed required.

Quoted 2 lines

I'll do a new version of the patch that includes the comment updates requested by Andrew.

Either this didn't happen or I lost the patch.

I parked mm-revalidate-anon_vma-in-page_lock_anon_vma.patch for now. Hopefully everything still works without it..

maintainer
#47 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

Quoted 23 lines · Andrew Morton
Quoted 20 lines · On Fri, 09 Apr 2010 10:44:53 +0200 Peter Zijlstra <[email protected]> wrote:
Quoted 15 lines · KOSAKI Motohiro
mm/rmap.c +1 -1
index 8b088f0..b4a0b5b 100644
@@ -295,7 +295,7 @@ struct anon_vma *page_lock_anon_vma(struct page
 *page)
        unsigned long anon_mapping;
 
        rcu_read_lock();
-       anon_mapping = (unsigned long) ACCESS_ONCE(page->mapping);
+       anon_mapping = (unsigned long) rcu_dereference(page->mapping);
        if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
                goto out;
Quoted 1 line
        if (!page_mapped(page)) 

Yes, I think this is indeed required.

Quoted 4 lines
Quoted 2 lines

I'll do a new version of the patch that includes the comment updates requested by Andrew.

Either this didn't happen or I lost the patch.

It didn't happen, I got distracted.

Quoted 2 lines

I parked mm-revalidate-anon_vma-in-page_lock_anon_vma.patch for now. Hopefully everything still works without it..

Yes, I think it actually does due to a number of really non-obvious things ;-)

I'll try and get back to my make mmu_gather preemptible stuff shortly and write comments there.

maintainer
#48 lore raw

Re: [PATCH 02/13] mm: Revalidate anon_vma in page_lock_anon_vma()

In reply to Peter Zijlstra
Quoted 15 lines · Peter Zijlstra

Index: linux-2.6/mm/rmap.c
===================================================================

mm/rmap.c +1 -0
@@ -294,6 +294,7 @@ struct anon_vma *page_lock_anon_vma(stru
 	unsigned long anon_mapping;
 
 	rcu_read_lock();
+again:
 	anon_mapping = (unsigned long) ACCESS_ONCE(page->mapping);
 	if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
 		goto out;
@@ -302,6 +303,12 @@ struct anon_vma *page_lock_anon_vma(stru
 
 	anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);

mm/rmap.c struct anon_vma *page_lock_anon_vma(stru · line 314

Quoted 10 lines
mm/rmap.c +6 -0
 	spin_lock(&anon_vma->lock);
+
+	if (page_rmapping(page) != anon_vma) {
+		spin_unlock(&anon_vma->lock);
+		goto again;
+	}
+
 	return anon_vma;
 out:
 	rcu_read_unlock();

OK, so I'm not quite sure about this anymore... this locking is quite,.. umh,. interesting.

We have:

- page_remove_rmap() - which decrements page->_mapcount but does not
clear page->mapping. Even though both it and page_add_anon_rmap() require pte_lock, there is no guarantee these are the same locks, so these can indeed race.

So there is indeed a possibility for page->mapping to change, but holding anon_vma->lock won't make a difference.

- anon_vma->lock - this does protect vma_anon_link/unlink like things
and is held during rmap walks, it only protects the list.

- SLAB_DESTROY_BY_RCU - only guarantees we can deref anon_vma, not that
we'll find the one we were looking for.

- we generally unmap/zap before unlink/free - so this should not see
pages race with anon_vma freeing (except when someone has a page ref), however on anon_vma merges we seem to simply unlink the next one, without updating potential pages referencing it?

- KSM seems to not do anything that swap doesn't also do (but I didn't
look too closely).

- page migration seems terribly broken..

As it stands I don't see a reliable way to obtain an anon_vma, vma_address() + page_check_address() things are indeed enough, but not everybody seems to do that.

#49 lore raw

PATCH 3/13 x86: Remove last traces of quicklist usage

In reply to Peter Zijlstra

We still have a stray quicklist header included even though we axed quicklist usage quite a while back.

---
 arch/x86/mm/pgtable_32.c |    1 -
 1 file changed, 1 deletion(-)
arch/x86/mm/pgtable_32.c +0 -1
Index: linux-2.6/arch/x86/mm/pgtable_32.c
===================================================================
@@ -9,7 +9,6 @@
 #include <linux/pagemap.h>
 #include <linux/spinlock.h>
 #include <linux/module.h>
-#include <linux/quicklist.h>
 
 #include <asm/system.h>
 #include <asm/pgtable.h>
#51 lore raw

PATCH 4/13 mm: Move anon_vma ref out from under CONFIG_KSM

In reply to Peter Zijlstra

We need an anon_vma refcount for preemptible anon_vma->lock as well as memory compaction, so move it out into generic code.

---

include/linux/rmap.h +1 -23
Index: linux-2.6/include/linux/rmap.h
===================================================================
@@ -26,9 +26,7 @@
  */
 struct anon_vma {
 	spinlock_t lock;	/* Serialize access to vma list */
-#ifdef CONFIG_KSM
-	atomic_t ksm_refcount;
-#endif
+	atomic_t ref;
 	/*
 	 * NOTE: the LSB of the head.next is set by
 	 * mm_take_all_locks() _after_ taking the above lock. So the
@@ -61,26 +59,6 @@ struct anon_vma_chain {
 };
 
 #ifdef CONFIG_MMU
-#ifdef CONFIG_KSM
-static inline void ksm_refcount_init(struct anon_vma *anon_vma)
-{
-	atomic_set(&anon_vma->ksm_refcount, 0);
-}
-
-static inline int ksm_refcount(struct anon_vma *anon_vma)
-{
-	return atomic_read(&anon_vma->ksm_refcount);
-}
-#else
-static inline void ksm_refcount_init(struct anon_vma *anon_vma)
-{
-}
-
-static inline int ksm_refcount(struct anon_vma *anon_vma)
-{
-	return 0;
-}
-#endif /* CONFIG_KSM */
 
 static inline struct anon_vma *page_anon_vma(struct page *page)
 {
mm/ksm.c +2 -2
Index: linux-2.6/mm/ksm.c
===================================================================
@@ -318,14 +318,14 @@ static void hold_anon_vma(struct rmap_it
 			  struct anon_vma *anon_vma)
 {
 	rmap_item->anon_vma = anon_vma;
-	atomic_inc(&anon_vma->ksm_refcount);
+	atomic_inc(&anon_vma->ref);
 }
 
 static void drop_anon_vma(struct rmap_item *rmap_item)
 {
 	struct anon_vma *anon_vma = rmap_item->anon_vma;
 
-	if (atomic_dec_and_lock(&anon_vma->ksm_refcount, &anon_vma->lock)) {
+	if (atomic_dec_and_lock(&anon_vma->ref, &anon_vma->lock)) {
 		int empty = list_empty(&anon_vma->head);
 		spin_unlock(&anon_vma->lock);
 		if (empty)
mm/rmap.c +2 -2
Index: linux-2.6/mm/rmap.c
===================================================================
@@ -248,7 +248,7 @@ static void anon_vma_unlink(struct anon_
 	list_del(&anon_vma_chain->same_anon_vma);
 
 	/* We must garbage collect the anon_vma if it's empty */
-	empty = list_empty(&anon_vma->head) && !ksm_refcount(anon_vma);
+	empty = list_empty(&anon_vma->head) && !atomic_read(&anon_vma->ref);
 	spin_unlock(&anon_vma->lock);
 
 	if (empty)
@@ -272,7 +272,7 @@ static void anon_vma_ctor(void *data)
 	struct anon_vma *anon_vma = data;
 
 	spin_lock_init(&anon_vma->lock);
-	ksm_refcount_init(anon_vma);
+	atomic_set(&anon_vma->ref, 0);
 	INIT_LIST_HEAD(&anon_vma->head);
 }
 
#53 lore raw

PATCH 5/13 mm: Make use of the anon_vma ref count

In reply to Peter Zijlstra

This patch changes the anon_vma refcount to be 0 when the object is free. It does this by adding 1 ref to being in use in the anon_vma structure (iow. the anon_vma->head list is not empty).

This allows a simpler release scheme without having to check both the refcount and the list as well as avoids taking a ref for each entry on the list.

We then use this new refcount in the migration code to avoid a long RCU read side section and convert page_lock_anon_vma() over to use refcounts.

This later is done for each of convertion of anon_vma from spinlock to mutex.

---
 include/linux/rmap.h |    7 +++++++
 mm/ksm.c             |    9 +--------
 mm/migrate.c         |   17 ++++++-----------
 mm/rmap.c            |   45 ++++++++++++++++++++++++++++++---------------
 4 files changed, 44 insertions(+), 34 deletions(-)
include/linux/rmap.h +7 -0
Index: linux-2.6/include/linux/rmap.h
===================================================================
@@ -100,6 +100,13 @@ static inline void anon_vma_merge(struct
 	unlink_anon_vmas(next);
 }
 
+struct anon_vma *anon_vma_get(struct page *page);
+static inline void anon_vma_put(struct anon_vma *anon_vma)
+{
+	if (atomic_dec_and_test(&anon_vma->ref))
+		anon_vma_free(anon_vma);
+}
+
 /*
  * rmap interfaces called when adding or removing pte of page
  */
mm/ksm.c +1 -8
Index: linux-2.6/mm/ksm.c
===================================================================
@@ -323,14 +323,7 @@ static void hold_anon_vma(struct rmap_it
 
 static void drop_anon_vma(struct rmap_item *rmap_item)
 {
-	struct anon_vma *anon_vma = rmap_item->anon_vma;
-
-	if (atomic_dec_and_lock(&anon_vma->ref, &anon_vma->lock)) {
-		int empty = list_empty(&anon_vma->head);
-		spin_unlock(&anon_vma->lock);
-		if (empty)
-			anon_vma_free(anon_vma);
-	}
+	anon_vma_put(rmap_item->anon_vma);
 }
 
 /*
mm/migrate.c +6 -11
Index: linux-2.6/mm/migrate.c
===================================================================
@@ -545,7 +545,7 @@ static int unmap_and_move(new_page_t get
 	int rc = 0;
 	int *result = NULL;
 	struct page *newpage = get_new_page(page, private, &result);
-	int rcu_locked = 0;
+	struct anon_vma *anon_vma = NULL;
 	int charge = 0;
 	struct mem_cgroup *mem = NULL;
 
@@ -601,10 +601,8 @@ static int unmap_and_move(new_page_t get
 	 * File Caches may use write_page() or lock_page() in migration, then,
 	 * just care Anon page here.
 	 */
-	if (PageAnon(page)) {
-		rcu_read_lock();
-		rcu_locked = 1;
-	}
+	if (PageAnon(page))
+		anon_vma = anon_vma_get(page);
 
 	/*
 	 * Corner case handling:
@@ -622,10 +620,7 @@ static int unmap_and_move(new_page_t get
 		if (!PageAnon(page) && page_has_private(page)) {
 			/*
 			 * Go direct to try_to_free_buffers() here because
-			 * a) that's what try_to_release_page() would do anyway
-			 * b) we may be under rcu_read_lock() here, so we can't
-			 *    use GFP_KERNEL which is what try_to_release_page()
-			 *    needs to be effective.
+			 * that's what try_to_release_page() would do anyway
 			 */
 			try_to_free_buffers(page);
 			goto rcu_unlock;
@@ -643,8 +638,8 @@ skip_unmap:
 	if (rc)
 		remove_migration_ptes(page, page);
 rcu_unlock:
-	if (rcu_locked)
-		rcu_read_unlock();
+	if (anon_vma)
+		anon_vma_put(anon_vma);
 uncharge:
 	if (!charge)
 		mem_cgroup_end_migration(mem, page, newpage);
mm/rmap.c +30 -15

102 lines not on this page · raw message

#54 lore raw

Re: [PATCH 05/13] mm: Make use of the anon_vma ref count

Hi,

mm/rmap.c again: · line 316

Quoted 13 lines · Peter Zijlstra
mm/rmap.c +3 -2
@@ -302,23 +307,33 @@ again:
 		goto out;
 
 	anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);
-	spin_lock(&anon_vma->lock);
+	if (!atomic_inc_not_zero(&anon_vma->ref))
+		anon_vma = NULL;
 
 	if (page_rmapping(page) != anon_vma) {
-		spin_unlock(&anon_vma->lock);
+		anon_vma_put(anon_vma);
 		goto again;
 	}

AFAICS anon_vma_put might be called with anon_vma == NULL here which will oops on the ref count. Not sure if

     page_rmapping(page) == anon_vma == NULL 

is possible, too.

regards Christian

maintainer
#55 lore raw

Re: [PATCH 05/13] mm: Make use of the anon_vma ref count

Quoted 20 lines · Christian Ehrhardt

Hi,

mm/rmap.c again: · line 316

Quoted 13 lines · Peter Zijlstra
mm/rmap.c +3 -2
@@ -302,23 +307,33 @@ again:
 		goto out;
 
 	anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);
-	spin_lock(&anon_vma->lock);
+	if (!atomic_inc_not_zero(&anon_vma->ref))
+		anon_vma = NULL;
 
 	if (page_rmapping(page) != anon_vma) {
-		spin_unlock(&anon_vma->lock);
+		anon_vma_put(anon_vma);
 		goto again;
 	}

AFAICS anon_vma_put might be called with anon_vma == NULL here which will oops on the ref count. Not sure if

Quoted 3 lines
     page_rmapping(page) == anon_vma == NULL 

is possible, too.

Gah, you're right, thanks!

#56 lore raw

PATCH 6/13 mm: Preemptible mmu_gather

In reply to Peter Zijlstra

Make mmu_gather preemptible by using a small on stack list and use an option allocation to speed things up.

This breaks at least PPC for which there is a patch in -rt which still needs porting.

Preemptible mmu_gather is desired in general and usable once i_mmap_lock becomes a mutex. Doing it before the mutex conversion saves us from having to rework the code by moving the mmu_gather bits inside the i_mmap_lock.

---
 fs/exec.c                 |   10 ++++-----
 include/asm-generic/tlb.h |   51 +++++++++++++++++++++++++++++-----------------
 include/linux/mm.h        |    2 -
 mm/memory.c               |   27 +++++-------------------
 mm/mmap.c                 |   16 +++++++-------
 5 files changed, 53 insertions(+), 53 deletions(-)
fs/exec.c +5 -5
Index: linux-2.6/fs/exec.c
===================================================================
@@ -503,7 +503,7 @@ static int shift_arg_pages(struct vm_are
 	unsigned long length = old_end - old_start;
 	unsigned long new_start = old_start - shift;
 	unsigned long new_end = old_end - shift;
-	struct mmu_gather *tlb;
+	struct mmu_gather tlb;
 
 	BUG_ON(new_start > new_end);
 
@@ -529,12 +529,12 @@ static int shift_arg_pages(struct vm_are
 		return -ENOMEM;
 
 	lru_add_drain();
-	tlb = tlb_gather_mmu(mm, 0);
+	tlb_gather_mmu(&tlb, mm, 0);
 	if (new_end > old_start) {
 		/*
 		 * when the old and new regions overlap clear from new_end.
 		 */
-		free_pgd_range(tlb, new_end, old_end, new_end,
+		free_pgd_range(&tlb, new_end, old_end, new_end,
 			vma->vm_next ? vma->vm_next->vm_start : 0);
 	} else {
 		/*
@@ -543,10 +543,10 @@ static int shift_arg_pages(struct vm_are
 		 * have constraints on va-space that make this illegal (IA64) -
 		 * for the others its just a little faster.
 		 */
-		free_pgd_range(tlb, old_start, old_end, new_end,
+		free_pgd_range(&tlb, old_start, old_end, new_end,
 			vma->vm_next ? vma->vm_next->vm_start : 0);
 	}
-	tlb_finish_mmu(tlb, new_end, old_end);
+	tlb_finish_mmu(&tlb, new_end, old_end);
 
 	/*
 	 * Shrink the vma to just the new range.  Always succeeds.
include/asm-generic/tlb.h +33 -18

97 lines not on this page · raw message

include/linux/mm.h +1 -1
Index: linux-2.6/include/linux/mm.h
===================================================================
@@ -760,7 +760,7 @@ int zap_vma_ptes(struct vm_area_struct *
 		unsigned long size);
 unsigned long zap_page_range(struct vm_area_struct *vma, unsigned long address,
 		unsigned long size, struct zap_details *);
-unsigned long unmap_vmas(struct mmu_gather **tlb,
+unsigned long unmap_vmas(struct mmu_gather *tlb,
 		struct vm_area_struct *start_vma, unsigned long start_addr,
 		unsigned long end_addr, unsigned long *nr_accounted,
 		struct zap_details *);
mm/memory.c +6 -21
Index: linux-2.6/mm/memory.c
===================================================================
@@ -1095,17 +1095,14 @@ static unsigned long unmap_page_range(st
  * ensure that any thus-far unmapped pages are flushed before unmap_vmas()
  * drops the lock and schedules.
  */
-unsigned long unmap_vmas(struct mmu_gather **tlbp,
+unsigned long unmap_vmas(struct mmu_gather *tlb,
 		struct vm_area_struct *vma, unsigned long start_addr,
 		unsigned long end_addr, unsigned long *nr_accounted,
 		struct zap_details *details)
 {
 	long zap_work = ZAP_BLOCK_SIZE;
-	unsigned long tlb_start = 0;	/* For tlb_finish_mmu */
-	int tlb_start_valid = 0;
 	unsigned long start = start_addr;
 	spinlock_t *i_mmap_lock = details? details->i_mmap_lock: NULL;
-	int fullmm = (*tlbp)->fullmm;
 	struct mm_struct *mm = vma->vm_mm;
 
 	mmu_notifier_invalidate_range_start(mm, start_addr, end_addr);
@@ -1126,11 +1123,6 @@ unsigned long unmap_vmas(struct mmu_gath
 			untrack_pfn_vma(vma, 0, 0);
 
 		while (start != end) {
-			if (!tlb_start_valid) {
-				tlb_start = start;
-				tlb_start_valid = 1;
-			}
-
 			if (unlikely(is_vm_hugetlb_page(vma))) {
 				/*
 				 * It is undesirable to test vma->vm_file as it
@@ -1151,7 +1143,7 @@ unsigned long unmap_vmas(struct mmu_gath
 
 				start = end;
 			} else
-				start = unmap_page_range(*tlbp, vma,
+				start = unmap_page_range(tlb, vma,
 						start, end, &zap_work, details);
 
 			if (zap_work > 0) {
@@ -1159,19 +1151,13 @@ unsigned long unmap_vmas(struct mmu_gath
 				break;
 			}
 
-			tlb_finish_mmu(*tlbp, tlb_start, start);
-
 			if (need_resched() ||
 				(i_mmap_lock && spin_needbreak(i_mmap_lock))) {
-				if (i_mmap_lock) {
-					*tlbp = NULL;
+				if (i_mmap_lock)
 					goto out;
-				}
 				cond_resched();
 			}
 
-			*tlbp = tlb_gather_mmu(vma->vm_mm, fullmm);
-			tlb_start_valid = 0;
 			zap_work = ZAP_BLOCK_SIZE;
 		}
 	}
@@ -1191,16 +1177,15 @@ unsigned long zap_page_range(struct vm_a
 		unsigned long size, struct zap_details *details)
 {
 	struct mm_struct *mm = vma->vm_mm;
-	struct mmu_gather *tlb;
+	struct mmu_gather tlb;
 	unsigned long end = address + size;
 	unsigned long nr_accounted = 0;
 
 	lru_add_drain();
-	tlb = tlb_gather_mmu(mm, 0);
+	tlb_gather_mmu(&tlb, mm, 0);
 	update_hiwater_rss(mm);
 	end = unmap_vmas(&tlb, vma, address, end, &nr_accounted, details);
-	if (tlb)
-		tlb_finish_mmu(tlb, address, end);
+	tlb_finish_mmu(&tlb, address, end);
 	return end;
 }
 
mm/mmap.c +8 -8
Index: linux-2.6/mm/mmap.c
===================================================================
@@ -1844,17 +1844,17 @@ static void unmap_region(struct mm_struc
 		unsigned long start, unsigned long end)
 {
 	struct vm_area_struct *next = prev? prev->vm_next: mm->mmap;
-	struct mmu_gather *tlb;
+	struct mmu_gather tlb;
 	unsigned long nr_accounted = 0;
 
 	lru_add_drain();
-	tlb = tlb_gather_mmu(mm, 0);
+	tlb_gather_mmu(&tlb, mm, 0);
 	update_hiwater_rss(mm);
 	unmap_vmas(&tlb, vma, start, end, &nr_accounted, NULL);
 	vm_unacct_memory(nr_accounted);
-	free_pgtables(tlb, vma, prev? prev->vm_end: FIRST_USER_ADDRESS,
+	free_pgtables(&tlb, vma, prev? prev->vm_end: FIRST_USER_ADDRESS,
 				 next? next->vm_start: 0);
-	tlb_finish_mmu(tlb, start, end);
+	tlb_finish_mmu(&tlb, start, end);
 }
 
 /*
@@ -2190,7 +2190,7 @@ EXPORT_SYMBOL(do_brk);
 /* Release all mmaps. */
 void exit_mmap(struct mm_struct *mm)
 {
-	struct mmu_gather *tlb;
+	struct mmu_gather tlb;
 	struct vm_area_struct *vma;
 	unsigned long nr_accounted = 0;
 	unsigned long end;
@@ -2215,14 +2215,14 @@ void exit_mmap(struct mm_struct *mm)
 
 	lru_add_drain();
 	flush_cache_mm(mm);
-	tlb = tlb_gather_mmu(mm, 1);
+	tlb_gather_mmu(&tlb, mm, 1);
 	/* update_hiwater_rss(mm) here? but nobody should be looking */
 	/* Use -1 here to ensure all VMAs in the mm are unmapped */
 	end = unmap_vmas(&tlb, vma, 0, -1, &nr_accounted, NULL);
 	vm_unacct_memory(nr_accounted);
 
-	free_pgtables(tlb, vma, FIRST_USER_ADDRESS, 0);
-	tlb_finish_mmu(tlb, 0, end);
+	free_pgtables(&tlb, vma, FIRST_USER_ADDRESS, 0);
+	tlb_finish_mmu(&tlb, 0, end);
 
 	/*
 	 * Walk the list again, actually closing and freeing it,
#57 lore raw

Re: [PATCH 06/13] mm: Preemptible mmu_gather

include/asm-generic/tlb.h line 43

Quoted 13 lines · Peter Zijlstra
include/asm-generic/tlb.h +6 -1
@@ -39,30 +33,48 @@
 struct mmu_gather {
 	struct mm_struct	*mm;
 	unsigned int		nr;	/* set to ~0U means fast mode */
+	unsigned int		max;	/* nr < max */
 	unsigned int		need_flush;/* Really unmapped some ptes? */
 	unsigned int		fullmm; /* non-zero means full mm flush */
-	struct page *		pages[FREE_PTE_NR];
+#ifdef HAVE_ARCH_MMU_GATHER
+	struct arch_mmu_gather	arch;
+#endif
+	struct page		**pages;
+	struct page		*local[8];

Have you done some profiling on this? What I would like to see, if it's not too much complexity, is to have a small set of pages to handle common size frees, and then use them up first by default before attempting to allocate more.

Also, it would be cool to be able to chain allocations to avoid TLB flushes even on big frees (overridable by arch of course, in case they're doing some non-preeemptible work or you wish to break up lock hold times). But that might be just getting over engineered.

include/asm-generic/tlb.h line 48

Quoted 7 lines
include/asm-generic/tlb.h +3 -2
 };
 
-/* Users of the generic TLB shootdown code must declare this storage space. */
-DECLARE_PER_CPU(struct mmu_gather, mmu_gathers);
+static inline void __tlb_alloc_pages(struct mmu_gather *tlb)
+{
+	unsigned long addr = __get_free_pages(GFP_ATOMIC, 0);

Slab allocations should be faster, so it's nice to use them in performance critical code if you don't need the struct page.

Otherwise, looks ok to me.

maintainer
#58 lore raw

Re: [PATCH 06/13] mm: Preemptible mmu_gather

Quoted 20 lines · Nick Piggin

include/asm-generic/tlb.h line 43

Quoted 13 lines · Peter Zijlstra
include/asm-generic/tlb.h +6 -1
@@ -39,30 +33,48 @@
 struct mmu_gather {
 	struct mm_struct	*mm;
 	unsigned int		nr;	/* set to ~0U means fast mode */
+	unsigned int		max;	/* nr < max */
 	unsigned int		need_flush;/* Really unmapped some ptes? */
 	unsigned int		fullmm; /* non-zero means full mm flush */
-	struct page *		pages[FREE_PTE_NR];
+#ifdef HAVE_ARCH_MMU_GATHER
+	struct arch_mmu_gather	arch;
+#endif
+	struct page		**pages;
+	struct page		*local[8];

Have you done some profiling on this? What I would like to see, if it's not too much complexity, is to have a small set of pages to handle common size frees, and then use them up first by default before attempting to allocate more.

Quoted 4 lines

Also, it would be cool to be able to chain allocations to avoid TLB flushes even on big frees (overridable by arch of course, in case they're doing some non-preeemptible work or you wish to break up lock hold times). But that might be just getting over engineered.

Did no profiling at all, back when I wrote this I was in a hurry to get this working for -rt.

But yes, those things do look like something we want to look into, we can easily add a head structure to these pages like we did for the RCU batches.

But as it stands I think we can do those things as incrementals on top of this, no?

What kind of workload would you recommend I use to profile this?

maintainer
#59 lore raw

Re: [PATCH 06/13] mm: Preemptible mmu_gather

In reply to Nick Piggin
Quoted 10 lines · Nick Piggin

Have you done some profiling on this? What I would like to see, if it's not too much complexity, is to have a small set of pages to handle common size frees, and then use them up first by default before attempting to allocate more.

Also, it would be cool to be able to chain allocations to avoid TLB flushes even on big frees (overridable by arch of course, in case they're doing some non-preeemptible work or you wish to break up lock hold times). But that might be just getting over engineered.

Measuring ITLB_FLUSH on Intel nehalem using:

perf stat -a -e r01ae make O=defconfig-build/ -j48 bzImage

-linus      5825850 +- 2545  (100%)
 +patches   5891341 +- 6045  (101%)
 +below     5783991 +- 4725  ( 99%)

(No slab allocations yet)

---
 include/asm-generic/tlb.h |  122 ++++++++++++++++++++++++++++++----------------
 1 file changed, 82 insertions(+), 40 deletions(-)
include/asm-generic/tlb.h +82 -40
Index: linux-2.6/include/asm-generic/tlb.h
===================================================================
@@ -17,16 +17,6 @@
 #include <asm/pgalloc.h>
 #include <asm/tlbflush.h>
 
-/*
- * For UP we don't need to worry about TLB flush
- * and page free order so much..
- */
-#ifdef CONFIG_SMP
-  #define tlb_fast_mode(tlb) ((tlb)->nr == ~0U)
-#else
-  #define tlb_fast_mode(tlb) 1
-#endif
-
 #ifdef HAVE_ARCH_RCU_TABLE_FREE
 /*
  * Semi RCU freeing of the page directories.
@@ -70,31 +60,66 @@ extern void tlb_remove_table(struct mmu_
 
 #endif
 
+struct mmu_gather_batch {
+	struct mmu_gather_batch	*next;
+	unsigned int		nr;
+	unsigned int		max;
+	struct page		*pages[0];
+};
+
+#define MAX_GATHER_BATCH	\
+	((PAGE_SIZE - sizeof(struct mmu_gather_batch)) / sizeof(unsigned long))
+
 /* struct mmu_gather is an opaque type used by the mm code for passing around
  * any data needed by arch specific code for tlb_remove_page.
  */
 struct mmu_gather {
 	struct mm_struct	*mm;
-	unsigned int		nr;	/* set to ~0U means fast mode */
-	unsigned int		max;	/* nr < max */
-	unsigned int		need_flush;/* Really unmapped some ptes? */
-	unsigned int		fullmm; /* non-zero means full mm flush */
-	struct page		**pages;
-	struct page		*local[8];
+	unsigned int		need_flush : 1,	/* Did free PTEs */
+				fast_mode  : 1; /* No batching   */
+	unsigned int		fullmm;		/* Flush full mm */
+
+	struct mmu_gather_batch *active;
+	struct mmu_gather_batch	local;
+	struct page		*__pages[8];
 
 #ifdef HAVE_ARCH_RCU_TABLE_FREE
 	struct mmu_table_batch	*batch;
 #endif
 };
 
-static inline void __tlb_alloc_pages(struct mmu_gather *tlb)
+/*
+ * For UP we don't need to worry about TLB flush
+ * and page free order so much..
+ */
+#ifdef CONFIG_SMP
+  #define tlb_fast_mode(tlb) (tlb->fast_mode)
+#else
+  #define tlb_fast_mode(tlb) 1
+#endif
+
+static inline int tlb_next_batch(struct mmu_gather *tlb)
 {
-	unsigned long addr = __get_free_pages(GFP_ATOMIC, 0);
+	struct mmu_gather_batch *batch;
 
-	if (addr) {
-		tlb->pages = (void *)addr;
-		tlb->max = PAGE_SIZE / sizeof(struct page *);
+	batch = tlb->active;
+	if (batch->next) {
+		tlb->active = batch->next;
+		return 1;
 	}
+
+	batch = (void *)__get_free_pages(GFP_ATOMIC, 0);
+	if (!batch)
+		return 0;
+
+	batch->next = NULL;
+	batch->nr   = 0;
+	batch->max  = MAX_GATHER_BATCH;
+
+	tlb->active->next = batch;
+	tlb->active = batch;
+
+	return 1;
 }
 
 /* tlb_gather_mmu
@@ -105,17 +130,16 @@ tlb_gather_mmu(struct mmu_gather *tlb, s
 {
 	tlb->mm = mm;
 
-	tlb->max = ARRAY_SIZE(tlb->local);
-	tlb->pages = tlb->local;
-
-	if (num_online_cpus() > 1) {
-		tlb->nr = 0;
-		__tlb_alloc_pages(tlb);
-	} else /* Use fast mode if only one CPU is online */
-		tlb->nr = ~0U;
-
+	tlb->need_flush = 0;
+	if (num_online_cpus() == 1)
+		tlb->fast_mode = 1;
 	tlb->fullmm = full_mm_flush;
 
+	tlb->local.next = NULL;
+	tlb->local.nr   = 0;
+	tlb->local.max  = ARRAY_SIZE(tlb->__pages);
+	tlb->active     = &tlb->local;
+
 #ifdef HAVE_ARCH_RCU_TABLE_FREE
 	tlb->batch = NULL;
 #endif
@@ -124,6 +148,8 @@ tlb_gather_mmu(struct mmu_gather *tlb, s
 static inline void
 tlb_flush_mmu(struct mmu_gather *tlb, unsigned long start, unsigned long end)
 {
+	struct mmu_gather_batch *batch;
+
 	if (!tlb->need_flush)
 		return;
 	tlb->need_flush = 0;
@@ -131,12 +157,14 @@ tlb_flush_mmu(struct mmu_gather *tlb, un
 #ifdef HAVE_ARCH_RCU_TABLE_FREE
 	tlb_table_flush(tlb);
 #endif
-	if (!tlb_fast_mode(tlb)) {
-		free_pages_and_swap_cache(tlb->pages, tlb->nr);
-		tlb->nr = 0;
-		if (tlb->pages == tlb->local)
-			__tlb_alloc_pages(tlb);
+	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;
 	}
+	tlb->active = &tlb->local;
 }
 
 /* tlb_finish_mmu
@@ -146,13 +174,18 @@ tlb_flush_mmu(struct mmu_gather *tlb, un
 static inline void
 tlb_finish_mmu(struct mmu_gather *tlb, unsigned long start, unsigned long end)
 {
+	struct mmu_gather_batch *batch, *next;
+
 	tlb_flush_mmu(tlb, start, end);
 
 	/* keep the page table cache within bounds */
 	check_pgt_cache();
 
-	if (tlb->pages != tlb->local)
-		free_pages((unsigned long)tlb->pages, 0);
+	for (batch = tlb->local.next; batch; batch = next) {
+		next = batch->next;
+		free_pages((unsigned long)batch, 0);
+	}
+	tlb->local.next = NULL;
 }
 
 /* tlb_remove_page
@@ -162,14 +195,23 @@ tlb_finish_mmu(struct mmu_gather *tlb, u
  */
 static inline void tlb_remove_page(struct mmu_gather *tlb, struct page *page)
 {
+	struct mmu_gather_batch *batch;
+
 	tlb->need_flush = 1;
+
 	if (tlb_fast_mode(tlb)) {
 		free_page_and_swap_cache(page);
 		return;
 	}
-	tlb->pages[tlb->nr++] = page;
-	if (tlb->nr >= tlb->max)
-		tlb_flush_mmu(tlb, 0, 0);
+
+	batch = tlb->active;
+	if (batch->nr == batch->max) {
+		if (!tlb_next_batch(tlb))
+			tlb_flush_mmu(tlb, 0, 0);
+		batch = tlb->active;
+	}
+
+	batch->pages[batch->nr++] = page;
 }
 
 /**
maintainer
#60 lore raw

Re: [PATCH 06/13] mm: Preemptible mmu_gather

Quoted 10 lines · Peter Zijlstra
Quoted 9 lines · Nick Piggin

Have you done some profiling on this? What I would like to see, if it's not too much complexity, is to have a small set of pages to handle common size frees, and then use them up first by default before attempting to allocate more.

Also, it would be cool to be able to chain allocations to avoid TLB flushes even on big frees (overridable by arch of course, in case they're doing some non-preeemptible work or you wish to break up lock hold times). But that might be just getting over engineered.

[ patch to do very long queues ]

One thing that comes from having preemptible mmu_gather, and esp. when we allow such very long gathers, is that we can potentially have a very large amount of pages stuck on these lists.

So we'd need to hook into reclaim somehow to allow flushing of them when we're falling short.

#61 lore raw

PATCH 7/13 powerpc: Preemptible mmu_gather

In reply to Peter Zijlstra

Fix up powerpc to the new mmu_gather stuffs.

PPC has an extra batching queue to RCU free the actual pagetable allocations, use the ARCH extentions for that for now.

For the ppc64_tlb_batch, which tracks the vaddrs to unhash from the hardware hash-table, keep using per-cpu arrays but flush on context switch and use a TIF bit to track the laxy_mmu state.

Cc Benjamin Herrenschmidt <[email protected]>
---
 arch/powerpc/include/asm/pgalloc.h     |    4 +--
 arch/powerpc/include/asm/thread_info.h |    2 +
 arch/powerpc/include/asm/tlb.h         |   10 +++++++++
 arch/powerpc/include/asm/tlbflush.h    |   16 ++++++++++-----
 arch/powerpc/kernel/process.c          |   18 +++++++++++++++++
 arch/powerpc/mm/pgtable.c              |   34 +++++++++++++++++++++++----------
 arch/powerpc/mm/tlb_hash32.c           |    2 -
 arch/powerpc/mm/tlb_hash64.c           |   12 ++++++-----
 arch/powerpc/mm/tlb_nohash.c           |    2 -
 9 files changed, 76 insertions(+), 24 deletions(-)
arch/powerpc/include/asm/tlb.h +10 -0
Index: linux-2.6/arch/powerpc/include/asm/tlb.h
===================================================================
@@ -28,6 +28,16 @@
 #define tlb_start_vma(tlb, vma)	do { } while (0)
 #define tlb_end_vma(tlb, vma)	do { } while (0)
 
+#define HAVE_ARCH_MMU_GATHER 1
+
+struct pte_freelist_batch;
+
+struct arch_mmu_gather {
+	struct pte_freelist_batch *batch;
+};
+
+#define ARCH_MMU_GATHER_INIT (struct arch_mmu_gather){ .batch = NULL, }
+
 extern void tlb_flush(struct mmu_gather *tlb);
 
 /* Get the generic bits... */
arch/powerpc/include/asm/tlbflush.h +11 -5
Index: linux-2.6/arch/powerpc/include/asm/tlbflush.h
===================================================================
@@ -108,18 +108,24 @@ extern void hpte_need_flush(struct mm_st
 
 static inline void arch_enter_lazy_mmu_mode(void)
 {
-	struct ppc64_tlb_batch *batch = &__get_cpu_var(ppc64_tlb_batch);
+	struct ppc64_tlb_batch *batch = &get_cpu_var(ppc64_tlb_batch);
 
 	batch->active = 1;
+
+	put_cpu_var(ppc64_tlb_batch);
 }
 
 static inline void arch_leave_lazy_mmu_mode(void)
 {
-	struct ppc64_tlb_batch *batch = &__get_cpu_var(ppc64_tlb_batch);
+	struct ppc64_tlb_batch *batch = &get_cpu_var(ppc64_tlb_batch);
+
+	if (batch->active) {
+		if (batch->index)
+			__flush_tlb_pending(batch);
+		batch->active = 0;
+	}
 
-	if (batch->index)
-		__flush_tlb_pending(batch);
-	batch->active = 0;
+	put_cpu_var(ppc64_tlb_batch);
 }
 
 #define arch_flush_lazy_mmu_mode()      do {} while (0)
arch/powerpc/kernel/process.c +18 -0
Index: linux-2.6/arch/powerpc/kernel/process.c
===================================================================
@@ -389,6 +389,9 @@ struct task_struct *__switch_to(struct t
 	struct thread_struct *new_thread, *old_thread;
 	unsigned long flags;
 	struct task_struct *last;
+#ifdef CONFIG_PPC64
+	struct ppc64_tlb_batch *batch;
+#endif
 
 #ifdef CONFIG_SMP
 	/* avoid complexity of lazy save/restore of fpu
@@ -479,6 +482,14 @@ struct task_struct *__switch_to(struct t
 		old_thread->accum_tb += (current_tb - start_tb);
 		new_thread->start_tb = current_tb;
 	}
+
+	batch = &__get_cpu_var(ppc64_tlb_batch);
+	if (batch->active) {
+		set_ti_thread_flag(task_thread_info(prev), TIF_LAZY_MMU);
+		if (batch->index)
+			__flush_tlb_pending(batch);
+		batch->active = 0;
+	}
 #endif
 
 	local_irq_save(flags);
@@ -495,6 +506,13 @@ struct task_struct *__switch_to(struct t
 	hard_irq_disable();
 	last = _switch(old_thread, new_thread);
 
+#ifdef CONFIG_PPC64
+	if (test_and_clear_ti_thread_flag(task_thread_info(new), TIF_LAZY_MMU)) {
+		batch = &__get_cpu_var(ppc64_tlb_batch);
+		batch->active = 1;
+	}
+#endif
+
 	local_irq_restore(flags);
 
 	return last;
arch/powerpc/mm/pgtable.c +24 -10
Index: linux-2.6/arch/powerpc/mm/pgtable.c
===================================================================
@@ -33,8 +33,6 @@
 
 #include "mmu_decl.h"
 
-DEFINE_PER_CPU(struct mmu_gather, mmu_gathers);
-
 #ifdef CONFIG_SMP
 
 /*
@@ -43,7 +41,6 @@ DEFINE_PER_CPU(struct mmu_gather, mmu_ga
  * freeing a page table page that is being walked without locks
  */
 
-static DEFINE_PER_CPU(struct pte_freelist_batch *, pte_freelist_cur);
 static unsigned long pte_freelist_forced_free;
 
 struct pte_freelist_batch
@@ -98,12 +95,30 @@ static void pte_free_submit(struct pte_f
 
 void pgtable_free_tlb(struct mmu_gather *tlb, void *table, unsigned shift)
 {
-	/* This is safe since tlb_gather_mmu has disabled preemption */
-	struct pte_freelist_batch **batchp = &__get_cpu_var(pte_freelist_cur);
+	struct pte_freelist_batch **batchp = &tlb->arch.batch;
 	unsigned long pgf;
 
-	if (atomic_read(&tlb->mm->mm_users) < 2 ||
-	    cpumask_equal(mm_cpumask(tlb->mm), cpumask_of(smp_processor_id()))){
+	/*
+	 * A comment here about on why we have RCU freed page tables might be
+	 * interesting, also explaining why we don't need any sort of grace
+	 * period for mm_users == 1, and have some home brewn smp_call_func()
+	 * for single frees.
+	 *
+	 * The only lockless page table walker I know of is gup_fast() which
+	 * relies on irq_disable(). So my guess is that mm_users == 1 means
+	 * that there cannot be another thread and so precludes gup_fast()
+	 * concurrency.
+	 *
+	 * If there are, but we fail to batch, we need to IPI (all?) CPUs so as
+	 * to serialize against the IRQ disable. In case we do batch, the RCU
+	 * grace period is at least long enough to cover IRQ disabled sections
+	 * (XXX assumption, not strictly true).
+	 *
+	 * All this results in us doing our own free batching and not using
+	 * the generic mmu_gather batches (XXX fix that somehow?).
+	 */
+
+	if (atomic_read(&tlb->mm->mm_users) < 2) {
 		pgtable_free(table, shift);
 		return;
 	}
@@ -125,10 +140,9 @@ void pgtable_free_tlb(struct mmu_gather 
 	}
 }
 
-void pte_free_finish(void)
+void pte_free_finish(struct mmu_gather *tlb)
 {
-	/* This is safe since tlb_gather_mmu has disabled preemption */
-	struct pte_freelist_batch **batchp = &__get_cpu_var(pte_freelist_cur);
+	struct pte_freelist_batch **batchp = &tlb->arch.batch;
 
 	if (*batchp == NULL)
 		return;
arch/powerpc/mm/tlb_hash64.c +7 -5
Index: linux-2.6/arch/powerpc/mm/tlb_hash64.c
===================================================================
@@ -38,13 +38,11 @@ DEFINE_PER_CPU(struct ppc64_tlb_batch, p
  * neesd to be flushed. This function will either perform the flush
  * immediately or will batch it up if the current CPU has an active
  * batch on it.
- *
- * Must be called from within some kind of spinlock/non-preempt region...
  */
 void hpte_need_flush(struct mm_struct *mm, unsigned long addr,
 		     pte_t *ptep, unsigned long pte, int huge)
 {
-	struct ppc64_tlb_batch *batch = &__get_cpu_var(ppc64_tlb_batch);
+	struct ppc64_tlb_batch *batch = &get_cpu_var(ppc64_tlb_batch);
 	unsigned long vsid, vaddr;
 	unsigned int psize;
 	int ssize;
@@ -99,6 +97,7 @@ void hpte_need_flush(struct mm_struct *m
 	 */
 	if (!batch->active) {
 		flush_hash_page(vaddr, rpte, psize, ssize, 0);
+		put_cpu_var(ppc64_tlb_batch);
 		return;
 	}
 
@@ -127,6 +126,7 @@ void hpte_need_flush(struct mm_struct *m
 	batch->index = ++i;
 	if (i >= PPC64_TLB_BATCH_NR)
 		__flush_tlb_pending(batch);
+	put_cpu_var(ppc64_tlb_batch);
 }
 
 /*
@@ -155,7 +155,7 @@ void __flush_tlb_pending(struct ppc64_tl
 
 void tlb_flush(struct mmu_gather *tlb)
 {
-	struct ppc64_tlb_batch *tlbbatch = &__get_cpu_var(ppc64_tlb_batch);
+	struct ppc64_tlb_batch *tlbbatch = &get_cpu_var(ppc64_tlb_batch);
 
 	/* If there's a TLB batch pending, then we must flush it because the
 	 * pages are going to be freed and we really don't want to have a CPU
@@ -164,8 +164,10 @@ void tlb_flush(struct mmu_gather *tlb)
 	if (tlbbatch->index)
 		__flush_tlb_pending(tlbbatch);
 
+	put_cpu_var(ppc64_tlb_batch);
+
 	/* Push out batch of freed page tables */
-	pte_free_finish();
+	pte_free_finish(tlb);
 }
 
 /**
arch/powerpc/include/asm/thread_info.h +2 -0
Index: linux-2.6/arch/powerpc/include/asm/thread_info.h
===================================================================
@@ -111,6 +111,7 @@ static inline struct thread_info *curren
 #define TIF_NOTIFY_RESUME	13	/* callback before returning to user */
 #define TIF_FREEZE		14	/* Freezing for suspend */
 #define TIF_RUNLATCH		15	/* Is the runlatch enabled? */
+#define TIF_LAZY_MMU		16	/* tlb_batch is active */
 
 /* as above, but as bit values */
 #define _TIF_SYSCALL_TRACE	(1<<TIF_SYSCALL_TRACE)
@@ -128,6 +129,7 @@ static inline struct thread_info *curren
 #define _TIF_NOTIFY_RESUME	(1<<TIF_NOTIFY_RESUME)
 #define _TIF_FREEZE		(1<<TIF_FREEZE)
 #define _TIF_RUNLATCH		(1<<TIF_RUNLATCH)
+#define _TIF_LAZY_MMU		(1<<TIF_LAZY_MMU)
 #define _TIF_SYSCALL_T_OR_A	(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SECCOMP)
 
 #define _TIF_USER_WORK_MASK	(_TIF_SIGPENDING | _TIF_NEED_RESCHED | \
arch/powerpc/include/asm/pgalloc.h +2 -2
Index: linux-2.6/arch/powerpc/include/asm/pgalloc.h
===================================================================
@@ -32,13 +32,13 @@ static inline void pte_free(struct mm_st
 
 #ifdef CONFIG_SMP
 extern void pgtable_free_tlb(struct mmu_gather *tlb, void *table, unsigned shift);
-extern void pte_free_finish(void);
+extern void pte_free_finish(struct mmu_gather *tlb);
 #else /* CONFIG_SMP */
 static inline void pgtable_free_tlb(struct mmu_gather *tlb, void *table, unsigned shift)
 {
 	pgtable_free(table, shift);
 }
-static inline void pte_free_finish(void) { }
+static inline void pte_free_finish(struct mmu_gather *tlb) { }
 #endif /* !CONFIG_SMP */
 
 static inline void __pte_free_tlb(struct mmu_gather *tlb, struct page *ptepage,
arch/powerpc/mm/tlb_hash32.c +1 -1
Index: linux-2.6/arch/powerpc/mm/tlb_hash32.c
===================================================================
@@ -73,7 +73,7 @@ void tlb_flush(struct mmu_gather *tlb)
 	}
 
 	/* Push out batch of freed page tables */
-	pte_free_finish();
+	pte_free_finish(tlb);
 }
 
 /*
arch/powerpc/mm/tlb_nohash.c +1 -1
Index: linux-2.6/arch/powerpc/mm/tlb_nohash.c
===================================================================
@@ -298,7 +298,7 @@ void tlb_flush(struct mmu_gather *tlb)
 	flush_tlb_mm(tlb->mm);
 
 	/* Push out batch of freed page tables */
-	pte_free_finish();
+	pte_free_finish(tlb);
 }
 
 /*
#62 lore raw

Re: [PATCH 07/13] powerpc: Preemptible mmu_gather

Quoted 8 lines · Peter Zijlstra

Fix up powerpc to the new mmu_gather stuffs.

PPC has an extra batching queue to RCU free the actual pagetable allocations, use the ARCH extentions for that for now.

For the ppc64_tlb_batch, which tracks the vaddrs to unhash from the hardware hash-table, keep using per-cpu arrays but flush on context switch and use a TIF bit to track the laxy_mmu state.

Hm. Pity powerpc can't just use tlb flush gathering for this batching, (which is what it was designed for). Then it could avoid these tricks. What's preventing this? Adding a tlb gather for COW case in copy_page_range?

maintainer
#63 lore raw

Re: [PATCH 07/13] powerpc: Preemptible mmu_gather

Quoted 6 lines · Nick Piggin
Quoted 5 lines · Peter Zijlstra

Fix up powerpc to the new mmu_gather stuffs.

PPC has an extra batching queue to RCU free the actual pagetable allocations, use the ARCH extentions for that for now.

Quoted 8 lines
Quoted 3 lines

For the ppc64_tlb_batch, which tracks the vaddrs to unhash from the hardware hash-table, keep using per-cpu arrays but flush on context switch and use a TIF bit to track the laxy_mmu state.

Hm. Pity powerpc can't just use tlb flush gathering for this batching, (which is what it was designed for). Then it could avoid these tricks. What's preventing this? Adding a tlb gather for COW case in copy_page_range?

I'm not quite sure what about that, didn't fully investigate it, just wanted to get something working for now.

Of of the things is that both power and sparc need more than the struct page we normally gather.

I did think of making the mmu_gather have something like

struct mmu_page {
struct page *page;
#ifdef HAVE_ARCH_TLB_VADDR
unsigned long vaddr;
#endif
};

struct mmu_gather {
...
unsigned int nr;
struct mmu_page *pages;
};

and doing that vaddr collection right along with it in the same batch.

I think that that would work, Ben, Dave?

#64 lore raw

Re: [PATCH 07/13] powerpc: Preemptible mmu_gather

Quoted 11 lines · Peter Zijlstra
Quoted 10 lines · Nick Piggin
Quoted 8 lines · Peter Zijlstra

Fix up powerpc to the new mmu_gather stuffs.

PPC has an extra batching queue to RCU free the actual pagetable allocations, use the ARCH extentions for that for now.

For the ppc64_tlb_batch, which tracks the vaddrs to unhash from the hardware hash-table, keep using per-cpu arrays but flush on context switch and use a TIF bit to track the laxy_mmu state.

Quoted 7 lines
Quoted 4 lines

Hm. Pity powerpc can't just use tlb flush gathering for this batching, (which is what it was designed for). Then it could avoid these tricks. What's preventing this? Adding a tlb gather for COW case in copy_page_range?

I'm not quite sure what about that, didn't fully investigate it, just wanted to get something working for now.

No it's not your problem, but just perhaps a good add-on to your patchset. Thanks for thinking about it, though.

Quoted 4 lines

Of of the things is that both power and sparc need more than the struct page we normally gather.

Quoted 8 lines

I did think of making the mmu_gather have something like

struct mmu_page {
struct page *page;
#ifdef HAVE_ARCH_TLB_VADDR
unsigned long vaddr;
#endif
};

Well you could also have a per-arch struct for this, which they can fill in their own info with (I think powerpc takes the pte as well)

Quoted 11 lines

struct mmu_gather {
...
unsigned int nr;
struct mmu_page *pages;
};

and doing that vaddr collection right along with it in the same batch.

I think that that would work, Ben, Dave?

maintainer
#65 lore raw

Re: [PATCH 07/13] powerpc: Preemptible mmu_gather

Quoted 9 lines · Nick Piggin
Quoted 6 lines

struct mmu_page {
struct page *page;
#ifdef HAVE_ARCH_TLB_VADDR
unsigned long vaddr;
#endif
};

Well you could also have a per-arch struct for this, which they can fill in their own info with (I think powerpc takes the pte as well)

Ah, right you are.

Maybe something like:

#ifndef tlb_fill_page

struct mmu_page {
	struct page *page;
};

#define tlb_fill_page(tlb, pte, addr) do { } while (0) #endif

Which can be used from the tlb_remove_tlb_entry() implementation to gather both the pte and addr. The only complication seems to be that tlb_remove_tlb_entry() and tlb_remove_page() aren't always matched.

Also, it looks like both power and sparc drive the pte/vaddr flush from the pte_update/set_pte_at like functions. Which means its not synced to the mmu_gather at all.

So I'm not at all sure its feasible to link these, but I'll keep it in mind.

#66 lore raw

Re: [PATCH 07/13] powerpc: Preemptible mmu_gather

In reply to Peter Zijlstra
Quoted 4 lines · Peter Zijlstra

and doing that vaddr collection right along with it in the same batch.

I think that that would work, Ben, Dave?

Well, ours aren't struct pages.

IE. There's fundamentally 3 things that we are trying to batch here :-)

1- The original mmu_gather: batching the freeing of the actual user
pages, so that the TLB flush can be delayed/gathered, plus there might be some micro-improvement in passing the page list to the allocator for freeing all at omce. This is thus purely a batch of struct pages.

2- The batching of the TLB flushes (or hash invalidates in the ppc
case) proper, which needs the addition of the vaddr for things like sparc and powerpc since we don't just invalidate the whole bloody thing unlike x86 :-) On powerpc, we actually need more, we need the actual PTE content since it also contains tracking information relative to where things have been put in the hash table.

3- The batching of the freeing of the page table structure, which we
want to delay more than batch, ie, the goal here is to delay that freeing using RCU until everybody has stopped walking them. This does rely on RCU grace period being "interrupt safe", ie, there's no rcu_read_lock() in the low level TLB or hash miss code, but that code runs with interrupts off.

Now, 2. has a problem I described earlier, which is that we must not have the possibility of introducing a duplicate in the hash table, thus it must not be possible to put a new PTE in until the previous one has been flushed or bad things would happen. This is why powerpc doesn't use the mmu_gather the way it was originally intended to do both 1. and 2. but really only for 1., while for 2. we use a small batch that only exist between lazy_mmu_enter/exit, since those are always fully enclosed by a pte lock section.

3. As you have noticed, relies on the irq stuff. Plus there seem to be a dubious optimization here with mm_users. Might be worth sorting that out. However, it's a very different goal than 1. and 2. in the sense that batching proper is a minor issue, what we want is synchronization with walkers, and that batching is a way to lower the cost of that synchronization (allocating of the RCU struct etc...).

Cheers,
Ben.

#67 lore raw

Re: [PATCH 07/13] powerpc: Preemptible mmu_gather

In reply to Nick Piggin
Quoted 11 lines · Nick Piggin
Quoted 6 lines

PPC has an extra batching queue to RCU free the actual pagetable allocations, use the ARCH extentions for that for now.

For the ppc64_tlb_batch, which tracks the vaddrs to unhash from the hardware hash-table, keep using per-cpu arrays but flush on context switch and use a TIF bit to track the laxy_mmu state.

Hm. Pity powerpc can't just use tlb flush gathering for this batching, (which is what it was designed for). Then it could avoid these tricks. What's preventing this? Adding a tlb gather for COW case in copy_page_range?

We must flush before the pte_lock is released. If not, we end up with this funny situation:

- PTE is read-only, hash contains a translation for it
- PTE gets cleared & added to the batch, hash not flushed yet
- PTE lock released, maybe even VMA fully removed
- Other CPU takes a write fault, puts in a new PTE
- Hash ends up with duplicates of the vaddr -> arch violation

Now we could get out of that one, I suppose, if we had some kind of way to force flush any batch pertaining to a given mm before a new valid PTE can be written, but that doesn't sound such a trivial thing to do.

Any better idea ?

Cheers,
Ben.

#68 lore raw

Re: [PATCH 07/13] powerpc: Preemptible mmu_gather

In reply to Peter Zijlstra

On Thu, 2010-04-08 at 21:17 +0200, Peter Zijlstra wrote:

.../...

arch/powerpc/include/asm/tlbflush.h extern void hpte_need_flush(struct mm_st · line 126

Quoted 10 lines

static inline void arch_leave_lazy_mmu_mode(void) {

arch/powerpc/include/asm/tlbflush.h +7 -1
-	struct ppc64_tlb_batch *batch = &__get_cpu_var(ppc64_tlb_batch);
+	struct ppc64_tlb_batch *batch = &get_cpu_var(ppc64_tlb_batch);
+
+	if (batch->active) {
+		if (batch->index)
+			__flush_tlb_pending(batch);
+		batch->active = 0;
+	}

Can index be > 0 if active == 0 ? I though not, which means you don't need to add a test on active, do you ?

I'm also pondering whether we should just stick something in the task/thread struct and avoid that whole per-cpu manipulation including the stuff in process.c in fact.

Heh, maybe it's time to introduce thread vars ? :-)

Quoted 24 lines
arch/powerpc/include/asm/tlbflush.h +1 -3
-	if (batch->index)
-		__flush_tlb_pending(batch);
-	batch->active = 0;
+	put_cpu_var(ppc64_tlb_batch);
 }
 
 #define arch_flush_lazy_mmu_mode()      do {} while (0)

Index: linux-2.6/arch/powerpc/kernel/process.c
===================================================================

arch/powerpc/kernel/process.c +3 -0
@@ -389,6 +389,9 @@ struct task_struct *__switch_to(struct t
 	struct thread_struct *new_thread, *old_thread;
 	unsigned long flags;
 	struct task_struct *last;
+#ifdef CONFIG_PPC64
+	struct ppc64_tlb_batch *batch;
+#endif
 
 #ifdef CONFIG_SMP
 	/* avoid complexity of lazy save/restore of fpu
@@ -479,6 +482,14 @@ struct task_struct *__switch_to(struct t
 		old_thread->accum_tb += (current_tb - start_tb);
 		new_thread->start_tb = current_tb;

arch/powerpc/kernel/process.c struct task_struct *__switch_to(struct t · line 493

Quoted 10 lines
arch/powerpc/kernel/process.c +8 -0
 	}
+
+	batch = &__get_cpu_var(ppc64_tlb_batch);
+	if (batch->active) {
+		set_ti_thread_flag(task_thread_info(prev), TIF_LAZY_MMU);
+		if (batch->index)
+			__flush_tlb_pending(batch);
+		batch->active = 0;
+	}
 #endif

Use ti->local_flags so you can do it non-atomically, which is a lot cheaper.

Quoted 42 lines
arch/powerpc/kernel/process.c +7 -0
 	local_irq_save(flags);
@@ -495,6 +506,13 @@ struct task_struct *__switch_to(struct t
 	hard_irq_disable();
 	last = _switch(old_thread, new_thread);
 
+#ifdef CONFIG_PPC64
+	if (test_and_clear_ti_thread_flag(task_thread_info(new), TIF_LAZY_MMU)) {
+		batch = &__get_cpu_var(ppc64_tlb_batch);
+		batch->active = 1;
+	}
+#endif
+
 	local_irq_restore(flags);
 
 	return last;

Index: linux-2.6/arch/powerpc/mm/pgtable.c
===================================================================

arch/powerpc/mm/pgtable.c +0 -5
@@ -33,8 +33,6 @@
 
 #include "mmu_decl.h"
 
-DEFINE_PER_CPU(struct mmu_gather, mmu_gathers);
-
 #ifdef CONFIG_SMP
 
 /*
@@ -43,7 +41,6 @@ DEFINE_PER_CPU(struct mmu_gather, mmu_ga
  * freeing a page table page that is being walked without locks
  */
 
-static DEFINE_PER_CPU(struct pte_freelist_batch *, pte_freelist_cur);
 static unsigned long pte_freelist_forced_free;
 
 struct pte_freelist_batch
@@ -98,12 +95,30 @@ static void pte_free_submit(struct pte_f
 
 void pgtable_free_tlb(struct mmu_gather *tlb, void *table, unsigned shift)
 {
-	/* This is safe since tlb_gather_mmu has disabled preemption */
-	struct pte_freelist_batch **batchp = &__get_cpu_var(pte_freelist_cur);

arch/powerpc/mm/pgtable.c static void pte_free_submit(struct pte_f · line 105

Quoted 10 lines
arch/powerpc/mm/pgtable.c +6 -2
+	struct pte_freelist_batch **batchp = &tlb->arch.batch;
 	unsigned long pgf;
 
-	if (atomic_read(&tlb->mm->mm_users) < 2 ||
-	    cpumask_equal(mm_cpumask(tlb->mm), cpumask_of(smp_processor_id()))){
+	/*
+	 * A comment here about on why we have RCU freed page tables might be
+	 * interesting, also explaining why we don't need any sort of grace
+	 * period for mm_users == 1, and have some home brewn smp_call_func()
+	 * for single frees.

iirc, we are synchronizing with CPUs walking page tables in their hash or TLB miss code, which is lockless. The mm_users test is a -little- bit dubious indeed. It may have to be mm_users < 2 && mm == current->active_mm, ie, we know for sure nobody else is currently walking those page tables ...

Tho even than is fishy nowadays. We -can- walk page tables on behave of another process. In fact, we do it in the Cell SPU code for faulting page table entries as a result of SPEs taking faults for example. So I'm starting to suspect that this mm_users optimisation is bogus.

We -do- want to optimize out the case where there is no user left though, ie, the MM is dead. IE. The typical exit case.

arch/powerpc/mm/pgtable.c static void pte_free_submit(struct pte_f · line 110

Quoted 5 lines
arch/powerpc/mm/pgtable.c +5 -0
+	 *
+	 * The only lockless page table walker I know of is gup_fast() which
+	 * relies on irq_disable(). So my guess is that mm_users == 1 means
+	 * that there cannot be another thread and so precludes gup_fast()
+	 * concurrency.

Which is fishy as I said above.

arch/powerpc/mm/pgtable.c static void pte_free_submit(struct pte_f · line 115

Quoted 4 lines
arch/powerpc/mm/pgtable.c +4 -0
+	 * If there are, but we fail to batch, we need to IPI (all?) CPUs so as
+	 * to serialize against the IRQ disable. In case we do batch, the RCU
+	 * grace period is at least long enough to cover IRQ disabled sections
+	 * (XXX assumption, not strictly true).

Yeah well ... I'm not that familiar with RCU anymore. Back when I wrote that, iirc, I would more or less be safe against a CPU that doesn't schedule, but things may have well changed.

We are trying to be safe against another CPU walking page tables in the asm lockless hash miss or TLB miss code. Note that sparc64 has a similar issue. This is highly optimized asm code that -cannot- call into things like rcu_read_lock().

arch/powerpc/mm/pgtable.c static void pte_free_submit(struct pte_f · line 119

Quoted 3 lines
arch/powerpc/mm/pgtable.c +3 -0
+	 * All this results in us doing our own free batching and not using
+	 * the generic mmu_gather batches (XXX fix that somehow?).
+	 */

When this was written, generic batch only dealt with the actual pages, not the page tables, and as you may have noticed, our page tables on powerpc aren't struct page*, they come from dedicated slab caches and are of variable sizes.

Cheers,
Ben.

Quoted 140 lines
arch/powerpc/mm/pgtable.c +3 -3
+	if (atomic_read(&tlb->mm->mm_users) < 2) {
 		pgtable_free(table, shift);
 		return;
 	}
@@ -125,10 +140,9 @@ void pgtable_free_tlb(struct mmu_gather 
 	}
 }
 
-void pte_free_finish(void)
+void pte_free_finish(struct mmu_gather *tlb)
 {
-	/* This is safe since tlb_gather_mmu has disabled preemption */
-	struct pte_freelist_batch **batchp = &__get_cpu_var(pte_freelist_cur);
+	struct pte_freelist_batch **batchp = &tlb->arch.batch;
 
 	if (*batchp == NULL)
 		return;

Index: linux-2.6/arch/powerpc/mm/tlb_hash64.c
===================================================================

arch/powerpc/mm/tlb_hash64.c +7 -5
@@ -38,13 +38,11 @@ DEFINE_PER_CPU(struct ppc64_tlb_batch, p
  * neesd to be flushed. This function will either perform the flush
  * immediately or will batch it up if the current CPU has an active
  * batch on it.
- *
- * Must be called from within some kind of spinlock/non-preempt region...
  */
 void hpte_need_flush(struct mm_struct *mm, unsigned long addr,
 		     pte_t *ptep, unsigned long pte, int huge)
 {
-	struct ppc64_tlb_batch *batch = &__get_cpu_var(ppc64_tlb_batch);
+	struct ppc64_tlb_batch *batch = &get_cpu_var(ppc64_tlb_batch);
 	unsigned long vsid, vaddr;
 	unsigned int psize;
 	int ssize;
@@ -99,6 +97,7 @@ void hpte_need_flush(struct mm_struct *m
 	 */
 	if (!batch->active) {
 		flush_hash_page(vaddr, rpte, psize, ssize, 0);
+		put_cpu_var(ppc64_tlb_batch);
 		return;
 	}
 
@@ -127,6 +126,7 @@ void hpte_need_flush(struct mm_struct *m
 	batch->index = ++i;
 	if (i >= PPC64_TLB_BATCH_NR)
 		__flush_tlb_pending(batch);
+	put_cpu_var(ppc64_tlb_batch);
 }
 
 /*
@@ -155,7 +155,7 @@ void __flush_tlb_pending(struct ppc64_tl
 
 void tlb_flush(struct mmu_gather *tlb)
 {
-	struct ppc64_tlb_batch *tlbbatch = &__get_cpu_var(ppc64_tlb_batch);
+	struct ppc64_tlb_batch *tlbbatch = &get_cpu_var(ppc64_tlb_batch);
 
 	/* If there's a TLB batch pending, then we must flush it because the
 	 * pages are going to be freed and we really don't want to have a CPU
@@ -164,8 +164,10 @@ void tlb_flush(struct mmu_gather *tlb)
 	if (tlbbatch->index)
 		__flush_tlb_pending(tlbbatch);
 
+	put_cpu_var(ppc64_tlb_batch);
+
 	/* Push out batch of freed page tables */
-	pte_free_finish();
+	pte_free_finish(tlb);
 }
 
 /**

Index: linux-2.6/arch/powerpc/include/asm/thread_info.h
===================================================================

arch/powerpc/include/asm/thread_info.h +2 -0
@@ -111,6 +111,7 @@ static inline struct thread_info *curren
 #define TIF_NOTIFY_RESUME	13	/* callback before returning to user */
 #define TIF_FREEZE		14	/* Freezing for suspend */
 #define TIF_RUNLATCH		15	/* Is the runlatch enabled? */
+#define TIF_LAZY_MMU		16	/* tlb_batch is active */
 
 /* as above, but as bit values */
 #define _TIF_SYSCALL_TRACE	(1<<TIF_SYSCALL_TRACE)
@@ -128,6 +129,7 @@ static inline struct thread_info *curren
 #define _TIF_NOTIFY_RESUME	(1<<TIF_NOTIFY_RESUME)
 #define _TIF_FREEZE		(1<<TIF_FREEZE)
 #define _TIF_RUNLATCH		(1<<TIF_RUNLATCH)
+#define _TIF_LAZY_MMU		(1<<TIF_LAZY_MMU)
 #define _TIF_SYSCALL_T_OR_A	(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SECCOMP)
 
 #define _TIF_USER_WORK_MASK	(_TIF_SIGPENDING | _TIF_NEED_RESCHED | \

Index: linux-2.6/arch/powerpc/include/asm/pgalloc.h
===================================================================

arch/powerpc/include/asm/pgalloc.h +2 -2
@@ -32,13 +32,13 @@ static inline void pte_free(struct mm_st
 
 #ifdef CONFIG_SMP
 extern void pgtable_free_tlb(struct mmu_gather *tlb, void *table, unsigned shift);
-extern void pte_free_finish(void);
+extern void pte_free_finish(struct mmu_gather *tlb);
 #else /* CONFIG_SMP */
 static inline void pgtable_free_tlb(struct mmu_gather *tlb, void *table, unsigned shift)
 {
 	pgtable_free(table, shift);
 }
-static inline void pte_free_finish(void) { }
+static inline void pte_free_finish(struct mmu_gather *tlb) { }
 #endif /* !CONFIG_SMP */
 
 static inline void __pte_free_tlb(struct mmu_gather *tlb, struct page *ptepage,

Index: linux-2.6/arch/powerpc/mm/tlb_hash32.c
===================================================================

arch/powerpc/mm/tlb_hash32.c +1 -1
@@ -73,7 +73,7 @@ void tlb_flush(struct mmu_gather *tlb)
 	}
 
 	/* Push out batch of freed page tables */
-	pte_free_finish();
+	pte_free_finish(tlb);
 }
 
 /*

Index: linux-2.6/arch/powerpc/mm/tlb_nohash.c
===================================================================

arch/powerpc/mm/tlb_nohash.c +1 -1
@@ -298,7 +298,7 @@ void tlb_flush(struct mmu_gather *tlb)
 	flush_tlb_mm(tlb->mm);
 
 	/* Push out batch of freed page tables */
-	pte_free_finish();
+	pte_free_finish(tlb);
 }
 
 /*
maintainer
#69 lore raw

Re: [PATCH 07/13] powerpc: Preemptible mmu_gather

Quoted 11 lines · Benjamin Herrenschmidt

arch/powerpc/include/asm/tlbflush.h extern void hpte_need_flush(struct mm_st · line 126

Quoted 10 lines

static inline void arch_leave_lazy_mmu_mode(void) {

arch/powerpc/include/asm/tlbflush.h +7 -1
-     struct ppc64_tlb_batch *batch = &__get_cpu_var(ppc64_tlb_batch);
+     struct ppc64_tlb_batch *batch = &get_cpu_var(ppc64_tlb_batch);
+
+     if (batch->active) {
+             if (batch->index)
+                     __flush_tlb_pending(batch);
+             batch->active = 0;
+     }
Quoted 2 lines

Can index be > 0 if active == 0 ? I though not, which means you don't need to add a test on active, do you ?

True I guess, but like this we avoid a write, doesn't really matter I suspect.

Quoted 3 lines

I'm also pondering whether we should just stick something in the task/thread struct and avoid that whole per-cpu manipulation including the stuff in process.c in fact.

Can do, I can add batching similar to the generic code to the thread_info thingy.

Quoted 1 line

Heh, maybe it's time to introduce thread vars ? :-)

Heh, that seems like a real good way to waste a lot of memory fast ;-)

maintainer
#70 lore raw

Re: [PATCH 07/13] powerpc: Preemptible mmu_gather

In reply to Benjamin Herrenschmidt
Quoted 11 lines · Benjamin Herrenschmidt

arch/powerpc/mm/pgtable.c static void pte_free_submit(struct pte_f · line 105

Quoted 4 lines
arch/powerpc/mm/pgtable.c +4 -0
+      * A comment here about on why we have RCU freed page tables might be
+      * interesting, also explaining why we don't need any sort of grace
+      * period for mm_users == 1, and have some home brewn smp_call_func()
+      * for single frees.

iirc, we are synchronizing with CPUs walking page tables in their hash or TLB miss code, which is lockless. The mm_users test is a -little- bit dubious indeed. It may have to be mm_users < 2 && mm == current->active_mm, ie, we know for sure nobody else is currently walking those page tables ...

Quoted 7 lines

Tho even than is fishy nowadays. We -can- walk page tables on behave of another process. In fact, we do it in the Cell SPU code for faulting page table entries as a result of SPEs taking faults for example. So I'm starting to suspect that this mm_users optimisation is bogus.

We -do- want to optimize out the case where there is no user left though, ie, the MM is dead. IE. The typical exit case.

Can't you fix that by having the SPE code take a reference on these mm_structs they're playing with?

Poking at one without a ref seems fishy anyway.

maintainer
#71 lore raw

Re: [PATCH 07/13] powerpc: Preemptible mmu_gather

In reply to Benjamin Herrenschmidt
Quoted 5 lines · Benjamin Herrenschmidt

arch/powerpc/mm/pgtable.c static void pte_free_submit(struct pte_f · line 115

Quoted 4 lines
arch/powerpc/mm/pgtable.c +4 -0
+      * If there are, but we fail to batch, we need to IPI (all?) CPUs so as
+      * to serialize against the IRQ disable. In case we do batch, the RCU
+      * grace period is at least long enough to cover IRQ disabled sections
+      * (XXX assumption, not strictly true).
Quoted 8 lines

Yeah well ... I'm not that familiar with RCU anymore. Back when I wrote that, iirc, I would more or less be safe against a CPU that doesn't schedule, but things may have well changed.

We are trying to be safe against another CPU walking page tables in the asm lockless hash miss or TLB miss code. Note that sparc64 has a similar issue. This is highly optimized asm code that -cannot- call into things like rcu_read_lock().

Right, so Paul has been working hard to remove certain implementation artifact from RCU, such as the preempt-disable == rcu_read_lock thing.

Now, even Preemptible RCU has IRQ-disabled == rcu_read_lock, simply because the RCU grace period state machine is driven from an interrupt.

But there is no such requirement on RCU at all, so in the interest of removing assumptions and code validating we're trying to remove such things.

#72 lore raw

PATCH 8/13 sparc: Preemptible mmu_gather

In reply to Peter Zijlstra

Rework the sparc mmu_gather usage to conform to the new world order :-)

Sparc mmu_gather does two things:
- tracks vaddrs to unhash
- tracks pages to free

Split these two things like powerpc has done and keep the vaddrs in per-cpu data structures and flush them on context switch.

The remaining bits can then use the generic mmu_gather.

---
 arch/sparc/include/asm/pgalloc_64.h  |    3 +
 arch/sparc/include/asm/tlb_64.h      |   91 ++---------------------------------
 arch/sparc/include/asm/tlbflush_64.h |   12 +++-
 arch/sparc/mm/tlb.c                  |   42 +++++++++-------
 arch/sparc/mm/tsb.c                  |   15 +++--
 5 files changed, 51 insertions(+), 112 deletions(-)
arch/sparc/include/asm/pgalloc_64.h +3 -0
Index: linux-2.6/arch/sparc/include/asm/pgalloc_64.h
===================================================================
@@ -78,4 +78,7 @@ static inline void check_pgt_cache(void)
 	quicklist_trim(0, NULL, 25, 16);
 }
 
+#define __pte_free_tlb(tlb, pte, addr)	pte_free((tlb)->mm, pte)
+#define __pmd_free_tlb(tlb, pmd, addr)	pmd_free((tlb)->mm, pmd)
+
 #endif /* _SPARC64_PGALLOC_H */
arch/sparc/include/asm/tlb_64.h +6 -85

110 lines not on this page · raw message

arch/sparc/include/asm/tlbflush_64.h +10 -2
Index: linux-2.6/arch/sparc/include/asm/tlbflush_64.h
===================================================================
@@ -5,9 +5,17 @@
 #include <asm/mmu_context.h>
 
 /* TSB flush operations. */
-struct mmu_gather;
+
+#define TLB_BATCH_NR	192
+
+struct tlb_batch {
+	struct mm_struct *mm;
+	unsigned long tlb_nr;
+	unsigned long vaddrs[TLB_BATCH_NR];
+};
+
 extern void flush_tsb_kernel_range(unsigned long start, unsigned long end);
-extern void flush_tsb_user(struct mmu_gather *mp);
+extern void flush_tsb_user(struct tlb_batch *tb);
 
 /* TLB flush operations. */
 
arch/sparc/mm/tlb.c +24 -18

78 lines not on this page · raw message

arch/sparc/mm/tsb.c +8 -7
Index: linux-2.6/arch/sparc/mm/tsb.c
===================================================================
@@ -47,12 +47,13 @@ void flush_tsb_kernel_range(unsigned lon
 	}
 }
 
-static void __flush_tsb_one(struct mmu_gather *mp, unsigned long hash_shift, unsigned long tsb, unsigned long nentries)
+static void __flush_tsb_one(struct tlb_batch *tb, unsigned long hash_shift,
+			    unsigned long tsb, unsigned long nentries)
 {
 	unsigned long i;
 
-	for (i = 0; i < mp->tlb_nr; i++) {
-		unsigned long v = mp->vaddrs[i];
+	for (i = 0; i < tb->tlb_nr; i++) {
+		unsigned long v = tb->vaddrs[i];
 		unsigned long tag, ent, hash;
 
 		v &= ~0x1UL;
@@ -65,9 +66,9 @@ static void __flush_tsb_one(struct mmu_g
 	}
 }
 
-void flush_tsb_user(struct mmu_gather *mp)
+void flush_tsb_user(struct tlb_batch *tb)
 {
-	struct mm_struct *mm = mp->mm;
+	struct mm_struct *mm = tb->mm;
 	unsigned long nentries, base, flags;
 
 	spin_lock_irqsave(&mm->context.lock, flags);
@@ -76,7 +77,7 @@ void flush_tsb_user(struct mmu_gather *m
 	nentries = mm->context.tsb_block[MM_TSB_BASE].tsb_nentries;
 	if (tlb_type == cheetah_plus || tlb_type == hypervisor)
 		base = __pa(base);
-	__flush_tsb_one(mp, PAGE_SHIFT, base, nentries);
+	__flush_tsb_one(tb, PAGE_SHIFT, base, nentries);
 
 #ifdef CONFIG_HUGETLB_PAGE
 	if (mm->context.tsb_block[MM_TSB_HUGE].tsb) {
@@ -84,7 +85,7 @@ void flush_tsb_user(struct mmu_gather *m
 		nentries = mm->context.tsb_block[MM_TSB_HUGE].tsb_nentries;
 		if (tlb_type == cheetah_plus || tlb_type == hypervisor)
 			base = __pa(base);
-		__flush_tsb_one(mp, HPAGE_SHIFT, base, nentries);
+		__flush_tsb_one(tb, HPAGE_SHIFT, base, nentries);
 	}
 #endif
 	spin_unlock_irqrestore(&mm->context.lock, flags);
#73 lore raw

PATCH 9/13 mm, powerpc: Move the RCU page-table freeing into generic code

In reply to Peter Zijlstra

In case other architectures require RCU freed page-tables to implement gup_fast(), provide the means to do so by moving the logic into generic code.

---
 arch/powerpc/include/asm/pgalloc.h |   23 ++++++-
 arch/powerpc/include/asm/tlb.h     |   10 ---
 arch/powerpc/mm/pgtable.c          |  119 -------------------------------------
 arch/powerpc/mm/tlb_hash32.c       |    3 
 arch/powerpc/mm/tlb_hash64.c       |    3 
 arch/powerpc/mm/tlb_nohash.c       |    3 
 include/asm-generic/tlb.h          |   57 ++++++++++++++++-
 mm/memory.c                        |   81 +++++++++++++++++++++++++
 8 files changed, 153 insertions(+), 146 deletions(-)
arch/powerpc/include/asm/pgalloc.h +20 -3
Index: linux-2.6/arch/powerpc/include/asm/pgalloc.h
===================================================================
@@ -31,14 +31,31 @@ static inline void pte_free(struct mm_st
 #endif
 
 #ifdef CONFIG_SMP
-extern void pgtable_free_tlb(struct mmu_gather *tlb, void *table, unsigned shift);
-extern void pte_free_finish(struct mmu_gather *tlb);
+#define HAVE_ARCH_RCU_TABLE_FREE
+
+struct mmu_gather;
+extern void tlb_remove_table(struct mmu_gather *, void *);
+
+static inline void pgtable_free_tlb(struct mmu_gather *tlb, void *table, int shift)
+{
+	unsigned long pgf = (unsigned long)table;
+	BUG_ON(shift > MAX_PGTABLE_INDEX_SIZE);
+	pgf |= shift;
+	tlb_remove_table(tlb, (void *)pgf);
+}
+
+static inline void __tlb_remove_table(void *_table)
+{
+	void *table = (void *)((unsigned long)_table & ~MAX_PGTABLE_INDEX_SIZE);
+	unsigned shift = (unsigned long)_table & MAX_PGTABLE_INDEX_SIZE;
+
+	pgtable_free(table, shift);
+}
 #else /* CONFIG_SMP */
 static inline void pgtable_free_tlb(struct mmu_gather *tlb, void *table, unsigned shift)
 {
 	pgtable_free(table, shift);
 }
-static inline void pte_free_finish(struct mmu_gather *tlb) { }
 #endif /* !CONFIG_SMP */
 
 static inline void __pte_free_tlb(struct mmu_gather *tlb, struct page *ptepage,
arch/powerpc/include/asm/tlb.h +0 -10
Index: linux-2.6/arch/powerpc/include/asm/tlb.h
===================================================================
@@ -28,16 +28,6 @@
 #define tlb_start_vma(tlb, vma)	do { } while (0)
 #define tlb_end_vma(tlb, vma)	do { } while (0)
 
-#define HAVE_ARCH_MMU_GATHER 1
-
-struct pte_freelist_batch;
-
-struct arch_mmu_gather {
-	struct pte_freelist_batch *batch;
-};
-
-#define ARCH_MMU_GATHER_INIT (struct arch_mmu_gather){ .batch = NULL, }
-
 extern void tlb_flush(struct mmu_gather *tlb);
 
 /* Get the generic bits... */
arch/powerpc/mm/pgtable.c +0 -119

125 lines not on this page · raw message

arch/powerpc/mm/tlb_hash32.c +0 -3
Index: linux-2.6/arch/powerpc/mm/tlb_hash32.c
===================================================================
@@ -71,9 +71,6 @@ void tlb_flush(struct mmu_gather *tlb)
 		 */
 		_tlbia();
 	}
-
-	/* Push out batch of freed page tables */
-	pte_free_finish(tlb);
 }
 
 /*
arch/powerpc/mm/tlb_hash64.c +0 -3
Index: linux-2.6/arch/powerpc/mm/tlb_hash64.c
===================================================================
@@ -165,9 +165,6 @@ void tlb_flush(struct mmu_gather *tlb)
 		__flush_tlb_pending(tlbbatch);
 
 	put_cpu_var(ppc64_tlb_batch);
-
-	/* Push out batch of freed page tables */
-	pte_free_finish(tlb);
 }
 
 /**
arch/powerpc/mm/tlb_nohash.c +0 -3
Index: linux-2.6/arch/powerpc/mm/tlb_nohash.c
===================================================================
@@ -296,9 +296,6 @@ EXPORT_SYMBOL(flush_tlb_range);
 void tlb_flush(struct mmu_gather *tlb)
 {
 	flush_tlb_mm(tlb->mm);
-
-	/* Push out batch of freed page tables */
-	pte_free_finish(tlb);
 }
 
 /*
include/asm-generic/tlb.h +52 -5

83 lines not on this page · raw message

mm/memory.c +81 -0

87 lines not on this page · raw message

#74 lore raw

Re: [PATCH 09/13] mm, powerpc: Move the RCU page-table freeing into generic code

include/asm-generic/tlb.h line 34

Quoted 13 lines · Peter Zijlstra

Index: linux-2.6/include/asm-generic/tlb.h
===================================================================

include/asm-generic/tlb.h +5 -0
@@ -27,6 +27,49 @@
   #define tlb_fast_mode(tlb) 1
 #endif
 
+#ifdef HAVE_ARCH_RCU_TABLE_FREE
+/*
+ * Semi RCU freeing of the page directories.
+ *
+ * This is needed by some architectures to implement gup_fast().

Really? I see the comment in the powerpc code, but powerpc was already using RCU before gup_fast(), and AFAIKS it is indeed using it so that it can handle faults by getting the linux pte with find_linux_pte ?

I would have thought this should be a well used method for handling software TLB faults.

But anyway this looks like a nice abstraction.

maintainer
#75 lore raw

Re: [PATCH 09/13] mm, powerpc: Move the RCU page-table freeing into generic code

Quoted 9 lines · Nick Piggin
Quoted 8 lines · Peter Zijlstra

Index: linux-2.6/include/asm-generic/tlb.h
===================================================================

include/asm-generic/tlb.h +0 -0
@@ -27,6 +27,49 @@
   #define tlb_fast_mode(tlb) 1
 #endif
 
Quoted 9 lines

include/asm-generic/tlb.h line 34

Quoted 5 lines
include/asm-generic/tlb.h +5 -0
+#ifdef HAVE_ARCH_RCU_TABLE_FREE
+/*
+ * Semi RCU freeing of the page directories.
+ *
+ * This is needed by some architectures to implement gup_fast().

Really? I see the comment in the powerpc code, but powerpc was already using RCU before gup_fast(), and AFAIKS it is indeed using it so that it can handle faults by getting the linux pte with find_linux_pte ?

Ah, see that is my ignorance of the powerpc mmu code, I've only been staring at this mmu_gather piece long enough to (hopefully) make it work.

If there is indeed more to it, then yes, that needs documenting.

#76 lore raw

PATCH 10/13 lockdep, mutex: Provide mutex_lock_nest_lock

In reply to Peter Zijlstra

Provide the mutex_lock_nest_lock() annotation.

---
 include/linux/lockdep.h |    3 +++
 include/linux/mutex.h   |    9 +++++++++
 kernel/mutex.c          |   25 +++++++++++++++++--------
 3 files changed, 29 insertions(+), 8 deletions(-)
include/linux/lockdep.h +3 -0
Index: linux-2.6/include/linux/lockdep.h
===================================================================
@@ -484,12 +484,15 @@ static inline void print_irqtrace_events
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 # ifdef CONFIG_PROVE_LOCKING
 #  define mutex_acquire(l, s, t, i)		lock_acquire(l, s, t, 0, 2, NULL, i)
+#  define mutex_acquire_nest(l, s, t, n, i)	lock_acquire(l, s, t, 0, 2, n, i)
 # else
 #  define mutex_acquire(l, s, t, i)		lock_acquire(l, s, t, 0, 1, NULL, i)
+#  define mutex_acquire_nest(l, s, t, n, i)	lock_acquire(l, s, t, 0, 1, n, i)
 # endif
 # define mutex_release(l, n, i)			lock_release(l, n, i)
 #else
 # define mutex_acquire(l, s, t, i)		do { } while (0)
+# define mutex_acquire_nest(l, s, t, n, i)	do { } while (0)
 # define mutex_release(l, n, i)			do { } while (0)
 #endif
 
include/linux/mutex.h +9 -0
Index: linux-2.6/include/linux/mutex.h
===================================================================
@@ -124,6 +124,7 @@ static inline int mutex_is_locked(struct
  */
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 extern void mutex_lock_nested(struct mutex *lock, unsigned int subclass);
+extern void _mutex_lock_nest_lock(struct mutex *lock, struct lockdep_map *nest_lock);
 extern int __must_check mutex_lock_interruptible_nested(struct mutex *lock,
 					unsigned int subclass);
 extern int __must_check mutex_lock_killable_nested(struct mutex *lock,
@@ -132,6 +133,13 @@ extern int __must_check mutex_lock_killa
 #define mutex_lock(lock) mutex_lock_nested(lock, 0)
 #define mutex_lock_interruptible(lock) mutex_lock_interruptible_nested(lock, 0)
 #define mutex_lock_killable(lock) mutex_lock_killable_nested(lock, 0)
+
+#define mutex_lock_nest_lock(lock, nest_lock)				\
+do {									\
+	typecheck(struct lockdep_map *, &(nest_lock)->dep_map);		\
+	_mutex_lock_nest_lock(lock, &(nest_lock)->dep_map);		\
+} while (0)
+
 #else
 extern void mutex_lock(struct mutex *lock);
 extern int __must_check mutex_lock_interruptible(struct mutex *lock);
@@ -140,6 +148,7 @@ extern int __must_check mutex_lock_killa
 # define mutex_lock_nested(lock, subclass) mutex_lock(lock)
 # define mutex_lock_interruptible_nested(lock, subclass) mutex_lock_interruptible(lock)
 # define mutex_lock_killable_nested(lock, subclass) mutex_lock_killable(lock)
+# define mutex_lock_nest_lock(lock, nest_lock) mutex_lock(lock)
 #endif
 
 /*
kernel/mutex.c +17 -8
Index: linux-2.6/kernel/mutex.c
===================================================================
@@ -140,14 +140,14 @@ EXPORT_SYMBOL(mutex_unlock);
  */
 static inline int __sched
 __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
-	       	unsigned long ip)
+		    struct lockdep_map *nest_lock, unsigned long ip)
 {
 	struct task_struct *task = current;
 	struct mutex_waiter waiter;
 	unsigned long flags;
 
 	preempt_disable();
-	mutex_acquire(&lock->dep_map, subclass, 0, ip);
+	mutex_acquire_nest(&lock->dep_map, subclass, 0, nest_lock, ip);
 
 #ifdef CONFIG_MUTEX_SPIN_ON_OWNER
 	/*
@@ -278,16 +278,25 @@ void __sched
 mutex_lock_nested(struct mutex *lock, unsigned int subclass)
 {
 	might_sleep();
-	__mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, subclass, _RET_IP_);
+	__mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, subclass, NULL, _RET_IP_);
 }
 
 EXPORT_SYMBOL_GPL(mutex_lock_nested);
 
+void __sched
+_mutex_lock_nest_lock(struct mutex *lock, struct lockdep_map *nest)
+{
+	might_sleep();
+	__mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, 0, nest, _RET_IP_);
+}
+
+EXPORT_SYMBOL_GPL(_mutex_lock_nest_lock);
+
 int __sched
 mutex_lock_killable_nested(struct mutex *lock, unsigned int subclass)
 {
 	might_sleep();
-	return __mutex_lock_common(lock, TASK_KILLABLE, subclass, _RET_IP_);
+	return __mutex_lock_common(lock, TASK_KILLABLE, subclass, NULL, _RET_IP_);
 }
 EXPORT_SYMBOL_GPL(mutex_lock_killable_nested);
 
@@ -296,7 +305,7 @@ mutex_lock_interruptible_nested(struct m
 {
 	might_sleep();
 	return __mutex_lock_common(lock, TASK_INTERRUPTIBLE,
-				   subclass, _RET_IP_);
+				   subclass, NULL, _RET_IP_);
 }
 
 EXPORT_SYMBOL_GPL(mutex_lock_interruptible_nested);
@@ -402,7 +411,7 @@ __mutex_lock_slowpath(atomic_t *lock_cou
 {
 	struct mutex *lock = container_of(lock_count, struct mutex, count);
 
-	__mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, 0, _RET_IP_);
+	__mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, 0, NULL, _RET_IP_);
 }
 
 static noinline int __sched
@@ -410,7 +419,7 @@ __mutex_lock_killable_slowpath(atomic_t 
 {
 	struct mutex *lock = container_of(lock_count, struct mutex, count);
 
-	return __mutex_lock_common(lock, TASK_KILLABLE, 0, _RET_IP_);
+	return __mutex_lock_common(lock, TASK_KILLABLE, 0, NULL, _RET_IP_);
 }
 
 static noinline int __sched
@@ -418,7 +427,7 @@ __mutex_lock_interruptible_slowpath(atom
 {
 	struct mutex *lock = container_of(lock_count, struct mutex, count);
 
-	return __mutex_lock_common(lock, TASK_INTERRUPTIBLE, 0, _RET_IP_);
+	return __mutex_lock_common(lock, TASK_INTERRUPTIBLE, 0, NULL, _RET_IP_);
 }
 #endif
 
#78 lore raw

PATCH 11/13 mutex: Provide mutex_is_contended

In reply to Peter Zijlstra

Usable for lock-breaks and such.

---
 include/linux/mutex.h |    5 +++++
 1 file changed, 5 insertions(+)
include/linux/mutex.h +5 -0
Index: linux-2.6/include/linux/mutex.h
===================================================================
@@ -118,6 +118,11 @@ static inline int mutex_is_locked(struct
 	return atomic_read(&lock->count) != 1;
 }
 
+static inline int mutex_is_contended(struct mutex *lock)
+{
+	return atomic_read(&lock->count) < 0;
+}
+
 /*
  * See kernel/mutex.c for detailed documentation of these APIs.
  * Also see Documentation/mutex-design.txt.
#80 lore raw

PATCH 12/13 mm: Convert i_mmap_lock and anon_vma->lock to mutexes

In reply to Peter Zijlstra

Straight fwd conversion of i_mmap_lock and anon_vma->lock to mutexes.

---
 arch/x86/mm/hugetlbpage.c |    4 ++--
 fs/hugetlbfs/inode.c      |    4 ++--
 fs/inode.c                |    2 +-
 include/linux/fs.h        |    2 +-
 include/linux/mm.h        |    2 +-
 include/linux/rmap.h      |   10 +++++-----
 kernel/fork.c             |    4 ++--
 mm/filemap_xip.c          |    4 ++--
 mm/fremap.c               |    4 ++--
 mm/hugetlb.c              |   12 ++++++------
 mm/ksm.c                  |   16 ++++++++--------
 mm/memory-failure.c       |    4 ++--
 mm/memory.c               |   14 +++++++-------
 mm/mmap.c                 |   20 ++++++++++----------
 mm/mremap.c               |    4 ++--
 mm/rmap.c                 |   42 +++++++++++++++++++++---------------------
 16 files changed, 74 insertions(+), 74 deletions(-)
arch/x86/mm/hugetlbpage.c +2 -2
Index: linux-2.6/arch/x86/mm/hugetlbpage.c
===================================================================
@@ -73,7 +73,7 @@ static void huge_pmd_share(struct mm_str
 	if (!vma_shareable(vma, addr))
 		return;
 
-	spin_lock(&mapping->i_mmap_lock);
+	mutex_lock(&mapping->i_mmap_lock);
 	vma_prio_tree_foreach(svma, &iter, &mapping->i_mmap, idx, idx) {
 		if (svma == vma)
 			continue;
@@ -98,7 +98,7 @@ static void huge_pmd_share(struct mm_str
 		put_page(virt_to_page(spte));
 	spin_unlock(&mm->page_table_lock);
 out:
-	spin_unlock(&mapping->i_mmap_lock);
+	mutex_unlock(&mapping->i_mmap_lock);
 }
 
 /*
fs/hugetlbfs/inode.c +2 -2
Index: linux-2.6/fs/hugetlbfs/inode.c
===================================================================
@@ -429,10 +429,10 @@ static int hugetlb_vmtruncate(struct ino
 	pgoff = offset >> PAGE_SHIFT;
 
 	i_size_write(inode, offset);
-	spin_lock(&mapping->i_mmap_lock);
+	mutex_lock(&mapping->i_mmap_lock);
 	if (!prio_tree_empty(&mapping->i_mmap))
 		hugetlb_vmtruncate_list(&mapping->i_mmap, pgoff);
-	spin_unlock(&mapping->i_mmap_lock);
+	mutex_unlock(&mapping->i_mmap_lock);
 	truncate_hugepages(inode, offset);
 	return 0;
 }
fs/inode.c +1 -1
Index: linux-2.6/fs/inode.c
===================================================================
@@ -258,7 +258,7 @@ void inode_init_once(struct inode *inode
 	INIT_LIST_HEAD(&inode->i_devices);
 	INIT_RADIX_TREE(&inode->i_data.page_tree, GFP_ATOMIC);
 	spin_lock_init(&inode->i_data.tree_lock);
-	spin_lock_init(&inode->i_data.i_mmap_lock);
+	mutex_init(&inode->i_data.i_mmap_lock);
 	INIT_LIST_HEAD(&inode->i_data.private_list);
 	spin_lock_init(&inode->i_data.private_lock);
 	INIT_RAW_PRIO_TREE_ROOT(&inode->i_data.i_mmap);
include/linux/fs.h +1 -1
Index: linux-2.6/include/linux/fs.h
===================================================================
@@ -627,7 +627,7 @@ struct address_space {
 	unsigned int		i_mmap_writable;/* count VM_SHARED mappings */
 	struct prio_tree_root	i_mmap;		/* tree of private and shared mappings */
 	struct list_head	i_mmap_nonlinear;/*list VM_NONLINEAR mappings */
-	spinlock_t		i_mmap_lock;	/* protect tree, count, list */
+	struct mutex		i_mmap_lock;	/* protect tree, count, list */
 	unsigned int		truncate_count;	/* Cover race condition with truncate */
 	unsigned long		nrpages;	/* number of total pages */
 	pgoff_t			writeback_index;/* writeback starts here */
include/linux/mm.h +1 -1
Index: linux-2.6/include/linux/mm.h
===================================================================
@@ -749,7 +749,7 @@ struct zap_details {
 	struct address_space *check_mapping;	/* Check page->mapping if set */
 	pgoff_t	first_index;			/* Lowest page->index to unmap */
 	pgoff_t last_index;			/* Highest page->index to unmap */
-	spinlock_t *i_mmap_lock;		/* For unmap_mapping_range: */
+	struct mutex *i_mmap_lock;		/* For unmap_mapping_range: */
 	unsigned long truncate_count;		/* Compare vm_truncate_count */
 };
 
include/linux/rmap.h +5 -5
Index: linux-2.6/include/linux/rmap.h
===================================================================
@@ -7,7 +7,7 @@
 #include <linux/list.h>
 #include <linux/slab.h>
 #include <linux/mm.h>
-#include <linux/spinlock.h>
+#include <linux/mutex.h>
 #include <linux/memcontrol.h>
 
 /*
@@ -25,8 +25,7 @@
  * pointing to this anon_vma once its vma list is empty.
  */
 struct anon_vma {
-	spinlock_t lock;	/* Serialize access to vma list */
-	atomic_t ref;
+	struct mutex lock;	/* Serialize access to vma list */
 	/*
 	 * NOTE: the LSB of the head.next is set by
 	 * mm_take_all_locks() _after_ taking the above lock. So the
@@ -36,6 +35,7 @@ struct anon_vma {
 	 * mm_take_all_locks() (mm_all_locks_mutex).
 	 */
 	struct list_head head;	/* Chain of private "related" vmas */
+	atomic_t ref;
 };
 
 /*
@@ -72,14 +72,14 @@ static inline void anon_vma_lock(struct 
 {
 	struct anon_vma *anon_vma = vma->anon_vma;
 	if (anon_vma)
-		spin_lock(&anon_vma->lock);
+		mutex_lock(&anon_vma->lock);
 }
 
 static inline void anon_vma_unlock(struct vm_area_struct *vma)
 {
 	struct anon_vma *anon_vma = vma->anon_vma;
 	if (anon_vma)
-		spin_unlock(&anon_vma->lock);
+		mutex_unlock(&anon_vma->lock);
 }
 
 /*
kernel/fork.c +2 -2
Index: linux-2.6/kernel/fork.c
===================================================================
@@ -355,7 +355,7 @@ static int dup_mmap(struct mm_struct *mm
 			get_file(file);
 			if (tmp->vm_flags & VM_DENYWRITE)
 				atomic_dec(&inode->i_writecount);
-			spin_lock(&mapping->i_mmap_lock);
+			mutex_lock(&mapping->i_mmap_lock);
 			if (tmp->vm_flags & VM_SHARED)
 				mapping->i_mmap_writable++;
 			tmp->vm_truncate_count = mpnt->vm_truncate_count;
@@ -363,7 +363,7 @@ static int dup_mmap(struct mm_struct *mm
 			/* insert tmp into the share list, just after mpnt */
 			vma_prio_tree_add(tmp, mpnt);
 			flush_dcache_mmap_unlock(mapping);
-			spin_unlock(&mapping->i_mmap_lock);
+			mutex_unlock(&mapping->i_mmap_lock);
 		}
 
 		/*
mm/filemap_xip.c +2 -2
Index: linux-2.6/mm/filemap_xip.c
===================================================================
@@ -182,7 +182,7 @@ __xip_unmap (struct address_space * mapp
 		return;
 
 retry:
-	spin_lock(&mapping->i_mmap_lock);
+	mutex_lock(&mapping->i_mmap_lock);
 	vma_prio_tree_foreach(vma, &iter, &mapping->i_mmap, pgoff, pgoff) {
 		mm = vma->vm_mm;
 		address = vma->vm_start +
@@ -200,7 +200,7 @@ retry:
 			page_cache_release(page);
 		}
 	}
-	spin_unlock(&mapping->i_mmap_lock);
+	mutex_unlock(&mapping->i_mmap_lock);
 
 	if (locked) {
 		mutex_unlock(&xip_sparse_mutex);
mm/fremap.c +2 -2
Index: linux-2.6/mm/fremap.c
===================================================================
@@ -208,13 +208,13 @@ SYSCALL_DEFINE5(remap_file_pages, unsign
 			}
 			goto out;
 		}
-		spin_lock(&mapping->i_mmap_lock);
+		mutex_lock(&mapping->i_mmap_lock);
 		flush_dcache_mmap_lock(mapping);
 		vma->vm_flags |= VM_NONLINEAR;
 		vma_prio_tree_remove(vma, &mapping->i_mmap);
 		vma_nonlinear_insert(vma, &mapping->i_mmap_nonlinear);
 		flush_dcache_mmap_unlock(mapping);
-		spin_unlock(&mapping->i_mmap_lock);
+		mutex_unlock(&mapping->i_mmap_lock);
 	}
 
 	if (vma->vm_flags & VM_LOCKED) {
mm/hugetlb.c +6 -6
Index: linux-2.6/mm/hugetlb.c
===================================================================
@@ -2210,9 +2210,9 @@ void __unmap_hugepage_range(struct vm_ar
 void unmap_hugepage_range(struct vm_area_struct *vma, unsigned long start,
 			  unsigned long end, struct page *ref_page)
 {
-	spin_lock(&vma->vm_file->f_mapping->i_mmap_lock);
+	mutex_lock(&vma->vm_file->f_mapping->i_mmap_lock);
 	__unmap_hugepage_range(vma, start, end, ref_page);
-	spin_unlock(&vma->vm_file->f_mapping->i_mmap_lock);
+	mutex_unlock(&vma->vm_file->f_mapping->i_mmap_lock);
 }
 
 /*
@@ -2244,7 +2244,7 @@ static int unmap_ref_private(struct mm_s
 	 * this mapping should be shared between all the VMAs,
 	 * __unmap_hugepage_range() is called as the lock is already held
 	 */
-	spin_lock(&mapping->i_mmap_lock);
+	mutex_lock(&mapping->i_mmap_lock);
 	vma_prio_tree_foreach(iter_vma, &iter, &mapping->i_mmap, pgoff, pgoff) {
 		/* Do not unmap the current VMA */
 		if (iter_vma == vma)
@@ -2262,7 +2262,7 @@ static int unmap_ref_private(struct mm_s
 				address, address + huge_page_size(h),
 				page);
 	}
-	spin_unlock(&mapping->i_mmap_lock);
+	mutex_unlock(&mapping->i_mmap_lock);
 
 	return 1;
 }
@@ -2678,7 +2678,7 @@ void hugetlb_change_protection(struct vm
 	BUG_ON(address >= end);
 	flush_cache_range(vma, address, end);
 
-	spin_lock(&vma->vm_file->f_mapping->i_mmap_lock);
+	mutex_lock(&vma->vm_file->f_mapping->i_mmap_lock);
 	spin_lock(&mm->page_table_lock);
 	for (; address < end; address += huge_page_size(h)) {
 		ptep = huge_pte_offset(mm, address);
@@ -2693,7 +2693,7 @@ void hugetlb_change_protection(struct vm
 		}
 	}
 	spin_unlock(&mm->page_table_lock);
-	spin_unlock(&vma->vm_file->f_mapping->i_mmap_lock);
+	mutex_unlock(&vma->vm_file->f_mapping->i_mmap_lock);
 
 	flush_tlb_range(vma, start, end);
 }
mm/ksm.c +8 -8
Index: linux-2.6/mm/ksm.c
===================================================================
@@ -1559,7 +1559,7 @@ again:
 		struct anon_vma_chain *vmac;
 		struct vm_area_struct *vma;
 
-		spin_lock(&anon_vma->lock);
+		mutex_lock(&anon_vma->lock);
 		list_for_each_entry(vmac, &anon_vma->head, same_anon_vma) {
 			vma = vmac->vma;
 			if (rmap_item->address < vma->vm_start ||
@@ -1582,7 +1582,7 @@ again:
 			if (!search_new_forks || !mapcount)
 				break;
 		}
-		spin_unlock(&anon_vma->lock);
+		mutex_unlock(&anon_vma->lock);
 		if (!mapcount)
 			goto out;
 	}
@@ -1612,7 +1612,7 @@ again:
 		struct anon_vma_chain *vmac;
 		struct vm_area_struct *vma;
 
-		spin_lock(&anon_vma->lock);
+		mutex_lock(&anon_vma->lock);
 		list_for_each_entry(vmac, &anon_vma->head, same_anon_vma) {
 			vma = vmac->vma;
 			if (rmap_item->address < vma->vm_start ||
@@ -1630,11 +1630,11 @@ again:
 			ret = try_to_unmap_one(page, vma,
 					rmap_item->address, flags);
 			if (ret != SWAP_AGAIN || !page_mapped(page)) {
-				spin_unlock(&anon_vma->lock);
+				mutex_unlock(&anon_vma->lock);
 				goto out;
 			}
 		}
-		spin_unlock(&anon_vma->lock);
+		mutex_unlock(&anon_vma->lock);
 	}
 	if (!search_new_forks++)
 		goto again;
@@ -1664,7 +1664,7 @@ again:
 		struct anon_vma_chain *vmac;
 		struct vm_area_struct *vma;
 
-		spin_lock(&anon_vma->lock);
+		mutex_lock(&anon_vma->lock);
 		list_for_each_entry(vmac, &anon_vma->head, same_anon_vma) {
 			vma = vmac->vma;
 			if (rmap_item->address < vma->vm_start ||
@@ -1681,11 +1681,11 @@ again:
 
 			ret = rmap_one(page, vma, rmap_item->address, arg);
 			if (ret != SWAP_AGAIN) {
-				spin_unlock(&anon_vma->lock);
+				mutex_unlock(&anon_vma->lock);
 				goto out;
 			}
 		}
-		spin_unlock(&anon_vma->lock);
+		mutex_unlock(&anon_vma->lock);
 	}
 	if (!search_new_forks++)
 		goto again;
mm/memory-failure.c +2 -2
Index: linux-2.6/mm/memory-failure.c
===================================================================
@@ -421,7 +421,7 @@ static void collect_procs_file(struct pa
 	 */
 
 	read_lock(&tasklist_lock);
-	spin_lock(&mapping->i_mmap_lock);
+	mutex_lock(&mapping->i_mmap_lock);
 	for_each_process(tsk) {
 		pgoff_t pgoff = page->index << (PAGE_CACHE_SHIFT - PAGE_SHIFT);
 
@@ -441,7 +441,7 @@ static void collect_procs_file(struct pa
 				add_to_kill(tsk, page, vma, to_kill, tkc);
 		}
 	}
-	spin_unlock(&mapping->i_mmap_lock);
+	mutex_unlock(&mapping->i_mmap_lock);
 	read_unlock(&tasklist_lock);
 }
 
mm/memory.c +7 -7
Index: linux-2.6/mm/memory.c
===================================================================
@@ -1102,7 +1102,7 @@ unsigned long unmap_vmas(struct mmu_gath
 {
 	long zap_work = ZAP_BLOCK_SIZE;
 	unsigned long start = start_addr;
-	spinlock_t *i_mmap_lock = details? details->i_mmap_lock: NULL;
+	struct mutex *i_mmap_lock = details ? details->i_mmap_lock : NULL;
 	struct mm_struct *mm = vma->vm_mm;
 
 	mmu_notifier_invalidate_range_start(mm, start_addr, end_addr);
@@ -1152,7 +1152,7 @@ unsigned long unmap_vmas(struct mmu_gath
 			}
 
 			if (need_resched() ||
-				(i_mmap_lock && spin_needbreak(i_mmap_lock))) {
+				(i_mmap_lock && mutex_is_contended(i_mmap_lock))) {
 				if (i_mmap_lock)
 					goto out;
 				cond_resched();
@@ -2427,7 +2427,7 @@ again:
 
 	restart_addr = zap_page_range(vma, start_addr,
 					end_addr - start_addr, details);
-	need_break = need_resched() || spin_needbreak(details->i_mmap_lock);
+	need_break = need_resched() || mutex_is_contended(details->i_mmap_lock);
 
 	if (restart_addr >= end_addr) {
 		/* We have now completed this vma: mark it so */
@@ -2441,9 +2441,9 @@ again:
 			goto again;
 	}
 
-	spin_unlock(details->i_mmap_lock);
+	mutex_unlock(details->i_mmap_lock);
 	cond_resched();
-	spin_lock(details->i_mmap_lock);
+	mutex_lock(details->i_mmap_lock);
 	return -EINTR;
 }
 
@@ -2539,7 +2539,7 @@ void unmap_mapping_range(struct address_
 		details.last_index = ULONG_MAX;
 	details.i_mmap_lock = &mapping->i_mmap_lock;
 
-	spin_lock(&mapping->i_mmap_lock);
+	mutex_lock(&mapping->i_mmap_lock);
 
 	/* Protect against endless unmapping loops */
 	mapping->truncate_count++;
@@ -2554,7 +2554,7 @@ void unmap_mapping_range(struct address_
 		unmap_mapping_range_tree(&mapping->i_mmap, &details);
 	if (unlikely(!list_empty(&mapping->i_mmap_nonlinear)))
 		unmap_mapping_range_list(&mapping->i_mmap_nonlinear, &details);
-	spin_unlock(&mapping->i_mmap_lock);
+	mutex_unlock(&mapping->i_mmap_lock);
 }
 EXPORT_SYMBOL(unmap_mapping_range);
 
mm/mmap.c +10 -10
Index: linux-2.6/mm/mmap.c
===================================================================
@@ -216,9 +216,9 @@ void unlink_file_vma(struct vm_area_stru
 
 	if (file) {
 		struct address_space *mapping = file->f_mapping;
-		spin_lock(&mapping->i_mmap_lock);
+		mutex_lock(&mapping->i_mmap_lock);
 		__remove_shared_vm_struct(vma, file, mapping);
-		spin_unlock(&mapping->i_mmap_lock);
+		mutex_unlock(&mapping->i_mmap_lock);
 	}
 }
 
@@ -449,7 +449,7 @@ static void vma_link(struct mm_struct *m
 		mapping = vma->vm_file->f_mapping;
 
 	if (mapping) {
-		spin_lock(&mapping->i_mmap_lock);
+		mutex_lock(&mapping->i_mmap_lock);
 		vma->vm_truncate_count = mapping->truncate_count;
 	}
 	anon_vma_lock(vma);
@@ -459,7 +459,7 @@ static void vma_link(struct mm_struct *m
 
 	anon_vma_unlock(vma);
 	if (mapping)
-		spin_unlock(&mapping->i_mmap_lock);
+		mutex_unlock(&mapping->i_mmap_lock);
 
 	mm->map_count++;
 	validate_mm(mm);
@@ -565,7 +565,7 @@ again:			remove_next = 1 + (end > next->
 		mapping = file->f_mapping;
 		if (!(vma->vm_flags & VM_NONLINEAR))
 			root = &mapping->i_mmap;
-		spin_lock(&mapping->i_mmap_lock);
+		mutex_lock(&mapping->i_mmap_lock);
 		if (importer &&
 		    vma->vm_truncate_count != next->vm_truncate_count) {
 			/*
@@ -626,7 +626,7 @@ again:			remove_next = 1 + (end > next->
 	}
 
 	if (mapping)
-		spin_unlock(&mapping->i_mmap_lock);
+		mutex_unlock(&mapping->i_mmap_lock);
 
 	if (remove_next) {
 		if (file) {
@@ -2440,7 +2440,7 @@ static void vm_lock_anon_vma(struct mm_s
 		 * The LSB of head.next can't change from under us
 		 * because we hold the mm_all_locks_mutex.
 		 */
-		spin_lock_nest_lock(&anon_vma->lock, &mm->mmap_sem);
+		mutex_lock_nest_lock(&anon_vma->lock, &mm->mmap_sem);
 		/*
 		 * We can safely modify head.next after taking the
 		 * anon_vma->lock. If some other vma in this mm shares
@@ -2470,7 +2470,7 @@ static void vm_lock_mapping(struct mm_st
 		 */
 		if (test_and_set_bit(AS_MM_ALL_LOCKS, &mapping->flags))
 			BUG();
-		spin_lock_nest_lock(&mapping->i_mmap_lock, &mm->mmap_sem);
+		mutex_lock_nest_lock(&mapping->i_mmap_lock, &mm->mmap_sem);
 	}
 }
 
@@ -2558,7 +2558,7 @@ static void vm_unlock_anon_vma(struct an
 		if (!__test_and_clear_bit(0, (unsigned long *)
 					  &anon_vma->head.next))
 			BUG();
-		spin_unlock(&anon_vma->lock);
+		mutex_unlock(&anon_vma->lock);
 	}
 }
 
@@ -2569,7 +2569,7 @@ static void vm_unlock_mapping(struct add
 		 * AS_MM_ALL_LOCKS can't change to 0 from under us
 		 * because we hold the mm_all_locks_mutex.
 		 */
-		spin_unlock(&mapping->i_mmap_lock);
+		mutex_unlock(&mapping->i_mmap_lock);
 		if (!test_and_clear_bit(AS_MM_ALL_LOCKS,
 					&mapping->flags))
 			BUG();
mm/mremap.c +2 -2
Index: linux-2.6/mm/mremap.c
===================================================================
@@ -91,7 +91,7 @@ static void move_ptes(struct vm_area_str
 		 * and we propagate stale pages into the dst afterward.
 		 */
 		mapping = vma->vm_file->f_mapping;
-		spin_lock(&mapping->i_mmap_lock);
+		mutex_lock(&mapping->i_mmap_lock);
 		if (new_vma->vm_truncate_count &&
 		    new_vma->vm_truncate_count != vma->vm_truncate_count)
 			new_vma->vm_truncate_count = 0;
@@ -123,7 +123,7 @@ static void move_ptes(struct vm_area_str
 	pte_unmap_nested(new_pte - 1);
 	pte_unmap_unlock(old_pte - 1, old_ptl);
 	if (mapping)
-		spin_unlock(&mapping->i_mmap_lock);
+		mutex_unlock(&mapping->i_mmap_lock);
 	mmu_notifier_invalidate_range_end(vma->vm_mm, old_start, old_end);
 }
 
mm/rmap.c +21 -21

159 lines not on this page · raw message

#81 lore raw

PATCH 13/13 mm: Optimize page_lock_anon_vma

In reply to Peter Zijlstra

Optimize page_lock_anon_vma() by removing the atomic ref count ops from the fast path.

Rather complicates the code a lot, but might be worth it.

---
 mm/rmap.c |   71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 67 insertions(+), 4 deletions(-)
mm/rmap.c +67 -4
Index: linux-2.6/mm/rmap.c
===================================================================
@@ -78,6 +78,12 @@ static inline struct anon_vma *anon_vma_
 void anon_vma_free(struct anon_vma *anon_vma)
 {
 	VM_BUG_ON(atomic_read(&anon_vma->ref));
+	/*
+	 * Sync against the anon_vma->lock, so that we can hold the
+	 * lock without requiring a reference. See page_lock_anon_vma().
+	 */
+	mutex_lock(&anon_vma->lock);
+	mutex_unlock(&anon_vma->lock);
 	kmem_cache_free(anon_vma_cachep, anon_vma);
 }
 
@@ -291,7 +297,7 @@ void __init anon_vma_init(void)
 
 /*
  * Getting a lock on a stable anon_vma from a page off the LRU is
- * tricky: page_lock_anon_vma relies on RCU to guard against the races.
+ * tricky: anon_vma_get relies on RCU to guard against the races.
  */
 struct anon_vma *anon_vma_get(struct page *page)
 {
@@ -320,12 +326,70 @@ out:
 	return anon_vma;
 }
 
+/*
+ * Similar to anon_vma_get(), however it relies on the anon_vma->lock
+ * to pin the object. However since we cannot wait for the mutex
+ * acquisition inside the RCU read lock, we use the ref count
+ * in the slow path.
+ */
 struct anon_vma *page_lock_anon_vma(struct page *page)
 {
-	struct anon_vma *anon_vma = anon_vma_get(page);
+	struct anon_vma *anon_vma = NULL;
+	unsigned long anon_mapping;
+
+again:
+	rcu_read_lock();
+	anon_mapping = (unsigned long) ACCESS_ONCE(page->mapping);
+	if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
+		goto unlock;
+	if (!page_mapped(page))
+		goto unlock;
+
+	anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);
+	if (!mutex_trylock(&anon_vma->lock)) {
+		/*
+		 * We failed to acquire the lock, take a ref so we can
+		 * drop the RCU read lock and sleep on it.
+		 */
+		if (!atomic_inc_not_zero(&anon_vma->ref)) {
+			/*
+			 * Failed to get a ref, we're dead, bail.
+			 */
+			anon_vma = NULL;
+			goto unlock;
+		}
+		rcu_read_unlock();
 
-	if (anon_vma)
 		mutex_lock(&anon_vma->lock);
+		/*
+		 * We got the lock, drop the temp. ref, if it was the last
+		 * one free it and bail.
+		 */
+		if (atomic_dec_and_test(&anon_vma->ref)) {
+			mutex_unlock(&anon_vma->lock);
+			anon_vma_free(anon_vma);
+			anon_vma = NULL;
+		}
+		goto out;
+	}
+	/*
+	 * Got the lock, check we're still alive. Seeing a ref
+	 * here guarantees the object will stay alive due to
+	 * anon_vma_free() syncing against the lock we now hold.
+	 */
+	smp_rmb(); /* Order against anon_vma_put() */
+	if (!atomic_read(&anon_vma->ref)) {
+		mutex_unlock(&anon_vma->lock);
+		anon_vma = NULL;
+	}
+
+unlock:
+	rcu_read_unlock();
+out:
+	if (anon_vma && page_rmapping(page) != anon_vma) {
+		mutex_unlock(&anon_vma->lock);
+		goto again;
+	}
 
 	return anon_vma;
 }
@@ -333,7 +397,6 @@ struct anon_vma *page_lock_anon_vma(stru
 void page_unlock_anon_vma(struct anon_vma *anon_vma)
 {
 	mutex_unlock(&anon_vma->lock);
-	anon_vma_put(anon_vma);
 }
 
 /*
#82 lore raw

Re: [PATCH 13/13] mm: Optimize page_lock_anon_vma

Quoted 4 lines · Peter Zijlstra

Optimize page_lock_anon_vma() by removing the atomic ref count ops from the fast path.

Rather complicates the code a lot, but might be worth it.

Some questions and a disclaimer below.

Quoted 10 lines
---
 mm/rmap.c |   71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 67 insertions(+), 4 deletions(-)

Index: linux-2.6/mm/rmap.c
===================================================================

mm/rmap.c static inline struct anon_vma *anon_vma_ · line 85

Quoted 8 lines
mm/rmap.c +5 -0
@@ -78,6 +78,12 @@ static inline struct anon_vma *anon_vma_
 void anon_vma_free(struct anon_vma *anon_vma)
 {
 	VM_BUG_ON(atomic_read(&anon_vma->ref));
+	/*
+	 * Sync against the anon_vma->lock, so that we can hold the
+	 * lock without requiring a reference. See page_lock_anon_vma().
+	 */
+	mutex_lock(&anon_vma->lock);

On some systems, the CPU is permitted to pull references into the critical section from either side. So, do we also need an smp_mb() here?

mm/rmap.c static inline struct anon_vma *anon_vma_ · line 86

Quoted 1 line
mm/rmap.c +1 -0
+	mutex_unlock(&anon_vma->lock);

So, a question...

Can the above mutex be contended? If yes, what happens when the competing mutex_lock() acquires the lock at this point? Or, worse yet, after the kmem_cache_free()?

If no, what do we accomplish by acquiring the lock?

If the above mutex can be contended, can we fix by substituting synchronize_rcu_expedited()? Which will soon require some scalability attention if it gets used here, but what else is new? ;-)

Quoted 20 lines
mm/rmap.c +5 -1
 	kmem_cache_free(anon_vma_cachep, anon_vma);
 }
 
@@ -291,7 +297,7 @@ void __init anon_vma_init(void)
 
 /*
  * Getting a lock on a stable anon_vma from a page off the LRU is
- * tricky: page_lock_anon_vma relies on RCU to guard against the races.
+ * tricky: anon_vma_get relies on RCU to guard against the races.
  */
 struct anon_vma *anon_vma_get(struct page *page)
 {
@@ -320,12 +326,70 @@ out:
 	return anon_vma;
 }
 
+/*
+ * Similar to anon_vma_get(), however it relies on the anon_vma->lock
+ * to pin the object. However since we cannot wait for the mutex
+ * acquisition inside the RCU read lock, we use the ref count

mm/rmap.c out: · line 341

Quoted 10 lines
mm/rmap.c +7 -1
+ * in the slow path.
+ */
 struct anon_vma *page_lock_anon_vma(struct page *page)
 {
-	struct anon_vma *anon_vma = anon_vma_get(page);
+	struct anon_vma *anon_vma = NULL;
+	unsigned long anon_mapping;
+
+again:
+	rcu_read_lock();

This is interesting. You have an RCU read-side critical section with no rcu_dereference().

This strange state of affairs is actually legal (assuming that anon_mapping is the RCU-protected structure) because all dereferences of the anon_vma variable are atomic operations that guarantee ordering (the mutex_trylock() and the atomic_inc_not_zero().

The other dereferences (the atomic_read()s) are under the lock, so are also OK assuming that the lock is held when initializing and updating these fields, and even more OK due to the smp_rmb() below.

But see below.

Quoted 30 lines
mm/rmap.c +27 -1
+	anon_mapping = (unsigned long) ACCESS_ONCE(page->mapping);
+	if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
+		goto unlock;
+	if (!page_mapped(page))
+		goto unlock;
+
+	anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);
+	if (!mutex_trylock(&anon_vma->lock)) {
+		/*
+		 * We failed to acquire the lock, take a ref so we can
+		 * drop the RCU read lock and sleep on it.
+		 */
+		if (!atomic_inc_not_zero(&anon_vma->ref)) {
+			/*
+			 * Failed to get a ref, we're dead, bail.
+			 */
+			anon_vma = NULL;
+			goto unlock;
+		}
+		rcu_read_unlock();
 
-	if (anon_vma)
 		mutex_lock(&anon_vma->lock);
+		/*
+		 * We got the lock, drop the temp. ref, if it was the last
+		 * one free it and bail.
+		 */
+		if (atomic_dec_and_test(&anon_vma->ref)) {
+			mutex_unlock(&anon_vma->lock);
+			anon_vma_free(anon_vma);

mm/rmap.c out: · line 380

Quoted 10 lines
mm/rmap.c +10 -0
+			anon_vma = NULL;
+		}
+		goto out;
+	}
+	/*
+	 * Got the lock, check we're still alive. Seeing a ref
+	 * here guarantees the object will stay alive due to
+	 * anon_vma_free() syncing against the lock we now hold.
+	 */
+	smp_rmb(); /* Order against anon_vma_put() */

This is ordering the fetch into anon_vma against the atomic_read() below? If so, smp_read_barrier_depends() will cover it more cheaply. Alternatively, use rcu_dereference() when fetching into anon_vma.

Or am I misunderstanding the purpose of this barrier?

(Disclaimer: I have not yet found anon_vma_put(), so I am assuming that anon_vma_free() plays the role of a grace period.)

Quoted 30 lines
mm/rmap.c +12 -2
+	if (!atomic_read(&anon_vma->ref)) {
+		mutex_unlock(&anon_vma->lock);
+		anon_vma = NULL;
+	}
+
+unlock:
+	rcu_read_unlock();
+out:
+	if (anon_vma && page_rmapping(page) != anon_vma) {
+		mutex_unlock(&anon_vma->lock);
+		goto again;
+	}
 
 	return anon_vma;
 }
@@ -333,7 +397,6 @@ struct anon_vma *page_lock_anon_vma(stru
 void page_unlock_anon_vma(struct anon_vma *anon_vma)
 {
 	mutex_unlock(&anon_vma->lock);
-	anon_vma_put(anon_vma);
 }
 
 /*
 
 
--

To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

maintainer
#83 lore raw

Re: [PATCH 13/13] mm: Optimize page_lock_anon_vma

Quoted 34 lines · Paul E. McKenney
Quoted 4 lines · Peter Zijlstra

Optimize page_lock_anon_vma() by removing the atomic ref count ops from the fast path.

Rather complicates the code a lot, but might be worth it.

Some questions and a disclaimer below.

Quoted 10 lines
---
 mm/rmap.c |   71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 67 insertions(+), 4 deletions(-)

Index: linux-2.6/mm/rmap.c
===================================================================

mm/rmap.c static inline struct anon_vma *anon_vma_ · line 85

Quoted 8 lines
mm/rmap.c +5 -0
@@ -78,6 +78,12 @@ static inline struct anon_vma *anon_vma_
 void anon_vma_free(struct anon_vma *anon_vma)
 {
 	VM_BUG_ON(atomic_read(&anon_vma->ref));
+	/*
+	 * Sync against the anon_vma->lock, so that we can hold the
+	 * lock without requiring a reference. See page_lock_anon_vma().
+	 */
+	mutex_lock(&anon_vma->lock);

On some systems, the CPU is permitted to pull references into the critical section from either side. So, do we also need an smp_mb() here?

mm/rmap.c static inline struct anon_vma *anon_vma_ · line 86

Quoted 1 line
mm/rmap.c +1 -0
+	mutex_unlock(&anon_vma->lock);

So, a question...

Quoted 5 lines

Can the above mutex be contended? If yes, what happens when the competing mutex_lock() acquires the lock at this point? Or, worse yet, after the kmem_cache_free()?

If no, what do we accomplish by acquiring the lock?

The thing we gain is that when the holder of the lock finds a !0 refcount it knows it can't go away because any free will first wait to acquire the lock.

Quoted 3 lines

If the above mutex can be contended, can we fix by substituting synchronize_rcu_expedited()? Which will soon require some scalability attention if it gets used here, but what else is new? ;-)

No, synchronize_rcu_expedited() will not work here, there is no RCU read side that covers the full usage of the anon_vma (there can't be, it needs to sleep).

Quoted 39 lines
Quoted 20 lines
mm/rmap.c +5 -1
 	kmem_cache_free(anon_vma_cachep, anon_vma);
 }
 
@@ -291,7 +297,7 @@ void __init anon_vma_init(void)
 
 /*
  * Getting a lock on a stable anon_vma from a page off the LRU is
- * tricky: page_lock_anon_vma relies on RCU to guard against the races.
+ * tricky: anon_vma_get relies on RCU to guard against the races.
  */
 struct anon_vma *anon_vma_get(struct page *page)
 {
@@ -320,12 +326,70 @@ out:
 	return anon_vma;
 }
 
+/*
+ * Similar to anon_vma_get(), however it relies on the anon_vma->lock
+ * to pin the object. However since we cannot wait for the mutex
+ * acquisition inside the RCU read lock, we use the ref count

mm/rmap.c out: · line 341

Quoted 10 lines
mm/rmap.c +7 -1
+ * in the slow path.
+ */
 struct anon_vma *page_lock_anon_vma(struct page *page)
 {
-	struct anon_vma *anon_vma = anon_vma_get(page);
+	struct anon_vma *anon_vma = NULL;
+	unsigned long anon_mapping;
+
+again:
+	rcu_read_lock();

This is interesting. You have an RCU read-side critical section with no rcu_dereference().

This strange state of affairs is actually legal (assuming that anon_mapping is the RCU-protected structure) because all dereferences of the anon_vma variable are atomic operations that guarantee ordering (the mutex_trylock() and the atomic_inc_not_zero().

Quoted 5 lines

The other dereferences (the atomic_read()s) are under the lock, so are also OK assuming that the lock is held when initializing and updating these fields, and even more OK due to the smp_rmb() below.

But see below.

Right so the only thing rcu_read_lock() does here is create the guarantee that anon_vma is safe to dereference (it lives on a SLAB_DESTROY_BY_RCU slab).

But yes, I suppose that page->mapping read that now uses ACCESS_ONCE() would actually want to be an rcu_dereference(), since that both provides the ACCESS_ONCE() as the read-depend barrier that I thing would be needed.

Quoted 45 lines
Quoted 30 lines
mm/rmap.c +27 -1
+	anon_mapping = (unsigned long) ACCESS_ONCE(page->mapping);
+	if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
+		goto unlock;
+	if (!page_mapped(page))
+		goto unlock;
+
+	anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);
+	if (!mutex_trylock(&anon_vma->lock)) {
+		/*
+		 * We failed to acquire the lock, take a ref so we can
+		 * drop the RCU read lock and sleep on it.
+		 */
+		if (!atomic_inc_not_zero(&anon_vma->ref)) {
+			/*
+			 * Failed to get a ref, we're dead, bail.
+			 */
+			anon_vma = NULL;
+			goto unlock;
+		}
+		rcu_read_unlock();
 
-	if (anon_vma)
 		mutex_lock(&anon_vma->lock);
+		/*
+		 * We got the lock, drop the temp. ref, if it was the last
+		 * one free it and bail.
+		 */
+		if (atomic_dec_and_test(&anon_vma->ref)) {
+			mutex_unlock(&anon_vma->lock);
+			anon_vma_free(anon_vma);

mm/rmap.c out: · line 380

Quoted 10 lines
mm/rmap.c +10 -0
+			anon_vma = NULL;
+		}
+		goto out;
+	}
+	/*
+	 * Got the lock, check we're still alive. Seeing a ref
+	 * here guarantees the object will stay alive due to
+	 * anon_vma_free() syncing against the lock we now hold.
+	 */
+	smp_rmb(); /* Order against anon_vma_put() */

This is ordering the fetch into anon_vma against the atomic_read() below? If so, smp_read_barrier_depends() will cover it more cheaply. Alternatively, use rcu_dereference() when fetching into anon_vma.

Quoted 1 line

Or am I misunderstanding the purpose of this barrier?

Yes, it is:

atomic_dec_and_test(&anon_vma->ref) /* implies mb */

				smp_rmb();
				atomic_read(&anon_vma->ref);
Quoted 2 lines

(Disclaimer: I have not yet found anon_vma_put(), so I am assuming that anon_vma_free() plays the role of a grace period.)

Yes, that lives in one of the other patches (does not exist in mainline).

#84 lore raw

Re: [PATCH 13/13] mm: Optimize page_lock_anon_vma

Quoted 39 lines · Peter Zijlstra
Quoted 38 lines · Paul E. McKenney
Quoted 4 lines · Peter Zijlstra

Optimize page_lock_anon_vma() by removing the atomic ref count ops from the fast path.

Rather complicates the code a lot, but might be worth it.

Some questions and a disclaimer below.

Quoted 10 lines
---
 mm/rmap.c |   71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 67 insertions(+), 4 deletions(-)

Index: linux-2.6/mm/rmap.c
===================================================================

mm/rmap.c static inline struct anon_vma *anon_vma_ · line 85

Quoted 8 lines
mm/rmap.c +5 -0
@@ -78,6 +78,12 @@ static inline struct anon_vma *anon_vma_
 void anon_vma_free(struct anon_vma *anon_vma)
 {
 	VM_BUG_ON(atomic_read(&anon_vma->ref));
+	/*
+	 * Sync against the anon_vma->lock, so that we can hold the
+	 * lock without requiring a reference. See page_lock_anon_vma().
+	 */
+	mutex_lock(&anon_vma->lock);

On some systems, the CPU is permitted to pull references into the critical section from either side. So, do we also need an smp_mb() here?

mm/rmap.c static inline struct anon_vma *anon_vma_ · line 86

Quoted 1 line
mm/rmap.c +1 -0
+	mutex_unlock(&anon_vma->lock);

So, a question...

Can the above mutex be contended? If yes, what happens when the competing mutex_lock() acquires the lock at this point? Or, worse yet, after the kmem_cache_free()?

Quoted 5 lines
Quoted 1 line

If no, what do we accomplish by acquiring the lock?

The thing we gain is that when the holder of the lock finds a !0 refcount it knows it can't go away because any free will first wait to acquire the lock.

OK. Here is the sequence of events that I am concerned about:

1. CPU 0 invokes page_lock_anon_vma() [13/13], and executes the

	assignment to anon_vma.  It has not yet attempted to
	acquire the anon_vma->lock mutex.

2. CPU 1 invokes page_unlock_anon_vma() [13/13], which in turn

	calls anon_vma_put() [5/13], which atomically decrements
	->ref, finds it zero, invokes anon_vma_free() [13/13], which 
	finds ->ref still zero, so acquires ->lock and immediately
	releases it, and then calls kmem_cache_free().

3. This kmem_cache does have SLAB_DESTROY_BY_RCU, so this
anon_vma structure will remain an anon_vma for as long as CPU 0 remains in its RCU read-side critical section.

4. CPU 2 allocates an anon_vma, and gets the one that

	CPU 0 just freed.  It initializes it and makes ->ref
	non-zero.

5. CPU 0 continues executing page_lock_anon_vma(), and therefore
invokes mutex_trylock() on a now-reused struct anon_vma. It finds ->ref nonzero, so increments it and continues using it, despite its having been reallocated, possibly to some other process.

Or am I missing a step? (Extremely possible, as I am not as familiar with this code as I might be.)

Quoted 7 lines
Quoted 3 lines

If the above mutex can be contended, can we fix by substituting synchronize_rcu_expedited()? Which will soon require some scalability attention if it gets used here, but what else is new? ;-)

No, synchronize_rcu_expedited() will not work here, there is no RCU read side that covers the full usage of the anon_vma (there can't be, it needs to sleep).

Got it, apologies for my confusion.

Quoted 45 lines
Quoted 39 lines
Quoted 20 lines
mm/rmap.c +5 -1
 	kmem_cache_free(anon_vma_cachep, anon_vma);
 }
 
@@ -291,7 +297,7 @@ void __init anon_vma_init(void)
 
 /*
  * Getting a lock on a stable anon_vma from a page off the LRU is
- * tricky: page_lock_anon_vma relies on RCU to guard against the races.
+ * tricky: anon_vma_get relies on RCU to guard against the races.
  */
 struct anon_vma *anon_vma_get(struct page *page)
 {
@@ -320,12 +326,70 @@ out:
 	return anon_vma;
 }
 
+/*
+ * Similar to anon_vma_get(), however it relies on the anon_vma->lock
+ * to pin the object. However since we cannot wait for the mutex
+ * acquisition inside the RCU read lock, we use the ref count

mm/rmap.c out: · line 341

Quoted 10 lines
mm/rmap.c +7 -1
+ * in the slow path.
+ */
 struct anon_vma *page_lock_anon_vma(struct page *page)
 {
-	struct anon_vma *anon_vma = anon_vma_get(page);
+	struct anon_vma *anon_vma = NULL;
+	unsigned long anon_mapping;
+
+again:
+	rcu_read_lock();

This is interesting. You have an RCU read-side critical section with no rcu_dereference().

This strange state of affairs is actually legal (assuming that anon_mapping is the RCU-protected structure) because all dereferences of the anon_vma variable are atomic operations that guarantee ordering (the mutex_trylock() and the atomic_inc_not_zero().

Quoted 5 lines

The other dereferences (the atomic_read()s) are under the lock, so are also OK assuming that the lock is held when initializing and updating these fields, and even more OK due to the smp_rmb() below.

But see below.

Quoted 8 lines

Right so the only thing rcu_read_lock() does here is create the guarantee that anon_vma is safe to dereference (it lives on a SLAB_DESTROY_BY_RCU slab).

But yes, I suppose that page->mapping read that now uses ACCESS_ONCE() would actually want to be an rcu_dereference(), since that both provides the ACCESS_ONCE() as the read-depend barrier that I thing would be needed.

Ah, I was getting the wrong access. Now that I see it, yes, this is tied to the access of page->mapping that is assigned to anon_mapping.

Quoted 51 lines
Quoted 45 lines
Quoted 30 lines
mm/rmap.c +27 -1
+	anon_mapping = (unsigned long) ACCESS_ONCE(page->mapping);
+	if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
+		goto unlock;
+	if (!page_mapped(page))
+		goto unlock;
+
+	anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);
+	if (!mutex_trylock(&anon_vma->lock)) {
+		/*
+		 * We failed to acquire the lock, take a ref so we can
+		 * drop the RCU read lock and sleep on it.
+		 */
+		if (!atomic_inc_not_zero(&anon_vma->ref)) {
+			/*
+			 * Failed to get a ref, we're dead, bail.
+			 */
+			anon_vma = NULL;
+			goto unlock;
+		}
+		rcu_read_unlock();
 
-	if (anon_vma)
 		mutex_lock(&anon_vma->lock);
+		/*
+		 * We got the lock, drop the temp. ref, if it was the last
+		 * one free it and bail.
+		 */
+		if (atomic_dec_and_test(&anon_vma->ref)) {
+			mutex_unlock(&anon_vma->lock);
+			anon_vma_free(anon_vma);

mm/rmap.c out: · line 380

Quoted 10 lines
mm/rmap.c +10 -0
+			anon_vma = NULL;
+		}
+		goto out;
+	}
+	/*
+	 * Got the lock, check we're still alive. Seeing a ref
+	 * here guarantees the object will stay alive due to
+	 * anon_vma_free() syncing against the lock we now hold.
+	 */
+	smp_rmb(); /* Order against anon_vma_put() */

This is ordering the fetch into anon_vma against the atomic_read() below? If so, smp_read_barrier_depends() will cover it more cheaply. Alternatively, use rcu_dereference() when fetching into anon_vma.

Quoted 1 line

Or am I misunderstanding the purpose of this barrier?

Yes, it is:

atomic_dec_and_test(&anon_vma->ref) /* implies mb */

Quoted 8 lines
				smp_rmb();
				atomic_read(&anon_vma->ref);
Quoted 2 lines

(Disclaimer: I have not yet found anon_vma_put(), so I am assuming that anon_vma_free() plays the role of a grace period.)

Yes, that lives in one of the other patches (does not exist in mainline).

Thank you -- and yes, I should have thought to search the patch set.

Thanx, Paul

maintainer
#85 lore raw
In reply to Peter Zijlstra

From: Peter Zijlstra <[email protected]>
Date: Thu, 08 Apr 2010 21:17:37 +0200

Quoted 4 lines

This patch-set seems to build and boot on my x86_64 machines and even builds a kernel. I've also attempted powerpc and sparc, which I've compile tested with their respective defconfigs, remaining are (afaikt the rest uses the generic tlb bits):

Did a build and test boot of this on my 128-cpu Niagara2 box, seems to work basically fine.

maintainer
#86 lore raw
Quoted 10 lines · David Miller

From: Peter Zijlstra <[email protected]>
Date: Thu, 08 Apr 2010 21:17:37 +0200

Quoted 4 lines

This patch-set seems to build and boot on my x86_64 machines and even builds a kernel. I've also attempted powerpc and sparc, which I've compile tested with their respective defconfigs, remaining are (afaikt the rest uses the generic tlb bits):

Did a build and test boot of this on my 128-cpu Niagara2 box, seems to work basically fine.

Wheee, thanks Dave!

maintainer
#87 lore raw
In reply to David Miller

From: David Miller <[email protected]>
Date: Thu, 08 Apr 2010 13:29:35 -0700 (PDT)

Quoted 10 lines

From: Peter Zijlstra <[email protected]>
Date: Thu, 08 Apr 2010 21:17:37 +0200

Quoted 4 lines

This patch-set seems to build and boot on my x86_64 machines and even builds a kernel. I've also attempted powerpc and sparc, which I've compile tested with their respective defconfigs, remaining are (afaikt the rest uses the generic tlb bits):

Did a build and test boot of this on my 128-cpu Niagara2 box, seems to work basically fine.

Here comes a set of 4 patches which build on top of your work by:

1) Killing quicklists on sparc64
2) Using the generic RCU page table liberation code on sparc64
3) Implement pte_special() et al. on sparc64
4) Implement get_user_pages_fast() on sparc64

Please add them to your patch set. If you change the RCU generic code enabler CPP define to be controlled via Kconfig (as we discussed on IRC) it should be easy to propagate that change into patch #2 here.

Thanks!

#88 lore raw
In reply to Peter Zijlstra
Quoted 11 lines · Peter Zijlstra

Hi,

This (still incomplete) patch-set makes part of the mm a lot more preemptible. It converts i_mmap_lock and anon_vma->lock to mutexes. On the way there it also makes mmu_gather preemptible.

The main motivation was making mm_take_all_locks() preemptible, since it appears people are nesting hundreds of spinlocks there.

The side-effects are that we can finally make mmu_gather preemptible, something which lots of people have wanted to do for a long time.

What's the straight-line performance impact of all this? And how about concurrency, I wonder. mutexes of course are double the atomics, and you've added a refcount which is two more again for those paths using it.

Page faults are very important. We unfortunately have some databases doing a significant amount of mmap/munmap activity too. I'd like to see microbenchmark numbers for each of those (both anon and file backed for page faults).

kbuild does quite a few pages faults, that would be an easy thing to test. Not sure what reasonable kinds of cases exercise parallelism.

Quoted 4 lines

What kind of performance tests would people have me run on this to satisfy their need for numbers? I've done a kernel build on x86_64 and if anything that was slightly faster with these patches, but it was well within the noise levels so it might be heat noise I'm looking at ;-)

Is it because you're reducing the number of TLB flushes, or what (kbuild isn't multi threaded so on x86 TLB flushes should be really fast anyway).

maintainer
#89 lore raw
Quoted 13 lines · Nick Piggin
Quoted 11 lines · Peter Zijlstra

Hi,

This (still incomplete) patch-set makes part of the mm a lot more preemptible. It converts i_mmap_lock and anon_vma->lock to mutexes. On the way there it also makes mmu_gather preemptible.

The main motivation was making mm_take_all_locks() preemptible, since it appears people are nesting hundreds of spinlocks there.

The side-effects are that we can finally make mmu_gather preemptible, something which lots of people have wanted to do for a long time.

Quoted 7 lines

What's the straight-line performance impact of all this? And how about concurrency, I wonder. mutexes of course are double the atomics, and you've added a refcount which is two more again for those paths using it.

Page faults are very important. We unfortunately have some databases doing a significant amount of mmap/munmap activity too.

You think this would affect the mmap/munmap times in any significant way? It seems to me those are relatively heavy ops to begin with.

Quoted 3 lines

I'd like to
see microbenchmark numbers for each of those (both anon and file backed for page faults).

OK, I'll dig out that fault test used in the whole mmap_sem/rwsem thread a while back and modify it to also do file backed faults.

Quoted 4 lines

kbuild does quite a few pages faults, that would be an easy thing to test. Not sure what reasonable kinds of cases exercise parallelism.

Quoted 8 lines
Quoted 4 lines

What kind of performance tests would people have me run on this to satisfy their need for numbers? I've done a kernel build on x86_64 and if anything that was slightly faster with these patches, but it was well within the noise levels so it might be heat noise I'm looking at ;-)

Is it because you're reducing the number of TLB flushes, or what (kbuild isn't multi threaded so on x86 TLB flushes should be really fast anyway).

I'll try and get some perf stat runs to get some insight into this. But the numbers were:

time make O=defconfig -j48 bzImage (5x, cache hot)

without:  avg: 39.2018s +- 0.3407
with:     avg: 38.9886s +- 0.1814
#90 lore raw
Quoted 19 lines · Peter Zijlstra
Quoted 18 lines · Nick Piggin
Quoted 11 lines · Peter Zijlstra

Hi,

This (still incomplete) patch-set makes part of the mm a lot more preemptible. It converts i_mmap_lock and anon_vma->lock to mutexes. On the way there it also makes mmu_gather preemptible.

The main motivation was making mm_take_all_locks() preemptible, since it appears people are nesting hundreds of spinlocks there.

The side-effects are that we can finally make mmu_gather preemptible, something which lots of people have wanted to do for a long time.

What's the straight-line performance impact of all this? And how about concurrency, I wonder. mutexes of course are double the atomics, and you've added a refcount which is two more again for those paths using it.

Quoted 5 lines
Quoted 2 lines

Page faults are very important. We unfortunately have some databases doing a significant amount of mmap/munmap activity too.

You think this would affect the mmap/munmap times in any significant way? It seems to me those are relatively heavy ops to begin with.

They're actually not _too_ heavy because they just setup and tear down vmas. No flushing or faulting required (well, in order to make any _use_ of them you need flushing and faulting of course).

I have some microbenchmarks like this and the page fault test I could try.

Quoted 6 lines
Quoted 3 lines

I'd like to
see microbenchmark numbers for each of those (both anon and file backed for page faults).

OK, I'll dig out that fault test used in the whole mmap_sem/rwsem thread a while back and modify it to also do file backed faults.

That'd be good. Anonymous as well of course, for non-databases :)

Quoted 13 lines
Quoted 4 lines

kbuild does quite a few pages faults, that would be an easy thing to test. Not sure what reasonable kinds of cases exercise parallelism.

Quoted 8 lines
Quoted 4 lines

What kind of performance tests would people have me run on this to satisfy their need for numbers? I've done a kernel build on x86_64 and if anything that was slightly faster with these patches, but it was well within the noise levels so it might be heat noise I'm looking at ;-)

Is it because you're reducing the number of TLB flushes, or what (kbuild isn't multi threaded so on x86 TLB flushes should be really fast anyway).

Quoted 7 lines

I'll try and get some perf stat runs to get some insight into this. But the numbers were:

time make O=defconfig -j48 bzImage (5x, cache hot)

without:  avg: 39.2018s +- 0.3407
with:     avg: 38.9886s +- 0.1814

Well that's interesting. Nice if it is an improvement not just some anomoly. I'd start by looking at TLB flushes maybe? For testing, it would be nice to make the flush sizes equal so you get more of a comparison of the straight line code.

Other than this, I don't have a good suggestion of what to test. I mean, how far can you go? :) Some threaded workloads would probably be a good idea, though. Java?

maintainer
#91 lore raw
Quoted 3 lines · Nick Piggin

I have some microbenchmarks like this and the page fault test I could try.

If you don't mind, please. If you send them my way I'll run them on my machines too.

#92 lore raw
In reply to Peter Zijlstra

Hi Peter,

Quoted 2 lines · On Thu, 08 Apr 2010 21:17:37 +0200 Peter Zijlstra <[email protected]>…

The side-effects are that we can finally make mmu_gather preemptible, something which lots of people have wanted to do for a long time.

Yes, that is a good thing. With the preemptible mmu_gather s390 will use less IDTEs (that is the instruction that flushes all TLBs for a given address space) on a full flush. Good :-)

Quoted 5 lines

This patch-set seems to build and boot on my x86_64 machines and even builds a kernel. I've also attempted powerpc and sparc, which I've compile tested with their respective defconfigs, remaining are (afaikt the rest uses the generic tlb bits):

Quoted 8 lines

- s390
- ia64
- arm
- superh
- um

From those, s390 and ia64 look 'interesting', arm and superh seem very similar and should be relatively easy (-rt has a patchlet for arm iirc).

To get the 'interesting' TLB flushing on s390 working again you need this patch:

--
[PATCH] s390: preemptible mmu_gather

From: Martin Schwidefsky <[email protected]>

Adapt the stand-alone s390 mmu_gather implementation to the new preemptible mmu_gather interface.

---
 arch/s390/include/asm/tlb.h |   43 +++++++++++++++++++++++++------------------
 1 file changed, 25 insertions(+), 18 deletions(-)
arch/s390/include/asm/tlb.h +25 -18
@@ -28,45 +28,51 @@
 #include <asm/smp.h>
 #include <asm/tlbflush.h>
 
-#ifndef CONFIG_SMP
-#define TLB_NR_PTRS	1
-#else
-#define TLB_NR_PTRS	508
-#endif
-
 struct mmu_gather {
 	struct mm_struct *mm;
 	unsigned int fullmm;
 	unsigned int nr_ptes;
 	unsigned int nr_pxds;
-	void *array[TLB_NR_PTRS];
+	unsigned int max;
+	void **array;
+	void *local[8];
 };
 
-DECLARE_PER_CPU(struct mmu_gather, mmu_gathers);
-
-static inline struct mmu_gather *tlb_gather_mmu(struct mm_struct *mm,
-						unsigned int full_mm_flush)
+static inline void __tlb_alloc_pages(struct mmu_gather *tlb)
 {
-	struct mmu_gather *tlb = &get_cpu_var(mmu_gathers);
+	unsigned long addr = __get_free_pages(GFP_ATOMIC, 0);
+
+	if (addr) {
+		tlb->array = (void *) addr;
+		tlb->max = PAGE_SIZE / sizeof(void *);
+	}
+}
 
+static inline void tlb_gather_mmu(struct mmu_gather *tlb,
+				  struct mm_struct *mm,
+				  unsigned int full_mm_flush)
+{
 	tlb->mm = mm;
+	tlb->max = ARRAY_SIZE(tlb->local);
+	tlb->array = tlb->local;
 	tlb->fullmm = full_mm_flush || (num_online_cpus() == 1) ||
 		(atomic_read(&mm->mm_users) <= 1 && mm == current->active_mm);
-	tlb->nr_ptes = 0;
-	tlb->nr_pxds = TLB_NR_PTRS;
 	if (tlb->fullmm)
 		__tlb_flush_mm(mm);
-	return tlb;
+	else
+		__tlb_alloc_pages(tlb);
+	tlb->nr_ptes = 0;
+	tlb->nr_pxds = tlb->max;
 }
 
 static inline void tlb_flush_mmu(struct mmu_gather *tlb,
 				 unsigned long start, unsigned long end)
 {
-	if (!tlb->fullmm && (tlb->nr_ptes > 0 || tlb->nr_pxds < TLB_NR_PTRS))
+	if (!tlb->fullmm && (tlb->nr_ptes > 0 || tlb->nr_pxds < tlb->max))
 		__tlb_flush_mm(tlb->mm);
 	while (tlb->nr_ptes > 0)
 		pte_free(tlb->mm, tlb->array[--tlb->nr_ptes]);
-	while (tlb->nr_pxds < TLB_NR_PTRS)
+	while (tlb->nr_pxds < tlb->max)
 		/* pgd_free frees the pointer as region or segment table */
 		pgd_free(tlb->mm, tlb->array[tlb->nr_pxds++]);
 }
@@ -79,7 +85,8 @@ static inline void tlb_finish_mmu(struct
 	/* keep the page table cache within bounds */
 	check_pgt_cache();
 
-	put_cpu_var(mmu_gathers);
+	if (tlb->array != tlb->local)
+		free_pages((unsigned long) tlb->array, 0);
 }
 
 /*
Signature · 4 lines
blue skies,
   Martin.

"Reality continues to ruin my life." - Calvin.
maintainer
#95 lore raw
Quoted 1 line · David Howells

Have you tried compiling this for NOMMU?

No, I will eventually, I'm sure there's something to fix.