libmthread: support for thread-local storage (keys/specifics)

This commit is contained in:
David van Moolenbroek 2011-04-14 11:54:43 +00:00
parent dc8ee363db
commit 020277a38f
7 changed files with 384 additions and 3 deletions

View file

@ -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) );

View file

@ -11,6 +11,7 @@ SRCS= \
misc.c \
queue.c \
condition.c \
scheduler.c
scheduler.c \
key.c
.include <bsd.lib.mk>

View file

@ -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 */

View file

@ -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

186
lib/libmthread/key.c Normal file
View file

@ -0,0 +1,186 @@
#include <minix/mthread.h>
#include <string.h>
#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);
}

View file

@ -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,

View file

@ -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();
}