PSREF(9)
okay bestie, welcome to psref(9) from the Kernel Developer's Manual β the passive references page, but make it make sense. π it's giving kernel-dev main character energy, no cap.
πΈ NAME πΈ
psref β passive references. that's the whole name section, periodt. π
β¨ SYNOPSIS β¨
here's the include and all the function shapes, exactly as the page says:
#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 lets CPUs cheaply acquire and release passive references to a resource. Those references 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. Apserialize(9)read section blocks soft interrupts on the local CPU until it is complete.- Reference counting requires interprocessor synchronization via
atomic_ops(3)ormutex(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.
okay important vibe check:struct psref_targetandstruct psrefobjects must be allocated by the caller, but they should be treated as opaque and should not be inspected or copied. don't peek, don't copy, just vibe. π
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 asps(1)for threads that are waiting to destroy passive reference targets. On failure, returnNULLinstead. πpsref_class_destroy(class)β Destroy a passive reference class created withpsref_class_create(). There must be no more passive references in this class. periodt.psref_target_init(target, class)β Initialize a passive reference target in astruct psref_targetobject allocated by the caller in the given class.
bestie, the caller must issue amembar_producer(3)after callingpsref_target_init()and before publishing a pointer to the target so that other CPUs can see it, e.g. by inserting it into apslist(9). don't skip the membar, iykyk. β οΈ
psref_target_destroy(target, class)β Wait for all extant passive references totargeton all CPUs to be released, and then destroy it. The passive reference targettargetmust have been initialized withpsref_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 withpsref_target_init(). Multiple concurrent calls topsref_target_destroy()are not allowed. not me catching you trying to double-destroy π
psref_acquire(ref, target, class)β Acquire a passive reference totarget, storing per-CPU bookkeeping inref. The class oftargetmust beclass.
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 referenceref, which must have been acquired to point attargetin the classclass, waking a thread callingpsref_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 referencepfromtopto, which must be to a target inclass. The resource represented by the target of the passive references will not be destroyed before both references are released.psref_held(target, class)β Returntrueif the current CPU holds a passive reference totargetin the passive reference classclass, orfalseif not. (this one lives under#ifdef DIAGNOSTICin the synopsis, bestie β¨)
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.
okay that last bullet is highkey the trap of the page: psref_held() is for assertions only, not runtime decisions. positive assertions only unless you can actually prove nobody holds a reference. no vibes-based locking, bestie. π
π EXAMPLES π
here's the whole example, copied exactly character for character β don't "fix" the little quirks, they're part of the lore:
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. go read the source if you want the full behind-the-scenes, bestie. π
π SEE ALSO π
π HISTORY π
The psref data structure first appeared in NetBSD 8.0. iconic behavior from day one. β¨
π«Ά AUTHORS π«Ά
Taylor R Campbell <riastradh@NetBSD.org>
and that's the whole PSREF(9) page, bestie β this Debian cutie is dated April 27, 2016, and it's lowkey iconic. slay π
β¨
π¦ where this page comes from
- nixpkgs unstable: netbsd-source 11.0
- Copyright (c) 2016 The NetBSD Foundation, Inc
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms