/* $OpenBSD: rthread_sync.c,v 1.23 2010/04/12 03:34:31 guenther Exp $ */ /* * Copyright (c) 2004,2005 Ted Unangst * All Rights Reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Mutexes, conditions, rwlocks, and semaphores - synchronization functions. */ #include #include #include #include #include #include #include #include #include #include #include #include "rthread.h" static _spinlock_lock_t static_init_lock = _SPINLOCK_UNLOCKED; /* * Internal implementation of semaphores */ int _sem_wait(sem_t sem, int tryonly) { _spinlock(&sem->lock); return (_sem_waitl(sem, tryonly, 0, NULL)); } int _sem_waitl(sem_t sem, int tryonly, clockid_t clock_id, const struct timespec *abstime) { int do_sleep; again: if (sem->value == 0) { if (tryonly) { _spinunlock(&sem->lock); return (0); } sem->waitcount++; do_sleep = 1; } else { sem->value--; do_sleep = 0; } if (do_sleep) { if (thrsleep(sem, clock_id, abstime, &sem->lock) == -1 && errno == EWOULDBLOCK) return (0); _spinlock(&sem->lock); sem->waitcount--; goto again; } _spinunlock(&sem->lock); return (1); } /* always increment count */ int _sem_post(sem_t sem) { int rv = 0; _spinlock(&sem->lock); sem->value++; if (sem->waitcount) { thrwakeup(sem, 1); rv = 1; } _spinunlock(&sem->lock); return (rv); } /* only increment count if a waiter */ int _sem_wakeup(sem_t sem) { int rv = 0; _spinlock(&sem->lock); if (sem->waitcount) { sem->value++; thrwakeup(sem, 1); rv = 1; } _spinunlock(&sem->lock); return (rv); } int _sem_wakeall(sem_t sem) { int rv; _spinlock(&sem->lock); rv = sem->waitcount; sem->value += rv; thrwakeup(sem, 0); _spinunlock(&sem->lock); return (rv); } /* * exported semaphores */ int sem_init(sem_t *semp, int pshared, unsigned int value) { sem_t sem; if (pshared) { errno = EPERM; return (-1); } sem = calloc(1, sizeof(*sem)); if (!sem) return (-1); sem->value = value; *semp = sem; return (0); } int sem_destroy(sem_t *semp) { if (!*semp) return (EINVAL); if ((*semp)->waitcount) { #define MSG "sem_destroy on semaphore with waiters!\n" write(2, MSG, sizeof(MSG) - 1); #undef MSG return (EBUSY); } free(*semp); *semp = NULL; return (0); } int sem_getvalue(sem_t *semp, int *sval) { sem_t sem = *semp; _spinlock(&sem->lock); *sval = sem->value; _spinunlock(&sem->lock); return (0); } int sem_post(sem_t *semp) { sem_t sem = *semp; _sem_post(sem); return (0); } int sem_wait(sem_t *semp) { sem_t sem = *semp; _sem_wait(sem, 0); return (0); } int sem_trywait(sem_t *semp) { sem_t sem = *semp; int rv; rv = _sem_wait(sem, 1); if (!rv) { errno = EAGAIN; return (-1); } return (0); } /* * mutexen */ int pthread_mutex_init(pthread_mutex_t *mutexp, const pthread_mutexattr_t *attr) { pthread_mutex_t mutex; mutex = calloc(1, sizeof(*mutex)); if (!mutex) return (errno); mutex->sem.lock = _SPINLOCK_UNLOCKED; mutex->sem.value = 1; /* unlocked */ mutex->type = attr ? (*attr)->type : PTHREAD_MUTEX_ERRORCHECK; *mutexp = mutex; return (0); } int pthread_mutex_destroy(pthread_mutex_t *mutexp) { if ((*mutexp) && (*mutexp)->count) { #define MSG "pthread_mutex_destroy on mutex with waiters!\n" write(2, MSG, sizeof(MSG) - 1); #undef MSG return (EBUSY); } free((void *)*mutexp); *mutexp = NULL; return (0); } static int _rthread_mutex_lock(pthread_mutex_t *mutexp, int trywait) { pthread_mutex_t mutex; pthread_t thread = pthread_self(); int ret = 0; /* * If the mutex is statically initialized, perform the dynamic * initialization. Note: _thread_mutex_lock() in libc requires * _rthread_mutex_lock() to perform the mutex init when *mutexp * is NULL. */ if (*mutexp == NULL) { _spinlock(&static_init_lock); if (*mutexp == NULL) ret = pthread_mutex_init(mutexp, NULL); _spinunlock(&static_init_lock); if (ret != 0) return (EINVAL); } mutex = *mutexp; if (mutex->owner == thread) { if (mutex->type == PTHREAD_MUTEX_RECURSIVE) { mutex->count++; return (0); } if (mutex->type == PTHREAD_MUTEX_ERRORCHECK) return (EDEADLK); } if (!_sem_wait((void *)&mutex->sem, trywait)) return (EBUSY); mutex->owner = thread; mutex->count = 1; return (0); } int pthread_mutex_lock(pthread_mutex_t *p) { return (_rthread_mutex_lock(p, 0)); } int pthread_mutex_trylock(pthread_mutex_t *p) { return (_rthread_mutex_lock(p, 1)); } int pthread_mutex_unlock(pthread_mutex_t *mutexp) { pthread_t thread = pthread_self(); pthread_mutex_t mutex = *mutexp; if (mutex->owner != thread) return (EPERM); if (--mutex->count == 0) { mutex->owner = NULL; _sem_post((void *)&mutex->sem); } return (0); } /* * mutexen attributes */ int pthread_mutexattr_init(pthread_mutexattr_t *attrp) { pthread_mutexattr_t attr; attr = calloc(1, sizeof(*attr)); if (!attr) return (errno); attr->type = PTHREAD_MUTEX_ERRORCHECK; *attrp = attr; return (0); } int pthread_mutexattr_destroy(pthread_mutexattr_t *attrp) { free(*attrp); *attrp = NULL; return (0); } int pthread_mutexattr_settype(pthread_mutexattr_t *attrp, int type) { (*attrp)->type = type; return (0); } int pthread_mutexattr_gettype(pthread_mutexattr_t *attrp, int *type) { *type = (*attrp)->type; return (0); } /* * condition variables */ int pthread_cond_init(pthread_cond_t *condp, const pthread_condattr_t *attrp) { pthread_cond_t cond; cond = calloc(1, sizeof(*cond)); if (!cond) return (errno); cond->sem.lock = _SPINLOCK_UNLOCKED; *condp = cond; return (0); } int pthread_cond_destroy(pthread_cond_t *condp) { free(*condp); *condp = NULL; return (0); } int pthread_cond_timedwait(pthread_cond_t *condp, pthread_mutex_t *mutexp, const struct timespec *abstime) { int error; int rv; if (!*condp) if ((error = pthread_cond_init(condp, NULL))) return (error); _spinlock(&(*condp)->sem.lock); pthread_mutex_unlock(mutexp); rv = _sem_waitl(&(*condp)->sem, 0, CLOCK_REALTIME, abstime); error = pthread_mutex_lock(mutexp); return (error ? error : rv ? 0 : ETIMEDOUT); } int pthread_cond_wait(pthread_cond_t *condp, pthread_mutex_t *mutexp) { return (pthread_cond_timedwait(condp, mutexp, NULL)); } int pthread_cond_signal(pthread_cond_t *condp) { int error; if (!*condp) if ((error = pthread_cond_init(condp, NULL))) return (error); _sem_wakeup(&(*condp)->sem); return (0); } int pthread_cond_broadcast(pthread_cond_t *condp) { if (!*condp) pthread_cond_init(condp, NULL); _sem_wakeall(&(*condp)->sem); return (0); } /* * condition variable attributes */ int pthread_condattr_init(pthread_condattr_t *attrp) { pthread_condattr_t attr; attr = calloc(1, sizeof(*attr)); if (!attr) return (errno); *attrp = attr; return (0); } int pthread_condattr_destroy(pthread_condattr_t *attrp) { free(*attrp); *attrp = NULL; return (0); } /* * rwlocks */ int pthread_rwlock_init(pthread_rwlock_t *lockp, const pthread_rwlockattr_t *attrp) { pthread_rwlock_t lock; lock = calloc(1, sizeof(*lock)); if (!lock) return (errno); lock->lock = _SPINLOCK_UNLOCKED; lock->sem.lock = _SPINLOCK_UNLOCKED; *lockp = lock; return (0); } int pthread_rwlock_destroy(pthread_rwlock_t *lockp) { if ((*lockp) && ((*lockp)->readers || (*lockp)->writer)) { #define MSG "pthread_rwlock_destroy on rwlock with waiters!\n" write(2, MSG, sizeof(MSG) - 1); #undef MSG return (EBUSY); } free(*lockp); *lockp = NULL; return (0); } static int _rthread_rwlock_ensure_init(pthread_rwlock_t *lockp) { int ret = 0; /* * If the rwlock is statically initialized, perform the dynamic * initialization. */ if (*lockp == NULL) { _spinlock(&static_init_lock); if (*lockp == NULL) ret = pthread_rwlock_init(lockp, NULL); _spinunlock(&static_init_lock); } return (ret); } int pthread_rwlock_rdlock(pthread_rwlock_t *lockp) { pthread_rwlock_t lock; int error; if ((error = _rthread_rwlock_ensure_init(lockp))) return (error); lock = *lockp; again: _spinlock(&lock->lock); if (lock->writer) { _spinlock(&lock->sem.lock); _spinunlock(&lock->lock); _sem_waitl(&lock->sem, 0, 0, NULL); goto again; } lock->readers++; _spinunlock(&lock->lock); return (0); } int pthread_rwlock_timedrdlock(pthread_rwlock_t *lockp, const struct timespec *abstime) { pthread_rwlock_t lock; int do_wait = 1; int error; if ((error = _rthread_rwlock_ensure_init(lockp))) return (error); lock = *lockp; _spinlock(&lock->lock); while (lock->writer && do_wait) { _spinlock(&lock->sem.lock); _spinunlock(&lock->lock); do_wait = _sem_waitl(&lock->sem, 0, CLOCK_REALTIME, abstime); _spinlock(&lock->lock); } if (lock->writer) { /* do_wait must be 0, so timed out */ _spinunlock(&lock->lock); return (ETIMEDOUT); } lock->readers++; _spinunlock(&lock->lock); return (0); } int pthread_rwlock_tryrdlock(pthread_rwlock_t *lockp) { pthread_rwlock_t lock; int error; if ((error = _rthread_rwlock_ensure_init(lockp))) return (error); lock = *lockp; _spinlock(&lock->lock); if (lock->writer) { _spinunlock(&lock->lock); return (EBUSY); } lock->readers++; _spinunlock(&lock->lock); return (0); } int pthread_rwlock_wrlock(pthread_rwlock_t *lockp) { pthread_rwlock_t lock; int error; if ((error = _rthread_rwlock_ensure_init(lockp))) return (error); lock = *lockp; _spinlock(&lock->lock); lock->writer++; while (lock->readers) { _spinlock(&lock->sem.lock); _spinunlock(&lock->lock); _sem_waitl(&lock->sem, 0, 0, NULL); _spinlock(&lock->lock); } lock->readers = -pthread_self()->tid; _spinunlock(&lock->lock); return (0); } int pthread_rwlock_timedwrlock(pthread_rwlock_t *lockp, const struct timespec *abstime) { pthread_rwlock_t lock; int do_wait = 1; int error; if ((error = _rthread_rwlock_ensure_init(lockp))) return (error); lock = *lockp; _spinlock(&lock->lock); lock->writer++; while (lock->readers && do_wait) { _spinlock(&lock->sem.lock); _spinunlock(&lock->lock); do_wait = _sem_waitl(&lock->sem, 0, CLOCK_REALTIME, abstime); _spinlock(&lock->lock); } if (lock->readers) { /* do_wait must be 0, so timed out */ lock->writer--; _spinunlock(&lock->lock); return (ETIMEDOUT); } lock->readers = -pthread_self()->tid; _spinunlock(&lock->lock); return (0); } int pthread_rwlock_trywrlock(pthread_rwlock_t *lockp) { pthread_rwlock_t lock; int error; if ((error = _rthread_rwlock_ensure_init(lockp))) return (error); lock = *lockp; _spinlock(&lock->lock); if (lock->readers || lock->writer) { _spinunlock(&lock->lock); return (EBUSY); } lock->writer = 1; lock->readers = -pthread_self()->tid; _spinunlock(&lock->lock); return (0); } int pthread_rwlock_unlock(pthread_rwlock_t *lockp) { pthread_rwlock_t lock; lock = *lockp; _spinlock(&lock->lock); if (lock->readers == -pthread_self()->tid) { lock->readers = 0; lock->writer--; } else if (lock->readers > 0) { lock->readers--; } else { _spinunlock(&lock->lock); return (EPERM); } _spinunlock(&lock->lock); _sem_wakeall(&lock->sem); return (0); } /* * rwlock attributes */ int pthread_rwlockattr_init(pthread_rwlockattr_t *attrp) { pthread_rwlockattr_t attr; attr = calloc(1, sizeof(*attr)); if (!attr) return (errno); *attrp = attr; return (0); } int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attrp) { free(*attrp); *attrp = NULL; return (0); } /* * pthread_once */ int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)) { pthread_mutex_lock(&once_control->mutex); if (once_control->state == PTHREAD_NEEDS_INIT) { init_routine(); once_control->state = PTHREAD_DONE_INIT; } pthread_mutex_unlock(&once_control->mutex); return (0); }