summaryrefslogtreecommitdiff
path: root/kern/sref.c
diff options
context:
space:
mode:
Diffstat (limited to 'kern/sref.c')
-rw-r--r--kern/sref.c757
1 files changed, 353 insertions, 404 deletions
diff --git a/kern/sref.c b/kern/sref.c
index 589b00c1..31bfa0d0 100644
--- a/kern/sref.c
+++ b/kern/sref.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014-2018 Richard Braun.
+ * Copyright (c) 2014-2019 Richard Braun.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,33 +22,24 @@
* outlined below.
*
* Refcache flushes delta caches directly from an interrupt handler, and
- * disables interrupts and preemption on cache access. That behaviour is
+ * disables interrupts and preemption on cache access. That behavior is
* realtime-unfriendly because of the potentially large number of deltas
* in a cache. This module uses dedicated manager threads to perform
- * cache flushes and review queue processing, and only disables preemption
- * on individual delta access.
- *
- * In addition, Refcache normally requires all processors to regularly
- * process their local data. That behaviour is dyntick-unfriendly. As a
- * result, this module handles processor registration so that processors
- * that aren't participating in reference counting (e.g. because they're
- * idling) don't prevent others from progressing. Instead of per-processor
- * review queues, there is one global review queue which can be managed
- * from any processor. Review queue access should still be considerably
- * infrequent in practice, keeping the impact on contention low.
+ * cache flushes and queue reviews, and only disables preemption on
+ * individual delta access.
*
* Locking protocol : cache -> counter -> global data
- *
- * TODO Reconsider whether it's possible to bring back local review queues.
*/
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <stdio.h>
-#include <kern/condition.h>
+#include <kern/atomic.h>
+#include <kern/clock.h>
#include <kern/cpumap.h>
#include <kern/init.h>
#include <kern/list.h>
@@ -62,54 +53,74 @@
#include <kern/sref_i.h>
#include <kern/syscnt.h>
#include <kern/thread.h>
+#include <kern/timer.h>
#include <machine/cpu.h>
/*
- * Maximum number of deltas per cache.
+ * Delay (in milliseconds) until a new global epoch starts.
+ */
+#define SREF_EPOCH_START_DELAY 10
+
+/*
+ * Per-cache delta table size.
*/
-#define SREF_MAX_DELTAS 4096
+#define SREF_CACHE_DELTA_TABLE_SIZE 4096
+
+#if !ISP2(SREF_CACHE_DELTA_TABLE_SIZE)
+#error "delta table size must be a power-of-two"
+#endif
#ifdef __LP64__
#define SREF_HASH_SHIFT 3
-#else /* __LP64__ */
+#else
#define SREF_HASH_SHIFT 2
-#endif /* __LP64__ */
+#endif
+
+/*
+ * Negative close to 0 so that an overflow occurs early.
+ */
+#define SREF_EPOCH_ID_INIT_VALUE ((unsigned int)-500)
/*
* Number of counters in review queue beyond which to issue a warning.
*/
#define SREF_NR_COUNTERS_WARN 10000
-struct sref_queue {
- struct slist counters;
- unsigned long size;
-};
-
/*
* Global data.
*
- * If there is a pending flush, its associated CPU must be registered.
- * Notwithstanding transient states, the number of pending flushes is 0
- * if and only if no processor is registered, in which case the sref
- * module, and probably the whole system, is completely idle.
+ * Processors regularly check the global epoch ID against their own,
+ * locally cached epoch ID. If they're the same, a processor flushes
+ * its cached deltas, acknowledges its flush by decrementing the number
+ * of pending acknowledgment counter, and increments its local epoch ID,
+ * preventing additional flushes during the same epoch.
+ *
+ * The last processor to acknowledge arms a timer to schedule the start
+ * of the next epoch.
+ *
+ * The epoch ID and the pending acknowledgments counter fill an entire
+ * cache line each in order to avoid false sharing on SMP. Whenever
+ * multiple processors may access them, they must use atomic operations
+ * to avoid data races.
*
- * The review queue is implemented with two queues of counters, one for
- * each of the last two epochs. The current queue ID is updated when a
- * new epoch starts, and the queues are flipped.
+ * Atomic operations on the pending acknowledgments counter are done
+ * with acquire-release ordering to enforce the memory ordering
+ * guarantees required by both the implementation and the interface.
*/
struct sref_data {
- struct spinlock lock;
- struct cpumap registered_cpus; /* TODO Review usage */
- unsigned int nr_registered_cpus;
- struct cpumap pending_flushes; /* TODO Review usage */
- unsigned int nr_pending_flushes;
- unsigned int current_queue_id;
- struct sref_queue queues[2];
+ struct {
+ alignas(CPU_L1_SIZE) unsigned int epoch_id;
+ };
+
+ struct {
+ alignas(CPU_L1_SIZE) unsigned int nr_pending_acks;
+ };
+
+ struct timer timer;
struct syscnt sc_epochs;
struct syscnt sc_dirty_zeroes;
- struct syscnt sc_revives;
struct syscnt sc_true_zeroes;
- bool no_warning;
+ struct syscnt sc_revives;
};
/*
@@ -130,95 +141,113 @@ struct sref_delta {
unsigned long value;
};
+struct sref_queue {
+ struct slist counters;
+ unsigned long size;
+};
+
/*
* Per-processor cache of deltas.
*
+ * A cache is dirty if there is at least one delta that requires flushing.
+ * It may only be flushed once per epoch.
+ *
* Delta caches are implemented with hash tables for quick ref count to
* delta lookups. For now, a very simple replacement policy, similar to
* that described in the RadixVM paper, is used. Improve with an LRU-like
* algorithm if this turns out to be a problem.
*
- * Manager threads periodically flush deltas and process the review queue.
- * Waking up a manager thread must be done with interrupts disabled to
- * prevent a race with the periodic event that drives regular flushes
- * (normally the periodic timer interrupt).
+ * Periodic events (normally the system timer tick) trigger cache checks.
+ * A cache check may wake up the manager thread if the cache needs management,
+ * i.e. if it's dirty or if there are counters to review. Otherwise, the
+ * flush acknowledgment is done directly to avoid the cost of a thread
+ * wake-up.
*
* Interrupts and preemption must be disabled when accessing a delta cache.
*/
struct sref_cache {
- struct sref_delta deltas[SREF_MAX_DELTAS];
+ struct sref_data *data;
+ bool dirty;
+ bool flushed;
+ unsigned int epoch_id;
+ struct sref_delta deltas[SREF_CACHE_DELTA_TABLE_SIZE];
struct list valid_deltas;
+ struct sref_queue queues[2];
+ struct thread *manager;
struct syscnt sc_collisions;
struct syscnt sc_flushes;
- struct thread *manager;
- bool registered;
- bool dirty;
};
static struct sref_data sref_data;
static struct sref_cache sref_cache __percpu;
-static struct sref_queue *
-sref_prev_queue(void)
+static unsigned int
+sref_data_get_epoch_id(const struct sref_data *data)
{
- return &sref_data.queues[!sref_data.current_queue_id];
+ return data->epoch_id;
}
-static struct sref_queue *
-sref_current_queue(void)
+static bool
+sref_data_check_epoch_id(const struct sref_data *data, unsigned int epoch_id)
{
- return &sref_data.queues[sref_data.current_queue_id];
-}
+ unsigned int global_epoch_id;
-static void __init
-sref_queue_init(struct sref_queue *queue)
-{
- slist_init(&queue->counters);
- queue->size = 0;
-}
+ global_epoch_id = atomic_load(&data->epoch_id, ATOMIC_RELAXED);
-static unsigned long
-sref_queue_size(const struct sref_queue *queue)
-{
- return queue->size;
-}
+ if (unlikely(global_epoch_id == epoch_id)) {
+ atomic_fence(ATOMIC_ACQUIRE);
+ return true;
+ }
-static bool
-sref_queue_empty(const struct sref_queue *queue)
-{
- return queue->size == 0;
+ return false;
}
static void
-sref_queue_push(struct sref_queue *queue, struct sref_counter *counter)
+sref_data_start_epoch(struct timer *timer)
{
- slist_insert_tail(&queue->counters, &counter->node);
- queue->size++;
+ struct sref_data *data;
+ unsigned int epoch_id;
+
+ data = structof(timer, struct sref_data, timer);
+ assert(data->nr_pending_acks == 0);
+ data->nr_pending_acks = cpu_count();
+
+ epoch_id = atomic_load(&data->epoch_id, ATOMIC_RELAXED);
+ atomic_store(&data->epoch_id, epoch_id + 1, ATOMIC_RELEASE);
}
-static struct sref_counter *
-sref_queue_pop(struct sref_queue *queue)
+static void
+sref_data_schedule_timer(struct sref_data *data)
{
- struct sref_counter *counter;
+ uint64_t ticks;
- counter = slist_first_entry(&queue->counters, typeof(*counter), node);
- slist_remove(&queue->counters, NULL);
- queue->size--;
- return counter;
+ ticks = clock_ticks_from_ms(SREF_EPOCH_START_DELAY);
+ timer_schedule(&data->timer, clock_get_time() + ticks);
}
static void
-sref_queue_transfer(struct sref_queue *dest, struct sref_queue *src)
+sref_data_ack_cpu(struct sref_data *data)
{
- slist_set_head(&dest->counters, &src->counters);
- dest->size = src->size;
+ unsigned int prev;
+
+ prev = atomic_fetch_sub(&data->nr_pending_acks, 1, ATOMIC_ACQ_REL);
+
+ if (prev != 1) {
+ assert(prev != 0);
+ return;
+ }
+
+ syscnt_inc(&data->sc_epochs);
+ sref_data_schedule_timer(data);
}
static void
-sref_queue_concat(struct sref_queue *queue1, struct sref_queue *queue2)
+sref_data_update_stats(struct sref_data *data, int64_t nr_dirty_zeroes,
+ int64_t nr_true_zeroes, int64_t nr_revives)
{
- slist_concat(&queue1->counters, &queue2->counters);
- queue1->size += queue2->size;
+ syscnt_add(&data->sc_dirty_zeroes, nr_dirty_zeroes);
+ syscnt_add(&data->sc_true_zeroes, nr_true_zeroes);
+ syscnt_add(&data->sc_revives, nr_revives);
}
static bool
@@ -287,12 +316,6 @@ sref_counter_hash(const struct sref_counter *counter)
return va >> SREF_HASH_SHIFT;
}
-static uintptr_t
-sref_counter_index(const struct sref_counter *counter)
-{
- return sref_counter_hash(counter) & (SREF_MAX_DELTAS - 1);
-}
-
static bool
sref_counter_is_queued(const struct sref_counter *counter)
{
@@ -359,8 +382,66 @@ sref_counter_kill_weakref(struct sref_counter *counter)
return sref_weakref_kill(counter->weakref);
}
+static void __init
+sref_queue_init(struct sref_queue *queue)
+{
+ slist_init(&queue->counters);
+ queue->size = 0;
+}
+
+static bool
+sref_queue_empty(const struct sref_queue *queue)
+{
+ return queue->size == 0;
+}
+
+static void
+sref_queue_push(struct sref_queue *queue, struct sref_counter *counter)
+{
+ slist_insert_tail(&queue->counters, &counter->node);
+ queue->size++;
+}
+
+static struct sref_counter *
+sref_queue_pop(struct sref_queue *queue)
+{
+ struct sref_counter *counter;
+
+ counter = slist_first_entry(&queue->counters, typeof(*counter), node);
+ slist_remove(&queue->counters, NULL);
+ queue->size--;
+ return counter;
+}
+
+static void
+sref_queue_move(struct sref_queue *dest, const struct sref_queue *src)
+{
+ slist_set_head(&dest->counters, &src->counters);
+ dest->size = src->size;
+}
+
+static struct sref_queue *
+sref_cache_get_queue(struct sref_cache *cache, size_t index)
+{
+ assert(index < ARRAY_SIZE(cache->queues));
+ return &cache->queues[index];
+}
+
+static struct sref_queue *
+sref_cache_get_prev_queue(struct sref_cache *cache)
+{
+ return sref_cache_get_queue(cache, (cache->epoch_id - 1) & 1);
+}
+
+static struct sref_queue *
+sref_cache_get_current_queue(struct sref_cache *cache)
+{
+ return sref_cache_get_queue(cache, cache->epoch_id & 1);
+}
+
static void
-sref_counter_schedule_review(struct sref_counter *counter)
+sref_cache_schedule_review(struct sref_cache *cache,
+ struct sref_counter *counter)
{
assert(!sref_counter_is_queued(counter));
assert(!sref_counter_is_dirty(counter));
@@ -368,13 +449,12 @@ sref_counter_schedule_review(struct sref_counter *counter)
sref_counter_mark_queued(counter);
sref_counter_mark_dying(counter);
- spinlock_lock(&sref_data.lock);
- sref_queue_push(sref_current_queue(), counter);
- spinlock_unlock(&sref_data.lock);
+ sref_queue_push(sref_cache_get_current_queue(cache), counter);
}
static void
-sref_counter_add(struct sref_counter *counter, unsigned long delta)
+sref_counter_add(struct sref_counter *counter, unsigned long delta,
+ struct sref_cache *cache)
{
assert(!cpu_intr_enabled());
@@ -386,13 +466,22 @@ sref_counter_add(struct sref_counter *counter, unsigned long delta)
if (sref_counter_is_queued(counter)) {
sref_counter_mark_dirty(counter);
} else {
- sref_counter_schedule_review(counter);
+ sref_cache_schedule_review(cache, counter);
}
}
spinlock_unlock(&counter->lock);
}
+static void
+sref_counter_noref(struct work *work)
+{
+ struct sref_counter *counter;
+
+ counter = structof(work, struct sref_counter, work);
+ counter->noref_fn(counter);
+}
+
static void __init
sref_delta_init(struct sref_delta *delta)
{
@@ -439,112 +528,44 @@ sref_delta_is_valid(const struct sref_delta *delta)
}
static void
-sref_delta_flush(struct sref_delta *delta)
+sref_delta_flush(struct sref_delta *delta, struct sref_cache *cache)
{
- sref_counter_add(delta->counter, delta->value);
+ sref_counter_add(delta->counter, delta->value, cache);
delta->value = 0;
}
static void
-sref_delta_evict(struct sref_delta *delta)
+sref_delta_evict(struct sref_delta *delta, struct sref_cache *cache)
{
- sref_delta_flush(delta);
+ sref_delta_flush(delta, cache);
sref_delta_clear(delta);
}
-static unsigned long
-sref_review_queue_size(void)
-{
- return sref_queue_size(&sref_data.queues[0])
- + sref_queue_size(&sref_data.queues[1]);
-}
-
-static bool
-sref_review_queue_empty(void)
+static struct sref_cache *
+sref_get_local_cache(void)
{
- return sref_review_queue_size() == 0;
+ return cpu_local_ptr(sref_cache);
}
-static void
-sref_reset_pending_flushes(void)
+static uintptr_t
+sref_cache_compute_counter_index(const struct sref_cache *cache,
+ const struct sref_counter *counter)
{
- cpumap_copy(&sref_data.pending_flushes, &sref_data.registered_cpus);
- sref_data.nr_pending_flushes = sref_data.nr_registered_cpus;
-}
-
-static void
-sref_end_epoch(struct sref_queue *queue)
-{
- struct sref_queue *prev_queue, *current_queue;
-
- assert(cpumap_find_first(&sref_data.registered_cpus) != -1);
- assert(sref_data.nr_registered_cpus != 0);
- assert(cpumap_find_first(&sref_data.pending_flushes) == -1);
- assert(sref_data.nr_pending_flushes == 0);
-
- if (!sref_data.no_warning
- && (sref_review_queue_size() >= SREF_NR_COUNTERS_WARN)) {
- sref_data.no_warning = 1;
- log_warning("sref: large number of counters in review queue");
- }
-
- prev_queue = sref_prev_queue();
- current_queue = sref_current_queue();
-
- if (sref_data.nr_registered_cpus == 1) {
- sref_queue_concat(prev_queue, current_queue);
- sref_queue_init(current_queue);
- }
-
- sref_queue_transfer(queue, prev_queue);
- sref_queue_init(prev_queue);
- sref_data.current_queue_id = !sref_data.current_queue_id;
- syscnt_inc(&sref_data.sc_epochs);
- sref_reset_pending_flushes();
+ return sref_counter_hash(counter) & (ARRAY_SIZE(cache->deltas) - 1);
}
static struct sref_delta *
-sref_cache_delta(struct sref_cache *cache, size_t i)
-{
- assert(i < ARRAY_SIZE(cache->deltas));
- return &cache->deltas[i];
-}
-
-static void __init
-sref_cache_init(struct sref_cache *cache, unsigned int cpu)
+sref_cache_get_delta(struct sref_cache *cache, size_t index)
{
- char name[SYSCNT_NAME_SIZE];
- struct sref_delta *delta;
-
- for (size_t i = 0; i < ARRAY_SIZE(cache->deltas); i++) {
- delta = sref_cache_delta(cache, i);
- sref_delta_init(delta);
- }
-
- list_init(&cache->valid_deltas);
- snprintf(name, sizeof(name), "sref_collisions/%u", cpu);
- syscnt_register(&cache->sc_collisions, name);
- snprintf(name, sizeof(name), "sref_flushes/%u", cpu);
- syscnt_register(&cache->sc_flushes, name);
- cache->manager = NULL;
- cache->registered = false;
- cache->dirty = false;
-}
-
-static struct sref_cache *
-sref_cache_get(void)
-{
- return cpu_local_ptr(sref_cache);
+ assert(index < ARRAY_SIZE(cache->deltas));
+ return &cache->deltas[index];
}
static struct sref_cache *
sref_cache_acquire(unsigned long *flags)
{
- struct sref_cache *cache;
-
thread_preempt_disable_intr_save(flags);
- cache = sref_cache_get();
- return cache;
+ return sref_get_local_cache();
}
static void
@@ -554,39 +575,39 @@ sref_cache_release(unsigned long flags)
}
static bool
-sref_cache_is_registered(const struct sref_cache *cache)
+sref_cache_is_dirty(const struct sref_cache *cache)
{
- return cache->registered;
+ return cache->dirty;
}
static void
-sref_cache_mark_registered(struct sref_cache *cache)
+sref_cache_set_dirty(struct sref_cache *cache)
{
- cache->registered = true;
+ cache->dirty = true;
}
static void
-sref_cache_clear_registered(struct sref_cache *cache)
+sref_cache_clear_dirty(struct sref_cache *cache)
{
- cache->registered = false;
+ cache->dirty = false;
}
static bool
-sref_cache_is_dirty(const struct sref_cache *cache)
+sref_cache_is_flushed(const struct sref_cache *cache)
{
- return cache->dirty;
+ return cache->flushed;
}
static void
-sref_cache_mark_dirty(struct sref_cache *cache)
+sref_cache_set_flushed(struct sref_cache *cache)
{
- cache->dirty = true;
+ cache->flushed = true;
}
static void
-sref_cache_clear_dirty(struct sref_cache *cache)
+sref_cache_clear_flushed(struct sref_cache *cache)
{
- cache->dirty = false;
+ cache->flushed = false;
}
static void
@@ -601,25 +622,27 @@ sref_cache_add_delta(struct sref_cache *cache, struct sref_delta *delta,
}
static void
-sref_cache_remove_delta(struct sref_delta *delta)
+sref_cache_remove_delta(struct sref_cache *cache, struct sref_delta *delta)
{
assert(sref_delta_is_valid(delta));
- sref_delta_evict(delta);
+ sref_delta_evict(delta, cache);
list_remove(&delta->node);
}
static struct sref_delta *
-sref_cache_get_delta(struct sref_cache *cache, struct sref_counter *counter)
+sref_cache_take_delta(struct sref_cache *cache, struct sref_counter *counter)
{
struct sref_delta *delta;
+ size_t index;
- delta = sref_cache_delta(cache, sref_counter_index(counter));
+ index = sref_cache_compute_counter_index(cache, counter);
+ delta = sref_cache_get_delta(cache, index);
if (!sref_delta_is_valid(delta)) {
sref_cache_add_delta(cache, delta, counter);
} else if (sref_delta_counter(delta) != counter) {
- sref_cache_remove_delta(delta);
+ sref_cache_remove_delta(cache, delta);
sref_cache_add_delta(cache, delta, counter);
syscnt_inc(&cache->sc_collisions);
}
@@ -627,14 +650,36 @@ sref_cache_get_delta(struct sref_cache *cache, struct sref_counter *counter)
return delta;
}
+static bool
+sref_cache_needs_management(struct sref_cache *cache)
+{
+ const struct sref_queue *queue;
+
+ assert(!cpu_intr_enabled());
+ assert(!thread_preempt_enabled());
+
+ queue = sref_cache_get_prev_queue(cache);
+ return sref_cache_is_dirty(cache) || !sref_queue_empty(queue);
+}
+
+static void
+sref_cache_end_epoch(struct sref_cache *cache)
+{
+ assert(!sref_cache_needs_management(cache));
+
+ sref_data_ack_cpu(cache->data);
+ cache->epoch_id++;
+}
+
static void
sref_cache_flush(struct sref_cache *cache, struct sref_queue *queue)
{
- struct sref_delta *delta;
+ struct sref_queue *prev_queue;
unsigned long flags;
- unsigned int cpu;
for (;;) {
+ struct sref_delta *delta;
+
thread_preempt_disable_intr_save(&flags);
if (list_empty(&cache->valid_deltas)) {
@@ -642,84 +687,38 @@ sref_cache_flush(struct sref_cache *cache, struct sref_queue *queue)
}
delta = list_first_entry(&cache->valid_deltas, typeof(*delta), node);
- sref_cache_remove_delta(delta);
+ sref_cache_remove_delta(cache, delta);
thread_preempt_enable_intr_restore(flags);
}
- cpu_intr_restore(flags);
-
- cpu = cpu_id();
-
- spinlock_lock(&sref_data.lock);
-
- assert(sref_cache_is_registered(cache));
- assert(cpumap_test(&sref_data.registered_cpus, cpu));
-
- if (!cpumap_test(&sref_data.pending_flushes, cpu)) {
- sref_queue_init(queue);
- } else {
- cpumap_clear(&sref_data.pending_flushes, cpu);
- sref_data.nr_pending_flushes--;
-
- if (sref_data.nr_pending_flushes != 0) {
- sref_queue_init(queue);
- } else {
- sref_end_epoch(queue);
- }
- }
-
- spinlock_unlock(&sref_data.lock);
-
sref_cache_clear_dirty(cache);
- syscnt_inc(&cache->sc_flushes);
-
- thread_preempt_enable();
-}
-
-static void
-sref_cache_manage(struct sref_cache *cache)
-{
- assert(!cpu_intr_enabled());
- assert(!thread_preempt_enabled());
-
- sref_cache_mark_dirty(cache);
- thread_wakeup(cache->manager);
-}
+ sref_cache_set_flushed(cache);
-static bool
-sref_cache_check(struct sref_cache *cache)
-{
- if (!sref_cache_is_dirty(cache)) {
- return false;
- }
+ prev_queue = sref_cache_get_prev_queue(cache);
+ sref_queue_move(queue, prev_queue);
+ sref_queue_init(prev_queue);
- sref_cache_manage(cache);
- return true;
-}
+ sref_cache_end_epoch(cache);
-static void
-sref_noref(struct work *work)
-{
- struct sref_counter *counter;
+ thread_preempt_enable_intr_restore(flags);
- counter = structof(work, struct sref_counter, work);
- counter->noref_fn(counter);
+ syscnt_inc(&cache->sc_flushes);
}
static void
-sref_review(struct sref_queue *queue)
+sref_queue_review(struct sref_queue *queue, struct sref_cache *cache)
{
- int64_t nr_dirty, nr_revive, nr_true;
+ int64_t nr_dirty_zeroes, nr_true_zeroes, nr_revives;
struct sref_counter *counter;
struct work_queue works;
unsigned long flags;
bool requeue;
int error;
- nr_dirty = 0;
- nr_revive = 0;
- nr_true = 0;
+ nr_dirty_zeroes = 0;
+ nr_true_zeroes = 0;
+ nr_revives = 0;
work_queue_init(&works);
while (!sref_queue_empty(queue)) {
@@ -737,7 +736,7 @@ sref_review(struct sref_queue *queue)
} else {
if (sref_counter_is_dirty(counter)) {
requeue = true;
- nr_dirty++;
+ nr_dirty_zeroes++;
sref_counter_clear_dirty(counter);
} else {
error = sref_counter_kill_weakref(counter);
@@ -746,12 +745,12 @@ sref_review(struct sref_queue *queue)
requeue = false;
} else {
requeue = true;
- nr_revive++;
+ nr_revives++;
}
}
if (requeue) {
- sref_counter_schedule_review(counter);
+ sref_cache_schedule_review(cache, counter);
spinlock_unlock_intr_restore(&counter->lock, flags);
} else {
/*
@@ -760,8 +759,8 @@ sref_review(struct sref_queue *queue)
* counter is now really at 0, but do it for consistency.
*/
spinlock_unlock_intr_restore(&counter->lock, flags);
- nr_true++;
- work_init(&counter->work, sref_noref);
+ nr_true_zeroes++;
+ work_init(&counter->work, sref_counter_noref);
work_queue_push(&works, &counter->work);
}
}
@@ -771,17 +770,12 @@ sref_review(struct sref_queue *queue)
work_queue_schedule(&works, 0);
}
- if ((nr_dirty + nr_revive + nr_true) != 0) {
- spinlock_lock(&sref_data.lock);
- syscnt_add(&sref_data.sc_dirty_zeroes, nr_dirty);
- syscnt_add(&sref_data.sc_revives, nr_revive);
- syscnt_add(&sref_data.sc_true_zeroes, nr_true);
- spinlock_unlock(&sref_data.lock);
- }
+ sref_data_update_stats(cache->data, nr_dirty_zeroes,
+ nr_true_zeroes, nr_revives);
}
static void
-sref_manage(void *arg)
+sref_cache_manage(void *arg)
{
struct sref_cache *cache;
struct sref_queue queue;
@@ -789,50 +783,75 @@ sref_manage(void *arg)
cache = arg;
+ thread_preempt_disable_intr_save(&flags);
+
for (;;) {
- thread_preempt_disable_intr_save(&flags);
- while (!sref_cache_is_dirty(cache)) {
+ while (sref_cache_is_flushed(cache)) {
thread_sleep(NULL, cache, "sref");
}
thread_preempt_enable_intr_restore(flags);
sref_cache_flush(cache, &queue);
- sref_review(&queue);
+ sref_queue_review(&queue, cache);
+
+ thread_preempt_disable_intr_save(&flags);
}
/* Never reached */
}
-static int __init
-sref_bootstrap(void)
+static void
+sref_cache_check(struct sref_cache *cache)
{
- spinlock_init(&sref_data.lock);
+ bool same_epoch;
- sref_data.current_queue_id = 0;
+ same_epoch = sref_data_check_epoch_id(&sref_data, cache->epoch_id);
- for (size_t i = 0; i < ARRAY_SIZE(sref_data.queues); i++) {
- sref_queue_init(&sref_data.queues[i]);
+ if (!same_epoch) {
+ return;
}
- syscnt_register(&sref_data.sc_epochs, "sref_epochs");
- syscnt_register(&sref_data.sc_dirty_zeroes, "sref_dirty_zeroes");
- syscnt_register(&sref_data.sc_revives, "sref_revives");
- syscnt_register(&sref_data.sc_true_zeroes, "sref_true_zeroes");
-
- sref_cache_init(sref_cache_get(), 0);
+ if (!sref_cache_needs_management(cache)) {
+ sref_cache_end_epoch(cache);
+ return;
+ }
- return 0;
+ sref_cache_clear_flushed(cache);
+ thread_wakeup(cache->manager);
}
-INIT_OP_DEFINE(sref_bootstrap,
- INIT_OP_DEP(cpu_setup, true),
- INIT_OP_DEP(spinlock_setup, true),
- INIT_OP_DEP(syscnt_setup, true));
+static void __init
+sref_cache_init(struct sref_cache *cache, unsigned int cpu,
+ struct sref_data *data)
+{
+ char name[SYSCNT_NAME_SIZE];
+
+ cache->data = data;
+ cache->dirty = false;
+ cache->flushed = true;
+ cache->epoch_id = sref_data_get_epoch_id(&sref_data) + 1;
+
+ for (size_t i = 0; i < ARRAY_SIZE(cache->deltas); i++) {
+ sref_delta_init(sref_cache_get_delta(cache, i));
+ }
+
+ list_init(&cache->valid_deltas);
+
+ for (size_t i = 0; i < ARRAY_SIZE(cache->queues); i++) {
+ sref_queue_init(sref_cache_get_queue(cache, i));
+ }
+
+ snprintf(name, sizeof(name), "sref_collisions/%u", cpu);
+ syscnt_register(&cache->sc_collisions, name);
+ snprintf(name, sizeof(name), "sref_flushes/%u", cpu);
+ syscnt_register(&cache->sc_flushes, name);
+ cache->manager = NULL;
+}
static void __init
-sref_setup_manager(struct sref_cache *cache, unsigned int cpu)
+sref_cache_init_manager(struct sref_cache *cache, unsigned int cpu)
{
char name[THREAD_NAME_SIZE];
struct thread_attr attr;
@@ -848,11 +867,12 @@ sref_setup_manager(struct sref_cache *cache, unsigned int cpu)
cpumap_zero(cpumap);
cpumap_set(cpumap, cpu);
- snprintf(name, sizeof(name), THREAD_KERNEL_PREFIX "sref_manage/%u", cpu);
+ snprintf(name, sizeof(name), THREAD_KERNEL_PREFIX "sref_cache_manage/%u",
+ cpu);
thread_attr_init(&attr, name);
thread_attr_set_cpumap(&attr, cpumap);
thread_attr_set_priority(&attr, THREAD_SCHED_FS_PRIO_MAX);
- error = thread_create(&manager, &attr, sref_manage, cache);
+ error = thread_create(&manager, &attr, sref_cache_manage, cache);
cpumap_destroy(cpumap);
if (error) {
@@ -862,15 +882,45 @@ sref_setup_manager(struct sref_cache *cache, unsigned int cpu)
cache->manager = manager;
}
+static void __init
+sref_data_init(struct sref_data *data)
+{
+ data->epoch_id = SREF_EPOCH_ID_INIT_VALUE;
+ data->nr_pending_acks = 0;
+
+ timer_init(&data->timer, sref_data_start_epoch, TIMER_HIGH_PRIO);
+ sref_data_schedule_timer(data);
+
+ syscnt_register(&data->sc_epochs, "sref_epochs");
+ syscnt_register(&data->sc_dirty_zeroes, "sref_dirty_zeroes");
+ syscnt_register(&data->sc_true_zeroes, "sref_true_zeroes");
+ syscnt_register(&data->sc_revives, "sref_revives");
+}
+
+static int __init
+sref_bootstrap(void)
+{
+ sref_data_init(&sref_data);
+ sref_cache_init(sref_get_local_cache(), 0, &sref_data);
+ return 0;
+}
+
+INIT_OP_DEFINE(sref_bootstrap,
+ INIT_OP_DEP(cpu_setup, true),
+ INIT_OP_DEP(spinlock_setup, true),
+ INIT_OP_DEP(syscnt_setup, true),
+ INIT_OP_DEP(thread_bootstrap, true),
+ INIT_OP_DEP(timer_bootstrap, true));
+
static int __init
sref_setup(void)
{
for (unsigned int i = 1; i < cpu_count(); i++) {
- sref_cache_init(percpu_ptr(sref_cache, i), i);
+ sref_cache_init(percpu_ptr(sref_cache, i), i, &sref_data);
}
for (unsigned int i = 0; i < cpu_count(); i++) {
- sref_setup_manager(percpu_ptr(sref_cache, i), i);
+ sref_cache_init_manager(percpu_ptr(sref_cache, i), i);
}
return 0;
@@ -884,110 +934,9 @@ INIT_OP_DEFINE(sref_setup,
INIT_OP_DEP(thread_setup, true));
void
-sref_register(void)
-{
- struct sref_cache *cache;
- unsigned int cpu;
-
- assert(!thread_preempt_enabled());
-
- cache = sref_cache_get();
- assert(!sref_cache_is_registered(cache));
- assert(!sref_cache_is_dirty(cache));
-
- cpu = cpu_id();
-
- spinlock_lock(&sref_data.lock);
-
- assert(!cpumap_test(&sref_data.registered_cpus, cpu));
- cpumap_set(&sref_data.registered_cpus, cpu);
- sref_data.nr_registered_cpus++;
-
- if ((sref_data.nr_registered_cpus == 1)
- && (sref_data.nr_pending_flushes == 0)) {
- assert(sref_review_queue_empty());
- sref_reset_pending_flushes();
- }
-
- spinlock_unlock(&sref_data.lock);
-
- sref_cache_mark_registered(cache);
-}
-
-int
-sref_unregister(void)
-{
- struct sref_cache *cache;
- unsigned long flags;
- unsigned int cpu;
- bool dirty;
- int error;
-
- assert(!thread_preempt_enabled());
-
- cache = sref_cache_get();
-
- cpu_intr_save(&flags);
-
- assert(sref_cache_is_registered(cache));
- sref_cache_clear_registered(cache);
- dirty = sref_cache_check(cache);
-
- if (dirty) {
- sref_cache_mark_registered(cache);
- error = EBUSY;
- goto out;
- }
-
- cpu = cpu_id();
-
- spinlock_lock(&sref_data.lock);
-
- assert(cpumap_test(&sref_data.registered_cpus, cpu));
-
- if (!cpumap_test(&sref_data.pending_flushes, cpu)) {
- assert(sref_data.nr_pending_flushes != 0);
- error = 0;
- } else if ((sref_data.nr_registered_cpus == 1)
- && (sref_data.nr_pending_flushes == 1)
- && sref_review_queue_empty()) {
- cpumap_clear(&sref_data.pending_flushes, cpu);
- sref_data.nr_pending_flushes--;
- error = 0;
- } else {
- sref_cache_manage(cache);
- error = EBUSY;
- }
-
- if (error) {
- sref_cache_mark_registered(cache);
- } else {
- cpumap_clear(&sref_data.registered_cpus, cpu);
- sref_data.nr_registered_cpus--;
- }
-
- spinlock_unlock(&sref_data.lock);
-
-out:
- cpu_intr_restore(flags);
-
- return error;
-}
-
-void
sref_report_periodic_event(void)
{
- struct sref_cache *cache;
-
- assert(thread_check_intr_context());
-
- cache = sref_cache_get();
-
- if (!sref_cache_is_registered(cache)) {
- return;
- }
-
- sref_cache_manage(cache);
+ sref_cache_check(sref_get_local_cache());
}
void
@@ -1014,8 +963,8 @@ sref_counter_inc_common(struct sref_counter *counter, struct sref_cache *cache)
{
struct sref_delta *delta;
- sref_cache_mark_dirty(cache);
- delta = sref_cache_get_delta(cache, counter);
+ sref_cache_set_dirty(cache);
+ delta = sref_cache_take_delta(cache, counter);
sref_delta_inc(delta);
}
@@ -1038,8 +987,8 @@ sref_counter_dec(struct sref_counter *counter)
unsigned long flags;
cache = sref_cache_acquire(&flags);
- sref_cache_mark_dirty(cache);
- delta = sref_cache_get_delta(cache, counter);
+ sref_cache_set_dirty(cache);
+ delta = sref_cache_take_delta(cache, counter);
sref_delta_dec(delta);
sref_cache_release(flags);
}