SYS_abort_syscall (XCC)
authorBarret Rhoden <brho@cs.berkeley.edu>
Fri, 22 Nov 2013 00:14:26 +0000 (16:14 -0800)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 16 Jan 2014 21:07:36 +0000 (13:07 -0800)
Userspace can request the cancellation of a syscall.  If that sysc is
blocked on a rendez, such as when listening on a network chan, it will
return via error().  If not, nothing will happen.

Reinstall your kernel headers.

12 files changed:
kern/include/env.h
kern/include/kthread.h
kern/include/ros/bits/syscall.h
kern/include/ros/syscall.h
kern/src/kthread.c
kern/src/process.c
kern/src/rendez.c
kern/src/syscall.c
kern/src/trap.c
user/parlib/include/parlib.h
user/parlib/syscall.c
user/parlib/uthread.c

index 2833b3d..838b3fb 100644 (file)
@@ -89,6 +89,8 @@ struct proc {
        struct small_hashlock           ucq_hl_noref;   /* don't reference directly */
        /* For devalarm */
        struct proc_alarm_set           alarmset;
+       struct cv_lookup_tailq          abortable_sleepers;
+       spinlock_t                                      abort_list_lock;
 };
 
 /* Til we remove all Env references */
index dd92d6a..6ac725c 100644 (file)
@@ -61,6 +61,16 @@ struct cond_var {
        bool                                            irq_okay;
 };
 
+struct cv_lookup_elm {
+       TAILQ_ENTRY(cv_lookup_elm)      link;
+       struct cond_var                         *cv;
+       struct kthread                          *kthread;
+       struct syscall                          *sysc;
+       struct proc                                     *proc;
+       bool                                            abort_in_progress;
+};
+TAILQ_HEAD(cv_lookup_tailq, cv_lookup_elm);
+
 uintptr_t get_kstack(void);
 void put_kstack(uintptr_t stacktop);
 uintptr_t *kstack_bottom_addr(uintptr_t stacktop);
@@ -101,4 +111,9 @@ void cv_broadcast(struct cond_var *cv);
 void cv_signal_irqsave(struct cond_var *cv, int8_t *irq_state);
 void cv_broadcast_irqsave(struct cond_var *cv, int8_t *irq_state);
 
+bool abort_sysc(struct proc *p, struct syscall *sysc);
+void __reg_abortable_cv(struct cv_lookup_elm *cle, struct cond_var *cv);
+void dereg_abortable_cv(struct cv_lookup_elm *cle);
+bool should_abort(struct cv_lookup_elm *cle);
+
 #endif /* ROS_KERN_KTHREAD_H */
index cfdd8e1..55e4161 100644 (file)
@@ -41,6 +41,7 @@
 #define SYS_init_arsc                          28
 #define SYS_change_to_m                                29
 #define SYS_poke_ksched                                30
+#define SYS_abort_sysc                         31
 
 /* Socket Syscalls */
 #define SYS_socket                                     40
index 52c9a95..0f6f52f 100644 (file)
@@ -12,6 +12,7 @@
 #define SC_PROGRESS                            0x0002          /* SC made progress */
 #define SC_UEVENT                              0x0004          /* user has an ev_q */
 #define SC_K_LOCK                              0x0008          /* kernel locked sysc */
+#define SC_ABORT                               0x0010          /* syscall abort attempted */
 
 #define MAX_ERRSTR_LEN                 128
 
index 478ba25..98f0c87 100644 (file)
@@ -681,3 +681,92 @@ void cv_broadcast_irqsave(struct cond_var *cv, int8_t *irq_state)
        cv_broadcast(cv);
        enable_irqsave(irq_state);
 }
+
+/* Attempts to abort p's sysc.  It will only do so if the sysc lookup succeeds,
+ * so we can handle "guesses" for syscalls that might not be sleeping.  This
+ * style of "do it if you know you can" is the best way here - anything else
+ * runs into situations where you don't know if the memory is safe to touch or
+ * not (we're doing a lookup via pointer address, and only dereferencing if that
+ * succeeds).  Even something simple like letting userspace write SC_ABORT is
+ * very hard for them, since they don't know a sysc's state for sure (under the
+ * current system).
+ *
+ * Here are the rules:
+ * - if you're flagged SC_ABORT, you don't sleep
+ * - if you sleep, you're on the list
+ * - if you are on the list or abort_in_progress is set, CV is signallable, and
+ *   all the memory for CLE is safe */
+bool abort_sysc(struct proc *p, struct syscall *sysc)
+{
+       struct cv_lookup_elm *cle;
+       int8_t irq_state = 0;
+       spin_lock_irqsave(&p->abort_list_lock);
+       TAILQ_FOREACH(cle, &p->abortable_sleepers, link) {
+               if (cle->sysc == sysc) {
+                       cle->abort_in_progress = TRUE;
+                       break;
+               }
+       }
+       spin_unlock_irqsave(&p->abort_list_lock);
+       if (!cle)
+               return FALSE;
+       /* At this point, we have a handle on the syscall that we want to abort (via
+        * the cle), and we know none of the memory will disappear on us (deregers
+        * wait on the flag).  So we'll signal ABORT, which rendez will pick up next
+        * time it is awake.  Then we make sure it is awake with a broadcast. */
+       atomic_or(&cle->sysc->flags, SC_ABORT);
+       cmb();  /* flags write before signal; atomic op provided CPU mb */
+       cv_broadcast_irqsave(cle->cv, &irq_state); 
+       wmb();  /* signal/broadcast writes before clearing the abort flag */
+       cle->abort_in_progress = FALSE;
+       return TRUE;
+}
+
+/* Being on the abortable list means that the CLE, KTH, SYSC, and CV are valid
+ * memory.  The lock ordering is {CV lock, list_lock}.  Callers to this *will*
+ * have CV held.  This is done to avoid excessive locking in places like
+ * rendez_sleep, which want to check the condition before registering. */
+void __reg_abortable_cv(struct cv_lookup_elm *cle, struct cond_var *cv)
+{
+       struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+       cle->cv = cv;
+       cle->kthread = pcpui->cur_kthread;
+       /* Could be a ktask.  Can build in support for aborting these later */
+       if (cle->kthread->is_ktask) {
+               cle->sysc = 0;
+               return;
+       }
+       cle->sysc = cle->kthread->sysc;
+       assert(cle->sysc);
+       cle->proc = pcpui->cur_proc;
+       cle->abort_in_progress = FALSE;
+       spin_lock_irqsave(&cle->proc->abort_list_lock);
+       TAILQ_INSERT_HEAD(&cle->proc->abortable_sleepers, cle, link);
+       spin_unlock_irqsave(&cle->proc->abort_list_lock);
+}
+
+/* We're racing with the aborter too, who will hold the flag in cle to protect
+ * its ref on our cle.  While the lock ordering is CV, list, callers to this
+ * must *not* have the cv lock held.  The reason is this waits on a successful
+ * abort_sysc, which is trying to cv_{signal,broadcast}, which could wait on the
+ * CV lock.  So if we hold the CV lock, we can deadlock (circular dependency).*/
+void dereg_abortable_cv(struct cv_lookup_elm *cle)
+{
+       if (cle->kthread->is_ktask)
+               return;
+       assert(cle->proc);
+       spin_lock_irqsave(&cle->proc->abort_list_lock);
+       TAILQ_REMOVE(&cle->proc->abortable_sleepers, cle, link);
+       spin_unlock_irqsave(&cle->proc->abort_list_lock);
+       /* If we won the race and yanked it out of the list before abort claimed it,
+        * this will already be FALSE. */
+       while (cle->abort_in_progress)
+               cpu_relax();
+}
+
+/* Helper to sleepers to know if they should abort or not.  I'll probably extend
+ * this with things for ktasks in the future. */
+bool should_abort(struct cv_lookup_elm *cle)
+{
+       return (cle->sysc && (atomic_read(&cle->sysc->flags) & SC_ABORT));
+}
index 32715f7..b2bb0b8 100644 (file)
@@ -312,6 +312,8 @@ error_t proc_alloc(struct proc **pp, struct proc *parent)
        frontend_proc_init(p);
        //plan9setup(p, parent);
        //devalarm_init(p);
+       TAILQ_INIT(&p->abortable_sleepers);
+       spinlock_init_irqsave(&p->abort_list_lock);
        printd("[%08x] new process %08x\n", current ? current->pid : 0, p->pid);
        } // INIT_STRUCT
        *pp = p;
index b1797b1..6c2665a 100644 (file)
@@ -11,6 +11,7 @@
 #include <alarm.h>
 #include <assert.h>
 #include <smp.h>
+#include <err.h>
 
 void rendez_init(struct rendez *rv)
 {
@@ -20,14 +21,32 @@ void rendez_init(struct rendez *rv)
 void rendez_sleep(struct rendez *rv, int (*cond)(void*), void *arg)
 {
        int8_t irq_state = 0;
+       struct cv_lookup_elm cle;
+       /* Do a quick check before registering and sleeping.  this is the 'check,
+        * signal, check again' pattern, where the first check is an optimization.
+        * Many rendezes will already be satisfied, so we want to avoid excessive
+        * locking associated with reg/dereg. */
        cv_lock_irqsave(&rv->cv, &irq_state);
+       if (cond(arg)) {
+               cv_unlock_irqsave(&rv->cv, &irq_state);
+               return;
+       }
+       __reg_abortable_cv(&cle, &rv->cv);
        /* Mesa-style semantics, which is definitely what you want.  See the
         * discussion at the end of the URL above. */
        while (!cond(arg)) {
+               /* it's okay if we miss the ABORT flag; we hold the cv lock, so an
+                * aborter's broadcast is waiting until we unlock. */
+               if (should_abort(&cle)) {
+                       cv_unlock_irqsave(&rv->cv, &irq_state);
+                       dereg_abortable_cv(&cle);
+                       error("syscall aborted");
+               }
                cv_wait(&rv->cv);
                cpu_relax();
        }
        cv_unlock_irqsave(&rv->cv, &irq_state);
+       dereg_abortable_cv(&cle);
 }
 
 /* Force a wakeup of all waiters on the rv, including non-timeout users.  For
@@ -45,28 +64,45 @@ void rendez_sleep_timeout(struct rendez *rv, int (*cond)(void*), void *arg,
 {
        int8_t irq_state = 0;
        struct alarm_waiter awaiter;
+       struct cv_lookup_elm cle;
        struct timer_chain *pcpui_tchain = &per_cpu_info[core_id()].tchain;
 
        assert((int)msec > 0);
+       /* Doing this cond check early, but then unlocking again.  Mostly just to
+        * avoid weird issues with the CV lock and the alarm tchain lock. */
+       cv_lock_irqsave(&rv->cv, &irq_state);
+       if (cond(arg)) {
+               cv_unlock_irqsave(&rv->cv, &irq_state);
+               return;
+       }
+       cv_unlock_irqsave(&rv->cv, &irq_state);
        /* The handler will call rendez_wake, but won't mess with the condition
         * state.  It's enough to break us out of cv_wait() to see .on_tchain. */
        init_awaiter(&awaiter, rendez_alarm_handler);
        awaiter.data = rv;
-       set_awaiter_rel(&awaiter, msec);
+       set_awaiter_rel(&awaiter, msec * 1000);
        /* Set our alarm on this cpu's tchain.  Note that when we sleep in cv_wait,
         * we could be migrated, and later on we could be unsetting the alarm
         * remotely. */
        set_alarm(pcpui_tchain, &awaiter);
        cv_lock_irqsave(&rv->cv, &irq_state);
+       __reg_abortable_cv(&cle, &rv->cv);
        /* We could wake early for a few reasons.  Legit wakeups after a changed
         * condition (and we should exit), other alarms with different timeouts (and
         * we should go back to sleep), etc.  Note it is possible for our alarm to
         * fire immediately upon setting it: before we even cv_lock. */
        while (!cond(arg) && awaiter.on_tchain) {
+               if (should_abort(&cle)) {
+                       cv_unlock_irqsave(&rv->cv, &irq_state);
+                       unset_alarm(pcpui_tchain, &awaiter);
+                       dereg_abortable_cv(&cle);
+                       error("syscall aborted");
+               }
                cv_wait(&rv->cv);
                cpu_relax();
        }
        cv_unlock_irqsave(&rv->cv, &irq_state);
+       dereg_abortable_cv(&cle);
        /* Turn off our alarm.  If it already fired, this is a no-op.  Note this
         * could be cross-core. */
        unset_alarm(pcpui_tchain, &awaiter);
index 2062a03..0aadb69 100644 (file)
@@ -991,6 +991,11 @@ out:
        return retval;
 }
 
+static int sys_abort_sysc(struct proc *p, struct syscall *sysc)
+{
+       return abort_sysc(p, sysc);
+}
+
 /************** Platform Specific Syscalls **************/
 
 //Read a buffer over the serial port
@@ -1703,6 +1708,7 @@ const static struct sys_table_entry syscall_table[] = {
 #endif
        [SYS_change_to_m] = {(syscall_t)sys_change_to_m, "change_to_m"},
        [SYS_poke_ksched] = {(syscall_t)sys_poke_ksched, "poke_ksched"},
+       [SYS_abort_sysc] = {(syscall_t)sys_abort_sysc, "abort_sysc"},
 
 // socket related syscalls
        [SYS_socket] ={(syscall_t)sys_socket, "socket"},
index d25b356..67266d6 100644 (file)
@@ -223,6 +223,6 @@ void print_kctx_depths(const char *str)
        
        if (!str)
                str = "(none)";
-       printk("%s: Core %d, irq depth %d, ktrap depth %d\n", str, coreid,
-              irq_depth(pcpui), ktrap_depth(pcpui));
+       printk("%s: Core %d, irq depth %d, ktrap depth %d, irqon %d\n", str, coreid,
+              irq_depth(pcpui), ktrap_depth(pcpui), irq_is_enabled());
 }
index c906b99..e94f023 100644 (file)
@@ -57,6 +57,7 @@ int         sys_block(unsigned int usec);
 int         sys_change_vcore(uint32_t vcoreid, bool enable_my_notif);
 int         sys_change_to_m(void);
 int         sys_poke_ksched(int pid, unsigned int res_type);
+int         sys_abort_sysc(struct syscall *sysc);
 
 void           init_posix_signals(void);       /* in signal.c */
 #ifdef __cplusplus
index f13f872..145094c 100644 (file)
@@ -191,3 +191,8 @@ int sys_poke_ksched(int pid, unsigned int res_type)
 {
        return ros_syscall(SYS_poke_ksched, pid, res_type, 0, 0, 0, 0);
 }
+
+int sys_abort_sysc(struct syscall *sysc)
+{
+       return ros_syscall(SYS_abort_sysc, sysc, 0, 0, 0, 0, 0);
+}
index c3b85de..44093b9 100644 (file)
@@ -416,7 +416,7 @@ void __ros_mcp_syscall_blockon(struct syscall *sysc)
        /* double check before doing all this crap */
        if (atomic_read(&sysc->flags) & (SC_DONE | SC_PROGRESS))
                return;
-       /* Debugging: so we can match sysc when it tries to wake us up later */
+       /* for both debugging and syscall cancelling */
        current_uthread->sysc = sysc;
        /* yield, calling 2ls-blockon(cur_uth, sysc) on the other side */
        uthread_yield(TRUE, sched_ops->thread_blockon_sysc, sysc);