aboutsummaryrefslogtreecommitdiff
path: root/linuxthreads/semaphore.c
diff options
context:
space:
mode:
Diffstat (limited to 'linuxthreads/semaphore.c')
-rw-r--r--linuxthreads/semaphore.c236
1 files changed, 236 insertions, 0 deletions
diff --git a/linuxthreads/semaphore.c b/linuxthreads/semaphore.c
new file mode 100644
index 0000000000..bd07439cde
--- /dev/null
+++ b/linuxthreads/semaphore.c
@@ -0,0 +1,236 @@
+/* Linuxthreads - a simple clone()-based implementation of Posix */
+/* threads for Linux. */
+/* Copyright (C) 1996 Xavier Leroy (Xavier.Leroy@inria.fr) */
+/* */
+/* This program is free software; you can redistribute it and/or */
+/* modify it under the terms of the GNU Library General Public License */
+/* as published by the Free Software Foundation; either version 2 */
+/* of the License, or (at your option) any later version. */
+/* */
+/* This program is distributed in the hope that it will be useful, */
+/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
+/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
+/* GNU Library General Public License for more details. */
+
+/* Semaphores a la POSIX 1003.1b */
+
+#include "pthread.h"
+#include "semaphore.h"
+#include "internals.h"
+#include "restart.h"
+
+
+#if !defined HAS_COMPARE_AND_SWAP && !defined TEST_FOR_COMPARE_AND_SWAP
+/* If we have no atomic compare and swap, fake it using an extra spinlock. */
+
+#include "spinlock.h"
+static inline int sem_compare_and_swap(sem_t *sem, long oldval, long newval)
+{
+ int ret;
+ acquire(&sem->sem_spinlock);
+ ret = (sem->sem_status == oldval);
+ if (ret) sem->sem_status = newval;
+ release(&sem->sem_spinlock);
+ return ret;
+}
+
+#elif defined TEST_FOR_COMPARE_AND_SWAP
+
+#include "spinlock.h"
+static int has_compare_and_swap = -1; /* to be determined at run-time */
+
+static inline int sem_compare_and_swap(sem_t *sem, long oldval, long newval)
+{
+ int ret;
+
+ if (has_compare_and_swap == 1)
+ return __compare_and_swap(&sem->sem_status, oldval, newval);
+
+ acquire(&sem->sem_spinlock);
+ ret = (sem->sem_status == oldval);
+ if (ret) sem->sem_status = newval;
+ release(&sem->sem_spinlock);
+ return ret;
+}
+
+#else
+/* But if we do have an atomic compare and swap, use it! */
+
+static inline int sem_compare_and_swap(sem_t *sem, long oldval, long newval)
+{
+ return __compare_and_swap(&sem->sem_status, oldval, newval);
+}
+
+#endif
+
+
+/* The state of a semaphore is represented by a long int encoding
+ either the semaphore count if >= 0 and no thread is waiting on it,
+ or the head of the list of threads waiting for the semaphore.
+ To distinguish the two cases, we encode the semaphore count N
+ as 2N+1, so that it has the lowest bit set.
+
+ A sequence of sem_wait operations on a semaphore initialized to N
+ result in the following successive states:
+ 2N+1, 2N-1, ..., 3, 1, &first_waiting_thread, &second_waiting_thread, ...
+*/
+
+static void sem_restart_list(pthread_descr waiting);
+
+int sem_init(sem_t *sem, int pshared, unsigned int value)
+{
+ if ((long)value > SEM_VALUE_MAX) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (pshared) {
+ errno = ENOSYS;
+ return -1;
+ }
+#ifdef TEST_FOR_COMPARE_AND_SWAP
+ if (has_compare_and_swap == -1) {
+ has_compare_and_swap = compare_and_swap_is_available();
+ }
+#endif
+#if !defined HAS_COMPARE_AND_SWAP || defined TEST_FOR_COMPARE_AND_SWAP
+ sem->sem_spinlock = 0;
+#endif
+ sem->sem_status = ((long)value << 1) + 1;
+ return 0;
+}
+
+int sem_wait(sem_t * sem)
+{
+ long oldstatus, newstatus;
+ volatile pthread_descr self = thread_self();
+ pthread_descr * th;
+
+ while (1) {
+ do {
+ oldstatus = sem->sem_status;
+ if ((oldstatus & 1) && (oldstatus != 1))
+ newstatus = oldstatus - 2;
+ else {
+ newstatus = (long) self;
+ self->p_nextwaiting = (pthread_descr) oldstatus;
+ }
+ }
+ while (! sem_compare_and_swap(sem, oldstatus, newstatus));
+ if (newstatus & 1)
+ /* We got the semaphore. */
+ return 0;
+ /* Wait for sem_post or cancellation */
+ suspend_with_cancellation(self);
+ /* This is a cancellation point */
+ if (self->p_canceled && self->p_cancelstate == PTHREAD_CANCEL_ENABLE) {
+ /* Remove ourselves from the waiting list if we're still on it */
+ /* First check if we're at the head of the list. */
+ do {
+ oldstatus = sem->sem_status;
+ if (oldstatus != (long) self) break;
+ newstatus = (long) self->p_nextwaiting;
+ }
+ while (! sem_compare_and_swap(sem, oldstatus, newstatus));
+ /* Now, check if we're somewhere in the list.
+ There's a race condition with sem_post here, but it does not matter:
+ the net result is that at the time pthread_exit is called,
+ self is no longer reachable from sem->sem_status. */
+ if (oldstatus != (long) self && (oldstatus & 1) == 0) {
+ for (th = &(((pthread_descr) oldstatus)->p_nextwaiting);
+ *th != NULL && *th != (pthread_descr) 1;
+ th = &((*th)->p_nextwaiting)) {
+ if (*th == self) {
+ *th = self->p_nextwaiting;
+ break;
+ }
+ }
+ }
+ pthread_exit(PTHREAD_CANCELED);
+ }
+ }
+}
+
+int sem_trywait(sem_t * sem)
+{
+ long oldstatus, newstatus;
+
+ do {
+ oldstatus = sem->sem_status;
+ if ((oldstatus & 1) == 0 || (oldstatus == 1)) {
+ errno = EAGAIN;
+ return -1;
+ }
+ newstatus = oldstatus - 2;
+ }
+ while (! sem_compare_and_swap(sem, oldstatus, newstatus));
+ return 0;
+}
+
+int sem_post(sem_t * sem)
+{
+ long oldstatus, newstatus;
+
+ do {
+ oldstatus = sem->sem_status;
+ if ((oldstatus & 1) == 0)
+ newstatus = 3;
+ else {
+ if (oldstatus >= SEM_VALUE_MAX) {
+ /* Overflow */
+ errno = ERANGE;
+ return -1;
+ }
+ newstatus = oldstatus + 2;
+ }
+ }
+ while (! sem_compare_and_swap(sem, oldstatus, newstatus));
+ if ((oldstatus & 1) == 0)
+ sem_restart_list((pthread_descr) oldstatus);
+ return 0;
+}
+
+int sem_getvalue(sem_t * sem, int * sval)
+{
+ long status = sem->sem_status;
+ if (status & 1)
+ *sval = (int)((unsigned long) status >> 1);
+ else
+ *sval = 0;
+ return 0;
+}
+
+int sem_destroy(sem_t * sem)
+{
+ if ((sem->sem_status & 1) == 0) {
+ errno = EBUSY;
+ return -1;
+ }
+ return 0;
+}
+
+/* Auxiliary function for restarting all threads on a waiting list,
+ in priority order. */
+
+static void sem_restart_list(pthread_descr waiting)
+{
+ pthread_descr th, towake, *p;
+
+ /* Sort list of waiting threads by decreasing priority (insertion sort) */
+ towake = NULL;
+ while (waiting != (pthread_descr) 1) {
+ th = waiting;
+ waiting = waiting->p_nextwaiting;
+ p = &towake;
+ while (*p != NULL && th->p_priority < (*p)->p_priority)
+ p = &((*p)->p_nextwaiting);
+ th->p_nextwaiting = *p;
+ *p = th;
+ }
+ /* Wake up threads in priority order */
+ while (towake != NULL) {
+ th = towake;
+ towake = towake->p_nextwaiting;
+ th->p_nextwaiting = NULL;
+ restart(th);
+ }
+}