Pthread create, join, and exit
authorBarret Rhoden <brho@cs.berkeley.edu>
Sat, 17 Apr 2010 22:42:10 +0000 (15:42 -0700)
committerKevin Klues <klueska@cs.berkeley.edu>
Thu, 3 Nov 2011 00:35:43 +0000 (17:35 -0700)
Primitive pthread scheduler.  It doesn't handle notifications, yield
between pthreads, support non-spinning sync, etc.  It just executes each
pthread to completion, FCFS, on whatever vcores are in the system.

P.S. - careful of races with free on exit/join...

kern/arch/i686/ros/trapframe.h
tests/pthread_test.c
user/include/i686/arch.h
user/include/i686/vcore.h
user/include/pthread.h
user/include/sparc/arch.h
user/include/vcore.h
user/parlib/pthread.c
user/parlib/vcore.c

index 02c5225..759ae53 100644 (file)
@@ -3,6 +3,8 @@
 #ifndef ROS_INCLUDE_ARCH_TRAPFRAME_H
 #define ROS_INCLUDE_ARCH_TRAPFRAME_H
 
+#include <ros/common.h>
+
 typedef struct pushregs {
        /* registers as pushed by pusha */
        uint32_t reg_edi;
index 76e548a..b84de8d 100644 (file)
@@ -7,35 +7,53 @@ pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
        printf(__VA_ARGS__); \
        pthread_mutex_unlock(&lock);
 
-int thread_num;
-pthread_t t0;
 pthread_t t1;
 pthread_t t2;
+pthread_t t3;
+
+pthread_t my_threads[10];
+void *my_retvals[10];
 
 __thread int my_id;
 void *thread(void* arg)
 {      
-       my_id = thread_num++;
-       printf_safe("thread %d\n", my_id);
+       printf_safe("[A] pthread %d on vcore %d\n", pthread_self()->id, vcore_id());
+       return (void*)(pthread_self()->id);
 }
 
 int main(int argc, char** argv) 
 {
-       thread_num = 0;
-       printf_safe("About to create thread 0\n");
-       pthread_create(&t0, NULL, &thread, NULL);
-       printf("returned from creating him, spinning\n");
-       while(1);
+       void *retval1 = 0;
+       void *retval2 = 0;
+
+       while (1) {
+               for (int i = 1; i < 10; i++) {
+                       printf_safe("[A] About to create thread %d\n", i);
+                       pthread_create(&my_threads[i], NULL, &thread, NULL);
+               }
+               for (int i = 1; i < 10; i++) {
+                       printf_safe("[A] About to join on thread %d\n", i);
+                       pthread_join(my_threads[i], &my_retvals[i]);
+                       printf_safe("[A] Successfully joined on thread %d (retval: %p)\n", i,
+                                   my_retvals[i]);
+               }
+       }
 
-//     printf_safe("About to create thread 1\n");
-//     pthread_create(&t1, NULL, &thread, NULL);
-//     printf_safe("About to create thread 2\n");
-//     pthread_create(&t2, NULL, &thread, NULL);
-
-       printf_safe("About to join on thread 0\n");
-       pthread_join(t0, NULL);
-//     printf_safe("About to join on thread 1\n");
-//     pthread_join(t1, NULL);
-//     printf_safe("About to join on thread 2\n");
-//     pthread_join(t2, NULL);
+       printf_safe("[A] About to create thread 1\n");
+       pthread_create(&t1, NULL, &thread, NULL);
+       printf_safe("[A] About to create thread 2\n");
+       pthread_create(&t2, NULL, &thread, NULL);
+       printf_safe("About to create thread 3\n");
+       pthread_create(&t3, NULL, &thread, NULL);
+
+       printf_safe("[A] Vcore0 spinning\n");
+       printf_safe("[A] About to join on thread 1\n");
+       pthread_join(t1, &retval1);
+       printf_safe("[A] Successfully joined on thread 1 (retval: %p)\n", retval1);
+       printf_safe("[A] About to join on thread 2\n");
+       pthread_join(t2, &retval2);
+       printf_safe("[A] Successfully joined on thread 2 (retval: %p)\n", retval2);
+       printf_safe("About to join on thread 3\n");
+       pthread_join(t3, NULL);
+       while(1);
 } 
index dba795e..71a88e4 100644 (file)
@@ -1,11 +1,16 @@
 #ifndef PARLIB_ARCH_H
 #define PARLIB_ARCH_H
 
+#include <ros/arch/trapframe.h>
+#include <ros/arch/mmu.h>
 #include <ros/common.h>
+#include <string.h>
+
+#define internal_function   __attribute ((regparm (3), stdcall))
 
 #define ARCH_CL_SIZE 64
 
-static __inline void
+static __inline void __attribute__((always_inline))
 set_stack_pointer(void* sp)
 {
        asm volatile ("mov %0,%%esp" : : "r"(sp) : "memory","esp");
@@ -31,4 +36,14 @@ cpu_relax(void)
        asm volatile("pause" : : : "memory");
 }
 
+// This assumes a user_tf looks like a regular kernel trapframe
+static __inline void
+init_user_tf(struct user_trapframe *u_tf, uint32_t entry_pt, uint32_t stack_top)
+{
+       memset(u_tf, 0, sizeof(struct user_trapframe));
+       u_tf->tf_eip = entry_pt;
+       u_tf->tf_cs = GD_UT | 3;
+       u_tf->tf_esp = stack_top;
+}
+
 #endif /* PARLIB_ARCH_H */
index 5aadd88..a883977 100644 (file)
@@ -109,7 +109,7 @@ static inline void set_tls_desc(void *tls_desc, uint32_t vcoreid)
   /* GS is still the same (should be!), but it needs to be reloaded to force a
    * re-read of the LDT. */
   uint32_t gs = (vcoreid << 3) | 0x07;
-  asm volatile("movl %0,%%gs" : : "r" (gs));
+  asm volatile("movl %0,%%gs" : : "r" (gs) : "memory");
 }
 
 // this is how we get our thread id on entry.
index b45a478..97b2049 100644 (file)
@@ -18,11 +18,14 @@ struct pthread_tcb {
        struct user_trapframe utf;
        struct ancillary_state as;
        void *tls_desc;
-       uint32_t vcoreid;
-       uint32_t pthreadid;
+       void *stacktop;
+       uint32_t id;
 
        int finished;
+       void *retval;
        int detached;
+       // whether or not the scheduler can migrate you from your vcore
+       bool dont_migrate;
 };
 typedef struct pthread_tcb* pthread_t;
 TAILQ_HEAD(pthread_queue, pthread_tcb);
index dd5a98d..a75f818 100644 (file)
@@ -3,6 +3,8 @@
 
 #include <ros/common.h>
 
+#define internal_function
+
 #define __RAMP__
 #define ARCH_CL_SIZE 128
 
@@ -47,4 +49,15 @@ cpu_relax(void)
        asm volatile("1: deccc %0; bne 1b; nop" :
                     "=r"(ctr) : "0"(ctr) : "cc","memory");
 }
+
+// This assumes a user_tf looks like a regular kernel trapframe
+static __inline void
+init_user_tf(struct user_trapframe *u_tf, uint32_t entry_pt, uint32_t stack_top)
+{
+       memset(u_tf, 0, sizeof(struct user_trapframe));
+       u_tf->gpr[14] = stack_top - 96;
+       u_tf->pc = entry_pt;
+       u_tf->npc = entry_pt + 4;
+}
+
 #endif /* PARLIB_ARCH_H */
index 1b5066f..2e6e11e 100644 (file)
@@ -11,6 +11,9 @@ extern "C" {
 #define LOG2_MAX_VCORES 6
 #define MAX_VCORES (1 << LOG2_MAX_VCORES)
 
+#define TRANSITION_STACK_PAGES 2
+#define TRANSITION_STACK_SIZE (TRANSITION_STACK_PAGES*PGSIZE)
+
 /* Defined by glibc; Must be implemented by a user level threading library */
 extern void vcore_entry();
 
index b6940ec..1e49467 100644 (file)
@@ -1,3 +1,4 @@
+#include <ros/arch/trapframe.h>
 #include <pthread.h>
 #include <vcore.h>
 #include <mcs.h>
@@ -9,7 +10,9 @@
 #include <parlib.h>
 #include <ros/notification.h>
 #include <arch/atomic.h>
+#include <arch/arch.h>
 #include <sys/queue.h>
+#include <sys/mman.h>
 #include <assert.h>
 
 struct pthread_queue ready_queue = TAILQ_HEAD_INITIALIZER(ready_queue);
@@ -17,6 +20,10 @@ struct pthread_queue active_queue = TAILQ_HEAD_INITIALIZER(active_queue);
 mcs_lock_t queue_lock = MCS_LOCK_INIT;
 pthread_once_t init_once = PTHREAD_ONCE_INIT;
 int threads_ready = 0;
+int threads_active = 0;
+
+/* Helper / local functions */
+static int get_next_pid(void);
 
 __thread struct pthread_tcb *current_thread = 0;
 
@@ -37,10 +44,13 @@ void _pthread_init()
 
        /* Create a pthread_tcb for the main thread */
        pthread_t t = (pthread_t)calloc(sizeof(struct pthread_tcb), 1);
-       t->pthreadid = 0; // The main thread gets id 0
+       t->id = get_next_pid();
+       assert(t->id == 0);
        
        /* Save a pointer to the newly created threads tls region into its tcb */
        t->tls_desc = get_tls_desc(0);
+       /* Save a pointer to the pthread in its own TLS */
+       current_thread = t;
 
        /* Change temporarily to vcore0s tls region so we can save the newly created
         * tcb into its current_thread variable and then restore it.  One minor
@@ -61,44 +71,58 @@ void _pthread_init()
 void vcore_entry()
 {
        uint32_t vcoreid = vcore_id();
-       printf("Hi entering vcore entry on vcore %d!!\n", vcoreid);
 
        struct preempt_data *vcpd = &__procdata.vcore_preempt_data[vcoreid];
        struct vcore *vc = &__procinfo.vcoremap[vcoreid];
 
+       /* Should always have notifications disabled when coming in here. */
+       assert(vcpd->notif_enabled == FALSE);
+
        /* Put this in the loop that deals with notifications.  It will return if
         * there is no preempt pending. */ 
+       // TODO: prob make a handle_notif() function
        if (vc->preempt_pending)
                sys_yield(TRUE);
-
+       // TODO: consider making this restart path work for restarting as well as
+       // freshly starting
        if (current_thread) {
                /* Do one last check for notifs before clearing pending */
+               // TODO: call the handle_notif() here (first)
                vcpd->notif_pending = 0;
-               set_tls_desc(current_thread->tls_desc, 0);
-
-               /* Load silly state (Floating point) too, though not for a restart -
-                * only for a fresh pthread */
-       
+               set_tls_desc(current_thread->tls_desc, vcoreid);
                /* Pop the user trap frame */
                pop_ros_tf(&vcpd->notif_tf, vcoreid);
                assert(0);
        }
-       while(1);
 
-#if 0
-       pthread_t node = NULL;
+       /* no one currently running, so lets get someone from the ready queue */
        mcs_lock_lock(&queue_lock);
-       if(ready_queue)
-               node = queue_remove(&work_queue_head,&work_queue_tail);
-mcs_lock_unlock(&work_queue_lock);
+       struct pthread_tcb *new_thread = TAILQ_FIRST(&ready_queue);
+       if (new_thread) {
+               TAILQ_REMOVE(&ready_queue, new_thread, next);
+               TAILQ_INSERT_TAIL(&active_queue, new_thread, next);
+               threads_active++;
+               threads_ready--;
+       }
+       mcs_lock_unlock(&queue_lock);
+       if (!new_thread) {
+               printf("[P] No threads, vcore %d is yielding\n", vcoreid);
+               sys_yield(0);
+       }
+       /* Save a ptr to the pthread running in the transition context's TLS */
+       current_thread = new_thread;
 
-if(node)
-  break;
-cpu_relax();
-active_threads[vcore_id()] = node;
+       /* Do one last check for notifs before clearing pending */
+       // TODO: call the handle_notif() here (first)
+       vcpd->notif_pending = 0;
+       set_tls_desc(new_thread->tls_desc, vcoreid);
 
-pthread_exit(node->start_routine(node->arg));
-#endif
+       /* Load silly state (Floating point) too.  For real */
+       // TODO
+
+       /* Pop the user trap frame */
+       pop_ros_tf(&new_thread->utf, vcoreid);
+       assert(0);
 }
 
 int pthread_attr_init(pthread_attr_t *a)
@@ -111,45 +135,125 @@ int pthread_attr_destroy(pthread_attr_t *a)
   return 0;
 }
 
-void __pthread_create(pthread_t* thread, const pthread_attr_t* attr,
-                   void *(*start_routine)(void *), void* arg) 
+static void __pthread_free_tls(struct pthread_tcb *pt)
+{
+       extern void _dl_deallocate_tls (void *tcb, bool dealloc_tcb) internal_function;
+
+       assert(pt->tls_desc);
+       _dl_deallocate_tls(pt->tls_desc, TRUE);
+       pt->tls_desc = NULL;
+}
+
+static int __pthread_allocate_tls(struct pthread_tcb *pt)
+{
+       extern void *_dl_allocate_tls (void *mem) internal_function;
+
+       assert(!pt->tls_desc);
+       pt->tls_desc = _dl_allocate_tls(NULL);
+       if (!pt->tls_desc) {
+               errno = ENOMEM;
+               return -1;
+       }
+       return 0;
+}
+
+// TODO: how big do we want these?  ideally, we want to be able to guard and map
+// more space if we go too far.
+#define PTHREAD_STACK_PAGES 4
+#define PTHREAD_STACK_SIZE (PTHREAD_STACK_PAGES*PGSIZE)
+
+static void __pthread_free_stack(struct pthread_tcb *pt)
+{
+       assert(!munmap(pt->stacktop - PTHREAD_STACK_SIZE, PTHREAD_STACK_SIZE));
+}
+
+static int __pthread_allocate_stack(struct pthread_tcb *pt)
+{
+       void* stackbot = mmap(0, PTHREAD_STACK_SIZE,
+                             PROT_READ|PROT_WRITE|PROT_EXEC,
+                             MAP_POPULATE|MAP_ANONYMOUS, -1, 0);
+       if (stackbot == MAP_FAILED)
+               return -1; // errno set by mmap
+       pt->stacktop = stackbot + PTHREAD_STACK_SIZE;
+       return 0;
+}
+
+void __pthread_run(void)
+{
+       struct pthread_tcb *me = current_thread;
+       pthread_exit(me->start_routine(me->arg));
+}
+
+// Warning, this will reuse numbers eventually
+static int get_next_pid(void)
 {
+       static uint32_t next_pid = 0;
+       return next_pid++;
 }
 
 int pthread_create(pthread_t* thread, const pthread_attr_t* attr,
                    void *(*start_routine)(void *), void* arg)
 {
+       /* After this init, we are an MCP and the caller is a pthread */
        pthread_once(&init_once,&_pthread_init);
+
+       struct pthread_tcb *t = pthread_self();
+       assert(t);
+       /* Don't migrate this thread to anothe vcore, since it depends on being on
+        * the same vcore throughout. */
+       t->dont_migrate = TRUE;
+
+       uint32_t vcoreid = vcore_id();
+
        // Most fields already zeroed out by the calloc below...
        *thread = (pthread_t)calloc(sizeof(struct pthread_tcb), 1);
        (*thread)->start_routine = start_routine;
        (*thread)->arg = arg;
-       (*thread)->pthreadid = 3413; // TODO
-       
-       // TODO: Allocate pthread tls regions and stacks
-       
+       (*thread)->id = get_next_pid();
+       if (__pthread_allocate_stack(*thread) ||  __pthread_allocate_tls(*thread))
+               printf("We're fucked\n");
+       /* Save the ptr to the new pthread in that pthread's TLS */
+       set_tls_desc((*thread)->tls_desc, vcoreid);
+       current_thread = *thread;
+       set_tls_desc(t->tls_desc, vcoreid);
+
+       /* Set the u_tf to start up in __pthread_run, which will call the real
+        * start_routine and pass it the arg. */
+       init_user_tf(&(*thread)->utf, (uint32_t)__pthread_run, 
+                 (uint32_t)((*thread)->stacktop));
+
        // Insert the newly created thread into the ready queue of threads.
        // It will be removed from this queue later when vcore_entry() comes up
        mcs_lock_lock(&queue_lock);
-       {
-               TAILQ_INSERT_TAIL(&ready_queue, *thread, next);
-               threads_ready++;
-       }
+       TAILQ_INSERT_TAIL(&ready_queue, *thread, next);
+       threads_ready++;
        mcs_lock_unlock(&queue_lock);
 
+       /* Okay to migrate now. */
+       t->dont_migrate = FALSE;
+
        // Attempt to request a new core, may or may not get it...
        vcore_request(1);
-       
+
        return 0;
 }
 
-int pthread_join(pthread_t t, void** arg)
+int pthread_join(pthread_t thread, void** retval)
 {
-  volatile pthread_t thread = t;
-  while(!thread->finished);
-  if(arg) *arg = thread->arg;
-  free(thread);
-  return 0;
+       /* Not sure if this is the right semantics.  There is a race if we deref
+        * thread and he is already freed (which would have happened if he was
+        * detached. */
+       if (thread->detached) {
+               printf("[pthread] trying to join on a detached pthread");
+               return -1;
+       }
+       // TODO: something smarter than spinning (deschedule, etc)
+       while(!thread->finished)
+               cpu_relax(); // has a memory clobber
+       if (retval)
+               *retval = thread->retval;
+       free(thread);
+       return 0;
 }
 
 int pthread_mutexattr_init(pthread_mutexattr_t* attr)
@@ -166,6 +270,7 @@ int pthread_mutexattr_destroy(pthread_mutexattr_t* attr)
 
 int pthread_attr_setdetachstate(pthread_attr_t *__attr,int __detachstate) {
        *__attr = __detachstate;
+       // TODO: the right thing
        return 0;
 }
 
@@ -283,8 +388,7 @@ int pthread_condattr_getpshared(pthread_condattr_t *a, int *s)
 
 pthread_t pthread_self()
 {
-  //return active_threads[vcore_id()];
-  return 0; // TODO
+  return current_thread;
 }
 
 int pthread_equal(pthread_t t1, pthread_t t2)
@@ -292,27 +396,63 @@ int pthread_equal(pthread_t t1, pthread_t t2)
   return t1 == t2;
 }
 
+/* Need to have this as a separate, non-inlined function since we clobber the
+ * stack pointer before calling it, and don't want the compiler to play games
+ * with my hart. */
+static void __attribute__((noinline)) internal_function
+__pthread_exit(struct pthread_tcb *t)
+{
+       __pthread_free_tls(t);
+       __pthread_free_stack(t);
+       if (t->detached)
+               free(t);
+       /* Once we do this, our joiner can free us.  He won't free us if we're
+        * detached, but there is still a potential race there (since he's accessing
+        * someone who is freed. */
+       t->finished = 1;
+       current_thread = NULL;
+       /* Go back to the entry point, where we can handle notifications or
+        * reschedule someone. */
+       vcore_entry();
+}
+
+/* This function cannot be migrated to a different vcore by the userspace
+ * scheduler.  Will need to sort that shit out.  */
 void pthread_exit(void* ret)
 {
-  pthread_t t = pthread_self();
+       struct pthread_tcb *t = pthread_self();
+       /* Don't migrate this thread to anothe vcore, since it depends on being on
+        * the same vcore throughout. */
+       assert(t);
+       t->dont_migrate = TRUE; // won't set this to false later, since he is dying
 
-#if 0
-  mcs_lock_lock(&queue_lock);
-  threads_active--;
-  if(threads_active == 0)
-    exit(0);
-  mcs_lock_unlock(&queue_lock);
+       uint32_t vcoreid = vcore_id();
+       struct preempt_data *vcpd = &__procdata.vcore_preempt_data[vcoreid];
 
-  if(t)
-  {
-    t->arg = ret;
-    t->finished = 1;
-    if(t->detached)
-      free(t);
-  }
+       t->retval = ret;
+       mcs_lock_lock(&queue_lock);
+       threads_active--;
+       TAILQ_REMOVE(&active_queue, t, next);
+       mcs_lock_unlock(&queue_lock);
 
-  vcore_entry();
-#endif
+       printf("[P] Pthread id %d is exiting on vcore %d\n", t->id, vcoreid);
+       
+       /* once we do this, we might miss a notif_pending, so we need to enter vcore
+        * entry later. */
+       vcpd->notif_enabled = FALSE;
+
+       /* 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_thread == t);    
+       /* After this, make sure you don't use local variables.  Also, make sure the
+        * compiler doesn't use them without telling you (TODO).  We take some space
+        * off the top of the stack since the compiler is going to assume it has a
+        * stack frame setup when it pushes (actually moves) the current_thread onto
+        * the stack before calling __pthread_cleanup(). */
+       set_stack_pointer((void*)vcpd->transition_stack - 32);
+       /* Finish exiting in another function.  Ugh. */
+       __pthread_exit(current_thread);
 }
 
 int pthread_once(pthread_once_t* once_control, void (*init_routine)(void))
index 0289451..096fccb 100644 (file)
@@ -1,3 +1,4 @@
+#include <arch/arch.h>
 #include <stdbool.h>
 #include <errno.h>
 #include <vcore.h>
@@ -9,14 +10,6 @@
 #include <sys/mman.h>
 #include <rstdio.h>
 
-// Only need in this file because _dl_allocate and friends are
-//  internal functions in glibc
-# ifdef __i386__
-#  define internal_function   __attribute ((regparm (3), stdcall))
-# else
-#  define internal_function
-# endif
-
 /* starting with 1 since we alloc vcore0's stacks and TLS in vcore_init(). */
 static size_t _max_vcores_ever_wanted = 1;
 static mcs_lock_t _vcore_lock = MCS_LOCK_INIT;
@@ -48,9 +41,6 @@ static int allocate_transition_tls(int id)
        return 0;
 }
 
-#define TRANSITION_STACK_PAGES 2
-#define TRANSITION_STACK_SIZE (TRANSITION_STACK_PAGES*PGSIZE)
-
 static void free_transition_stack(int id)
 {
        // don't actually free stacks