X-Git-Url: http://akaros.cs.berkeley.edu/gitweb/?p=akaros.git;a=blobdiff_plain;f=user%2Fpthread%2Fpthread.c;h=1a4d3f82a15ceb6648160c289799c6eb571a1bbe;hp=59faf384786eb6f0984c410a2e0555c4fe0475bd;hb=1b64bce15d420eb72455d3050e7357da1b3096c0;hpb=3ceb3ef060e850a7f3da7d3ac58b738d18b694b9 diff --git a/user/pthread/pthread.c b/user/pthread/pthread.c index 59faf38..1a4d3f8 100644 --- a/user/pthread/pthread.c +++ b/user/pthread/pthread.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -17,6 +16,24 @@ #include #include #include +#include +#include +#include + +#include +#include +#include + +/* TODO: eventually, we probably want to split this into the pthreads interface + * and a default 2LS. That way, apps can use the pthreads interface and use any + * 2LS. Here's a few blockers: + * - pthread_cleanup(): probably support at the uthread level + * - attrs and creation: probably use a default stack size and handle detached + * - getattrs_np: return -1, mostly due to the stackaddr. Callers probably want + * a real 2LS operation. + * Then we can split pthreads into parlib/default_sched.c (replaces thread0) and + * pthread.c. After that, we can have a signal handling thread (even for + * 'thread0'), which allows us to close() or do other vcore-ctx-unsafe ops. */ struct pthread_queue ready_queue = TAILQ_HEAD_INITIALIZER(ready_queue); struct pthread_queue active_queue = TAILQ_HEAD_INITIALIZER(active_queue); @@ -25,6 +42,8 @@ int threads_ready = 0; int threads_active = 0; atomic_t threads_total; bool need_tls = TRUE; +static uint64_t fork_generation; +#define INIT_FORK_GENERATION 1 /* Array of per-vcore structs to manage waiting on syscalls and handling * overflow. Init'd in pth_init(). */ @@ -32,10 +51,10 @@ struct sysc_mgmt *sysc_mgmt = 0; /* Helper / local functions */ static int get_next_pid(void); -static inline void spin_to_sleep(unsigned int spins, unsigned int *spun); static inline void pthread_exit_no_cleanup(void *ret); /* Pthread 2LS operations */ +static void pth_sched_init(void); static void pth_sched_entry(void); static void pth_thread_runnable(struct uthread *uthread); static void pth_thread_paused(struct uthread *uthread); @@ -43,20 +62,31 @@ static void pth_thread_blockon_sysc(struct uthread *uthread, void *sysc); static void pth_thread_has_blocked(struct uthread *uthread, int flags); static void pth_thread_refl_fault(struct uthread *uth, struct user_context *ctx); +static void pth_thread_exited(struct uthread *uth); +static struct uthread *pth_thread_create(void *(*func)(void *), void *arg); +static void pth_got_posix_signal(int sig_nr, struct siginfo *info); +static void pth_thread_bulk_runnable(uth_sync_t *wakees); /* Event Handlers */ static void pth_handle_syscall(struct event_msg *ev_msg, unsigned int ev_type, void *data); struct schedule_ops pthread_sched_ops = { + .sched_init = pth_sched_init, .sched_entry = pth_sched_entry, .thread_runnable = pth_thread_runnable, .thread_paused = pth_thread_paused, .thread_blockon_sysc = pth_thread_blockon_sysc, .thread_has_blocked = pth_thread_has_blocked, .thread_refl_fault = pth_thread_refl_fault, + .thread_exited = pth_thread_exited, + .thread_create = pth_thread_create, + .got_posix_signal = pth_got_posix_signal, + .thread_bulk_runnable = pth_thread_bulk_runnable, }; +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); @@ -69,8 +99,8 @@ static void __attribute__((noreturn)) pth_sched_entry(void) { 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. */ + /* Prep the pthread to run any pending posix signal handlers + * registered via pthread_kill once it is restored. */ uthread_prep_pending_signals(current_uthread); /* Run the thread itself */ run_current_uthread(); @@ -78,14 +108,20 @@ static void __attribute__((noreturn)) pth_sched_entry(void) } /* no one currently running, so lets get someone from the ready queue */ struct pthread_tcb *new_thread = NULL; - /* Try to get a thread. If we get one, we'll break out and run it. If not, - * we'll try to yield. vcore_yield() might return, if we lost a race and - * had a new event come in, one that may make us able to get a new_thread */ + + /* Try to get a thread. If we get one, we'll break out and run it. If + * not, we'll try to yield. vcore_yield() might return, if we lost a + * race and had a new event come in, one that may make us able to get a + * new_thread */ do { handle_events(vcoreid); __check_preempt_pending(vcoreid); mcs_pdr_lock(&queue_lock); - new_thread = TAILQ_FIRST(&ready_queue); + TAILQ_FOREACH(new_thread, &ready_queue, tq_next) { + if (new_thread->fork_generation < fork_generation) + continue; + break; + } if (new_thread) { TAILQ_REMOVE(&ready_queue, new_thread, tq_next); assert(new_thread->state == PTH_RUNNABLE); @@ -94,8 +130,9 @@ static void __attribute__((noreturn)) pth_sched_entry(void) threads_active++; threads_ready--; mcs_pdr_unlock(&queue_lock); - /* If you see what looks like the same uthread running in multiple - * places, your list might be jacked up. Turn this on. */ + /* If you see what looks like the same uthread running + * in multiple places, your list might be jacked up. + * Turn this on. */ printd("[P] got uthread %08p on vc %d state %08p flags %08p\n", new_thread, vcoreid, ((struct uthread*)new_thread)->state, @@ -105,12 +142,12 @@ static void __attribute__((noreturn)) pth_sched_entry(void) mcs_pdr_unlock(&queue_lock); /* no new thread, try to yield */ printd("[P] No threads, vcore %d is yielding\n", vcore_id()); - /* TODO: you can imagine having something smarter here, like spin for a - * bit before yielding. */ + /* TODO: you can imagine having something smarter here, like + * spin for a bit before yielding. */ vcore_yield(FALSE); } while (1); /* Prep the pthread to run any pending posix signal handlers registered - * via pthread_kill once it is restored. */ + * via pthread_kill once it is restored. */ uthread_prep_pending_signals((struct uthread*)new_thread); /* Run the thread itself */ run_uthread((struct uthread*)new_thread); @@ -129,33 +166,36 @@ static void __pthread_run(void) static 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); + + /* 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_PAUSED): - case (PTH_BLK_MUTEX): - /* can do whatever for each of these cases */ - break; - default: - panic("Odd state %d for pthread %08p\n", pthread->state, pthread); + case (PTH_CREATED): + case (PTH_BLK_YIELDING): + case (PTH_BLK_SYSC): + case (PTH_BLK_PAUSED): + case (PTH_BLK_MUTEX): + case (PTH_BLK_MISC): + /* can do whatever for each of these cases */ + break; + default: + panic("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 */ + /* 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); /* Again, GIANT WARNING: if you change this, change batch wakeup code */ TAILQ_INSERT_TAIL(&ready_queue, pthread, tq_next); threads_ready++; mcs_pdr_unlock(&queue_lock); - /* Smarter schedulers should look at the num_vcores() and how much work is - * going on to make a decision about how many vcores to request. */ + /* Smarter schedulers should look at the num_vcores() and how much work + * is going on to make a decision about how many vcores to request. */ vcore_request_more(threads_ready); } @@ -176,9 +216,9 @@ static void pth_thread_paused(struct uthread *uthread) __pthread_generic_yield(pthread); /* communicate to pth_thread_runnable */ pthread->state = PTH_BLK_PAUSED; - /* At this point, you could do something clever, like put it at the front of - * the runqueue, see if it was holding a lock, do some accounting, or - * whatever. */ + /* At this point, you could do something clever, like put it at the + * front of the runqueue, see if it was holding a lock, do some + * accounting, or whatever. */ pth_thread_runnable(uthread); } @@ -203,14 +243,16 @@ static void pth_handle_syscall(struct event_msg *ev_msg, unsigned int ev_type, struct syscall *sysc; assert(in_vcore_context()); /* if we just got a bit (not a msg), it should be because the process is - * still an SCP and hasn't started using the MCP ev_q yet (using the simple - * ev_q and glibc's blockon) or because the bit is still set from an old - * ev_q (blocking syscalls from before we could enter vcore ctx). Either - * way, just return. Note that if you screwed up the pth ev_q and made it - * NO_MSG, you'll never notice (we used to assert(ev_msg)). */ + * still an SCP and hasn't started using the MCP ev_q yet (using the + * simple ev_q and glibc's blockon) or because the bit is still set from + * an old ev_q (blocking syscalls from before we could enter vcore ctx). + * Either way, just return. Note that if you screwed up the pth ev_q + * and made it NO_MSG, you'll never notice (we used to assert(ev_msg)). + * */ if (!ev_msg) return; - /* It's a bug if we don't have a msg (we're handling a syscall bit-event) */ + /* It's a bug if we don't have a msg (we're handling a syscall + * bit-event) */ assert(ev_msg); /* Get the sysc from the message and just restart it */ sysc = ev_msg->ev_arg3; @@ -235,8 +277,8 @@ static void pth_thread_blockon_sysc(struct uthread *uthread, void *syscall) sysc->u_data = uthread; /* Register our vcore's syscall ev_q to hear about this syscall. */ if (!register_evq(sysc, sysc_mgmt[vcoreid].ev_q)) { - /* Lost the race with the call being done. The kernel won't send the - * event. Just restart him. */ + /* Lost the race with the call being done. The kernel won't + * send the event. Just restart him. */ restart_thread(sysc); } /* GIANT WARNING: do not touch the thread after this point. */ @@ -247,14 +289,19 @@ static void pth_thread_has_blocked(struct uthread *uthread, int flags) struct pthread_tcb *pthread = (struct pthread_tcb*)uthread; __pthread_generic_yield(pthread); - /* could imagine doing something with the flags. For now, we just treat all - * externally blocked reasons as 'MUTEX'. Whatever we do here, we are - * mostly communicating to our future selves in pth_thread_runnable(), which - * gets called by whoever triggered this callback */ - pthread->state = PTH_BLK_MUTEX; - /* Just for yucks: */ - if (flags == UTH_EXT_BLK_JUSTICE) - printf("For great justice!\n"); + /* Whatever we do here, we are mostly communicating to our future selves + * in pth_thread_runnable(), which gets called by whoever triggered this + * callback */ + switch (flags) { + case UTH_EXT_BLK_YIELD: + pthread->state = PTH_BLK_YIELDING; + break; + case UTH_EXT_BLK_MUTEX: + pthread->state = PTH_BLK_MUTEX; + break; + default: + pthread->state = PTH_BLK_MISC; + }; } static void __signal_and_restart(struct uthread *uthread, @@ -270,9 +317,84 @@ static void handle_div_by_zero(struct uthread *uthread, unsigned int err, __signal_and_restart(uthread, SIGFPE, FPE_INTDIV, (void*)aux); } +// checks that usys in go passes its arguments correctly +// it only automatically checks with 7 arguments, print is for the rest +int go_usys_tester(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, + uint64_t f, uint64_t g, uint64_t h, uint64_t i, uint64_t j, + uint64_t k, uint64_t l) +{ + printf("a = %lu, b = %lu, c = %lu, d = %lu, e = %lu, f = %lu, g = %lu, h = %lu, i = %lu, j = %lu, k = %lu, l = %lu\n", + a, b, c, d, e, f, g, h, i, j, k, l); + uint64_t ret_val = 0; + + ret_val |= a; + ret_val |= (b << 8); + ret_val |= (c << 16); + ret_val |= (d << 24); + ret_val |= (e << 32); + ret_val |= (f << 40); + ret_val |= (g << 48); + return ret_val; +} + +struct alarm_waiter *abort_syscall_at_abs_unix(uint64_t deadline) +{ + // note the malloc of waiter instead of it going on the stack + struct alarm_waiter *waiter = malloc(sizeof(struct alarm_waiter)); + + init_awaiter(waiter, alarm_abort_sysc); + waiter->data = current_uthread; + set_awaiter_abs_unix(waiter, deadline); + set_alarm(waiter); + return waiter; +} + +bool unset_alarm_with_free(struct alarm_waiter *waiter) +{ + // we need to free the waiter we created in abort_syscall_at_abs_unix + bool ret = unset_alarm(waiter); + + free(waiter); + return ret; +} + +// ros_syscall_sync, but makes sure errors are zeros if there is no error +void go_syscall(struct syscall *sysc) +{ + ros_syscall_sync(sysc); + if (!syscall_retval_is_error(sysc->num, sysc->retval)) { + sysc->err = 0; + sysc->errstr[0] = 0; + } +} + +static void set_up_go_table(void **table) +{ + table[0] = abort_syscall_at_abs_unix; + table[1] = unset_alarm_with_free; + table[2] = go_syscall; + table[3] = go_usys_tester; + table[4] = futex; + table[5] = serialize_argv_envp; + table[6] = free; + assert(table[7] == (void*) 0xDEADBEEF); +} + static void handle_gp_fault(struct uthread *uthread, unsigned int err, unsigned long aux) { + //TODO this code is x86-64 only + uint64_t rax = uthread->u_ctx.tf.hw_tf.tf_rax; + + // we fault with a known high 16 bits in go to set up a function pointer + // table, the address of the table is the low 48 bits + if (rax >> 48 == 0xDEAD) { + set_up_go_table((void **)(0xFFFFFFFFFFFFUL & rax)); + // we jump over the call instruction which is 2 bytes + uthread->u_ctx.tf.hw_tf.tf_rip += 2; + pth_thread_runnable(uthread); + return; + } __signal_and_restart(uthread, SIGSEGV, SEGV_ACCERR, (void*)aux); } @@ -332,8 +454,74 @@ static void pth_thread_refl_fault(struct uthread *uth, } } +static void pth_thread_exited(struct uthread *uth) +{ + struct pthread_tcb *pthread = (struct pthread_tcb*)uth; + + __pthread_generic_yield(pthread); + /* Catch some bugs */ + pthread->state = PTH_EXITING; + /* Destroy the pthread */ + uthread_cleanup(uth); + /* Cleanup, mirroring pthread_create() */ + __pthread_free_stack(pthread); + /* 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); +} + +/* Careful, if someone used the pthread_need_tls() hack to turn off TLS, it will + * also be turned off for these threads. */ +static struct uthread *pth_thread_create(void *(*func)(void *), void *arg) +{ + struct pthread_tcb *pth; + int ret; + + ret = pthread_create(&pth, NULL, func, arg); + return ret == 0 ? (struct uthread*)pth : NULL; +} + +/* Careful, that fake_uctx takes up a lot of stack space. We could call + * pthread_kill too. Note the VMM 2LS has similar code. */ +static void pth_got_posix_signal(int sig_nr, struct siginfo *info) +{ + struct user_context fake_uctx; + + /* If we happen to have a current uthread, we can use that - perhaps + * that's what the user wants. If not, we'll build a fake one + * representing our current call stack. */ + if (current_uthread) { + trigger_posix_signal(sig_nr, info, get_cur_uth_ctx()); + } else { + init_user_ctx(&fake_uctx, (uintptr_t)pth_got_posix_signal, + get_stack_pointer()); + trigger_posix_signal(sig_nr, info, &fake_uctx); + } +} + +static void pth_thread_bulk_runnable(uth_sync_t *wakees) +{ + struct uthread *uth_i; + struct pthread_tcb *pth_i; + + /* Amortize the lock grabbing over all restartees */ + mcs_pdr_lock(&queue_lock); + while ((uth_i = __uth_sync_get_next(wakees))) { + pth_i = (struct pthread_tcb*)uth_i; + pth_i->state = PTH_RUNNABLE; + TAILQ_INSERT_TAIL(&ready_queue, pth_i, tq_next); + threads_ready++; + } + mcs_pdr_unlock(&queue_lock); + vcore_request_more(threads_ready); +} + /* Akaros pthread extensions / hacks */ +/* Careful using this - glibc and gcc are likely to use TLS without you knowing + * it. */ void pthread_need_tls(bool need) { need_tls = need; @@ -369,8 +557,8 @@ static int __pthread_allocate_stack(struct pthread_tcb *pt) int force_a_page_fault; assert(pt->stacksize); void* stackbot = mmap(0, pt->stacksize, - PROT_READ|PROT_WRITE|PROT_EXEC, - MAP_ANONYMOUS, -1, 0); + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (stackbot == MAP_FAILED) return -1; // errno set by mmap pt->stacktop = stackbot + pt->stacksize; @@ -412,7 +600,7 @@ int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize) } int pthread_attr_getstack(const pthread_attr_t *__restrict __attr, - void **__stackaddr, size_t *__stacksize) + void **__stackaddr, size_t *__stacksize) { *__stackaddr = __attr->stackaddr; *__stacksize = __attr->stacksize; @@ -421,59 +609,86 @@ int pthread_attr_getstack(const pthread_attr_t *__restrict __attr, int pthread_getattr_np(pthread_t __th, pthread_attr_t *__attr) { + struct uthread *uth = (struct uthread*)__th; + __attr->stackaddr = __th->stacktop - __th->stacksize; __attr->stacksize = __th->stacksize; - if (__th->detached) + if (atomic_read(&uth->join_ctl.state) == UTH_JOIN_DETACHED) __attr->detachstate = PTHREAD_CREATE_DETACHED; else __attr->detachstate = PTHREAD_CREATE_JOINABLE; return 0; } +/* All multi-threading is suspended during a fork. Thread0 will continue to + * run, which could come up if SYS_fork blocks or we get interrupted. Parents + * will continue threading after the fork, like normal. Old threads in the + * child will never run again. New threads in the child will run. */ +static void pth_pre_fork(void) +{ + struct pthread_tcb *pth_0 = (struct pthread_tcb*)current_uthread; + + if (!uthread_is_thread0(current_uthread)) + panic("Tried to fork from a non-thread0 thread!"); + if (in_multi_mode()) + panic("Tried to fork from an MCP!"); + pth_0->fork_generation = fork_generation + 1; + /* in case we get interrupted after incrementing the global gen */ + cmb(); + /* We're single-core and thread0 here, so we can modify fork_generation + */ + fork_generation++; + /* At this point, whether we come back as the child or the parent, no + * old thread (from the previous generation) will run. */ +} + +static void pth_post_fork(pid_t ret) +{ + struct pthread_tcb *pth_0 = (struct pthread_tcb*)current_uthread; + + if (ret) { + fork_generation--; + pth_0->fork_generation = fork_generation; + } +} + /* Do whatever init you want. At some point call uthread_2ls_init() and pass it * a uthread representing thread0 (int main()) */ -void __attribute__((constructor)) pthread_lib_init(void) +void pth_sched_init(void) { uintptr_t mmap_block; struct pthread_tcb *t; int ret; - /* Only run once, but make sure that uthread_lib_init() has run already. */ - init_once_racy(return); - uthread_lib_init(); - mcs_pdr_init(&queue_lock); + fork_generation = INIT_FORK_GENERATION; /* Create a pthread_tcb for the main thread */ ret = posix_memalign((void**)&t, __alignof__(struct pthread_tcb), sizeof(struct pthread_tcb)); assert(!ret); - memset(t, 0, sizeof(struct pthread_tcb)); /* aggressively 0 for bugs */ + /* aggressively 0 for bugs */ + memset(t, 0, sizeof(struct pthread_tcb)); t->id = get_next_pid(); + t->fork_generation = fork_generation; t->stacksize = USTACK_NUM_PAGES * PGSIZE; t->stacktop = (void*)USTACKTOP; - t->detached = TRUE; t->state = PTH_RUNNING; - t->joiner = 0; /* implies that sigmasks are longs, which they are. */ assert(t->id == 0); - t->sched_policy = SCHED_FIFO; - t->sched_priority = 0; SLIST_INIT(&t->cr_stack); /* Put the new pthread (thread0) on the active queue */ mcs_pdr_lock(&queue_lock); threads_active++; TAILQ_INSERT_TAIL(&active_queue, t, tq_next); mcs_pdr_unlock(&queue_lock); - /* Tell the kernel where and how we want to receive events. This is just an - * example of what to do to have a notification turned on. We're turning on - * USER_IPIs, posting events to vcore 0's vcpd, and telling the kernel to - * send to vcore 0. Note sys_self_notify will ignore the vcoreid and - * private preference. Also note that enable_kevent() is just an example, - * and you probably want to use parts of event.c to do what you want. */ + /* Tell the kernel where and how we want to receive events. This is + * just an example of what to do to have a notification turned on. + * We're turning on USER_IPIs, posting events to vcore 0's vcpd, and + * telling the kernel to send to vcore 0. Note sys_self_notify will + * ignore the vcoreid and private preference. Also note that + * enable_kevent() is just an example, and you probably want to use + * parts of event.c to do what you want. */ enable_kevent(EV_USER_IPI, 0, EVENT_IPI | EVENT_VCORE_PRIVATE); - - /* Handle syscall events. */ - 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); @@ -481,10 +696,11 @@ void __attribute__((constructor)) pthread_lib_init(void) /* Get a block of pages for our per-vcore (but non-VCPD) ev_qs */ mmap_block = (uintptr_t)mmap(0, PGSIZE * 2 * max_vcores(), PROT_WRITE | PROT_READ, - MAP_POPULATE | MAP_ANONYMOUS, -1, 0); + MAP_POPULATE | MAP_ANONYMOUS | MAP_PRIVATE, + -1, 0); assert(mmap_block); - /* Could be smarter and do this on demand (in case we don't actually want - * max_vcores()). */ + /* Could be smarter and do this on demand (in case we don't actually + * want max_vcores()). */ for (int i = 0; i < max_vcores(); i++) { /* Each vcore needs to point to a non-VCPD ev_q */ sysc_mgmt[i].ev_q = get_eventq_raw(); @@ -501,8 +717,10 @@ void __attribute__((constructor)) pthread_lib_init(void) #endif #if 0 /* One global ev_mbox, separate ev_q per vcore */ struct event_mbox *sysc_mbox = malloc(sizeof(struct event_mbox)); - uintptr_t two_pages = (uintptr_t)mmap(0, PGSIZE * 2, PROT_WRITE | PROT_READ, - MAP_POPULATE | MAP_ANONYMOUS, -1, 0); + uintptr_t two_pages = (uintptr_t)mmap(0, PGSIZE * 2, PROT_WRITE | + PROT_READ, MAP_POPULATE | + MAP_ANONYMOUS | MAP_PRIVATE, -1, + 0); printd("Global ucq: %08p\n", &sysc_mbox->ev_msgs); assert(sysc_mbox); assert(two_pages); @@ -517,21 +735,22 @@ void __attribute__((constructor)) pthread_lib_init(void) sysc_mgmt[i].ev_q->ev_mbox = sysc_mbox; } #endif - /* Sched ops is set by 2ls_init */ - uthread_2ls_init((struct uthread*)t, &pthread_sched_ops); + uthread_2ls_init((struct uthread*)t, pth_handle_syscall, NULL); atomic_init(&threads_total, 1); /* one for thread0 */ + pre_fork_2ls = pth_pre_fork; + post_fork_2ls = pth_post_fork; } /* Make sure our scheduler runs inside an MCP rather than an SCP. */ void pthread_mcp_init() { /* Prevent this from happening more than once. */ - init_once_racy(return); + parlib_init_once_racy(return); uthread_mcp_init(); - /* From here forward we are an MCP running on vcore 0. Could consider doing - * other pthread specific initialization based on knowing we are an mcp - * after this point. */ + /* From here forward we are an MCP running on vcore 0. Could consider + * doing other pthread specific initialization based on knowing we are + * an mcp after this point. */ } int __pthread_create(pthread_t *thread, const pthread_attr_t *attr, @@ -542,42 +761,35 @@ int __pthread_create(pthread_t *thread, const pthread_attr_t *attr, struct pthread_tcb *pthread; int ret; - /* For now, unconditionally become an mcp when creating a pthread (if not - * one already). This may change in the future once we support 2LSs in an - * SCP. */ + /* For now, unconditionally become an mcp when creating a pthread (if + * not one already). This may change in the future once we support 2LSs + * in an SCP. */ pthread_mcp_init(); parent = (struct pthread_tcb*)current_uthread; ret = posix_memalign((void**)&pthread, __alignof__(struct pthread_tcb), sizeof(struct pthread_tcb)); assert(!ret); - memset(pthread, 0, sizeof(struct pthread_tcb)); /* aggressively 0 for bugs*/ + /* aggressively 0 for bugs*/ + memset(pthread, 0, sizeof(struct pthread_tcb)); pthread->stacksize = PTHREAD_STACK_SIZE; /* default */ pthread->state = PTH_CREATED; pthread->id = get_next_pid(); - pthread->detached = FALSE; /* default */ - pthread->joiner = 0; - /* Might override these later, based on attr && EXPLICIT_SCHED */ - pthread->sched_policy = parent->sched_policy; - pthread->sched_priority = parent->sched_priority; + pthread->fork_generation = fork_generation; SLIST_INIT(&pthread->cr_stack); /* Respect the attributes */ if (attr) { - if (attr->stacksize) /* don't set a 0 stacksize */ + if (attr->stacksize) /* don't set a 0 stacksize */ pthread->stacksize = attr->stacksize; if (attr->detachstate == PTHREAD_CREATE_DETACHED) - pthread->detached = TRUE; - if (attr->sched_inherit == PTHREAD_EXPLICIT_SCHED) { - pthread->sched_policy = attr->sched_policy; - pthread->sched_priority = attr->sched_priority; - } + uth_attr.detached = TRUE; } /* allocate a stack */ if (__pthread_allocate_stack(pthread)) printf("We're fucked\n"); /* Set the u_tf to start up in __pthread_run, which will call the real - * start_routine and pass it the arg. Note those aren't set until later in - * pthread_create(). */ + * start_routine and pass it the arg. Note those aren't set until later + * in pthread_create(). */ init_user_ctx(&pthread->uthread.u_ctx, (uintptr_t)&__pthread_run, (uintptr_t)(pthread->stacktop)); pthread->start_routine = start_routine; @@ -610,100 +822,20 @@ void __pthread_generic_yield(struct pthread_tcb *pthread) mcs_pdr_unlock(&queue_lock); } -/* Callback/bottom half of join, called from __uthread_yield (vcore context). - * join_target is who we are trying to join on (and who is calling exit). */ -static void __pth_join_cb(struct uthread *uthread, void *arg) -{ - struct pthread_tcb *pthread = (struct pthread_tcb*)uthread; - struct pthread_tcb *join_target = (struct pthread_tcb*)arg; - struct pthread_tcb *temp_pth = 0; - __pthread_generic_yield(pthread); - /* We're trying to join, yield til we get woken up */ - pthread->state = PTH_BLK_JOINING; /* could do this front-side */ - /* Put ourselves in the join target's joiner slot. If we get anything back, - * we lost the race and need to wake ourselves. Syncs with __pth_exit_cb.*/ - temp_pth = atomic_swap_ptr((void**)&join_target->joiner, pthread); - /* After that atomic swap, the pthread might be woken up (if it succeeded), - * so don't touch pthread again after that (this following if () is okay).*/ - if (temp_pth) { /* temp_pth != 0 means they exited first */ - assert(temp_pth == join_target); /* Sanity */ - /* wake ourselves, not the exited one! */ - printd("[pth] %08p already exit, rewaking ourselves, joiner %08p\n", - temp_pth, pthread); - pth_thread_runnable(uthread); /* wake ourselves */ - } -} - int pthread_join(struct pthread_tcb *join_target, void **retval) { - /* Not sure if this is the right semantics. There is a race if we deref - * join_target and he is already freed (which would have happened if he was - * detached. */ - if (join_target->detached) { - printf("[pthread] trying to join on a detached pthread"); - return -1; - } - /* See if it is already done, to avoid the pain of a uthread_yield() (the - * early check is an optimization, pth_thread_yield() handles the race). */ - if (!join_target->joiner) { - uthread_yield(TRUE, __pth_join_cb, join_target); - /* When we return/restart, the thread will be done */ - } else { - assert(join_target->joiner == join_target); /* sanity check */ - } - if (retval) - *retval = join_target->retval; - free(join_target); + uthread_join((struct uthread*)join_target, retval); return 0; } -/* Callback/bottom half of exit. Syncs with __pth_join_cb. Here's how it - * works: the slot for joiner is initially 0. Joiners try to swap themselves - * into that spot. Exiters try to put 'themselves' into it. Whoever gets 0 - * back won the race. If the exiter lost the race, it must wake up the joiner - * (which was the value from temp_pth). If the joiner lost the race, it must - * wake itself up, and for sanity reasons can ensure the value from temp_pth is - * the join target). */ -static void __pth_exit_cb(struct uthread *uthread, void *junk) -{ - struct pthread_tcb *pthread = (struct pthread_tcb*)uthread; - struct pthread_tcb *temp_pth = 0; - __pthread_generic_yield(pthread); - /* Catch some bugs */ - pthread->state = PTH_EXITING; - /* Destroy the pthread */ - uthread_cleanup(uthread); - /* Cleanup, mirroring pthread_create() */ - __pthread_free_stack(pthread); - /* TODO: race on detach state (see join) */ - if (pthread->detached) { - free(pthread); - } else { - /* See if someone is joining on us. If not, we're done (and the - * joiner will wake itself when it saw us there instead of 0). */ - temp_pth = atomic_swap_ptr((void**)&pthread->joiner, pthread); - if (temp_pth) { - /* they joined before we exited, we need to wake them */ - printd("[pth] %08p exiting, waking joiner %08p\n", - pthread, 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); -} - static inline void pthread_exit_no_cleanup(void *ret) { struct pthread_tcb *pthread = pthread_self(); - pthread->retval = ret; - destroy_dtls(); + while (SLIST_FIRST(&pthread->cr_stack)) pthread_cleanup_pop(FALSE); - uthread_yield(FALSE, __pth_exit_cb, 0); + destroy_dtls(); + uth_2ls_thread_exit(ret); } void pthread_exit(void *ret) @@ -714,22 +846,10 @@ void pthread_exit(void *ret) pthread_exit_no_cleanup(ret); } -/* Callback/bottom half of yield. For those writing these pth callbacks, the - * minimum is call generic, set state (communicate with runnable), then do - * something that causes it to be runnable in the future (or right now). */ -static void __pth_yield_cb(struct uthread *uthread, void *junk) -{ - struct pthread_tcb *pthread = (struct pthread_tcb*)uthread; - __pthread_generic_yield(pthread); - pthread->state = PTH_BLK_YIELDING; - /* just immediately restart it */ - pth_thread_runnable(uthread); -} - /* Cooperative yielding of the processor, to allow other threads to run */ int pthread_yield(void) { - uthread_yield(TRUE, __pth_yield_cb, 0); + uthread_sched_yield(); return 0; } @@ -744,6 +864,7 @@ void pthread_cleanup_push(void (*routine)(void *), void *arg) { struct pthread_tcb *p = pthread_self(); struct pthread_cleanup_routine *r = malloc(sizeof(*r)); + r->routine = routine; r->arg = arg; SLIST_INSERT_HEAD(&p->cr_stack, r, cr_next); @@ -753,6 +874,7 @@ void pthread_cleanup_pop(int execute) { struct pthread_tcb *p = pthread_self(); struct pthread_cleanup_routine *r = SLIST_FIRST(&p->cr_stack); + if (r) { SLIST_REMOVE_HEAD(&p->cr_stack, cr_next); if (execute) @@ -761,15 +883,15 @@ void pthread_cleanup_pop(int execute) } } -int pthread_mutexattr_init(pthread_mutexattr_t* attr) +int pthread_mutexattr_init(pthread_mutexattr_t *attr) { - attr->type = PTHREAD_MUTEX_DEFAULT; - return 0; + attr->type = PTHREAD_MUTEX_DEFAULT; + return 0; } -int pthread_mutexattr_destroy(pthread_mutexattr_t* attr) +int pthread_mutexattr_destroy(pthread_mutexattr_t *attr) { - return 0; + return 0; } int pthread_attr_setdetachstate(pthread_attr_t *__attr, int __detachstate) @@ -778,141 +900,150 @@ int pthread_attr_setdetachstate(pthread_attr_t *__attr, int __detachstate) return 0; } -int pthread_mutexattr_gettype(const pthread_mutexattr_t* attr, int* type) +int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type) { - *type = attr ? attr->type : PTHREAD_MUTEX_DEFAULT; - return 0; + *type = attr ? attr->type : PTHREAD_MUTEX_DEFAULT; + return 0; } -int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type) +static bool __pthread_mutex_type_ok(int type) { - if(type != PTHREAD_MUTEX_NORMAL) - return EINVAL; - attr->type = type; - return 0; + switch (type) { + case PTHREAD_MUTEX_NORMAL: + case PTHREAD_MUTEX_RECURSIVE: + return TRUE; + } + return FALSE; } -int pthread_mutex_init(pthread_mutex_t* m, const pthread_mutexattr_t* attr) +int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type) { - m->attr = attr; - atomic_init(&m->lock, 0); - return 0; + if (!__pthread_mutex_type_ok(type)) + return EINVAL; + attr->type = type; + return 0; } -/* Helper for spinning sync, returns TRUE if it is okay to keep spinning. - * - * Alternatives include: - * old_count <= num_vcores() (barrier code, pass in old_count as *state, - * but this only works if every awake pthread - * will belong to the barrier). - * just spin for a bit (use *state to track spins) - * FALSE (always is safe) - * etc... - * 'threads_ready' isn't too great since sometimes it'll be non-zero when it is - * about to become 0. We really want "I have no threads waiting to run that - * aren't going to run on their on unless this core yields instead of spins". */ -/* TODO: consider making this a 2LS op */ -static inline bool safe_to_spin(unsigned int *state) +int pthread_mutex_init(pthread_mutex_t *m, const pthread_mutexattr_t *attr) { - return !threads_ready; + if (attr) { + if (!__pthread_mutex_type_ok(attr->type)) + return EINVAL; + m->type = attr->type; + } else { + m->type = PTHREAD_MUTEX_NORMAL; + } + switch (m->type) { + case PTHREAD_MUTEX_NORMAL: + uth_mutex_init(&m->mtx); + break; + case PTHREAD_MUTEX_RECURSIVE: + uth_recurse_mutex_init(&m->r_mtx); + break; + } + return 0; } -/* Set *spun to 0 when calling this the first time. It will yield after 'spins' - * calls. Use this for adaptive mutexes and such. */ -static inline void spin_to_sleep(unsigned int spins, unsigned int *spun) +int pthread_mutex_lock(pthread_mutex_t *m) { - if ((*spun)++ == spins) { - pthread_yield(); - *spun = 0; + switch (m->type) { + case PTHREAD_MUTEX_NORMAL: + uth_mutex_lock(&m->mtx); + break; + case PTHREAD_MUTEX_RECURSIVE: + uth_recurse_mutex_lock(&m->r_mtx); + break; + default: + panic("Bad pth mutex type %d!", m->type); } + return 0; } -int pthread_mutex_lock(pthread_mutex_t* m) +int pthread_mutex_trylock(pthread_mutex_t *m) { - unsigned int spinner = 0; - while(pthread_mutex_trylock(m)) - while(*(volatile size_t*)&m->lock) { - cpu_relax(); - spin_to_sleep(PTHREAD_MUTEX_SPINS, &spinner); - } - /* normally we'd need a wmb() and a wrmb() after locking, but the - * atomic_swap handles the CPU mb(), so just a cmb() is necessary. */ - cmb(); - return 0; + bool got_it; + + switch (m->type) { + case PTHREAD_MUTEX_NORMAL: + got_it = uth_mutex_trylock(&m->mtx); + break; + case PTHREAD_MUTEX_RECURSIVE: + got_it = uth_recurse_mutex_trylock(&m->r_mtx); + break; + default: + panic("Bad pth mutex type %d!", m->type); + } + return got_it ? 0 : EBUSY; } -int pthread_mutex_trylock(pthread_mutex_t* m) +int pthread_mutex_unlock(pthread_mutex_t *m) { - return atomic_swap(&m->lock, 1) == 0 ? 0 : EBUSY; + switch (m->type) { + case PTHREAD_MUTEX_NORMAL: + uth_mutex_unlock(&m->mtx); + break; + case PTHREAD_MUTEX_RECURSIVE: + uth_recurse_mutex_unlock(&m->r_mtx); + break; + default: + panic("Bad pth mutex type %d!", m->type); + } + return 0; } -int pthread_mutex_unlock(pthread_mutex_t* m) +int pthread_mutex_destroy(pthread_mutex_t *m) { - /* keep reads and writes inside the protected region */ - rwmb(); - wmb(); - atomic_set(&m->lock, 0); - return 0; + switch (m->type) { + case PTHREAD_MUTEX_NORMAL: + uth_mutex_destroy(&m->mtx); + break; + case PTHREAD_MUTEX_RECURSIVE: + uth_recurse_mutex_destroy(&m->r_mtx); + break; + default: + panic("Bad pth mutex type %d!", m->type); + } + return 0; } -int pthread_mutex_destroy(pthread_mutex_t* m) +int pthread_mutex_timedlock(pthread_mutex_t *m, const struct timespec *abstime) { - return 0; + bool got_it; + + switch (m->type) { + case PTHREAD_MUTEX_NORMAL: + got_it = uth_mutex_timed_lock(&m->mtx, abstime); + break; + case PTHREAD_MUTEX_RECURSIVE: + got_it = uth_recurse_mutex_timed_lock(&m->r_mtx, abstime); + break; + default: + panic("Bad pth mutex type %d!", m->type); + } + return got_it ? 0 : ETIMEDOUT; } int pthread_cond_init(pthread_cond_t *c, const pthread_condattr_t *a) { - SLIST_INIT(&c->waiters); - spin_pdr_init(&c->spdr_lock); if (a) { - c->attr_pshared = a->pshared; - c->attr_clock = a->clock; - } else { - c->attr_pshared = PTHREAD_PROCESS_PRIVATE; - c->attr_clock = 0; + if (a->pshared != PTHREAD_PROCESS_PRIVATE) + fprintf(stderr, + "pthreads only supports private condvars"); + /* We also ignore clock_id */ } + uth_cond_var_init(c); return 0; } int pthread_cond_destroy(pthread_cond_t *c) { + uth_cond_var_destroy(c); return 0; } -static void swap_slists(struct pthread_list *a, struct pthread_list *b) -{ - struct pthread_list temp; - temp = *a; - *a = *b; - *b = temp; -} - -static void wake_slist(struct pthread_list *to_wake) -{ - unsigned int nr_woken = 0; /* assuming less than 4 bil threads */ - struct pthread_tcb *pthread_i, *pth_temp; - /* Amortize the lock grabbing over all restartees */ - mcs_pdr_lock(&queue_lock); - /* Do the work of pth_thread_runnable(). We're in uth context here, but I - * think it's okay. When we need to (when locking) we drop into VC ctx, as - * far as the kernel and other cores are concerned. */ - SLIST_FOREACH_SAFE(pthread_i, to_wake, sl_next, pth_temp) { - pthread_i->state = PTH_RUNNABLE; - nr_woken++; - TAILQ_INSERT_TAIL(&ready_queue, pthread_i, tq_next); - } - threads_ready += nr_woken; - mcs_pdr_unlock(&queue_lock); - vcore_request_more(threads_ready); -} - int pthread_cond_broadcast(pthread_cond_t *c) { - struct pthread_list restartees = SLIST_HEAD_INITIALIZER(restartees); - spin_pdr_lock(&c->spdr_lock); - swap_slists(&restartees, &c->waiters); - spin_pdr_unlock(&c->spdr_lock); - wake_slist(&restartees); + uth_cond_var_broadcast(c); return 0; } @@ -920,50 +1051,41 @@ int pthread_cond_broadcast(pthread_cond_t *c) * already. */ int pthread_cond_signal(pthread_cond_t *c) { - struct pthread_tcb *pthread; - spin_pdr_lock(&c->spdr_lock); - pthread = SLIST_FIRST(&c->waiters); - if (!pthread) { - spin_pdr_unlock(&c->spdr_lock); - return 0; - } - SLIST_REMOVE_HEAD(&c->waiters, sl_next); - spin_pdr_unlock(&c->spdr_lock); - pth_thread_runnable((struct uthread*)pthread); + uth_cond_var_signal(c); return 0; } -/* Communicate btw cond_wait and its callback */ -struct cond_junk { - pthread_cond_t *c; - pthread_mutex_t *m; -}; - -/* Callback/bottom half of cond wait. For those writing these pth callbacks, - * the minimum is call generic, set state (communicate with runnable), then do - * something that causes it to be runnable in the future (or right now). */ -static void __pth_wait_cb(struct uthread *uthread, void *junk) +int pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m) { - struct pthread_tcb *pthread = (struct pthread_tcb*)uthread; - pthread_cond_t *c = ((struct cond_junk*)junk)->c; - pthread_mutex_t *m = ((struct cond_junk*)junk)->m; - /* this removes us from the active list; we can reuse next below */ - __pthread_generic_yield(pthread); - pthread->state = PTH_BLK_MUTEX; - spin_pdr_lock(&c->spdr_lock); - SLIST_INSERT_HEAD(&c->waiters, pthread, sl_next); - spin_pdr_unlock(&c->spdr_lock); - pthread_mutex_unlock(m); + switch (m->type) { + case PTHREAD_MUTEX_NORMAL: + uth_cond_var_wait(c, &m->mtx); + break; + case PTHREAD_MUTEX_RECURSIVE: + uth_cond_var_wait_recurse(c, &m->r_mtx); + break; + default: + panic("Bad pth mutex type %d!", m->type); + } + return 0; } -int pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m) +int pthread_cond_timedwait(pthread_cond_t *c, pthread_mutex_t *m, + const struct timespec *abstime) { - struct cond_junk local_junk; - local_junk.c = c; - local_junk.m = m; - uthread_yield(TRUE, __pth_wait_cb, &local_junk); - pthread_mutex_lock(m); - return 0; + bool got_it; + + switch (m->type) { + case PTHREAD_MUTEX_NORMAL: + got_it = uth_cond_var_timed_wait(c, &m->mtx, abstime); + break; + case PTHREAD_MUTEX_RECURSIVE: + got_it = uth_cond_var_timed_wait_recurse(c, &m->r_mtx, abstime); + break; + default: + panic("Bad pth mutex type %d!", m->type); + } + return got_it ? 0 : ETIMEDOUT; } int pthread_condattr_init(pthread_condattr_t *a) @@ -1008,21 +1130,66 @@ int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id) return 0; } -pthread_t pthread_self() +int pthread_rwlock_init(pthread_rwlock_t *rwl, const pthread_rwlockattr_t *a) { - return (struct pthread_tcb*)current_uthread; + uth_rwlock_init(rwl); + return 0; +} + +int pthread_rwlock_destroy(pthread_rwlock_t *rwl) +{ + uth_rwlock_destroy(rwl); + return 0; +} + +int pthread_rwlock_rdlock(pthread_rwlock_t *rwl) +{ + uth_rwlock_rdlock(rwl); + return 0; +} + +int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwl) +{ + return uth_rwlock_try_rdlock(rwl) ? 0 : EBUSY; +} + +int pthread_rwlock_wrlock(pthread_rwlock_t *rwl) +{ + uth_rwlock_wrlock(rwl); + return 0; +} + +int pthread_rwlock_trywrlock(pthread_rwlock_t *rwl) +{ + return uth_rwlock_try_wrlock(rwl) ? 0 : EBUSY; +} + +int pthread_rwlock_unlock(pthread_rwlock_t *rwl) +{ + uth_rwlock_unlock(rwl); + return 0; +} + +pthread_t pthread_self(void) +{ + return (struct pthread_tcb*)uthread_self(); } int pthread_equal(pthread_t t1, pthread_t t2) { - return t1 == t2; + return t1 == t2; } -int pthread_once(pthread_once_t* once_control, void (*init_routine)(void)) +int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)) { - if (atomic_swap_u32(once_control, 1) == 0) - init_routine(); - return 0; + /* pthread_once's init routine doesn't take an argument, like parlibs. + * This means the func will be run with an argument passed to it, but + * it'll be ignored. */ + parlib_run_once(once_control, (void (*)(void *))init_routine, NULL); + /* The return for pthread_once isn't an error from the function, it's + * just an overall error. Note pthread's init_routine() has no return + * value. */ + return 0; } int pthread_barrier_init(pthread_barrier_t *b, @@ -1032,37 +1199,52 @@ int pthread_barrier_init(pthread_barrier_t *b, b->sense = 0; atomic_set(&b->count, count); spin_pdr_init(&b->lock); - SLIST_INIT(&b->waiters); + __uth_sync_init(&b->waiters); b->nr_waiters = 0; return 0; } struct barrier_junk { - pthread_barrier_t *b; - int ls; + pthread_barrier_t *b; + int ls; }; +/* Helper for spinning sync, returns TRUE if it is okay to keep spinning. + * + * Alternatives include: + * old_count <= num_vcores() (barrier code, pass in old_count as *state, + * but this only works if every awake pthread + * will belong to the barrier). + * just spin for a bit (use *state to track spins) + * FALSE (always is safe) + * etc... + * 'threads_ready' isn't too great since sometimes it'll be non-zero when it is + * about to become 0. We really want "I have no threads waiting to run that + * aren't going to run on their on unless this core yields instead of spins". */ +/* TODO: consider making this a 2LS op */ +static inline bool safe_to_spin(unsigned int *state) +{ + return (*state)++ % PTHREAD_BARRIER_SPINS; +} + /* Callback/bottom half of barrier. */ static void __pth_barrier_cb(struct uthread *uthread, void *junk) { - struct pthread_tcb *pthread = (struct pthread_tcb*)uthread; pthread_barrier_t *b = ((struct barrier_junk*)junk)->b; int ls = ((struct barrier_junk*)junk)->ls; - /* Removes from active list, we can reuse. must also restart */ - __pthread_generic_yield(pthread); + + uthread_has_blocked(uthread, UTH_EXT_BLK_MUTEX); /* TODO: if we used a trylock, we could bail as soon as we see sense */ spin_pdr_lock(&b->lock); - /* If sense is ls (our free value), we lost the race and shouldn't sleep */ + /* If sense is ls (our free value), we lost the race and shouldn't sleep + */ if (b->sense == ls) { - /* TODO: i'd like to fast-path the wakeup, skipping pth_runnable */ - pthread->state = PTH_BLK_YIELDING; /* not sure which state for this */ spin_pdr_unlock(&b->lock); - pth_thread_runnable(uthread); + uthread_runnable(uthread); return; } /* otherwise, we sleep */ - pthread->state = PTH_BLK_MUTEX; /* TODO: consider ignoring this */ - SLIST_INSERT_HEAD(&b->waiters, pthread, sl_next); + __uth_sync_enqueue(uthread, &b->waiters); b->nr_waiters++; spin_pdr_unlock(&b->lock); } @@ -1084,22 +1266,22 @@ static void __pth_barrier_cb(struct uthread *uthread, void *junk) int pthread_barrier_wait(pthread_barrier_t *b) { unsigned int spin_state = 0; - int ls = !b->sense; /* when b->sense is the value we read, then we're free*/ - struct pthread_list restartees = SLIST_HEAD_INITIALIZER(restartees); - struct pthread_tcb *pthread_i; + /* when b->sense is the value we read, then we're free*/ + int ls = !b->sense; + uth_sync_t restartees; + struct uthread *uth_i; struct barrier_junk local_junk; long old_count = atomic_fetch_and_add(&b->count, -1); if (old_count == 1) { - printd("Thread %d is last to hit the barrier, resetting...\n", - pthread_self()->id); - /* TODO: we might want to grab the lock right away, so a few short - * circuit faster? */ + /* TODO: we might want to grab the lock right away, so a few + * short circuit faster? */ atomic_set(&b->count, b->total_threads); - /* we still need to maintain ordering btw count and sense, in case - * another thread doesn't sleep (if we wrote sense first, they could - * break out, race around, and muck with count before it is time) */ + /* we still need to maintain ordering btw count and sense, in + * case another thread doesn't sleep (if we wrote sense first, + * they could break out, race around, and muck with count before + * it is time) */ /* wmb(); handled by the spin lock */ spin_pdr_lock(&b->lock); /* Sense is only protected in addition to decisions to sleep */ @@ -1109,13 +1291,15 @@ int pthread_barrier_wait(pthread_barrier_t *b) spin_pdr_unlock(&b->lock); return PTHREAD_BARRIER_SERIAL_THREAD; } - swap_slists(&restartees, &b->waiters); + __uth_sync_init(&restartees); + __uth_sync_swap(&restartees, &b->waiters); b->nr_waiters = 0; spin_pdr_unlock(&b->lock); - wake_slist(&restartees); + __uth_sync_wake_all(&restartees); return PTHREAD_BARRIER_SERIAL_THREAD; } else { - /* Spin if there are no other threads to run. No sense sleeping */ + /* Spin if there are no other threads to run. No sense sleeping + */ do { if (b->sense == ls) return 0; @@ -1133,16 +1317,15 @@ int pthread_barrier_wait(pthread_barrier_t *b) int pthread_barrier_destroy(pthread_barrier_t *b) { - assert(SLIST_EMPTY(&b->waiters)); assert(!b->nr_waiters); + __uth_sync_destroy(&b->waiters); /* Free any locks (if we end up using an MCS) */ return 0; } int pthread_detach(pthread_t thread) { - /* TODO: race on this state. Someone could be trying to join now */ - thread->detached = TRUE; + uthread_detach((struct uthread*)thread); return 0; } @@ -1192,26 +1375,16 @@ int pthread_setspecific(pthread_key_t key, const void *value) } -/* Scheduling Stuff */ - -static bool policy_is_supported(int policy) -{ - /* As our scheduler changes, we can add more policies here */ - switch (policy) { - case SCHED_FIFO: - return TRUE; - default: - return FALSE; - } -} +/* Scheduling Stuff. Actually, these don't tell the 2LS anything - they just + * pretend to muck with attrs and params, as expected by pthreads apps. */ int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param) { /* The set of acceptable priorities are based on the scheduling policy. * We'll just accept any old number, since we might not know the policy - * yet. I didn't see anything in the man pages saying attr had to have a - * policy set before setting priority. */ + * yet. I didn't see anything in the man pages saying attr had to have + * a policy set before setting priority. */ attr->sched_priority = param->sched_priority; return 0; } @@ -1225,8 +1398,6 @@ int pthread_attr_getschedparam(pthread_attr_t *attr, int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy) { - if (!policy_is_supported(policy)) - return -EINVAL; attr->sched_policy = policy; return 0; } @@ -1276,40 +1447,14 @@ int pthread_attr_getinheritsched(const pthread_attr_t *attr, int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param *param) { - if (!policy_is_supported(policy)) - return -EINVAL; - thread->sched_policy = policy; - /* We actually could check if the priority falls in the range of the - * specified policy here, since we have both policy and priority. */ - thread->sched_priority = param->sched_priority; return 0; } int pthread_getschedparam(pthread_t thread, int *policy, struct sched_param *param) { - *policy = thread->sched_policy; - param->sched_priority = thread->sched_priority; + /* Faking {FIFO, 0}. It's up to the 2LS to do whatever it wants. */ + *policy = SCHED_FIFO; + param->sched_priority = 0; return 0; } - - -/* Unsupported Stuff */ - -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; -}