aos/lib/aos/threads.c
2022-04-06 11:31:31 +02:00

1462 lines
45 KiB
C

/**
* \file
* \brief Threads implementation.
*/
/*
* Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, ETH Zurich.
* All rights reserved.
*
* This file is distributed under the terms in the attached LICENSE file.
* If you do not find this file, copies can be found by writing to:
* ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <aos/aos.h>
#include <aos/dispatch.h>
#include <aos/dispatcher_arch.h>
#include <aos/debug.h>
#include <aos/slab.h>
#include <aos/caddr.h>
#include <aos/curdispatcher_arch.h>
#include <aos/paging.h>
#include <barrelfish_kpi/cpu_arch.h>
#include <barrelfish_kpi/domain_params.h>
#include <arch/registers.h>
#include "arch/threads.h"
#include "threads_priv.h"
#include "init.h"
#if defined(__x86_64__)
# include "arch/ldt.h"
#endif
// (M4): define this once your self-paging implementation works...
#define SELF_PAGING_WORKS
/// Maximum number of threads in a domain, used to size VM region for thread structures
// there is no point having MAX_THREADS > LDT_NENTRIES on x86 (see ldt.c)
#define MAX_THREADS 256
/// Static stack and storage for a bootstrap/cleanup thread
// XXX: 16-byte aligned for x86-64
static uintptr_t staticstack[THREADS_DEFAULT_STACK_BYTES / sizeof(uintptr_t)]
__attribute__((aligned(STACK_ALIGNMENT)));
static struct thread staticthread __attribute__((aligned(THREAD_ALIGNMENT))) = {
.stack = staticstack,
.stack_top = (char *)staticstack + sizeof(staticstack)
};
static struct thread_mutex staticthread_lock = THREAD_MUTEX_INITIALIZER;
/// Storage metadata for thread structures (and TLS data)
static struct slab_allocator thread_slabs;
// XXX: mutex and spinlock protecting thread slabs in spanned domains
/* This ought to be just a mutex. However, thread_create() is called on the
* inter-disp message handler thread, and if it blocks in a mutex, there is no
* way to wake it up and we will deadlock. This is a quick-fix workaround:
* The spinlock protects the data structure
* The mutex avoids unneccessary spinning (it is acquired first when safe)
*/
static spinlock_t thread_slabs_spinlock;
static struct thread_mutex thread_slabs_mutex = THREAD_MUTEX_INITIALIZER;
/// Base and size of the original ("pristine") thread-local storage init data
static void *tls_block_init_base;
static size_t tls_block_init_len;
static size_t tls_block_total_len;
/// Warning already issued about RSP usage. (Prevent repeated warnings
/// from the same domain -- e.g., when using THC whose stacks appear
/// invalid here).
__attribute__((unused)) static bool stack_warned=0;
/// Wrapper function for most threads, runs given function then deletes itself
static void thread_entry(thread_func_t start_func, void *start_data)
{
assert((lvaddr_t)start_func >= BASE_PAGE_SIZE);
int retval = start_func(start_data);
thread_exit(retval);
assert(!"thread_exit returned");
}
/// int counter for assigning initial thread ids
static uintptr_t threadid = 1;
#ifndef NDEBUG
/// Debugging assertions on thread queues
static void check_queue(struct thread *queue)
{
if (queue == NULL) {
return;
}
struct thread *q = queue;
int i = 0;
do {
assert_disabled(q != NULL);
// check for NULL next and prev pointers
assert_disabled((lvaddr_t)q->next > BASE_PAGE_SIZE);
assert_disabled((lvaddr_t)q->prev > BASE_PAGE_SIZE);
// check that next and prev pointers are sane
assert_disabled(q->next->prev == q);
assert_disabled(q->prev->next == q);
// advance to next elem
q = q->next;
i++;
assert_disabled(i < MAX_THREADS);
} while (q != queue);
}
#else /* NDEBUG version */
static inline void check_queue(struct thread *queue) {}
#endif
/**
* \brief Enqueue a thread in the given queue
*
* For safety, should only happen while disabled.
*/
void thread_enqueue(struct thread *thread, struct thread **queue)
{
assert_disabled(thread != NULL);
assert_disabled(queue != NULL);
check_queue(*queue);
if (*queue == NULL) {
*queue = thread->prev = thread->next = thread;
} else {
assert_disabled((*queue)->prev != NULL);
thread->prev = (*queue)->prev;
thread->next = *queue;
(*queue)->prev = thread;
assert_disabled(thread->prev != NULL);
thread->prev->next = thread;
}
check_queue(*queue);
}
/**
* \brief Dequeue the first thread on the given queue
*
* For safety, should only happen while disabled.
*
* \returns Pointer to thread that was dequeued
*/
struct thread *thread_dequeue(struct thread **queue)
{
assert_disabled(queue != NULL);
struct thread *thread = *queue;
assert_disabled(thread != NULL);
check_queue(thread);
if (thread->prev == thread) {
assert_disabled(thread->next == thread);
*queue = NULL;
} else {
thread->prev->next = thread->next;
thread->next->prev = thread->prev;
*queue = thread->next;
}
check_queue(*queue);
#ifndef NDEBUG
thread->prev = thread->next = NULL;
#endif
return thread;
}
/**
* \brief Remove a specific thread from a queue
*
* Does not check that the thread is in the given queue, which it must be.
* For safety, should only happen while disabled.
*/
void thread_remove_from_queue(struct thread **queue, struct thread *thread)
{
assert_disabled(queue != NULL);
assert_disabled(thread != NULL);
check_queue(*queue);
if (thread->prev == thread) {
assert_disabled(thread->next == thread);
assert_disabled(*queue == thread);
*queue = NULL;
} else {
thread->prev->next = thread->next;
thread->next->prev = thread->prev;
if (*queue == thread) {
*queue = thread->next;
}
}
check_queue(*queue);
#ifndef NDEBUG
thread->prev = thread->next = NULL;
#endif
}
/// Refill backing storage for thread region
#ifdef SELF_PAGING_WORKS
static errval_t refill_thread_slabs(struct slab_allocator *slabs)
{
// (M4):
// - implement me!
return slab_default_refill(slabs);
}
#endif
/// Initialise the state of a new thread structure
static void thread_init(dispatcher_handle_t disp, struct thread *newthread)
{
newthread->self = newthread;
#ifndef NDEBUG
newthread->next = newthread->prev = NULL;
#endif
newthread->tls_dtv = NULL;
newthread->disp = disp;
newthread->coreid = get_dispatcher_generic(disp)->core_id;
newthread->userptr = NULL;
memset(newthread->userptrs, 0, sizeof(newthread->userptrs));
newthread->yield_epoch = 0;
newthread->wakeup_reason = NULL;
newthread->return_value = 0;
thread_cond_init(&newthread->exit_condition);
thread_mutex_init(&newthread->exit_lock);
newthread->state = THREAD_STATE_RUNNABLE;
newthread->detached = false;
newthread->joining = false;
newthread->in_exception = false;
newthread->paused = false;
newthread->slab = NULL;
newthread->token = 0;
newthread->token_number = 1;
newthread->rpc_in_progress = false;
newthread->async_error = SYS_ERR_OK;
newthread->local_trigger = NULL;
}
/**
* \brief Returns false if the stack pointer is out of bounds.
*/
static bool thread_check_stack_bounds(struct thread *thread,
arch_registers_state_t *archregs) {
lvaddr_t sp = (lvaddr_t) registers_get_sp(archregs);
return sp > (lvaddr_t)thread->stack ||
sp <= (lvaddr_t)thread->stack_top;
}
/**
* \brief Schedule and run the next active thread, or yield the dispatcher.
*
* This may only be called from the dispatcher (on its stack and while
* disabled!).
*
* \param disp Dispatcher pointer
*/
void thread_run_disabled(dispatcher_handle_t handle)
{
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
struct dispatcher_shared_generic *disp =
get_dispatcher_shared_generic(handle);
arch_registers_state_t *enabled_area =
dispatcher_get_enabled_save_area(handle);
if (disp_gen->current != NULL) {
assert_disabled(disp_gen->runq != NULL);
// check stack bounds
warn_disabled(&stack_warned,
thread_check_stack_bounds(disp_gen->current, enabled_area));
struct thread *next = disp_gen->current->next;
assert_disabled(next != NULL);
if (next != disp_gen->current) {
// save previous thread's state
arch_registers_state_t *cur_regs = &disp_gen->current->regs;
memcpy(cur_regs, enabled_area, sizeof(arch_registers_state_t));
disp_gen->current = next;
disp_resume(handle, &next->regs);
} else {
// same thread as before
disp_resume(handle, enabled_area);
}
} else if (disp_gen->runq != NULL) {
disp_gen->current = disp_gen->runq;
disp->haswork = true;
disp_resume(handle, &disp_gen->runq->regs);
} else {
// kernel gave us the CPU when we have nothing to do. block!
disp->haswork = havework_disabled(handle);
disp_gen->current = NULL;
disp_yield_disabled(handle);
}
}
/** Free all heap/slab-allocated state associated with a thread */
static void free_thread(struct thread *thread)
{
#if defined(__x86_64__) // XXX: gungy segment selector stuff
assert(thread->thread_seg_selector != 0);
uint16_t fs;
__asm("mov %%fs, %0" : "=r" (fs));
if (thread->thread_seg_selector == fs) {
assert(thread->disp == curdispatcher());
struct dispatcher_x86_64 *disp_priv = get_dispatcher_x86_64(thread->disp);
// we're freeing the current thread; make sure we reload a valid segment
// selector so that curdispatcher() keeps working!
__asm volatile("mov %%ax, %%fs"
: /* No outputs */
: "a" (disp_priv->disp_seg_selector));
}
ldt_free_segment(thread->thread_seg_selector);
#endif
free(thread->stack);
if (thread->tls_dtv != NULL) {
free(thread->tls_dtv);
}
thread_mutex_lock(&thread_slabs_mutex);
acquire_spinlock(&thread_slabs_spinlock);
slab_free(&thread_slabs, thread->slab); // frees thread itself
release_spinlock(&thread_slabs_spinlock);
thread_mutex_unlock(&thread_slabs_mutex);
}
#define ALIGN_PTR(ptr, alignment) ((((uintptr_t)(ptr)) + (alignment) - 1) & ~((alignment) - 1))
/**
* \brief Creates a new thread that will not be runnable
*
* \param start_func Function to run on the new thread
* \param arg Argument to pass to function
* \param stacksize Size of stack, in bytes
*
* \returns Thread pointer on success, NULL on failure
*/
struct thread *thread_create_unrunnable(thread_func_t start_func, void *arg,
size_t stacksize)
{
// allocate stack
assert((stacksize % sizeof(uintptr_t)) == 0);
void *stack = malloc(stacksize);
if (stack == NULL) {
return NULL;
}
// allocate space for TCB + initial TLS data
// no mutex as it may deadlock: see comment for thread_slabs_spinlock
// thread_mutex_lock(&thread_slabs_mutex);
acquire_spinlock(&thread_slabs_spinlock);
void *space = slab_alloc(&thread_slabs);
release_spinlock(&thread_slabs_spinlock);
// thread_mutex_unlock(&thread_slabs_mutex);
if (space == NULL) {
free(stack);
return NULL;
}
// split space into TLS data followed by TCB
// XXX: this layout is specific to the x86 ABIs! once other (saner)
// architectures support TLS, we'll need to break out the logic.
void *tls_data = space;
struct thread *newthread = (void *)ALIGN_PTR((uintptr_t)space + tls_block_total_len, THREAD_ALIGNMENT);
// init thread
thread_init(curdispatcher(), newthread);
newthread->slab = space;
if (tls_block_total_len > 0) {
// populate initial TLS data from pristine copy
assert(tls_block_init_len <= tls_block_total_len);
memcpy(tls_data, tls_block_init_base, tls_block_init_len);
// zero-fill remainder
memset((char *)tls_data + tls_block_init_len, 0,
tls_block_total_len - tls_block_init_len);
// create a TLS thread vector
struct tls_dtv *dtv = malloc(sizeof(struct tls_dtv) + 1 * sizeof(void *));
assert(dtv != NULL);
dtv->gen = 0;
dtv->dtv[0] = tls_data;
newthread->tls_dtv = dtv;
}
// FIXME: make arch-specific
#if defined(__x86_64__) || defined(__k1om__)
// create segment for TCB
errval_t err = ldt_alloc_segment(newthread, &newthread->thread_seg_selector);
if (err_is_fail(err)) {
DEBUG_ERR(err, "error allocating LDT segment for new thread");
free_thread(newthread);
free(stack);
return NULL;
}
#endif
// init stack
newthread->stack = stack;
newthread->stack_top = (char *)stack + stacksize;
// waste space for alignment, if malloc gave us an unaligned stack
newthread->stack_top = (char *)newthread->stack_top
- (lvaddr_t)newthread->stack_top % STACK_ALIGNMENT;
// set thread's ID
newthread->id = threadid++;
paging_init_onthread(newthread);
// init registers
registers_set_initial(&newthread->regs, newthread, (lvaddr_t)thread_entry,
(lvaddr_t)newthread->stack_top,
(lvaddr_t)start_func, (lvaddr_t)arg, 0, 0);
return newthread;
}
/**
* \brief Creates a new thread, and makes it runnable
*
* \param start_func Function to run on the new thread
* \param arg Argument to pass to function
* \param stacksize Size of stack, in bytes
*
* \returns Thread pointer on success, NULL on failure
*/
struct thread *thread_create_varstack(thread_func_t start_func, void *arg,
size_t stacksize)
{
struct thread *newthread = thread_create_unrunnable(start_func, arg, stacksize);
if (newthread) {
// enqueue on runq
dispatcher_handle_t handle = disp_disable();
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
newthread->disp = handle;
thread_enqueue(newthread, &disp_gen->runq);
disp_enable(handle);
}
return newthread;
}
/**
* \brief Creates a new thread, and makes it runnable
*
* \param start_func Function to run on the new thread
* \param arg Argument to pass to function
*
* \returns Thread pointer on success, NULL on failure
*/
struct thread *thread_create(thread_func_t start_func, void *arg)
{
return thread_create_varstack(start_func, arg, THREADS_DEFAULT_STACK_BYTES);
}
/**
* \brief Wait for termination of another thread
*
* \param thread Pointer to thread to wait for
* \param retval Pointer to variable to hold return value of thread, or NULL
*
* \returns SYS_ERR_OK on success, error code on error.
*/
errval_t thread_join(struct thread *thread, int *retval)
{
assert(thread != NULL);
// this function should only be called for threads on same core
assert(thread->coreid == disp_get_core_id());
thread_mutex_lock(&thread->exit_lock);
if(thread->detached) {
// Thread is detached and thus not joinable
thread_mutex_unlock(&thread->exit_lock);
return LIB_ERR_THREAD_JOIN_DETACHED;
}
if(thread->joining) {
// Someone else already joins, that's an error
thread_mutex_unlock(&thread->exit_lock);
return LIB_ERR_THREAD_JOIN;
}
thread->joining = true;
if(thread->state != THREAD_STATE_EXITED) { // Possibly wait for thread exit
thread_cond_wait(&thread->exit_condition, &thread->exit_lock);
}
if(retval != NULL) {
*retval = thread->return_value;
}
thread_mutex_unlock(&thread->exit_lock); // Not really needed
free_thread(thread);
return SYS_ERR_OK;
}
/**
* \brief Detach a thread. Free its state when it terminates.
*
* \param thread Pointer to thread to detach
*
* \return SYS_ERR_OK on success.
*/
errval_t thread_detach(struct thread *thread)
{
assert(thread != NULL);
thread_mutex_lock(&thread->exit_lock);
if(thread->joining) {
// Someone else already joins, that's an error
thread_mutex_unlock(&thread->exit_lock);
return LIB_ERR_THREAD_JOIN;
}
if(!thread->detached) {
thread->detached = true;
} else {
// Detaching more than once is an error
thread_mutex_unlock(&thread->exit_lock);
return LIB_ERR_THREAD_DETACHED;
}
if(thread->state == THREAD_STATE_EXITED) {
// Thread already exited before we detached, clean it up
free_thread(thread);
return SYS_ERR_OK;
}
thread_mutex_unlock(&thread->exit_lock);
return SYS_ERR_OK;
}
/**
* \brief Returns the thread pointer to the currently-running thread
*/
struct thread *thread_self(void)
{
struct thread *me;
#if defined(__x86_64__) // XXX: AB's silly little arch-specific optimisation
__asm("movq %%fs:0, %0" : "=r" (me));
#else
// it's not necessary to disable, but might be once we do migration
bool was_enabled;
dispatcher_handle_t handle = disp_try_disable(&was_enabled);
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
me = disp_gen->current;
if (was_enabled)
disp_enable(handle);
#endif
return me;
}
struct thread *thread_self_disabled(void)
{
dispatcher_handle_t handle = curdispatcher();
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
return disp_gen->current;
}
uintptr_t thread_id(void)
{
return thread_self()->id;
}
uintptr_t thread_get_id(struct thread *t)
{
return t->id;
}
void thread_set_id(uintptr_t id)
{
struct thread *me = thread_self();
me->id = id;
}
uint32_t thread_set_token(struct waitset_chanstate *channel)
{
struct thread *me = thread_self();
// generate new token
uint32_t outgoing_token = (uint32_t)((me->id << 16) |
(me->coreid << 24) | ((me->token_number & 255) << 8)) | 1;
assert(me->token == 0);
me->token_number++;
if (!(me->token_number & 255))
me->token_number = 1;
me->token = outgoing_token & ~1; // wait for this token
me->channel = channel; // on that channel
return outgoing_token;
}
void thread_clear_token(struct waitset_chanstate *channel)
{
struct thread *me = thread_self();
me->token = 0; // don't wait anymore
me->channel = NULL;
}
uint32_t thread_current_token(void)
{
return thread_self()->token;
}
void thread_set_outgoing_token(uint32_t token)
{
struct thread *me = thread_self();
assert(!me->outgoing_token);
me->outgoing_token = token;
}
void thread_get_outgoing_token(uint32_t *token)
{
struct thread *me = thread_self();
// if thread's outgoing token is set, get it
if (me->outgoing_token) {
*token = me->outgoing_token;
me->outgoing_token = 0;
}
}
void thread_set_local_trigger(struct waitset_chanstate *trigger)
{
struct thread *me = thread_self();
me->local_trigger = trigger;
}
struct waitset_chanstate * thread_get_local_trigger(void)
{
struct thread *me = thread_self();
return me->local_trigger;
}
void thread_set_rpc_in_progress(bool v)
{
thread_self()->rpc_in_progress = v;
}
bool thread_get_rpc_in_progress(void)
{
return thread_self()->rpc_in_progress;
}
void thread_set_async_error(errval_t e)
{
thread_self()->async_error = e;
}
errval_t thread_get_async_error(void)
{
return thread_self()->async_error;
}
/**
* \brief Store receive slot provided by rpc
*/
void thread_store_recv_slot(struct capref recv_slot)
{
dispatcher_handle_t handle = disp_disable();
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
assert(disp_gen->recv_slot_count < MAX_RECV_SLOTS);
assert(disp_gen->recv_slot_count >= 0);
disp_gen->recv_slots[disp_gen->recv_slot_count++] = recv_slot;
disp_enable(handle);
}
struct capref thread_get_next_recv_slot(void)
{
dispatcher_handle_t handle = disp_disable();
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
struct capref retcap;
// HERE: recv_slot_count is > 0 if we have one+ caps stored
if (disp_gen->recv_slot_count <= 0) {
retcap = NULL_CAP;
} else {
retcap = disp_gen->recv_slots[--disp_gen->recv_slot_count];
}
disp_enable(handle);
return retcap;
}
void thread_set_status(int status) {
struct thread *me = thread_self();
me->return_value = status;
}
/**
* \brief Yield the calling thread
*
* Switches to the next runnable thread in this dispatcher, or if none is
* available, yields the dispatcher.
*/
void thread_yield(void)
{
dispatcher_handle_t handle = disp_disable();
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
struct dispatcher_shared_generic *disp =
get_dispatcher_shared_generic(handle);
arch_registers_state_t *enabled_area =
dispatcher_get_enabled_save_area(handle);
struct thread *me = disp_gen->current;
struct thread *next = me;
me->yield_epoch = disp_gen->timeslice;
do {
assert_disabled(next != NULL);
next = next->next;
if (next == me) {
break; // Everybody yielded this timeslice
}
} while(next->yield_epoch == disp_gen->timeslice);
poll_channels_disabled(handle);
if (next != me) {
disp_gen->current = next;
disp_switch(handle, &me->regs, &next->regs);
} else {
assert_disabled(disp_gen->runq != NULL);
assert_disabled(disp->haswork);
disp_save(handle, enabled_area, true, CPTR_NULL);
}
}
/**
* \brief Yield both the calling thread, and the dispatcher to another domain
*
* \param endpoint Endpoint cap to which we wish to yield, or #CAP_NULL
* for an undirected yield
*
* Yields the dispatcher, optionally to another specified dispatcher.
*/
void thread_yield_dispatcher(struct capref endpoint)
{
dispatcher_handle_t handle = disp_disable();
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
struct dispatcher_shared_generic *disp =
get_dispatcher_shared_generic(handle);
arch_registers_state_t *enabled_area =
dispatcher_get_enabled_save_area(handle);
assert_disabled(disp_gen->runq != NULL);
assert_disabled(disp->haswork);
disp_save(handle, enabled_area, true, get_cap_addr(endpoint));
}
/// Function that runs on the static thread/stack to clean up a "real" (alloced) thread
static int cleanup_thread(void *arg)
{
struct thread *thread = arg;
// free old thread and its stack
if (thread != NULL) {
free_thread(thread);
}
// disable and release static thread
dispatcher_handle_t handle = disp_disable();
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
struct dispatcher_shared_generic *disp =
get_dispatcher_shared_generic(handle);
struct thread *me = disp_gen->current;
struct thread *ft =
thread_mutex_unlock_disabled(handle, &disp_gen->cleanupthread_lock);
assert(ft == NULL);
// run the next thread, if any
struct thread *next = me->next;
thread_remove_from_queue(&disp_gen->runq, me);
if (next != me) {
disp_gen->current = next;
disp_resume(handle, &next->regs);
} else {
disp_gen->current = NULL;
disp->haswork = havework_disabled(handle);
disp_yield_disabled(handle);
}
return 0;
}
/**
* \brief Terminate the calling thread
*/
void thread_exit(int status)
{
struct thread *me = thread_self();
thread_mutex_lock(&me->exit_lock);
// if this is the static thread, we don't need to do anything but cleanup
if (me == &staticthread) {
assert(me->detached);
// disable and release static thread
dispatcher_handle_t handle = disp_disable();
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
struct dispatcher_shared_generic *disp =
get_dispatcher_shared_generic(handle);
assert_disabled(me == &staticthread);
assert_disabled(me->stack == staticstack);
struct thread *ft =
thread_mutex_unlock_disabled(handle, &staticthread_lock);
assert(ft == NULL);
// run the next thread, if any
struct thread *next = me->next;
thread_remove_from_queue(&disp_gen->runq, me);
if (next != me) {
disp_gen->current = next;
disp_resume(handle, &next->regs);
} else {
disp_gen->current = NULL;
disp->haswork = havework_disabled(handle);
disp_yield_disabled(handle);
}
}
if (me->detached) {
// otherwise, we use a dispatcher-local thread to perform cleanup
struct dispatcher_generic *dg = get_dispatcher_generic(curdispatcher());
thread_mutex_lock(&dg->cleanupthread_lock);
if(dg->cleanupthread == NULL) {
dg->cleanupthread =
thread_create_unrunnable(cleanup_thread, me,
THREADS_DEFAULT_STACK_BYTES);
}
thread_init(curdispatcher(), dg->cleanupthread);
registers_set_initial(&dg->cleanupthread->regs, dg->cleanupthread,
(lvaddr_t)cleanup_thread,
(lvaddr_t)dg->cleanupthread->stack_top, (lvaddr_t)me,
0, 0, 0);
// Switch to it (on this dispatcher)
dispatcher_handle_t handle = disp_disable();
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
thread_remove_from_queue(&disp_gen->runq, me);
thread_enqueue(dg->cleanupthread, &disp_gen->runq);
disp_gen->cleanupthread->disp = handle;
disp_gen->current = dg->cleanupthread;
disp_resume(handle, &dg->cleanupthread->regs);
} else {
// We're not detached -- wakeup joiner
me->return_value = status;
me->state = THREAD_STATE_EXITED;
thread_cond_signal(&me->exit_condition);
// Disable and unlock exit lock
dispatcher_handle_t handle = disp_disable();
struct thread *wakeup =
thread_mutex_unlock_disabled(handle, &me->exit_lock);
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
struct dispatcher_shared_generic *disp =
get_dispatcher_shared_generic(handle);
assert_disabled(wakeup == NULL);
// run the next thread, if any
struct thread *next = me->next;
thread_remove_from_queue(&disp_gen->runq, me);
if (next != me) {
disp_gen->current = next;
disp_resume(handle, &next->regs);
} else {
disp_gen->current = NULL;
disp->haswork = havework_disabled(handle);
disp_yield_disabled(handle);
}
}
USER_PANIC("should never be reached");
}
/**
* \brief Block the caller, and optionally release a spinlock, while disabled
*
* The caller is unconditionally blocked, and placed into the given queue
* pending a call that will unblock it. After manipulating the queues, and
* before switching threds, the given spinlock, if specified, is unlocked.
* This function must only be called while disabled.
*
* This function is intended for use by multi-processor thread synchronisation
* functions.
*
* \param disp Dispatcher pointer
* \param queue (Optional) Queue of threads in which to place caller
* \param spinlock (Optional) pointer to spinlock
*
* \returns Argument passed to thread_unblock, when unblocked
*/
void *thread_block_and_release_spinlock_disabled(dispatcher_handle_t handle,
struct thread **queue,
spinlock_t *spinlock)
{
struct dispatcher_shared_generic *disp =
get_dispatcher_shared_generic(handle);
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
struct thread *me = disp_gen->current;
struct thread *next = me->next;
assert_disabled(next != NULL);
assert_disabled(me->state == THREAD_STATE_RUNNABLE);
me->state = THREAD_STATE_BLOCKED;
thread_remove_from_queue(&disp_gen->runq, me);
if (queue != NULL) {
thread_enqueue(me, queue);
}
if (spinlock != NULL) {
release_spinlock(spinlock);
}
if (next != me) {
assert_disabled(disp_gen->runq != NULL);
disp_gen->current = next;
disp_switch(handle, &me->regs, &next->regs);
} else {
assert_disabled(disp_gen->runq == NULL);
disp_gen->current = NULL;
disp->haswork = havework_disabled(handle);
disp_save(handle, &me->regs, true, CPTR_NULL);
}
assert(me->disp == handle); // didn't migrate while asleep
return me->wakeup_reason;
}
/**
* \brief Block the calling thread, while disabled
*
* The caller is unconditionally blocked, and placed into the given queue
* pending a call that will unblock it.
* This function must only be called while disabled.
*
* \param disp Dispatcher pointer
* \param queue Queue of threads in which to place caller
*
* \returns Argument passed to thread_unblock, when unblocked
*/
void *thread_block_disabled(dispatcher_handle_t disp, struct thread **queue)
{
return thread_block_and_release_spinlock_disabled(disp, queue, NULL);
}
/**
* \brief Block the calling thread, while enabled
*
* The caller is unconditionally blocked, and placed into the given queue
* pending a call that will unblock it.
* This function must only be called while enabled.
*
* \param queue Queue of threads in which to place caller
*
* \returns Argument passed to thread_unblock, when unblocked
*/
void *thread_block(struct thread **queue)
{
return thread_block_disabled(disp_disable(), queue);
}
/**
* \brief Unblock a single thread from a given queue, while disabled
*
* A single thread is removed from the queue of blocked threads, and awoken.
* This function must only be called while disabled.
*
* \param disp Dispatcher pointer
* \param queue Queue of threads from which to unblock one
* \param reason Value to be returned from thread_block()
*
* \returns Pointer to thread to be woken on a foreign dispatcher
*/
struct thread *thread_unblock_one_disabled(dispatcher_handle_t handle,
struct thread **queue,
void *reason)
{
assert_disabled(queue != NULL);
// Any threads in queue?
if (*queue == NULL) {
return NULL;
}
// Wakeup one waiting thread
struct thread *wakeup = thread_dequeue(queue);
wakeup->wakeup_reason = reason;
assert_disabled(wakeup->state == THREAD_STATE_BLOCKED);
wakeup->state = THREAD_STATE_RUNNABLE;
/* enqueue on run queue if it's "our" thread, and not paused */
if (wakeup->disp == handle) {
if (!wakeup->paused) {
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
thread_enqueue(wakeup, &disp_gen->runq);
}
return NULL;
} else {
return wakeup;
}
}
/**
* \brief Unblock a single thread from a given queue, while enabled
*
* A single thread is removed from the queue of blocked threads, and awoken.
* This function must only be called while enabled.
*
* \param queue Queue of threads from which to unblock one
* \param reason Value to be returned from thread_block()
*
* \returns Pointer to thread to be woken on a foreign dispatcher
*/
struct thread *thread_unblock_one(struct thread **queue, void *reason)
{
struct thread *thread;
dispatcher_handle_t handle = disp_disable();
thread = thread_unblock_one_disabled(handle, queue, reason);
disp_enable(handle);
return thread;
}
/**
* \brief Unblock all threads on a given queue, while disabled
*
* All threads on the queue of blocked threads are awoken.
* This function must only be called while disabled.
*
* \param disp Dispatcher pointer
* \param queue Queue of threads to unblock
* \param reason Value to be returned from thread_block()
*
* \returns Pointer to list of threads to be woken on a foreign dispatcher
*/
struct thread *thread_unblock_all_disabled(dispatcher_handle_t handle,
struct thread **queue, void *reason)
{
assert_disabled(queue != NULL);
struct thread *wakeupq = NULL;
// Wakeup all waiting threads
while (*queue != NULL) {
struct thread *wakeup = thread_unblock_one_disabled(handle, queue, reason);
if (wakeup != NULL) {
wakeup->next = wakeupq;
wakeupq = wakeup;
}
}
return wakeupq;
}
extern int _main(int argc, const char *argv[]);
/// Thread created in new domain that runs main()
static int main_thread(void *params)
{
struct spawn_domain_params *p = params;
exit(_main(p->argc, p->argv));
return EXIT_FAILURE;
}
static bool init_domain_global; // XXX
/// Thread created on static stack in new domain that runs init code
static int bootstrap_thread(struct spawn_domain_params *params)
//int bootstrap_thread(struct spawn_domain_params *params);
//int bootstrap_thread(struct spawn_domain_params *params)
{
errval_t err;
// Set libc function pointers
barrelfish_libc_glue_init();
if (params == NULL) {
printf("%s: error in creating a thread, NULL parameters given\n",
disp_name());
}
assert(params != NULL);
// Do we have TLS data?
tls_block_init_base = params->tls_init_base;
tls_block_init_len = params->tls_init_len;
tls_block_total_len = params->tls_total_len;
// Initialize subsystems
err = barrelfish_init_onthread(params);
if (err_is_fail(err)) {
DEBUG_ERR(err, "error during libbarrelfish init");
exit(EXIT_FAILURE);
assert(!"exit returned!");
}
// Until we have self-paging, we cannot use the paging-region based thread
// control block slab allocator, so just run main thread directly
#ifndef SELF_PAGING_WORKS
// we aren't prepared to run real threads yet
main_thread(params);
#else
// Allocate storage region for real threads
size_t blocksize = sizeof(struct thread) + tls_block_total_len + THREAD_ALIGNMENT;
slab_init(&thread_slabs, blocksize, refill_thread_slabs);
if (init_domain_global) {
// run main() on this thread, since we can't allocate
if (tls_block_total_len > 0) {
USER_PANIC("unsupported: use of TLS data in bootstrap domain\n");
}
DEBUG_PRINTF("running main on staticthread!\n");
main_thread(params);
} else {
// Start real thread to run main()
struct thread *thread = thread_create(main_thread, params);
assert(thread != NULL);
}
#endif
return 0; // ignored
}
/**
* \brief Initialise thread system while still disabled
*
* This function initialises the thread system while the dispatcher is still
* disabled, before enabling the dispatcher, running the general initialisation
* code, and calling main().
*
* \param disp Dispatcher pointer
* \param init_domain True if we are a bootstrap domain
*/
void thread_init_disabled(dispatcher_handle_t handle, bool init_domain)
{
struct dispatcher_shared_generic *disp =
get_dispatcher_shared_generic(handle);
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
arch_registers_state_t *enabled_area =
dispatcher_get_enabled_save_area(handle);
init_domain_global = init_domain;
// Create the first thread manually
struct thread *thread = &staticthread;
staticthread_lock.locked = true; // XXX: safe while disabled
// waste space for alignment, if unaligned
thread->stack_top = (char *)thread->stack_top
- (lvaddr_t)thread->stack_top % STACK_ALIGNMENT;
// Initialise the first (static) thread
thread_init(handle, thread);
thread->detached = true;
#if defined(__x86_64__)
// create segment for TCB
errval_t err = ldt_alloc_segment_disabled(handle, thread,
&thread->thread_seg_selector);
if (err_is_fail(err)) {
USER_PANIC_ERR(err, "error allocating LDT segment for first thread");
}
#endif
uintptr_t param;
registers_get_param(enabled_area, &param);
registers_set_initial(&thread->regs, thread, (lvaddr_t)thread_entry,
/* TODO: pass stack base and limit, choose in arch
* code (possibly setting up other hints on stack) */
(lvaddr_t)thread->stack_top,
(lvaddr_t)bootstrap_thread, param, 0, 0);
// Switch to it (always on this dispatcher)
thread->disp = handle;
thread_enqueue(thread, &disp_gen->runq);
disp_gen->current = thread;
disp->haswork = true;
disp_resume(handle, &thread->regs);
}
/**
* \brief Called on the remote core when spanning a domain across cores
*
* Runs the provided thread after enqueuing it and enabling the dispatcher
*/
void thread_init_remote(dispatcher_handle_t handle, struct thread *thread)
{
struct dispatcher_shared_generic *disp =
get_dispatcher_shared_generic(handle);
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
thread_enqueue(thread, &disp_gen->runq);
disp_gen->current = thread;
disp->haswork = true;
disp_resume(handle, &thread->regs);
}
/**
* \brief Pause (suspend execution of) the given thread, and optionally capture its register state
*
* The thread will not be run, until a subsequent call to thread_resume()
*/
void thread_pause_and_capture_state(struct thread *thread,
arch_registers_state_t **ret_regs)
{
assert(thread != NULL);
dispatcher_handle_t dh = disp_disable();
struct dispatcher_generic *disp = get_dispatcher_generic(dh);
if (thread->disp == dh) {
if (!thread->paused) {
thread->paused = true;
if (thread == disp->current) { // doesn't make much sense...
sys_print("Warning: pausing current thread!\n",100);
assert_disabled(thread->state == THREAD_STATE_RUNNABLE);
thread_block_disabled(dh, NULL);
} else if (thread->state == THREAD_STATE_RUNNABLE) {
thread_remove_from_queue(&disp->runq, thread);
}
}
if (ret_regs != NULL) {
*ret_regs = &thread->regs;
}
} else {
USER_PANIC("NYI: remote dispatcher thread_pause()");
}
disp_enable(dh);
}
/**
* \brief Pause (suspend execution of) the given thread
*
* The thread will not be run, until a subsequent call to thread_resume()
*/
void thread_pause(struct thread *thread)
{
thread_pause_and_capture_state(thread, NULL);
}
/**
* \brief Resume execution of a thread previously suspended by thread_pause()
*/
void thread_resume(struct thread *thread)
{
assert(thread != NULL);
dispatcher_handle_t dh = disp_disable();
struct dispatcher_generic *disp = get_dispatcher_generic(dh);
if (thread->disp == dh) {
if (thread->paused) {
thread->paused = false;
if (thread->state == THREAD_STATE_RUNNABLE) {
thread_enqueue(thread, &disp->runq);
}
}
} else {
USER_PANIC("NYI: remote dispatcher thread_resume()");
}
disp_enable(dh);
}
/**
* \brief Set old-style thread-local storage pointer.
* \param p User's pointer
*/
void thread_set_tls(void *p)
{
struct thread *me = thread_self();
me->userptr = p;
}
void thread_set_tls_key(int key, void *p)
{
struct thread *me = thread_self();
me->userptrs[key] = p;
}
/**
* \brief Return old-style thread-local storage pointer.
* \return User's pointer, previously passed to thread_set_tls()
*/
void *thread_get_tls(void)
{
struct thread *me = thread_self();
return me->userptr;
}
void *thread_get_tls_key(int key)
{
struct thread *me = thread_self();
return me->userptrs[key];
}
/**
* \brief Set the exception handler function for the current thread.
* Optionally also change its stack, and return the old values.
*
* \param newhandler New exception handler. Pass NULL to disable an existing handler.
* \param oldhandler If non-NULL, returns previous exception handler
* \param new_stack_base If non-NULL, sets a new exception handler stack (base)
* \param new_stack_top If non-NULL, sets a new exception handler stack (top)
* \param old_stack_base If non-NULL, returns previous stack base
* \param old_stack_top If non-NULL, returns previous stack top
*/
errval_t thread_set_exception_handler(exception_handler_fn newhandler,
exception_handler_fn *oldhandler,
void *new_stack_base, void *new_stack_top,
void **old_stack_base, void **old_stack_top)
{
struct thread *me = thread_self();
if (oldhandler != NULL) {
*oldhandler = me->exception_handler;
}
if (old_stack_base != NULL) {
*old_stack_base = me->exception_stack;
}
if (old_stack_top != NULL) {
*old_stack_top = me->exception_stack_top;
}
me->exception_handler = newhandler;
if (new_stack_base != NULL && new_stack_top != NULL) {
me->exception_stack = new_stack_base;
me->exception_stack_top = new_stack_top;
}
return SYS_ERR_OK;
}
static void exception_handler_wrapper(arch_registers_state_t *cpuframe,
uintptr_t hack_arg, void *addr)
{
struct thread *me = thread_self();
assert(me->in_exception);
assert(me->exception_handler != NULL);
// XXX: unpack hack arg
enum exception_type type = hack_arg >> 16;
int subtype = hack_arg & 0xffff;
// run handler
me->exception_handler(type, subtype, addr, cpuframe);
// resume state
dispatcher_handle_t dh = disp_disable();
struct dispatcher_generic *disp_gen = get_dispatcher_generic(dh);
//memcpy(&me->regs, cpuframe, sizeof(arch_registers_state_t));
assert_disabled(me->in_exception);
me->in_exception = false;
assert_disabled(disp_gen->current == me);
disp_resume(dh, cpuframe);
}
#if 0
void thread_debug_regs(struct thread *t);
void thread_debug_regs(struct thread *t)
{
printf("%d: RIP = %lx, RSP = %lx\n", disp_get_domain_id(),
t->regs.rip, t->regs.rsp);
uint64_t *stack = (uint64_t *)t->regs.rsp;
printf("%d: ", disp_get_domain_id());
for(int i = 0; i < 30; i++) {
printf("%lx ", stack[i]);
}
printf("\n");
}
#endif
/**
* \brief Deliver an exception to the current thread, and resume.
*
* This may only be called from the dispatcher (on its stack and while
* disabled!).
*
* \param handle Dispatcher handle
* \param type Exception type
* \param subtype Exception subtype
* \param addr Exception address
* \param regs CPU register state at time of exception
*/
void thread_deliver_exception_disabled(dispatcher_handle_t handle,
enum exception_type type, int subtype,
void *addr, arch_registers_state_t *regs)
{
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
struct thread *thread = disp_gen->current;
assert_disabled(thread != NULL);
assert_disabled(disp_gen->runq != NULL);
// can we deliver the exception?
if (thread->exception_handler == NULL || thread->exception_stack_top == NULL
|| thread->in_exception) {
if (thread->in_exception) {
sys_print("Can't deliver exception to thread: already in handler\n",
100);
} else {
sys_print("Can't deliver exception to thread: handler not set\n",
100);
}
// warn on stack overflow.
lvaddr_t sp = (lvaddr_t) registers_get_sp(regs);
if (sp < (lvaddr_t)thread->stack ||
sp > (lvaddr_t)thread->stack_top) {
char str[256];
snprintf(str, sizeof(str), "Error: stack bounds exceeded: sp = 0x%"
PRIxPTR " but [bottom, top] = [0x%" PRIxPTR ", 0x%"
PRIxPTR "]\n", (lvaddr_t) sp, (lvaddr_t) thread->stack,
(lvaddr_t) thread->stack_top);
sys_print(str, sizeof(str));
}
// TODO: actually delete the thread!
disp_gen->current = NULL;
thread_remove_from_queue(&disp_gen->runq, thread);
return;
}
thread->in_exception = true;
lvaddr_t stack_top = (lvaddr_t)thread->exception_stack_top;
// save thread's state at time of fault on top of exception stack
stack_top -= sizeof(arch_registers_state_t);
// Make sure we store the state at an aligned position
stack_top -= stack_top % STACK_ALIGNMENT;
arch_registers_state_t *cpuframe = (void *)stack_top;
memcpy(cpuframe, regs, sizeof(arch_registers_state_t));
// XXX: sanity-check to ensure we have a sensible amount of exception stack left
assert_disabled(stack_top > (lvaddr_t)thread->exception_stack + 8192);
// XXX: pack two small ints together to fit into a single register
uintptr_t hack_arg = (uintptr_t)type << 16 | (subtype & 0xffff);
registers_set_initial(&thread->regs, thread,
(lvaddr_t)exception_handler_wrapper,
stack_top, (lvaddr_t)cpuframe, hack_arg,
(lvaddr_t)addr, 0);
disp_resume(handle, &thread->regs);
}