Uthreads do not require TLS
authorBarret Rhoden <brho@cs.berkeley.edu>
Tue, 30 Jul 2013 02:11:03 +0000 (19:11 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Tue, 30 Jul 2013 18:23:03 +0000 (11:23 -0700)
By default, individual uthreads will not have their own TLS; they will piggyback
off of their vcore's TLS.  The uthread library can handle individual
uthreads having TLS.

Note that thread0 (from glibc) will have TLS, no matter what.   FYI,
thread0's TLS gets used at exit, and needs to be the same as when the
program started (C++ programs, fluidanimate at least).

2LSs choose whether or not their specific uthreads have TLS.  To be
somewhat compatible, pthreads will have them by default.  If you're app
doesn't need TLS, call pthread_need_tls(FALSE), preferably before call
pthread_create().

For reference, this lowers the latency of the context switch (via the
untuned pthread_test 100 100000 1) from ~360ns -> 194ns.  That includes
3 mcs locks in the 2LS, which we could minimize.

tests/lock_test.c
tests/pthread_test.c
user/parlib/include/uthread.h
user/parlib/include/x86/vcore64.h
user/parlib/uthread.c
user/pthread/pthread.c
user/pthread/pthread.h

index 3950923..8eb2310 100644 (file)
@@ -662,6 +662,7 @@ static void os_prep_work(int nr_threads)
        atomic_init(&preempt_cnt, 0);
        atomic_init(&indir_cnt, 0);
        pthread_can_vcore_request(FALSE);       /* 2LS won't manage vcores */
+       pthread_need_tls(FALSE);
        pthread_lib_init();                                     /* gives us one vcore */
        ev_handlers[EV_VCORE_PREEMPT] = handle_preempt;
        ev_handlers[EV_CHECK_MSGS] = handle_indir;
index 5c314f2..ec17604 100644 (file)
@@ -69,6 +69,7 @@ int main(int argc, char** argv)
        if (nr_vcores) {
                /* Only do the vcore trickery if requested */
                pthread_can_vcore_request(FALSE);       /* 2LS won't manage vcores */
+               pthread_need_tls(FALSE);
                pthread_lib_init();                                     /* gives us one vcore */
                vcore_request(nr_vcores - 1);           /* ghetto incremental interface */
                for (int i = 0; i < nr_vcores; i++) {
index 7ccfbfa..c88793e 100644 (file)
@@ -54,9 +54,13 @@ void uthread_lib_init(struct uthread *uthread);
  * pthread_create(), which can wrap these with their own stuff (like attrs,
  * retvals, etc). */
 
-/* Does the uthread initialization of a uthread that the caller created.  Call
- * this whenever you are "starting over" with a thread. */
-void uthread_init(struct uthread *new_thread);
+/* uthread_init() does the uthread initialization of a uthread that the caller
+ * created.  Call this whenever you are "starting over" with a thread.  Pass in
+ * attr, if you want to override any defaults. */
+struct uth_thread_attr {
+       bool want_tls;          /* default, no */
+};
+void uthread_init(struct uthread *new_thread, struct uth_thread_attr *attr);
 /* Low-level _S code calls this for basic uthreading without a 2LS */
 void uthread_slim_init(void);
 /* Call this when you are done with a uthread, forever, but before you free it */
index 32f71fb..984f8d7 100644 (file)
@@ -136,11 +136,6 @@ static inline void pop_hw_tf(struct hw_trapframe *tf, uint32_t vcoreid)
 static inline void pop_sw_tf(struct sw_trapframe *sw_tf, uint32_t vcoreid)
 {
        struct preempt_data *vcpd = &__procdata.vcore_preempt_data[vcoreid];
-       /* If we have a TLS base set in the TF, then it was saved by the kernel.
-        * whoever calls pop_tf should have set their TF, so we could assert it is
-        * the same here.  Can remove for perf reasons. */
-       if (sw_tf->tf_fsbase)
-               assert(get_tls_desc(vcoreid) == (void*)sw_tf->tf_fsbase);
        /* Restore callee-saved FPU state.  We need to clear exceptions before
         * reloading the FP CW, in case the new CW unmasks any.  We also need to
         * reset the tag word to clear out the stack.
index 867bba7..772f6ce 100644 (file)
@@ -16,7 +16,9 @@ __thread struct uthread *current_uthread = 0;
  * extensively about the details.  Will call out when necessary. */
 struct event_queue *preempt_ev_q;
 
-/* static helpers: */
+/* Helpers: */
+#define UTH_TLSDESC_NOTLS (void*)(-1)
+static inline bool __uthread_has_tls(struct uthread *uthread);
 static int __uthread_allocate_tls(struct uthread *uthread);
 static int __uthread_reinit_tls(struct uthread *uthread);
 static void __uthread_free_tls(struct uthread *uthread);
@@ -28,7 +30,13 @@ static void __ros_mcp_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
  * vcore0 thinks the uthread is running there.  Called only by slim_init (early
- * _S code) and lib_init. */
+ * _S code) and lib_init.
+ *
+ * Whether or not uthreads have TLS, thread0 has TLS, given to it by glibc.
+ * This TLS will get set whenever we use thread0, regardless of whether or not
+ * we use TLS for uthreads in general.  glibc cares about this TLS and will use
+ * it at exit.  We can't simply use that TLS for VC0 either, since we don't know
+ * where thread0 will be running when the program ends. */
 static void uthread_manage_thread0(struct uthread *uthread)
 {
        assert(uthread);
@@ -177,7 +185,7 @@ void __attribute__((noreturn)) uthread_vcore_entry(void)
 
 /* Does the uthread initialization of a uthread that the caller created.  Call
  * this whenever you are "starting over" with a thread. */
-void uthread_init(struct uthread *new_thread)
+void uthread_init(struct uthread *new_thread, struct uth_thread_attr *attr)
 {
        int ret;
        assert(new_thread);
@@ -188,13 +196,17 @@ void uthread_init(struct uthread *new_thread)
         * There is no FP context to be restored yet.  We only save the FPU when we
         * were interrupted off a core. */
        new_thread->flags |= UTHREAD_SAVED;
-       /* Get a TLS.  If we already have one, reallocate/refresh it */
-       if (new_thread->tls_desc)
-               ret = __uthread_reinit_tls(new_thread);
-       else
-               ret = __uthread_allocate_tls(new_thread);
-       assert(!ret);
-       uthread_set_tls_var(new_thread, current_uthread, new_thread);
+       if (attr && attr->want_tls) {
+               /* Get a TLS.  If we already have one, reallocate/refresh it */
+               if (new_thread->tls_desc)
+                       ret = __uthread_reinit_tls(new_thread);
+               else
+                       ret = __uthread_allocate_tls(new_thread);
+               assert(!ret);
+               uthread_set_tls_var(new_thread, current_uthread, new_thread);
+       } else {
+               new_thread->tls_desc = UTH_TLSDESC_NOTLS;
+       }
 }
 
 /* This is a wrapper for the sched_ops thread_runnable, for use by functions
@@ -300,11 +312,18 @@ void uthread_yield(bool save_state, void (*yield_func)(struct uthread*, void*),
                save_fp_state(&uthread->as);
                uthread->flags |= UTHREAD_FPSAVED;
        }
-       /* Change to the transition context (both TLS and stack). */
-       extern void** vcore_thread_control_blocks;
-       set_tls_desc(vcore_thread_control_blocks[vcoreid], vcoreid);
-       assert(current_uthread == uthread);
-       assert(in_vcore_context());     /* technically, we aren't fully in vcore ctx */
+       /* Change to the transition context (both TLS (if applicable) and stack). */
+       if (__uthread_has_tls(uthread)) {
+               extern void **vcore_thread_control_blocks;
+               set_tls_desc(vcore_thread_control_blocks[vcoreid], vcoreid);
+               assert(current_uthread == uthread);
+               assert(in_vcore_context());
+       } else {
+               /* Since uthreads and vcores share TLS (it's always the vcore's TLS, the
+                * uthread one just bootstraps from it), we need to change our state at
+                * boundaries between the two 'contexts' */
+               __vcore_context = TRUE;
+       }
        /* After this, make sure you don't use local variables.  Also, make sure the
         * compiler doesn't use them without telling you (TODO).
         *
@@ -329,7 +348,8 @@ void uthread_cleanup(struct uthread *uthread)
 {
        printd("[U] thread %08p on vcore %d is DYING!\n", uthread, vcore_id());
        /* we alloc and manage the TLS, so lets get rid of it */
-       __uthread_free_tls(uthread);
+       if (__uthread_has_tls(uthread))
+               __uthread_free_tls(uthread);
 }
 
 static void __ros_syscall_spinon(struct syscall *sysc)
@@ -377,12 +397,32 @@ void highjack_current_uthread(struct uthread *uthread)
 {
        uint32_t vcoreid = vcore_id();
        assert(uthread != current_uthread);
-       assert(uthread->tls_desc);
        current_uthread->state = UT_NOT_RUNNING;
        uthread->state = UT_RUNNING;
-       vcore_set_tls_var(current_uthread, uthread);
-       set_tls_desc(uthread->tls_desc, vcoreid);
-       __vcoreid = vcoreid;    /* setting the uthread's TLS var */
+       /* Make sure the vcore is tracking the new uthread struct */
+       if (__uthread_has_tls(current_uthread))
+               vcore_set_tls_var(current_uthread, uthread);
+       else
+               current_uthread = uthread;
+       /* and make sure we are using the correct TLS for the new uthread */
+       if (__uthread_has_tls(uthread)) {
+               assert(uthread->tls_desc);
+               set_tls_desc(uthread->tls_desc, vcoreid);
+               __vcoreid = vcoreid;    /* setting the uthread's TLS var */
+       }
+}
+
+/* Helper: loads a uthread's TLS on this vcore, if applicable.  If our uthreads
+ * do not have their own TLS, we simply switch the __vc_ctx, signalling that the
+ * context running here is (soon to be) a uthread. */
+static void set_uthread_tls(struct uthread *uthread, uint32_t vcoreid)
+{
+       if (__uthread_has_tls(uthread)) {
+               set_tls_desc(uthread->tls_desc, vcoreid);
+               __vcoreid = vcoreid;    /* setting the uthread's TLS var */
+       } else {
+               __vcore_context = FALSE;
+       }
 }
 
 /* Run the thread that was current_uthread, from a previous run.  Should be
@@ -401,8 +441,7 @@ void run_current_uthread(void)
        printd("[U] Vcore %d is restarting uthread %08p\n", vcoreid,
               current_uthread);
        /* Go ahead and start the uthread */
-       set_tls_desc(current_uthread->tls_desc, vcoreid);
-       __vcoreid = vcoreid;    /* setting the uthread's TLS var */
+       set_uthread_tls(current_uthread, vcoreid);
        /* Run, using the TF in the VCPD.  FP state should already be loaded */
        pop_user_ctx(&vcpd->uthread_ctx, vcoreid);
        assert(0);
@@ -442,9 +481,7 @@ void run_uthread(struct uthread *uthread)
                uthread->flags &= ~UTHREAD_FPSAVED;
                restore_fp_state(&uthread->as);
        }
-       /* Go ahead and start the uthread */
-       set_tls_desc(uthread->tls_desc, vcoreid);
-       __vcoreid = vcoreid;    /* setting the uthread's TLS var */
+       set_uthread_tls(uthread, vcoreid);
        /* the uth's context will soon be in the cpu (or VCPD), no longer saved */
        uthread->flags &= ~UTHREAD_SAVED;
        pop_user_ctx(&uthread->u_ctx, vcoreid);
@@ -463,8 +500,7 @@ static void __run_current_uthread_raw(void)
        vcpd->notif_pending = TRUE;
        assert(!(current_uthread->flags & UTHREAD_SAVED));
        assert(!(current_uthread->flags & UTHREAD_FPSAVED));
-       set_tls_desc(current_uthread->tls_desc, vcoreid);
-       __vcoreid = vcoreid;    /* setting the uthread's TLS var */
+       set_uthread_tls(current_uthread, vcoreid);
        pop_user_ctx_raw(&vcpd->uthread_ctx, vcoreid);
        assert(0);
 }
@@ -759,7 +795,8 @@ void handle_vc_preempt(struct event_msg *ev_msg, unsigned int ev_type)
        /* At this point, we need to try to recover */
        /* This case handles when the remote core was in vcore context */
        if (rem_vcpd->notif_disabled) {
-               printd("VC %d recovering %d, notifs were disabled\n", vcoreid, rem_vcoreid);
+               printd("VC %d recovering %d, notifs were disabled\n", vcoreid,
+                      rem_vcoreid);
                change_to_vcore(vcpd, rem_vcoreid);
                return; /* in case it returns.  we've done our job recovering */
        }
@@ -777,7 +814,8 @@ void handle_vc_preempt(struct event_msg *ev_msg, unsigned int ev_type)
         * don't need to worry about handling the message any further.  Future
         * preemptions will generate another message, so we can ignore getting the
         * uthread or anything like that. */
-       printd("VC %d recovering %d, trying to steal uthread\n", vcoreid, rem_vcoreid);
+       printd("VC %d recovering %d, trying to steal uthread\n", vcoreid,
+              rem_vcoreid);
        if (!(atomic_read(&rem_vcpd->flags) & VC_PREEMPTED))
                goto out_stealing;
        /* Might be preempted twice quickly, and the second time had notifs
@@ -811,7 +849,8 @@ void handle_vc_preempt(struct event_msg *ev_msg, unsigned int ev_type)
        /* Extremely rare: they have a uthread, but it can't migrate.  So we'll need
         * to change to them. */
        if (cant_migrate) {
-               printd("VC %d recovering %d, can't migrate uthread!\n", vcoreid, rem_vcoreid);
+               printd("VC %d recovering %d, can't migrate uthread!\n", vcoreid,
+                      rem_vcoreid);
                stop_uth_stealing(rem_vcpd);
                change_to_vcore(vcpd, rem_vcoreid);
                return; /* in case it returns.  we've done our job recovering */
@@ -901,6 +940,11 @@ void deregister_evq(struct syscall *sysc)
        } while (!atomic_cas(&sysc->flags, old_flags, old_flags & ~SC_UEVENT));
 }
 
+static inline bool __uthread_has_tls(struct uthread *uthread)
+{
+       return uthread->tls_desc != UTH_TLSDESC_NOTLS;
+}
+
 /* TLS helpers */
 static int __uthread_allocate_tls(struct uthread *uthread)
 {
index 508cf8f..bec5098 100644 (file)
@@ -22,6 +22,7 @@ struct mcs_pdr_lock queue_lock;
 int threads_ready = 0;
 int threads_active = 0;
 bool can_adjust_vcores = TRUE;
+bool need_tls = TRUE;
 
 /* Array of per-vcore structs to manage waiting on syscalls and handling
  * overflow.  Init'd in pth_init(). */
@@ -272,6 +273,11 @@ void pthread_can_vcore_request(bool can)
        can_adjust_vcores = can;
 }
 
+void pthread_need_tls(bool need)
+{
+       need_tls = need;
+}
+
 /* Pthread interface stuff and helpers */
 
 int pthread_attr_init(pthread_attr_t *a)
@@ -316,6 +322,7 @@ int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)
        attr->stacksize = stacksize;
        return 0;
 }
+
 int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize)
 {
        *stacksize = attr->stacksize;
@@ -413,6 +420,7 @@ void pthread_lib_init(void)
 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());
        /* Create the actual thread */
        struct pthread_tcb *pthread;
@@ -443,7 +451,9 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
        pthread->start_routine = start_routine;
        pthread->arg = arg;
        /* Initialize the uthread */
-       uthread_init((struct uthread*)pthread);
+       if (need_tls)
+               uth_attr.want_tls = TRUE;
+       uthread_init((struct uthread*)pthread, &uth_attr);
        pth_thread_runnable((struct uthread*)pthread);
        *thread = pthread;
        return 0;
index 8a8cdcd..1bd2ae9 100644 (file)
@@ -131,6 +131,7 @@ typedef dtls_key_t pthread_key_t;
 
 /* Akaros pthread extensions / hacks */
 void pthread_can_vcore_request(bool can);      /* default is TRUE */
+void pthread_need_tls(bool need);                      /* default is TRUE */
 void pthread_lib_init(void);
 void __pthread_generic_yield(struct pthread_tcb *pthread);