rcu: Panic if a call_rcu() CB blocks
RCU callbacks are not allowed to block. Here's what Linux has to say:
... the callback function must be written to be called from softirq
context. In particular, it cannot block.
Same goes for us. If a CB blocks, then all further CBs will be blocked
too, and the code that will wake up the first CB could also be blocked on a
later CB.
Note the distinction between RCU callbacks and RCU threads. The threads
are critical parts of implementing RCU; they are never allowed to block on
RCU primitives. Yes, any RCU callback will be run by an RCU thread, but
the problem is slightly different.
Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
diff --git a/kern/include/trap.h b/kern/include/trap.h
index ab26759..2a7f081 100644
--- a/kern/include/trap.h
+++ b/kern/include/trap.h
@@ -160,7 +160,8 @@
#define __CTX_IRQ_D_MASK ((1 << 8) - 1)
#define __CTX_KTRAP_D_MASK ((1 << 8) - 1)
#define __CTX_NESTED_CTX_MASK ((1 << 16) - 1)
-#define __CTX_EARLY_RKM (1 << __CTX_FLAG_SHIFT)
+#define __CTX_EARLY_RKM (1 << (__CTX_FLAG_SHIFT + 0))
+#define __CTX_RCU_CB (1 << (__CTX_FLAG_SHIFT + 1))
/* Basic functions to get or change depths */
@@ -188,6 +189,12 @@
#define clear_rkmsg(pcpui) \
((pcpui)->__ctx_depth &= ~__CTX_EARLY_RKM)
+#define set_rcu_cb(pcpui) \
+ ((pcpui)->__ctx_depth |= __CTX_RCU_CB)
+
+#define clear_rcu_cb(pcpui) \
+ ((pcpui)->__ctx_depth &= ~__CTX_RCU_CB)
+
/* Functions to query the kernel context depth/state. I haven't fully decided
* on whether or not 'default' context includes RKMs or not. Will depend on
* how we use it. Check the code below to see what the latest is. */
@@ -198,14 +205,17 @@
#define in_early_rkmsg_ctx(pcpui) \
((pcpui)->__ctx_depth & __CTX_EARLY_RKM)
+#define in_rcu_cb_ctx(pcpui) \
+ ((pcpui)->__ctx_depth & __CTX_RCU_CB)
+
/* Right now, anything (KTRAP, IRQ, or RKM) makes us not 'default' */
#define in_default_ctx(pcpui) \
(!(pcpui)->__ctx_depth)
/* Can block only if we have no nested contexts (ktraps or irqs, (which are
- * potentially nested contexts)) */
+ * potentially nested contexts)) and not in an RCU CB*/
#define can_block(pcpui) \
- (!((pcpui)->__ctx_depth & __CTX_NESTED_CTX_MASK))
+ (!((pcpui)->__ctx_depth & (__CTX_NESTED_CTX_MASK | __CTX_RCU_CB)))
/* TRUE if we are allowed to spin, given that the 'lock' was declared as not
* grabbable from IRQ context. Meaning, we can't grab the lock from any nested
diff --git a/kern/src/rcu.c b/kern/src/rcu.c
index 2aec4ac..8481025 100644
--- a/kern/src/rcu.c
+++ b/kern/src/rcu.c
@@ -116,8 +116,10 @@
struct sync_cb_blob b[1];
struct semaphore sem[1];
- if (is_rcu_ktask(current_kthread))
+ if (in_rcu_cb_ctx(this_pcpui_ptr()))
panic("Attempted synchronize_rcu() from an RCU callback!");
+ if (is_rcu_ktask(current_kthread))
+ panic("Attempted synchronize_rcu() from an RCU thread!");
sem_init(sem, 0);
init_rcu_head_on_stack(&b->h);
b->sem = sem;
@@ -229,8 +231,10 @@
struct sync_cb_blob *b;
int nr_sent = 0;
- if (is_rcu_ktask(current_kthread))
+ if (in_rcu_cb_ctx(this_pcpui_ptr()))
panic("Attempted rcu_barrier() from an RCU callback!");
+ if (is_rcu_ktask(current_kthread))
+ panic("Attempted rcu_barrier() from an RCU thread!");
/* TODO: if we have concurrent rcu_barriers, we might be able to share the
* CBs. Say we have 1 CB on a core, then N rcu_barriers. We'll have N
* call_rcus in flight, though we could share. Linux does this with a mtx
@@ -514,10 +518,16 @@
assert(list_empty(&work));
return;
}
+ /* When we're in an RCU callback, we can't block. In our non-preemptive
+ * world, not blocking also means our kthread won't migrate from this core,
+ * such that the pcpui pointer (and thus the specific __ctx_depth) won't
+ * change. */
+ set_rcu_cb(this_pcpui_ptr());
list_for_each_entry_safe(head, temp, &work, link) {
list_del(&head->link);
rcu_exec_cb(head);
}
+ clear_rcu_cb(this_pcpui_ptr());
/* We kept nr_cbs in place until the CBs, which could block, completed.
* This allows other readers (rcu_barrier()) of our pcpui to tell if we have