NAME
psref —
passive references
SYNOPSIS
#include <sys/psref.h>
struct psref_class *
psref_class_create(
const
char *name,
int ipl);
void
psref_class_destroy(
struct
psref_class *class);
void
psref_target_init(
struct
psref_target *target,
struct psref_class *class);
void
psref_target_destroy(
struct
psref_target *target,
struct psref_class *class);
void
psref_acquire(
struct
psref *ref,
const struct
psref_target *target,
struct psref_class *class);
void
psref_release(
struct
psref *ref,
const struct
psref_target *target,
struct psref_class *class);
void
psref_copy(
struct
psref *pto,
const struct
psref *pfrom,
struct
psref_class *class);
#ifdef DIAGNOSTIC
bool
psref_held(
const
struct psref_target *target,
struct psref_class *class);
#endif
DESCRIPTION
The
psref abstraction allows CPUs to cheaply acquire and
release
passive references to a resource, which guarantee
the resource will not be destroyed until the reference is released. Acquiring
and releasing passive references requires no interprocessor synchronization,
except when the resource is pending destruction.
Passive references are an intermediate between
pserialize(9) and reference
counting:
- pserialize(9)
read sections require no interprocessor synchronization, but must be of
short duration, and may not sleep. A
pserialize(9) read
section blocks soft interrupts on the local CPU until it is complete.
- Reference counting requires interprocessor synchronization
via atomic_ops(3) or
mutex(9). However, with
reference counting, a reference may be held for arbitrary durations, may
be transferred between owners across CPUs and threads, and may be held by
a caller that sleeps.
Passive references share some properties of both: passive references avoid
interprocessor synchronization, and do not block soft interrupts, but can be
held by a caller that sleeps. However, a caller holding a passive reference
may not transfer it from one LWP to another, and the caller's LWP must be
bound to a single CPU while it holds any passive references.
Thus, passive references are useful for incrementally parallelizing resources
whose operations may sleep, such as in the network stack, before
comprehensively removing sleeps from the code paths involved.
Resources to which callers may hold passive references are called
targets, and must contain an embedded
struct
psref_target object, initialized with
psref_target_init().
When a caller wants to guarantee that a resource will not be destroyed until it
is done, it must allocate storage for a
struct psref
object, find the
struct psref_target for the resource it
seeks, and use
psref_acquire() to acquire a passive
reference. When a caller is done with the resource, it must release the
resource with
psref_release().
When a resource is about to go away, its passive reference target must be passed
to
psref_target_destroy() to wait until all extant passive
references are released; then the resource itself may be freed.
struct psref_target and
struct psref
objects must be allocated by the caller, but they should be treated as opaque
and should not be inspected or copied.
Passive reference targets are grouped into
classes,
represented by an opaque
struct psref_class object, e.g.
the class of all network routes, or the class of all file systems mount
points, which may be needed at different interrupt priority levels.
FUNCTIONS
-
-
- psref_class_create(name,
ipl)
- Create a passive reference class with the given name and
interrupt priority level, and return an opaque pointer describing it. The
name must be at most eight characters long, and will be shown in utilities
such as ps(1) for threads that
are waiting to destroy passive reference targets. On failure, return
NULL
instead.
-
-
- psref_class_destroy(class)
- Destroy a passive reference class created with
psref_class_create(). There must be no more passive
references in this class.
-
-
- psref_target_init(target,
class)
- Initialize a passive reference target in a
struct psref_target object allocated by the caller
in the given class.
The caller must issue a
membar_producer(3)
after calling psref_target_init() and before publishing
a pointer to the target so that other CPUs can see it, e.g. by inserting
it into a pslist(9).
-
-
- psref_target_destroy(target,
class)
- Wait for all extant passive references to
target on all CPUs to be released, and then destroy
it. The passive reference target target must have
been initialized with psref_target_init() in the same
class. May sleep.
The caller must guarantee that no new references to
target will be acquired once it calls
psref_target_destroy(), e.g. by removing the target from
a pslist(9) and calling
pserialize_perform(9)
to wait for
pserialize(9) readers to
complete.
No further use of the target is allowed unless it is reinitialized with
psref_target_init(). Multiple concurrent calls to
psref_target_destroy() are not allowed.
-
-
- psref_acquire(ref,
target, class)
- Acquire a passive reference to
target, storing per-CPU bookkeeping in
ref. The class of target must
be class.
The caller must ensure by some other mechanism than passive references that
the target will not be destroyed before the call to
psref_acquire(); typically this will be via a
pserialize(9) read
section.
The caller's LWP must be bound to a CPU.
-
-
- psref_release(ref,
target, class)
- Release the passive reference ref,
which must have been acquired to point at target in
the class class, waking a thread calling
psref_target_destroy() if any.
Further use of the resource represented by target is
not allowed, unless it is re-acquired in the same way that it was
originally acquired.
-
-
- psref_copy(pto,
pfrom, class)
- Copy the passive reference pfrom to
pto, which must be to a target in
class. The resource represented by the target of the
passive references will not be destroyed before both references are
released.
-
-
- psref_held(target,
class)
- Return true if the current CPU holds a passive reference to
target in the passive reference class
class, or false if not.
This does not answer about other CPUs — it does not tell you whether
any CPU holds a passive reference to
target.
This may be used only in assertions, e.g. with
KASSERT(9), not for making
run-time decisions. This should be used only for positive assertions, as
in
KASSERT(psref_held(
target,
class))
, not for negative
assertions, as in
KASSERT(!psref_held(
target,
class))
, unless you are sure
you can prove that no caller holds a reference either.
EXAMPLES
struct frotz {
int f_key;
...
struct pslist_entry f_entry;
struct psref_target f_target;
};
static struct {
kmutex_t lock;
struct pslist_head list;
} frobbotzim __cacheline_aligned;
static pserialize_t frobbotzim_psz __read_mostly;
static struct psref_class *frobbotzim_prc __read_mostly;
void
publish_as_frotz(uint64_t key, ...)
{
struct frotz *f;
f = kmem_alloc(sizeof(*f), KM_SLEEP);
f->f_key = key;
f->f_... = ...;
PSLIST_ENTRY_INIT(f, f_entry);
psref_target_init(&f->f_target, frobbotzim_prc);
mutex_enter(&frobbotzim.lock);
PSLIST_WRITER_INSERT_HEAD(&frobbotzim.list, f, f_entry);
mutex_exit(&frobbotzim.lock);
}
int
use_frotz(int key, int op)
{
struct frotz *f;
struct psref ref;
/* Acquire a passive reference. */
if ((f = lookup_frotz(key, &ref)) == NULL)
return ENOENT;
/* Do something that may sleep. */
do_stuff_with_frotz(f, op);
/* Release passive reference, possibly waking destroy_frotz. */
psref_release(&ref, &f->f_psref, frobbotzim_prc);
return 0;
}
struct frotz *
lookup_frotz(int key, struct psref *ref)
{
struct frotz *f;
int s;
/* Look up a frotz in a pserialized list. */
s = pserialize_read_enter();
PSLIST_READER_FOREACH(f, &frobbotzim.list, struct frotz, f_next) {
/* f is stable until pserialize_read_exit. */
if (f->f_key == key) {
/* Acquire a passive reference. */
psref_acquire(ref, &f->f_target, frobbotzim_prc);
/* f is now stable until psref_release. */
break;
}
}
pserialize_read_exit(s);
return f;
}
void
destroy_frotz(int key)
{
struct frotz *f;
/* Look up and delete a frotz. */
mutex_enter(&frobbotzim.lock);
PSLIST_WRITER_FOREACH(f, &frobbotzim.list, struct frotz, f_entry) {
if (f->f_key == key) {
/*
* Unlink the frotz from the list to stop new
* pserialize read sections from seeing it.
*/
PSLIST_WRITER_REMOVE(f, f_entry);
/*
* Wait until extant pserialize read sections
* have completed.
*/
pserialize_perform(frobbotzim_psz);
break;
}
}
mutex_exit(&frobbotzim.lock);
if (f != NULL) {
/* Wait for all readers to drain before freeing. */
psref_target_destroy(&f->f_target, frobbotzim_prc);
PSLIST_ENTRY_DESTROY(f, f_entry);
kmem_free(f, sizeof(*f));
}
}
CODE REFERENCES
The
psref abstraction is implemented in
sys/kern/subr_psref.c.
SEE ALSO
pserialize(9),
pslist(9)
HISTORY
The
psref data structure first appeared in
NetBSD 8.0.
AUTHORS
Taylor R Campbell
<
riastradh@NetBSD.org>