blob: e8691057432724615a082b23378e919b3aad3198 [file] [log] [blame]
/* Copyright (c) 2012 The Regents of the University of California
* Kevin Klues <klueska@cs.berkeley.edu>
*
* See LICENSE for details. */
#include <parlib/assert.h>
#include <parlib/dtls.h>
#include <parlib/slab.h>
#include <parlib/spinlock.h>
#include <stddef.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 <parlib/spinlock.h>
#include <sys/queue.h>
/* Define some number of static keys, for which the memory containing the keys
* and the per-thread memory for the values associated with those keys is
* allocated statically. This is adapted from glibc's notion of the
* "specific_1stblock" field embedded directly into its pthread structure for
* pthread_get/specific() calls. */
#define NUM_STATIC_KEYS 32
/* The dynamic tls key structure */
struct dtls_key {
int id;
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;
const 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;
/* Memory to hold dtls values for the first NUM_STATIC_KEYS keys */
struct dtls_value early_values[NUM_STATIC_KEYS];
} 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;
static __thread dtls_data_t __dtls_data;
static __thread bool __dtls_initialized;
static struct dtls_key static_dtls_keys[NUM_STATIC_KEYS];
static int num_dtls_keys;
/* Initialize the slab caches for allocating dtls keys and values. */
int dtls_cache_init(void)
{
/* Make sure this only runs once */
static bool initialized;
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,
NULL);
/* Initialize the global cache of dtls_values */
__dtls_values_cache =
kmem_cache_create("dtls_values_cache", sizeof(struct dtls_value),
__alignof__(struct dtls_value), 0, NULL, NULL,
NULL);
return 0;
}
static dtls_key_t __allocate_dtls_key(void)
{
dtls_key_t key;
int keyid = __sync_fetch_and_add(&num_dtls_keys, 1);
if (keyid < NUM_STATIC_KEYS) {
key = &static_dtls_keys[keyid];
} else {
dtls_cache_init();
key = kmem_cache_alloc(__dtls_keys_cache, 0);
}
assert(key);
key->id = keyid;
key->ref_count = 1;
return key;
}
static void __maybe_free_dtls_key(dtls_key_t key)
{
int ref_count = __sync_add_and_fetch(&key->ref_count, -1);
if (ref_count == 0 && key->id >= NUM_STATIC_KEYS)
kmem_cache_free(__dtls_keys_cache, key);
}
static struct dtls_value *__allocate_dtls_value(struct dtls_data *dtls_data,
struct dtls_key *key)
{
struct dtls_value *v;
if (key->id < NUM_STATIC_KEYS)
v = &dtls_data->early_values[key->id];
else
v = kmem_cache_alloc(__dtls_values_cache, 0);
assert(v);
return v;
}
static void __free_dtls_value(struct dtls_value *v)
{
if (v->key->id >= NUM_STATIC_KEYS)
kmem_cache_free(__dtls_values_cache, v);
}
dtls_key_t dtls_key_create(dtls_dtor_t dtor)
{
dtls_key_t key = __allocate_dtls_key();
key->valid = true;
key->dtor = dtor;
return key;
}
void dtls_key_delete(dtls_key_t key)
{
assert(key);
key->valid = false;
__maybe_free_dtls_key(key);
}
static inline struct dtls_value *__get_dtls(dtls_data_t *dtls_data,
dtls_key_t key)
{
struct dtls_value *v;
assert(key);
if (key->id < NUM_STATIC_KEYS) {
v = &dtls_data->early_values[key->id];
if (v->key != NULL)
return v;
} else {
TAILQ_FOREACH(v, &dtls_data->list, link)
if (v->key == key)
return v;
}
return NULL;
}
static inline void __set_dtls(dtls_data_t *dtls_data, dtls_key_t key,
const void *dtls)
{
struct dtls_value *v;
assert(key);
v = __get_dtls(dtls_data, key);
if (!v) {
v = __allocate_dtls_value(dtls_data, key);
__sync_fetch_and_add(&key->ref_count, 1);
v->key = key;
TAILQ_INSERT_HEAD(&dtls_data->list, v, link);
}
v->dtls = dtls;
}
static inline void __destroy_dtls(dtls_data_t *dtls_data)
{
struct dtls_value *v, *n;
dtls_key_t key;
const void *dtls;
v = TAILQ_FIRST(&dtls_data->list);
while (v != NULL) {
key = v->key;
/* The dtor must be called outside of a spinlock so that it can
* 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 (key->valid && key->dtor) {
dtls = v->dtls;
v->dtls = NULL;
key->dtor((void*)dtls);
}
n = TAILQ_NEXT(v, link);
TAILQ_REMOVE(&dtls_data->list, v, link);
/* Free both the key (which is v->key) and v *after* removing v
* from the list. It's possible that free() will call back into
* the DTLS (e.g. pthread_getspecific()), and v must be off the
* list by then.
*
* For a similar, hilarious bug in glibc, check out:
* https://sourceware.org/bugzilla/show_bug.cgi?id=3317 */
__maybe_free_dtls_key(key);
__free_dtls_value(v);
v = n;
}
}
void set_dtls(dtls_key_t key, const 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;
struct dtls_value *v;
if (!__dtls_initialized)
return NULL;
dtls_data = &__dtls_data;
v = __get_dtls(dtls_data, key);
return v ? (void*)v->dtls : NULL;
}
void destroy_dtls(void)
{
dtls_data_t *dtls_data = NULL;
if (!__dtls_initialized)
return;
dtls_data = &__dtls_data;
__destroy_dtls(dtls_data);
}