srcu: Create a tiny SRCU
authorPaul E. McKenney <paulmck@linux.vnet.ibm.com>
Sat, 25 Mar 2017 16:59:38 +0000 (09:59 -0700)
committerPaul E. McKenney <paulmck@linux.vnet.ibm.com>
Tue, 18 Apr 2017 18:38:22 +0000 (11:38 -0700)
In response to automated complaints about modifications to SRCU
increasing its size, this commit creates a tiny SRCU that is
used in SMP=n && PREEMPT=n builds.

Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
include/linux/srcu.h
include/linux/srcutiny.h [new file with mode: 0644]
include/linux/srcutree.h [new file with mode: 0644]
init/Kconfig
kernel/rcu/Makefile
kernel/rcu/rcutorture.c
kernel/rcu/srcutiny.c [new file with mode: 0644]

index 73a1b6296224ffe49da677f3806a2b5654b810f9..907f09b14edac8ce33fea2b85bba694eb109ec11 100644 (file)
 #include <linux/workqueue.h>
 #include <linux/rcu_segcblist.h>
 
-struct srcu_array {
-       unsigned long lock_count[2];
-       unsigned long unlock_count[2];
-};
-
-struct srcu_struct {
-       unsigned long completed;
-       unsigned long srcu_gp_seq;
-       atomic_t srcu_exp_cnt;
-       struct srcu_array __percpu *per_cpu_ref;
-       spinlock_t queue_lock; /* protect ->srcu_cblist */
-       struct rcu_segcblist srcu_cblist;
-       struct delayed_work work;
-#ifdef CONFIG_DEBUG_LOCK_ALLOC
-       struct lockdep_map dep_map;
-#endif /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */
-};
-
-/* Values for -> state variable. */
-#define SRCU_STATE_IDLE                0
-#define SRCU_STATE_SCAN1       1
-#define SRCU_STATE_SCAN2       2
+struct srcu_struct;
 
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 
@@ -77,42 +56,13 @@ int init_srcu_struct(struct srcu_struct *sp);
 #define __SRCU_DEP_MAP_INIT(srcu_name)
 #endif /* #else #ifdef CONFIG_DEBUG_LOCK_ALLOC */
 
-void process_srcu(struct work_struct *work);
-
-#define __SRCU_STRUCT_INIT(name)                                       \
-       {                                                               \
-               .completed = -300,                                      \
-               .per_cpu_ref = &name##_srcu_array,                      \
-               .queue_lock = __SPIN_LOCK_UNLOCKED(name.queue_lock),    \
-               .srcu_cblist = RCU_SEGCBLIST_INITIALIZER(name.srcu_cblist),\
-               .work = __DELAYED_WORK_INITIALIZER(name.work, process_srcu, 0),\
-               __SRCU_DEP_MAP_INIT(name)                               \
-       }
-
-/*
- * Define and initialize a srcu struct at build time.
- * Do -not- call init_srcu_struct() nor cleanup_srcu_struct() on it.
- *
- * Note that although DEFINE_STATIC_SRCU() hides the name from other
- * files, the per-CPU variable rules nevertheless require that the
- * chosen name be globally unique.  These rules also prohibit use of
- * DEFINE_STATIC_SRCU() within a function.  If these rules are too
- * restrictive, declare the srcu_struct manually.  For example, in
- * each file:
- *
- *     static struct srcu_struct my_srcu;
- *
- * Then, before the first use of each my_srcu, manually initialize it:
- *
- *     init_srcu_struct(&my_srcu);
- *
- * See include/linux/percpu-defs.h for the rules on per-CPU variables.
- */
-#define __DEFINE_SRCU(name, is_static)                                 \
-       static DEFINE_PER_CPU(struct srcu_array, name##_srcu_array);\
-       is_static struct srcu_struct name = __SRCU_STRUCT_INIT(name)
-#define DEFINE_SRCU(name)              __DEFINE_SRCU(name, /* not static */)
-#define DEFINE_STATIC_SRCU(name)       __DEFINE_SRCU(name, static)
+#ifdef CONFIG_TINY_SRCU
+#include <linux/srcutiny.h>
+#elif defined(CONFIG_TREE_SRCU)
+#include <linux/srcutree.h>
+#else
+#error "Unknown SRCU implementation specified to kernel configuration"
+#endif
 
 /**
  * call_srcu() - Queue a callback for invocation after an SRCU grace period
@@ -138,9 +88,6 @@ void cleanup_srcu_struct(struct srcu_struct *sp);
 int __srcu_read_lock(struct srcu_struct *sp) __acquires(sp);
 void __srcu_read_unlock(struct srcu_struct *sp, int idx) __releases(sp);
 void synchronize_srcu(struct srcu_struct *sp);
-void synchronize_srcu_expedited(struct srcu_struct *sp);
-unsigned long srcu_batches_completed(struct srcu_struct *sp);
-void srcu_barrier(struct srcu_struct *sp);
 
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 
diff --git a/include/linux/srcutiny.h b/include/linux/srcutiny.h
new file mode 100644 (file)
index 0000000..4f284e4
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Sleepable Read-Copy Update mechanism for mutual exclusion,
+ *     tiny variant.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you can access it online at
+ * http://www.gnu.org/licenses/gpl-2.0.html.
+ *
+ * Copyright (C) IBM Corporation, 2017
+ *
+ * Author: Paul McKenney <paulmck@us.ibm.com>
+ */
+
+#ifndef _LINUX_SRCU_TINY_H
+#define _LINUX_SRCU_TINY_H
+
+#include <linux/swait.h>
+
+struct srcu_struct {
+       int srcu_lock_nesting[2];       /* srcu_read_lock() nesting depth. */
+       struct swait_queue_head srcu_wq;
+                                       /* Last srcu_read_unlock() wakes GP. */
+       unsigned long srcu_gp_seq;      /* GP seq # for callback tagging. */
+       struct rcu_segcblist srcu_cblist;
+                                       /* Pending SRCU callbacks. */
+       int srcu_idx;                   /* Current reader array element. */
+       bool srcu_gp_running;           /* GP workqueue running? */
+       bool srcu_gp_waiting;           /* GP waiting for readers? */
+       struct work_struct srcu_work;   /* For driving grace periods. */
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
+       struct lockdep_map dep_map;
+#endif /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */
+};
+
+void srcu_drive_gp(struct work_struct *wp);
+
+#define __SRCU_STRUCT_INIT(name)                                       \
+{                                                                      \
+       .srcu_wq = __SWAIT_QUEUE_HEAD_INITIALIZER(name.srcu_wq),        \
+       .srcu_cblist = RCU_SEGCBLIST_INITIALIZER(name.srcu_cblist),     \
+       .srcu_work = __WORK_INITIALIZER(name.srcu_work, srcu_drive_gp), \
+       __SRCU_DEP_MAP_INIT(name)                                       \
+}
+
+/*
+ * This odd _STATIC_ arrangement is needed for API compatibility with
+ * Tree SRCU, which needs some per-CPU data.
+ */
+#define DEFINE_SRCU(name) \
+       struct srcu_struct name = __SRCU_STRUCT_INIT(name)
+#define DEFINE_STATIC_SRCU(name) \
+       static struct srcu_struct name = __SRCU_STRUCT_INIT(name)
+
+void synchronize_srcu(struct srcu_struct *sp);
+
+static inline void synchronize_srcu_expedited(struct srcu_struct *sp)
+{
+       synchronize_srcu(sp);
+}
+
+static inline void srcu_barrier(struct srcu_struct *sp)
+{
+       synchronize_srcu(sp);
+}
+
+static inline unsigned long srcu_batches_completed(struct srcu_struct *sp)
+{
+       return 0;
+}
+
+#endif
diff --git a/include/linux/srcutree.h b/include/linux/srcutree.h
new file mode 100644 (file)
index 0000000..f2b3bd6
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Sleepable Read-Copy Update mechanism for mutual exclusion,
+ *     tree variant.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you can access it online at
+ * http://www.gnu.org/licenses/gpl-2.0.html.
+ *
+ * Copyright (C) IBM Corporation, 2017
+ *
+ * Author: Paul McKenney <paulmck@us.ibm.com>
+ */
+
+#ifndef _LINUX_SRCU_TREE_H
+#define _LINUX_SRCU_TREE_H
+
+struct srcu_array {
+       unsigned long lock_count[2];
+       unsigned long unlock_count[2];
+};
+
+struct srcu_struct {
+       unsigned long completed;
+       unsigned long srcu_gp_seq;
+       atomic_t srcu_exp_cnt;
+       struct srcu_array __percpu *per_cpu_ref;
+       spinlock_t queue_lock; /* protect ->srcu_cblist */
+       struct rcu_segcblist srcu_cblist;
+       struct delayed_work work;
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
+       struct lockdep_map dep_map;
+#endif /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */
+};
+
+/* Values for -> state variable. */
+#define SRCU_STATE_IDLE                0
+#define SRCU_STATE_SCAN1       1
+#define SRCU_STATE_SCAN2       2
+
+void process_srcu(struct work_struct *work);
+
+#define __SRCU_STRUCT_INIT(name)                                       \
+       {                                                               \
+               .completed = -300,                                      \
+               .per_cpu_ref = &name##_srcu_array,                      \
+               .queue_lock = __SPIN_LOCK_UNLOCKED(name.queue_lock),    \
+               .srcu_cblist = RCU_SEGCBLIST_INITIALIZER(name.srcu_cblist),\
+               .work = __DELAYED_WORK_INITIALIZER(name.work, process_srcu, 0),\
+               __SRCU_DEP_MAP_INIT(name)                               \
+       }
+
+/*
+ * Define and initialize a srcu struct at build time.
+ * Do -not- call init_srcu_struct() nor cleanup_srcu_struct() on it.
+ *
+ * Note that although DEFINE_STATIC_SRCU() hides the name from other
+ * files, the per-CPU variable rules nevertheless require that the
+ * chosen name be globally unique.  These rules also prohibit use of
+ * DEFINE_STATIC_SRCU() within a function.  If these rules are too
+ * restrictive, declare the srcu_struct manually.  For example, in
+ * each file:
+ *
+ *     static struct srcu_struct my_srcu;
+ *
+ * Then, before the first use of each my_srcu, manually initialize it:
+ *
+ *     init_srcu_struct(&my_srcu);
+ *
+ * See include/linux/percpu-defs.h for the rules on per-CPU variables.
+ */
+#define __DEFINE_SRCU(name, is_static)                                 \
+       static DEFINE_PER_CPU(struct srcu_array, name##_srcu_array);\
+       is_static struct srcu_struct name = __SRCU_STRUCT_INIT(name)
+#define DEFINE_SRCU(name)              __DEFINE_SRCU(name, /* not static */)
+#define DEFINE_STATIC_SRCU(name)       __DEFINE_SRCU(name, static)
+
+void synchronize_srcu_expedited(struct srcu_struct *sp);
+void srcu_barrier(struct srcu_struct *sp);
+unsigned long srcu_batches_completed(struct srcu_struct *sp);
+
+#endif
index a92f27da4a272ec873707bc4b7a68e46a4340b86..d269f2ca17b82c958eaafbf745c49617bcbc92be 100644 (file)
@@ -526,6 +526,18 @@ config SRCU
          permits arbitrary sleeping or blocking within RCU read-side critical
          sections.
 
+config TINY_SRCU
+       bool
+       default y if TINY_RCU
+       help
+         This option selects the single-CPU non-preemptible version of SRCU.
+
+config TREE_SRCU
+       bool
+       default y if !TINY_RCU
+       help
+         This option selects the full-fledged version of SRCU.
+
 config TASKS_RCU
        bool
        default n
index 18dfc485225c3954ccf29a8743bc636f10db7d41..b853214a2b99a8bea01f9a58430db50134d051ed 100644 (file)
@@ -3,7 +3,8 @@
 KCOV_INSTRUMENT := n
 
 obj-y += update.o sync.o
-obj-$(CONFIG_SRCU) += srcu.o
+obj-$(CONFIG_TREE_SRCU) += srcu.o
+obj-$(CONFIG_TINY_SRCU) += srcutiny.o
 obj-$(CONFIG_RCU_TORTURE_TEST) += rcutorture.o
 obj-$(CONFIG_RCU_PERF_TEST) += rcuperf.o
 obj-$(CONFIG_TREE_RCU) += tree.o
index cccc417a813502f8bc0f6d0af930b25d3d1b4f52..98591e16db1ad6ed952fc5b23b18b6c2ff01ac9b 100644 (file)
@@ -559,6 +559,7 @@ static void srcu_torture_barrier(void)
 
 static void srcu_torture_stats(void)
 {
+#ifdef CONFIG_TREE_SRCU
        int cpu;
        int idx = srcu_ctlp->completed & 0x1;
 
@@ -587,6 +588,7 @@ static void srcu_torture_stats(void)
                pr_cont(" %d(%ld,%ld)", cpu, c0, c1);
        }
        pr_cont("\n");
+#endif
 }
 
 static void srcu_torture_synchronize_expedited(void)
diff --git a/kernel/rcu/srcutiny.c b/kernel/rcu/srcutiny.c
new file mode 100644 (file)
index 0000000..b829352
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * Sleepable Read-Copy Update mechanism for mutual exclusion,
+ *     tiny version for non-preemptible single-CPU use.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you can access it online at
+ * http://www.gnu.org/licenses/gpl-2.0.html.
+ *
+ * Copyright (C) IBM Corporation, 2017
+ *
+ * Author: Paul McKenney <paulmck@us.ibm.com>
+ */
+
+#include <linux/export.h>
+#include <linux/mutex.h>
+#include <linux/preempt.h>
+#include <linux/rcupdate_wait.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/srcu.h>
+
+#include <linux/rcu_node_tree.h>
+#include "rcu.h"
+
+static int init_srcu_struct_fields(struct srcu_struct *sp)
+{
+       sp->srcu_lock_nesting[0] = 0;
+       sp->srcu_lock_nesting[1] = 0;
+       init_swait_queue_head(&sp->srcu_wq);
+       sp->srcu_gp_seq = 0;
+       rcu_segcblist_init(&sp->srcu_cblist);
+       sp->srcu_gp_running = false;
+       sp->srcu_gp_waiting = false;
+       sp->srcu_idx = 0;
+       INIT_WORK(&sp->srcu_work, srcu_drive_gp);
+       return 0;
+}
+
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
+
+int __init_srcu_struct(struct srcu_struct *sp, const char *name,
+                      struct lock_class_key *key)
+{
+       /* Don't re-initialize a lock while it is held. */
+       debug_check_no_locks_freed((void *)sp, sizeof(*sp));
+       lockdep_init_map(&sp->dep_map, name, key, 0);
+       return init_srcu_struct_fields(sp);
+}
+EXPORT_SYMBOL_GPL(__init_srcu_struct);
+
+#else /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */
+
+/*
+ * init_srcu_struct - initialize a sleep-RCU structure
+ * @sp: structure to initialize.
+ *
+ * Must invoke this on a given srcu_struct before passing that srcu_struct
+ * to any other function.  Each srcu_struct represents a separate domain
+ * of SRCU protection.
+ */
+int init_srcu_struct(struct srcu_struct *sp)
+{
+       return init_srcu_struct_fields(sp);
+}
+EXPORT_SYMBOL_GPL(init_srcu_struct);
+
+#endif /* #else #ifdef CONFIG_DEBUG_LOCK_ALLOC */
+
+/*
+ * cleanup_srcu_struct - deconstruct a sleep-RCU structure
+ * @sp: structure to clean up.
+ *
+ * Must invoke this after you are finished using a given srcu_struct that
+ * was initialized via init_srcu_struct(), else you leak memory.
+ */
+void cleanup_srcu_struct(struct srcu_struct *sp)
+{
+       WARN_ON(sp->srcu_lock_nesting[0] || sp->srcu_lock_nesting[1]);
+       flush_work(&sp->srcu_work);
+       WARN_ON(rcu_seq_state(sp->srcu_gp_seq));
+       WARN_ON(sp->srcu_gp_running);
+       WARN_ON(sp->srcu_gp_waiting);
+       WARN_ON(!rcu_segcblist_empty(&sp->srcu_cblist));
+}
+EXPORT_SYMBOL_GPL(cleanup_srcu_struct);
+
+/*
+ * Counts the new reader in the appropriate per-CPU element of the
+ * srcu_struct.  Must be called from process context.
+ * Returns an index that must be passed to the matching srcu_read_unlock().
+ */
+int __srcu_read_lock(struct srcu_struct *sp)
+{
+       int idx;
+
+       idx = READ_ONCE(sp->srcu_idx);
+       WRITE_ONCE(sp->srcu_lock_nesting[idx], sp->srcu_lock_nesting[idx] + 1);
+       return idx;
+}
+EXPORT_SYMBOL_GPL(__srcu_read_lock);
+
+/*
+ * Removes the count for the old reader from the appropriate element of
+ * the srcu_struct.  Must be called from process context.
+ */
+void __srcu_read_unlock(struct srcu_struct *sp, int idx)
+{
+       int newval = sp->srcu_lock_nesting[idx] - 1;
+
+       WRITE_ONCE(sp->srcu_lock_nesting[idx], newval);
+       if (!newval && READ_ONCE(sp->srcu_gp_waiting))
+               swake_up(&sp->srcu_wq);
+}
+EXPORT_SYMBOL_GPL(__srcu_read_unlock);
+
+/*
+ * Workqueue handler to drive one grace period and invoke any callbacks
+ * that become ready as a result.  Single-CPU and !PREEMPT operation
+ * means that we get away with murder on synchronization.  ;-)
+ */
+void srcu_drive_gp(struct work_struct *wp)
+{
+       int idx;
+       struct rcu_cblist ready_cbs;
+       struct srcu_struct *sp;
+       struct rcu_head *rhp;
+
+       sp = container_of(wp, struct srcu_struct, srcu_work);
+       if (sp->srcu_gp_running || rcu_segcblist_empty(&sp->srcu_cblist))
+               return; /* Already running or nothing to do. */
+
+       /* Tag recently arrived callbacks and wait for readers. */
+       WRITE_ONCE(sp->srcu_gp_running, true);
+       rcu_segcblist_accelerate(&sp->srcu_cblist,
+                                rcu_seq_snap(&sp->srcu_gp_seq));
+       rcu_seq_start(&sp->srcu_gp_seq);
+       idx = sp->srcu_idx;
+       WRITE_ONCE(sp->srcu_idx, !sp->srcu_idx);
+       WRITE_ONCE(sp->srcu_gp_waiting, true);  /* srcu_read_unlock() wakes! */
+       swait_event(sp->srcu_wq, !READ_ONCE(sp->srcu_lock_nesting[idx]));
+       WRITE_ONCE(sp->srcu_gp_waiting, false); /* srcu_read_unlock() cheap. */
+       rcu_seq_end(&sp->srcu_gp_seq);
+
+       /* Update callback list based on GP, and invoke ready callbacks. */
+       rcu_segcblist_advance(&sp->srcu_cblist,
+                             rcu_seq_current(&sp->srcu_gp_seq));
+       if (rcu_segcblist_ready_cbs(&sp->srcu_cblist)) {
+               rcu_cblist_init(&ready_cbs);
+               local_irq_disable();
+               rcu_segcblist_extract_done_cbs(&sp->srcu_cblist, &ready_cbs);
+               local_irq_enable();
+               rhp = rcu_cblist_dequeue(&ready_cbs);
+               for (; rhp != NULL; rhp = rcu_cblist_dequeue(&ready_cbs)) {
+                       local_bh_disable();
+                       rhp->func(rhp);
+                       local_bh_enable();
+               }
+               local_irq_disable();
+               rcu_segcblist_insert_count(&sp->srcu_cblist, &ready_cbs);
+               local_irq_enable();
+       }
+       WRITE_ONCE(sp->srcu_gp_running, false);
+
+       /*
+        * If more callbacks, reschedule ourselves.  This can race with
+        * a call_srcu() at interrupt level, but the ->srcu_gp_running
+        * checks will straighten that out.
+        */
+       if (!rcu_segcblist_empty(&sp->srcu_cblist))
+               schedule_work(&sp->srcu_work);
+}
+EXPORT_SYMBOL_GPL(srcu_drive_gp);
+
+/*
+ * Enqueue an SRCU callback on the specified srcu_struct structure,
+ * initiating grace-period processing if it is not already running.
+ */
+void call_srcu(struct srcu_struct *sp, struct rcu_head *head,
+              rcu_callback_t func)
+{
+       unsigned long flags;
+
+       head->func = func;
+       local_irq_save(flags);
+       rcu_segcblist_enqueue(&sp->srcu_cblist, head, false);
+       local_irq_restore(flags);
+       if (!READ_ONCE(sp->srcu_gp_running))
+               schedule_work(&sp->srcu_work);
+}
+EXPORT_SYMBOL_GPL(call_srcu);
+
+/*
+ * synchronize_srcu - wait for prior SRCU read-side critical-section completion
+ */
+void synchronize_srcu(struct srcu_struct *sp)
+{
+       struct rcu_synchronize rs;
+
+       init_rcu_head_on_stack(&rs.head);
+       init_completion(&rs.completion);
+       call_srcu(sp, &rs.head, wakeme_after_rcu);
+       wait_for_completion(&rs.completion);
+       destroy_rcu_head_on_stack(&rs.head);
+}
+EXPORT_SYMBOL_GPL(synchronize_srcu);