From 020277a38f4701adadc9881cc53a8ae97a78e8b6 Mon Sep 17 00:00:00 2001 From: David van Moolenbroek Date: Thu, 14 Apr 2011 11:54:43 +0000 Subject: [PATCH] libmthread: support for thread-local storage (keys/specifics) --- common/include/minix/mthread.h | 9 ++ lib/libmthread/Makefile | 3 +- lib/libmthread/allocate.c | 3 + lib/libmthread/global.h | 4 +- lib/libmthread/key.c | 186 +++++++++++++++++++++++++++++++++ lib/libmthread/proto.h | 4 + test/test59.c | 178 +++++++++++++++++++++++++++++++ 7 files changed, 384 insertions(+), 3 deletions(-) create mode 100644 lib/libmthread/key.c diff --git a/common/include/minix/mthread.h b/common/include/minix/mthread.h index a2c525016..19012dfbe 100644 --- a/common/include/minix/mthread.h +++ b/common/include/minix/mthread.h @@ -19,6 +19,7 @@ typedef int mthread_thread_t; typedef int mthread_once_t; +typedef int mthread_key_t; typedef void * mthread_condattr_t; typedef void * mthread_mutexattr_t; @@ -58,6 +59,7 @@ typedef struct __mthread_attr *mthread_attr_t; #define MTHREAD_CREATE_DETACHED 002 #define MTHREAD_ONCE_INIT 0 #define MTHREAD_STACK_MIN MINSIGSTKSZ +#define MTHREAD_KEYS_MAX 128 /* allocate.c */ _PROTOTYPE( int mthread_create, (mthread_thread_t *thread, @@ -99,6 +101,13 @@ _PROTOTYPE( int mthread_cond_signal, (mthread_cond_t *cond) ); _PROTOTYPE( int mthread_cond_wait, (mthread_cond_t *cond, mthread_mutex_t *mutex) ); +/* key.c */ +_PROTOTYPE( int mthread_key_create, (mthread_key_t *key, + void (*destructor)(void *)) ); +_PROTOTYPE( int mthread_key_delete, (mthread_key_t key) ); +_PROTOTYPE( void *mthread_getspecific, (mthread_key_t key) ); +_PROTOTYPE( int mthread_setspecific, (mthread_key_t key, void *value) ); + /* misc.c */ _PROTOTYPE( void mthread_stats, (void) ); _PROTOTYPE( void mthread_verify_f, (char *f, int l) ); diff --git a/lib/libmthread/Makefile b/lib/libmthread/Makefile index b74280037..5e8c3b7d8 100644 --- a/lib/libmthread/Makefile +++ b/lib/libmthread/Makefile @@ -11,6 +11,7 @@ SRCS= \ misc.c \ queue.c \ condition.c \ - scheduler.c + scheduler.c \ + key.c .include diff --git a/lib/libmthread/allocate.c b/lib/libmthread/allocate.c index e8ecede7b..854ef96b6 100644 --- a/lib/libmthread/allocate.c +++ b/lib/libmthread/allocate.c @@ -121,6 +121,8 @@ void *value; if (tcb->m_state == MS_EXITING) /* Already stopping, nothing to do. */ return; + mthread_cleanup_values(); + /* When we're called from the fallback thread, the fallback thread * will invoke the scheduler. However, if the thread itself called * mthread_exit, _we_ will have to wake up the scheduler. @@ -283,6 +285,7 @@ PUBLIC void mthread_init(void) mthread_init_valid_mutexes(); mthread_init_valid_conditions(); mthread_init_valid_attributes(); + mthread_init_keys(); mthread_init_scheduler(); /* Initialize the fallback thread */ diff --git a/lib/libmthread/global.h b/lib/libmthread/global.h index 45773e197..30e480e28 100644 --- a/lib/libmthread/global.h +++ b/lib/libmthread/global.h @@ -9,8 +9,8 @@ #define NO_THREADS 4 #define MAX_THREAD_POOL 1024 #define STACKSZ 4096 -#define MAIN_THREAD -1 -#define NO_THREAD -2 +#define MAIN_THREAD (-1) +#define NO_THREAD (-2) #define isokthreadid(i) (i == MAIN_THREAD || (i >= 0 && i < no_threads)) #define MTHREAD_INIT_MAGIC 0xca11ab1e #define MTHREAD_NOT_INUSE 0xdefec7 diff --git a/lib/libmthread/key.c b/lib/libmthread/key.c new file mode 100644 index 000000000..5ad11494d --- /dev/null +++ b/lib/libmthread/key.c @@ -0,0 +1,186 @@ +#include +#include +#include "global.h" +#include "proto.h" + +PRIVATE struct { + int used; + int nvalues; + void *mvalue; + void **value; + void (*destr)(void *); +} keys[MTHREAD_KEYS_MAX]; + +/*===========================================================================* + * mthread_init_keys * + *===========================================================================*/ +PUBLIC void mthread_init_keys(void) +{ +/* Initialize the table of key entries. + */ + mthread_key_t k; + + for (k = 0; k < MTHREAD_KEYS_MAX; k++) + keys[k].used = FALSE; +} + +/*===========================================================================* + * mthread_key_create * + *===========================================================================*/ +PUBLIC int mthread_key_create(mthread_key_t *key, void (*destructor)(void *)) +{ +/* Allocate a key. + */ + mthread_key_t k; + + mthread_init(); /* Make sure libmthread is initialized */ + + /* We do not yet allocate storage space for the values here, because we can + * not estimate how many threads will be created in the common case that the + * application creates keys before spawning threads. + */ + for (k = 0; k < MTHREAD_KEYS_MAX; k++) { + if (!keys[k].used) { + keys[k].used = TRUE; + keys[k].nvalues = 0; + keys[k].mvalue = NULL; + keys[k].value = NULL; + keys[k].destr = destructor; + *key = k; + + return(0); + } + } + + return(EAGAIN); +} + +/*===========================================================================* + * mthread_key_delete * + *===========================================================================*/ +PUBLIC int mthread_key_delete(mthread_key_t key) +{ +/* Free up a key, as well as any associated storage space. + */ + + mthread_init(); /* Make sure libmthread is initialized */ + + if (key < 0 || key >= MTHREAD_KEYS_MAX || !keys[key].used) + return(EINVAL); + + free(keys[key].value); + + keys[key].used = FALSE; + + return(0); +} + +/*===========================================================================* + * mthread_getspecific * + *===========================================================================*/ +PUBLIC void *mthread_getspecific(mthread_key_t key) +{ +/* Get this thread's local value for the given key. The default is NULL. + */ + + mthread_init(); /* Make sure libmthread is initialized */ + + if (key < 0 || key >= MTHREAD_KEYS_MAX || !keys[key].used) + return(NULL); + + if (current_thread == MAIN_THREAD) + return keys[key].mvalue; + + if (current_thread < keys[key].nvalues) + return(keys[key].value[current_thread]); + + return(NULL); +} + +/*===========================================================================* + * mthread_setspecific * + *===========================================================================*/ +PUBLIC int mthread_setspecific(mthread_key_t key, void *value) +{ +/* Set this thread's value for the given key. Allocate more resources as + * necessary. + */ + void **p; + + mthread_init(); /* Make sure libmthread is initialized */ + + if (key < 0 || key >= MTHREAD_KEYS_MAX || !keys[key].used) + return(EINVAL); + + if (current_thread == MAIN_THREAD) { + keys[key].mvalue = value; + + return(0); + } + + if (current_thread >= keys[key].nvalues) { + if (current_thread >= no_threads) + mthread_panic("Library state corrupt"); + + if ((p = (void **) realloc(keys[key].value, + sizeof(void*) * no_threads)) == NULL) + return(ENOMEM); + + memset(&p[keys[key].nvalues], 0, + sizeof(void*) * (no_threads - keys[key].nvalues)); + + keys[key].nvalues = no_threads; + keys[key].value = p; + } + + keys[key].value[current_thread] = value; + + return(0); +} + +/*===========================================================================* + * mthread_cleanup_values * + *===========================================================================*/ +PUBLIC void mthread_cleanup_values(void) +{ +/* Clean up all the values associated with an exiting thread, calling keys' + * destruction procedures as appropriate. + */ + mthread_key_t k; + void *value; + int found; + + /* Any of the destructors may set a new value on any key, so we may have to + * loop over the table of keys multiple times. This implementation has no + * protection against infinite loops in this case. + */ + do { + found = FALSE; + + for (k = 0; k < MTHREAD_KEYS_MAX; k++) { + if (!keys[k].used) continue; + if (keys[k].destr == NULL) continue; + + if (current_thread == MAIN_THREAD) { + value = keys[k].mvalue; + + keys[k].mvalue = NULL; + } else { + if (current_thread >= keys[k].nvalues) continue; + + value = keys[k].value[current_thread]; + + keys[k].value[current_thread] = NULL; + } + + if (value != NULL) { + /* Note: calling mthread_exit() from a destructor + * causes undefined behavior. + */ + keys[k].destr(value); + + found = TRUE; + } + } + } while (found); +} diff --git a/lib/libmthread/proto.h b/lib/libmthread/proto.h index 42623da89..2212c2fa5 100644 --- a/lib/libmthread/proto.h +++ b/lib/libmthread/proto.h @@ -16,6 +16,10 @@ _PROTOTYPE( void mthread_init_valid_conditions, (void) ); _PROTOTYPE( int mthread_cond_verify, (void) ); #endif +/* key.c */ +_PROTOTYPE( void mthread_init_keys, (void) ); +_PROTOTYPE( void mthread_cleanup_values, (void) ); + /* misc.c */ #define mthread_panic(m) mthread_panic_f(__FILE__, __LINE__, (m)) _PROTOTYPE( void mthread_panic_f, (const char *file, int line, diff --git a/test/test59.c b/test/test59.c index 745c6b186..844e8d4b4 100644 --- a/test/test59.c +++ b/test/test59.c @@ -9,6 +9,7 @@ #define cond_t mthread_cond_t #define once_t mthread_once_t #define attr_t mthread_attr_t +#define key_t mthread_key_t #define MAX_ERROR 5 #include "common.c" @@ -21,6 +22,10 @@ PRIVATE mutex_t mu[3]; PRIVATE cond_t condition; PRIVATE mutex_t *count_mutex, *condition_mutex; PRIVATE once_t once; +PRIVATE key_t key[MTHREAD_KEYS_MAX+1]; +PRIVATE int values[4]; +PRIVATE int first; + #define VERIFY_MUTEX(a,b,c,esub,eno) do { \ if (mutex_a_step != a) { \ printf("Expected %d %d %d, got: %d %d %d\n", \ @@ -47,6 +52,7 @@ FORWARD _PROTOTYPE( void test_scheduling, (void) ); FORWARD _PROTOTYPE( void test_mutex, (void) ); FORWARD _PROTOTYPE( void test_condition, (void) ); FORWARD _PROTOTYPE( void test_attributes, (void) ); +FORWARD _PROTOTYPE( void test_keys, (void) ); FORWARD _PROTOTYPE( void err, (int subtest, int error) ); /*===========================================================================* @@ -724,6 +730,177 @@ PRIVATE void test_attributes(void) #endif } +/*===========================================================================* + * destr_a * + *===========================================================================*/ +PRIVATE void destr_a(void *value) +{ + int num; + + num = (int) value; + + /* This destructor must be called once for all of the values 1..4. */ + if (num <= 0 || num > 4) err(15, 1); + + if (values[num - 1] != 1) err(15, 2); + + values[num - 1] = 2; +} + +/*===========================================================================* + * destr_b * + *===========================================================================*/ +PRIVATE void destr_b(void *value) +{ + /* This destructor must never trigger. */ + err(16, 1); +} + +/*===========================================================================* + * key_a * + *===========================================================================*/ +PRIVATE void key_a(void *arg) +{ + int i; + + if (!first) mthread_yield(); + + /* Each new threads gets NULL-initialized values. */ + for (i = 0; i < 5; i++) + if (mthread_getspecific(key[i]) != NULL) err(17, 1); + + /* Make sure that the local values persist despite other threads' actions. */ + for (i = 1; i < 5; i++) + if (mthread_setspecific(key[i], (void *) i) != 0) err(17, 2); + + mthread_yield(); + + for (i = 1; i < 5; i++) + if (mthread_getspecific(key[i]) != (void *) i) err(17, 3); + + mthread_yield(); + + /* The other thread has deleted this key by now. */ + if (mthread_setspecific(key[3], NULL) != EINVAL) err(17, 4); + + /* If a key's value is set to NULL, its destructor must not be called. */ + if (mthread_setspecific(key[4], NULL) != 0) err(17, 5); +} + +/*===========================================================================* + * key_b * + *===========================================================================*/ +PRIVATE void key_b(void *arg) +{ + int i; + + first = 1; + mthread_yield(); + + /* Each new threads gets NULL-initialized values. */ + for (i = 0; i < 5; i++) + if (mthread_getspecific(key[i]) != NULL) err(18, 1); + + for (i = 0; i < 4; i++) + if (mthread_setspecific(key[i], (void *) (i + 2)) != 0) err(18, 2); + + mthread_yield(); + + /* Deleting a key will not cause a call its destructor at any point. */ + if (mthread_key_delete(key[3]) != 0) err(18, 3); + + mthread_exit(NULL); +} + +/*===========================================================================* + * key_c * + *===========================================================================*/ +PRIVATE void key_c(void *arg) +{ + /* The only thing that this thread should do, is set a value. */ + if (mthread_setspecific(key[0], (void *) mthread_self()) != 0) err(19, 1); + + mthread_yield(); + + if (!mthread_equal((thread_t) mthread_getspecific(key[0]), mthread_self())) + err(19, 2); +} + +/*===========================================================================* + * test_keys * + *===========================================================================*/ +PRIVATE void test_keys(void) +{ + thread_t t[24]; + int i, j; + + /* Make sure that we can create exactly MTHREAD_KEYS_MAX keys. */ + memset(key, 0, sizeof(key)); + + for (i = 0; i < MTHREAD_KEYS_MAX; i++) { + if (mthread_key_create(&key[i], NULL) != 0) err(20, 1); + + for (j = 0; j < i - 1; j++) + if (key[i] == key[j]) err(20, 2); + } + + if (mthread_key_create(&key[i], NULL) != EAGAIN) err(20, 3); + + for (i = 3; i < MTHREAD_KEYS_MAX; i++) + if (mthread_key_delete(key[i]) != 0) err(20, 4); + + /* Test basic good and bad value assignment and retrieval. */ + if (mthread_setspecific(key[0], (void *) 1) != 0) err(20, 5); + if (mthread_setspecific(key[1], (void *) 2) != 0) err(20, 6); + if (mthread_setspecific(key[2], (void *) 3) != 0) err(20, 7); + if (mthread_setspecific(key[1], NULL) != 0) err(20, 8); + if (mthread_getspecific(key[0]) != (void *) 1) err(20, 9); + if (mthread_getspecific(key[1]) != NULL) err(20, 10); + if (mthread_getspecific(key[2]) != (void *) 3) err(20, 11); + if (mthread_setspecific(key[3], (void *) 4) != EINVAL) err(20, 12); + if (mthread_setspecific(key[3], NULL) != EINVAL) err(20, 13); + + if (mthread_key_delete(key[1]) != 0) err(20, 14); + if (mthread_key_delete(key[2]) != 0) err(20, 15); + + /* Test thread locality and destructors. */ + if (mthread_key_create(&key[1], destr_a) != 0) err(20, 16); + if (mthread_key_create(&key[2], destr_a) != 0) err(20, 17); + if (mthread_key_create(&key[3], destr_b) != 0) err(20, 18); + if (mthread_key_create(&key[4], destr_b) != 0) err(20, 19); + + if (mthread_getspecific(key[2]) != NULL) err(20, 20); + + for (i = 0; i < 4; i++) + values[i] = 1; + first = 0; + + if (mthread_create(&t[0], NULL, key_a, NULL) != 0) err(20, 21); + if (mthread_create(&t[1], NULL, key_b, NULL) != 0) err(20, 22); + + for (i = 0; i < 2; i++) + if (mthread_join(t[i], NULL) != 0) err(20, 23); + + /* The destructors must have changed all these values now. */ + for (i = 0; i < 4; i++) + if (values[i] != 2) err(20, 24); + + /* The original values must not have changed. */ + if (mthread_getspecific(key[0]) != (void *) 1) err(20, 25); + + /* Deleting a deleted key should not cause any problems either. */ + if (mthread_key_delete(key[3]) != EINVAL) err(20, 26); + + /* Make sure everything still works when using a larger number of threads. + * This should trigger reallocation code within libmthread's key handling. + */ + for (i = 0; i < 24; i++) + if (mthread_create(&t[i], NULL, key_c, NULL) != 0) err(20, 27); + + for (i = 0; i < 24; i++) + if (mthread_join(t[i], NULL) != 0) err(20, 28); +} + /*===========================================================================* * main * *===========================================================================*/ @@ -740,6 +917,7 @@ int main(void) test_mutex(); test_condition(); test_attributes(); + test_keys(); quit(); } -- 2.44.0