uth: add got_posix_signal() to the 2LS ops
authorBarret Rhoden <brho@cs.berkeley.edu>
Thu, 13 Jun 2019 20:32:41 +0000 (16:32 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 11 Jul 2019 18:29:21 +0000 (14:29 -0400)
How we handle a POSIX signal is very 2LS-specific.  Thread0 can easily
abort syscalls for its only thread.  Pthreads can call pthread_kill.
The VMM-2LS can route signals to specific task threads.

This commit moves the default logic into 2LS-specific ops.  VMM and
pthreads keep the old behavior.  Thread0 gets behavior more similar to
what the shells want: interrupt their syscall.  It also doesn't need to
use a fake context.

To some extent, the 2LS just needs to pick a thread, and then we
trigger_posix_signal() and/or abort the syscall.  Whether or not those
all need to happen, or whether we need to provide a context to the
threads, is currently 2LS dependent.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
user/parlib/include/parlib/uthread.h
user/parlib/signal.c
user/parlib/thread0_sched.c
user/pthread/pthread.c
user/vmm/sched.c

index c0e5c9f..534fa83 100644 (file)
@@ -111,6 +111,7 @@ struct schedule_ops {
        void (*thread_refl_fault)(struct uthread *, struct user_context *);
        void (*thread_exited)(struct uthread *);
        struct uthread *(*thread_create)(void *(*)(void *), void *);
+       void (*got_posix_signal)(int sig_nr, struct siginfo *info);
        /**** Defining these functions is optional. ****/
        void (*sync_init)(uth_sync_t *);
        void (*sync_destroy)(uth_sync_t *);
index 2918903..0ec27d6 100644 (file)
@@ -84,23 +84,24 @@ static void handle_event(struct event_msg *ev_msg, unsigned int ev_type,
        int sig_nr;
        struct siginfo info = {0};
        info.si_code = SI_USER;
-       struct user_context fake_uctx;
 
        assert(ev_msg);
        sig_nr = ev_msg->ev_arg1;
-       /* We're handling a process-wide signal, but signal handlers will want a
-        * user context.  They operate on the model that some thread got the
-        * signal, but that didn't happen on Akaros.  If we happen to have a
-        * current uthread, we can use that - perhaps that's what the user
-        * wants.  If not, we'll build a fake one representing our current call
-        * stack. */
-       if (current_uthread) {
-               trigger_posix_signal(sig_nr, &info, get_cur_uth_ctx());
-       } else {
-               init_user_ctx(&fake_uctx, (uintptr_t)handle_event,
-                             get_stack_pointer());
-               trigger_posix_signal(sig_nr, &info, &fake_uctx);
-       }
+       /* These POSIX signals are process-wide, but legacy applications and
+        * their signal handlers often expect the signals to be routed to
+        * particular threads.  This manifests in a couple ways: the signal
+        * handlers expect a user context, and the program expects syscalls to
+        * be interrupted.  Which context?  Which syscall?
+        *
+        * On Akaros, signals only go to the process, since there is no kernel
+        * notion of a thread/task within a process.  All knowledge of
+        * threads and how to resolve this mismatch between process-wide signals
+        * and threads is held in the 2LS.  If we wanted to abort a syscall,
+        * we'd need to know which one - after all, on Akaros syscalls are
+        * asynchronous and it is only in the 2LS that they are coupled to
+        * uthreads.  When it comes to routing the signal, the 2LS could do
+        * something like pthread_kill, or just execute the handler. */
+       sched_ops->got_posix_signal(sig_nr, &info);
 }
 
 /* Called from uthread_slim_init() */
index 51399cf..62a7f6f 100644 (file)
@@ -27,6 +27,7 @@ static void thread0_thread_runnable(struct uthread *uth);
 static void thread0_thread_has_blocked(struct uthread *uth, int flags);
 static void thread0_thread_exited(struct uthread *uth);
 static struct uthread *thread0_thread_create(void *(*func)(void *), void *arg);
+static void thread0_got_posix_signal(int sig_nr, struct siginfo *info);
 static void thread0_sync_init(uth_sync_t *s);
 static void thread0_sync_destroy(uth_sync_t *s);
 static void thread0_sync_enqueue(struct uthread *uth, uth_sync_t *s);
@@ -46,6 +47,7 @@ struct schedule_ops thread0_2ls_ops = {
        .thread_has_blocked = thread0_thread_has_blocked,
        .thread_exited = thread0_thread_exited,
        .thread_create = thread0_thread_create,
+       .got_posix_signal = thread0_got_posix_signal,
        .sync_init = thread0_sync_init,
        .sync_destroy = thread0_sync_destroy,
        .sync_enqueue = thread0_sync_enqueue,
@@ -124,6 +126,7 @@ static void thread0_sched_entry(void)
 static void thread0_thread_blockon_sysc(struct uthread *uthread, void *arg)
 {
        struct syscall *sysc = (struct syscall*)arg;
+
        thread0_thread_has_blocked(uthread, 0);
        if (!register_evq(sysc, sysc_evq))
                thread0_thread_runnable(uthread);
@@ -192,6 +195,21 @@ static struct uthread *thread0_thread_create(void *(*func)(void *), void *arg)
        panic("Thread0 sched asked to create more threads!");
 }
 
+static void thread0_got_posix_signal(int sig_nr, struct siginfo *info)
+{
+       if (current_uthread)
+               trigger_posix_signal(sig_nr, info, get_cur_uth_ctx());
+       else
+               trigger_posix_signal(sig_nr, info, &thread0_uth->u_ctx);
+       /* Legacy single-threaded programs, which often use thread0, expect
+        * signals to interrupt their syscall.  For most 2LSes, we can't match a
+        * process-wide signal to a particular thread; the kernel knows nothing
+        * of threads, we're just receiving an event.  However, thread0 has only
+        * one thread. */
+       if (thread0_uth->sysc)
+               sys_abort_sysc(thread0_uth->sysc);
+}
+
 static void thread0_sync_init(uth_sync_t *s)
 {
        memset(s, 0x5a, sizeof(uth_sync_t));
index cefb965..1a4d3f8 100644 (file)
@@ -64,6 +64,7 @@ static void pth_thread_refl_fault(struct uthread *uth,
                                   struct user_context *ctx);
 static void pth_thread_exited(struct uthread *uth);
 static struct uthread *pth_thread_create(void *(*func)(void *), void *arg);
+static void pth_got_posix_signal(int sig_nr, struct siginfo *info);
 static void pth_thread_bulk_runnable(uth_sync_t *wakees);
 
 /* Event Handlers */
@@ -80,6 +81,7 @@ struct schedule_ops pthread_sched_ops = {
        .thread_refl_fault = pth_thread_refl_fault,
        .thread_exited = pth_thread_exited,
        .thread_create = pth_thread_create,
+       .got_posix_signal = pth_got_posix_signal,
        .thread_bulk_runnable = pth_thread_bulk_runnable,
 };
 
@@ -481,6 +483,24 @@ static struct uthread *pth_thread_create(void *(*func)(void *), void *arg)
        return ret == 0 ? (struct uthread*)pth : NULL;
 }
 
+/* Careful, that fake_uctx takes up a lot of stack space.  We could call
+ * pthread_kill too.  Note the VMM 2LS has similar code. */
+static void pth_got_posix_signal(int sig_nr, struct siginfo *info)
+{
+       struct user_context fake_uctx;
+
+       /* If we happen to have a current uthread, we can use that - perhaps
+        * that's what the user wants.  If not, we'll build a fake one
+        * representing our current call stack. */
+       if (current_uthread) {
+               trigger_posix_signal(sig_nr, info, get_cur_uth_ctx());
+       } else {
+               init_user_ctx(&fake_uctx, (uintptr_t)pth_got_posix_signal,
+                             get_stack_pointer());
+               trigger_posix_signal(sig_nr, info, &fake_uctx);
+       }
+}
+
 static void pth_thread_bulk_runnable(uth_sync_t *wakees)
 {
        struct uthread *uth_i;
index c91753b..fc531ec 100644 (file)
@@ -47,6 +47,7 @@ static void vmm_thread_refl_fault(struct uthread *uth,
                                   struct user_context *ctx);
 static void vmm_thread_exited(struct uthread *uth);
 static struct uthread *vmm_thread_create(void *(*func)(void *), void *arg);
+static void vmm_got_posix_signal(int sig_nr, struct siginfo *info);
 
 struct schedule_ops vmm_sched_ops = {
        .sched_init = vmm_sched_init,
@@ -58,6 +59,7 @@ struct schedule_ops vmm_sched_ops = {
        .thread_refl_fault = vmm_thread_refl_fault,
        .thread_exited = vmm_thread_exited,
        .thread_create = vmm_thread_create,
+       .got_posix_signal = vmm_got_posix_signal,
 };
 
 struct schedule_ops *sched_ops = &vmm_sched_ops;
@@ -717,6 +719,26 @@ static struct uthread *vmm_thread_create(void *(*func)(void *), void *arg)
        return (struct uthread*)tth;
 }
 
+/* Careful, that fake_uctx takes up a lot of stack space.  We could do something
+ * to route signals to task threads too.  The VMM-2LS has less need for this at
+ * all, so we could just run the signal handler as-is, without worrying about
+ * user contexts.  Note the pthread 2LS has similar code. */
+static void vmm_got_posix_signal(int sig_nr, struct siginfo *info)
+{
+       struct user_context fake_uctx;
+
+       /* If we happen to have a current uthread, we can use that - perhaps
+        * that's what the user wants.  If not, we'll build a fake one
+        * representing our current call stack. */
+       if (current_uthread) {
+               trigger_posix_signal(sig_nr, info, get_cur_uth_ctx());
+       } else {
+               init_user_ctx(&fake_uctx, (uintptr_t)vmm_got_posix_signal,
+                             get_stack_pointer());
+               trigger_posix_signal(sig_nr, info, &fake_uctx);
+       }
+}
+
 /* Helpers for tracking nr_unblk_* threads. */
 static void acct_thread_blocked(struct vmm_thread *vth)
 {