Create rcutree plugins to handle hotplug CPU for multi-level trees
PATCH 1/1 Create rcutree plugins to handle hotplug CPU for multi-level trees
When offlining CPUs from a multi-level tree, there is the possibility of offlining the last CPU from a given node when there are preempted RCU read-side critical sections that started life on one of the CPUs on that node. In this case, the corresponding tasks will be enqueued via the task_struct's rcu_node_entry list_head onto one of the rcu_node's blocked_tasks[] lists. These tasks need to be moved somewhere else so that they will prevent the current grace period from ending. That somewhere is the root rcu_node.
With this patch, TREE_PREEMPT_RCU passes moderate rcutorture testing with aggressive CPU-hotplugging (no delay between inserting/removing randomly selected CPU).
---
include/linux/init_task.h | 2 - include/linux/sched.h | 4 +- kernel/rcutree.c | 2 + kernel/rcutree_plugin.h | 69 ++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 69 insertions(+), 8 deletions(-)
include/linux/init_task.h +1 -1
@@ -98,7 +98,7 @@ extern struct group_info init_groups;#define INIT_TASK_RCU_PREEMPT (tsk) \ .rcu_read_lock_nesting = 0, \ .rcu_read_unlock_special = 0, \ - .rcu_blocked_cpu = -1, \ + .rcu_blocked_node = NULL, \ .rcu_node_entry = LIST_HEAD_INIT(tsk.rcu_node_entry),#else #define INIT_TASK_RCU_PREEMPT (tsk)
include/linux/sched.h +2 -2
@@ -1208,7 +1208,7 @@ struct task_struct {#ifdef CONFIG_TREE_PREEMPT_RCU int rcu_read_lock_nesting;char rcu_read_unlock_special; -int rcu_blocked_cpu; +void *rcu_blocked_node;struct list_head rcu_node_entry;#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */
@@ -1735,7 +1735,7 @@ static inline void rcu_copy_process(struct task_struct *p) { p->rcu_read_lock_nesting =0 ; p->rcu_read_unlock_special =0 ; - p->rcu_blocked_cpu =-1 ; + p->rcu_blocked_node =NULL ;INIT_LIST_HEAD (&p->rcu_node_entry); }
kernel/rcutree.c +2 -0
@@ -81,6 +81,7 @@ struct rcu_state rcu_bh_state = RCU_STATE_INITIALIZER(rcu_bh_state); DEFINE_PER_CPU(struct rcu_data, rcu_bh_data);extern long rcu_batches_completed_sched (void ); +static struct rcu_node *rcu_get_root (struct rcu_state *rsp);static void cpu_quiet_msk (unsigned long mask,struct rcu_state *rsp,struct rcu_node *rnp,unsigned long flags);static void cpu_quiet_msk_finish (struct rcu_state *rsp,unsigned long flags);
@@ -876,6 +877,7 @@ static void __rcu_offline_cpu(int cpu, struct rcu_state *rsp)spin_unlock (&rnp->lock);/* irqs remain disabled. */ break ; } +rcu_preempt_offline_tasks (rsp, rnp); mask = rnp->grpmask;spin_unlock (&rnp->lock);/* irqs remain disabled. */ rnp = rnp->parent;
kernel/rcutree_plugin.h +64 -5
95 lines not on this page · raw message
Quoted 9 lines · Paul E. McKenney
When offlining CPUs from a multi-level tree, there is the possibility of offlining the last CPU from a given node when there are preempted RCU read-side critical sections that started life on one of the CPUs on that node. In this case, the corresponding tasks will be enqueued via the task_struct's
rcu_node_entrylist_headonto one of the rcu_node's blocked_tasks[] lists. These tasks need to be moved somewhere else so that they will prevent the current grace period from ending. That somewhere is the rootrcu_node.
Quoted 5 lines
With this patch,
TREE_PREEMPT_RCUpasses moderate rcutorture testing with aggressive CPU-hotplugging (no delay between inserting/removing randomly selected CPU).Signed-off-by Paul E. McKenney <[email protected]>
Looks good. One comment below.
include/linux/sched.h struct task_struct { · line 1211
Quoted 8 lines
include/linux/sched.h +1
-1@@ -1208,7 +1208,7 @@ struct task_struct {#ifdef CONFIG_TREE_PREEMPT_RCU int rcu_read_lock_nesting;char rcu_read_unlock_special; -int rcu_blocked_cpu; +void *rcu_blocked_node;
This should use struct rcu_node *, not void *. That would eliminate several casts in the changes below. You can forward-declare struct rcu_node if you want to avoid including RCU headers in sched.h.
kernel/rcutree_plugin.h static void rcu_preempt_qs(int cpu) · line 95
Quoted 8 lines
kernel/rcutree_plugin.h +1
-1@@ -92,7 +92,7 @@ static void rcu_preempt_qs(int cpu) rnp = rdp->mynode;spin_lock (&rnp->lock); t->rcu_read_unlock_special |=RCU_READ_UNLOCK_BLOCKED ; - t->rcu_blocked_cpu = cpu; + t->rcu_blocked_node = (void *)rnp;
Regardless of whether you change the type in the structure, you never need to cast a pointer to type void *; any non-function pointer will become void * without complaint.
Quoted 9 lines
kernel/rcutree_plugin.h +2
-3@@ -170,12 +170,21 @@ static void rcu_read_unlock_special(struct task_struct *t)if (special &RCU_READ_UNLOCK_BLOCKED ) { t->rcu_read_unlock_special &= ~RCU_READ_UNLOCK_BLOCKED ; -/* Remove this task from the list it blocked on. */ - rnp = rcu_preempt_state.rda[t->rcu_blocked_cpu]->mynode; -spin_lock (&rnp->lock); +/* +* Remove this task from the list it blocked on. The
kernel/rcutree_plugin.h static void rcu_read_unlock_special(struct task_struct *t) · line 184
Quoted 10 lines
kernel/rcutree_plugin.h +10
-0+* task can migrate while we acquire the lock, but at +* most one time. So at most two passes through loop. +*/ +for (;;) { + rnp = (struct rcu_node *)t->rcu_blocked_node; +spin_lock (&rnp->lock); +if (rnp == (struct rcu_node *)t->rcu_blocked_node) +break ; +spin_unlock (&rnp->lock); + }
Both of the casts of t->rcu_blocked_node can go away here, given the type change in the structure.
- Josh Triplett
Quoted 15 lines · * Paul E. McKenney ([email protected]) wrote:
When offlining CPUs from a multi-level tree, there is the possibility of offlining the last CPU from a given node when there are preempted RCU read-side critical sections that started life on one of the CPUs on that node. In this case, the corresponding tasks will be enqueued via the task_struct's
rcu_node_entrylist_headonto one of the rcu_node's blocked_tasks[] lists. These tasks need to be moved somewhere else so that they will prevent the current grace period from ending. That somewhere is the rootrcu_node.With this patch,
TREE_PREEMPT_RCUpasses moderate rcutorture testing with aggressive CPU-hotplugging (no delay between inserting/removing randomly selected CPU).Signed-off-by Paul E. McKenney <[email protected]>---
[...]
Quoted 11 lines
/*
+ * Handle tasklist migration for case in which all CPUs covered by the
+ * specifiedrcu_nodehave gone offline. Move them up to the root
+ *rcu_node. The reason for not just moving them to the immediate
+ * parent is to remove the need forrcu_read_unlock_special()to
+ * make more than two attempts to acquire the target rcu_node's lock.
+ *
+ * The caller must holdrnp->lockwith irqs disabled.
+ */kernel/rcutree_plugin.h +2
-0+static void rcu_preempt_offline_tasks (struct rcu_state *rsp, +struct rcu_node *rnp)
kernel/rcutree_plugin.h static int rcu_preempted_readers(struct rcu_node *rnp) · line 293
Quoted 10 lines
kernel/rcutree_plugin.h +10
-0+{ +int i; +struct list_head *lp; +struct list_head *lp_root; +struct rcu_node *rnp_root =rcu_get_root (rsp); +struct task_struct *tp; + +if (rnp == rnp_root) +return ;/* Shouldn't happen: at least one CPU online. */ +
Hrm, is it "shouldn't happen" or "could be called, but we should not move anything" ?
If it is really the former, we could put a WARN_ON_ONCE (or, more aggressively, a BUG_ON) there and see when the caller is going crazy rather than ignoring the error.
kernel/rcutree_plugin.h static int rcu_preempted_readers(struct rcu_node *rnp)
Quoted 4 lines
kernel/rcutree_plugin.h +4
-0+/* +* Move tasks up to root rcu_node. Rely on the fact that the +* root rcu_node can be at most one ahead of the rest of the +* rcu_nodes in terms of gp_num value.
Do you gather the description of such constraints in a central place somewhere around the code or design documentation in the kernel tree ? I just want to point out that every clever assumption like this, which is based on the constraints imposed by the current design, should be easy to list in a year from now if we ever decide to move from tree to hashed RCU (or whichever next step will be necessary then).
I am just worried that migration helpers seems to be added to the design as an afterthought, and therefore might make future evolution more difficult.
Thanks,
Mathieu
Quoted 37 lines
This fact allows us to
kernel/rcutree_plugin.h +26
-0+* move the blocked_tasks[] array directly, element by element. +*/ +for (i =0 ; i <2 ; i++) { + lp = &rnp->blocked_tasks[i]; + lp_root = &rnp_root->blocked_tasks[i]; +while (!list_empty (lp)) { + tp =list_entry (lp->next,typeof (*tp), rcu_node_entry); +spin_lock (&rnp_root->lock);/* irqs already disabled */ +list_del (&tp->rcu_node_entry); + tp->rcu_blocked_node = rnp_root; +list_add (&tp->rcu_node_entry, lp_root); +spin_unlock (&rnp_root->lock);/* irqs remain disabled */ + } + } +} + +/* * Do CPU-offline processing for preemptable RCU. */ static void rcu_preempt_offline_cpu (int cpu)@@ -410,6 +460,15 @@ static int rcu_preempted_readers(struct rcu_node *rnp)#ifdef CONFIG_HOTPLUG_CPU /* +* Because preemptable RCU does not exist, it never needs to migrate +* tasks that were blocked within RCU read-side critical sections. +*/ +static void rcu_preempt_offline_tasks (struct rcu_state *rsp, +struct rcu_node *rnp) +{ +} + +/* * Because preemptable RCU does not exist, it never needs CPU-offline * processing. */
Signature · 2 lines
Mathieu Desnoyers OpenPGP key fingerprint: 8CD5 52C3 8E3C 4140 715F BA06 3F25 A8FE 3BAE 9A68
Quoted 18 lines · Josh Triplett
Quoted 9 lines · Paul E. McKenney
When offlining CPUs from a multi-level tree, there is the possibility of offlining the last CPU from a given node when there are preempted RCU read-side critical sections that started life on one of the CPUs on that node. In this case, the corresponding tasks will be enqueued via the task_struct's
rcu_node_entrylist_headonto one of the rcu_node's blocked_tasks[] lists. These tasks need to be moved somewhere else so that they will prevent the current grace period from ending. That somewhere is the rootrcu_node.Quoted 5 lines
With this patch,
TREE_PREEMPT_RCUpasses moderate rcutorture testing with aggressive CPU-hotplugging (no delay between inserting/removing randomly selected CPU).Signed-off-by Paul E. McKenney <[email protected]>Looks good. One comment below.
Quoted 12 lines
include/linux/sched.h struct task_struct { · line 1211
Quoted 8 lines
include/linux/sched.h +1
-1@@ -1208,7 +1208,7 @@ struct task_struct {#ifdef CONFIG_TREE_PREEMPT_RCU int rcu_read_lock_nesting;char rcu_read_unlock_special; -int rcu_blocked_cpu; +void *rcu_blocked_node;This should use struct
rcu_node*, not void *. That would eliminate several casts in the changes below. You can forward-declare structrcu_nodeif you want to avoid including RCU headers insched.h.
Good point -- it would be nice to avoid the casts.
Quoted 27 lines
kernel/rcutree_plugin.h static void rcu_preempt_qs(int cpu) · line 95
Quoted 8 lines
kernel/rcutree_plugin.h +1
-1@@ -92,7 +92,7 @@ static void rcu_preempt_qs(int cpu) rnp = rdp->mynode;spin_lock (&rnp->lock); t->rcu_read_unlock_special |=RCU_READ_UNLOCK_BLOCKED ; - t->rcu_blocked_cpu = cpu; + t->rcu_blocked_node = (void *)rnp;Regardless of whether you change the type in the structure, you never need to cast a pointer to type void *; any non-function pointer will become void * without complaint.
Quoted 14 lines
kernel/rcutree_plugin.h +7
-3@@ -170,12 +170,21 @@ static void rcu_read_unlock_special(struct task_struct *t)if (special &RCU_READ_UNLOCK_BLOCKED ) { t->rcu_read_unlock_special &= ~RCU_READ_UNLOCK_BLOCKED ; -/* Remove this task from the list it blocked on. */ - rnp = rcu_preempt_state.rda[t->rcu_blocked_cpu]->mynode; -spin_lock (&rnp->lock); +/* +* Remove this task from the list it blocked on. The +* task can migrate while we acquire the lock, but at +* most one time. So at most two passes through loop. +*/ +for (;;) { + rnp = (struct rcu_node *)t->rcu_blocked_node;
Quoted 8 lines
kernel/rcutree_plugin.h static void rcu_read_unlock_special(struct task_struct *t) · line 184
Quoted 5 lines
kernel/rcutree_plugin.h +5
-0+spin_lock (&rnp->lock); +if (rnp == (struct rcu_node *)t->rcu_blocked_node) +break ; +spin_unlock (&rnp->lock); + }Both of the casts of
t->rcu_blocked_nodecan go away here, given the type change in the structure.
Indeed. Fixed, will submit early tomorrow.
Thanx, Paul
Quoted 42 lines · Mathieu Desnoyers
Quoted 15 lines · * Paul E. McKenney ([email protected]) wrote:
When offlining CPUs from a multi-level tree, there is the possibility of offlining the last CPU from a given node when there are preempted RCU read-side critical sections that started life on one of the CPUs on that node. In this case, the corresponding tasks will be enqueued via the task_struct's
rcu_node_entrylist_headonto one of the rcu_node's blocked_tasks[] lists. These tasks need to be moved somewhere else so that they will prevent the current grace period from ending. That somewhere is the rootrcu_node.With this patch,
TREE_PREEMPT_RCUpasses moderate rcutorture testing with aggressive CPU-hotplugging (no delay between inserting/removing randomly selected CPU).Signed-off-by Paul E. McKenney <[email protected]>---
[...]
Quoted 11 lines
/*
+ * Handle tasklist migration for case in which all CPUs covered by the
+ * specifiedrcu_nodehave gone offline. Move them up to the root
+ *rcu_node. The reason for not just moving them to the immediate
+ * parent is to remove the need forrcu_read_unlock_special()to
+ * make more than two attempts to acquire the target rcu_node's lock.
+ *
+ * The caller must holdrnp->lockwith irqs disabled.
+ */kernel/rcutree_plugin.h +2
-0+static void rcu_preempt_offline_tasks (struct rcu_state *rsp, +struct rcu_node *rnp)kernel/rcutree_plugin.h static int rcu_preempted_readers(struct rcu_node *rnp) · line 293
Quoted 10 lines
kernel/rcutree_plugin.h +10
-0+{ +int i; +struct list_head *lp; +struct list_head *lp_root; +struct rcu_node *rnp_root =rcu_get_root (rsp); +struct task_struct *tp; + +if (rnp == rnp_root) +return ;/* Shouldn't happen: at least one CPU online. */ +Hrm, is it "shouldn't happen" or "could be called, but we should not move anything" ?
Quoted 3 lines
If it is really the former, we could put a
WARN_ON_ONCE(or, more aggressively, aBUG_ON) there and see when the caller is going crazy rather than ignoring the error.
The only way this could happen is if we managed to offline the last CPU. Which does raise the question as to exactly what would be executing this code, doesn't it? ;-)
So, yes, WARN_ON_ONCE() seems reasonable here. Or perhaps WARN_ONCE().
I added a WARN_ONCE(). Will send patch later.
Quoted 11 lines
kernel/rcutree_plugin.h static int rcu_preempted_readers(struct rcu_node *rnp)
Quoted 4 lines
kernel/rcutree_plugin.h +4
-0+/* +* Move tasks up to root rcu_node. Rely on the fact that the +* root rcu_node can be at most one ahead of the rest of the +* rcu_nodes in terms of gp_num value. Do you gather the description of such constraints in a central place somewhere around the code or design documentation in the kernel tree ? I just want to point out that every clever assumption like this, which is based on the constraints imposed by the current design, should be easy to list in a year from now if we ever decide to move from tree to hashed RCU (or whichever next step will be necessary then).
Interesting point... The LWN article doesn't get to this level of detail (http://lwn.net/Articles/305782/), and in any case struct rcu_node didn't have a gpnum when that article was written.
For the moment, I added a comment to the ->gpnum field's comment.
Quoted 3 lines
I am just worried that migration helpers seems to be added to the design as an afterthought, and therefore might make future evolution more difficult.
Ah!
No, this limitation is inherent, at least for any rcu_node covering at least one online CPU. The root rcu_node's ->gpnum field cannot advance until all subordinate nodes' ->gpnum fields have caught up, as this defines a grace period.
As to the case of the rcu_node that has no online CPUs, such nodes had better not be able to have additional CPUs go offline, as the code is in no shape to tolerate negative numbers of online CPUs per node. Nor do I plan to add support for this situation. ;-)
Thanx, Paul