From 6e90ad6d73bcadd53af8dfd30d25606fe68bf853 Mon Sep 17 00:00:00 2001 From: Martin Landsmann Date: Wed, 21 May 2014 17:12:46 +0200 Subject: [PATCH] sys/posix/pthread: added dynamic pthread thread local storage --- sys/posix/pthread/include/pthread.h | 1 + sys/posix/pthread/include/pthread_tls.h | 91 +++++++++++ sys/posix/pthread/pthread.c | 18 ++- sys/posix/pthread/pthread_tls.c | 198 ++++++++++++++++++++++++ tests/pthread_tls/Makefile | 12 ++ tests/pthread_tls/main.c | 138 +++++++++++++++++ 6 files changed, 457 insertions(+), 1 deletion(-) create mode 100644 sys/posix/pthread/include/pthread_tls.h create mode 100644 sys/posix/pthread/pthread_tls.c create mode 100644 tests/pthread_tls/Makefile create mode 100644 tests/pthread_tls/main.c diff --git a/sys/posix/pthread/include/pthread.h b/sys/posix/pthread/include/pthread.h index 081997e4e9..262b539289 100644 --- a/sys/posix/pthread/include/pthread.h +++ b/sys/posix/pthread/include/pthread.h @@ -39,6 +39,7 @@ #include "pthread_scheduling.h" #include "pthread_cancellation.h" #include "pthread_cond.h" +#include "pthread_tls.h" #ifdef __cplusplus extern "C" { diff --git a/sys/posix/pthread/include/pthread_tls.h b/sys/posix/pthread/include/pthread_tls.h new file mode 100644 index 0000000000..23a221f625 --- /dev/null +++ b/sys/posix/pthread/include/pthread_tls.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2014 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup pthread + * @{ + * @file + * @brief RIOT POSIX thread local storage + * @author Martin Landsmann + * @author René Kijewski + */ + +#ifndef __SYS__POSIX__PTHREAD_TLS__H +#define __SYS__POSIX__PTHREAD_TLS__H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Internal representation of a thread-specific key. + * @internal + */ +struct __pthread_tls_key; + +/** + * @brief A single thread-specific datum. + * @internal + */ +struct __pthread_tls_datum; + +/** + * @brief A thread-specific key. + */ +typedef struct __pthread_tls_key *pthread_key_t; + +/** + * @brief Returns the requested tls + * @param[in] key the identifier for the requested tls + * @return returns pointer to the storage on success, a 0 value otherwise + */ +void *pthread_getspecific(pthread_key_t key); + +/** + * @brief Set and binds a specific tls to a key + * @param[in] key the identifier for the tls + * @param[in] value pointer to the location of the tls + * @return returns 0 on success, an errorcode otherwise + */ +int pthread_setspecific(pthread_key_t key, const void *value); + +/** + * @brief Creates a new key to be used to identify a specific tls + * @param[out] key the created key is scribed to the given pointer + * @param[in] destructor function pointer called when non NULL just befor the pthread exits + * @return returns 0 on success, an errorcode otherwise + */ +int pthread_key_create(pthread_key_t *key, void (*destructor)(void *)); + +/** + * @brief Deletes a pthread_key_t that was previously created with pthread_key_create. + * @details does not call the destructor of the key + * @param[in] key the identifier of the key to be deleted + * @return returns 0 on success, an errorcode otherwise + */ +int pthread_key_delete(pthread_key_t key); + +/** + * @brief Destroys all thread-specific keys for pthread `self_id`. + * @param[in] self_id the result of pthread_self(). + * @internal + */ +void __pthread_keys_exit(int self_id); + +/** + * @brief Returns the pointer to the head of the list of thread-specific data. + * @internal + */ +struct __pthread_tls_datum **__pthread_get_tls_head(int self_id) PURE; + +#ifdef __cplusplus +} +#endif + +#endif /* __SYS__POSIX__PTHREAD_TLS__H */ +/** @} */ diff --git a/sys/posix/pthread/pthread.c b/sys/posix/pthread/pthread.c index d657f8ba0d..761bc75aaf 100644 --- a/sys/posix/pthread/pthread.c +++ b/sys/posix/pthread/pthread.c @@ -66,6 +66,8 @@ typedef struct pthread_thread { char *stack; + struct __pthread_tls_datum *tls_head; + __pthread_cleanup_datum_t *cleanup_top; } pthread_thread_t; @@ -87,6 +89,7 @@ static int insert(pthread_thread_t *pt) { int result = -1; mutex_lock(&pthread_mutex); + for (int i = 0; i < MAXTHREADS; i++){ if (!pthread_sched_threads[i]) { pthread_sched_threads[i] = pt; @@ -94,6 +97,7 @@ static int insert(pthread_thread_t *pt) break; } } + mutex_unlock(&pthread_mutex); return result; } @@ -175,7 +179,7 @@ void pthread_exit(void *retval) DEBUG("ERROR called pthread_self() returned 0 in \"%s\"!\n", __func__); } else { - pthread_thread_t *self = pthread_sched_threads[self_id-1]; + pthread_thread_t *self = pthread_sched_threads[self_id - 1]; while (self->cleanup_top) { __pthread_cleanup_datum_t *ct = self->cleanup_top; @@ -184,6 +188,12 @@ void pthread_exit(void *retval) ct->__routine(ct->__arg); } + /* Prevent linking in pthread_tls.o if no TSS functions were used. */ + extern void __pthread_keys_exit(int self_id) __attribute__((weak)); + if (__pthread_keys_exit) { + __pthread_keys_exit(self_id); + } + self->thread_pid = KERNEL_PID_UNDEF; DEBUG("pthread_exit(%p), self == %p\n", retval, (void *) self); if (self->status != PTS_DETACHED) { @@ -353,3 +363,9 @@ void __pthread_cleanup_pop(__pthread_cleanup_datum_t *datum, int execute) datum->__routine(datum->__arg); } } + +struct __pthread_tls_datum **__pthread_get_tls_head(int self_id) +{ + pthread_thread_t *self = pthread_sched_threads[self_id-1]; + return self ? &self->tls_head : NULL; +} diff --git a/sys/posix/pthread/pthread_tls.c b/sys/posix/pthread/pthread_tls.c new file mode 100644 index 0000000000..73d3899c9b --- /dev/null +++ b/sys/posix/pthread/pthread_tls.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2014 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup pthread + * @{ + * @file + * @brief RIOT POSIX thread local storage + * @author Martin Landsmann + * @author René Kijewski + * @} + */ + +#include + +#include "pthread.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +typedef struct __pthread_tls_datum { + pthread_key_t key; + struct __pthread_tls_datum *next; + void *value; +} tls_data_t; + +struct __pthread_tls_key { + void (*destructor)(void *); +}; + +/** + * @brief Used while manipulating the TLS of a pthread. + */ +static struct mutex_t tls_mutex; + +/** + * @brief Find a thread-specific datum. + * @param[in] tls Pointer to the list of the thread-specific datums. + * @param[in] key The key to look up. + * @param[out] prev The datum before the result. `NULL` if the result is the first key. Spurious if the key was not found. + * @returns The datum or `NULL`. + */ +static tls_data_t *find_specific(tls_data_t **tls, pthread_key_t key, tls_data_t **prev) +{ + tls_data_t *specific = *tls; + *prev = NULL; + + while (specific) { + if (specific->key == key) { + return specific; + } + + *prev = specific; + specific = specific->next; + } + + return 0; +} + +/** + * @brief Find or allocate a thread specific datum. + * @details The `key` must be initialized. + * The result will be the head of the thread-specific datums afterwards. + * @param[in] key The key to lookup. + * @returns The datum. `NULL` on ENOMEM or if the caller is not a pthread. + */ +static tls_data_t *get_specific(pthread_key_t key) +{ + pthread_t self_id = pthread_self(); + if (self_id == 0) { + DEBUG("ERROR called pthread_self() returned 0 in \"%s\"!\n", __func__); + return NULL; + } + + tls_data_t **tls = __pthread_get_tls_head(self_id); + tls_data_t *prev, *specific = find_specific(tls, key, &prev); + + /* Did the datum already exist? */ + if (specific) { + if (prev) { + /* Move the datum to the front for a faster next lookup. */ + /* Let's pretend that we have a totally degenerated splay tree. ;-) */ + prev->next = specific->next; + specific->next = *tls; + *tls = specific; + } + return specific; + } + + /* Allocate new datum. */ + specific = malloc(sizeof (*specific)); + if (specific) { + specific->key = key; + specific->next = *tls; + specific->value = NULL; + *tls = specific; + } + else { + DEBUG("ERROR out of memory in %s!\n", __func__); + } + return specific; +} + +int pthread_key_create(pthread_key_t *key, void (*destructor)(void *)) +{ + *key = malloc(sizeof (**key)); + if (!*key) { + return ENOMEM; + } + + (*key)->destructor = destructor; + return 0; +} + +int pthread_key_delete(pthread_key_t key) +{ + if (!key) { + return EINVAL; + } + + mutex_lock(&tls_mutex); + for (unsigned i = 1; i <= MAXTHREADS; ++i) { + tls_data_t **tls = __pthread_get_tls_head(i); + if (!tls) { + continue; + } + + tls_data_t *prev, *specific = find_specific(tls, key, &prev); + if (specific) { + if (prev) { + prev->next = specific->next; + } + else { + *tls = specific->next; + } + free(specific); + } + } + mutex_unlock(&tls_mutex); + + return 0; +} + +void *pthread_getspecific(pthread_key_t key) +{ + if (!key) { + return NULL; + } + + mutex_lock(&tls_mutex); + tls_data_t *specific = get_specific(key); + void *result = specific ? specific->value : NULL; + mutex_unlock(&tls_mutex); + + return result; +} + +int pthread_setspecific(pthread_key_t key, const void *value) +{ + if (!key) { + return EINVAL; + } + + mutex_lock(&tls_mutex); + tls_data_t *specific = get_specific(key); + if (specific) { + specific->value = (void *) value; + } + mutex_unlock(&tls_mutex); + + return specific ? 0 : ENOMEM; +} + +void __pthread_keys_exit(int self_id) +{ + tls_data_t **tls = __pthread_get_tls_head(self_id); + + /* Calling the dtor could cause another pthread_exit(), so we dehead and free defore calling it. */ + mutex_lock(&tls_mutex); + for (tls_data_t *specific; (specific = *tls); ) { + *tls = specific->next; + void *value = specific->value; + void (*destructor)(void *) = specific->key->destructor; + free(specific); + + if (value && destructor) { + mutex_unlock(&tls_mutex); + destructor(value); + mutex_lock(&tls_mutex); + } + } + mutex_unlock(&tls_mutex); +} diff --git a/tests/pthread_tls/Makefile b/tests/pthread_tls/Makefile new file mode 100644 index 0000000000..e4b10419fd --- /dev/null +++ b/tests/pthread_tls/Makefile @@ -0,0 +1,12 @@ +APPLICATION = pthread_tls +include ../Makefile.tests_common + +BOARD_BLACKLIST := arduino-mega2560 +# arduino-mega2560: unknown type name: clockid_t + +USEMODULE += posix +USEMODULE += pthread + +DISABLE_MODULE += auto_init + +include $(RIOTBASE)/Makefile.include diff --git a/tests/pthread_tls/main.c b/tests/pthread_tls/main.c new file mode 100644 index 0000000000..2d117bbadc --- /dev/null +++ b/tests/pthread_tls/main.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2014 Hamburg University of Applied Sciences (HAW) + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief pthread tls test application + * + * @author Martin Landsmann + * + * @} + */ + +#include +#include "pthread.h" + +#define NUMBER_OF_TLS (20) + +void *run(void *parameter) +{ + pthread_key_t aKeys[NUMBER_OF_TLS]; + int aTLS_values[NUMBER_OF_TLS]; + + (void)parameter; + + printf("\n-= TEST 1 - create %d tls with sequencial values 0...%d =-\n", NUMBER_OF_TLS, NUMBER_OF_TLS - 1); + + for (int i = 0; i < NUMBER_OF_TLS; ++i) { + aTLS_values[i] = i; + pthread_key_create(&(aKeys[i]), NULL); + pthread_setspecific(aKeys[i], &aTLS_values[i]); + } + + printf("now rise sequencial by one values 1...%d\n", NUMBER_OF_TLS); + + for (int i = 0; i < NUMBER_OF_TLS; ++i) { + aTLS_values[i]++; + } + + printf("pick deliberate storage (key[3]:%d) and change the value\n", (int)aKeys[3]); + void *val = pthread_getspecific(aKeys[3]); + *((int *)val) = 42; + + printf("show tls values:\n"); + + for (int i = 0; i < NUMBER_OF_TLS; ++i) { + void *val = pthread_getspecific(aKeys[i]); + int x = *(int *)val; + printf("key[%d]: %d, val: %d\n",i, (int)aKeys[i], x); + } + + printf("\n -= TEST 2 - delete deliberate key (key[5]:%d) =-\n", (int)aKeys[5]); + pthread_key_delete(aKeys[5]); + + printf("show tls values:\n"); + + for (int i = 0; i < NUMBER_OF_TLS; ++i) { + void *val = pthread_getspecific(aKeys[i]); + + if (val != NULL) { + int x = *(int *)val; + printf("key[%d]: %d, val: %d\n",i, (int)aKeys[i], x); + } + } + + printf("\n-= TEST 3 - create new tls =-\n"); + int new_val = 99; + pthread_key_t new_key; + pthread_key_create(&new_key, NULL); + pthread_setspecific(new_key, &new_val); + + printf("added new tls, key: %d, val: %d\n", (int)new_key, new_val); + printf("show tls values:\n"); + + for (int i = 0; i < NUMBER_OF_TLS; ++i) { + void *val = pthread_getspecific(aKeys[i]); + + if (val != NULL) { + int x = *(int *)val; + printf("key[%d]: %d, val: %d\n",i, (int)aKeys[i], x); + } + } + + printf("\n-= TEST 4 - delete all keys =-\n"); + + for (int i = 0; i < NUMBER_OF_TLS; ++i) { + pthread_key_delete(aKeys[i]); + } + + printf("show tls values:\n"); + + for (int i = 0; i < NUMBER_OF_TLS; ++i) { + void *val = pthread_getspecific(aKeys[i]); + + if (val != NULL) { + int x = *(int *)val; + printf("key[%d]: %d, val: %d\n",i, (int)aKeys[i], x); + } + } + + printf("\n-= TEST 5 - try delete non-existing key =-\n"); + printf("try to delete returns: %d\n", pthread_key_delete((pthread_key_t)99)); + + printf("\n-= TEST 6 - add key and delete without a tls =-\n"); + pthread_key_create(&new_key, NULL); + printf("crated key: %d\n", (int)new_key); + printf("try to delete returns: %d\n", pthread_key_delete(new_key)); + + printf("\n-= TEST 7 - add key without tls =-\n"); + pthread_key_create(&new_key, NULL); + printf("crated key: %d\n", (int)new_key); + void* test_7_val = pthread_getspecific(new_key); + printf("test_7_val: %p\n", test_7_val); + + + return NULL; +} + +int main(void) +{ + pthread_t th_id; + pthread_attr_t th_attr; + + pthread_attr_init(&th_attr); + pthread_create(&th_id, &th_attr, run, NULL); + + size_t res; + pthread_join(th_id, (void **) &res); + puts("\ntls tests finished."); + return 0; +}