UMA(9)
okay so bestie, you've opened the UMA(9) manual page and honestly? it's giving kernel-level main character energy. π let's break it down.
πΈ NAME πΈ
UMA β general-purpose kernel object allocator
β¨ SYNOPSIS β¨
#include <sys/param.h>
#include <sys/queue.h>
#include <vm/uma.h>
options UMA_FIRSTTOUCH
options UMA_XDOMAIN
typedef int (*uma_ctor)(void *mem, int size, void *arg, int flags);
typedef void (*uma_dtor)(void *mem, int size, void *arg);
typedef int (*uma_init)(void *mem, int size, int flags);
typedef void (*uma_fini)(void *mem, int size);
typedef int (*uma_import)(void *arg, void **store, int count, int domain,
int flags);
typedef void (*uma_release)(void *arg, void **store, int count);
typedef void *(*uma_alloc)(uma_zone_t zone, vm_size_t size, int domain,
uint8_t *pflag, int wait);
typedef void (*uma_free)(void *item, vm_size_t size, uint8_t pflag);
uma_zone_t
uma_zcreate(char *name, int size, uma_ctor ctor, uma_dtor dtor,
uma_init zinit, uma_fini zfini, int align, uint16_t flags);
uma_zone_t
uma_zcache_create(char *name, int size, uma_ctor ctor, uma_dtor dtor,
uma_init zinit, uma_fini zfini, uma_import zimport,
uma_release zrelease, void *arg, int flags);
uma_zone_t
uma_zsecond_create(char *name, uma_ctor ctor, uma_dtor dtor,
uma_init zinit, uma_fini zfini, uma_zone_t master);
void
uma_zdestroy(uma_zone_t zone);
void *
uma_zalloc(uma_zone_t zone, int flags);
void *
uma_zalloc_arg(uma_zone_t zone, void *arg, int flags);
void *
uma_zalloc_domain(uma_zone_t zone, void *arg, int domain, int flags);
void *
uma_zalloc_pcpu(uma_zone_t zone, int flags);
void *
uma_zalloc_pcpu_arg(uma_zone_t zone, void *arg, int flags);
void
uma_zfree(uma_zone_t zone, void *item);
void
uma_zfree_arg(uma_zone_t zone, void *item, void *arg);
void
uma_zfree_domain(uma_zone_t zone, void *item, void *arg);
void
uma_zfree_pcpu(uma_zone_t zone, void *item);
void
uma_zfree_pcpu_arg(uma_zone_t zone, void *item, void *arg);
void
uma_prealloc(uma_zone_t zone, int nitems);
void
uma_zone_reserve(uma_zone_t zone, int nitems);
void
uma_zone_reserve_kva(uma_zone_t zone, int nitems);
void
uma_zone_set_allocf(uma_zone_t zone, uma_alloc allocf);
void
uma_zone_set_freef(uma_zone_t zone, uma_free freef);
int
uma_zone_set_max(uma_zone_t zone, int nitems);
int
uma_zone_set_maxcache(uma_zone_t zone, int nitems);
int
uma_zone_get_max(uma_zone_t zone);
int
uma_zone_get_cur(uma_zone_t zone);
void
uma_zone_set_warning(uma_zone_t zone, const char *warning);
void
uma_zone_set_maxaction(uma_zone_t zone, void (*maxaction)(uma_zone_t));
void
uma_reclaim();
#include <sys/sysctl.h>
SYSCTL_UMA_MAX(parent, nbr, name, access, zone, descr);
SYSCTL_ADD_UMA_MAX(ctx, parent, nbr, name, access, zone, descr);
SYSCTL_UMA_CUR(parent, nbr, name, access, zone, descr);
SYSCTL_ADD_UMA_CUR(ctx, parent, nbr, name, access, zone, descr);
π DESCRIPTION π
okay so UMA β that's the Universal Memory Allocator β is literally the efficient interface for managing dynamically-sized collections of items that are all the same size. those collections? they're called zones. and zones are keeping track of which items are in use and which are not, no cap. UMA gives you functions to allocate items from a zone and release them back, so they're available for the next allocation request. zones maintain per-CPU caches with linear scalability on SMP systems, plus round-robin and first-touch policies for NUMA systems. the number of items cached per CPU is bounded, and each zone also keeps an unbounded cache of items that's used to quickly satisfy per-CPU cache allocation misses. π
Two types of zones exist: regular zones and cache zones. In a regular zone, items are allocated from a slab, which is one or more virtually contiguous memory pages that have been allocated from the kernel's page allocator. Internally, slabs are managed by a UMA keg, which is responsible for allocating slabs and keeping track of their usage by one or more zones. In typical usage, there's one keg per zone, so slabs aren't shared among multiple zones.
Normal zones import items from a keg, and release items back to that keg if requested. Cache zones don't have a keg, and instead use custom import and release methods. For example, some collections of kernel objects are statically allocated at boot-time, and the size of the collection doesn't change. A cache zone can be used to implement an efficient allocator for the objects in such a collection.
The uma_zcreate() and uma_zcache_create() functions create a new regular zone and cache zone, respectively. The uma_zsecond_create() function creates a regular zone which shares the keg of the zone specified by the master argument. The name argument is a text name of the zone for debugging and stats; this memory should not be freed until the zone has been deallocated.
The ctor and dtor arguments are callback functions that are called by the UMA subsystem at the time of the call to uma_zalloc() and uma_zfree() respectively. Their purpose is to provide hooks for initializing or destroying things that need to be done at the time of the allocation or release of a resource. A good usage for the ctor and dtor callbacks might be to initialize a data structure embedded in the item, such as a queue(3) head.
The zinit and zfini arguments are used to optimize the allocation of items from the zone. They are called by the UMA subsystem whenever it needs to allocate or free items to satisfy requests or memory pressure. A good use for the zinit and zfini callbacks might be to initialize and destroy a mutex contained within an item. This would allow one to avoid destroying and re-initializing the mutex each time the item is freed and re-allocated. They are not called on each call to uma_zalloc() and uma_zfree() but rather when an item is imported into a zone's cache, and when a zone releases an item to the slab allocator, typically as a response to memory pressure.
For uma_zcache_create(), the zimport and zrelease functions are called to import items into the zone and to release items from the zone, respectively. The zimport function should store pointers to items in the store array, which contains a maximum of count entries. The function must return the number of imported items, which may be less than the maximum. Similarly, the store parameter to the zrelease function contains an array of count pointers to items. The arg parameter passed to uma_zcache_create() is provided to the import and release functions. The domain parameter to zimport specifies the requested numa(4) domain for the allocation. It is either a NUMA domain number or the special value UMA_ANYDOMAIN.
The flags argument of uma_zcreate() and uma_zcache_create() is a subset of the following flags:
UMA_ZONE_NOFREEβ Slabs allocated to the zone's keg are never freed.UMA_ZONE_NODUMPβ Pages belonging to the zone will not be included in minidumps.UMA_ZONE_PCPUβ An allocation from zone would havemp_ncpushadow copies, that are privately assigned to CPUs. A CPU can address its private copy using base the allocation address plus a multiple of the current CPU ID andsizeof(struct pcpu):
foo_zone = uma_zcreate(..., UMA_ZONE_PCPU);
...
foo_base = uma_zalloc(foo_zone, ...);
...
critical_enter();
foo_pcpu = (foo_t *)zpcpu_get(foo_base);
/* do something with foo_pcpu */
critical_exit();
Note thatM_ZEROcannot be used when allocating items from a PCPU zone. To obtain zeroed memory from a PCPU zone, use theuma_zalloc_pcpu()function and its variants instead, and passM_ZERO.
UMA_ZONE_OFFPAGEβ By default book-keeping of items within a slab is done in the slab page itself. This flag explicitly tells subsystem that book-keeping structure should be allocated separately from special internal zone. This flag requires eitherUMA_ZONE_VTOSLABorUMA_ZONE_HASH, since subsystem requires a mechanism to find a book-keeping structure to an item being freed. The subsystem may choose to prefer offpage book-keeping for certain zones implicitly.UMA_ZONE_ZINITβ The zone will have itsuma_initmethod set to internal method that initializes a new allocated slab to all zeros. Do not mistakeuma_initmethod withuma_ctor. A zone withUMA_ZONE_ZINITflag would not return zeroed memory on everyuma_zalloc().UMA_ZONE_HASHβ The zone should use an internal hash table to find slab book-keeping structure where an allocation being freed belongs to.UMA_ZONE_VTOSLABβ The zone should use special field ofvm_page_tto find slab book-keeping structure where an allocation being freed belongs to.UMA_ZONE_MALLOCβ The zone is for themalloc(9)subsystem.UMA_ZONE_VMβ The zone is for the VM subsystem.UMA_ZONE_NUMAβ The zone should use a first-touch NUMA policy rather than the round-robin default. If theUMA_FIRSTTOUCHkernel option is configured, all zones implicitly use a first-touch policy, and theUMA_ZONE_NUMAflag has no effect. TheUMA_XDOMAINkernel option, when configured, causes UMA to do the extra tracking to ensure that allocations from first-touch zones are always local. Otherwise, consumers that do not free memory on the same domain from which it was allocated will cause mixing in per-CPU caches. Seenuma(4)for more details.
Zones can be destroyed using uma_zdestroy(), freeing all memory that is cached in the zone. All items allocated from the zone must be freed to the zone before the zone may be safely destroyed.
To allocate an item from a zone, simply call uma_zalloc() with a pointer to that zone and set the flags argument to selected flags as documented in malloc(9). It will return a pointer to an item if successful, or NULL in the rare case where all items in the zone are in use and the allocator is unable to grow the zone and M_NOWAIT is specified.
Items are released back to the zone from which they were allocated by calling uma_zfree() with a pointer to the zone and a pointer to the item. If item is NULL, then uma_zfree() does nothing.
Okay so the uma_zalloc_arg() and uma_zfree_arg() variants? They let you hand the zone a lil something extra β an argument that gets passed straight to the zone's ctor and dtor functions respectively. Bestie, it's giving "custom setup, custom teardown" energy. π
And uma_zalloc_domain()? That one lets you name a fixed numa(4) domain to allocate from. Heads up though β it uses a guaranteed but slow path in the allocator, and that path reduces concurrency. π¬ When you're done with memory you grabbed that way, you return it with uma_zfree_domain(). The cute part? That function figures out the domain from the pointer itself, so you don't gotta pass it in as an argument. β¨
uma_prealloc() allocates slabs for however many items you ask for, typically right after you first create a zone. After that, subsequent allocations from the zone get satisfied using those pre-allocated slabs. But watch it β slab allocation happens with the M_WAITOK flag, so uma_prealloc() may sleep. π€
uma_zone_reserve() sets how many reserved items the zone keeps. uma_zalloc() and friends will make sure the zone always has <em>at least</em> that many free items hanging around. You can actually use those reserved items by passing M_USE_RESERVE in the allocation request flags. And ngl β uma_zone_reserve() does not pre-allocate anything by itself. It's just setting the number, bestie. π
uma_zone_reserve_kva() is different β it pre-allocates kernel virtual address space for the requested number of items, and later allocations from the zone get satisfied from that pre-allocated address space. And the plot twist: unlike uma_zone_reserve(), it does not restrict that pre-allocation to M_USE_RESERVE requests only. π¦
uma_zone_set_allocf() and uma_zone_set_freef() let you override a zone's default slab allocation and free functions. Obsessed β this is for when your zone's items have special memory allocation constraints. Like, say your multi-page objects <em>need</em> to be physically contiguous? Then you'd use an allocf function that requests contiguous memory from the kernel's page allocator. Perfect fit. π₯
uma_zone_set_max() caps the number of items β and therefore the memory β that can be allocated to a zone. The <em>nitems</em> argument is your requested upper limit. It returns the effective limit to you though, because it might end up higher than you asked for: the implementation rounds up so all the memory pages allocated to the zone get used to capacity. No wasted space, periodt. π
Vibe check on that limit: it applies to the total number of items in the zone β allocated items, free items, <em>and</em> free items sitting in the per-cpu caches. On systems with more than one CPU, it may not be possible to allocate your specified number of items even when there's no memory shortage, because when the limit gets hit, all the remaining free items might be chillin' in the caches of the <em>other</em> CPUs. π
uma_zone_set_maxcache() limits how many free items can be cached in the zone β excluding the per-CPU caches, which are bounded in size anyway. Example time, bestie: to implement a 'pure' per-CPU cache, you configure a cache zone with a maximum cache size of 0. Slay. β¨
uma_zone_get_max() returns the effective upper limit number of items for a zone. And uma_zone_get_cur() returns an <em>approximation</em> of how many items are currently allocated from the zone. Why approximate? Because the implementation doesn't do the synchronisation needed to nail an exact value. That's a tradeoff on purpose β low overhead, at the expense of potentially stale data being used in the calculation. π«Ά
uma_zone_set_warning() sets a warning that gets printed on the system console when the given zone becomes full and fails to allocate an item. It won't print more often than every five minutes. And you can turn warnings off globally by setting the vm.zone_warnings sysctl tunable to 0. π
uma_zone_set_maxaction() sets a function that gets called when the given zone becomes full and fails to allocate an item.
Okay real talk, this is important: that function gets called with the zone locked. And on top of that, whatever function called the allocation may have been holding additional locks. So this function should do very little work β think "similar to a signal handler". Don't go doing a whole side quest in there, bestie. π
SYSCTL_UMA_MAX(parent, nbr, name, access, zone, descr) declares a static sysctl(9) oid that exports the effective upper limit number of items for a zone. The <em>zone</em> argument should be a pointer to uma_zone_t. Reading the oid gives you the value from uma_zone_get_max(). Writing to the oid sets a new value via uma_zone_set_max(). And if you wanna make one of these dynamically, there's SYSCTL_ADD_UMA_MAX(ctx, parent, nbr, name, access, zone, descr) for exactly that. π
SYSCTL_UMA_CUR(parent, nbr, name, access, zone, descr) declares a static read-only sysctl(9) oid that exports the approximate current occupancy of the zone. Again, <em>zone</em> should be a pointer to uma_zone_t, and reading the oid gives you the value from uma_zone_get_cur(). Wanna create this type dynamically? That's SYSCTL_ADD_UMA_CUR(ctx, parent, nbr, name, zone, descr). β
π§ IMPLEMENTATION NOTES π§
The memory that these allocation calls return is not executable. Like, uma_zalloc() does not support the M_EXEC flag for allocating executable memory. Though ngl, not all platforms even enforce a distinction between executable and non-executable memory. π€·βοΈ
π SEE ALSO π
Also on the reading list: Jeff Bonwick, The Slab Allocator: An Object-Caching Kernel Memory Allocator, 1994. π
π HISTORY π
The zone allocator first appeared in FreeBSD 3.0. Then it got radically changed in FreeBSD 5.0 to function as a slab allocator. Glow-up of the century honestly. β¨
π AUTHORS π
The zone allocator was written by John S. Dyson. It was rewritten in large parts by Jeff Roberson <jeff@FreeBSD.org> to function as a slab allocator. Main character energy fr fr. π
This manual page was written by Dag-Erling SmΓΈrgrav <des@FreeBSD.org>, and the changes for UMA were by Jeroen Ruigrok van der Werven <asmodai@FreeBSD.org>. π¦
And that's the whole page, bestie β this one's from the Debian crew, dated August 20, 2020, and it's UMA(9) in the manual. Go forth and allocate responsibly π πΈ
π¦ where this page comes from
- debian bookworm: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- debian trixie: freebsd-manpages 12.2-2 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu jammy: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu noble: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu questing: freebsd-manpages 12.2-2
- ubuntu resolute: freebsd-manpages 12.2-2build1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu stonking: freebsd-manpages 12.2-2build1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- Copyright (c) 2001 Dag-Erling CoΓ―dan SmΓΈrgrav
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms