rcu: Do not let RCU callbacks block on RCU
authorBarret Rhoden <brho@cs.berkeley.edu>
Fri, 8 Jun 2018 16:11:12 +0000 (12:11 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Fri, 8 Jun 2018 16:11:12 +0000 (12:11 -0400)
The callbacks are run from a ktask.  If the callbacks attempt to block on
RCU with e.g. rcu_barrier() or synchronize_rcu(), we'll deadlock.  The
ktask blocks on RCU callbacks, but the ktask is the only thing that will
run the callbacks.

As far as I can tell, this is also illegal on Linux.  For instance, this
will throw a bunch of errors at runtime on Linux:

static void __cb(struct rcu_head *h)
{
    printk(KERN_INFO "about to sync\n");
    // works only if we comment this out, dies otherwise
    synchronize_rcu();
}

static void foo(void)
{
    struct rcu_head head[1];

    init_rcu_head_on_stack(head);
    call_rcu(head, __cb);
    rcu_barrier();
    destroy_rcu_head_on_stack(head);
}

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
kern/include/kthread.h
kern/src/rcu.c

index 72dfc90..01af545 100644 (file)
@@ -29,7 +29,12 @@ TAILQ_HEAD(semaphore_tailq, semaphore);
 
 #define KTH_IS_KTASK                   (1 << 0)
 #define KTH_SAVE_ADDR_SPACE            (1 << 1)
 
 #define KTH_IS_KTASK                   (1 << 0)
 #define KTH_SAVE_ADDR_SPACE            (1 << 1)
+#define KTH_IS_RCU_KTASK               (1 << 2)
+
+/* These flag sets are for toggling between ktasks and default/process ktasks */
+/* These are the flags for *any* ktask */
 #define KTH_KTASK_FLAGS                        (KTH_IS_KTASK)
 #define KTH_KTASK_FLAGS                        (KTH_IS_KTASK)
+/* These are the flags used for normal process context */
 #define KTH_DEFAULT_FLAGS              (KTH_SAVE_ADDR_SPACE)
 
 /* This captures the essence of a kernel context that we want to suspend.  When
 #define KTH_DEFAULT_FLAGS              (KTH_SAVE_ADDR_SPACE)
 
 /* This captures the essence of a kernel context that we want to suspend.  When
@@ -116,6 +121,11 @@ static inline bool is_ktask(struct kthread *kthread)
        return kthread->flags & KTH_IS_KTASK;
 }
 
        return kthread->flags & KTH_IS_KTASK;
 }
 
+static inline bool is_rcu_ktask(struct kthread *kthread)
+{
+       return kthread->flags & KTH_IS_RCU_KTASK;
+}
+
 void sem_init(struct semaphore *sem, int signals);
 void sem_init_irqsave(struct semaphore *sem, int signals);
 bool sem_trydown_bulk(struct semaphore *sem, int nr_signals);
 void sem_init(struct semaphore *sem, int signals);
 void sem_init_irqsave(struct semaphore *sem, int signals);
 bool sem_trydown_bulk(struct semaphore *sem, int nr_signals);
index 9a4bc36..2aec4ac 100644 (file)
@@ -116,6 +116,8 @@ void synchronize_rcu(void)
        struct sync_cb_blob b[1];
        struct semaphore sem[1];
 
        struct sync_cb_blob b[1];
        struct semaphore sem[1];
 
+       if (is_rcu_ktask(current_kthread))
+               panic("Attempted synchronize_rcu() from an RCU callback!");
        sem_init(sem, 0);
        init_rcu_head_on_stack(&b->h);
        b->sem = sem;
        sem_init(sem, 0);
        init_rcu_head_on_stack(&b->h);
        b->sem = sem;
@@ -227,6 +229,8 @@ void rcu_barrier(void)
        struct sync_cb_blob *b;
        int nr_sent = 0;
 
        struct sync_cb_blob *b;
        int nr_sent = 0;
 
+       if (is_rcu_ktask(current_kthread))
+               panic("Attempted rcu_barrier() from an RCU callback!");
        /* 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
        /* 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
@@ -459,6 +463,7 @@ static void rcu_gp_ktask(void *arg)
 {
        struct rcu_state *rsp = arg;
 
 {
        struct rcu_state *rsp = arg;
 
+       current_kthread->flags |= KTH_IS_RCU_KTASK;
        while (1) {
                rendez_sleep_timeout(&rsp->gp_ktask_rv, should_wake_ctl,
                                     &rsp->gp_ktask_ctl, RCU_GP_MIN_PERIOD);
        while (1) {
                rendez_sleep_timeout(&rsp->gp_ktask_rv, should_wake_ctl,
                                     &rsp->gp_ktask_ctl, RCU_GP_MIN_PERIOD);
@@ -528,6 +533,7 @@ static void rcu_mgmt_ktask(void *arg)
        struct rcu_pcpui *rpi = arg;
        struct rcu_state *rsp = rpi->rsp;
 
        struct rcu_pcpui *rpi = arg;
        struct rcu_state *rsp = rpi->rsp;
 
+       current_kthread->flags |= KTH_IS_RCU_KTASK;
        while (1) {
                rendez_sleep(&rpi->mgmt_ktask_rv, should_wake_ctl,
                             &rpi->mgmt_ktask_ctl);
        while (1) {
                rendez_sleep(&rpi->mgmt_ktask_rv, should_wake_ctl,
                             &rpi->mgmt_ktask_ctl);