From fe3a83a9343f0e4ff654f09ef8ffc8a773c7c105 Mon Sep 17 00:00:00 2001 From: Yabin Cui Date: Tue, 17 Nov 2015 16:03:18 -0800 Subject: [PATCH] Implement pthread spin. In order to run tsan unit tests, we need to support pthread spin APIs. Bug: 18623621 Bug: 25392375 Change-Id: Icbb4a74e72e467824b3715982a01600031868e29 --- libc/Android.mk | 1 + libc/bionic/pthread_spinlock.cpp | 81 ++++++++++++++++++++++++++++++++ libc/include/pthread.h | 14 ++++++ libc/libc.arm.map | 5 ++ libc/libc.arm64.map | 5 ++ libc/libc.map.txt | 5 ++ libc/libc.mips.map | 5 ++ libc/libc.mips64.map | 5 ++ libc/libc.x86.map | 5 ++ libc/libc.x86_64.map | 5 ++ libc/private/bionic_lock.h | 7 +++ tests/pthread_test.cpp | 11 +++++ 12 files changed, 149 insertions(+) create mode 100644 libc/bionic/pthread_spinlock.cpp diff --git a/libc/Android.mk b/libc/Android.mk index 2849591e4..99a841ac4 100644 --- a/libc/Android.mk +++ b/libc/Android.mk @@ -584,6 +584,7 @@ libc_pthread_src_files := \ bionic/pthread_setname_np.cpp \ bionic/pthread_setschedparam.cpp \ bionic/pthread_sigmask.cpp \ + bionic/pthread_spinlock.cpp \ libc_thread_atexit_impl_src_files := \ bionic/__cxa_thread_atexit_impl.cpp \ diff --git a/libc/bionic/pthread_spinlock.cpp b/libc/bionic/pthread_spinlock.cpp new file mode 100644 index 000000000..f26f28303 --- /dev/null +++ b/libc/bionic/pthread_spinlock.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015 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 "private/bionic_lock.h" + +// User-level spinlocks can be hazardous to battery life on Android. +// We implement a simple compromise that behaves mostly like a spinlock, +// but prevents excessively long spinning. + +struct pthread_spinlock_internal_t { + Lock lock; +}; + +static_assert(sizeof(pthread_spinlock_t) == sizeof(pthread_spinlock_internal_t), + "pthread_spinlock_t should actually be pthread_spinlock_internal_t."); + +static_assert(alignof(pthread_spinlock_t) >= 4, + "pthread_spinlock_t should fulfill the alignment of pthread_spinlock_internal_t."); + +static inline pthread_spinlock_internal_t* __get_internal_spinlock(pthread_spinlock_t* lock) { + return reinterpret_cast(lock); +} + +int pthread_spin_init(pthread_spinlock_t* lock_interface, int pshared) { + pthread_spinlock_internal_t* lock = __get_internal_spinlock(lock_interface); + lock->lock.init(pshared); + return 0; +} + +int pthread_spin_destroy(pthread_spinlock_t* lock_interface) { + pthread_spinlock_internal_t* lock = __get_internal_spinlock(lock_interface); + return lock->lock.trylock() ? 0 : EBUSY; +} + +int pthread_spin_trylock(pthread_spinlock_t* lock_interface) { + pthread_spinlock_internal_t* lock = __get_internal_spinlock(lock_interface); + return lock->lock.trylock() ? 0 : EBUSY; +} + +int pthread_spin_lock(pthread_spinlock_t* lock_interface) { + pthread_spinlock_internal_t* lock = __get_internal_spinlock(lock_interface); + for (int i = 0; i < 10000; ++i) { + if (lock->lock.trylock()) { + return 0; + } + } + lock->lock.lock(); + return 0; +} + +int pthread_spin_unlock(pthread_spinlock_t* lock_interface) { + pthread_spinlock_internal_t* lock = __get_internal_spinlock(lock_interface); + lock->lock.unlock(); + return 0; +} diff --git a/libc/include/pthread.h b/libc/include/pthread.h index 48b2bca2d..21d34fb28 100644 --- a/libc/include/pthread.h +++ b/libc/include/pthread.h @@ -108,6 +108,14 @@ typedef int pthread_barrierattr_t; #define PTHREAD_BARRIER_SERIAL_THREAD -1 +typedef struct { +#if defined(__LP64__) + int64_t __private; +#else + int32_t __private[2]; +#endif +} pthread_spinlock_t; + #if defined(__LP64__) #define PTHREAD_STACK_MIN (4 * PAGE_SIZE) #else @@ -229,6 +237,12 @@ int pthread_barrier_init(pthread_barrier_t*, const pthread_barrierattr_t*, unsig int pthread_barrier_destroy(pthread_barrier_t*) __nonnull((1)); int pthread_barrier_wait(pthread_barrier_t*) __nonnull((1)); +int pthread_spin_destroy(pthread_spinlock_t*) __nonnull((1)); +int pthread_spin_init(pthread_spinlock_t*, int) __nonnull((1)); +int pthread_spin_lock(pthread_spinlock_t*) __nonnull((1)); +int pthread_spin_trylock(pthread_spinlock_t*) __nonnull((1)); +int pthread_spin_unlock(pthread_spinlock_t*) __nonnull((1)); + pthread_t pthread_self(void) __pure2; int pthread_setname_np(pthread_t, const char*) __nonnull((2)); diff --git a/libc/libc.arm.map b/libc/libc.arm.map index f7805fed6..69aa272eb 100644 --- a/libc/libc.arm.map +++ b/libc/libc.arm.map @@ -1323,6 +1323,11 @@ LIBC_N { pthread_barrier_destroy; pthread_barrier_init; pthread_barrier_wait; + pthread_spin_destroy; + pthread_spin_init; + pthread_spin_lock; + pthread_spin_trylock; + pthread_spin_unlock; pwritev; pwritev64; scandirat; diff --git a/libc/libc.arm64.map b/libc/libc.arm64.map index d5fe2625b..763f77ef1 100644 --- a/libc/libc.arm64.map +++ b/libc/libc.arm64.map @@ -1168,6 +1168,11 @@ LIBC_N { pthread_barrier_destroy; pthread_barrier_init; pthread_barrier_wait; + pthread_spin_destroy; + pthread_spin_init; + pthread_spin_lock; + pthread_spin_trylock; + pthread_spin_unlock; pwritev; pwritev64; scandirat; diff --git a/libc/libc.map.txt b/libc/libc.map.txt index 738836cd7..76cb16849 100644 --- a/libc/libc.map.txt +++ b/libc/libc.map.txt @@ -1350,6 +1350,11 @@ LIBC_N { pthread_barrier_destroy; pthread_barrier_init; pthread_barrier_wait; + pthread_spin_destroy; + pthread_spin_init; + pthread_spin_lock; + pthread_spin_trylock; + pthread_spin_unlock; pwritev; pwritev64; scandirat; diff --git a/libc/libc.mips.map b/libc/libc.mips.map index 2121ba710..e268bad41 100644 --- a/libc/libc.mips.map +++ b/libc/libc.mips.map @@ -1286,6 +1286,11 @@ LIBC_N { pthread_barrier_destroy; pthread_barrier_init; pthread_barrier_wait; + pthread_spin_destroy; + pthread_spin_init; + pthread_spin_lock; + pthread_spin_trylock; + pthread_spin_unlock; pwritev; pwritev64; scandirat; diff --git a/libc/libc.mips64.map b/libc/libc.mips64.map index d5fe2625b..763f77ef1 100644 --- a/libc/libc.mips64.map +++ b/libc/libc.mips64.map @@ -1168,6 +1168,11 @@ LIBC_N { pthread_barrier_destroy; pthread_barrier_init; pthread_barrier_wait; + pthread_spin_destroy; + pthread_spin_init; + pthread_spin_lock; + pthread_spin_trylock; + pthread_spin_unlock; pwritev; pwritev64; scandirat; diff --git a/libc/libc.x86.map b/libc/libc.x86.map index fdee2c647..6578370ee 100644 --- a/libc/libc.x86.map +++ b/libc/libc.x86.map @@ -1284,6 +1284,11 @@ LIBC_N { pthread_barrier_destroy; pthread_barrier_init; pthread_barrier_wait; + pthread_spin_destroy; + pthread_spin_init; + pthread_spin_lock; + pthread_spin_trylock; + pthread_spin_unlock; pwritev; pwritev64; scandirat; diff --git a/libc/libc.x86_64.map b/libc/libc.x86_64.map index d5fe2625b..763f77ef1 100644 --- a/libc/libc.x86_64.map +++ b/libc/libc.x86_64.map @@ -1168,6 +1168,11 @@ LIBC_N { pthread_barrier_destroy; pthread_barrier_init; pthread_barrier_wait; + pthread_spin_destroy; + pthread_spin_init; + pthread_spin_lock; + pthread_spin_trylock; + pthread_spin_unlock; pwritev; pwritev64; scandirat; diff --git a/libc/private/bionic_lock.h b/libc/private/bionic_lock.h index a36d1edd3..238ace471 100644 --- a/libc/private/bionic_lock.h +++ b/libc/private/bionic_lock.h @@ -30,6 +30,7 @@ #include #include "private/bionic_futex.h" +#include "private/bionic_macros.h" // Lock is used in places like pthread_rwlock_t, which can be initialized without calling // an initialization function. So make sure Lock can be initialized by setting its memory to 0. @@ -49,6 +50,12 @@ class Lock { this->process_shared = process_shared; } + bool trylock() { + LockState old_state = Unlocked; + return __predict_true(atomic_compare_exchange_strong_explicit(&state, &old_state, + LockedWithoutWaiter, memory_order_acquire, memory_order_relaxed)); + } + void lock() { LockState old_state = Unlocked; if (__predict_true(atomic_compare_exchange_strong_explicit(&state, &old_state, diff --git a/tests/pthread_test.cpp b/tests/pthread_test.cpp index e62518ac8..e237d2647 100755 --- a/tests/pthread_test.cpp +++ b/tests/pthread_test.cpp @@ -1779,3 +1779,14 @@ TEST(pthread, pthread_barrier_check_ordering) { ASSERT_EQ(0, pthread_join(threads[i], nullptr)); } } + +TEST(pthread, pthread_spinlock_smoke) { + pthread_spinlock_t lock; + ASSERT_EQ(0, pthread_spin_init(&lock, 0)); + ASSERT_EQ(0, pthread_spin_trylock(&lock)); + ASSERT_EQ(0, pthread_spin_unlock(&lock)); + ASSERT_EQ(0, pthread_spin_lock(&lock)); + ASSERT_EQ(EBUSY, pthread_spin_trylock(&lock)); + ASSERT_EQ(0, pthread_spin_unlock(&lock)); + ASSERT_EQ(0, pthread_spin_destroy(&lock)); +}