Skip to contentSkip to conversation

rcu: Make __rcu_read_lock() inlinable

RFC PATCH In review Paul E. McKenney 4 participants 11 messages Mar 25, 2012 – Mar 27, 2012

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

RFC PATCH 1/1 rcu: Make __rcu_read_lock() inlinable

The preemptible-RCU implementations of __rcu_read_lock() have not been inlinable due to task_struct references that ran afoul of include-file dependencies. Fix this (as suggested by Linus) by moving the task_struct ->rcu_read_lock_nesting field to a per-CPU variable that is saved and restored at context-switch time. With this change, __rcu_read_lock() references only that new per-CPU variable, and so may be safely inlined. This change also allows some code that was duplicated in kernel/rcutree_plugin.h and kernel/rcutiny_plugin.h to be merged into include/linux/rcupdate.h.

This same approach unfortunately cannot be used on __rcu_read_unlock() because it also references the task_struct ->rcu_read_unlock_special field, to which cross-task access is required by rcu_boost(). This function will be handled in a separate commit, if need be.

The TREE_RCU variant passes modest rcutorture runs, while TINY_RCU still has a few bugs. Peter Zijlstra might have some thoughts on hooking into the scheduler. Disallows use of RCU from within the architecture-specific switch_to() function, which probably runs afoul of tracing for at least some architectures. There probably are still a few other bugs, as well.

TREE_RCU should be OK for experimental usage.

---

 arch/um/drivers/mconsole_kern.c |    2 
 include/linux/init_task.h       |    2 
 include/linux/rcupdate.h        |   66 +++++++++++++++++++++++++++++++-
 include/linux/rcutiny.h         |    7 ---
 include/linux/rcutree.h         |   12 -----
 include/linux/sched.h           |    4 -
 kernel/rcupdate.c               |    4 +
 kernel/rcutiny_plugin.h         |   82 ++++++++++++++++------------------------
 kernel/rcutree_plugin.h         |   72 +++++++++++++++++------------------
 kernel/sched/core.c             |    2 
 10 files changed, 145 insertions(+), 108 deletions(-)

The preemptible-RCU implementations of __rcu_read_lock() have not been inlinable due to task_struct references that ran afoul of include-file dependencies. Fix this (as suggested by Linus) by moving the task_struct ->rcu_read_lock_nesting field to a per-CPU variable that is saved and restored at context-switch time. With this change, __rcu_read_lock() references only that new per-CPU variable, and so may be safely inlined. This change also allows some code that was duplicated in kernel/rcutree_plugin.h and kernel/rcutiny_plugin.h to be merged into include/linux/rcupdate.h.

This same approach unfortunately cannot be used on __rcu_read_unlock() because it also references the task_struct ->rcu_read_unlock_special field, to which cross-task access is required by rcu_boost(). This function will be handled in a separate commit, if need be.

arch/um/drivers/mconsole_kern.c +2 -0
index c70e047..a98693f 100644
@@ -704,7 +704,9 @@ static void stack_proc(void *arg)
 	struct task_struct *from = current, *to = arg;
 
 	to->thread.saved_task = from;
+	rcu_switch_from();
 	switch_to(from, to, from);
+	rcu_switch_to();
 }
 
 /*
include/linux/init_task.h +1 -1
index 9c66b1a..6148cd6 100644
@@ -105,7 +105,7 @@ extern struct group_info init_groups;
 #endif
 #ifdef CONFIG_PREEMPT_RCU
 #define INIT_TASK_RCU_PREEMPT(tsk)					\
-	.rcu_read_lock_nesting = 0,					\
+	.rcu_read_lock_nesting_save = 0,				\
 	.rcu_read_unlock_special = 0,					\
 	.rcu_node_entry = LIST_HEAD_INIT(tsk.rcu_node_entry),		\
 	INIT_TASK_RCU_TREE_PREEMPT()					\
include/linux/rcupdate.h +64 -2

93 lines not on this page · raw message

include/linux/rcutiny.h +1 -6
index e93df77..ef39878 100644
@@ -87,11 +87,7 @@ static inline void kfree_call_rcu(struct rcu_head *head,
 
 #ifdef CONFIG_TINY_RCU
 
-static inline void rcu_preempt_note_context_switch(void)
-{
-}
-
-static inline void exit_rcu(void)
+static void rcu_preempt_note_context_switch(void)
 {
 }
 
@@ -103,7 +99,6 @@ static inline int rcu_needs_cpu(int cpu)
 #else /* #ifdef CONFIG_TINY_RCU */
 
 void rcu_preempt_note_context_switch(void);
-extern void exit_rcu(void);
 int rcu_preempt_needs_cpu(void);
 
 static inline int rcu_needs_cpu(int cpu)
include/linux/rcutree.h +0 -12
index e8ee5dd..782a8ab 100644
@@ -45,18 +45,6 @@ static inline void rcu_virt_note_context_switch(int cpu)
 	rcu_note_context_switch(cpu);
 }
 
-#ifdef CONFIG_TREE_PREEMPT_RCU
-
-extern void exit_rcu(void);
-
-#else /* #ifdef CONFIG_TREE_PREEMPT_RCU */
-
-static inline void exit_rcu(void)
-{
-}
-
-#endif /* #else #ifdef CONFIG_TREE_PREEMPT_RCU */
-
 extern void synchronize_rcu_bh(void);
 extern void synchronize_sched_expedited(void);
 extern void synchronize_rcu_expedited(void);
include/linux/sched.h +2 -2
index e692aba..f24bf6a 100644
@@ -1275,7 +1275,7 @@ struct task_struct {
 	cpumask_t cpus_allowed;
 
 #ifdef CONFIG_PREEMPT_RCU
-	int rcu_read_lock_nesting;
+	int rcu_read_lock_nesting_save;
 	char rcu_read_unlock_special;
 	struct list_head rcu_node_entry;
 #endif /* #ifdef CONFIG_PREEMPT_RCU */
@@ -1868,7 +1868,7 @@ extern void task_clear_jobctl_pending(struct task_struct *task,
 
 static inline void rcu_copy_process(struct task_struct *p)
 {
-	p->rcu_read_lock_nesting = 0;
+	p->rcu_read_lock_nesting_save = 0;
 	p->rcu_read_unlock_special = 0;
 #ifdef CONFIG_TREE_PREEMPT_RCU
 	p->rcu_blocked_node = NULL;
kernel/rcupdate.c +4 -0
index a86f174..91b8623 100644
@@ -51,6 +51,10 @@
 
 #include "rcu.h"
 
+#ifdef CONFIG_PREEMPT_RCU
+DEFINE_PER_CPU(int, rcu_read_lock_nesting);
+#endif /* #ifdef CONFIG_PREEMPT_RCU */
+
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 static struct lock_class_key rcu_lock_key;
 struct lockdep_map rcu_lock_map =
kernel/rcutiny_plugin.h +33 -49

129 lines not on this page · raw message

kernel/rcutree_plugin.h +36 -36

136 lines not on this page · raw message

kernel/sched/core.c +2 -0
index 5255c9d..9f10b76 100644
@@ -2051,7 +2051,9 @@ context_switch(struct rq *rq, struct task_struct *prev,
 #endif
 
 	/* Here we just switch the register state and the stack. */
+	rcu_switch_from();
 	switch_to(prev, next, prev);
+	rcu_switch_to();
 
 	barrier();
 	/*
maintainer
#1 lore raw
Quoted 15 lines · Paul E. McKenney

The preemptible-RCU implementations of __rcu_read_lock() have not been inlinable due to task_struct references that ran afoul of include-file dependencies. Fix this (as suggested by Linus) by moving the task_struct ->rcu_read_lock_nesting field to a per-CPU variable that is saved and restored at context-switch time. With this change, __rcu_read_lock() references only that new per-CPU variable, and so may be safely inlined. This change also allows some code that was duplicated in kernel/rcutree_plugin.h and kernel/rcutiny_plugin.h to be merged into include/linux/rcupdate.h.

This same approach unfortunately cannot be used on __rcu_read_unlock() because it also references the task_struct ->rcu_read_unlock_special field, to which cross-task access is required by rcu_boost(). This function will be handled in a separate commit, if need be.

Quoted 7 lines

The TREE_RCU variant passes modest rcutorture runs, while TINY_RCU still has a few bugs. Peter Zijlstra might have some thoughts on hooking into the scheduler. Disallows use of RCU from within the architecture-specific switch_to() function, which probably runs afoul of tracing for at least some architectures. There probably are still a few other bugs, as well.

TREE_RCU should be OK for experimental usage.

Right, so I really dislike adding this cache-miss to the context switch path, that said, rcu is used often enough that the savings on rcu_read_lock() might just come out in favour of this.. but it would be very nice to have some numbers.

Also,

Quoted 8 lines

/*
+ * Save the incoming task's value for rcu_read_lock_nesting at the
+ * end of a context switch. There can be no process-state RCU read-side
+ * critical sections between the call to rcu_switch_from() and to
+ * rcu_switch_to(). Interrupt-level RCU read-side critical sections are
+ * OK because rcu_read_unlock_special() takes early exits when called
+ * at interrupt level.
+ */

kernel/rcutiny_plugin.h void rcu_preempt_note_context_switch(void) · line 527

Quoted 7 lines
kernel/rcutiny_plugin.h +7 -0
+void rcu_switch_from(void)
+{
+	current->rcu_read_lock_nesting_save =
+		__this_cpu_read(rcu_read_lock_nesting);
+	barrier();
+	__this_cpu_write(rcu_read_lock_nesting, 0);
+}

Since rcu_switch_to() will again write rcu_read_lock_nesting, what's the point of setting it to zero?

Also, that barrier(), there's a clear dependency between the operations how can the compiler mess that up?

kernel/rcutiny_plugin.h void rcu_preempt_note_context_switch(void) · line 539

Quoted 11 lines
kernel/rcutiny_plugin.h +8 -0
+/*
+ * Restore the incoming task's value for rcu_read_lock_nesting at the
+ * end of a context switch.
  */
+void rcu_switch_to(void)
 {
+	__this_cpu_write(rcu_read_lock_nesting,
+			 current->rcu_read_lock_nesting_save);
+	barrier();
+	current->rcu_read_lock_nesting_save = 0;
 }

Similar, a future rcu_switch_from() will again over-write current->rcu_read_lock_nesting_save, what's the point of clearing it?

kernel/sched/core.c context_switch(struct rq *rq, struct task_struct *prev, · line 2059

Quoted 12 lines
kernel/sched/core.c +2 -0
@@ -2051,7 +2051,9 @@ context_switch(struct rq *rq, struct task_struct *prev,
 #endif
 
 	/* Here we just switch the register state and the stack. */
+	rcu_switch_from();
 	switch_to(prev, next, prev);
+	rcu_switch_to();
 
 	barrier();
 	/*

So why not save one call and do:

	switch_to(prev, next, prev);

rcu_switch_to(prev, next);

and have

void rcu_switch_to(struct task_struct *prev, struct task_struct *next)

{
	prev->rcu_read_lock_nesting_save = __this_cpu_read(rcu_read_lock_nesting);
	__this_cpu_write(rcu_read_lock_nesting) = next->rcu_read_lock_nesting_save;
}

preferably as an inline function so we can avoid all calls.

#2 lore raw
Quoted 22 lines · Peter Zijlstra
Quoted 21 lines · Paul E. McKenney

The preemptible-RCU implementations of __rcu_read_lock() have not been inlinable due to task_struct references that ran afoul of include-file dependencies. Fix this (as suggested by Linus) by moving the task_struct ->rcu_read_lock_nesting field to a per-CPU variable that is saved and restored at context-switch time. With this change, __rcu_read_lock() references only that new per-CPU variable, and so may be safely inlined. This change also allows some code that was duplicated in kernel/rcutree_plugin.h and kernel/rcutiny_plugin.h to be merged into include/linux/rcupdate.h.

This same approach unfortunately cannot be used on __rcu_read_unlock() because it also references the task_struct ->rcu_read_unlock_special field, to which cross-task access is required by rcu_boost(). This function will be handled in a separate commit, if need be.

The TREE_RCU variant passes modest rcutorture runs, while TINY_RCU still has a few bugs. Peter Zijlstra might have some thoughts on hooking into the scheduler. Disallows use of RCU from within the architecture-specific switch_to() function, which probably runs afoul of tracing for at least some architectures. There probably are still a few other bugs, as well.

Quoted 6 lines
Quoted 1 line

TREE_RCU should be OK for experimental usage.

Right, so I really dislike adding this cache-miss to the context switch path, that said, rcu is used often enough that the savings on rcu_read_lock() might just come out in favour of this.. but it would be very nice to have some numbers.

I need to get it into known-good shape before evaluating, but yes, some justification is clearly required.

Quoted 21 lines

Also,

Quoted 8 lines

/*
+ * Save the incoming task's value for rcu_read_lock_nesting at the
+ * end of a context switch. There can be no process-state RCU read-side
+ * critical sections between the call to rcu_switch_from() and to
+ * rcu_switch_to(). Interrupt-level RCU read-side critical sections are
+ * OK because rcu_read_unlock_special() takes early exits when called
+ * at interrupt level.
+ */

kernel/rcutiny_plugin.h void rcu_preempt_note_context_switch(void) · line 527

Quoted 7 lines
kernel/rcutiny_plugin.h +7 -0
+void rcu_switch_from(void)
+{
+	current->rcu_read_lock_nesting_save =
+		__this_cpu_read(rcu_read_lock_nesting);
+	barrier();
+	__this_cpu_write(rcu_read_lock_nesting, 0);
+}

Since rcu_switch_to() will again write rcu_read_lock_nesting, what's the point of setting it to zero?

Quoted 2 lines

Also, that barrier(), there's a clear dependency between the operations how can the compiler mess that up?

Both were debugging assists which I have now removed.

Quoted 6 lines
Quoted 6 lines
kernel/rcutiny_plugin.h +4 -0
+/*
+ * Restore the incoming task's value for rcu_read_lock_nesting at the
+ * end of a context switch.
  */
+void rcu_switch_to(void)
 {
Quoted 8 lines

kernel/rcutiny_plugin.h void rcu_preempt_note_context_switch(void) · line 539

Quoted 5 lines
kernel/rcutiny_plugin.h +4 -0
+	__this_cpu_write(rcu_read_lock_nesting,
+			 current->rcu_read_lock_nesting_save);
+	barrier();
+	current->rcu_read_lock_nesting_save = 0;
 }

Similar, a future rcu_switch_from() will again over-write current->rcu_read_lock_nesting_save, what's the point of clearing it?

I removed that one as well, again, debug code.

Quoted 20 lines

kernel/sched/core.c context_switch(struct rq *rq, struct task_struct *prev, · line 2059

Quoted 12 lines
kernel/sched/core.c +2 -0
@@ -2051,7 +2051,9 @@ context_switch(struct rq *rq, struct task_struct *prev,
 #endif
 
 	/* Here we just switch the register state and the stack. */
+	rcu_switch_from();
 	switch_to(prev, next, prev);
+	rcu_switch_to();
 
 	barrier();
 	/*

So why not save one call and do:

	switch_to(prev, next, prev);

rcu_switch_to(prev, next);

and have

Quoted 7 lines

void rcu_switch_to(struct task_struct *prev, struct task_struct *next)

{
	prev->rcu_read_lock_nesting_save = __this_cpu_read(rcu_read_lock_nesting);
	__this_cpu_write(rcu_read_lock_nesting) = next->rcu_read_lock_nesting_save;
}

preferably as an inline function so we can avoid all calls.

I could inline them into sched.h, if you are agreeable.

I am a bit concerned about putting them both together because I am betting that at least some of the architectures having tracing in switch_to(), which I currently do not handle well. At the moment, the ways I can think of to handle it well require saving before the switch and restoring afterwards. Otherwise, I can end up with the ->rcu_read_unlock_special flags getting associated with the wrong RCU read-side critical section, as happened last year.

Preemption is disabled at this point, correct?

Hmmm... One way that I could reduce the overhead that preemptible RCU imposes on the scheduler would be to move the task_struct queuing from its current point upon entry to the scheduler to just before switch_to(). (The _bh and _sched quiescent states still need to remain at scheduler entry.) That would mean that RCU would not queue tasks that entered the scheduler, but did not actually do a context switch.

Would that be helpful?

Thanx, Paul

#3 lore raw
Quoted 2 lines · Paul E. McKenney

Preemption is disabled at this point, correct?

Is this a philosophical question? You're asking if preemption is disabled while a task is currently being preempted ;-)

-- Steve

#4 lore raw
Quoted 6 lines · Steven Rostedt
Quoted 2 lines · Paul E. McKenney

Preemption is disabled at this point, correct?

Is this a philosophical question? You're asking if preemption is disabled while a task is currently being preempted ;-)

That was my first reaction as well, but never underestimate Peter's cleverness. ;-)

Thanx, Paul

maintainer
#5 lore raw
In reply to Paul E. McKenney
Quoted 2 lines · Paul E. McKenney

I could inline them into sched.h, if you are agreeable.

Sure, or put it in kernel/sched/core.c.

Quoted 3 lines

I am a bit concerned about putting them both together because I am betting that at least some of the architectures having tracing in switch_to(), which I currently do not handle well.

I would hope not.. there's a generic trace_sched_switch() and switch_to() is supposed to be black magic. I'd be fine breaking that as long as we can detect it.

Quoted 7 lines

At the moment, the ways I can
think of to handle it well require saving before the switch and restoring afterwards. Otherwise, I can end up with the ->rcu_read_unlock_special flags getting associated with the wrong RCU read-side critical section, as happened last year.

Preemption is disabled at this point, correct?

Yeah, and soon we'll have interrupts disabled as well (on all archs, currently only ARM has interrupts enabled at this point).

Quoted 6 lines

Hmmm... One way that I could reduce the overhead that preemptible RCU imposes on the scheduler would be to move the task_struct queuing from its current point upon entry to the scheduler to just before switch_to(). (The _bh and _sched quiescent states still need to remain at scheduler entry.) That would mean that RCU would not queue tasks that entered the scheduler, but did not actually do a context switch.

That would make sense anyhow, right? No point in queueing a task if you didn't actually switch away from it.

Quoted 1 line

Would that be helpful?

For now that's preemptible rcu only, and as such a somewhat niche feature (iirc its not enabled in the big distros) so I don't think it matters too much. But yeah, would be nice.

#6 lore raw
Quoted 5 lines · Peter Zijlstra
Quoted 2 lines · Paul E. McKenney

I could inline them into sched.h, if you are agreeable.

Sure, or put it in kernel/sched/core.c.

That was my first thought, but there is a use of switch_to() in arch/um/drivers/mconsole_kern.c.

Quoted 7 lines
Quoted 3 lines

I am a bit concerned about putting them both together because I am betting that at least some of the architectures having tracing in switch_to(), which I currently do not handle well.

I would hope not.. there's a generic trace_sched_switch() and switch_to() is supposed to be black magic. I'd be fine breaking that as long as we can detect it.

Hmmm... I am not yet sure whether it is easier to make RCU use legal in switch_to() or to detect it. I am inclined to take whatever course is easiest, which is likely to make it legal. :-/

Quoted 10 lines
Quoted 7 lines

At the moment, the ways I can
think of to handle it well require saving before the switch and restoring afterwards. Otherwise, I can end up with the ->rcu_read_unlock_special flags getting associated with the wrong RCU read-side critical section, as happened last year.

Preemption is disabled at this point, correct?

Yeah, and soon we'll have interrupts disabled as well (on all archs, currently only ARM has interrupts enabled at this point).

Good to know!

Quoted 9 lines
Quoted 6 lines

Hmmm... One way that I could reduce the overhead that preemptible RCU imposes on the scheduler would be to move the task_struct queuing from its current point upon entry to the scheduler to just before switch_to(). (The _bh and _sched quiescent states still need to remain at scheduler entry.) That would mean that RCU would not queue tasks that entered the scheduler, but did not actually do a context switch.

That would make sense anyhow, right? No point in queueing a task if you didn't actually switch away from it.

Also it would simplify the save and restore operation, I believe.

Quoted 5 lines
Quoted 1 line

Would that be helpful?

For now that's preemptible rcu only, and as such a somewhat niche feature (iirc its not enabled in the big distros) so I don't think it matters too much. But yeah, would be nice.

OK, let me see what works best.

Thanx, Paul

#7 lore raw
Quoted 3 lines · Paul E. McKenney

Hmmm... I am not yet sure whether it is easier to make RCU use legal in switch_to() or to detect it. I am inclined to take whatever course is easiest, which is likely to make it legal. :-/

We could just declare that we do not allow tracepoints in arch specific "switch_to" code. Then you shouldn't need to worry about RCU in switch_to().

sched_rcu can still work there correct? That is, a synchronize_sched() should not be affected. As that is needed for the function tracing, and that may be called within a switch_to.

-- Steve

#8 lore raw
Quoted 9 lines · Steven Rostedt
Quoted 3 lines · Paul E. McKenney

Hmmm... I am not yet sure whether it is easier to make RCU use legal in switch_to() or to detect it. I am inclined to take whatever course is easiest, which is likely to make it legal. :-/

We could just declare that we do not allow tracepoints in arch specific "switch_to" code. Then you shouldn't need to worry about RCU in switch_to().

Heh. I expect that to work about as well as the earlier declaration that RCU not be used in the idle loop. ;-)

Quoted 3 lines

sched_rcu can still work there correct? That is, a synchronize_sched() should not be affected. As that is needed for the function tracing, and that may be called within a switch_to.

Yep, good point.

Thanx, Paul

#9 lore raw
In reply to Paul E. McKenney

kernel/rcutree_plugin.h static void rcu_preempt_note_context_switch(int cpu) · line 248

Quoted 8 lines · Paul E. McKenney
kernel/rcutree_plugin.h +5 -2
+void rcu_switch_from(void)
 {
-	current->rcu_read_lock_nesting++;
-	barrier();  /* needed if we ever invoke rcu_read_lock in rcutree.c */
+	current->rcu_read_lock_nesting_save =
+		__this_cpu_read(rcu_read_lock_nesting);
+	barrier();
+	__this_cpu_write(rcu_read_lock_nesting, 0);

- __this_cpu_write(rcu_read_lock_nesting, 0);
+ __this_cpu_write(rcu_read_lock_nesting, 1);

if prev or next task has non-zero rcu_read_unlock_special, "__this_cpu_write(rcu_read_lock_nesting, 1)" will prevent wrong qs reporting when rcu_read_unlock() is called in any interrupt/tracing while doing switch_to().

kernel/rcutree_plugin.h static void rcu_preempt_note_context_switch(int cpu) · line 261

Quoted 13 lines
kernel/rcutree_plugin.h +12 -0
+}
+
+/*
+ * Restore the incoming task's value for rcu_read_lock_nesting at the
+ * end of a context switch.
+ */
+void rcu_switch_to(void)
+{
+	__this_cpu_write(rcu_read_lock_nesting,
+			 current->rcu_read_lock_nesting_save);
+	barrier();
+	current->rcu_read_lock_nesting_save = 0;
 }

- barrier();
- current->rcu_read_lock_nesting_save = 0;

rcu_read_lock_nesting_save is set but not used before next set here, just remove it.

I don't like it hooks too much into scheduler.

Approaches:
0) stay using function call
1) hook into kbuild(https://lkml.org/lkml/2011/3/27/170,https://lkml.org/lkml/2011/3/27/171)
2) hook into scheduler(still need more works for rcu_read_unlock())
3) Add rcu_read_lock_nesting to thread_info like preempt_count
4) resolve header-file dependence

For me
3=4>1>2>0

Thanks,
Lai

#10 lore raw
Quoted 14 lines · Lai Jiangshan
Quoted 8 lines · Paul E. McKenney
kernel/rcutree_plugin.h +5 -2
+void rcu_switch_from(void)
 {
-	current->rcu_read_lock_nesting++;
-	barrier();  /* needed if we ever invoke rcu_read_lock in rcutree.c */
+	current->rcu_read_lock_nesting_save =
+		__this_cpu_read(rcu_read_lock_nesting);
+	barrier();
+	__this_cpu_write(rcu_read_lock_nesting, 0);
(quoted diff) +1 -1
-	__this_cpu_write(rcu_read_lock_nesting, 0);
+	__this_cpu_write(rcu_read_lock_nesting, 1);
Quoted 3 lines

if prev or next task has non-zero rcu_read_unlock_special, "__this_cpu_write(rcu_read_lock_nesting, 1)" will prevent wrong qs reporting when rcu_read_unlock() is called in any interrupt/tracing while doing switch_to().

This is one approach that I have been considering. I am concerned about interactions with ->rcu_read_unlock_special, however. The approach that I am favoring at the moment is to save and restore ->rcu_read_unlock_special from another per-CPU variable, which would allow that per-CPU variable to be zeroed at this point. Then because there can be no preemption at this point in the code, execution would stay out of rcu_read_unlock_special() for the duration of the context switch.

Quoted 17 lines
Quoted 13 lines
kernel/rcutree_plugin.h +12 -0
+}
+
+/*
+ * Restore the incoming task's value for rcu_read_lock_nesting at the
+ * end of a context switch.
+ */
+void rcu_switch_to(void)
+{
+	__this_cpu_write(rcu_read_lock_nesting,
+			 current->rcu_read_lock_nesting_save);
+	barrier();
+	current->rcu_read_lock_nesting_save = 0;
 }
(quoted diff) +0 -2
-	barrier();
-	current->rcu_read_lock_nesting_save = 0;
Quoted 1 line

rcu_read_lock_nesting_save is set but not used before next set here, just remove it.

Yep, as noted earlier.

Quoted 11 lines

I don't like it hooks too much into scheduler.

Approaches:
0) stay using function call
1) hook into kbuild(https://lkml.org/lkml/2011/3/27/170,https://lkml.org/lkml/2011/3/27/171)
2) hook into scheduler(still need more works for rcu_read_unlock())
3) Add rcu_read_lock_nesting to thread_info like preempt_count
4) resolve header-file dependence

For me
3=4>1>2>0

The advantage of the current per-CPU-variable approach is that it permits x86 to reduce rcu_read_lock() to a single instruction, so it seems worthwhile persuing it. In addition, having RCU-preempt hook at switch_to() eliminates needless task queuing in the case where the scheduler is entered, but no context switch actually takes place.

Thanx, Paul