From 04303f5a8ab9a992f3671d46b6ee2171582cbd61 Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Thu, 18 Sep 2014 16:11:59 -0700 Subject: [PATCH] Add semaphore tests, fix sem_destroy. Bug: https://code.google.com/p/android/issues/detail?id=76088 Change-Id: I4a0561b23e90312384d40a1c804ca64ee98f4066 --- libc/Android.mk | 2 +- libc/bionic/bionic_time_conversions.cpp | 18 ++ libc/bionic/pthread_cond.cpp | 6 +- libc/bionic/pthread_internal.h | 2 - libc/bionic/pthread_internals.cpp | 16 - libc/bionic/pthread_mutex.cpp | 14 +- libc/bionic/pthread_rwlock.cpp | 3 +- libc/bionic/semaphore.c | 398 ------------------------ libc/bionic/semaphore.cpp | 322 +++++++++++++++++++ libc/bionic/sysconf.cpp | 133 ++------ libc/include/limits.h | 4 + libc/include/semaphore.h | 27 +- libc/private/bionic_constants.h | 22 ++ libc/private/bionic_time_conversions.h | 2 + tests/Android.mk | 1 + tests/semaphore_test.cpp | 143 +++++++++ tests/time_test.cpp | 4 +- 17 files changed, 567 insertions(+), 550 deletions(-) delete mode 100644 libc/bionic/semaphore.c create mode 100644 libc/bionic/semaphore.cpp create mode 100644 libc/private/bionic_constants.h create mode 100644 tests/semaphore_test.cpp diff --git a/libc/Android.mk b/libc/Android.mk index 0f828ed7c..489200f7e 100644 --- a/libc/Android.mk +++ b/libc/Android.mk @@ -53,7 +53,6 @@ libc_common_src_files := \ bionic/pututline.c \ bionic/sched_cpualloc.c \ bionic/sched_cpucount.c \ - bionic/semaphore.c \ bionic/sigblock.c \ bionic/siginterrupt.c \ bionic/sigsetmask.c \ @@ -182,6 +181,7 @@ libc_bionic_src_files := \ bionic/scandir.cpp \ bionic/sched_getaffinity.cpp \ bionic/sched_getcpu.cpp \ + bionic/semaphore.cpp \ bionic/send.cpp \ bionic/setegid.cpp \ bionic/__set_errno.cpp \ diff --git a/libc/bionic/bionic_time_conversions.cpp b/libc/bionic/bionic_time_conversions.cpp index 7f3c026e2..75e8d4986 100644 --- a/libc/bionic/bionic_time_conversions.cpp +++ b/libc/bionic/bionic_time_conversions.cpp @@ -28,6 +28,8 @@ #include "private/bionic_time_conversions.h" +#include "private/bionic_constants.h" + bool timespec_from_timeval(timespec& ts, const timeval& tv) { // Whole seconds can just be copied. ts.tv_sec = tv.tv_sec; @@ -49,3 +51,19 @@ void timeval_from_timespec(timeval& tv, const timespec& ts) { tv.tv_sec = ts.tv_sec; tv.tv_usec = ts.tv_nsec / 1000; } + +// Initializes 'ts' with the difference between 'abs_ts' and the current time +// according to 'clock'. Returns false if abstime already expired, true otherwise. +bool timespec_from_absolute_timespec(timespec& ts, const timespec& abs_ts, clockid_t clock) { + clock_gettime(clock, &ts); + ts.tv_sec = abs_ts.tv_sec - ts.tv_sec; + ts.tv_nsec = abs_ts.tv_nsec - ts.tv_nsec; + if (ts.tv_nsec < 0) { + ts.tv_sec--; + ts.tv_nsec += NS_PER_S; + } + if (ts.tv_nsec < 0 || ts.tv_sec < 0) { + return false; + } + return true; +} diff --git a/libc/bionic/pthread_cond.cpp b/libc/bionic/pthread_cond.cpp index e623b62eb..32ff81ac6 100644 --- a/libc/bionic/pthread_cond.cpp +++ b/libc/bionic/pthread_cond.cpp @@ -163,12 +163,12 @@ int __pthread_cond_timedwait_relative(pthread_cond_t* cond, pthread_mutex_t* mut } __LIBC_HIDDEN__ -int __pthread_cond_timedwait(pthread_cond_t* cond, pthread_mutex_t* mutex, const timespec* abstime, clockid_t clock) { +int __pthread_cond_timedwait(pthread_cond_t* cond, pthread_mutex_t* mutex, const timespec* abs_ts, clockid_t clock) { timespec ts; timespec* tsp; - if (abstime != NULL) { - if (__timespec_from_absolute(&ts, abstime, clock) < 0) { + if (abs_ts != NULL) { + if (!timespec_from_absolute_timespec(ts, *abs_ts, clock)) { return ETIMEDOUT; } tsp = &ts; diff --git a/libc/bionic/pthread_internal.h b/libc/bionic/pthread_internal.h index a5b300220..392e781e4 100644 --- a/libc/bionic/pthread_internal.h +++ b/libc/bionic/pthread_internal.h @@ -117,8 +117,6 @@ __LIBC_HIDDEN__ void _pthread_internal_remove_locked(pthread_internal_t* thread) __LIBC_HIDDEN__ extern pthread_internal_t* g_thread_list; __LIBC_HIDDEN__ extern pthread_mutex_t g_thread_list_lock; -__LIBC_HIDDEN__ int __timespec_from_absolute(timespec*, const timespec*, clockid_t); - /* Needed by fork. */ __LIBC_HIDDEN__ extern void __bionic_atfork_run_prepare(); __LIBC_HIDDEN__ extern void __bionic_atfork_run_child(); diff --git a/libc/bionic/pthread_internals.cpp b/libc/bionic/pthread_internals.cpp index 4c08ba87b..19c00d47c 100644 --- a/libc/bionic/pthread_internals.cpp +++ b/libc/bionic/pthread_internals.cpp @@ -67,19 +67,3 @@ void _pthread_internal_add(pthread_internal_t* thread) { pthread_internal_t* __get_thread(void) { return reinterpret_cast(__get_tls()[TLS_SLOT_THREAD_ID]); } - -// Initialize 'ts' with the difference between 'abstime' and the current time -// according to 'clock'. Returns -1 if abstime already expired, or 0 otherwise. -int __timespec_from_absolute(timespec* ts, const timespec* abstime, clockid_t clock) { - clock_gettime(clock, ts); - ts->tv_sec = abstime->tv_sec - ts->tv_sec; - ts->tv_nsec = abstime->tv_nsec - ts->tv_nsec; - if (ts->tv_nsec < 0) { - ts->tv_sec--; - ts->tv_nsec += 1000000000; - } - if ((ts->tv_nsec < 0) || (ts->tv_sec < 0)) { - return -1; - } - return 0; -} diff --git a/libc/bionic/pthread_mutex.cpp b/libc/bionic/pthread_mutex.cpp index e00ffb437..cbb6ef7a4 100644 --- a/libc/bionic/pthread_mutex.cpp +++ b/libc/bionic/pthread_mutex.cpp @@ -36,7 +36,9 @@ #include "pthread_internal.h" #include "private/bionic_atomic_inline.h" +#include "private/bionic_constants.h" #include "private/bionic_futex.h" +#include "private/bionic_time_conversions.h" #include "private/bionic_tls.h" #include "private/bionic_systrace.h" @@ -615,7 +617,7 @@ int pthread_mutex_trylock(pthread_mutex_t* mutex) { return EBUSY; } -static int __pthread_mutex_timedlock(pthread_mutex_t* mutex, const timespec* abs_timeout, clockid_t clock) { +static int __pthread_mutex_timedlock(pthread_mutex_t* mutex, const timespec* abs_ts, clockid_t clock) { timespec ts; int mvalue = mutex->value; @@ -638,7 +640,7 @@ static int __pthread_mutex_timedlock(pthread_mutex_t* mutex, const timespec* abs // Loop while needed. while (__bionic_swap(locked_contended, &mutex->value) != unlocked) { - if (__timespec_from_absolute(&ts, abs_timeout, clock) < 0) { + if (!timespec_from_absolute_timespec(ts, *abs_ts, clock)) { return ETIMEDOUT; } __futex_wait_ex(&mutex->value, shared, locked_contended, &ts); @@ -681,7 +683,7 @@ static int __pthread_mutex_timedlock(pthread_mutex_t* mutex, const timespec* abs } // The value changed before we could lock it. We need to check // the time to avoid livelocks, reload the value, then loop again. - if (__timespec_from_absolute(&ts, abs_timeout, clock) < 0) { + if (!timespec_from_absolute_timespec(ts, *abs_ts, clock)) { return ETIMEDOUT; } @@ -703,7 +705,7 @@ static int __pthread_mutex_timedlock(pthread_mutex_t* mutex, const timespec* abs } // Check time and update 'ts'. - if (__timespec_from_absolute(&ts, abs_timeout, clock) < 0) { + if (timespec_from_absolute_timespec(ts, *abs_ts, clock)) { return ETIMEDOUT; } @@ -726,9 +728,9 @@ extern "C" int pthread_mutex_lock_timeout_np(pthread_mutex_t* mutex, unsigned ms clock_gettime(CLOCK_MONOTONIC, &abs_timeout); abs_timeout.tv_sec += ms / 1000; abs_timeout.tv_nsec += (ms % 1000) * 1000000; - if (abs_timeout.tv_nsec >= 1000000000) { + if (abs_timeout.tv_nsec >= NS_PER_S) { abs_timeout.tv_sec++; - abs_timeout.tv_nsec -= 1000000000; + abs_timeout.tv_nsec -= NS_PER_S; } int error = __pthread_mutex_timedlock(mutex, &abs_timeout, CLOCK_MONOTONIC); diff --git a/libc/bionic/pthread_rwlock.cpp b/libc/bionic/pthread_rwlock.cpp index 063137b1b..0d63457d2 100644 --- a/libc/bionic/pthread_rwlock.cpp +++ b/libc/bionic/pthread_rwlock.cpp @@ -30,6 +30,7 @@ #include "pthread_internal.h" #include "private/bionic_futex.h" +#include "private/bionic_time_conversions.h" /* Technical note: * @@ -71,7 +72,7 @@ static inline bool rwlock_is_shared(const pthread_rwlock_t* rwlock) { static bool timespec_from_absolute(timespec* rel_timeout, const timespec* abs_timeout) { if (abs_timeout != NULL) { - if (__timespec_from_absolute(rel_timeout, abs_timeout, CLOCK_REALTIME) < 0) { + if (!timespec_from_absolute_timespec(*rel_timeout, *abs_timeout, CLOCK_REALTIME)) { return false; } } diff --git a/libc/bionic/semaphore.c b/libc/bionic/semaphore.c deleted file mode 100644 index 1fa019e06..000000000 --- a/libc/bionic/semaphore.c +++ /dev/null @@ -1,398 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -#include -#include -#include -#include -#include - -#include "private/bionic_atomic_inline.h" -#include "private/bionic_futex.h" - -/* In this implementation, a semaphore contains a - * 31-bit signed value and a 1-bit 'shared' flag - * (for process-sharing purpose). - * - * We use the value -1 to indicate contention on the - * semaphore, 0 or more to indicate uncontended state, - * any value lower than -2 is invalid at runtime. - * - * State diagram: - * - * post(1) ==> 2 - * post(0) ==> 1 - * post(-1) ==> 1, then wake all waiters - * - * wait(2) ==> 1 - * wait(1) ==> 0 - * wait(0) ==> -1 then wait for a wake up + loop - * wait(-1) ==> -1 then wait for a wake up + loop - * - */ - -/* Use the upper 31-bits for the counter, and the lower one - * for the shared flag. - */ -#define SEMCOUNT_SHARED_MASK 0x00000001 -#define SEMCOUNT_VALUE_MASK 0xfffffffe -#define SEMCOUNT_VALUE_SHIFT 1 - -/* Maximum unsigned value that can be stored in the semaphore. - * One bit is used for the shared flag, another one for the - * sign bit, leaving us with only 30 bits. - */ -#define SEM_MAX_VALUE 0x3fffffff - -/* convert a value into the corresponding sem->count bit pattern */ -#define SEMCOUNT_FROM_VALUE(val) (((val) << SEMCOUNT_VALUE_SHIFT) & SEMCOUNT_VALUE_MASK) - -/* convert a sem->count bit pattern into the corresponding signed value */ -#define SEMCOUNT_TO_VALUE(sval) ((int)(sval) >> SEMCOUNT_VALUE_SHIFT) - -/* the value +1 as a sem->count bit-pattern. */ -#define SEMCOUNT_ONE SEMCOUNT_FROM_VALUE(1) - -/* the value -1 as a sem->count bit-pattern. */ -#define SEMCOUNT_MINUS_ONE SEMCOUNT_FROM_VALUE(-1) - -#define SEMCOUNT_DECREMENT(sval) (((sval) - (1U << SEMCOUNT_VALUE_SHIFT)) & SEMCOUNT_VALUE_MASK) -#define SEMCOUNT_INCREMENT(sval) (((sval) + (1U << SEMCOUNT_VALUE_SHIFT)) & SEMCOUNT_VALUE_MASK) - -/* return the shared bitflag from a semaphore */ -#define SEM_GET_SHARED(sem) ((sem)->count & SEMCOUNT_SHARED_MASK) - - -int sem_init(sem_t *sem, int pshared, unsigned int value) -{ - if (sem == NULL) { - errno = EINVAL; - return -1; - } - - /* ensure that 'value' can be stored in the semaphore */ - if (value > SEM_MAX_VALUE) { - errno = EINVAL; - return -1; - } - - sem->count = SEMCOUNT_FROM_VALUE(value); - if (pshared != 0) - sem->count |= SEMCOUNT_SHARED_MASK; - - return 0; -} - - -int sem_destroy(sem_t *sem) -{ - int count; - - if (sem == NULL) { - errno = EINVAL; - return -1; - } - count = SEMCOUNT_TO_VALUE(sem->count); - if (count < 0) { - errno = EBUSY; - return -1; - } - sem->count = 0; - return 0; -} - - -sem_t *sem_open(const char *name __unused, int oflag __unused, ...) -{ - errno = ENOSYS; - return SEM_FAILED; -} - - -int sem_close(sem_t *sem) -{ - if (sem == NULL) { - errno = EINVAL; - return -1; - } - errno = ENOSYS; - return -1; -} - - -int sem_unlink(const char* name __unused) -{ - errno = ENOSYS; - return -1; -} - - -/* Decrement a semaphore's value atomically, - * and return the old one. As a special case, - * this returns immediately if the value is - * negative (i.e. -1) - */ -static int -__sem_dec(volatile unsigned int *pvalue) -{ - unsigned int shared = (*pvalue & SEMCOUNT_SHARED_MASK); - unsigned int old, new; - int ret; - - do { - old = (*pvalue & SEMCOUNT_VALUE_MASK); - ret = SEMCOUNT_TO_VALUE(old); - if (ret < 0) - break; - - new = SEMCOUNT_DECREMENT(old); - } - while (__bionic_cmpxchg((int)(old|shared), - (int)(new|shared), - (volatile int *)pvalue) != 0); - return ret; -} - -/* Same as __sem_dec, but will not touch anything if the - * value is already negative *or* 0. Returns the old value. - */ -static int -__sem_trydec(volatile unsigned int *pvalue) -{ - unsigned int shared = (*pvalue & SEMCOUNT_SHARED_MASK); - unsigned int old, new; - int ret; - - do { - old = (*pvalue & SEMCOUNT_VALUE_MASK); - ret = SEMCOUNT_TO_VALUE(old); - if (ret <= 0) - break; - - new = SEMCOUNT_DECREMENT(old); - } - while (__bionic_cmpxchg((int)(old|shared), - (int)(new|shared), - (volatile int *)pvalue) != 0); - - return ret; -} - - -/* "Increment" the value of a semaphore atomically and - * return its old value. Note that this implements - * the special case of "incrementing" any negative - * value to +1 directly. - * - * NOTE: The value will _not_ wrap above SEM_VALUE_MAX - */ -static int -__sem_inc(volatile unsigned int *pvalue) -{ - unsigned int shared = (*pvalue & SEMCOUNT_SHARED_MASK); - unsigned int old, new; - int ret; - - do { - old = (*pvalue & SEMCOUNT_VALUE_MASK); - ret = SEMCOUNT_TO_VALUE(old); - - /* Can't go higher than SEM_MAX_VALUE */ - if (ret == SEM_MAX_VALUE) - break; - - /* If the counter is negative, go directly to +1, - * otherwise just increment */ - if (ret < 0) - new = SEMCOUNT_ONE; - else - new = SEMCOUNT_INCREMENT(old); - } - while ( __bionic_cmpxchg((int)(old|shared), - (int)(new|shared), - (volatile int*)pvalue) != 0); - - return ret; -} - -/* lock a semaphore */ -int sem_wait(sem_t *sem) -{ - unsigned shared; - - if (sem == NULL) { - errno = EINVAL; - return -1; - } - - shared = SEM_GET_SHARED(sem); - - for (;;) { - if (__sem_dec(&sem->count) > 0) - break; - - __futex_wait_ex(&sem->count, shared, shared|SEMCOUNT_MINUS_ONE, NULL); - } - ANDROID_MEMBAR_FULL(); - return 0; -} - -int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout) -{ - unsigned int shared; - - if (sem == NULL) { - errno = EINVAL; - return -1; - } - - /* POSIX says we need to try to decrement the semaphore - * before checking the timeout value. Note that if the - * value is currently 0, __sem_trydec() does nothing. - */ - if (__sem_trydec(&sem->count) > 0) { - ANDROID_MEMBAR_FULL(); - return 0; - } - - /* Check it as per Posix */ - if (abs_timeout == NULL || - abs_timeout->tv_sec < 0 || - abs_timeout->tv_nsec < 0 || - abs_timeout->tv_nsec >= 1000000000) - { - errno = EINVAL; - return -1; - } - - shared = SEM_GET_SHARED(sem); - - for (;;) { - struct timespec ts; - int ret; - - /* Posix mandates CLOCK_REALTIME here */ - clock_gettime( CLOCK_REALTIME, &ts ); - ts.tv_sec = abs_timeout->tv_sec - ts.tv_sec; - ts.tv_nsec = abs_timeout->tv_nsec - ts.tv_nsec; - if (ts.tv_nsec < 0) { - ts.tv_nsec += 1000000000; - ts.tv_sec -= 1; - } - - if (ts.tv_sec < 0 || ts.tv_nsec < 0) { - errno = ETIMEDOUT; - return -1; - } - - /* Try to grab the semaphore. If the value was 0, this - * will also change it to -1 */ - if (__sem_dec(&sem->count) > 0) { - ANDROID_MEMBAR_FULL(); - break; - } - - /* Contention detected. wait for a wakeup event */ - ret = __futex_wait_ex(&sem->count, shared, shared|SEMCOUNT_MINUS_ONE, &ts); - - /* return in case of timeout or interrupt */ - if (ret == -ETIMEDOUT || ret == -EINTR) { - errno = -ret; - return -1; - } - } - return 0; -} - -/* Unlock a semaphore */ -int sem_post(sem_t *sem) -{ - unsigned int shared; - int old; - - if (sem == NULL) - return EINVAL; - - shared = SEM_GET_SHARED(sem); - - ANDROID_MEMBAR_FULL(); - old = __sem_inc(&sem->count); - if (old < 0) { - /* contention on the semaphore, wake up all waiters */ - __futex_wake_ex(&sem->count, shared, INT_MAX); - } - else if (old == SEM_MAX_VALUE) { - /* overflow detected */ - errno = EOVERFLOW; - return -1; - } - - return 0; -} - -int sem_trywait(sem_t *sem) -{ - if (sem == NULL) { - errno = EINVAL; - return -1; - } - - if (__sem_trydec(&sem->count) > 0) { - ANDROID_MEMBAR_FULL(); - return 0; - } else { - errno = EAGAIN; - return -1; - } -} - -/* Note that Posix requires that sem_getvalue() returns, in - * case of contention, the negative of the number of waiting - * threads. - * - * However, code that depends on this negative value to be - * meaningful is most probably racy. The GLibc sem_getvalue() - * only returns the semaphore value, which is 0, in case of - * contention, so we will mimick this behaviour here instead - * for better compatibility. - */ -int sem_getvalue(sem_t *sem, int *sval) -{ - int val; - - if (sem == NULL || sval == NULL) { - errno = EINVAL; - return -1; - } - - val = SEMCOUNT_TO_VALUE(sem->count); - if (val < 0) - val = 0; - - *sval = val; - return 0; -} diff --git a/libc/bionic/semaphore.cpp b/libc/bionic/semaphore.cpp new file mode 100644 index 000000000..c23eb752d --- /dev/null +++ b/libc/bionic/semaphore.cpp @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include "private/bionic_atomic_inline.h" +#include "private/bionic_constants.h" +#include "private/bionic_futex.h" +#include "private/bionic_time_conversions.h" + +// In this implementation, a semaphore contains a +// 31-bit signed value and a 1-bit 'shared' flag +// (for process-sharing purpose). +// +// We use the value -1 to indicate contention on the +// semaphore, 0 or more to indicate uncontended state, +// any value lower than -2 is invalid at runtime. +// +// State diagram: +// +// post(1) ==> 2 +// post(0) ==> 1 +// post(-1) ==> 1, then wake all waiters +// +// wait(2) ==> 1 +// wait(1) ==> 0 +// wait(0) ==> -1 then wait for a wake up + loop +// wait(-1) ==> -1 then wait for a wake up + loop + +// Use the upper 31-bits for the counter, and the lower one +// for the shared flag. +#define SEMCOUNT_SHARED_MASK 0x00000001 +#define SEMCOUNT_VALUE_MASK 0xfffffffe +#define SEMCOUNT_VALUE_SHIFT 1 + +// Convert a value into the corresponding sem->count bit pattern. +#define SEMCOUNT_FROM_VALUE(val) (((val) << SEMCOUNT_VALUE_SHIFT) & SEMCOUNT_VALUE_MASK) + +// Convert a sem->count bit pattern into the corresponding signed value. +static inline int SEMCOUNT_TO_VALUE(uint32_t sval) { + return (static_cast(sval) >> SEMCOUNT_VALUE_SHIFT); +} + +// The value +1 as a sem->count bit-pattern. +#define SEMCOUNT_ONE SEMCOUNT_FROM_VALUE(1) + +// The value -1 as a sem->count bit-pattern. +#define SEMCOUNT_MINUS_ONE SEMCOUNT_FROM_VALUE(-1) + +#define SEMCOUNT_DECREMENT(sval) (((sval) - (1U << SEMCOUNT_VALUE_SHIFT)) & SEMCOUNT_VALUE_MASK) +#define SEMCOUNT_INCREMENT(sval) (((sval) + (1U << SEMCOUNT_VALUE_SHIFT)) & SEMCOUNT_VALUE_MASK) + +// Return the shared bitflag from a semaphore. +static inline uint32_t SEM_GET_SHARED(sem_t* sem) { + return (sem->count & SEMCOUNT_SHARED_MASK); +} + + +int sem_init(sem_t* sem, int pshared, unsigned int value) { + if (sem == NULL) { + errno = EINVAL; + return -1; + } + + // Ensure that 'value' can be stored in the semaphore. + if (value > SEM_VALUE_MAX) { + errno = EINVAL; + return -1; + } + + sem->count = SEMCOUNT_FROM_VALUE(value); + if (pshared != 0) { + sem->count |= SEMCOUNT_SHARED_MASK; + } + return 0; +} + +int sem_destroy(sem_t* sem) { + if (sem == NULL) { + errno = EINVAL; + return -1; + } + return 0; +} + +sem_t* sem_open(const char*, int, ...) { + errno = ENOSYS; + return SEM_FAILED; +} + +int sem_close(sem_t*) { + errno = ENOSYS; + return -1; +} + +int sem_unlink(const char*) { + errno = ENOSYS; + return -1; +} + +// Decrement a semaphore's value atomically, +// and return the old one. As a special case, +// this returns immediately if the value is +// negative (i.e. -1) +static int __sem_dec(volatile uint32_t* sem) { + volatile int32_t* ptr = reinterpret_cast(sem); + uint32_t shared = (*sem & SEMCOUNT_SHARED_MASK); + uint32_t old_value, new_value; + int ret; + + do { + old_value = (*sem & SEMCOUNT_VALUE_MASK); + ret = SEMCOUNT_TO_VALUE(old_value); + if (ret < 0) { + break; + } + + new_value = SEMCOUNT_DECREMENT(old_value); + } while (__bionic_cmpxchg((old_value|shared), (new_value|shared), ptr) != 0); + + return ret; +} + +// Same as __sem_dec, but will not touch anything if the +// value is already negative *or* 0. Returns the old value. +static int __sem_trydec(volatile uint32_t* sem) { + volatile int32_t* ptr = reinterpret_cast(sem); + uint32_t shared = (*sem & SEMCOUNT_SHARED_MASK); + uint32_t old_value, new_value; + int ret; + + do { + old_value = (*sem & SEMCOUNT_VALUE_MASK); + ret = SEMCOUNT_TO_VALUE(old_value); + if (ret <= 0) { + break; + } + + new_value = SEMCOUNT_DECREMENT(old_value); + } while (__bionic_cmpxchg((old_value|shared), (new_value|shared), ptr) != 0); + + return ret; +} + + +// "Increment" the value of a semaphore atomically and +// return its old value. Note that this implements +// the special case of "incrementing" any negative +// value to +1 directly. +// +// NOTE: The value will _not_ wrap above SEM_VALUE_MAX +static int __sem_inc(volatile uint32_t* sem) { + volatile int32_t* ptr = reinterpret_cast(sem); + uint32_t shared = (*sem & SEMCOUNT_SHARED_MASK); + uint32_t old_value, new_value; + int ret; + + do { + old_value = (*sem & SEMCOUNT_VALUE_MASK); + ret = SEMCOUNT_TO_VALUE(old_value); + + // Can't go higher than SEM_VALUE_MAX. + if (ret == SEM_VALUE_MAX) { + break; + } + + // If the counter is negative, go directly to +1, otherwise just increment. + if (ret < 0) { + new_value = SEMCOUNT_ONE; + } else { + new_value = SEMCOUNT_INCREMENT(old_value); + } + } while (__bionic_cmpxchg((old_value|shared), (new_value|shared), ptr) != 0); + + return ret; +} + +int sem_wait(sem_t* sem) { + if (sem == NULL) { + errno = EINVAL; + return -1; + } + + uint32_t shared = SEM_GET_SHARED(sem); + + while (true) { + if (__sem_dec(&sem->count) > 0) { + ANDROID_MEMBAR_FULL(); + return 0; + } + + __futex_wait_ex(&sem->count, shared, shared|SEMCOUNT_MINUS_ONE, NULL); + } +} + +int sem_timedwait(sem_t* sem, const timespec* abs_timeout) { + if (sem == NULL) { + errno = EINVAL; + return -1; + } + + // POSIX says we need to try to decrement the semaphore + // before checking the timeout value. Note that if the + // value is currently 0, __sem_trydec() does nothing. + if (__sem_trydec(&sem->count) > 0) { + ANDROID_MEMBAR_FULL(); + return 0; + } + + // Check it as per POSIX. + if (abs_timeout == NULL || abs_timeout->tv_sec < 0 || abs_timeout->tv_nsec < 0 || abs_timeout->tv_nsec >= NS_PER_S) { + errno = EINVAL; + return -1; + } + + uint32_t shared = SEM_GET_SHARED(sem); + + while (true) { + // POSIX mandates CLOCK_REALTIME here. + timespec ts; + if (!timespec_from_absolute_timespec(ts, *abs_timeout, CLOCK_REALTIME)) { + errno = ETIMEDOUT; + return -1; + } + + // Try to grab the semaphore. If the value was 0, this will also change it to -1. + if (__sem_dec(&sem->count) > 0) { + ANDROID_MEMBAR_FULL(); + break; + } + + // Contention detected. Wait for a wakeup event. + int ret = __futex_wait_ex(&sem->count, shared, shared|SEMCOUNT_MINUS_ONE, &ts); + + // Return in case of timeout or interrupt. + if (ret == -ETIMEDOUT || ret == -EINTR) { + errno = -ret; + return -1; + } + } + return 0; +} + +int sem_post(sem_t* sem) { + if (sem == NULL) { + return EINVAL; + } + + uint32_t shared = SEM_GET_SHARED(sem); + + ANDROID_MEMBAR_FULL(); + int old_value = __sem_inc(&sem->count); + if (old_value < 0) { + // Contention on the semaphore. Wake up all waiters. + __futex_wake_ex(&sem->count, shared, INT_MAX); + } else if (old_value == SEM_VALUE_MAX) { + // Overflow detected. + errno = EOVERFLOW; + return -1; + } + + return 0; +} + +int sem_trywait(sem_t* sem) { + if (sem == NULL) { + errno = EINVAL; + return -1; + } + + if (__sem_trydec(&sem->count) > 0) { + ANDROID_MEMBAR_FULL(); + return 0; + } else { + errno = EAGAIN; + return -1; + } +} + +int sem_getvalue(sem_t* sem, int* sval) { + if (sem == NULL || sval == NULL) { + errno = EINVAL; + return -1; + } + + int val = SEMCOUNT_TO_VALUE(sem->count); + if (val < 0) { + val = 0; + } + + *sval = val; + return 0; +} diff --git a/libc/bionic/sysconf.cpp b/libc/bionic/sysconf.cpp index d8aac4fe0..24a1ae7dd 100644 --- a/libc/bionic/sysconf.cpp +++ b/libc/bionic/sysconf.cpp @@ -49,7 +49,6 @@ #define SYSTEM_MQ_OPEN_MAX 8 #define SYSTEM_MQ_PRIO_MAX 32768 #define SYSTEM_SEM_NSEMS_MAX 256 -#define SYSTEM_SEM_VALUE_MAX 0x3fffffff /* see bionic/semaphore.c */ #define SYSTEM_SIGQUEUE_MAX 32 #define SYSTEM_TIMER_MAX 32 #define SYSTEM_LOGIN_NAME_MAX 256 @@ -151,33 +150,17 @@ static int __sysconf_monotonic_clock() { } int sysconf(int name) { - switch (name) { -#ifdef _POSIX_ARG_MAX + switch (name) { case _SC_ARG_MAX: return _POSIX_ARG_MAX; -#endif -#ifdef _POSIX2_BC_BASE_MAX case _SC_BC_BASE_MAX: return _POSIX2_BC_BASE_MAX; -#endif -#ifdef _POSIX2_BC_DIM_MAX case _SC_BC_DIM_MAX: return _POSIX2_BC_DIM_MAX; -#endif -#ifdef _POSIX2_BC_SCALE_MAX case _SC_BC_SCALE_MAX: return _POSIX2_BC_SCALE_MAX; -#endif -#ifdef _POSIX2_BC_STRING_MAX case _SC_BC_STRING_MAX: return _POSIX2_BC_STRING_MAX; -#endif case _SC_CHILD_MAX: return CHILD_MAX; case _SC_CLK_TCK: return SYSTEM_CLK_TCK; -#ifdef _POSIX2_COLL_WEIGHTS_MASK - case _SC_COLL_WEIGHTS_MAX: return _POSIX2_COLL_WEIGHTS_MASK; -#endif -#ifdef _POSIX2_EXPR_NEST_MAX - case _SC_EXPR_NEST_MAX: return _POSIX2_EXPR_NEST_MAX; -#endif -#ifdef _POSIX2_LINE_MAX + case _SC_COLL_WEIGHTS_MAX: return _POSIX2_COLL_WEIGHTS_MAX; + case _SC_EXPR_NEST_MAX: return _POSIX2_EXPR_NEST_MAX; case _SC_LINE_MAX: return _POSIX2_LINE_MAX; -#endif case _SC_NGROUPS_MAX: return NGROUPS_MAX; case _SC_OPEN_MAX: return OPEN_MAX; //case _SC_PASS_MAX: return PASS_MAX; @@ -191,42 +174,20 @@ int sysconf(int name) { case _SC_2_SW_DEV: return SYSTEM_2_SW_DEV; case _SC_2_UPE: return SYSTEM_2_UPE; case _SC_2_VERSION: return SYSTEM_2_VERSION; -#ifdef _POSIX_JOB_CONTROL case _SC_JOB_CONTROL: return _POSIX_JOB_CONTROL; -#endif -#ifdef _POSIX_SAVED_IDS case _SC_SAVED_IDS: return _POSIX_SAVED_IDS; -#endif -#ifdef _POSIX_VERSION case _SC_VERSION: return _POSIX_VERSION; -#endif - //case _SC_RE_DUP_ + +#include +#include +#include +#include +#include +#include + +#include "private/bionic_constants.h" + +TEST(semaphore, sem_init) { + sem_t s; + + // Perfectly fine initial values. + ASSERT_EQ(0, sem_init(&s, 0, 0)); + ASSERT_EQ(0, sem_init(&s, 0, 1)); + ASSERT_EQ(0, sem_init(&s, 0, 123)); + + // Too small an initial value. + errno = 0; + ASSERT_EQ(-1, sem_init(&s, 0, -1)); + ASSERT_EQ(EINVAL, errno); + + ASSERT_EQ(SEM_VALUE_MAX, sysconf(_SC_SEM_VALUE_MAX)); + + // The largest initial value. + ASSERT_EQ(0, sem_init(&s, 0, SEM_VALUE_MAX)); + + // Too large an initial value. + errno = 0; + ASSERT_EQ(-1, sem_init(&s, 0, SEM_VALUE_MAX + 1)); + ASSERT_EQ(EINVAL, errno); + + ASSERT_EQ(0, sem_destroy(&s)); +} + +TEST(semaphore, sem_trywait) { + sem_t s; + ASSERT_EQ(0, sem_init(&s, 0, 3)); + ASSERT_EQ(0, sem_trywait(&s)); + ASSERT_EQ(0, sem_trywait(&s)); + ASSERT_EQ(0, sem_trywait(&s)); + errno = 0; + ASSERT_EQ(-1, sem_trywait(&s)); + ASSERT_EQ(EAGAIN, errno); + ASSERT_EQ(0, sem_destroy(&s)); +} + +static void SemWaitThreadTestFn(sem_t& sem) { + ASSERT_EQ(0, sem_wait(&sem)); +} + +static void* SemWaitThreadFn(void* arg) { + SemWaitThreadTestFn(*reinterpret_cast(arg)); + return nullptr; +} + +TEST(semaphore, sem_wait__sem_post) { + sem_t s; + ASSERT_EQ(0, sem_init(&s, 0, 0)); + + pthread_t t1, t2, t3; + ASSERT_EQ(0, pthread_create(&t1, NULL, SemWaitThreadFn, &s)); + ASSERT_EQ(0, pthread_create(&t2, NULL, SemWaitThreadFn, &s)); + ASSERT_EQ(0, pthread_create(&t3, NULL, SemWaitThreadFn, &s)); + + ASSERT_EQ(0, sem_post(&s)); + ASSERT_EQ(0, sem_post(&s)); + ASSERT_EQ(0, sem_post(&s)); + + void* result; + ASSERT_EQ(0, pthread_join(t1, &result)); + ASSERT_EQ(0, pthread_join(t2, &result)); + ASSERT_EQ(0, pthread_join(t3, &result)); +} + +static inline void timespec_add_ms(timespec& ts, size_t ms) { + ts.tv_sec += ms / 1000; + ts.tv_nsec += (ms % 1000) * 1000000; + if (ts.tv_nsec >= NS_PER_S) { + ts.tv_sec++; + ts.tv_nsec -= NS_PER_S; + } +} + +TEST(semaphore, sem_timedwait) { + sem_t s; + ASSERT_EQ(0, sem_init(&s, 0, 0)); + + timespec ts; + ASSERT_EQ(0, clock_gettime(CLOCK_REALTIME, &ts)); + timespec_add_ms(ts, 100); + + errno = 0; + ASSERT_EQ(-1, sem_timedwait(&s, &ts)); + ASSERT_EQ(ETIMEDOUT, errno); + + // A negative timeout is an error. + errno = 0; + ts.tv_nsec = -1; + ASSERT_EQ(-1, sem_timedwait(&s, &ts)); + ASSERT_EQ(EINVAL, errno); + + ASSERT_EQ(0, sem_destroy(&s)); +} + +TEST(semaphore, sem_getvalue) { + sem_t s; + ASSERT_EQ(0, sem_init(&s, 0, 0)); + + int i; + ASSERT_EQ(0, sem_getvalue(&s, &i)); + ASSERT_EQ(0, i); + + ASSERT_EQ(0, sem_post(&s)); + ASSERT_EQ(0, sem_getvalue(&s, &i)); + ASSERT_EQ(1, i); + + ASSERT_EQ(0, sem_post(&s)); + ASSERT_EQ(0, sem_getvalue(&s, &i)); + ASSERT_EQ(2, i); + + ASSERT_EQ(0, sem_wait(&s)); + ASSERT_EQ(0, sem_getvalue(&s, &i)); + ASSERT_EQ(1, i); +} diff --git a/tests/time_test.cpp b/tests/time_test.cpp index c9ead8dbb..c631b6adb 100644 --- a/tests/time_test.cpp +++ b/tests/time_test.cpp @@ -26,6 +26,8 @@ #include "ScopedSignalHandler.h" +#include "private/bionic_constants.h" + TEST(time, gmtime) { time_t t = 0; tm* broken_down = gmtime(&t); @@ -395,7 +397,7 @@ TEST(time, clock_gettime) { ts2.tv_nsec -= ts1.tv_nsec; if (ts2.tv_nsec < 0) { --ts2.tv_sec; - ts2.tv_nsec += 1000000000; + ts2.tv_nsec += NS_PER_S; } // Should be less than (a very generous, to try to avoid flakiness) 1000000ns.