Fixes up syscall_blockon functions (XCC)
authorBarret Rhoden <brho@cs.berkeley.edu>
Mon, 13 Jul 2015 18:19:41 +0000 (14:19 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 16 Jul 2015 20:07:52 +0000 (16:07 -0400)
Our old blockon functions had three problems:

- MCP blockon was assuming MCP == 2LS.

- SCPs in VC context that blocked would clear notif disabled!  The
  danger here is that the vcore's stack would get clobbered.  To have
this happen, you'd need to have an SCP that dropped into vcore context
and issued a blocking syscall, and then receive a notif.

- Excessive SCP notifies, due to using the early blockon in glibc.  For
  instance, any SCP that had a blocking syscall would issue the call, a
yield, and then a self-notify.  The notify was due to us manually
disabling/enabling notifs.

This commit fixes the old early blockon (was scp_blockon) and only uses
it for early SCPs (pre-vcore context).  It updates the uthread blockon
(was mcp_blockon) to support *all* SCPs, with or without 2LSs, and MCPs.

Note that the early blockon is basically like vcore context, and it is
used for vcore contexts that issued blocking syscalls for SCPs in any
state (2LS or not).  Likewise, before parlib initializes, the kernel
knows the process can't handle vcore context, and handles it
accordingly.

Rebuild glibc.

tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/Versions
tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/sys/syscall.h
tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/syscall.c
user/parlib/thread0_sched.c
user/parlib/uthread.c

index b71ebc7..3fba033 100644 (file)
@@ -9,7 +9,7 @@ libc {
     werrstr;
     ros_syscall_blockon;
     ros_syscall_sync;
-    __ros_scp_syscall_blockon;
+    __ros_early_syscall_blockon;
     __ros_scp_simple_evq;
     __ros_syscall_noerrno;
     __ros_syscall_errno;
index 8f6a137..0049a2e 100644 (file)
@@ -53,7 +53,7 @@ extern void (*ros_syscall_blockon)(struct syscall *sysc);
 
 /* Glibc initial blockon, usable before parlib code can init things (or if it
  * never can, like for RTLD).  MCPs will need the 'uthread-aware' blockon. */
-void __ros_scp_syscall_blockon(struct syscall *sysc);
+void __ros_early_syscall_blockon(struct syscall *sysc);
 
 #ifdef __cplusplus
 }
index 01e915d..ab49fc1 100644 (file)
@@ -62,31 +62,39 @@ static bool register_evq(struct syscall *sysc, struct event_queue *ev_q)
 }
 
 /* Glibc initial blockon, usable before parlib code can init things (or if it
- * never can, like for RTLD).  MCPs will need the 'uthread-aware' blockon. */
-void __ros_scp_syscall_blockon(struct syscall *sysc)
+ * never can, like for RTLD).  As processes initialize further, they will use
+ * different functions.
+ *
+ * In essence, we're in vcore context already.  For one, this function could be
+ * called from a full SCP in vcore context.  For early processes, we are not
+ * vcctx_ready.  Either way, we don't need to worry about the kernel forcing us
+ * into vcore context and otherwise clearing notif_pending.  For those curious,
+ * the old race was that the kernel sets notif pending after we register, then
+ * we drop into VC ctx, clear notif pending, and yield. */
+void __ros_early_syscall_blockon(struct syscall *sysc)
 {
-       /* Need to disable notifs before registering, so we don't take an __notify
-        * that drops us into VC ctx and forces us to eat the notif_pending that was
-        * meant to prevent us from yielding if the syscall completed early. */
-       __procdata.vcore_preempt_data[0].notif_disabled = TRUE;
+       /* For early SCPs, notif_pending will probably be false anyways.  For SCPs
+        * in VC ctx, it might be set.  Regardless, when we pop back up,
+        * notif_pending will be set (for a full SCP in VC ctx). */
+       __procdata.vcore_preempt_data[0].notif_pending = FALSE;
+       /* order register after clearing notif_pending, handled by register_evq */
        /* Ask for a SYSCALL event when the sysc is done.  We don't need a handler,
         * we just need the kernel to restart us from proc_yield.  If register
         * fails, we're already done. */
        if (register_evq(sysc, &__ros_scp_simple_evq)) {
                /* Sending false for now - we want to signal proc code that we want to
-                * wait (piggybacking on the MCP meaning of this variable) */
+                * wait (piggybacking on the MCP meaning of this variable).  If
+                * notif_pending is set, the kernel will immediately return us. */
                __ros_syscall_noerrno(SYS_yield, FALSE, 0, 0, 0, 0, 0);
        }
-       /* Manually doing an enable_notifs for VC 0 */
-       __procdata.vcore_preempt_data[0].notif_disabled = FALSE;
-       wrmb(); /* need to read after the write that enabled notifs */
-       if (__procdata.vcore_preempt_data[0].notif_pending)
-               __ros_syscall_noerrno(SYS_self_notify, 0, EV_NONE, 0, TRUE, 0, 0);
+       /* For early SCPs, the kernel turns off notif_pending for us.  For SCPs in
+        * vcore context that blocked (should be rare!), it'll still be set.  Other
+        * VC ctx code must handle it later. (could have coalesced notifs) */
 }
 
 /* Function pointer for the blockon function.  MCPs need to switch to the parlib
  * blockon before becoming an MCP.  Default is the glibc SCP handler */
-void (*ros_syscall_blockon)(struct syscall *sysc) = __ros_scp_syscall_blockon;
+void (*ros_syscall_blockon)(struct syscall *sysc) = __ros_early_syscall_blockon;
 
 /* Issue a single syscall and block into the 2LS until it completes */
 static inline void __ros_syscall_sync(struct syscall *sysc)
index f5c838d..587b1e6 100644 (file)
 #include <stdlib.h>
 
 static void thread0_sched_entry(void);
+static void thread0_thread_blockon_sysc(struct uthread *uthread, void *sysc);
 
 /* externed into uthread.c */
 struct schedule_ops thread0_2ls_ops = {
        .sched_entry = thread0_sched_entry,
+       .thread_blockon_sysc = thread0_thread_blockon_sysc,
 };
 
 /* externed into uthread.c */
@@ -33,3 +35,32 @@ static void thread0_sched_entry(void)
        else
                run_uthread(thread0_uth);
 }
+
+static void thread0_thread_blockon_sysc(struct uthread *uthread, void *arg)
+{
+       struct syscall *sysc = (struct syscall*)arg;
+       /* We're in vcore context.  Regardless of what we do here, we'll pop back in
+        * to vcore entry, just like with any uthread_yield.  We don't have a 2LS,
+        * but we always have one uthread: the SCP's thread0.  Note that at this
+        * point, current_uthread is still set, but will be cleared as soon as the
+        * callback returns (and before we start over in vcore_entry).
+        *
+        * If notif_pending is already set (due to a concurrent signal), we'll fail
+        * to yield.  Once in VC ctx, we'll handle any other signals/events that
+        * arrived, then restart the uthread that issued the syscall, which if the
+        * syscall isn't done yet, will just blockon again.
+        *
+        * The one trick is that we don't want to register the evq twice.  The way
+        * register_evq currently works, if a SC completed (SC_DONE) while we were
+        * registering, we could end up clearing sysc->ev_q before the kernel sees
+        * it.  We'll use u_data to track whether we registered or not. */
+       #define U_DATA_BLOB ((void*)0x55555555)
+       if ((sysc->u_data == U_DATA_BLOB)
+           || register_evq(sysc, &__ros_scp_simple_evq)) {
+               sysc->u_data = U_DATA_BLOB;
+               /* Sending false for now - we want to signal proc code that we want to
+                * wait (piggybacking on the MCP meaning of this variable).  If
+                * notif_pending is set, the kernel will immediately return us. */
+               __ros_syscall_noerrno(SYS_yield, FALSE, 0, 0, 0, 0, 0);
+       }
+}
index be6f896..a7745f4 100644 (file)
@@ -32,9 +32,7 @@ static void handle_vc_preempt(struct event_msg *ev_msg, unsigned int ev_type,
                               void *data);
 static void handle_vc_indir(struct event_msg *ev_msg, unsigned int ev_type,
                             void *data);
-
-/* Block the calling uthread on sysc until it makes progress or is done */
-static void __ros_mcp_syscall_blockon(struct syscall *sysc);
+static void __ros_uth_syscall_blockon(struct syscall *sysc);
 
 /* Helper, make the uthread code manage thread0.  This sets up uthread such
  * that the calling code and its TLS are tracked by the uthread struct, and
@@ -184,12 +182,13 @@ void __attribute__((constructor)) uthread_lib_init(void)
        memset(thread0_uth, 0, sizeof(struct uthread)); /* aggressively 0 for bugs*/
        uthread_manage_thread0(thread0_uth);
        scp_vcctx_ready();
+       /* Change our blockon from glibc's internal one to the regular one, which
+        * uses vcore context and works for SCPs (with or without 2LS) and MCPs.
+        * Once we tell the kernel we are ready to utilize vcore context, we need
+        * our blocking syscalls to utilize it as well. */
+       ros_syscall_blockon = __ros_uth_syscall_blockon;
+       cmb();
        init_posix_signals();
-       /* change our blockon from glibc's internal one to the mcp one (which can
-        * handle SCPs too).  we must do this before switching to _M, or at least
-        * before blocking while an _M.  it's harmless (and probably saner) to do it
-        * earlier, so we do it as early as possible. */
-       ros_syscall_blockon = __ros_mcp_syscall_blockon;
        /* Switch our errno/errstr functions to be uthread-aware.  See glibc's
         * errno.c for more info. */
        ros_errno_loc = __ros_errno_loc;
@@ -440,34 +439,34 @@ static void __ros_syscall_spinon(struct syscall *sysc)
                cpu_relax();
 }
 
-/* Attempts to block on sysc, returning when it is done or progress has been
- * made. */
-void __ros_mcp_syscall_blockon(struct syscall *sysc)
+static void __ros_vcore_ctx_syscall_blockon(struct syscall *sysc)
 {
-       /* even if we are in 'vcore context', an _S can block */
-       if (!in_multi_mode()) {
-               /* the SCP could have an alarm set to abort this sysc.  When we have a
-                * uth blocked on a sysc, we want this pointer set up (like we do below
-                * for MCP)s */
-               current_uthread->sysc = sysc;
-               __ros_scp_syscall_blockon(sysc);
-               current_uthread->sysc = 0;
-               return;
+       if (in_multi_mode()) {
+               /* MCP vcore's don't know what to do yet, so we have to spin */
+               __ros_syscall_spinon(sysc);
+       } else {
+               /* SCPs can use the early blockon, which acts like VC ctx. */
+               __ros_early_syscall_blockon(sysc);
        }
-       /* MCP vcore's don't know what to do yet, so we have to spin */
+}
+
+/* Attempts to block on sysc, returning when it is done or progress has been
+ * made.  Made for initialized processes using uthreads. */
+static void __ros_uth_syscall_blockon(struct syscall *sysc)
+{
        if (in_vcore_context()) {
-               __ros_syscall_spinon(sysc);
+               __ros_vcore_ctx_syscall_blockon(sysc);
                return;
        }
-       /* At this point, we know we're a uthread in an MCP.  If we're a
-        * DONT_MIGRATE uthread, then it's disabled notifs and is basically in
-        * vcore context, enough so that it can't call into the 2LS. */
+       /* At this point, we know we're a uthread.  If we're a DONT_MIGRATE uthread,
+        * then it's disabled notifs and is basically in vcore context, enough so
+        * that it can't call into the 2LS. */
        assert(current_uthread);
        if (current_uthread->flags & UTHREAD_DONT_MIGRATE) {
                assert(!notif_is_enabled(vcore_id()));  /* catch bugs */
                /* if we had a notif_disabled_depth, then we should also have
                 * DONT_MIGRATE set */
-               __ros_syscall_spinon(sysc);
+               __ros_vcore_ctx_syscall_blockon(sysc);
                return;
        }
        assert(!current_uthread->notif_disabled_depth);