akaros/user/parlib/dtls.c
<<
>>
Prefs
   1/* Copyright (c) 2012 The Regents of the University of California
   2 * Kevin Klues <klueska@cs.berkeley.edu>
   3 *
   4 * See LICENSE for details. */
   5
   6#include <parlib/assert.h>
   7#include <parlib/dtls.h>
   8#include <parlib/slab.h>
   9#include <parlib/spinlock.h>
  10#include <stddef.h>
  11
  12/* The current dymamic tls implementation uses a locked linked list
  13 * to find the key for a given thread. We should probably find a better way to
  14 * do this based on a custom lock-free hash table or something. */
  15#include <parlib/spinlock.h>
  16#include <sys/queue.h>
  17
  18/* Define some number of static keys, for which the memory containing the keys
  19 * and the per-thread memory for the values associated with those keys is
  20 * allocated statically. This is adapted from glibc's notion of the
  21 * "specific_1stblock" field embedded directly into its pthread structure for
  22 * pthread_get/specific() calls. */
  23#define NUM_STATIC_KEYS 32
  24
  25/* The dynamic tls key structure */
  26struct dtls_key {
  27        int id;
  28        int ref_count;
  29        bool valid;
  30        void (*dtor)(void *);
  31};
  32
  33/* The definition of a dtls_key list and its elements */
  34struct dtls_value {
  35        TAILQ_ENTRY(dtls_value) link;
  36        struct dtls_key *key;
  37        const void *dtls;
  38};
  39TAILQ_HEAD(dtls_list, dtls_value);
  40
  41/* A struct containing all of the per thread (i.e. vcore or uthread) data
  42 * associated with dtls */
  43typedef struct dtls_data {
  44        /* A per-thread list of dtls regions */
  45        struct dtls_list list;
  46        /* Memory to hold dtls values for the first NUM_STATIC_KEYS keys */
  47        struct dtls_value early_values[NUM_STATIC_KEYS];
  48} dtls_data_t;
  49
  50/* A slab of dtls keys (global to all threads) */
  51static struct kmem_cache *__dtls_keys_cache;
  52
  53/* A slab of values for use when mapping a dtls_key to its per-thread value */
  54struct kmem_cache *__dtls_values_cache;
  55
  56static __thread dtls_data_t __dtls_data;
  57static __thread bool __dtls_initialized;
  58static struct dtls_key static_dtls_keys[NUM_STATIC_KEYS];
  59static int num_dtls_keys;
  60
  61/* Initialize the slab caches for allocating dtls keys and values. */
  62int dtls_cache_init(void)
  63{
  64        /* Make sure this only runs once */
  65        static bool initialized;
  66
  67        if (initialized)
  68                return 0;
  69        initialized = true;
  70
  71        /* Initialize the global cache of dtls_keys */
  72        __dtls_keys_cache =
  73            kmem_cache_create("dtls_keys_cache", sizeof(struct dtls_key),
  74                              __alignof__(struct dtls_key), 0, NULL, NULL,
  75                              NULL);
  76
  77        /* Initialize the global cache of dtls_values */
  78        __dtls_values_cache =
  79            kmem_cache_create("dtls_values_cache", sizeof(struct dtls_value),
  80                              __alignof__(struct dtls_value), 0, NULL, NULL,
  81                              NULL);
  82
  83        return 0;
  84}
  85
  86static dtls_key_t __allocate_dtls_key(void)
  87{
  88        dtls_key_t key;
  89        int keyid = __sync_fetch_and_add(&num_dtls_keys, 1);
  90
  91        if (keyid < NUM_STATIC_KEYS) {
  92                key = &static_dtls_keys[keyid];
  93        } else {
  94                dtls_cache_init();
  95                key = kmem_cache_alloc(__dtls_keys_cache, 0);
  96        }
  97        assert(key);
  98        key->id = keyid;
  99        key->ref_count = 1;
 100        return key;
 101}
 102
 103static void __maybe_free_dtls_key(dtls_key_t key)
 104{
 105        int ref_count = __sync_add_and_fetch(&key->ref_count, -1);
 106
 107        if (ref_count == 0 && key->id >= NUM_STATIC_KEYS)
 108                kmem_cache_free(__dtls_keys_cache, key);
 109}
 110
 111static struct dtls_value *__allocate_dtls_value(struct dtls_data *dtls_data,
 112                                                struct dtls_key *key)
 113{
 114        struct dtls_value *v;
 115
 116        if (key->id < NUM_STATIC_KEYS)
 117                v = &dtls_data->early_values[key->id];
 118        else
 119                v = kmem_cache_alloc(__dtls_values_cache, 0);
 120        assert(v);
 121        return v;
 122}
 123
 124static void __free_dtls_value(struct dtls_value *v)
 125{
 126        if (v->key->id >= NUM_STATIC_KEYS)
 127                kmem_cache_free(__dtls_values_cache, v);
 128}
 129
 130dtls_key_t dtls_key_create(dtls_dtor_t dtor)
 131{
 132        dtls_key_t key = __allocate_dtls_key();
 133
 134        key->valid = true;
 135        key->dtor = dtor;
 136        return key;
 137}
 138
 139void dtls_key_delete(dtls_key_t key)
 140{
 141        assert(key);
 142
 143        key->valid = false;
 144        __maybe_free_dtls_key(key);
 145}
 146
 147static inline struct dtls_value *__get_dtls(dtls_data_t *dtls_data,
 148                                            dtls_key_t key)
 149{
 150        struct dtls_value *v;
 151
 152        assert(key);
 153        if (key->id < NUM_STATIC_KEYS) {
 154                v = &dtls_data->early_values[key->id];
 155                if (v->key != NULL)
 156                        return v;
 157        } else {
 158                TAILQ_FOREACH(v, &dtls_data->list, link)
 159                        if (v->key == key)
 160                                return v;
 161        }
 162        return NULL;
 163}
 164
 165static inline void __set_dtls(dtls_data_t *dtls_data, dtls_key_t key,
 166                              const void *dtls)
 167{
 168        struct dtls_value *v;
 169
 170        assert(key);
 171        v = __get_dtls(dtls_data, key);
 172        if (!v) {
 173                v = __allocate_dtls_value(dtls_data, key);
 174                __sync_fetch_and_add(&key->ref_count, 1);
 175                v->key = key;
 176                TAILQ_INSERT_HEAD(&dtls_data->list, v, link);
 177        }
 178        v->dtls = dtls;
 179}
 180
 181static inline void __destroy_dtls(dtls_data_t *dtls_data)
 182{
 183        struct dtls_value *v, *n;
 184        dtls_key_t key;
 185        const void *dtls;
 186
 187        v = TAILQ_FIRST(&dtls_data->list);
 188        while (v != NULL) {
 189                key = v->key;
 190                /* The dtor must be called outside of a spinlock so that it can
 191                 * call code that may deschedule it for a while (i.e. a mutex).
 192                 * Probably a good idea anyway since it can be arbitrarily long
 193                 * and is written by the user. Note, there is a small race here
 194                 * on the valid field, whereby we may run a destructor on an
 195                 * invalid key. At least the keys memory wont be deleted though,
 196                 * as protected by the ref count. Any reasonable usage of this
 197                 * interface should safeguard that a key is never destroyed
 198                 * before all of the threads that use it have exited anyway. */
 199                if (key->valid && key->dtor) {
 200                        dtls = v->dtls;
 201                        v->dtls = NULL;
 202                        key->dtor((void*)dtls);
 203                }
 204                n = TAILQ_NEXT(v, link);
 205                TAILQ_REMOVE(&dtls_data->list, v, link);
 206                /* Free both the key (which is v->key) and v *after* removing v
 207                 * from the list.  It's possible that free() will call back into
 208                 * the DTLS (e.g.  pthread_getspecific()), and v must be off the
 209                 * list by then.
 210                 *
 211                 * For a similar, hilarious bug in glibc, check out:
 212                 * https://sourceware.org/bugzilla/show_bug.cgi?id=3317 */
 213                __maybe_free_dtls_key(key);
 214                __free_dtls_value(v);
 215                v = n;
 216        }
 217}
 218
 219void set_dtls(dtls_key_t key, const void *dtls)
 220{
 221        bool initialized = true;
 222        dtls_data_t *dtls_data = NULL;
 223
 224        if (!__dtls_initialized) {
 225                initialized = false;
 226                __dtls_initialized = true;
 227        }
 228        dtls_data = &__dtls_data;
 229        if (!initialized)
 230                TAILQ_INIT(&dtls_data->list);
 231        __set_dtls(dtls_data, key, dtls);
 232}
 233
 234void *get_dtls(dtls_key_t key)
 235{
 236        dtls_data_t *dtls_data = NULL;
 237        struct dtls_value *v;
 238
 239        if (!__dtls_initialized)
 240                return NULL;
 241        dtls_data = &__dtls_data;
 242        v = __get_dtls(dtls_data, key);
 243        return v ? (void*)v->dtls : NULL;
 244}
 245
 246void destroy_dtls(void)
 247{
 248        dtls_data_t *dtls_data = NULL;
 249
 250        if (!__dtls_initialized)
 251                return;
 252        dtls_data = &__dtls_data;
 253        __destroy_dtls(dtls_data);
 254}
 255