libmthread: support for thread-local storage (keys/specifics)
This commit is contained in:
parent
dc8ee363db
commit
020277a38f
7 changed files with 384 additions and 3 deletions
|
@ -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) );
|
||||
|
|
|
@ -11,6 +11,7 @@ SRCS= \
|
|||
misc.c \
|
||||
queue.c \
|
||||
condition.c \
|
||||
scheduler.c
|
||||
scheduler.c \
|
||||
key.c
|
||||
|
||||
.include <bsd.lib.mk>
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
186
lib/libmthread/key.c
Normal 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);
|
||||
}
|
|
@ -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,
|
||||
|
|
178
test/test59.c
178
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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue