Skip to contentSkip to conversation

v2 Add callback-free CPUs

PATCH In review Paul E. McKenney 3 participants 8 messages Oct 31, 2012 – Nov 2, 2012

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

cover v2 Add callback-free CPUs

Hello!

RCU callback execution can add significant OS jitter and also can degrade scheduling latency. This commit therefore adds the ability for selected CPUs ("rcu_nocbs=" boot parameter) to have their callbacks offloaded to kthreads, inspired by Joe Korty's and Jim Houston's JRCU. If the "rcu_nocb_poll" boot parameter is also specified, these kthreads will do polling, removing the need for the offloaded CPUs to do wakeups. At least one CPU must be doing normal callback processing: currently CPU 0 cannot be selected as a no-CBs CPU. In addition, attempts to offline the last normal-CBs CPU will fail.

Changes since v1 (https://lkml.org/lkml/2012/9/5/572):

1. Contains fixes for a few problems located by Fengguang Wu's
kbuild test robot.
2. Counters are now atomic_long_t rather than atomic_t, as
suggested by Peter Zijlstra.
3. The rcu_nocbs= kernel boot parameter is now documented.
4. Fixed a bug introduced by commit c96ea7cf (Avoid spurious
RCU CPU stall warnings) that resulted in boot-time NULL-pointer dereferences (reported by Paul Gortmaker).
4. Account for normal and offloaded callbacks separately, so that
offloading is represented in debugfs output.

The patches in this series are as follows:

1. Offload RCU callbacks based on boot-time kernel parameter.
2. Account for normal and offloaded callbacks separately, so that
offloading is represented in debugfs output.

Thanx, Paul

 b/Documentation/kernel-parameters.txt |    5 
 b/include/trace/events/rcu.h          |    1 
 b/init/Kconfig                        |   19 +
 b/kernel/rcutree.c                    |   63 ++++-
 b/kernel/rcutree.h                    |   48 ++++
 b/kernel/rcutree_plugin.h             |  399 +++++++++++++++++++++++++++++++++-
 b/kernel/rcutree_trace.c              |   12 -
 7 files changed, 528 insertions(+), 19 deletions(-)
#1 lore raw

PATCH 1/2 rcu: Add callback-free CPUs

From: "Paul E. McKenney" <[email protected]>

RCU callback execution can add significant OS jitter and also can degrade scheduling latency. This commit therefore adds the ability for selected CPUs ("rcu_nocbs=" boot parameter) to have their callbacks offloaded to kthreads. If the "rcu_nocb_poll" boot parameter is also specified, these kthreads will do polling, removing the need for the offloaded CPUs to do wakeups. At least one CPU must be doing normal callback processing:
currently CPU 0 cannot be selected as a no-CBs CPU. In addition, attempts to offline the last normal-CBs CPU will fail.

This feature was inspired by Jim Houston's and Joe Korty's JRCU, and this commit includes fixes to problems located by Fengguang Wu's kbuild test robot.

[ paulmck: Added gfp.h include file as suggested by Fengguang Wu. ]

---
 Documentation/kernel-parameters.txt |    5 +
 include/trace/events/rcu.h          |    1 +
 init/Kconfig                        |   19 ++
 kernel/rcutree.c                    |   63 +++++-
 kernel/rcutree.h                    |   47 ++++
 kernel/rcutree_plugin.h             |  397 ++++++++++++++++++++++++++++++++++-
 kernel/rcutree_trace.c              |    7 +-
 7 files changed, 523 insertions(+), 16 deletions(-)
Documentation/kernel-parameters.txt +5 -0
index 9776f06..dfd03272 100644
@@ -2394,6 +2394,11 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
 	ramdisk_size=	[RAM] Sizes of RAM disks in kilobytes
 			See Documentation/blockdev/ramdisk.txt.
 
+	rcu_nocbs=	[KNL,BOOT]
+			Set the specified list of CPUs to be no-callback
+			CPUs.  Invocation of these CPUs' RCU callbacks will
+			be offloaded to kthreads created for that purpose.
+
 	rcutree.blimit=	[KNL,BOOT]
 			Set maximum number of finished RCU callbacks to process
 			in one batch.
include/trace/events/rcu.h +1 -0
index 5bde94d..d4f559b 100644
@@ -549,6 +549,7 @@ TRACE_EVENT(rcu_torture_read,
  *	"EarlyExit": rcu_barrier_callback() piggybacked, thus early exit.
  *	"Inc1": rcu_barrier_callback() piggyback check counter incremented.
  *	"Offline": rcu_barrier_callback() found offline CPU
+ *	"OnlineNoCB": rcu_barrier_callback() found online no-CBs CPU.
  *	"OnlineQ": rcu_barrier_callback() found online CPU with callbacks.
  *	"OnlineNQ": rcu_barrier_callback() found online CPU, no callbacks.
  *	"IRQ": An rcu_barrier_callback() callback posted on remote CPU.
init/Kconfig +19 -0
index ec62139..e2343d4 100644
@@ -654,6 +654,25 @@ config RCU_BOOST_DELAY
 
 	  Accept the default if unsure.
 
+config RCU_NOCB_CPU
+	bool "Offload RCU callback processing from boot-selected CPUs"
+	depends on TREE_RCU || TREE_PREEMPT_RCU
+	default n
+	help
+	  Use this option to reduce OS jitter for aggressive HPC or
+	  real-time workloads.
+
+	  This option offloads callback invocation from the set of CPUs
+	  specified at boot time by the rcu_nocbs parameter.  For each
+	  such CPU, a kthread ("rcuoN") will be created to invoke callbacks.
+	  Nothing prevents this kthread from running on of of the specified
+	  CPUs, but (1) the kthreads may be preempted between each callback
+	  and (2) affinity or cgroups can be used to force the kthreads off
+	  of those CPUs if desired.
+
+	  Say Y here if you want reduced OS jitter on selected CPUs.
+	  Say N here if you are unsure.
+
 endmenu # "RCU Subsystem"
 
 config IKCONFIG
kernel/rcutree.c +51 -12

163 lines not on this page · raw message

kernel/rcutree.h +47 -0

70 lines not on this page · raw message

kernel/rcutree_plugin.h +394 -3

436 lines not on this page · raw message

kernel/rcutree_trace.c +6 -1
index e84679c..5e9ca3e 100644
@@ -111,6 +111,8 @@ static char convert_kthread_status(unsigned int kthread_status)
 
 static void print_one_rcu_data(struct seq_file *m, struct rcu_data *rdp)
 {
+	long ql, qll;
+
 	if (!rdp->beenonline)
 		return;
 	seq_printf(m, "%3d%cc=%lu g=%lu pq=%d qp=%d",
@@ -124,8 +126,11 @@ static void print_one_rcu_data(struct seq_file *m, struct rcu_data *rdp)
 		   rdp->dynticks->dynticks_nmi_nesting,
 		   rdp->dynticks_fqs);
 	seq_printf(m, " of=%lu", rdp->offline_fqs);
+	rcu_nocb_q_lengths(rdp, &ql, &qll);
+	qll += rdp->qlen_lazy;
+	ql += rdp->qlen;
 	seq_printf(m, " ql=%ld/%ld qs=%c%c%c%c",
-		   rdp->qlen_lazy, rdp->qlen,
+		   qll, ql,
 		   ".N"[rdp->nxttail[RCU_NEXT_READY_TAIL] !=
 			rdp->nxttail[RCU_NEXT_TAIL]],
 		   ".R"[rdp->nxttail[RCU_WAIT_TAIL] !=
Signature · 1 line
1.7.8
#2 lore raw

PATCH 2/2 rcu: Separate accounting of callbacks from callback-free CPUs

From: "Paul E. McKenney" <[email protected]>

Currently, callback invocations from callback-free CPUs are accounted to the CPU that registered the callback, but using the same field that is used for normal callbacks. This makes it impossible to determine from debugfs output whether callbacks are in fact being diverted. This commit therefore adds a separate ->n_nocbs_invoked field in the rcu_data structure in which diverted callback invocations are counted. RCU's debugfs tracing still displays normal callback invocations using ci=, but displayed diverted callbacks with nci=.

---
 kernel/rcutree.h        |    1 +
 kernel/rcutree_plugin.h |    2 +-
 kernel/rcutree_trace.c  |    5 +++--
 3 files changed, 5 insertions(+), 3 deletions(-)
kernel/rcutree.h +1 -0
index 79954bb..db9bec8 100644
@@ -287,6 +287,7 @@ struct rcu_data {
 	long		qlen_last_fqs_check;
 					/* qlen at last check for QS forcing */
 	unsigned long	n_cbs_invoked;	/* count of RCU cbs invoked. */
+	unsigned long	n_nocbs_invoked; /* count of no-CBs RCU cbs invoked. */
 	unsigned long   n_cbs_orphaned; /* RCU cbs orphaned by dying CPU */
 	unsigned long   n_cbs_adopted;  /* RCU cbs adopted from dying CPU */
 	unsigned long	n_force_qs_snap;
kernel/rcutree_plugin.h +1 -1
index ea960c4..31d3a95 100644
@@ -2406,7 +2406,7 @@ static int rcu_nocb_kthread(void *arg)
 		trace_rcu_batch_end(rdp->rsp->name, c, !!list, 0, 0, 1);
 		ACCESS_ONCE(rdp->nocb_p_count) -= c;
 		ACCESS_ONCE(rdp->nocb_p_count_lazy) -= cl;
-		rdp->n_cbs_invoked += c;
+		rdp->n_nocbs_invoked += c;
 	}
 	return 0;
 }
kernel/rcutree_trace.c +3 -2
index 5e9ca3e..167375d 100644
@@ -146,8 +146,9 @@ static void print_one_rcu_data(struct seq_file *m, struct rcu_data *rdp)
 		   per_cpu(rcu_cpu_kthread_loops, rdp->cpu) & 0xffff);
 #endif /* #ifdef CONFIG_RCU_BOOST */
 	seq_printf(m, " b=%ld", rdp->blimit);
-	seq_printf(m, " ci=%lu co=%lu ca=%lu\n",
-		   rdp->n_cbs_invoked, rdp->n_cbs_orphaned, rdp->n_cbs_adopted);
+	seq_printf(m, " ci=%lu nci=%lu co=%lu ca=%lu\n",
+		   rdp->n_cbs_invoked, rdp->n_nocbs_invoked,
+		   rdp->n_cbs_orphaned, rdp->n_cbs_adopted);
 }
 
 static int show_rcudata(struct seq_file *m, void *v)
Signature · 1 line
1.7.8
#3 lore raw

Re: [PATCH tip/core/rcu 1/2] rcu: Add callback-free CPUs

In reply to Paul E. McKenney

2012/10/31 Paul E. McKenney <[email protected]>:

Quoted 70 lines
kernel/rcutree_plugin.h +70 -0
+/*
+ * Per-rcu_data kthread, but only for no-CBs CPUs.  Each kthread invokes
+ * callbacks queued by the corresponding no-CBs CPU.
+ */
+static int rcu_nocb_kthread(void *arg)
+{
+       int c, cl;
+       struct rcu_head *list;
+       struct rcu_head *next;
+       struct rcu_head **tail;
+       struct rcu_data *rdp = arg;
+
+       /* Each pass through this loop invokes one batch of callbacks */
+       for (;;) {
+               /* If not polling, wait for next batch of callbacks. */
+               if (!rcu_nocb_poll)
+                       wait_event(rdp->nocb_wq, rdp->nocb_head);
+               list = ACCESS_ONCE(rdp->nocb_head);
+               if (!list) {
+                       schedule_timeout_interruptible(1);
+                       continue;
+               }
+
+               /*
+                * Extract queued callbacks, update counts, and wait
+                * for a grace period to elapse.
+                */
+               ACCESS_ONCE(rdp->nocb_head) = NULL;
+               tail = xchg(&rdp->nocb_tail, &rdp->nocb_head);
+               c = atomic_long_xchg(&rdp->nocb_q_count, 0);
+               cl = atomic_long_xchg(&rdp->nocb_q_count_lazy, 0);
+               ACCESS_ONCE(rdp->nocb_p_count) += c;
+               ACCESS_ONCE(rdp->nocb_p_count_lazy) += cl;
+               wait_rcu_gp(rdp->rsp->call_remote);
+
+               /* Each pass through the following loop invokes a callback. */
+               trace_rcu_batch_start(rdp->rsp->name, cl, c, -1);
+               c = cl = 0;
+               while (list) {
+                       next = list->next;
+                       /* Wait for enqueuing to complete, if needed. */
+                       while (next == NULL && &list->next != tail) {
+                               schedule_timeout_interruptible(1);
+                               next = list->next;
+                       }
+                       debug_rcu_head_unqueue(list);
+                       local_bh_disable();
+                       if (__rcu_reclaim(rdp->rsp->name, list))
+                               cl++;
+                       c++;
+                       local_bh_enable();
+                       list = next;
+               }
+               trace_rcu_batch_end(rdp->rsp->name, c, !!list, 0, 0, 1);
+               ACCESS_ONCE(rdp->nocb_p_count) -= c;
+               ACCESS_ONCE(rdp->nocb_p_count_lazy) -= cl;
+               rdp->n_cbs_invoked += c;
+       }
+       return 0;
+}
+
+/* Initialize per-rcu_data variables for no-CBs CPUs. */
+static void __init rcu_boot_init_nocb_percpu_data(struct rcu_data *rdp)
+{
+       rdp->nocb_tail = &rdp->nocb_head;
+       init_waitqueue_head(&rdp->nocb_wq);
+}
+
+/* Create a kthread for each RCU flavor for each no-CBs CPU. */
+static void __init rcu_spawn_nocb_kthreads(struct rcu_state *rsp)

kernel/rcutree_plugin.h static void increment_cpu_stall_ticks(void) · line 2432

Quoted 10 lines
kernel/rcutree_plugin.h +10 -0
+{
+       int cpu;
+       struct rcu_data *rdp;
+       struct task_struct *t;
+
+       if (rcu_nocb_mask == NULL)
+               return;
+       for_each_cpu(cpu, rcu_nocb_mask) {
+               rdp = per_cpu_ptr(rsp->rda, cpu);
+               t = kthread_run(rcu_nocb_kthread, rdp, "rcuo%d", cpu);

Sorry, I think I left my brain in the middle of the diff. But there is something I'm misunderstanding I think. Here you're creating an rcu_nocb_kthread per nocb cpu. Looking at the code of rcu_nocb_kthread(), it seems to execute the callbacks with __rcu_reclaim().

So, in the end, no callbacks CPU execute their callbacks. Isn't it the opposite than what is expected? (again, just referring to my misunderstanding).

Thanks.

Quoted 4 lines
kernel/rcutree_plugin.h +4 -0
+               BUG_ON(IS_ERR(t));
+               ACCESS_ONCE(rdp->nocb_kthread) = t;
+       }
+}
#4 lore raw

Re: [PATCH tip/core/rcu 1/2] rcu: Add callback-free CPUs

Quoted 79 lines · Frederic Weisbecker

2012/10/31 Paul E. McKenney <[email protected]>:

Quoted 78 lines
kernel/rcutree_plugin.h +78 -0
+/*
+ * Per-rcu_data kthread, but only for no-CBs CPUs.  Each kthread invokes
+ * callbacks queued by the corresponding no-CBs CPU.
+ */
+static int rcu_nocb_kthread(void *arg)
+{
+       int c, cl;
+       struct rcu_head *list;
+       struct rcu_head *next;
+       struct rcu_head **tail;
+       struct rcu_data *rdp = arg;
+
+       /* Each pass through this loop invokes one batch of callbacks */
+       for (;;) {
+               /* If not polling, wait for next batch of callbacks. */
+               if (!rcu_nocb_poll)
+                       wait_event(rdp->nocb_wq, rdp->nocb_head);
+               list = ACCESS_ONCE(rdp->nocb_head);
+               if (!list) {
+                       schedule_timeout_interruptible(1);
+                       continue;
+               }
+
+               /*
+                * Extract queued callbacks, update counts, and wait
+                * for a grace period to elapse.
+                */
+               ACCESS_ONCE(rdp->nocb_head) = NULL;
+               tail = xchg(&rdp->nocb_tail, &rdp->nocb_head);
+               c = atomic_long_xchg(&rdp->nocb_q_count, 0);
+               cl = atomic_long_xchg(&rdp->nocb_q_count_lazy, 0);
+               ACCESS_ONCE(rdp->nocb_p_count) += c;
+               ACCESS_ONCE(rdp->nocb_p_count_lazy) += cl;
+               wait_rcu_gp(rdp->rsp->call_remote);
+
+               /* Each pass through the following loop invokes a callback. */
+               trace_rcu_batch_start(rdp->rsp->name, cl, c, -1);
+               c = cl = 0;
+               while (list) {
+                       next = list->next;
+                       /* Wait for enqueuing to complete, if needed. */
+                       while (next == NULL && &list->next != tail) {
+                               schedule_timeout_interruptible(1);
+                               next = list->next;
+                       }
+                       debug_rcu_head_unqueue(list);
+                       local_bh_disable();
+                       if (__rcu_reclaim(rdp->rsp->name, list))
+                               cl++;
+                       c++;
+                       local_bh_enable();
+                       list = next;
+               }
+               trace_rcu_batch_end(rdp->rsp->name, c, !!list, 0, 0, 1);
+               ACCESS_ONCE(rdp->nocb_p_count) -= c;
+               ACCESS_ONCE(rdp->nocb_p_count_lazy) -= cl;
+               rdp->n_cbs_invoked += c;
+       }
+       return 0;
+}
+
+/* Initialize per-rcu_data variables for no-CBs CPUs. */
+static void __init rcu_boot_init_nocb_percpu_data(struct rcu_data *rdp)
+{
+       rdp->nocb_tail = &rdp->nocb_head;
+       init_waitqueue_head(&rdp->nocb_wq);
+}
+
+/* Create a kthread for each RCU flavor for each no-CBs CPU. */
+static void __init rcu_spawn_nocb_kthreads(struct rcu_state *rsp)
+{
+       int cpu;
+       struct rcu_data *rdp;
+       struct task_struct *t;
+
+       if (rcu_nocb_mask == NULL)
+               return;
+       for_each_cpu(cpu, rcu_nocb_mask) {
Quoted 8 lines

kernel/rcutree_plugin.h static void increment_cpu_stall_ticks(void) · line 2432

Quoted 2 lines
kernel/rcutree_plugin.h +2 -0
+               rdp = per_cpu_ptr(rsp->rda, cpu);
+               t = kthread_run(rcu_nocb_kthread, rdp, "rcuo%d", cpu);

Sorry, I think I left my brain in the middle of the diff. But there is something I'm misunderstanding I think. Here you're creating an rcu_nocb_kthread per nocb cpu. Looking at the code of rcu_nocb_kthread(), it seems to execute the callbacks with __rcu_reclaim().

True, executing within the context of the kthread, which might be executing on any CPU.

Quoted 3 lines

So, in the end, no callbacks CPU execute their callbacks. Isn't it the opposite than what is expected? (again, just referring to my misunderstanding).

The no-callbacks CPU would execute its own callbacks only if the corresponding kthread happened to be executing on that CPU. If you wanted a given CPU to be completely free of callbacks, you would instead constrain the corresponding kthread to run elsewhere.

Thanx, Paul

Quoted 7 lines

Thanks.

Quoted 4 lines
kernel/rcutree_plugin.h +4 -0
+               BUG_ON(IS_ERR(t));
+               ACCESS_ONCE(rdp->nocb_kthread) = t;
+       }
+}
#5 lore raw

Re: [PATCH tip/core/rcu 1/2] rcu: Add callback-free CPUs

2012/11/2 Paul E. McKenney <[email protected]>:

Quoted 5 lines

The no-callbacks CPU would execute its own callbacks only if the corresponding kthread happened to be executing on that CPU. If you wanted a given CPU to be completely free of callbacks, you would instead constrain the corresponding kthread to run elsewhere.

Ah ok, makes sense.

Thanks.

#6 lore raw
In reply to Paul E. McKenney
Quoted 8 lines · Paul E. McKenney

This commit therefore adds the ability
for selected CPUs ("rcu_nocbs=" boot parameter) to have their callbacks
offloaded to kthreads, inspired by Joe Korty's and Jim Houston's JRCU. If the "rcu_nocb_poll" boot parameter is also specified, these kthreads
will do polling, removing the need for the offloaded CPUs to do wakeups.

How much pain to make that dynamically configurable?

#7 lore raw
Quoted 11 lines · Peter Zijlstra
Quoted 8 lines · Paul E. McKenney

This commit therefore adds the ability
for selected CPUs ("rcu_nocbs=" boot parameter) to have their callbacks
offloaded to kthreads, inspired by Joe Korty's and Jim Houston's JRCU. If the "rcu_nocb_poll" boot parameter is also specified, these kthreads
will do polling, removing the need for the offloaded CPUs to do wakeups.

How much pain to make that dynamically configurable?

A fair amount of pain, though I do hope to make it runtime configurable at some point. Known problems include races between changes in the nocb configuration, rcu_barrier(), grace-period initialization, and CPU hotplug. Should be solvable, but want to get the other bugs out of the way first.

Thanx, Paul