Control groups (cgroups) account for and control sets of Linux tasks. A cgroup is not a process-tree node: a task inherits its parent’s cgroup when it forks, but a manager can subsequently migrate it. Consequently, cgroups provide the resource boundary behind service managers and containers, while namespaces provide views of global resources.
This article describes cgroup version 2 (cgroup v2). Its documented interface is
the cgroup2 filesystem; files and supported controllers depend on the running
kernel configuration and available hardware.
Unified Hierarchy and Membership #
Cgroup v2 has one unified hierarchy. Every task belongs to exactly one cgroup in that hierarchy, including the unnamed root cgroup. A child cgroup inherits the constraints and controller configuration permitted by its ancestors, so a child cannot obtain CPU, memory, or devices that its parent has not delegated.
The hierarchy is represented by a cgroup2 mount, conventionally
/sys/fs/cgroup:
findmnt -t cgroup2
cat /proc/self/cgroup
ps -o pid,cgroup,comm -p $$Each directory is a cgroup. Creating a directory creates a child cgroup, and
writing a PID to its cgroup.procs migrates the process’s thread group:
sudo mkdir /sys/fs/cgroup/build
printf '%s\n' "$$" | sudo tee /sys/fs/cgroup/build/cgroup.procsUse a service manager rather than moving its own processes by hand. For example, systemd creates and manages cgroups for slices, scopes, and services; manual migration can violate the manager’s accounting and lifecycle assumptions.
Domain and Threaded Cgroups #
The normal hierarchy consists of domain cgroups, whose controllers apply to a process as a unit. The no-internal-process rule prevents a non-root domain cgroup from both containing processes and distributing domain controllers to children. Move the workload into a leaf and use an empty interior node to allocate a controller among child cgroups:
The root is exempt. A threaded subtree, configured through cgroup.type, permits
individual threads to be placed with cgroup.threads. It is intended for
thread-aware controllers and has additional structural constraints; it does not
turn every controller into a per-thread limit. Inspect cgroup.type and the
kernel documentation before mixing threaded and domain subtrees.
Controllers and Enablement #
A controller is kernel code that implements a resource policy. Availability and enablement are distinct:
cgroup.controllerslists controllers that the parent has made available for the cgroup’s children.cgroup.subtree_controllists controllers enabled to control those children. Write+nameor-nameto alter it.- Controller interface files appear only where their controller, configuration, and device context make them applicable.
For a new subtree, first enable controllers on the parent, then configure child cgroups. A non-root domain cgroup must not contain processes while enabling a domain controller for its children.
cd /sys/fs/cgroup
cat cgroup.controllers
sudo sh -c 'printf "+cpu +memory +pids\n" > cgroup.subtree_control'
sudo mkdir build
sudo sh -c 'printf "50000 100000\n" > build/cpu.max'
sudo sh -c 'printf "2G\n" > build/memory.max'
sudo sh -c 'printf "512\n" > build/pids.max'cpu.max in the example allows 50,000 microseconds of CPU time per 100,000
microsecond period. It throttles aggregate CPU bandwidth; it does not reserve a
dedicated CPU. The effective controller set remains bounded by every ancestor.
Controller Interfaces #
Controller names at the root identify what a particular kernel exposes. Current v2 documentation covers the following resource-controller families:
| Controller | Representative files | Policy |
|---|---|---|
| CPU | cpu.weight, cpu.max, cpu.stat | Proportional sharing, bandwidth caps, and throttling statistics. |
| Memory | memory.current, memory.min, memory.low, memory.high, memory.max, memory.swap.max, memory.events | Accounting, protection, throttling/reclaim pressure, hard limits, swap limits, and OOM events. |
| I/O | io.weight, io.max, io.stat, io.pressure | Proportional and per-device bandwidth/IOPS controls plus statistics. |
| Cpuset | cpuset.cpus, cpuset.cpus.effective, cpuset.mems, cpuset.mems.effective | Requested and effective CPU and NUMA-node placement. |
| PIDs | pids.current, pids.max, pids.events | Current process count, a hard process limit, and limit-hit events. |
| HugeTLB | hugetlb.<size>.current, hugetlb.<size>.max, hugetlb.<size>.events | Per-huge-page-size usage and limits. |
| RDMA | rdma.max, rdma.current, rdma.events | Per-RDMA-device HCA-object and handle limits, usage, and failures. |
| Misc | misc.current, misc.max, misc.events | Limits for registered scalar resources. |
memory.high is a reclaim-and-throttle boundary, while memory.max is a hard
limit that can lead to cgroup-local OOM handling when reclaim cannot make
progress. memory.oom.group can request group-oriented killing for a workload.
Read memory.events, memory.stat, and pressure-stall information before
changing a limit; a limit that fits steady-state RSS can still fail during page
faults, fork, or cache growth.
cpuset.cpus and cpuset.mems express requests subject to parent constraints.
Their *.effective counterparts show the CPUs and NUMA nodes actually granted,
including hotplug effects. CPU affinity and cpusets are intersected, so either
mechanism can reduce where a task runs.
Common Cgroup Files #
In addition to controller files, cgroup v2 supplies hierarchy-wide files:
cgroup.procslists thread-group IDs in a cgroup and accepts a PID migration.cgroup.threadslists threads in a threaded subtree and accepts thread IDs.cgroup.eventsreports state such as whether the cgroup or its descendants are populated; poll or inotify can observe changes.cgroup.freezestops and resumes execution in a non-root subtree, andcgroup.killkills all processes in a non-root subtree.cgroup.max.depthandcgroup.max.descendantsbound hierarchy growth.cgroup.stat,cgroup.type, andcgroup.controllersexpose hierarchy and controller state.
File writes are operations, not configuration transactions. A manager must
handle EBUSY, permission failures, hotplug changes, concurrent migration, and
partial setup. Limits may be written as max where the interface permits an
unbounded value.
Delegation, Namespaces, and Containers #
Delegation gives a subordinate manager control of a subtree without granting control of its ancestors. A delegator creates an empty cgroup, enables the necessary controllers on its parent, assigns ownership or access rights to the subtree, and allows the delegate to create and manage descendants. The delegate must operate only below that boundary because ancestor configuration determines its available resources.
systemd’s Delegate=yes is a common way to establish such a boundary. A
container runtime then composes several mechanisms:
- create or select the cgroup and apply host resource policy;
- create user, mount, PID, network, and other namespaces;
- arrange a private mount view and mount
/procafter the PID namespace exists; - execute the workload and retain a host-side handle for lifecycle operations.
A cgroup namespace changes the cgroup-path view seen by a process: /proc/PID/cgroup
and cgroup filesystem paths are rooted relative to that namespace. It does not
create a separate controller hierarchy, relax ancestor limits, or isolate
resource accounting. Host-side managers retain the authoritative hierarchy.
Kernel Representation #
The user-visible tree is backed by cgroup objects in the kernel. task_struct
points to a css_set; a css_set groups the task’s
cgroup_subsys_state (CSS) references for the active controllers. A controller
embeds or associates its state with a CSS and uses that association in its
accounting and enforcement paths. Tasks can share a css_set, which avoids
duplicating an identical controller-state combination for every task.
These implementation details explain why migration must update controller
membership consistently, but they are not a stable userspace ABI. Consult
kernel/cgroup/cgroup.c
and include/linux/cgroup-defs.h
for a particular kernel revision.
Contrast with Cgroup Version 1 #
Version 1 permits multiple independent hierarchies and controller attachment to different hierarchies. Therefore a task can have distinct memberships for different controllers, which makes an application’s resource boundary difficult to reason about. Cgroup v2 uses one hierarchy, common delegation rules, and consistent controller interfaces. Systems can mount v1 and v2 during migration, but a controller cannot be simultaneously bound to both versions.
The authoritative, version-specific reference is the
cgroup v2 kernel documentation.
Check the target kernel’s cgroup.controllers and that document rather than
assuming a controller or interface file exists.
Comments
Read or comment on GitHub (sign-in required to comment)