Pthread state tracking
authorBarret Rhoden <brho@cs.berkeley.edu>
Wed, 18 Apr 2012 20:52:12 +0000 (13:52 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Wed, 18 Apr 2012 20:52:12 +0000 (13:52 -0700)
We don't do anything smart in pth_thread_runnable(), but that's where
you can make different decisions on what to do with the thread based on
why it slept/woke up.

All yielding/blocking paths will touch the 2LS (pthread in this case),
and that is where the 2LS can record info (states) for later
(thread_runnable).

user/pthread/pthread.c
user/pthread/pthread.h

index 6c7d476..0eb1ff8 100644 (file)
@@ -102,6 +102,7 @@ void __attribute__((noreturn)) pth_sched_entry(void)
                 * bit before yielding (or not at all if you want to be greedy). */
                vcore_yield(FALSE);
        } while (1);
+       assert(new_thread->state == PTH_RUNNABLE);
        run_uthread((struct uthread*)new_thread);
        assert(0);
 }
@@ -116,6 +117,23 @@ static void __pthread_run(void)
 void pth_thread_runnable(struct uthread *uthread)
 {
        struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
+       /* At this point, the 2LS can see why the thread blocked and was woken up in
+        * the first place (coupling these things together).  On the yield path, the
+        * 2LS was involved and was able to set the state.  Now when we get the
+        * thread back, we can take a look. */
+       printd("pthread %08p runnable, state was %d\n", pthread, pthread->state);
+       switch (pthread->state) {
+               case (PTH_CREATED):
+               case (PTH_BLK_YIELDING):
+               case (PTH_BLK_JOINING):
+               case (PTH_BLK_SYSC):
+               case (PTH_BLK_MUTEX):
+                       /* can do whatever for each of these cases */
+                       break;
+               default:
+                       printf("Odd state %d for pthread %08p\n", pthread->state, pthread);
+       }
+       pthread->state = PTH_RUNNABLE;
        /* 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_pdr_lock(&queue_lock);
@@ -139,7 +157,7 @@ void pth_thread_yield(struct uthread *uthread)
        threads_active--;
        TAILQ_REMOVE(&active_queue, pthread, next);
        mcs_pdr_unlock(&queue_lock);
-       if (pthread->flags & PTHREAD_EXITING) {
+       if (pthread->state == PTH_EXITING) {
                /* Destroy the pthread */
                uthread_cleanup(uthread);
                /* Cleanup, mirroring pthread_create() */
@@ -158,7 +176,7 @@ void pth_thread_yield(struct uthread *uthread)
                                uthread_runnable((struct uthread*)temp_pth);
                        }
                }
-       } else if (pthread->flags & PTHREAD_JOINING) {
+       } else if (pthread->state == PTH_BLK_JOINING) {
                /* We're trying to join, yield til we get woken up */
                /* put ourselves in the join target's joiner slot.  If we get anything
                 * back, we lost the race and need to wake ourselves. */
@@ -175,6 +193,7 @@ void pth_thread_yield(struct uthread *uthread)
                        uthread_runnable((struct uthread*)pthread);
                }
        } else {
+               assert(pthread->state == PTH_BLK_YIELDING);
                /* Yielding for no apparent reason (being nice / help break deadlocks).
                 * Just wake it up and make it ready again. */
                uthread_runnable((struct uthread*)pthread);
@@ -222,7 +241,7 @@ static void restart_thread(struct syscall *sysc)
        struct uthread *ut_restartee = (struct uthread*)sysc->u_data;
        /* uthread stuff here: */
        assert(ut_restartee);
-       //assert(ut_restartee->state == UT_BLOCKED);
+       assert(((struct pthread_tcb*)ut_restartee)->state == PTH_BLK_SYSC);
        assert(ut_restartee->sysc == sysc);
        ut_restartee->sysc = 0; /* so we don't 'reblock' on this later */
        uthread_runnable(ut_restartee);
@@ -260,9 +279,9 @@ void pth_blockon_sysc(struct syscall *sysc)
        bool need_to_restart = FALSE;
        uint32_t vcoreid = vcore_id();
 
-       //assert(current_uthread->state == UT_BLOCKED);
        /* rip from the active queue */
        struct pthread_tcb *pthread = (struct pthread_tcb*)current_uthread;
+       pthread->state = PTH_BLK_SYSC;
        mcs_pdr_lock(&queue_lock);
        threads_active--;
        TAILQ_REMOVE(&active_queue, pthread, next);
@@ -346,7 +365,7 @@ static int pthread_lib_init(void)
        t->stacksize = USTACK_NUM_PAGES * PGSIZE;
        t->stacktop = (void*)USTACKTOP;
        t->detached = TRUE;
-       t->flags = 0;
+       t->state = PTH_RUNNING;
        t->join_target = 0;
        t->joiner = 0;
        assert(t->id == 0);
@@ -426,7 +445,7 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
        pthread = (pthread_t)calloc(1, sizeof(struct pthread_tcb));
        assert(pthread);
        pthread->stacksize = PTHREAD_STACK_SIZE;        /* default */
-       pthread->flags = 0;
+       pthread->state = PTH_CREATED;
        pthread->id = get_next_pid();
        pthread->detached = FALSE;                              /* default */
        pthread->join_target = 0;
@@ -469,7 +488,7 @@ int pthread_join(pthread_t thread, void** retval)
         * early check is an optimization, pth_thread_yield() handles the race). */
        if (!thread->joiner) {
                /* Time to join, set things up so pth_thread_yield() knows what to do */
-               caller->flags |= PTHREAD_JOINING;
+               caller->state = PTH_BLK_JOINING;
                caller->join_target = thread;
                uthread_yield(TRUE);
                /* When we return/restart, the thread will be done */
@@ -484,6 +503,8 @@ int pthread_join(pthread_t thread, void** retval)
 
 int pthread_yield(void)
 {
+       struct pthread_tcb *caller = (struct pthread_tcb*)current_uthread;
+       caller->state = PTH_BLK_YIELDING;
        uthread_yield(TRUE);
        return 0;
 }
@@ -667,7 +688,7 @@ void pthread_exit(void *ret)
        struct pthread_tcb *pthread = pthread_self();
        pthread->retval = ret;
        /* So our pth_thread_yield knows we want to exit */
-       pthread->flags |= PTHREAD_EXITING;
+       pthread->state = PTH_EXITING;
        uthread_yield(FALSE);
 }
 
index f6fc594..0e64ccc 100644 (file)
   extern "C" {
 #endif
 
-/* Flags */
-#define PTHREAD_EXITING                0x001
-#define PTHREAD_JOINING                0x002
+/* Pthread states.  These are mostly examples for other 2LSs */
+#define PTH_CREATED                    1
+#define PTH_RUNNABLE           2
+#define PTH_RUNNING                    3
+#define PTH_EXITING                    4
+#define PTH_BLK_YIELDING       5       /* brief state btw pth_yield and pth_runnable */
+#define PTH_BLK_JOINING                6       /* joining on a child */
+#define PTH_BLK_SYSC           7       /* blocked on a syscall */
+#define PTH_BLK_MUTEX          8       /* blocked externally, possibly on a mutex */
 
 /* Pthread struct.  First has to be the uthread struct, which the vcore code
  * will access directly (as if pthread_tcb is a struct uthread). */
@@ -20,7 +26,7 @@ struct pthread_tcb;
 struct pthread_tcb {
        struct uthread uthread;
        TAILQ_ENTRY(pthread_tcb) next;
-       int flags;
+       int state;
        bool detached;
        struct pthread_tcb *join_target;        /* only used to communicate with yield*/
        struct pthread_tcb *joiner;                     /* raced on by exit and join */