am 6b73d13f
: am 2d3e7233
: Merge "Revert "Revert "Pull the pthread_key_t functions out of pthread.c."""
# Via Android Git Automerger (1) and others * commit '6b73d13fa414afeecba6718bf724e8ac922bac39': Revert "Revert "Pull the pthread_key_t functions out of pthread.c.""
This commit is contained in:
commit
5bb4f54b4d
8 changed files with 336 additions and 323 deletions
|
@ -407,6 +407,7 @@ libc_common_src_files += \
|
||||||
|
|
||||||
libc_static_common_src_files += \
|
libc_static_common_src_files += \
|
||||||
bionic/pthread.c.arm \
|
bionic/pthread.c.arm \
|
||||||
|
bionic/pthread_key.cpp.arm \
|
||||||
|
|
||||||
# these are used by the static and dynamic versions of the libc
|
# these are used by the static and dynamic versions of the libc
|
||||||
# respectively
|
# respectively
|
||||||
|
@ -447,6 +448,7 @@ libc_common_src_files += \
|
||||||
|
|
||||||
libc_static_common_src_files += \
|
libc_static_common_src_files += \
|
||||||
bionic/pthread.c \
|
bionic/pthread.c \
|
||||||
|
bionic/pthread_key.cpp \
|
||||||
|
|
||||||
libc_arch_static_src_files := \
|
libc_arch_static_src_files := \
|
||||||
bionic/dl_iterate_phdr_static.c
|
bionic/dl_iterate_phdr_static.c
|
||||||
|
@ -493,6 +495,7 @@ libc_common_src_files += \
|
||||||
|
|
||||||
libc_static_common_src_files += \
|
libc_static_common_src_files += \
|
||||||
bionic/pthread.c
|
bionic/pthread.c
|
||||||
|
bionic/pthread_key.cpp \
|
||||||
|
|
||||||
libc_arch_static_src_files := \
|
libc_arch_static_src_files := \
|
||||||
bionic/dl_iterate_phdr_static.c
|
bionic/dl_iterate_phdr_static.c
|
||||||
|
|
|
@ -101,8 +101,8 @@ static const pthread_attr_t gDefaultPthreadAttr = {
|
||||||
.sched_priority = 0
|
.sched_priority = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
static pthread_internal_t* gThreadList = NULL;
|
__LIBC_HIDDEN__ pthread_internal_t* gThreadList = NULL;
|
||||||
static pthread_mutex_t gThreadListLock = PTHREAD_MUTEX_INITIALIZER;
|
__LIBC_HIDDEN__ pthread_mutex_t gThreadListLock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
static pthread_mutex_t gDebuggerNotificationLock = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_mutex_t gDebuggerNotificationLock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
static void _pthread_internal_remove_locked(pthread_internal_t* thread) {
|
static void _pthread_internal_remove_locked(pthread_internal_t* thread) {
|
||||||
|
@ -550,9 +550,6 @@ void __pthread_cleanup_pop( __pthread_cleanup_t* c, int execute )
|
||||||
c->__cleanup_routine(c->__cleanup_arg);
|
c->__cleanup_routine(c->__cleanup_arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* used by pthread_exit() to clean all TLS keys of the current thread */
|
|
||||||
static void pthread_key_clean_all(void);
|
|
||||||
|
|
||||||
void pthread_exit(void * retval)
|
void pthread_exit(void * retval)
|
||||||
{
|
{
|
||||||
pthread_internal_t* thread = __get_thread();
|
pthread_internal_t* thread = __get_thread();
|
||||||
|
@ -1780,303 +1777,6 @@ int pthread_cond_timeout_np(pthread_cond_t *cond,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* A technical note regarding our thread-local-storage (TLS) implementation:
|
|
||||||
*
|
|
||||||
* There can be up to TLSMAP_SIZE independent TLS keys in a given process,
|
|
||||||
* though the first TLSMAP_START keys are reserved for Bionic to hold
|
|
||||||
* special thread-specific variables like errno or a pointer to
|
|
||||||
* the current thread's descriptor.
|
|
||||||
*
|
|
||||||
* while stored in the TLS area, these entries cannot be accessed through
|
|
||||||
* pthread_getspecific() / pthread_setspecific() and pthread_key_delete()
|
|
||||||
*
|
|
||||||
* also, some entries in the key table are pre-allocated (see tlsmap_lock)
|
|
||||||
* to greatly simplify and speedup some OpenGL-related operations. though the
|
|
||||||
* initialy value will be NULL on all threads.
|
|
||||||
*
|
|
||||||
* you can use pthread_getspecific()/setspecific() on these, and in theory
|
|
||||||
* you could also call pthread_key_delete() as well, though this would
|
|
||||||
* probably break some apps.
|
|
||||||
*
|
|
||||||
* The 'tlsmap_t' type defined below implements a shared global map of
|
|
||||||
* currently created/allocated TLS keys and the destructors associated
|
|
||||||
* with them. You should use tlsmap_lock/unlock to access it to avoid
|
|
||||||
* any race condition.
|
|
||||||
*
|
|
||||||
* the global TLS map simply contains a bitmap of allocated keys, and
|
|
||||||
* an array of destructors.
|
|
||||||
*
|
|
||||||
* each thread has a TLS area that is a simple array of TLSMAP_SIZE void*
|
|
||||||
* pointers. the TLS area of the main thread is stack-allocated in
|
|
||||||
* __libc_init_common, while the TLS area of other threads is placed at
|
|
||||||
* the top of their stack in pthread_create.
|
|
||||||
*
|
|
||||||
* when pthread_key_create() is called, it finds the first free key in the
|
|
||||||
* bitmap, then set it to 1, saving the destructor altogether
|
|
||||||
*
|
|
||||||
* when pthread_key_delete() is called. it will erase the key's bitmap bit
|
|
||||||
* and its destructor, and will also clear the key data in the TLS area of
|
|
||||||
* all created threads. As mandated by Posix, it is the responsability of
|
|
||||||
* the caller of pthread_key_delete() to properly reclaim the objects that
|
|
||||||
* were pointed to by these data fields (either before or after the call).
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* TLS Map implementation
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define TLSMAP_START (TLS_SLOT_MAX_WELL_KNOWN+1)
|
|
||||||
#define TLSMAP_SIZE BIONIC_TLS_SLOTS
|
|
||||||
#define TLSMAP_BITS 32
|
|
||||||
#define TLSMAP_WORDS ((TLSMAP_SIZE+TLSMAP_BITS-1)/TLSMAP_BITS)
|
|
||||||
#define TLSMAP_WORD(m,k) (m)->map[(k)/TLSMAP_BITS]
|
|
||||||
#define TLSMAP_MASK(k) (1U << ((k)&(TLSMAP_BITS-1)))
|
|
||||||
|
|
||||||
/* this macro is used to quickly check that a key belongs to a reasonable range */
|
|
||||||
#define TLSMAP_VALIDATE_KEY(key) \
|
|
||||||
((key) >= TLSMAP_START && (key) < TLSMAP_SIZE)
|
|
||||||
|
|
||||||
/* the type of tls key destructor functions */
|
|
||||||
typedef void (*tls_dtor_t)(void*);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int init; /* see comment in tlsmap_lock() */
|
|
||||||
uint32_t map[TLSMAP_WORDS]; /* bitmap of allocated keys */
|
|
||||||
tls_dtor_t dtors[TLSMAP_SIZE]; /* key destructors */
|
|
||||||
} tlsmap_t;
|
|
||||||
|
|
||||||
static pthread_mutex_t _tlsmap_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
||||||
static tlsmap_t _tlsmap;
|
|
||||||
|
|
||||||
/* lock the global TLS map lock and return a handle to it */
|
|
||||||
static __inline__ tlsmap_t* tlsmap_lock(void)
|
|
||||||
{
|
|
||||||
tlsmap_t* m = &_tlsmap;
|
|
||||||
|
|
||||||
pthread_mutex_lock(&_tlsmap_lock);
|
|
||||||
/* we need to initialize the first entry of the 'map' array
|
|
||||||
* with the value TLS_DEFAULT_ALLOC_MAP. doing it statically
|
|
||||||
* when declaring _tlsmap is a bit awkward and is going to
|
|
||||||
* produce warnings, so do it the first time we use the map
|
|
||||||
* instead
|
|
||||||
*/
|
|
||||||
if (__unlikely(!m->init)) {
|
|
||||||
TLSMAP_WORD(m,0) = TLS_DEFAULT_ALLOC_MAP;
|
|
||||||
m->init = 1;
|
|
||||||
}
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* unlock the global TLS map */
|
|
||||||
static __inline__ void tlsmap_unlock(tlsmap_t* m)
|
|
||||||
{
|
|
||||||
pthread_mutex_unlock(&_tlsmap_lock);
|
|
||||||
(void)m; /* a good compiler is a happy compiler */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* test to see wether a key is allocated */
|
|
||||||
static __inline__ int tlsmap_test(tlsmap_t* m, int key)
|
|
||||||
{
|
|
||||||
return (TLSMAP_WORD(m,key) & TLSMAP_MASK(key)) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* set the destructor and bit flag on a newly allocated key */
|
|
||||||
static __inline__ void tlsmap_set(tlsmap_t* m, int key, tls_dtor_t dtor)
|
|
||||||
{
|
|
||||||
TLSMAP_WORD(m,key) |= TLSMAP_MASK(key);
|
|
||||||
m->dtors[key] = dtor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* clear the destructor and bit flag on an existing key */
|
|
||||||
static __inline__ void tlsmap_clear(tlsmap_t* m, int key)
|
|
||||||
{
|
|
||||||
TLSMAP_WORD(m,key) &= ~TLSMAP_MASK(key);
|
|
||||||
m->dtors[key] = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* allocate a new TLS key, return -1 if no room left */
|
|
||||||
static int tlsmap_alloc(tlsmap_t* m, tls_dtor_t dtor)
|
|
||||||
{
|
|
||||||
int key;
|
|
||||||
|
|
||||||
for ( key = TLSMAP_START; key < TLSMAP_SIZE; key++ ) {
|
|
||||||
if ( !tlsmap_test(m, key) ) {
|
|
||||||
tlsmap_set(m, key, dtor);
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int pthread_key_create(pthread_key_t *key, void (*destructor_function)(void *))
|
|
||||||
{
|
|
||||||
uint32_t err = ENOMEM;
|
|
||||||
tlsmap_t* map = tlsmap_lock();
|
|
||||||
int k = tlsmap_alloc(map, destructor_function);
|
|
||||||
|
|
||||||
if (k >= 0) {
|
|
||||||
*key = k;
|
|
||||||
err = 0;
|
|
||||||
}
|
|
||||||
tlsmap_unlock(map);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* This deletes a pthread_key_t. note that the standard mandates that this does
|
|
||||||
* not call the destructor of non-NULL key values. Instead, it is the
|
|
||||||
* responsibility of the caller to properly dispose of the corresponding data
|
|
||||||
* and resources, using any means it finds suitable.
|
|
||||||
*
|
|
||||||
* On the other hand, this function will clear the corresponding key data
|
|
||||||
* values in all known threads. this prevents later (invalid) calls to
|
|
||||||
* pthread_getspecific() to receive invalid/stale values.
|
|
||||||
*/
|
|
||||||
int pthread_key_delete(pthread_key_t key)
|
|
||||||
{
|
|
||||||
uint32_t err;
|
|
||||||
pthread_internal_t* thr;
|
|
||||||
tlsmap_t* map;
|
|
||||||
|
|
||||||
if (!TLSMAP_VALIDATE_KEY(key)) {
|
|
||||||
return EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
map = tlsmap_lock();
|
|
||||||
|
|
||||||
if (!tlsmap_test(map, key)) {
|
|
||||||
err = EINVAL;
|
|
||||||
goto err1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* clear value in all threads */
|
|
||||||
pthread_mutex_lock(&gThreadListLock);
|
|
||||||
for ( thr = gThreadList; thr != NULL; thr = thr->next ) {
|
|
||||||
/* avoid zombie threads with a negative 'join_count'. these are really
|
|
||||||
* already dead and don't have a TLS area anymore.
|
|
||||||
*
|
|
||||||
* similarly, it is possible to have thr->tls == NULL for threads that
|
|
||||||
* were just recently created through pthread_create() but whose
|
|
||||||
* startup trampoline (__thread_entry) hasn't been run yet by the
|
|
||||||
* scheduler. thr->tls will also be NULL after it's stack has been
|
|
||||||
* unmapped but before the ongoing pthread_join() is finished.
|
|
||||||
* so check for this too.
|
|
||||||
*/
|
|
||||||
if (thr->join_count < 0 || !thr->tls)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
thr->tls[key] = NULL;
|
|
||||||
}
|
|
||||||
tlsmap_clear(map, key);
|
|
||||||
|
|
||||||
pthread_mutex_unlock(&gThreadListLock);
|
|
||||||
err = 0;
|
|
||||||
|
|
||||||
err1:
|
|
||||||
tlsmap_unlock(map);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int pthread_setspecific(pthread_key_t key, const void *ptr)
|
|
||||||
{
|
|
||||||
int err = EINVAL;
|
|
||||||
tlsmap_t* map;
|
|
||||||
|
|
||||||
if (TLSMAP_VALIDATE_KEY(key)) {
|
|
||||||
/* check that we're trying to set data for an allocated key */
|
|
||||||
map = tlsmap_lock();
|
|
||||||
if (tlsmap_test(map, key)) {
|
|
||||||
((uint32_t *)__get_tls())[key] = (uint32_t)ptr;
|
|
||||||
err = 0;
|
|
||||||
}
|
|
||||||
tlsmap_unlock(map);
|
|
||||||
}
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
void * pthread_getspecific(pthread_key_t key)
|
|
||||||
{
|
|
||||||
if (!TLSMAP_VALIDATE_KEY(key)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* for performance reason, we do not lock/unlock the global TLS map
|
|
||||||
* to check that the key is properly allocated. if the key was not
|
|
||||||
* allocated, the value read from the TLS should always be NULL
|
|
||||||
* due to pthread_key_delete() clearing the values for all threads.
|
|
||||||
*/
|
|
||||||
return (void *)(((unsigned *)__get_tls())[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Posix mandates that this be defined in <limits.h> but we don't have
|
|
||||||
* it just yet.
|
|
||||||
*/
|
|
||||||
#ifndef PTHREAD_DESTRUCTOR_ITERATIONS
|
|
||||||
# define PTHREAD_DESTRUCTOR_ITERATIONS 4
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* this function is called from pthread_exit() to remove all TLS key data
|
|
||||||
* from this thread's TLS area. this must call the destructor of all keys
|
|
||||||
* that have a non-NULL data value (and a non-NULL destructor).
|
|
||||||
*
|
|
||||||
* because destructors can do funky things like deleting/creating other
|
|
||||||
* keys, we need to implement this in a loop
|
|
||||||
*/
|
|
||||||
static void pthread_key_clean_all(void)
|
|
||||||
{
|
|
||||||
tlsmap_t* map;
|
|
||||||
void** tls = (void**)__get_tls();
|
|
||||||
int rounds = PTHREAD_DESTRUCTOR_ITERATIONS;
|
|
||||||
|
|
||||||
map = tlsmap_lock();
|
|
||||||
|
|
||||||
for (rounds = PTHREAD_DESTRUCTOR_ITERATIONS; rounds > 0; rounds--)
|
|
||||||
{
|
|
||||||
int kk, count = 0;
|
|
||||||
|
|
||||||
for (kk = TLSMAP_START; kk < TLSMAP_SIZE; kk++) {
|
|
||||||
if ( tlsmap_test(map, kk) )
|
|
||||||
{
|
|
||||||
void* data = tls[kk];
|
|
||||||
tls_dtor_t dtor = map->dtors[kk];
|
|
||||||
|
|
||||||
if (data != NULL && dtor != NULL)
|
|
||||||
{
|
|
||||||
/* we need to clear the key data now, this will prevent the
|
|
||||||
* destructor (or a later one) from seeing the old value if
|
|
||||||
* it calls pthread_getspecific() for some odd reason
|
|
||||||
*
|
|
||||||
* we do not do this if 'dtor == NULL' just in case another
|
|
||||||
* destructor function might be responsible for manually
|
|
||||||
* releasing the corresponding data.
|
|
||||||
*/
|
|
||||||
tls[kk] = NULL;
|
|
||||||
|
|
||||||
/* because the destructor is free to call pthread_key_create
|
|
||||||
* and/or pthread_key_delete, we need to temporarily unlock
|
|
||||||
* the TLS map
|
|
||||||
*/
|
|
||||||
tlsmap_unlock(map);
|
|
||||||
(*dtor)(data);
|
|
||||||
map = tlsmap_lock();
|
|
||||||
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* if we didn't call any destructor, there is no need to check the
|
|
||||||
* TLS data again
|
|
||||||
*/
|
|
||||||
if (count == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
tlsmap_unlock(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
// man says this should be in <linux/unistd.h>, but it isn't
|
// man says this should be in <linux/unistd.h>, but it isn't
|
||||||
extern int tgkill(int tgid, int tid, int sig);
|
extern int tgkill(int tgid, int tid, int sig);
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,11 @@ int _init_thread(pthread_internal_t* thread, pid_t kernel_id, const pthread_attr
|
||||||
void _pthread_internal_add( pthread_internal_t* thread );
|
void _pthread_internal_add( pthread_internal_t* thread );
|
||||||
pthread_internal_t* __get_thread(void);
|
pthread_internal_t* __get_thread(void);
|
||||||
|
|
||||||
|
__LIBC_HIDDEN__ void pthread_key_clean_all(void);
|
||||||
|
|
||||||
|
extern pthread_internal_t* gThreadList;
|
||||||
|
extern pthread_mutex_t gThreadListLock;
|
||||||
|
|
||||||
/* needed by posix-timers.c */
|
/* needed by posix-timers.c */
|
||||||
|
|
||||||
static __inline__ void timespec_add( struct timespec* a, const struct timespec* b )
|
static __inline__ void timespec_add( struct timespec* a, const struct timespec* b )
|
||||||
|
|
280
libc/bionic/pthread_key.cpp
Normal file
280
libc/bionic/pthread_key.cpp
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
/*
|
||||||
|
* 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 <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <memory.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/atomics.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "bionic_atomic_inline.h"
|
||||||
|
#include "bionic_futex.h"
|
||||||
|
#include "bionic_pthread.h"
|
||||||
|
#include "bionic_ssp.h"
|
||||||
|
#include "bionic_tls.h"
|
||||||
|
#include "debug_format.h"
|
||||||
|
#include "pthread_internal.h"
|
||||||
|
#include "thread_private.h"
|
||||||
|
|
||||||
|
/* A technical note regarding our thread-local-storage (TLS) implementation:
|
||||||
|
*
|
||||||
|
* There can be up to BIONIC_TLS_SLOTS independent TLS keys in a given process,
|
||||||
|
* The keys below TLS_SLOT_FIRST_USER_SLOT are reserved for Bionic to hold
|
||||||
|
* special thread-specific variables like errno or a pointer to
|
||||||
|
* the current thread's descriptor. These entries cannot be accessed through
|
||||||
|
* pthread_getspecific() / pthread_setspecific() or pthread_key_delete()
|
||||||
|
*
|
||||||
|
* The 'tls_map_t' type defined below implements a shared global map of
|
||||||
|
* currently created/allocated TLS keys and the destructors associated
|
||||||
|
* with them.
|
||||||
|
*
|
||||||
|
* The global TLS map simply contains a bitmap of allocated keys, and
|
||||||
|
* an array of destructors.
|
||||||
|
*
|
||||||
|
* Each thread has a TLS area that is a simple array of BIONIC_TLS_SLOTS void*
|
||||||
|
* pointers. the TLS area of the main thread is stack-allocated in
|
||||||
|
* __libc_init_common, while the TLS area of other threads is placed at
|
||||||
|
* the top of their stack in pthread_create.
|
||||||
|
*
|
||||||
|
* When pthread_key_delete() is called it will erase the key's bitmap bit
|
||||||
|
* and its destructor, and will also clear the key data in the TLS area of
|
||||||
|
* all created threads. As mandated by Posix, it is the responsibility of
|
||||||
|
* the caller of pthread_key_delete() to properly reclaim the objects that
|
||||||
|
* were pointed to by these data fields (either before or after the call).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define TLSMAP_BITS 32
|
||||||
|
#define TLSMAP_WORDS ((BIONIC_TLS_SLOTS+TLSMAP_BITS-1)/TLSMAP_BITS)
|
||||||
|
#define TLSMAP_WORD(m,k) (m).map[(k)/TLSMAP_BITS]
|
||||||
|
#define TLSMAP_MASK(k) (1U << ((k)&(TLSMAP_BITS-1)))
|
||||||
|
|
||||||
|
static inline bool IsValidUserKey(pthread_key_t key) {
|
||||||
|
return (key >= TLS_SLOT_FIRST_USER_SLOT && key < BIONIC_TLS_SLOTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef void (*key_destructor_t)(void*);
|
||||||
|
|
||||||
|
struct tls_map_t {
|
||||||
|
bool is_initialized;
|
||||||
|
|
||||||
|
/* bitmap of allocated keys */
|
||||||
|
uint32_t map[TLSMAP_WORDS];
|
||||||
|
|
||||||
|
key_destructor_t key_destructors[BIONIC_TLS_SLOTS];
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScopedTlsMapAccess {
|
||||||
|
public:
|
||||||
|
ScopedTlsMapAccess() {
|
||||||
|
Lock();
|
||||||
|
|
||||||
|
// If this is the first time the TLS map has been accessed,
|
||||||
|
// mark the slots belonging to well-known keys as being in use.
|
||||||
|
// This isn't currently necessary because the well-known keys
|
||||||
|
// can only be accessed directly by bionic itself, do not have
|
||||||
|
// destructors, and all the functions that touch the TLS map
|
||||||
|
// start after the maximum well-known slot.
|
||||||
|
if (!s_tls_map_.is_initialized) {
|
||||||
|
for (pthread_key_t key = 0; key < TLS_SLOT_FIRST_USER_SLOT; ++key) {
|
||||||
|
SetInUse(key, NULL);
|
||||||
|
}
|
||||||
|
s_tls_map_.is_initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~ScopedTlsMapAccess() {
|
||||||
|
Unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
int CreateKey(pthread_key_t* result, void (*key_destructor)(void*)) {
|
||||||
|
// Take the first unallocated key.
|
||||||
|
for (int key = 0; key < BIONIC_TLS_SLOTS; ++key) {
|
||||||
|
if (!IsInUse(key)) {
|
||||||
|
SetInUse(key, key_destructor);
|
||||||
|
*result = key;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We hit PTHREAD_KEYS_MAX. POSIX says EAGAIN for this case.
|
||||||
|
return EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteKey(pthread_key_t key) {
|
||||||
|
TLSMAP_WORD(s_tls_map_, key) &= ~TLSMAP_MASK(key);
|
||||||
|
s_tls_map_.key_destructors[key] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsInUse(pthread_key_t key) {
|
||||||
|
return (TLSMAP_WORD(s_tls_map_, key) & TLSMAP_MASK(key)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetInUse(pthread_key_t key, void (*key_destructor)(void*)) {
|
||||||
|
TLSMAP_WORD(s_tls_map_, key) |= TLSMAP_MASK(key);
|
||||||
|
s_tls_map_.key_destructors[key] = key_destructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from pthread_exit() to remove all TLS key data
|
||||||
|
// from this thread's TLS area. This must call the destructor of all keys
|
||||||
|
// that have a non-NULL data value and a non-NULL destructor.
|
||||||
|
void CleanAll() {
|
||||||
|
void** tls = (void**)__get_tls();
|
||||||
|
|
||||||
|
// Because destructors can do funky things like deleting/creating other
|
||||||
|
// keys, we need to implement this in a loop.
|
||||||
|
for (int rounds = PTHREAD_DESTRUCTOR_ITERATIONS; rounds > 0; --rounds) {
|
||||||
|
size_t called_destructor_count = 0;
|
||||||
|
for (int key = 0; key < BIONIC_TLS_SLOTS; ++key) {
|
||||||
|
if (IsInUse(key)) {
|
||||||
|
void* data = tls[key];
|
||||||
|
void (*key_destructor)(void*) = s_tls_map_.key_destructors[key];
|
||||||
|
|
||||||
|
if (data != NULL && key_destructor != NULL) {
|
||||||
|
// we need to clear the key data now, this will prevent the
|
||||||
|
// destructor (or a later one) from seeing the old value if
|
||||||
|
// it calls pthread_getspecific() for some odd reason
|
||||||
|
|
||||||
|
// we do not do this if 'key_destructor == NULL' just in case another
|
||||||
|
// destructor function might be responsible for manually
|
||||||
|
// releasing the corresponding data.
|
||||||
|
tls[key] = NULL;
|
||||||
|
|
||||||
|
// because the destructor is free to call pthread_key_create
|
||||||
|
// and/or pthread_key_delete, we need to temporarily unlock
|
||||||
|
// the TLS map
|
||||||
|
Unlock();
|
||||||
|
(*key_destructor)(data);
|
||||||
|
Lock();
|
||||||
|
++called_destructor_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't call any destructors, there is no need to check the TLS data again.
|
||||||
|
if (called_destructor_count == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static tls_map_t s_tls_map_;
|
||||||
|
static pthread_mutex_t s_tls_map_lock_;
|
||||||
|
|
||||||
|
void Lock() {
|
||||||
|
pthread_mutex_lock(&s_tls_map_lock_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unlock() {
|
||||||
|
pthread_mutex_unlock(&s_tls_map_lock_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tls_map_t ScopedTlsMapAccess::s_tls_map_;
|
||||||
|
pthread_mutex_t ScopedTlsMapAccess::s_tls_map_lock_;
|
||||||
|
|
||||||
|
__LIBC_HIDDEN__ void pthread_key_clean_all() {
|
||||||
|
ScopedTlsMapAccess tls_map;
|
||||||
|
tls_map.CleanAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
int pthread_key_create(pthread_key_t* key, void (*key_destructor)(void*)) {
|
||||||
|
ScopedTlsMapAccess tls_map;
|
||||||
|
return tls_map.CreateKey(key, key_destructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deletes a pthread_key_t. note that the standard mandates that this does
|
||||||
|
// not call the destructors for non-NULL key values. Instead, it is the
|
||||||
|
// responsibility of the caller to properly dispose of the corresponding data
|
||||||
|
// and resources, using any means it finds suitable.
|
||||||
|
int pthread_key_delete(pthread_key_t key) {
|
||||||
|
ScopedTlsMapAccess tls_map;
|
||||||
|
|
||||||
|
if (!IsValidUserKey(key) || !tls_map.IsInUse(key)) {
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear value in all threads.
|
||||||
|
pthread_mutex_lock(&gThreadListLock);
|
||||||
|
for (pthread_internal_t* t = gThreadList; t != NULL; t = t->next) {
|
||||||
|
// Avoid zombie threads with a negative 'join_count'. These are really
|
||||||
|
// already dead and don't have a TLS area anymore.
|
||||||
|
|
||||||
|
// Similarly, it is possible to have t->tls == NULL for threads that
|
||||||
|
// were just recently created through pthread_create() but whose
|
||||||
|
// startup trampoline (__thread_entry) hasn't been run yet by the
|
||||||
|
// scheduler. t->tls will also be NULL after it's stack has been
|
||||||
|
// unmapped but before the ongoing pthread_join() is finished.
|
||||||
|
// so check for this too.
|
||||||
|
if (t->join_count < 0 || !t->tls) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
t->tls[key] = NULL;
|
||||||
|
}
|
||||||
|
tls_map.DeleteKey(key);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&gThreadListLock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* pthread_getspecific(pthread_key_t key) {
|
||||||
|
if (!IsValidUserKey(key)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For performance reasons, we do not lock/unlock the global TLS map
|
||||||
|
// to check that the key is properly allocated. If the key was not
|
||||||
|
// allocated, the value read from the TLS should always be NULL
|
||||||
|
// due to pthread_key_delete() clearing the values for all threads.
|
||||||
|
return (void *)(((unsigned *)__get_tls())[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int pthread_setspecific(pthread_key_t key, const void* ptr) {
|
||||||
|
ScopedTlsMapAccess tls_map;
|
||||||
|
|
||||||
|
if (!IsValidUserKey(key) || !tls_map.IsInUse(key)) {
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
((uint32_t *)__get_tls())[key] = (uint32_t)ptr;
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -55,7 +55,6 @@
|
||||||
|
|
||||||
/* the following depends on our implementation */
|
/* the following depends on our implementation */
|
||||||
#define SYSTEM_ATEXIT_MAX 65536 /* our implementation is unlimited */
|
#define SYSTEM_ATEXIT_MAX 65536 /* our implementation is unlimited */
|
||||||
#define SYSTEM_THREAD_KEYS_MAX BIONIC_TLS_SLOTS
|
|
||||||
#define SYSTEM_THREAD_STACK_MIN 32768 /* lower values may be possible, but be conservative */
|
#define SYSTEM_THREAD_STACK_MIN 32768 /* lower values may be possible, but be conservative */
|
||||||
#define SYSTEM_THREAD_THREADS_MAX 2048 /* really unlimited */
|
#define SYSTEM_THREAD_THREADS_MAX 2048 /* really unlimited */
|
||||||
|
|
||||||
|
@ -302,10 +301,13 @@ int sysconf(int name) {
|
||||||
// GETPW_R_SIZE_MAX ?
|
// GETPW_R_SIZE_MAX ?
|
||||||
|
|
||||||
case _SC_LOGIN_NAME_MAX: return SYSTEM_LOGIN_NAME_MAX;
|
case _SC_LOGIN_NAME_MAX: return SYSTEM_LOGIN_NAME_MAX;
|
||||||
#ifdef _POSIX_THREAD_DESTRUCTOR_ITERATIONS
|
|
||||||
case _SC_THREAD_DESTRUCTOR_ITERATIONS: return _POSIX_THREAD_DESTRUCTOR_ITERATIONS;
|
case _SC_THREAD_DESTRUCTOR_ITERATIONS:
|
||||||
#endif
|
return _POSIX_THREAD_DESTRUCTOR_ITERATIONS;
|
||||||
case _SC_THREAD_KEYS_MAX: return SYSTEM_THREAD_KEYS_MAX;
|
|
||||||
|
case _SC_THREAD_KEYS_MAX:
|
||||||
|
return (BIONIC_TLS_SLOTS - TLS_SLOT_FIRST_USER_SLOT);
|
||||||
|
|
||||||
case _SC_THREAD_STACK_MIN: return SYSTEM_THREAD_STACK_MIN;
|
case _SC_THREAD_STACK_MIN: return SYSTEM_THREAD_STACK_MIN;
|
||||||
case _SC_THREAD_THREADS_MAX: return SYSTEM_THREAD_THREADS_MAX;
|
case _SC_THREAD_THREADS_MAX: return SYSTEM_THREAD_THREADS_MAX;
|
||||||
case _SC_TTY_NAME_MAX: return SYSTEM_TTY_NAME_MAX;
|
case _SC_TTY_NAME_MAX: return SYSTEM_TTY_NAME_MAX;
|
||||||
|
|
|
@ -175,6 +175,12 @@
|
||||||
#define _POSIX_SAVED_IDS 1 /* saved user ids is a Linux feature */
|
#define _POSIX_SAVED_IDS 1 /* saved user ids is a Linux feature */
|
||||||
#define _POSIX_JOB_CONTROL 1 /* job control is a Linux feature */
|
#define _POSIX_JOB_CONTROL 1 /* job control is a Linux feature */
|
||||||
|
|
||||||
|
#define _POSIX_THREAD_DESTRUCTOR_ITERATIONS 4 /* the minimum mandated by POSIX */
|
||||||
|
#define PTHREAD_DESTRUCTOR_ITERATIONS 4
|
||||||
|
#define _POSIX_THREAD_KEYS_MAX 128 /* the minimum mandated by POSIX */
|
||||||
|
/* TODO: our PTHREAD_KEYS_MAX is currently too low to be posix compliant! */
|
||||||
|
#define _POSIX_THREAD_THREADS_MAX 64 /* the minimum mandated by POSIX */
|
||||||
|
#define PTHREAD_THREADS_MAX /* bionic has no specific limit */
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -47,27 +47,25 @@ __BEGIN_DECLS
|
||||||
#define BIONIC_TLS_SLOTS 64
|
#define BIONIC_TLS_SLOTS 64
|
||||||
|
|
||||||
/* Well-known TLS slots. What data goes in which slot is arbitrary unless otherwise noted. */
|
/* Well-known TLS slots. What data goes in which slot is arbitrary unless otherwise noted. */
|
||||||
#define TLS_SLOT_SELF 0 /* The kernel requires this specific slot for x86. */
|
enum {
|
||||||
#define TLS_SLOT_THREAD_ID 1
|
TLS_SLOT_SELF = 0, /* The kernel requires this specific slot for x86. */
|
||||||
#define TLS_SLOT_ERRNO 2
|
TLS_SLOT_THREAD_ID,
|
||||||
|
TLS_SLOT_ERRNO,
|
||||||
|
TLS_SLOT_OPENGL_API = 3,
|
||||||
|
TLS_SLOT_OPENGL = 4,
|
||||||
|
TLS_SLOT_STACK_GUARD = 5, /* GCC requires this specific slot for x86. */
|
||||||
|
TLS_SLOT_DLERROR,
|
||||||
|
|
||||||
#define TLS_SLOT_OPENGL_API 3
|
TLS_SLOT_FIRST_USER_SLOT /* Must come last! */
|
||||||
#define TLS_SLOT_OPENGL 4
|
};
|
||||||
|
|
||||||
#define TLS_SLOT_STACK_GUARD 5 /* GCC requires this specific slot for x86. */
|
|
||||||
#define TLS_SLOT_DLERROR 6
|
|
||||||
|
|
||||||
#define TLS_SLOT_MAX_WELL_KNOWN TLS_SLOT_DLERROR
|
|
||||||
|
|
||||||
/* This slot is only used to pass information from the dynamic linker to
|
/* This slot is only used to pass information from the dynamic linker to
|
||||||
* libc.so when the C library is loaded in to memory. The C runtime init
|
* libc.so when the C library is loaded in to memory. The C runtime init
|
||||||
* function will then clear it. Since its use is extremely temporary,
|
* function will then clear it. Since its use is extremely temporary,
|
||||||
* we reuse an existing location.
|
* we reuse an existing location that isn't needed during libc startup.
|
||||||
*/
|
*/
|
||||||
#define TLS_SLOT_BIONIC_PREINIT TLS_SLOT_OPENGL_API
|
#define TLS_SLOT_BIONIC_PREINIT TLS_SLOT_OPENGL_API
|
||||||
|
|
||||||
#define TLS_DEFAULT_ALLOC_MAP 0x0000001F
|
|
||||||
|
|
||||||
/* set the Thread Local Storage, must contain at least BIONIC_TLS_SLOTS pointers */
|
/* set the Thread Local Storage, must contain at least BIONIC_TLS_SLOTS pointers */
|
||||||
extern void __init_tls(void** tls, void* thread_info);
|
extern void __init_tls(void** tls, void* thread_info);
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,25 @@ TEST(pthread, pthread_key_create) {
|
||||||
ASSERT_EQ(EINVAL, pthread_key_delete(key));
|
ASSERT_EQ(EINVAL, pthread_key_delete(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(pthread, pthread_key_create_lots) {
|
||||||
|
// We can allocate _SC_THREAD_KEYS_MAX keys.
|
||||||
|
std::vector<pthread_key_t> keys;
|
||||||
|
for (int i = 0; i < sysconf(_SC_THREAD_KEYS_MAX); ++i) {
|
||||||
|
pthread_key_t key;
|
||||||
|
ASSERT_EQ(0, pthread_key_create(&key, NULL));
|
||||||
|
keys.push_back(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...and that really is the maximum.
|
||||||
|
pthread_key_t key;
|
||||||
|
ASSERT_EQ(EAGAIN, pthread_key_create(&key, NULL));
|
||||||
|
|
||||||
|
// (Don't leak all those keys!)
|
||||||
|
for (size_t i = 0; i < keys.size(); ++i) {
|
||||||
|
ASSERT_EQ(0, pthread_key_delete(keys[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void* IdFn(void* arg) {
|
static void* IdFn(void* arg) {
|
||||||
return arg;
|
return arg;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue