pthread_yield()
authorBarret Rhoden <brho@cs.berkeley.edu>
Mon, 19 Apr 2010 06:28:33 +0000 (23:28 -0700)
committerKevin Klues <klueska@cs.berkeley.edu>
Thu, 3 Nov 2011 00:35:43 +0000 (17:35 -0700)
This allows a pthread to yield, which basically saves its state, puts it
on the ready queue, and jumps to the transition stack / vcore_entry().
Lightly tested.

This needs save_ros_tf() to be implemented on sparc.

kern/arch/sparc/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/sparc/vcore.h
user/parlib/pthread.c

index 64eabfa..9caa5c9 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef ROS_INCLUDE_ARCH_TRAPFRAME_H
 #define ROS_INCLUDE_ARCH_TRAPFRAME_H
 
+#include <ros/common.h>
+
 typedef struct trapframe
 {
        uint32_t gpr[32] __attribute__((aligned (8)));
index b84de8d..6dc52fa 100644 (file)
@@ -17,7 +17,12 @@ void *my_retvals[10];
 __thread int my_id;
 void *thread(void* arg)
 {      
-       printf_safe("[A] pthread %d on vcore %d\n", pthread_self()->id, vcore_id());
+       for (int i = 0; i < 10; i++) {
+               printf_safe("[A] pthread %d on vcore %d\n", pthread_self()->id, vcore_id());
+               pthread_yield();
+               printf_safe("[A] pthread %d returned from yield on vcore %d\n",
+                           pthread_self()->id, vcore_id());
+       }
        return (void*)(pthread_self()->id);
 }
 
@@ -26,6 +31,14 @@ int main(int argc, char** argv)
        void *retval1 = 0;
        void *retval2 = 0;
 
+       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);
+       while(1);
+
        while (1) {
                for (int i = 1; i < 10; i++) {
                        printf_safe("[A] About to create thread %d\n", i);
index 6ba010c..e5ade85 100644 (file)
@@ -39,14 +39,4 @@ 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 a883977..86fe86a 100644 (file)
@@ -87,6 +87,42 @@ static inline void pop_ros_tf(struct user_trapframe *tf, uint32_t vcoreid)
                      : "memory");
 }
 
+/* Save the current context/registers into the given tf, setting the pc of the
+ * tf to the end of this function.  You only need to save that which you later
+ * restore with pop_ros_tf(). */
+static inline void save_ros_tf(struct user_trapframe *tf)
+{
+       memset(tf, 0, sizeof(struct user_trapframe)); /* sanity */
+       /* set CS and make sure eflags is okay */
+       tf->tf_cs = GD_UT | 3;
+       tf->tf_eflags = 0x00000200; /* interrupts enabled.  bare minimum eflags. */
+       /* Save the regs and the future esp. */
+       asm volatile("movl %%esp,(%0);       " /* save esp in it's slot*/
+                    "pushl %%eax;           " /* temp save eax */
+                    "leal 1f,%%eax;         " /* get future eip */
+                    "movl %%eax,(%1);       " /* store future eip */
+                    "popl %%eax;            " /* restore eax */
+                    "movl %2,%%esp;         " /* move to the beginning of the tf */
+                    "addl $0x20,%%esp;      " /* move to after the push_regs */
+                    "pushal;                " /* save regs */
+                    "addl $0x44,%%esp;      " /* move to esp slot */
+                    "popl %%esp;            " /* restore esp */
+                    "1:                     " /* where this tf will restart */
+                    : 
+                    : "g"(&tf->tf_esp), "g"(&tf->tf_eip), "g"(tf)
+                    : "eax", "memory", "cc");
+}
+
+/* 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;
+}
+
 /* Reading from the LDT.  Could also use %gs, but that would require including
  * half of libc's TLS header.  Sparc will probably ignore the vcoreid, so don't
  * rely on it too much.  The intent of it is vcoreid is the caller's vcoreid,
@@ -127,4 +163,30 @@ __vcore_id()
        return (int)ros_syscall(SYS_getvcoreid,0,0,0,0,0);
 }
 
+/* For debugging. */
+#include <rstdio.h>
+static __inline void print_trapframe(struct user_trapframe *tf)
+{
+       printf("[user] TRAP frame\n");
+       printf("  edi  0x%08x\n", tf->tf_regs.reg_edi);
+       printf("  esi  0x%08x\n", tf->tf_regs.reg_esi);
+       printf("  ebp  0x%08x\n", tf->tf_regs.reg_ebp);
+       printf("  oesp 0x%08x\n", tf->tf_regs.reg_oesp);
+       printf("  ebx  0x%08x\n", tf->tf_regs.reg_ebx);
+       printf("  edx  0x%08x\n", tf->tf_regs.reg_edx);
+       printf("  ecx  0x%08x\n", tf->tf_regs.reg_ecx);
+       printf("  eax  0x%08x\n", tf->tf_regs.reg_eax);
+       printf("  gs   0x----%04x\n", tf->tf_gs);
+       printf("  fs   0x----%04x\n", tf->tf_fs);
+       printf("  es   0x----%04x\n", tf->tf_es);
+       printf("  ds   0x----%04x\n", tf->tf_ds);
+       printf("  trap 0x%08x\n", tf->tf_trapno);
+       printf("  err  0x%08x\n", tf->tf_err);
+       printf("  eip  0x%08x\n", tf->tf_eip);
+       printf("  cs   0x----%04x\n", tf->tf_cs);
+       printf("  flag 0x%08x\n", tf->tf_eflags);
+       printf("  esp  0x%08x\n", tf->tf_esp);
+       printf("  ss   0x----%04x\n", tf->tf_ss);
+}
+
 #endif /* PARLIB_ARCH_VCORE_H */
index 97b2049..8532dc3 100644 (file)
@@ -25,6 +25,8 @@ struct pthread_tcb {
        void *retval;
        int detached;
        // whether or not the scheduler can migrate you from your vcore
+       // will be slightly difficult to see this when given just a vcoreid and
+       // notif_tf ptr
        bool dont_migrate;
 };
 typedef struct pthread_tcb* pthread_t;
@@ -89,6 +91,7 @@ int pthread_attr_destroy(pthread_attr_t *);
 int pthread_create(pthread_t *, const pthread_attr_t *,
                    void *(*)(void *), void *);
 int pthread_join(pthread_t, void **);
+int pthread_yield(void);
 
 int pthread_attr_setdetachstate(pthread_attr_t *__attr,int __detachstate);
 
index a75f818..cdf5203 100644 (file)
@@ -50,14 +50,4 @@ cpu_relax(void)
                     "=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 e211ee2..8e62675 100644 (file)
@@ -40,6 +40,24 @@ static inline void pop_ros_tf(struct user_trapframe *tf, uint32_t vcoreid)
        asm volatile ("mov %0, %%o0; ta 4" : : "r"(tf) : "memory");
 }
 
+/* Save the current context/registers into the given tf, setting the pc of the
+ * tf to the end of this function.  You only need to save that which you later
+ * restore with pop_ros_tf(). */
+static inline void save_ros_tf(struct user_trapframe *tf)
+{
+       // TODO!!
+}
+
+/* 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;
+}
+
 /* Feel free to ignore vcoreid.  It helps x86 to avoid a call to
  * sys_getvcoreid() if we pass it in. */
 static inline void *get_tls_desc(uint32_t vcoreid)
index 71179fe..1f9d9cb 100644 (file)
@@ -106,11 +106,12 @@ void __attribute__((noreturn)) vcore_entry()
        }
        mcs_lock_unlock(&queue_lock);
        if (!new_thread) {
-               printf("[P] No threads, vcore %d is yielding\n", vcoreid);
+               printd("[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;
+       printd("[P] Vcore %d is starting pthread %d\n", vcoreid, new_thread->id);
 
        /* Do one last check for notifs before clearing pending */
        // TODO: call the handle_notif() here (first)
@@ -118,7 +119,7 @@ void __attribute__((noreturn)) vcore_entry()
        set_tls_desc(new_thread->tls_desc, vcoreid);
 
        /* Load silly state (Floating point) too.  For real */
-       // TODO
+       // TODO: (HSS)
 
        /* Pop the user trap frame */
        pop_ros_tf(&new_thread->utf, vcoreid);
@@ -202,10 +203,7 @@ int pthread_create(pthread_t* thread, const pthread_attr_t* attr,
        /* 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;
@@ -216,25 +214,20 @@ int pthread_create(pthread_t* thread, const pthread_attr_t* attr,
        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
+       /* 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++;
        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...
+       /* Attempt to request a new core, may or may not get it... */
        vcore_request(1);
-
        return 0;
 }
 
@@ -256,6 +249,74 @@ int pthread_join(pthread_t thread, void** retval)
        return 0;
 }
 
+static void __attribute__((noinline, noreturn)) 
+__pthread_yield(struct pthread_tcb *t)
+{
+       /* TODO: want to set this to FALSE once we no longer depend on being on this
+        * vcore.  Though if we are using TLS, we are depending on the vcore.  Since
+        * notifs are disabled and we are in a transition context, we probably
+        * shouldn't be moved anyway.  It does mean that a pthread could get jammed.
+        * If we do this after putting it on the active list, we'll have a race on
+        * dont_migrate. */
+       t->dont_migrate = FALSE;
+       /* Take from the active list, and put on the ready list (tail).  Don't do
+        * this until we are done completely with the thread, since it can be
+        * restarted somewhere else. */
+       mcs_lock_lock(&queue_lock);
+       threads_active--;
+       TAILQ_REMOVE(&active_queue, t, next);
+       threads_ready++;
+       TAILQ_INSERT_TAIL(&ready_queue, t, next);
+       mcs_lock_unlock(&queue_lock);
+       /* Leave the current vcore completely */
+       current_thread = NULL; // this might be okay, even with a migration
+       /* Go back to the entry point, where we can handle notifications or
+        * reschedule someone. */
+       vcore_entry();
+}
+
+int pthread_yield(void)
+{
+       struct pthread_tcb *t = pthread_self();
+       volatile bool yielding = TRUE; /* signal to short circuit when restarting */
+
+       /* TODO: (HSS) Save silly state */
+       // save_fp_state(&t->as);
+
+       /* Don't migrate this thread to another vcore, since it depends on being on
+        * the same vcore throughout (once it disables notifs). */
+       t->dont_migrate = TRUE;
+       uint32_t vcoreid = vcore_id();
+       printd("[P] Pthread id %d is yielding on vcore %d\n", t->id, vcoreid);
+       struct preempt_data *vcpd = &__procdata.vcore_preempt_data[vcoreid];
+       /* once we do this, we might miss a notif_pending, so we need to enter vcore
+        * entry later.  Need to disable notifs so we don't get in weird loops with
+        * save_ros_tf() and pop_ros_tf(). */
+       vcpd->notif_enabled = FALSE;
+       /* take the current state and save it into t->utf when this pthread
+        * restarts, it will continue from right after this, see yielding is false,
+        * and short ciruit the function. */
+       save_ros_tf(&t->utf);
+       if (!yielding)
+               goto yield_return_path;
+       yielding = FALSE; /* for when it starts back up */
+       /* 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.  Note the warning in
+        * pthread_exit() */
+       set_stack_pointer((void*)vcpd->transition_stack);
+       /* Finish exiting in another function. */
+       __pthread_yield(current_thread);
+       /* Should never get here */
+       assert(0);
+       /* Will jump here when the pthread's trapframe is restarted/popped. */
+yield_return_path:
+       printd("[P] pthread %d returning from a yield!\n", t->id);
+       return 0;
+}
+
 int pthread_mutexattr_init(pthread_mutexattr_t* attr)
 {
   attr->type = PTHREAD_MUTEX_DEFAULT;
@@ -423,7 +484,6 @@ void pthread_exit(void* ret)
        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
 
        uint32_t vcoreid = vcore_id();
@@ -435,7 +495,7 @@ void pthread_exit(void* ret)
        TAILQ_REMOVE(&active_queue, t, next);
        mcs_lock_unlock(&queue_lock);
 
-       printf("[P] Pthread id %d is exiting on vcore %d\n", t->id, vcoreid);
+       printd("[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. */