Add dynamic-tls (dtls) implementation to parlib
authorKevin Klues <klueska@cs.berkeley.edu>
Fri, 14 Dec 2012 22:45:34 +0000 (14:45 -0800)
committerKevin Klues <klueska@cs.berkeley.edu>
Fri, 14 Dec 2012 22:45:34 +0000 (14:45 -0800)
tests/dtls_test.c [new file with mode: 0644]
user/parlib/dtls.c [new file with mode: 0644]
user/parlib/include/dtls.h [new file with mode: 0644]

diff --git a/tests/dtls_test.c b/tests/dtls_test.c
new file mode 100644 (file)
index 0000000..dbb7371
--- /dev/null
@@ -0,0 +1,62 @@
+#include <stdio.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/* OS dependent #incs */
+#include <parlib.h>
+#include <dtls.h>
+
+#define NR_TEST_THREADS 10
+#define NUM_DTLS_KEYS 10
+pthread_t my_threads[NR_TEST_THREADS];
+void *my_retvals[NR_TEST_THREADS];
+dtls_key_t dtls_keys[NUM_DTLS_KEYS];
+pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+void *thread(void* arg)
+{      
+  long *dtls_value[NUM_DTLS_KEYS];
+  for(int i=0; i<NUM_DTLS_KEYS; i++) {
+    dtls_value[i] = malloc(sizeof(long));
+    *dtls_value[i] = (long)pthread_self() + i;
+    set_dtls(dtls_keys[i], dtls_value[i]);
+  }
+
+  pthread_mutex_lock(&mutex);
+  long self = (long)pthread_self();
+  printf("In pthread %p (%ld)\n", (void*)self, self);
+  for(int i=0; i<NUM_DTLS_KEYS; i++) {
+    long *value = get_dtls(dtls_keys[i]);
+    printf("  dtls_value[%d] = %ld\n", i, *value);
+  }
+  pthread_mutex_unlock(&mutex);
+
+  return (void*)(self);
+}
+
+static void dtls_dtor(void *dtls)
+{
+  pthread_mutex_lock(&mutex);
+  printf("Phread %p freeing dtls %p.\n", pthread_self(), dtls);
+  free(dtls);
+  pthread_mutex_unlock(&mutex);
+}
+
+int main(int argc, char** argv) 
+{
+  printf("Starting dtls test.\n");
+  for(int i=0; i<NUM_DTLS_KEYS; i++) {
+    dtls_keys[i] = dtls_key_create(dtls_dtor);
+  }
+  for (int i = 0; i < NR_TEST_THREADS; i++) {
+       assert(!pthread_create(&my_threads[i], NULL, &thread, NULL));
+  }
+  for (int i = 0; i < NR_TEST_THREADS; i++) {
+       pthread_join(my_threads[i], &my_retvals[i]);
+  }
+  for(int i=0; i<NUM_DTLS_KEYS; i++) {
+    dtls_key_delete(dtls_keys[i]);
+  }
+  printf("Test complete!\n");
+} 
diff --git a/user/parlib/dtls.c b/user/parlib/dtls.c
new file mode 100644 (file)
index 0000000..ad71cf3
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2012 The Regents of the University of California
+ * Kevin Klues <klueska@cs.berkeley.edu>
+ *
+ * This file is part of Parlib.
+ *
+ * Parlib is free software: you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * Parlib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * Lesser GNU General Public License for more details.
+ * 
+ * See COPYING.LESSER for details on the GNU Lesser General Public License.
+ * See COPYING for details on the GNU General Public License.
+ */
+
+#include <stddef.h>
+#include "spinlock.h"
+#include "dtls.h"
+#include "slab.h"
+
+/* The current dymamic tls implementation uses a locked linked list
+ * to find the key for a given thread. We should probably find a better way to
+ * do this based on a custom lock-free hash table or something. */
+#include <sys/queue.h>
+#include "spinlock.h"
+
+/* The dynamic tls key structure */
+struct dtls_key {
+  atomic_t lock;
+  int ref_count;
+  bool valid;
+  void (*dtor)(void*);
+};
+
+/* The definition of a dtls_key list and its elements */
+struct dtls_value {
+  TAILQ_ENTRY(dtls_value) link;
+  struct dtls_key *key;
+  void *dtls;
+}; 
+TAILQ_HEAD(dtls_list, dtls_value);
+
+/* A struct containing all of the per thread (i.e. vcore or uthread) data
+ * associated with dtls */
+typedef struct dtls_data {
+  /* A per-thread list of dtls regions */
+  struct dtls_list list;
+
+} dtls_data_t;
+
+/* A slab of dtls keys (global to all threads) */
+static struct kmem_cache *__dtls_keys_cache;
+
+/* A slab of values for use when mapping a dtls_key to its per-thread value */
+struct kmem_cache *__dtls_values_cache;
+  
+/* A lock protecting access to the caches above */
+static atomic_t __dtls_lock;
+
+static __thread dtls_data_t __dtls_data;
+static __thread bool __dtls_initialized = false;
+
+static dtls_key_t __allocate_dtls_key() 
+{
+  spinlock_lock(&__dtls_lock);
+  dtls_key_t key = kmem_cache_alloc(__dtls_keys_cache, 0);
+  assert(key);
+  key->ref_count = 1;
+  spinlock_unlock(&__dtls_lock);
+  return key;
+}
+
+static void __maybe_free_dtls_key(dtls_key_t key)
+{
+  if(key->ref_count == 0) {
+    spinlock_lock(&__dtls_lock);
+    kmem_cache_free(__dtls_keys_cache, key);
+    spinlock_unlock(&__dtls_lock);
+  }
+}
+
+/* Constructor to get a reference to the main thread's TLS descriptor */
+int dtls_lib_init()
+{
+  /* Make sure this only runs once */
+  static bool initialized = false;
+  if (initialized)
+      return 0;
+  initialized = true;
+  
+  /* Initialize the global cache of dtls_keys */
+  __dtls_keys_cache = kmem_cache_create("dtls_keys_cache", 
+    sizeof(struct dtls_key), __alignof__(struct dtls_key), 0, NULL, NULL);
+  
+  __dtls_values_cache = kmem_cache_create("dtls_values_cache", 
+    sizeof(struct dtls_value), __alignof__(struct dtls_value), 0, NULL, NULL);
+  
+  /* Initialize the lock that protects the cache */
+  spinlock_init(&__dtls_lock);
+  return 0;
+}
+
+dtls_key_t dtls_key_create(dtls_dtor_t dtor)
+{
+  dtls_lib_init();
+  dtls_key_t key = __allocate_dtls_key();
+  spinlock_init(&key->lock);
+  key->valid = true;
+  key->dtor = dtor;
+  return key;
+}
+
+void dtls_key_delete(dtls_key_t key)
+{
+  assert(key);
+
+  spinlock_lock(&key->lock);
+  key->valid = false;
+  key->ref_count--;
+  spinlock_unlock(&key->lock);
+  __maybe_free_dtls_key(key);
+}
+
+static inline void __set_dtls(dtls_data_t *dtls_data, dtls_key_t key, void *dtls)
+{
+  assert(key);
+
+  spinlock_lock(&key->lock);
+  key->ref_count++;
+  spinlock_unlock(&key->lock);
+
+  struct dtls_value *v = NULL;
+  TAILQ_FOREACH(v, &dtls_data->list, link)
+    if(v->key == key) break;
+
+  if(!v) {
+    spinlock_lock(&__dtls_lock);
+    v = kmem_cache_alloc(__dtls_values_cache, 0);
+    spinlock_unlock(&__dtls_lock);
+    assert(v);
+    v->key = key;
+    TAILQ_INSERT_HEAD(&dtls_data->list, v, link);
+  }
+  v->dtls = dtls;
+}
+
+static inline void *__get_dtls(dtls_data_t *dtls_data, dtls_key_t key)
+{
+  assert(key);
+
+  struct dtls_value *v = NULL;
+  TAILQ_FOREACH(v, &dtls_data->list, link)
+    if(v->key == key) return v->dtls;
+  return v;
+}
+
+static inline void __destroy_dtls(dtls_data_t *dtls_data)
+{
+ struct dtls_value *v,*n;
+  v = TAILQ_FIRST(&dtls_data->list);
+  while(v != NULL) {
+    dtls_key_t key = v->key;
+    bool run_dtor = false;
+  
+    spinlock_lock(&key->lock);
+    if(key->valid)
+      if(key->dtor)
+        run_dtor = true;
+    spinlock_unlock(&key->lock);
+
+       // MUST run the dtor outside the spinlock if we want it to be able to call
+       // code that may deschedule it for a while (i.e. a mutex). Probably a
+       // good idea anyway since it can be arbitrarily long and is written by the
+       // user. Note, there is a small race here on the valid field, whereby we
+       // may run a destructor on an invalid key. At least the keys memory wont
+       // be deleted though, as protected by the ref count. Any reasonable usage
+       // of this interface should safeguard that a key is never destroyed before
+       // all of the threads that use it have exited anyway.
+    if(run_dtor) {
+         void *dtls = v->dtls;
+      v->dtls = NULL;
+      key->dtor(dtls);
+    }
+
+    spinlock_lock(&key->lock);
+    key->ref_count--;
+    spinlock_unlock(&key->lock);
+    __maybe_free_dtls_key(key);
+
+    n = TAILQ_NEXT(v, link);
+    TAILQ_REMOVE(&dtls_data->list, v, link);
+    spinlock_lock(&__dtls_lock);
+    kmem_cache_free(__dtls_values_cache, v);
+    spinlock_unlock(&__dtls_lock);
+    v = n;
+  }
+}
+
+void set_dtls(dtls_key_t key, void *dtls)
+{
+  bool initialized = true;
+  dtls_data_t *dtls_data = NULL;
+  if(!__dtls_initialized) {
+    initialized = false;
+    __dtls_initialized  = true;
+  }
+  dtls_data = &__dtls_data;
+  if(!initialized) {
+    TAILQ_INIT(&dtls_data->list);
+  }
+  __set_dtls(dtls_data, key, dtls);
+}
+
+void *get_dtls(dtls_key_t key)
+{
+  dtls_data_t *dtls_data = NULL;
+  if(!__dtls_initialized)
+    return NULL;
+  dtls_data = &__dtls_data;
+  return __get_dtls(dtls_data, key);
+}
+
+void destroy_dtls()
+{
+  dtls_data_t *dtls_data = NULL;
+  if(!__dtls_initialized)
+    return;
+  dtls_data = &__dtls_data;
+  __destroy_dtls(dtls_data);
+}
+
diff --git a/user/parlib/include/dtls.h b/user/parlib/include/dtls.h
new file mode 100644 (file)
index 0000000..b4e9168
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2012 The Regents of the University of California
+ * Kevin Klues <klueska@cs.berkeley.edu>
+ *
+ * This file is part of Parlib.
+ * 
+ * Parlib is free software: you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * Parlib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * Lesser GNU General Public License for more details.
+ * 
+ * See COPYING.LESSER for details on the GNU Lesser General Public License.
+ * See COPYING for details on the GNU General Public License.
+ */
+
+#ifndef PARLIB_DTLS_H
+#define PARLIB_DTLS_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifndef __GNUC__
+  #error "You need to be using gcc to compile this library..."
+#endif 
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Declaration of types needed for dynamically allocatable tls */
+typedef struct dtls_key *dtls_key_t;
+typedef void (*dtls_dtor_t)(void*);
+
+/* Initialize a dtls_key for dynamically setting/getting uthread local storage
+ * on a uthread or vcore. */
+dtls_key_t dtls_key_create(dtls_dtor_t dtor);
+
+/* Destroy a dtls key. */
+void dtls_key_delete(dtls_key_t key);
+
+/* Set dtls storage for the provided dtls key on the current uthread or vcore. */
+void set_dtls(dtls_key_t key, void *dtls);
+
+/* Get dtls storage for the provided dtls key on the current uthread or vcore. */
+void *get_dtls(dtls_key_t key);
+
+/* Destroy all dtls storage associated with the current uthread or vcore. */
+void destroy_dtls();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PARLIB_DTLS_H */
+