]> Zhao Yanbai Git Server - minix.git/commitdiff
libmthread: support for thread-local storage (keys/specifics)
authorDavid van Moolenbroek <david@minix3.org>
Thu, 14 Apr 2011 11:54:43 +0000 (11:54 +0000)
committerDavid van Moolenbroek <david@minix3.org>
Thu, 14 Apr 2011 11:54:43 +0000 (11:54 +0000)
common/include/minix/mthread.h
lib/libmthread/Makefile
lib/libmthread/allocate.c
lib/libmthread/global.h
lib/libmthread/key.c [new file with mode: 0644]
lib/libmthread/proto.h
test/test59.c

index a2c525016b79589f5d0264f54f03ded66ccce878..19012dfbe81b16b73ac3a2af865cf57aefe2db4e 100644 (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)                                    );
index b7428003798c9d521578f26e64e10c8e33c82e34..5e8c3b7d8a7e1e50a69f40a80c79d959be4ed750 100644 (file)
@@ -11,6 +11,7 @@ SRCS= \
        misc.c \
        queue.c \
        condition.c \
-       scheduler.c 
+       scheduler.c \
+       key.c
 
 .include <bsd.lib.mk>
index e8ecede7b4abee6c65e6b418d3e6155d1f0216f7..854ef96b6c548f8ca2026a626947ff637768498b 100644 (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 */
index 45773e197126b1a5eaaadc84da92b3bdad7ee964..30e480e28d28af21b80ea6c9ec2f1afee6a4e057 100644 (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
diff --git a/lib/libmthread/key.c b/lib/libmthread/key.c
new file mode 100644 (file)
index 0000000..5ad1149
--- /dev/null
@@ -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);
+}
index 42623da89adce3700ebf252cb656e6bab40f4008..2212c2fa51e45f1aaace1dc4587420da5031ef2d 100644 (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,
index 745c6b186c1e3445996962bf7c7a7ed47dd0f707..844e8d4b4698c6e91f548e28722a040beef4d3dc 100644 (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();
 }