The misc Controller
The
misccontroller is cgroups v2’s catch-all for scalar hardware resources — countable, machine-finite quantities that exist in too small a supply and too narrow a use case to justify a dedicated controller of their own. Rather than write a whole new controller every time a CPU vendor ships a feature gated on a handful of hardware IDs, the kernel offers one generic limiter keyed by resource name. As of Linux 6.12 LTS, the only resources registered are AMD SEV and SEV-ES address-space identifiers (ASIDs) — the encryption-key slots used by confidential-VM features; in Linux 6.18 LTS a third, Intel TDX host key IDs (HKIDs), is added (per v6.12 vs v6.18misc_cgroup.h). The controller is enabled byCONFIG_CGROUP_MISCand was merged in Linux 5.13 (per LWN and Phoronix). Its interface is deliberately tiny: per resource you get a totalmisc.capacity, a per-cgroupmisc.maxlimit, amisc.currentusage, amisc.peakhigh-watermark, and amisc.eventscounter of limit hits.
The mechanism is interesting precisely because of what it isn’t: there is no CPU scheduler integration, no memory reclaim, no I/O throttling. The misc controller is a pure counting allocator with a hierarchical ceiling — atomic charge/uncharge of an integer against a budget. That simplicity is the whole design: it exists so that the next niche scalar resource can be added in a few lines of a vendor’s KVM driver instead of a new subsystem. It belongs to the containers family because, like every other v2 controller, it bounds what a process tree may consume — here, how many confidential-VM encryption slots a container or a tenant may hold.
Mental Model — A Named-Token Budget per Subtree
Think of misc as a bank with a few named token denominations. The machine has a fixed total of each token (misc.capacity, e.g. “509 SEV ASIDs”). Each cgroup may set a ceiling on how many of each token its subtree may hold (misc.max). When the kernel hands out a token (charges), it walks the cgroup up to the root, debiting each ancestor; if any ancestor would exceed its ceiling or the machine-wide capacity, the whole charge fails with -EBUSY and a misc.events→max counter ticks.
flowchart TB CAP["misc.capacity (root only)<br/>sev 509 sev_es 100<br/>= total tokens on this machine"] CAP --> ROOT["root misc cgroup"] ROOT -->|"misc.max sev 200"| T1["tenant-A<br/>misc.current sev 137<br/>misc.peak sev 180"] ROOT -->|"misc.max sev 200"| T2["tenant-B<br/>misc.current sev 40"] T1 -->|"misc.max sev 50"| C1["container-1<br/>charge here walks UP:<br/>container-1 -> tenant-A -> root"] CHARGE["misc_cg_try_charge(SEV, cg, 1)"] -.->|"debit cg AND every ancestor;<br/>any over max or capacity => -EBUSY,<br/>misc.events max++"| C1
The misc controller as a hierarchical token budget. What it shows: a charge for one SEV ASID at container-1 is debited not just locally but at every ancestor up to root, and it succeeds only if no ancestor’s misc.max and the machine-wide misc.capacity are both respected. The insight to take: misc enforces limits hierarchically and additively — a child’s usage counts against every parent — and misc.capacity is a hard machine ceiling that even a generous misc.max cannot exceed. The limit is on a count, not bytes or CPU time; this is what “scalar resource” means.
Mechanical Walk-through — Charge, Limit, Account
The entire controller is ~490 lines of kernel/cgroup/misc.c (v6.12), and the data model is the small include/linux/misc_cgroup.h. Walking the real code:
The resource registry
A resource is two things in lockstep: an entry in enum misc_res_type (in the header) and a matching string in misc_res_name[] (in misc.c). In v6.12 both are gated on CONFIG_KVM_AMD_SEV:
/* misc_cgroup.h, v6.12 */
enum misc_res_type {
#ifdef CONFIG_KVM_AMD_SEV
MISC_CG_RES_SEV, /* AMD SEV ASIDs */
MISC_CG_RES_SEV_ES, /* AMD SEV-ES ASIDs */
#endif
MISC_CG_RES_TYPES /* count sentinel */
};MISC_CG_RES_TYPES is not a real resource — it is the array-size sentinel (the classic C enum-as-count idiom), so struct misc_res res[MISC_CG_RES_TYPES] sizes itself to exactly the number of compiled-in resources. If CONFIG_KVM_AMD_SEV is off and (in 6.12) nothing else registers, the array is empty and the controller is inert. The cgroup-v2.rst documentation (lines 2665–2668) confirms the contract: “A resource can be added to the controller via enum misc_res_type{} … and the corresponding name via misc_res_name[].”
Per-resource state
Each (cgroup, resource) pair holds a struct misc_res (misc_cgroup.h line 38):
struct misc_res {
u64 max; /* the misc.max ceiling */
atomic64_t watermark; /* misc.peak: historical high usage */
atomic64_t usage; /* misc.current */
atomic64_t events; /* misc.events: hierarchical max-hit count */
atomic64_t events_local; /* misc.events.local: non-hierarchical */
};usage, watermark, events, and events_local are atomic64_t because charging happens from any context with no cgroup mutex held — the controller relies on lock-free atomics rather than a spinlock, which is part of why it is cheap.
Capacity: the machine ceiling
A provider of the resource — the AMD SEV code in KVM — calls misc_cg_set_capacity(type, capacity) (line 98) at init, recording the machine-wide total in a static misc_res_capacity[] array. Capacity 0 means “this resource does not exist / is not initialised on this host,” and any charge against a zero-capacity resource fails with -EINVAL. Crucially, the root cgroup’s max and the capacity are independent: the in-source comment (lines 36–39) notes root_cg.max can be set higher than the actual capacity — this is the cgroup “Limits” distribution model, where the real hardware ceiling (capacity) is the backstop regardless of what max says.
Charging: walk up, debit, validate, roll back
misc_cg_try_charge(type, cg, amount) (line 164) is the heart of the controller:
- Reject if the type is invalid, the cgroup is null, or the resource’s capacity is 0 →
-EINVAL. A zeroamountis a trivial success. - Walk from
cgto the root (for (i = cg; i; i = parent_misc(i))). At each level,atomic64_add_return(amount, &res->usage)charges and reads the new usage in one atomic op. - If the new usage exceeds either
res->max(this cgroup’s ceiling) ormisc_res_capacity[type](the machine total), setret = -EBUSYand jump to error handling. - On success at each level, update the watermark (
misc_cg_update_watermark, an atomic compare-exchange loop that only ever raises the recorded peak). - On failure, roll back:
misc_cg_event(type, i)fires the events counter at the level that failed and propagates it up; then every level below the failure point that was already charged is uncharged (misc_cg_cancel_charge). The charge is therefore all-or-nothing across the hierarchy.
This is why the limit is genuinely hierarchical: a container deep in the tree cannot allocate a SEV ASID if any ancestor — a per-tenant cgroup, say — is already at its misc.max. The failure is reported at the constraining level.
The events counter
misc_cg_event() (line 137) increments events_local at the failing cgroup and cgroup_file_notifys its misc.events.local file, then walks up incrementing each ancestor’s hierarchical events and notifying their misc.events files. So misc.events’s max field at a parent counts every limit-hit anywhere in its subtree; misc.events.local counts only hits at that exact cgroup. Both are surfaced by __misc_events_show() (line 379), which prints "%s.max %llu\n" — note the key is literally <resname>.max, the only event field defined.
Uncharging and migration
misc_cg_uncharge() (line 208) simply walks up cancelling the charge at every level — symmetric to the charge walk. A subtle but documented property: charges do not follow process migration. cgroup-v2.rst (lines 2742–2745) states a misc resource “is charged to the cgroup in which it is used first, and stays charged to that cgroup until that resource is freed. Migrating a process to a different cgroup does not move the charge.” This makes sense given the resource model — a SEV ASID is bound to a running VM, not to the bookkeeping of which cgroup the controlling thread currently lives in.
Interface Files — All Six
The kernel doc’s prose says the controller “provides 3 interface files,” but the actual misc_cg_files[] cftype array in v6.12 (misc.c line 407) registers six. The full set, with their flags:
| File | Where | Direction | Meaning |
|---|---|---|---|
misc.capacity | root only (CFTYPE_ONLY_ON_ROOT) | read | Machine-wide total of each resource. Zero-capacity resources are omitted. |
misc.current | all cgroups | read | Current usage (res.usage) of each resource in this cgroup and its children. |
misc.peak | all cgroups | read | Historical maximum usage (res.watermark). |
misc.max | non-root (CFTYPE_NOT_ON_ROOT) | read/write | The ceiling. Default is max (i.e. U64_MAX, set in misc_cg_alloc). |
misc.events | non-root | read | Hierarchical count of max-boundary hits (<res>.max <n>). |
misc.events.local | non-root | read | Same, but counting only hits at this cgroup. |
Two of these — misc.peak and misc.events.local — are not mentioned in the original three-file documentation paragraph and are easy to miss if you read prose rather than source.
Reading the files
# At the root: what does the machine have?
cat /sys/fs/cgroup/misc.capacity
# sev 509
# sev_es 100 # (numbers illustrative; depend on CPU + firmware)
# In a child cgroup: set and inspect a limit.
cd /sys/fs/cgroup/confidential-vms
echo "sev 50" > misc.max # cap this subtree at 50 SEV ASIDs
echo "sev max" > misc.max # remove the cap (back to U64_MAX)
cat misc.max
# sev 50
cat misc.current
# sev 37
cat misc.peak
# sev 48
cat misc.events
# sev.max 3 # this subtree hit its limit 3 timesThe write parser
misc_cg_max_write() (line 266) takes input like echo "sev 23" — a strsep on the space splits the resource name from the value; the name is matched against misc_res_name[], an unknown name yields -EINVAL, the literal string max maps to U64_MAX, otherwise kstrtou64 parses the number. A write to a resource whose capacity is 0 (not present on this host) also returns -EINVAL — you cannot limit what does not exist.
Allocation defaults
misc_cg_alloc() (line 451) initialises every new cgroup’s per-resource max to MAX_NUM (U64_MAX) and usage to 0 — so a fresh cgroup imposes no limit until you write one. The root cgroup is a static root_cg; children are kzalloc’d. The controller registers both legacy_cftypes and dfl_cftypes to the same file array (line 488), so the interface is identical on the (rare) v1 hierarchy and the v2 default hierarchy.
The Canonical Consumers — Confidential Computing
The misc controller exists because confidential-VM features anchor each guest to a scarce, hardware-enumerated key slot, and cloud hosts pack many tenants onto one machine.
AMD SEV / SEV-ES ASIDs (v6.12 and later). Secure Encrypted Virtualization (SEV) encrypts a VM’s memory with a key tied to an address-space identifier (ASID); SEV-ES (Encrypted State) additionally encrypts the guest’s CPU register state across VMEXIT. The number of simultaneously usable ASIDs is a small, CPU- and firmware-fixed quantity (commonly in the low hundreds, partitioned between plain-SEV and SEV-ES). Without a limiter, one noisy tenant could exhaust the host’s ASIDs and starve every other tenant of confidential-VM capability. The very first misc.c commit registered SEV and SEV-ES ASIDs to the controller for exactly this reason; the controller was, in fact, originally proposed as a SEV ASID controller and generalised during review (LWN: “The misc control group”).
Intel TDX HKIDs (v6.18, not v6.12). Trust Domain Extensions (TDX) is Intel’s confidential-VM technology; each trust domain uses a host key ID (HKID) to select its memory-encryption key, and HKIDs are likewise a limited per-machine resource. In v6.18 the misc controller gains a third resource, tdx, gated on CONFIG_INTEL_TDX_HOST (verified in v6.18 misc_cgroup.h and misc.c).
Uncertain
Verify: the exact LTS release in which TDX HKIDs first appeared in the misc controller. Reason: I confirmed by direct fetch that the
tdxresource is absent in v6.12 and present in v6.18 — that part is source-verified. I did not pin the precise introducing version (it could be any release in the 6.13–6.18 range); the KVM/TDX merge commit (“KVM: TDX: Register TDX host key IDs to cgroup misc controller”) exists but I did not map it to a kernel tag. To resolve:git log --oneline v6.12..v6.18 -- kernel/cgroup/misc.cand read the tag the HKID commit landed in. Do not rely on this note for “TDX support exists in 6.12” — it does not. uncertain
The task brief named both SEV/SEV-ES and TDX as “canonical consumers.” That is accurate for the mainline / 6.18 kernel but wrong for 6.12: pinned to 6.12 LTS, the misc controller knows only sev and sev_es. This note corrects that to match the version it is pinned to. See the Linux Virtualization MOC for the SEV/SEV-ES/TDX confidential-computing mechanisms themselves.
Failure Modes and Gotchas
- A zero-capacity resource silently rejects everything. If the host CPU lacks SEV (or
CONFIG_KVM_AMD_SEVis off),misc.capacityomits it,misc.maxwrites return-EINVAL, and charges return-EINVAL. The resource is invisible, not “unlimited.” misc.maxcan exceedmisc.capacityand that is intentional. The in-source comment is explicit:root_cg.maxmay be larger thancapacity. The capacity is the real backstop —misc_cg_try_chargechecksnew_usage > misc_res_capacity[type]independently ofmax. Settingmisc.max sev maxdoes not grant more ASIDs than the silicon has.- Charges don’t migrate with the process. Move a process holding a SEV ASID into another cgroup and the charge stays with the original cgroup until the VM is torn down. Accounting tools that assume charge-follows-task will misattribute usage.
- The doc undercounts the files. Reading only the “3 interface files” paragraph hides
misc.peakandmisc.events.local. Trust the cftype array. misc.events’ only field ismax. There is no “high” or “oom” event like the memory controller; the sole event is the limit-hit (<res>.max). Don’t grep for fields that don’t exist.- It is not a reservation. Setting
misc.max sev 50does not pre-allocate 50 ASIDs; it caps usage. Actual ASIDs are charged lazily as VMs start, and a start can fail with-EBUSYat any point up to the cap or capacity.
Alternatives and When to Choose Them
- A dedicated controller (like
cpu,memory,io,pids). Justified when the resource needs scheduling, reclaim, throttling, or pressure feedback — anything beyond counting. SEV ASIDs need none of that; they are bind-on-VM-create, free-on-destroy integers, which is exactly themiscsweet spot. See The cgroup pids Controller for the closest counting sibling (it limits process/thread count, but is dedicated because PID accounting is universal, not niche). - No controller — global host limit only. Before
misc, ASID exhaustion was a global host failure with no per-tenant attribution.miscadds the per-subtree ceiling and the events counter so a cloud host can fairly partition and observe a scarce hardware resource across tenants. pidscontroller for the specific case of “limit how many processes.”miscis for non-process scalar hardware tokens; the two are complementary, not substitutes.
The decision rule the LWN coverage settled on: if a resource is countable, scalar, machine-finite, and niche, add it to misc (a few lines); if it needs active management, it earns its own controller.
Production Notes
The misc controller’s reach is, by design, narrow: in v6.12 it is only meaningful on AMD EPYC hosts running confidential VMs with SEV. Container/VM platforms that schedule confidential guests (cloud providers offering AMD SEV-SNP or Intel TDX instances, Kata Containers / Confidential Containers stacks) are the realistic operators of misc.max. For the overwhelming majority of containers — which use no memory-encryption hardware — the controller is present but inert, every resource at zero capacity. This is why a typical kubectl/Kubernetes cgroup hierarchy never touches misc: there is nothing scalar-and-scarce to bound. Its importance is as the extension point: when the next vendor ships a feature gated on a handful of hardware IDs, the kernel community can expose it through misc without the design debate and code of a new subsystem — which is exactly how TDX HKIDs were added later with a one-resource patch.
See Also
- Control Groups Overview — the cgroup framework and the charge/limit model the misc controller specialises
- cgroups v2 Unified Hierarchy — the v2 design
miscwas built for (it post-dates v1’s per-controller hierarchies) - The cgroup pids Controller — the closest sibling: another counting limiter, but dedicated because PID accounting is universal
- The cgroup Freezer — the other §G “cgroup lifecycle/accounting” leaf
- Pressure Stall Information — by contrast, a feedback signal;
mischas no pressure notion, only a hard count - Linux Virtualization MOC — owner of the AMD SEV / SEV-ES / Intel TDX confidential-computing mechanisms the misc controller meters
- Linux Containers and Isolation MOC — parent MOC (§G, cgroup pressure and lifecycle control)