COUNTERS_ALLOC(9)
π counters_alloc(9) β per CPU counters π
okay so real talk bestie: this page is from the Kernel Developer's Manual (section 9, kernel-devs-only territory, iykyk) and it's literally the whole rundown on per CPU counters. if you've ever needed a pile of uint64_t counters that live on every CPU and get summed up later without tanking your performance, this is your page. let's get into it π
πΈ NAME πΈ
counters_alloc, counters_free, COUNTERS_BOOT_MEMORY, COUNTERS_BOOT_INITIALIZER, counters_alloc_ncpus, counters_enter, counters_leave, counters_inc, counters_add, counters_pkt, counters_read, counters_zero β per CPU counters β¨
π¦ SYNOPSIS π¦
#include <sys/percpu.h>
struct cpumem *
counters_alloc(unsigned int ncounters);
void
counters_free(struct cpumem *cm, unsigned int ncounters);
COUNTERS_BOOT_MEMORY(NAME, unsigned int ncounters);
COUNTERS_BOOT_INITIALIZER(NAME);
struct cpumemt *
counters_alloc_ncpus(struct cpumem *cm, unsigned int ncounters);
uint64_t *
counters_enter(struct counters_ref *ref, struct cpumem *cm);
void
counters_leave(struct counters_ref *ref, struct cpumem *cm);
void
counters_inc(struct cpumem *cm, unsigned int counter);
void
counters_add(struct cpumem *cm, unsigned int counter, uint64_t v);
void
counters_pkt(struct cpumem *cm, unsigned int pcounter,
unsigned int bcounter, uint64_t bytes);
void
counters_read(struct cpumem *cm, uint64_t *counters,
unsigned int ncounters, uint64_t *scratch);
void
counters_zero(struct cpumem *cm, unsigned int ncounters);
heads up bestie: yes, that really does saystruct cpumemt *in thecounters_alloc_ncpus()declaration. that's exactly how it's written, so we're keeping it character for character. π no notes, moving on.
π DESCRIPTION π
the per CPU counter API builds on the per CPU memory API and gives you access to a whole series of uint64_t counter values on each CPU. it's giving "counters, but distributed." π«Ά
the API does versioning of counter updates without using interlocked CPU instructions, so they can all be read in a consistent state. huge. and updates to counters should be limited to addition or subtraction of uint64_t values β that's the whole vibe, don't get creative with it.
there's also an alternate implementation of the API on uni-processor systems (i.e. when the kernel is not built with MULTIPROCESSOR defined) that provides no overhead compared to direct access to a data structure. so you can use the API without affecting the performance of uni-processor systems β literally free, we love that for you β¨
counters_alloc()β allocates memory for a series ofuint64_tvalues on each CPU.ncountersspecifies the number of counters to be allocated. the counters will be zeroed on allocation. clean slate, iconic.counters_free()β deallocates each CPU's counters. the samencountersargument originally provided tocounters_alloc()must be passed tocounters_free(). no mixing up your numbers, bestie, they have to match πCOUNTERS_BOOT_MEMORY()β statically allocates a set of counter for use on the boot CPU before the other CPUs in the system have been attached. the allocation is identified byNAMEand provides memory for the number of counters specified byncounters. the counters will be initialised to zero. early-boot main character energy πCOUNTERS_BOOT_INITIALIZER()β used to initialise acpumempointer with the memory that was previously allocated usingCOUNTERS_BOOT_MEMORY()and identified byNAME. it's the matching set, the duo, the friendship bracelet π€counters_alloc_ncpus()β allocates additional sets of counters for the CPUs that were attached during boot. thecpumemstructurecmmust have been initialised withCOUNTERS_BOOT_INITIALIZER(). the same number of counters originally passed toCOUNTERS_BOOT_MEMORYmust be specified byncounters. the counters on the boot CPU will be preserved, while the counters for the additional CPUs will be zeroed on allocation. glow-up for the new CPUs, no reset for the OG π₯counters_enter()β provides access to the current CPU's set of counters referenced bycm. the caller's reference to the counters is held byref. yes, you needref, it's your little ticket stub ποΈcounters_leave()β indicates the end of access to the current CPU's set of counters referenced bycm. the reference held byrefis released by this call. bye bestie, come back soon πΈcounters_inc()β increments the counter at the index specified bycounterin the array of counters referenced bycm. +1, we stan.counters_add()β adds the value ofvto the counter at the index specified bycounterin the array of counters referenced bycm. bulk vibes only.counters_pkt()β increments the value at the index specified bypcounterand adds the value ofbytesto the counter at the index specified bybcounterin the array of counters referenced bycm. two-for-one, packet style πcounters_read()β iterates over each CPU's set of counters referenced bycm, takes a consistent snapshot of the counters, and then sums them together. the sum of the counters is written to thecountersarray. the number of counters is specified withncounters.scratchmay point to a buffer used to temporarily hold counters. ifNULL, one will be dynamically allocated and freed. it's the "collect them all and add them up" moment π§counters_zero()β iterates over each CPU's set of counters referenced bycmand zeroes them. the number of counters is specified withncounters. but listen:counters_zero()itself does not prevent concurrent updates of the counters; it is up to the caller to serialise this call with other actions. not me warning you twice about this, but here we are π
β¨ the extra important bits from above, in one place:counters_alloc()may only be used after all the CPUs in the system have been attached. if a set of CPU counters needs to be available during early boot, acpumempointer and counters for the boot CPU may be statically allocated. and β say it with me β counters that have been allocated withCOUNTERS_BOOT_MEMORY()andcounters_alloc_ncpus()cannot be deallocated withcounters_free. do not try it. nobody wins. π
π CONTEXT π
counters_alloc(),counters_free(),counters_alloc_ncpus(), andcounters_read()may be called during autoconf, or from process context.counters_enter(),counters_leave(),counters_inc(),counters_add(),counters_pkt(), andcounters_zero()may be called during autoconf, from process context, or from interrupt context.
β οΈ okay this is the serious bit, bestie: the per CPU counters API does not provide any locking or serialisation of access to each CPU's set of counters beyond isolating each CPU's update. it is up to the caller to provide appropriate locking or serialisation around calls to these functions to prevent concurrent access to the relevant data structures. no cap, this one matters. π
β RETURN VALUES β
counters_alloc()andcounters_alloc_ncpus()will return an opaquecpumempointer that references each CPU's set of counters.counters_enter()returns a reference to the current CPU's set of counters.
π EXAMPLES π
the following is an example of providing per CPU counters at boot time based on the mbuf(9) statistics code in sys/kern/uipc_mbuf.c. presenting: the whole thing, verbatim, because it ate and left no crumbs π
/* mbuf stats */
COUNTERS_BOOT_MEMORY(mbstat_boot, MBSTAT_COUNT);
struct cpumem *mbstat = COUNTERS_BOOT_INITIALIZER(mbstat_boot);
/*
* this function is called from init_main.c after devices
* (including additional CPUs) have been attached
*/
void
mbcpuinit()
{
mbstat = counters_alloc_ncpus(mbstat, MBSTAT_COUNT);
}
struct mbuf *
m_get(int nowait, int type)
{
...
struct counters_ref cr;
uint64_t *counters;
int s;
...
s = splnet();
counters = counters_enter(&cr, mbstat);
counters[type]++;
counters_leave(&cr, mbstat);
splx(s);
...
}
struct mbuf *
m_free(struct mbuf *m)
{
...
struct counters_ref cr;
uint64_t *counters;
int s;
...
s = splnet();
counters = counters_enter(&cr, mbstat);
counters[m->m_type]--;
counters_leave(&cr, mbstat);
splx(s);
...
}
see how it's ++ in one and -- in the other? addition or subtraction of uint64_t values, exactly like we said. consistency queen π
π§ SEE ALSO π§
π₯ HISTORY π₯
the per CPU counter API first appeared in OpenBSD 6.1. vintage but make it fresh β¨
π AUTHORS π
the per CPU counter API was written by David Gwynne <dlg@openbsd.org>. absolute legend behaviour, we bow π«‘
and that's the page!! this Debian build is dated September 16, 2023, and it's filed under COUNTERS_ALLOC(9) β go forth and count your per-CPU little hearts out, bestie πππ¦
π¦ where this page comes from
- nixpkgs unstable: cvs-export
- Copyright (c) 2016 David Gwynne <dlg@openbsd.org>
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms