aos/lib/aos/thread_sync.c
Daniel Schwyn 6d444bf552 Main handout
Signed-off-by: Daniel Schwyn <daniel.schwyn@inf.ethz.ch>
2022-03-03 14:57:51 +01:00

383 lines
9.0 KiB
C

/**
* \file
* \brief Thread synchronisation primitives.
*/
/*
* Copyright (c) 2007, 2008, 2009, 2010, 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 <aos/aos.h>
#include <aos/dispatch.h>
#include <aos/dispatcher_arch.h>
#include "threads_priv.h"
/**
* \brief Initialise a condition variable
*
* \param cond Condition variable pointer
*/
void thread_cond_init(struct thread_cond *cond)
{
cond->queue = NULL;
cond->lock = 0;
}
/**
* \brief Wait for a condition variable
*
* This function waits for the given condition variable to be signalled
* (through thread_cond_signal() or thread_cond_broadcast()) before
* returning, while atomically unlocking the mutex pointed to by
* 'mutex'.
*
* \param cond Condition variable pointer
* \param mutex Optional pointer to mutex to unlock.
*/
void thread_cond_wait(struct thread_cond *cond, struct thread_mutex *mutex)
{
dispatcher_handle_t disp = disp_disable();
acquire_spinlock(&cond->lock);
// Release the lock
if (mutex != NULL) {
struct thread *wakeup = thread_mutex_unlock_disabled(disp, mutex);
if(wakeup != NULL) {
assert(disp == wakeup->disp);
thread_resume(wakeup);
}
}
// Block on the condition variable and release spinlock
thread_block_and_release_spinlock_disabled(disp, &cond->queue, &cond->lock);
// Re-acquire the mutex
if (mutex != NULL) {
thread_mutex_lock(mutex);
}
}
/**
* \brief Signal a condition variable
*
* This function signals the condition variable, and wakes up one
* thread waiting on it.
*
* \param cond Condition variable pointer
*/
void thread_cond_signal(struct thread_cond *cond)
{
struct thread *wakeup = NULL;
errval_t err = SYS_ERR_OK;
// Wakeup one waiting thread
dispatcher_handle_t disp = disp_disable();
acquire_spinlock(&cond->lock);
if (cond->queue != NULL) {
wakeup = thread_unblock_one_disabled(disp, &cond->queue, NULL);
if(wakeup != NULL) {
thread_resume(wakeup);
}
}
release_spinlock(&cond->lock);
disp_enable(disp);
if(err_is_fail(err)) {
USER_PANIC_ERR(err, "remote wakeup from condition signal");
}
if(wakeup != NULL) {
// XXX: Need directed yield to inter-disp thread
thread_yield();
}
}
/**
* \brief Broadcast signal a condition variable
*
* This function signals the condition variable, and wakes up all
* threads waiting on it.
*
* \param cond Condition variable pointer
*/
void thread_cond_broadcast(struct thread_cond *cond)
{
struct thread *wakeupq = NULL;
bool foreignwakeup = false;
// Wakeup all waiting threads
dispatcher_handle_t disp = disp_disable();
acquire_spinlock(&cond->lock);
wakeupq = thread_unblock_all_disabled(disp, &cond->queue, NULL);
release_spinlock(&cond->lock);
disp_enable(disp);
foreignwakeup = (wakeupq != NULL);
// Now, wakeup all on foreign dispatchers
while (wakeupq != NULL) {
struct thread *wakeup = wakeupq;
wakeupq = wakeupq->next;
thread_resume(wakeup);
}
if(foreignwakeup) {
// XXX: Need directed yield to inter-disp thread
thread_yield();
}
}
/**
* \brief Initialise a mutex
*
* \param mutex Mutex pointer
*/
void thread_mutex_init(struct thread_mutex *mutex)
{
mutex->locked = 0;
mutex->holder = NULL;
mutex->queue = NULL;
mutex->lock = 0;
}
/**
* \brief Lock a mutex
*
* This blocks until the given mutex is unlocked, and then atomically locks it.
*
* \param mutex Mutex pointer
*/
void thread_mutex_lock(struct thread_mutex *mutex)
{
dispatcher_handle_t handle = disp_disable();
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
acquire_spinlock(&mutex->lock);
if (mutex->locked > 0) {
thread_block_and_release_spinlock_disabled(handle, &mutex->queue,
&mutex->lock);
} else {
mutex->locked = 1;
mutex->holder = disp_gen->current;
release_spinlock(&mutex->lock);
disp_enable(handle);
}
}
/**
* \brief Lock a mutex
*
* This blocks until the given mutex is unlocked, and then atomically locks it.
*
* \param mutex Mutex pointer
*/
void thread_mutex_lock_nested(struct thread_mutex *mutex)
{
dispatcher_handle_t handle = disp_disable();
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
acquire_spinlock(&mutex->lock);
if (mutex->locked > 0
&& mutex->holder != disp_gen->current) {
thread_block_and_release_spinlock_disabled(handle, &mutex->queue,
&mutex->lock);
} else {
mutex->locked++;
mutex->holder = disp_gen->current;
release_spinlock(&mutex->lock);
disp_enable(handle);
}
}
/**
* \brief Try to lock a mutex
*
* If the given mutex is unlocked, this atomically locks it and returns true,
* otherwise it returns false immediately.
*
* \param mutex Mutex pointer
*
* \returns true if lock acquired, false otherwise
*/
bool thread_mutex_trylock(struct thread_mutex *mutex)
{
// Try first to avoid contention
if (mutex->locked > 0) {
return false;
}
dispatcher_handle_t handle = disp_disable();
struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
bool ret;
acquire_spinlock(&mutex->lock);
if (mutex->locked > 0) {
ret = false;
} else {
ret = true;
mutex->locked = 1;
mutex->holder = disp_gen->current;
}
release_spinlock(&mutex->lock);
disp_enable(handle);
return ret;
}
/**
* \brief Unlock a mutex, while disabled
*
* This function unlocks the given mutex. It may only be called while disabled.
*
* \param disp Dispatcher pointer
* \param mutex Mutex pointer
*
* \return Pointer to thread to be woken on foreign dispatcher
*/
struct thread *thread_mutex_unlock_disabled(dispatcher_handle_t handle,
struct thread_mutex *mutex)
{
struct thread *ft = NULL;
acquire_spinlock(&mutex->lock);
assert_disabled(mutex->locked > 0);
if(mutex->locked == 1) {
// Wakeup one waiting thread
if (mutex->queue != NULL) {
// XXX: This assumes dequeueing is off the top of the queue
mutex->holder = mutex->queue;
ft = thread_unblock_one_disabled(handle, &mutex->queue, NULL);
} else {
mutex->holder = NULL;
mutex->locked = 0;
}
} else {
mutex->locked--;
}
release_spinlock(&mutex->lock);
return ft;
}
/**
* \brief Unlock a mutex
*
* This unlocks the given mutex.
*
* \param mutex Mutex pointer
*/
void thread_mutex_unlock(struct thread_mutex *mutex)
{
dispatcher_handle_t disp = disp_disable();
struct thread *wakeup = thread_mutex_unlock_disabled(disp, mutex);
errval_t err = SYS_ERR_OK;
if (wakeup != NULL) {
thread_resume(wakeup);
}
disp_enable(disp);
if(err_is_fail(err)) {
USER_PANIC_ERR(err, "remote wakeup from mutex unlock");
}
if(wakeup != NULL) {
// XXX: Need directed yield to inter-disp thread
thread_yield();
}
}
void thread_sem_init(struct thread_sem *sem, unsigned int value)
{
assert(sem != NULL);
sem->value = value;
sem->queue = NULL;
sem->lock = 0;
}
void thread_sem_wait(struct thread_sem *sem)
{
assert(sem != NULL);
dispatcher_handle_t disp = disp_disable();
acquire_spinlock(&sem->lock);
if(sem->value < 1) {
// Not possible to decrement -- wait!
thread_block_and_release_spinlock_disabled(disp, &sem->queue, &sem->lock);
} else {
// Decrement possible
sem->value--;
release_spinlock(&sem->lock);
disp_enable(disp);
}
}
bool thread_sem_trywait(struct thread_sem *sem)
{
assert(sem != NULL);
bool ret = false;
dispatcher_handle_t disp = disp_disable();
acquire_spinlock(&sem->lock);
if(sem->value >= 1) {
// Decrement possible
sem->value--;
ret = true;
}
release_spinlock(&sem->lock);
disp_enable(disp);
return ret;
}
void thread_sem_post(struct thread_sem *sem)
{
assert(sem != NULL);
dispatcher_handle_t disp = disp_disable();
struct thread *wakeup = NULL;
errval_t err = SYS_ERR_OK;
acquire_spinlock(&sem->lock);
// Wakeup one?
if(sem->value == 0 && sem->queue != NULL) {
wakeup = thread_unblock_one_disabled(disp, &sem->queue, NULL);
} else {
sem->value++;
}
if(wakeup != NULL) {
thread_resume(wakeup);
}
release_spinlock(&sem->lock);
disp_enable(disp);
if(err_is_fail(err)) {
USER_PANIC_ERR(err, "remote wakeup from semaphore post");
}
if(wakeup != NULL) {
// XXX: Need directed yield to inter-disp thread
thread_yield();
}
}