srcu: Apply trivial callback lists to shrink Tiny SRCU
authorPaul E. McKenney <paulmck@linux.vnet.ibm.com>
Thu, 4 May 2017 21:29:16 +0000 (14:29 -0700)
committerPaul E. McKenney <paulmck@linux.vnet.ibm.com>
Fri, 9 Jun 2017 01:52:35 +0000 (18:52 -0700)
The rcu_segcblist structure provides quite a bit of functionality, and
Tiny SRCU needs almost none of it.  So this commit replaces Tiny SRCU's
uses of rcu_segcblist with a simple singly linked list with tail pointer.
This change significantly reduces Tiny SRCU's memory footprint, more
than making up for the growth caused by the creation of rcu_segcblist.c

Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
include/linux/srcutiny.h
init/Kconfig
kernel/rcu/rcu.h
kernel/rcu/srcutiny.c

index 4c53e69..cfbfc54 100644 (file)
@@ -33,9 +33,8 @@ struct srcu_struct {
        u8 srcu_gp_waiting;             /* GP waiting for readers? */
        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. */
+       struct rcu_head *srcu_cb_head;  /* Pending callbacks: Head. */
+       struct rcu_head **srcu_cb_tail; /* Pending callbacks: Tail. */
        struct work_struct srcu_work;   /* For driving grace periods. */
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
        struct lockdep_map dep_map;
@@ -47,7 +46,7 @@ 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_cb_tail = &name.srcu_cb_head,                             \
        .srcu_work = __WORK_INITIALIZER(name.srcu_work, srcu_drive_gp), \
        __SRCU_DEP_MAP_INIT(name)                                       \
 }
index d928a37..a2cfde1 100644 (file)
@@ -573,7 +573,7 @@ config RCU_STALL_COMMON
          making these warnings mandatory for the tree variants.
 
 config RCU_NEED_SEGCBLIST
-       def_bool ( TREE_RCU || PREEMPT_RCU || TINY_SRCU || TREE_SRCU )
+       def_bool ( TREE_RCU || PREEMPT_RCU || TREE_SRCU )
 
 config CONTEXT_TRACKING
        bool
index 74d9fc2..6a1e85b 100644 (file)
@@ -398,7 +398,7 @@ static inline void srcutorture_get_gp_data(enum rcutorture_type test_type,
        if (test_type != SRCU_FLAVOR)
                return;
        *flags = 0;
-       *completed = sp->srcu_gp_seq;
+       *completed = sp->srcu_idx;
        *gpnum = *completed;
 }
 
index 9885437..1a1c104 100644 (file)
@@ -38,8 +38,8 @@ 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_cb_head = NULL;
+       sp->srcu_cb_tail = &sp->srcu_cb_head;
        sp->srcu_gp_running = false;
        sp->srcu_gp_waiting = false;
        sp->srcu_idx = 0;
@@ -88,10 +88,10 @@ 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));
+       WARN_ON(sp->srcu_cb_head);
+       WARN_ON(&sp->srcu_cb_head != sp->srcu_cb_tail);
 }
 EXPORT_SYMBOL_GPL(cleanup_srcu_struct);
 
@@ -117,52 +117,44 @@ EXPORT_SYMBOL_GPL(__srcu_read_unlock);
 void srcu_drive_gp(struct work_struct *wp)
 {
        int idx;
-       struct rcu_cblist ready_cbs;
-       struct srcu_struct *sp;
+       struct rcu_head *lh;
        struct rcu_head *rhp;
+       struct srcu_struct *sp;
 
        sp = container_of(wp, struct srcu_struct, srcu_work);
-       if (sp->srcu_gp_running || rcu_segcblist_empty(&sp->srcu_cblist))
+       if (sp->srcu_gp_running || !READ_ONCE(sp->srcu_cb_head))
                return; /* Already running or nothing to do. */
 
-       /* Tag recently arrived callbacks and wait for readers. */
+       /* Remove 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);
+       local_irq_disable();
+       lh = sp->srcu_cb_head;
+       sp->srcu_cb_head = NULL;
+       sp->srcu_cb_tail = &sp->srcu_cb_head;
+       local_irq_enable();
        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();
+
+       /* Invoke the callbacks we removed above. */
+       while (lh) {
+               rhp = lh;
+               lh = lh->next;
+               local_bh_disable();
+               rhp->func(rhp);
+               local_bh_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.
+        * Enable rescheduling, and if there are 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))
+       WRITE_ONCE(sp->srcu_gp_running, false);
+       if (READ_ONCE(sp->srcu_cb_head))
                schedule_work(&sp->srcu_work);
 }
 EXPORT_SYMBOL_GPL(srcu_drive_gp);
@@ -171,14 +163,16 @@ 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,
+void call_srcu(struct srcu_struct *sp, struct rcu_head *rhp,
               rcu_callback_t func)
 {
        unsigned long flags;
 
-       head->func = func;
+       rhp->func = func;
+       rhp->next = NULL;
        local_irq_save(flags);
-       rcu_segcblist_enqueue(&sp->srcu_cblist, head, false);
+       *sp->srcu_cb_tail = rhp;
+       sp->srcu_cb_tail = &rhp->next;
        local_irq_restore(flags);
        if (!READ_ONCE(sp->srcu_gp_running))
                schedule_work(&sp->srcu_work);