Add a few more pthread compilant definitions
[akaros.git] / user / pthread / pthread.c
index bec5098..af7a5fc 100644 (file)
@@ -1,3 +1,6 @@
+// Needed for sigmask functions...
+#define _GNU_SOURCE
+
 #include <ros/trapframe.h>
 #include <pthread.h>
 #include <vcore.h>
 #include <ros/trapframe.h>
 #include <pthread.h>
 #include <vcore.h>
@@ -21,6 +24,7 @@ struct pthread_queue active_queue = TAILQ_HEAD_INITIALIZER(active_queue);
 struct mcs_pdr_lock queue_lock;
 int threads_ready = 0;
 int threads_active = 0;
 struct mcs_pdr_lock queue_lock;
 int threads_ready = 0;
 int threads_active = 0;
+atomic_t threads_total;
 bool can_adjust_vcores = TRUE;
 bool need_tls = TRUE;
 
 bool can_adjust_vcores = TRUE;
 bool need_tls = TRUE;
 
@@ -38,11 +42,14 @@ void pth_thread_runnable(struct uthread *uthread);
 void pth_thread_paused(struct uthread *uthread);
 void pth_thread_blockon_sysc(struct uthread *uthread, void *sysc);
 void pth_thread_has_blocked(struct uthread *uthread, int flags);
 void pth_thread_paused(struct uthread *uthread);
 void pth_thread_blockon_sysc(struct uthread *uthread, void *sysc);
 void pth_thread_has_blocked(struct uthread *uthread, int flags);
+void pth_thread_refl_fault(struct uthread *uthread, unsigned int trap_nr,
+                           unsigned int err, unsigned long aux);
 void pth_preempt_pending(void);
 void pth_spawn_thread(uintptr_t pc_start, void *data);
 
 /* Event Handlers */
 void pth_preempt_pending(void);
 void pth_spawn_thread(uintptr_t pc_start, void *data);
 
 /* Event Handlers */
-static void pth_handle_syscall(struct event_msg *ev_msg, unsigned int ev_type);
+static void pth_handle_syscall(struct event_msg *ev_msg, unsigned int ev_type,
+                               void *data);
 
 struct schedule_ops pthread_sched_ops = {
        pth_sched_entry,
 
 struct schedule_ops pthread_sched_ops = {
        pth_sched_entry,
@@ -50,6 +57,7 @@ struct schedule_ops pthread_sched_ops = {
        pth_thread_paused,
        pth_thread_blockon_sysc,
        pth_thread_has_blocked,
        pth_thread_paused,
        pth_thread_blockon_sysc,
        pth_thread_has_blocked,
+       pth_thread_refl_fault,
        0, /* pth_preempt_pending, */
        0, /* pth_spawn_thread, */
 };
        0, /* pth_preempt_pending, */
        0, /* pth_spawn_thread, */
 };
@@ -60,6 +68,135 @@ struct schedule_ops *sched_ops = &pthread_sched_ops;
 /* Static helpers */
 static void __pthread_free_stack(struct pthread_tcb *pt);
 static int __pthread_allocate_stack(struct pthread_tcb *pt);
 /* Static helpers */
 static void __pthread_free_stack(struct pthread_tcb *pt);
 static int __pthread_allocate_stack(struct pthread_tcb *pt);
+static void __pth_yield_cb(struct uthread *uthread, void *junk);
+
+/* Swap the contents of two user contexts (not just their pointers). */
+static void swap_user_contexts(struct user_context *c1, struct user_context *c2)
+{
+       struct user_context temp_ctx;
+       temp_ctx = *c1;
+       *c1 = *c2;
+       *c2 = temp_ctx;
+}
+
+/* Prep a pthread to run a signal handler.  The original context of the pthread
+ * is saved, and a new context with a new stack is set up to run the signal
+ * handler the next time the pthread is run. */
+static void __pthread_prep_sighandler(struct pthread_tcb *pthread,
+                                      void (*entry)(void),
+                                      struct siginfo *info)
+{
+       struct user_context *ctx;
+
+       pthread->sigdata = alloc_sigdata();
+       if (info != NULL)
+               pthread->sigdata->info = *info;
+       init_user_ctx(&pthread->sigdata->u_ctx,
+                     (uintptr_t)entry,
+                     (uintptr_t)pthread->sigdata->stack);
+       if (pthread->uthread.flags & UTHREAD_SAVED) {
+               ctx = &pthread->uthread.u_ctx;
+               if (pthread->uthread.flags & UTHREAD_FPSAVED) {
+                       pthread->sigdata->as = pthread->uthread.as;
+                       pthread->uthread.flags &= ~UTHREAD_FPSAVED;
+               }
+       } else {
+               assert(current_uthread == &pthread->uthread);
+               ctx = &vcpd_of(vcore_id())->uthread_ctx;
+               save_fp_state(&pthread->sigdata->as);
+       }
+       swap_user_contexts(ctx, &pthread->sigdata->u_ctx);
+}
+
+/* Restore the context saved as the result of running a signal handler on a
+ * pthread. This context will execute the next time the pthread is run. */
+static void __pthread_restore_after_sighandler(struct pthread_tcb *pthread)
+{
+       pthread->uthread.u_ctx = pthread->sigdata->u_ctx;
+       pthread->uthread.flags |= UTHREAD_SAVED;
+       if (pthread->uthread.u_ctx.type == ROS_HW_CTX) {
+               pthread->uthread.as = pthread->sigdata->as;
+               pthread->uthread.flags |= UTHREAD_FPSAVED;
+       }
+       free_sigdata(pthread->sigdata);
+       pthread->sigdata = NULL;
+}
+
+/* Callback when yielding a pthread after upon completion of a sighandler.  We
+ * didn't save the current context on yeild, but that's ok because here we
+ * restore the original saved context of the pthread and then treat this like a
+ * normal voluntary yield. */
+static void __exit_sighandler_cb(struct uthread *uthread, void *junk)
+{
+       __pthread_restore_after_sighandler((struct pthread_tcb*)uthread);
+       __pth_yield_cb(uthread, 0);
+}
+
+/* Run a specific sighandler from the top of the sigdata stack. The 'info'
+ * struct is prepopulated before the call is triggered as the result of a
+ * reflected fault. */
+static void __run_sighandler()
+{
+       struct pthread_tcb *me = pthread_self();
+       __sigdelset(&me->sigpending, me->sigdata->info.si_signo);
+       trigger_posix_signal(me->sigdata->info.si_signo,
+                            &me->sigdata->info,
+                            &me->sigdata->u_ctx);
+       uthread_yield(FALSE, __exit_sighandler_cb, 0);
+}
+
+/* Run through all pending sighandlers and trigger them with a NULL info field.
+ * These handlers are triggered as the result of a pthread_kill(), and thus
+ * don't require individual 'info' structs. */
+static void __run_pending_sighandlers()
+{
+       struct pthread_tcb *me = pthread_self();
+       sigset_t andset = me->sigpending & (~me->sigmask);
+       for (int i = 1; i < _NSIG; i++) {
+               if (__sigismember(&andset, i)) {
+                       __sigdelset(&me->sigpending, i);
+                       trigger_posix_signal(i, NULL, &me->sigdata->u_ctx);
+               }
+       }
+       uthread_yield(FALSE, __exit_sighandler_cb, 0);
+}
+
+/* If the given signal is unmasked, prep the pthread to run it's signal
+ * handler, but don't run it yet. In either case, make the pthread runnable
+ * again. Once the signal handler is complete, the original context will be
+ * restored and restarted. */
+static void __pthread_signal_and_restart(struct pthread_tcb *pthread,
+                                          int signo, int code, void *addr)
+{
+       if (!__sigismember(&pthread->sigmask, signo)) {
+               if (pthread->sigdata) {
+                       printf("Pthread sighandler faulted, signal: %d\n", signo);
+                       /* uthread.c already copied out the faulting ctx into the uth */
+                       print_user_context(&pthread->uthread.u_ctx);
+                       exit(-1);
+               }
+               struct siginfo info = {0};
+               info.si_signo = signo;
+               info.si_code = code;
+               info.si_addr = addr;
+               __pthread_prep_sighandler(pthread, __run_sighandler, &info);
+       }
+       pth_thread_runnable(&pthread->uthread);
+}
+
+/* If there are any pending signals, prep the pthread to run it's signal
+ * handler. The next time the pthread is run, it will pop into it's signal
+ * handler context instead of its original saved context. Once the signal
+ * handler is complete, the original context will be restored and restarted. */
+static void __pthread_prep_for_pending_posix_signals(pthread_t pthread)
+{
+       if (!pthread->sigdata && pthread->sigpending) {
+               sigset_t andset = pthread->sigpending & (~pthread->sigmask);
+               if (!__sigisemptyset(&andset)) {
+                       __pthread_prep_sighandler(pthread, __run_pending_sighandlers, NULL);
+               }
+       }
+}
 
 /* Called from vcore entry.  Options usually include restarting whoever was
  * running there before or running a new thread.  Events are handled out of
 
 /* Called from vcore entry.  Options usually include restarting whoever was
  * running there before or running a new thread.  Events are handled out of
@@ -68,6 +205,10 @@ void __attribute__((noreturn)) pth_sched_entry(void)
 {
        uint32_t vcoreid = vcore_id();
        if (current_uthread) {
 {
        uint32_t vcoreid = vcore_id();
        if (current_uthread) {
+               /* Prep the pthread to run any pending posix signal handlers registered
+         * via pthread_kill once it is restored. */
+               __pthread_prep_for_pending_posix_signals((pthread_t)current_uthread);
+               /* Run the thread itself */
                run_current_uthread();
                assert(0);
        }
                run_current_uthread();
                assert(0);
        }
@@ -104,6 +245,10 @@ void __attribute__((noreturn)) pth_sched_entry(void)
                        vcore_yield(FALSE);
        } while (1);
        assert(new_thread->state == PTH_RUNNABLE);
                        vcore_yield(FALSE);
        } while (1);
        assert(new_thread->state == PTH_RUNNABLE);
+       /* Prep the pthread to run any pending posix signal handlers registered
+     * via pthread_kill once it is restored. */
+       __pthread_prep_for_pending_posix_signals(new_thread);
+       /* Run the thread itself */
        run_uthread((struct uthread*)new_thread);
        assert(0);
 }
        run_uthread((struct uthread*)new_thread);
        assert(0);
 }
@@ -194,7 +339,8 @@ static void restart_thread(struct syscall *sysc)
 
 /* This handler is usually run in vcore context, though I can imagine it being
  * called by a uthread in some other threading library. */
 
 /* This handler is usually run in vcore context, though I can imagine it being
  * called by a uthread in some other threading library. */
-static void pth_handle_syscall(struct event_msg *ev_msg, unsigned int ev_type)
+static void pth_handle_syscall(struct event_msg *ev_msg, unsigned int ev_type,
+                               void *data)
 {
        struct syscall *sysc;
        assert(in_vcore_context());
 {
        struct syscall *sysc;
        assert(in_vcore_context());
@@ -222,7 +368,6 @@ void pth_thread_blockon_sysc(struct uthread *uthread, void *syscall)
 {
        struct syscall *sysc = (struct syscall*)syscall;
        int old_flags;
 {
        struct syscall *sysc = (struct syscall*)syscall;
        int old_flags;
-       bool need_to_restart = FALSE;
        uint32_t vcoreid = vcore_id();
        /* rip from the active queue */
        struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
        uint32_t vcoreid = vcore_id();
        /* rip from the active queue */
        struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
@@ -255,6 +400,77 @@ void pth_thread_has_blocked(struct uthread *uthread, int flags)
                printf("For great justice!\n");
 }
 
                printf("For great justice!\n");
 }
 
+static void handle_div_by_zero(struct uthread *uthread, unsigned int err,
+                               unsigned long aux)
+{
+       struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
+       __pthread_signal_and_restart(pthread, SIGFPE, FPE_INTDIV, (void*)aux);
+}
+
+static void handle_gp_fault(struct uthread *uthread, unsigned int err,
+                            unsigned long aux)
+{
+       struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
+       __pthread_signal_and_restart(pthread, SIGSEGV, SEGV_ACCERR, (void*)aux);
+}
+
+static void handle_page_fault(struct uthread *uthread, unsigned int err,
+                              unsigned long aux)
+{
+       struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
+       if (!(err & PF_VMR_BACKED)) {
+               __pthread_signal_and_restart(pthread, SIGSEGV, SEGV_MAPERR, (void*)aux);
+       } else {
+               /* stitching for the event handler.  sysc -> uth, uth -> sysc */
+               uthread->local_sysc.u_data = uthread;
+               uthread->sysc = &uthread->local_sysc;
+               pthread->state = PTH_BLK_SYSC;
+               /* one downside is that we'll never check the return val of the syscall.  if
+                * we errored out, we wouldn't know til we PF'd again, and inspected the old
+                * retval/err and other sysc fields (make sure the PF is on the same addr,
+                * etc).  could run into this issue on truncated files too. */
+               syscall_async(&uthread->local_sysc, SYS_populate_va, aux, 1);
+               if (!register_evq(&uthread->local_sysc, sysc_mgmt[vcore_id()].ev_q)) {
+                       /* Lost the race with the call being done.  The kernel won't send the
+                        * event.  Just restart him. */
+                       restart_thread(&uthread->local_sysc);
+               }
+       }
+}
+
+void pth_thread_refl_fault(struct uthread *uthread, unsigned int trap_nr,
+                           unsigned int err, unsigned long aux)
+{
+       struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
+       pthread->state = PTH_BLK_SYSC;
+       mcs_pdr_lock(&queue_lock);
+       threads_active--;
+       TAILQ_REMOVE(&active_queue, pthread, next);
+       mcs_pdr_unlock(&queue_lock);
+
+       /* TODO: RISCV/x86 issue! (0 is divby0, 14 is PF, etc) */
+#if defined(__i386__) || defined(__x86_64__) 
+       switch(trap_nr) {
+               case 0:
+                       handle_div_by_zero(uthread, err, aux);
+                       break;
+               case 13:
+                       handle_gp_fault(uthread, err, aux);
+                       break;
+               case 14:
+                       handle_page_fault(uthread, err, aux);
+                       break;
+               default:
+                       printf("Pthread has unhandled fault: %d\n", trap_nr);
+                       /* Note that uthread.c already copied out our ctx into the uth struct */
+                       print_user_context(&uthread->u_ctx);
+                       exit(-1);
+       }
+#else
+       #error "Handling hardware faults is currently only supported on x86"
+#endif
+}
+
 void pth_preempt_pending(void)
 {
 }
 void pth_preempt_pending(void)
 {
 }
@@ -329,6 +545,25 @@ int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize)
        return 0;
 }
 
        return 0;
 }
 
+int pthread_attr_getstack(const pthread_attr_t *__restrict __attr,
+                                                  void **__stackaddr, size_t *__stacksize)
+{
+       *__stackaddr = __attr->stackaddr;
+       *__stacksize = __attr->stacksize;
+       return 0;
+}
+
+int pthread_getattr_np(pthread_t __th, pthread_attr_t *__attr)
+{
+       __attr->stackaddr = __th->stacktop - __th->stacksize;
+       __attr->stacksize = __th->stacksize;
+       if (__th->detached)
+               __attr->detachstate = PTHREAD_CREATE_DETACHED;
+       else
+               __attr->detachstate = PTHREAD_CREATE_JOINABLE;
+       return 0;
+}
+
 /* Do whatever init you want.  At some point call uthread_lib_init() and pass it
  * a uthread representing thread0 (int main()) */
 void pthread_lib_init(void)
 /* Do whatever init you want.  At some point call uthread_lib_init() and pass it
  * a uthread representing thread0 (int main()) */
 void pthread_lib_init(void)
@@ -353,6 +588,8 @@ void pthread_lib_init(void)
        t->detached = TRUE;
        t->state = PTH_RUNNING;
        t->joiner = 0;
        t->detached = TRUE;
        t->state = PTH_RUNNING;
        t->joiner = 0;
+       __sigemptyset(&t->sigmask);
+       __sigemptyset(&t->sigpending);
        assert(t->id == 0);
        /* Put the new pthread (thread0) on the active queue */
        mcs_pdr_lock(&queue_lock);
        assert(t->id == 0);
        /* Put the new pthread (thread0) on the active queue */
        mcs_pdr_lock(&queue_lock);
@@ -368,7 +605,7 @@ void pthread_lib_init(void)
        enable_kevent(EV_USER_IPI, 0, EVENT_IPI | EVENT_VCORE_PRIVATE);
 
        /* Handle syscall events. */
        enable_kevent(EV_USER_IPI, 0, EVENT_IPI | EVENT_VCORE_PRIVATE);
 
        /* Handle syscall events. */
-       ev_handlers[EV_SYSCALL] = pth_handle_syscall;
+       register_ev_handler(EV_SYSCALL, pth_handle_syscall, 0);
        /* Set up the per-vcore structs to track outstanding syscalls */
        sysc_mgmt = malloc(sizeof(struct sysc_mgmt) * max_vcores());
        assert(sysc_mgmt);
        /* Set up the per-vcore structs to track outstanding syscalls */
        sysc_mgmt = malloc(sizeof(struct sysc_mgmt) * max_vcores());
        assert(sysc_mgmt);
@@ -415,10 +652,11 @@ void pthread_lib_init(void)
         * have its init stuff use things like vcore stacks or TLSs, we'll need to
         * change this. */
        uthread_lib_init((struct uthread*)t);
         * have its init stuff use things like vcore stacks or TLSs, we'll need to
         * change this. */
        uthread_lib_init((struct uthread*)t);
+       atomic_init(&threads_total, 1);                 /* one for thread0 */
 }
 
 }
 
-int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
-                   void *(*start_routine)(void *), void *arg)
+int __pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+                     void *(*start_routine)(void *), void *arg)
 {
        struct uth_thread_attr uth_attr = {0};
        run_once(pthread_lib_init());
 {
        struct uth_thread_attr uth_attr = {0};
        run_once(pthread_lib_init());
@@ -433,6 +671,9 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
        pthread->id = get_next_pid();
        pthread->detached = FALSE;                              /* default */
        pthread->joiner = 0;
        pthread->id = get_next_pid();
        pthread->detached = FALSE;                              /* default */
        pthread->joiner = 0;
+       pthread->sigmask = ((pthread_t)current_uthread)->sigmask;
+       __sigemptyset(&pthread->sigpending);
+       pthread->sigdata = NULL;
        /* Respect the attributes */
        if (attr) {
                if (attr->stacksize)                                    /* don't set a 0 stacksize */
        /* Respect the attributes */
        if (attr) {
                if (attr->stacksize)                                    /* don't set a 0 stacksize */
@@ -454,8 +695,16 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
        if (need_tls)
                uth_attr.want_tls = TRUE;
        uthread_init((struct uthread*)pthread, &uth_attr);
        if (need_tls)
                uth_attr.want_tls = TRUE;
        uthread_init((struct uthread*)pthread, &uth_attr);
-       pth_thread_runnable((struct uthread*)pthread);
        *thread = pthread;
        *thread = pthread;
+       atomic_inc(&threads_total);
+       return 0;
+}
+
+int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+                   void *(*start_routine)(void *), void *arg)
+{
+       if (!__pthread_create(thread, attr, start_routine, arg))
+               pth_thread_runnable((struct uthread*)*thread);
        return 0;
 }
 
        return 0;
 }
 
@@ -549,11 +798,19 @@ static void __pth_exit_cb(struct uthread *uthread, void *junk)
                        pth_thread_runnable((struct uthread*)temp_pth);
                }
        }
                        pth_thread_runnable((struct uthread*)temp_pth);
                }
        }
+       /* If we were the last pthread, we exit for the whole process.  Keep in mind
+        * that thread0 is counted in this, so this will only happen if that thread
+        * calls pthread_exit(). */
+       if ((atomic_fetch_and_add(&threads_total, -1) == 1))
+               exit(0);
 }
 
 void pthread_exit(void *ret)
 {
        struct pthread_tcb *pthread = pthread_self();
 }
 
 void pthread_exit(void *ret)
 {
        struct pthread_tcb *pthread = pthread_self();
+       /* Some apps could call pthread_exit before initing.  This will slow down
+        * our pthread exits slightly. */
+       pthread_lib_init();
        pthread->retval = ret;
        destroy_dtls();
        uthread_yield(FALSE, __pth_exit_cb, 0);
        pthread->retval = ret;
        destroy_dtls();
        uthread_yield(FALSE, __pth_exit_cb, 0);
@@ -961,17 +1218,38 @@ int pthread_detach(pthread_t thread)
        return 0;
 }
 
        return 0;
 }
 
-int pthread_kill (pthread_t __threadid, int __signo)
+int pthread_kill(pthread_t thread, int signo)
 {
 {
-       printf("pthread_kill is not yet implemented!");
-       return -1;
+       // Slightly racy with clearing of mask when triggering the signal, but
+       // that's OK, as signals are inherently racy since they don't queue up.
+       return sigaddset(&thread->sigpending, signo);
 }
 
 
 int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
 {
 }
 
 
 int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
 {
-       printf("pthread_sigmask is not yet implemented!");
-       return -1;
+       if (how != SIG_BLOCK && how != SIG_SETMASK && how != SIG_UNBLOCK) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       pthread_t pthread = ((struct pthread_tcb*)current_uthread);
+       if (oset)
+               *oset = pthread->sigmask;
+       switch (how) {
+               case SIG_BLOCK:
+                       pthread->sigmask = pthread->sigmask | *set;
+                       break;
+               case SIG_SETMASK:
+                       pthread->sigmask = *set;
+                       break;
+               case SIG_UNBLOCK:
+                       pthread->sigmask = pthread->sigmask & ~(*set);
+                       break;
+       }
+       // Ensures any signals we just unmasked get processed if they are pending
+       pthread_yield();
+       return 0;
 }
 
 int pthread_sigqueue(pthread_t *thread, int sig, const union sigval value)
 }
 
 int pthread_sigqueue(pthread_t *thread, int sig, const union sigval value)
@@ -1004,3 +1282,26 @@ int pthread_setspecific(pthread_key_t key, const void *value)
        return 0;
 }
 
        return 0;
 }
 
+int pthread_mutex_timedlock (pthread_mutex_t *__restrict __mutex,
+                                       const struct timespec *__restrict
+                                       __abstime)
+{
+       fprintf(stderr, "Unsupported %s!", __FUNCTION__);
+       abort();
+       return -1;
+}
+int pthread_cond_timedwait (pthread_cond_t *__restrict __cond,
+                                  pthread_mutex_t *__restrict __mutex,
+                                  const struct timespec *__restrict __abstime)
+{
+       fprintf(stderr, "Unsupported %s!", __FUNCTION__);
+       abort();
+       return -1;
+}
+
+int pthread_cancel (pthread_t __th)
+{
+       fprintf(stderr, "Unsupported %s!", __FUNCTION__);
+       abort();
+       return -1;
+}