/* * This file contains the old semaphore code that we need to * preserve for glibc-2.0 backwards compatibility. Port to glibc 2.1 * done by Cristian Gafton. */ /* 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 #include "pthread.h" #include "internals.h" #include "spinlock.h" #include "restart.h" #include "queue.h" typedef struct { long int sem_status; int sem_spinlock; } old_sem_t; /* Maximum value the semaphore can have. */ #define SEM_VALUE_MAX ((int) ((~0u) >> 1)) static inline int sem_compare_and_swap(old_sem_t *sem, long oldval, long newval) { return __pthread_compare_and_swap(&sem->sem_status, oldval, newval, &sem->sem_spinlock); } /* 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 __old_sem_init(old_sem_t *sem, int pshared, unsigned int value) { if (value > SEM_VALUE_MAX) { errno = EINVAL; return -1; } if (pshared) { errno = ENOSYS; return -1; } sem->sem_spinlock = 0; sem->sem_status = ((long)value << 1) + 1; return 0; } int __old_sem_wait(old_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 __old_sem_trywait(old_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 __old_sem_post(old_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 __old_sem_getvalue(old_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 __old_sem_destroy(old_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); } } #if defined PIC && DO_VERSIONING symbol_version (__old_sem_init, sem_init, GLIBC_2.0); symbol_version (__old_sem_wait, sem_wait, GLIBC_2.0); symbol_version (__old_sem_trywait, sem_trywait, GLIBC_2.0); symbol_version (__old_sem_post, sem_post, GLIBC_2.0); symbol_version (__old_sem_getvalue, sem_getvalue, GLIBC_2.0); symbol_version (__old_sem_destroy, sem_destroy, GLIBC_2.0); #endif