cgroups(7)
โจ cgroups(7) โจ โ the Linux control groups situation, explained by your unix bestie ๐
Miscellaneous Information Manual energy, fr fr. Buckle up bestie, this one's a whole saga.
๐ธ NAME ๐ธ
cgroups โ Linux control groups. That's it, that's the vibe check.
๐ฆ DESCRIPTION ๐ฆ
okay so. Control groups โ everybody just says cgroups, no cap โ are a Linux kernel feature that lets you organize processes into hierarchical groups, and then limit and monitor how much of various resources those groups are allowed to use. ๐
The kernel exposes the cgroup interface through a pseudo-filesystem called cgroupfs. The actual grouping happens in the core cgroup kernel code, while the resource tracking and limiting is handled by a whole crew of per-resource-type subsystems (memory, CPU, and so on). Different jobs, different girlies. ๐
๐ Terminology ๐
A cgroup is a collection of processes that are bound to a set of limits or parameters defined through the cgroup filesystem. Simple as.
A subsystem is a kernel component that changes the behavior of the processes inside a cgroup. Lots of subsystems exist, and they let you do stuff like cap how much CPU time and memory a cgroup gets, account for the CPU time a cgroup uses, and freeze/resume the processes in a cgroup. Subsystems are also called resource controllers, or just controllers if you're being casual about it. iykyk. โจ
The cgroups for a controller are arranged in a hierarchy. You build that hierarchy by creating, removing, and renaming subdirectories inside the cgroup filesystem. At every level of the hierarchy you can define attributes (limits, for example). And here's the tea: the limits, control, and accounting that cgroups give you generally apply throughout the whole subhierarchy underneath the cgroup where you defined those attributes. So the limits you put on a cgroup higher up in the hierarchy literally cannot be exceeded by its descendant cgroups. generational limits, literally. ๐
๐ Cgroups version 1 and version 2 ๐
The first ever release of the cgroups implementation landed in Linux 2.6.24. Over time, all kinds of cgroup controllers got added to manage different types of resources. But โ and this is the messy part โ development of these controllers was largely uncoordinated, so a ton of inconsistencies popped up between controllers and managing the cgroup hierarchies got kinda complicated. ๐ญ If you want the long, dramatic version of these problems, it's in the kernel source file Documentation/admin-guide/cgroup-v2.rst (or Documentation/cgroup-v2.rst in Linux 4.17 and earlier).
Because the original implementation (cgroups version 1) had these problems, work on a new, orthogonal implementation started back in Linux 3.10 to fix all that. It was initially marked experimental and hidden behind the -o __DEVEL__sane_behavior mount option, and then the new version (cgroups version 2) officially became a thing with the release of Linux 4.5. ๐ The differences between the two versions get spelled out below. Also, the cgroup.sane_behavior file โ which exists in cgroups v1 โ is literally just a relic of that mount option. It always reports "0" and is only still there for backward compatibility. ghost of features past. ๐
cgroups v2 is meant to replace cgroups v1, but the older system is still around (and for compatibility reasons it's unlikely to ever get removed, so don't hold your breath). Right now, cgroups v2 only implements a subset of the controllers available in cgroups v1. The two systems are implemented so that both v1 and v2 controllers can be mounted on the same system โ so yes, bestie, you can use the controllers that v2 supports while also using v1 controllers for the ones v2 doesn't support yet. There's exactly one restriction here: a controller can't be employed in a cgroups v1 hierarchy and the cgroups v2 hierarchy at the same time. Pick a lane, controller. ๐
๐ CGROUPS VERSION 1 ๐
Under cgroups v1, each controller can be mounted against its own separate cgroup filesystem, giving it its own hierarchical organization of the processes on the system. You can also comount multiple (or even all) cgroups v1 controllers against the same cgroup filesystem โ which means those comounted controllers manage the same hierarchical organization of processes. shared custody, but make it kernel. ๐ซถ
For each mounted hierarchy, the directory tree mirrors the control group hierarchy. Every control group is a directory, and each of its child control cgroups is a child directory. For example, /user/joe/1.session represents control group 1.session, which is a child of cgroup joe, which is a child of /user. Under each cgroup directory there's a set of files you can read from or write to, reflecting resource limits and a few general cgroup properties.
๐ง Tasks (threads) versus processes ๐ง
In cgroups v1, there's a distinction between processes and tasks. In this view, a process can consist of multiple tasks (more commonly called threads from a user-space perspective, and called that in the rest of this man page). And in cgroups v1, you can independently manipulate the cgroup memberships of the threads inside a process. wild, right?
That ability to split threads across different cgroups caused problems in some cases though. For example, it made zero sense for the memory controller, because all the threads of a process share a single address space. Because of these problems, the ability to independently manipulate the cgroup memberships of threads in a process was removed in the initial cgroups v2 implementation, and then later restored in a more limited form (see the discussion of "thread mode" below). glow up + character development arc. โจ
๐ Mounting v1 controllers ๐
Using cgroups requires a kernel built with the CONFIG_CGROUP option. On top of that, every v1 controller has its own associated configuration option that has to be set before you can use that controller.
To use a v1 controller, it must be mounted against a cgroup filesystem. The usual spot for this kind of mount is under a tmpfs(5) filesystem mounted at /sys/fs/cgroup. So for example, you might mount the cpu controller like this:
mount -t cgroup -o cpu none /sys/fs/cgroup/cpu
You can comount multiple controllers against the same hierarchy. For example, here the cpu and cpuacct controllers are comounted against a single hierarchy:
mount -t cgroup -o cpu,cpuacct none /sys/fs/cgroup/cpu,cpuacct
Comounting controllers has this effect: a process is in the same cgroup for all of the comounted controllers. Mounting controllers separately, on the other hand, lets a process be in cgroup /foo1 for one controller while being in /foo2/foo3 for another. multitasking queen. ๐
You can also comount all v1 controllers against the same hierarchy:
mount -t cgroup -o all cgroup /sys/fs/cgroup
(You get the same result by just omitting -o all, since that's the default when no controllers are explicitly specified. sneaky.)
It is not possible to mount the same controller against multiple cgroup hierarchies. For example, you can't mount both the cpu and cpuacct controllers against one hierarchy and mount the cpu controller alone against another hierarchy. ๐ You can create multiple mounts with exactly the same set of comounted controllers โ but all that gets you is multiple mount points providing a view of the same hierarchy. same girl, different outfit.
Note that on many systems, the v1 controllers are automatically mounted under /sys/fs/cgroup; in particular, systemd(1) automatically creates such mounts. โจ
๐ธ Unmounting v1 controllers ๐ธ
A mounted cgroup filesystem can be unmounted using the umount(8) command, like this:
umount /sys/fs/cgroup/pids
But listen, and listen well bestie: a cgroup filesystem is unmounted only if it is not busy โ meaning it has no child cgroups. If that's not the case, then the only thing umount(8) does is make the mount invisible. ๐ So if you want to make sure the mount is really removed, you have to first remove all the child cgroups, and that in turn can only be done after all member processes have been moved out of those cgroups and into the root cgroup. tedious but we do it for the plot.
๐ Cgroups version 1 controllers ๐
Each of the cgroups version 1 controllers is governed by a kernel configuration option (listed below). And also, the availability of the whole cgroups feature is governed by the CONFIG_CGROUPS kernel configuration option. got it? got it.
cpu(since Linux 2.6.24;CONFIG_CGROUP_SCHED) โ Cgroups can be guaranteed a minimum number of "CPU shares" when the system is busy. But heads up: this does not limit a cgroup's CPU usage when the CPUs aren't busy. For further information, rack up the kernel source fileDocumentation/scheduler/sched-design-CFS.rst.
In Linux 3.2, this controller got extended to provide CPU "bandwidth" control. If the kernel is configured with CONFIG_CFS_BANDWIDTH, then within each scheduling period (defined via a file in the cgroup directory) you can define an upper limit on the CPU time allocated to the processes in a cgroup. This upper limit applies even if there's no competition at all for the CPU. Further information lives in the kernel source file Documentation/scheduler/sched-bwc.rst.
cpuacct(since Linux 2.6.24;CONFIG_CGROUP_CPUACCT) โ This one provides accounting for CPU usage by groups of processes. ๐
Further information can be found in the kernel source file Documentation/admin-guide/cgroup-v1/cpuacct.rst.
cpuset(since Linux 2.6.24;CONFIG_CPUSETS) โ This cgroup can be used to bind the processes in a cgroup to a specified set of CPUs and NUMA nodes. ๐
Further information can be found in the kernel source file Documentation/admin-guide/cgroup-v1/cpusets.rst.
memory(since Linux 2.6.25;CONFIG_MEMCG) โ The memory controller supports reporting and limiting of process memory, kernel memory, and swap used by cgroups.
Further information can be found in the kernel source file Documentation/admin-guide/cgroup-v1/memory.rst.
devices(since Linux 2.6.26;CONFIG_CGROUP_DEVICE) โ This one supports controlling which processes may create (mknod) devices as well as open them for reading or writing. The policies can be specified as allow-lists and deny-lists. Hierarchy is enforced, so new rules must not violate existing rules for the target or ancestor cgroups. don't embarrass your ancestors, literally. ๐ซถ
Further information can be found in the kernel source file Documentation/admin-guide/cgroup-v1/devices.rst.
freezer(since Linux 2.6.28;CONFIG_CGROUP_FREEZER) โ The freezer cgroup can suspend and restore (resume) all the processes in a cgroup. Freezing a cgroup/Aalso causes its children to be frozen โ so for example, the processes in/A/B. ๐ง
Further information can be found in the kernel source file Documentation/admin-guide/cgroup-v1/freezer-subsystem.rst.
net_cls (since Linux 2.6.29; CONFIG_CGROUP_NET_CLASSID)
okay so this one slaps a classid โ one that you specify for the cgroup โ onto network packets that the cgroup creates. ๐ Then you can use those classids in firewall rules, and also to shape traffic with tc(8). Big vibe check though: this applies only to packets leaving the cgroup, not to traffic arriving at the cgroup. Leaving only, bestie. ๐ช
More deets live in the kernel source file Documentation/admin-guide/cgroup-v1/net_cls.rst.
blkio (since Linux 2.6.33; CONFIG_BLK_CGROUP)
The blkio cgroup controls and limits access to specified block devices by applying IO control in the form of throttling and upper limits against leaf nodes and intermediate nodes in the storage hierarchy. ๐งฑ
Two policies are available, and they're giving very different energy:
- Policy one โ a proportional-weight, time-based division of disk implemented with CFQ. This is in effect for leaf nodes using CFQ.
- Policy two โ a throttling policy which specifies upper I/O rate limits on a device.
More deets live in the kernel source file Documentation/admin-guide/cgroup-v1/blkio-controller.rst.
perf_event (since Linux 2.6.39; CONFIG_CGROUP_PERF)
This controller allows perf monitoring of the set of processes grouped in a cgroup. ๐โจ
Further information can be found in the kernel source files โ and yes, that's literally where the original page just trails off, no filename, nothing ๐ iconic of it to leave us on read.
net_prio (since Linux 3.3; CONFIG_CGROUP_NET_PRIO)
This allows priorities to be specified, per network interface, for cgroups. ๐
More deets live in the kernel source file Documentation/admin-guide/cgroup-v1/net_prio.rst.
hugetlb (since Linux 3.5; CONFIG_CGROUP_HUGETLB)
This supports limiting the use of huge pages by cgroups. ๐๐
More deets live in the kernel source file Documentation/admin-guide/cgroup-v1/hugetlb.rst.
pids (since Linux 4.3; CONFIG_CGROUP_PIDS)
This controller permits limiting the number of process that may be created in a cgroup (and its descendants). ๐ผ Counting your little process babies, fr fr.
More deets live in the kernel source file Documentation/admin-guide/cgroup-v1/pids.rst.
rdma (since Linux 4.11; CONFIG_CGROUP_RDMA)
The RDMA controller permits limiting the use of RDMA/IB-specific resources per cgroup. ๐ฆ
More deets live in the kernel source file Documentation/admin-guide/cgroup-v1/rdma.rst.
๐ซถ Creating cgroups and moving processes
A cgroup filesystem initially contains a single root cgroup, /, which all processes belong to. Everybody starts here, no exceptions. A new cgroup is created by creating a directory in the cgroup filesystem:
mkdir /sys/fs/cgroup/cpu/cg1
This creates a new empty cgroup. Fresh out the box, no thoughts, head empty ๐
A process may be moved to this cgroup by writing its PID into the cgroup's cgroup.procs file:
echo $$ > /sys/fs/cgroup/cpu/cg1/cgroup.procs
Only one PID at a time should be written to this file. One. At. A time. Don't be greedy ๐
Writing the value 0 to a cgroup.procs file causes the writing process to be moved to the corresponding cgroup. (Not me writing myself into a new cgroup ๐)
When writing a PID into the cgroup.procs, all threads in the process are moved into the new cgroup at once. Whole crew, together, no thread left behind. ๐ซ
Within a hierarchy, a process can be a member of exactly one cgroup. Writing a process's PID to a cgroup.procs file automatically removes it from the cgroup of which it was previously a member. It's giving exclusive relationship ๐
The cgroup.procs file can be read to obtain a list of the processes that are members of a cgroup. But heads up:
- the returned list of PIDs is not guaranteed to be in order, and
- nor is it guaranteed to be free of duplicates. (For example, a PID may be recycled while reading from the list.)
So don't trust the vibes of that list too hard bestie ๐ญ
In cgroups v1, an individual thread can be moved to another cgroup by writing its thread ID (i.e., the kernel thread ID returned by clone(2) and gettid(2)) to the tasks file in a cgroup directory. This file can be read to discover the set of threads that are members of the cgroup.
๐๏ธ Removing cgroups
To remove a cgroup, it must first have no child cgroups and contain no (nonzombie) processes. So long as that is the case, one can simply remove the corresponding directory pathname. Clean slate energy โจ
Note that files in a cgroup directory cannot and need not be removed. Leave them, they're fine, they live there now ๐
๐ Cgroups v1 release notification
Two files can be used to determine whether the kernel provides notifications when a cgroup becomes empty. A cgroup is considered to be empty when it contains no child cgroups and no member processes.
A special file in the root directory of each cgroup hierarchy, release_agent, can be used to register the pathname of a program that may be invoked when a cgroup in the hierarchy becomes empty. The pathname of the newly empty cgroup (relative to the cgroup mount point) is provided as the sole command-line argument when the release_agent program is invoked. The release_agent program might remove the cgroup directory, or perhaps repopulate it with a process. It's giving "you empty? here's a little treat" ๐
The default value of the release_agent file is empty, meaning that no release agent is invoked.
The content of the release_agent file can also be specified via a mount option when the cgroup filesystem is mounted:
mount -o release_agent=pathname ...
Whether or not the release_agent program is invoked when a particular cgroup becomes empty is determined by the value in the notify_on_release file in the corresponding cgroup directory:
- If this file contains the value
0, then therelease_agentprogram is not invoked. - If it contains the value
1, therelease_agentprogram is invoked. ๐จ
The default value for this file in the root cgroup is 0. At the time when a new cgroup is created, the value in this file is inherited from the corresponding file in the parent cgroup. Genetics, but for cgroup files ๐
๐ท๏ธ Cgroup v1 named hierarchies
In cgroups v1, it is possible to mount a cgroup hierarchy that has no attached controllers:
mount -t cgroup -o none,name=somename none /some/mount/point
Multiple instances of such hierarchies can be mounted; each hierarchy must have a unique name. The only purpose of such hierarchies is to track processes. (See the discussion of release notification below.) An example of this is the name=systemd cgroup hierarchy that is used by systemd(1) to track services and user sessions.
Since Linux 5.0, the cgroup_no_v1 kernel boot option (described below) can be used to disable cgroup v1 named hierarchies, by specifying cgroup_no_v1=named.
๐ CGROUPS VERSION 2
In cgroups v2, all mounted controllers reside in a single unified hierarchy. While (different) controllers may be simultaneously mounted under the v1 and v2 hierarchies, it is not possible to mount the same controller simultaneously under both the v1 and the v2 hierarchies. Pick a lane, controller ๐
The new behaviors in cgroups v2 are summarized here, and in some cases elaborated in the following subsections.
- Cgroups v2 provides a unified hierarchy against which all controllers are mounted.
- "Internal" processes are not permitted. With the exception of the root cgroup, processes may reside only in leaf nodes (cgroups that do not themselves contain child cgroups). The details are somewhat more subtle than this, and are described below.
- Active cgroups must be specified via the files
cgroup.controllersandcgroup.subtree_control. - The
tasksfile has been removed. In addition, thecgroup.clone_childrenfile that is employed by the cpuset controller has been removed. - An improved mechanism for notification of empty cgroups is provided by the
cgroup.eventsfile.
For more changes, see the Documentation/admin-guide/cgroup-v2.rst file in the kernel source.
Some of the new behaviors listed above saw subsequent modification with the addition in Linux 4.14 of "thread mode" (described below).
๐ธ Cgroups v2 unified hierarchy
In cgroups v1, the ability to mount different controllers against different hierarchies was intended to allow great flexibility for application design. In practice, though, the flexibility turned out to be less useful than expected, and in many cases added complexity. So v2 said "okay besties, we're simplifying" ๐
Therefore, in cgroups v2, all available controllers are mounted against a single hierarchy. The available controllers are automatically mounted, meaning that it is not necessary (or possible) to specify the controllers when mounting the cgroup v2 filesystem using a command such as the following:
mount -t cgroup2 none /mnt/cgroup2
A cgroup v2 controller is available only if it is not currently in use via a mount against a cgroup v1 hierarchy. Or, to put things another way, it is not possible to employ the same controller against both a v1 hierarchy and the unified v2 hierarchy. This means that it may be necessary first to unmount a v1 controller (as described above) before that controller is available in v2.
Since systemd(1) makes heavy use of some v1 controllers by default, it can in some cases be simpler to boot the system with selected v1 controllers disabled. To do this, specify the cgroup_no_v1=list option on the kernel boot command line; list is a comma-separated list of the names of the controllers to disable, or the word all to disable all v1 controllers. (This situation is correctly handled by systemd(1), which falls back to operating without the specified controllers.) She adapts, she's flexible ๐
Note that on many modern systems, systemd(1) automatically mounts the cgroup2 filesystem at /sys/fs/cgroup/unified during the boot process.
โ๏ธ Cgroups v2 mount options
The following options (mount -o) can be specified when mounting the group v2 filesystem:
nsdelegate (since Linux 4.15)
Treat cgroup namespaces as delegation boundaries. For details, see below. ๐ง
memory_localevents (since Linux 5.2)
The memory.events should show statistics only for the cgroup itself, and not for any descendant cgroups. This was the behavior before Linux 5.2. Since Linux 5.2, the default behavior is to include statistics for descendant cgroups in memory.events, and this mount option can be used to revert to the legacy behavior. This option is system wide and can be set on mount or modified through remount only from the initial mount namespace; it is silently ignored in noninitial namespaces. ๐
๐ Cgroups v2 controllers
The following controllers, documented in the kernel source file Documentation/admin-guide/cgroup-v2.rst, are supported in cgroups version 2:
cpu (since Linux 4.15)
This is the successor to the version 1 cpu and cpuacct controllers. The glow-up is real โญ
cpuset (since Linux 5.0)
This is the successor of the version 1 cpuset controller.
freezer (since Linux 5.2)
This is the successor of the version 1 freezer controller. ๐ง
hugetlb (since Linux 5.6)
This is the successor of the version 1 hugetlb controller. ๐
io โ since Linux 4.5
bestie this one is the successor of the version 1 blkio controller, it's giving glow-up energy ๐
memory โ since Linux 4.5
this is the successor of the version 1 memory controller. the OG but upgraded, no cap.
perf_event โ since Linux 4.11
this one is literally the same as the version 1 perf_event controller. same vibe, new hierarchy.
pids โ since Linux 4.5
same as the version 1 pids controller, she just moved houses ๐
rdma โ since Linux 4.11
same as the version 1 rdma controller. periodt.
okay so here's the tea ๐ซ โ there is no direct equivalent of the net_cls and net_prio controllers from cgroups version 1. instead, support has been added to iptables(8) to allow eBPF filters that hook on cgroup v2 pathnames so they can make decisions about network traffic on a per-cgroup basis. iconic honestly.
also! the v2 devices controller provides no interface files at all (yes, zero, nothing, empty). instead, device control is gated by attaching an eBPF (BPF_CGROUP_DEVICE) program to a v2 cgroup. so you do it with a program, not a file. ๐ง
๐ Cgroups v2 subtree control ๐
every cgroup in the v2 hierarchy contains the following two files, and yes bestie you're gonna want to know both:
cgroup.controllersโ this read-only file exposes a list of the controllers that are available in this cgroup. the contents of this file match the contents of thecgroup.subtree_controlfile in the parent cgroup. ๐cgroup.subtree_controlโ this is a list of controllers that are active (enabled) in the cgroup. the set of controllers in this file is a subset of the set in thecgroup.controllersof this cgroup. you modify the set of active controllers by writing strings to this file, containing space-delimited controller names, each preceded by'+'(to enable a controller) or'-'(to disable a controller), like this:
echo '+pids -memory' > x/y/cgroup.subtree_control
heads up though โ an attempt to enable a controller that is not present in cgroup.controllers leads to an ENOENT error when writing to the cgroup.subtree_control file. so don't try it, it will not slay. ๐
because the list of controllers in cgroup.subtree_control is a subset of those in cgroup.controllers, a controller that has been disabled in one cgroup in the hierarchy can never be re-enabled in the subtree below that cgroup. like, ever. that's just the rules.
a cgroup's cgroup.subtree_control file determines the set of controllers that are exercised in the child cgroups. so when a controller (say, pids) is present in the cgroup.subtree_control file of a parent cgroup, then the corresponding controller-interface files (so, pids.max) are automatically created in the children of that cgroup and can be used to exert resource control in the child cgroups. it's giving automatic โจ
๐ Cgroups v2 "no internal processes" rule ๐
cgroups v2 enforces a so-called "no internal processes" rule. roughly speaking, this rule means that, with the exception of the root cgroup, processes may reside only in leaf nodes (cgroups that do not themselves contain child cgroups). the whole point is avoiding the need to decide how to partition resources between processes which are members of cgroup A and processes in child cgroups of A. messy, we don't do that here.
for instance, if cgroup /cg1/cg2 exists, then a process may reside in /cg1/cg2, but not in /cg1. this is to avoid an ambiguity in cgroups v1 with respect to the delegation of resources between processes in /cg1 and its child cgroups.
the recommended approach in cgroups v2 is to create a subdirectory called leaf for any nonleaf cgroup which should contain processes, but no child cgroups. thus, processes which previously would have gone into /cg1 would now go into /cg1/leaf. the advantage here is that it makes explicit the relationship between processes in /cg1/leaf and /cg1's other children. so clean. so organized. ๐งผ
and ngl, the "no internal processes" rule is in fact more subtle than stated above. more precisely, the rule is that a (nonroot) cgroup can't both (1) have member processes, and (2) distribute resources into child cgroups โ that is, have a nonempty cgroup.subtree_control file. so it is possible for a cgroup to have both member processes and child cgroups, but before controllers can be enabled for that cgroup, the member processes must be moved out of the cgroup (e.g., perhaps into the child cgroups). you gotta clear the room before the party starts ๐ชฉ
oh and with the Linux 4.14 addition of "thread mode" (described below), the "no internal processes" rule has been relaxed in some cases. so... sometimes it's chill.
๐ธ Cgroups v2 cgroup.events file ๐ธ
each nonroot cgroup in the v2 hierarchy contains a read-only file, cgroup.events, whose contents are key-value pairs (delimited by newline characters, with the key and value separated by spaces) providing state information about the cgroup:
$ cat mygrp/cgroup.events
populated 1
frozen 0
the following keys may appear in this file:
populatedโ the value of this key is either1, if this cgroup or any of its descendants has member processes, or otherwise0.frozen(since Linux 5.2) โ the value of this key is1if this cgroup is currently frozen, or0if it is not.
the cgroup.events file can be monitored, in order to receive notification when the value of one of its keys changes. bestie you can do this with inotify(7), which notifies changes as IN_MODIFY events, or with poll(2), which notifies changes by returning the POLLPRI and POLLERR bits in the revents field. pick your fighter ๐
๐ Cgroup v2 release notification ๐
cgroups v2 provides a new mechanism for obtaining notification when a cgroup becomes empty. the cgroups v1 release_agent and notify_on_release files are removed, and replaced by the populated key in the cgroup.events file. this key either has the value 0, meaning that the cgroup (and its descendants) contain no (nonzombie) member processes, or 1, meaning that the cgroup (or one of its descendants) contains member processes.
the cgroups v2 release-notification mechanism offers the following advantages over the cgroups v1 release_agent mechanism:
- it allows for cheaper notification, since a single process can monitor multiple
cgroup.eventsfiles (using the techniques described earlier). by contrast, the cgroups v1 mechanism requires the expense of creating a process for each notification. wasteful honestly. - notification for different cgroup subhierarchies can be delegated to different processes. by contrast, the cgroups v1 mechanism allows only one release agent for an entire hierarchy. one!! ๐
โญ Cgroups v2 cgroup.stat file โญ
each cgroup in the v2 hierarchy contains a read-only cgroup.stat file (first introduced in Linux 4.14) that consists of lines containing key-value pairs. the following keys currently appear in this file:
nr_descendantsโ this is the total number of visible (i.e., living) descendant cgroups underneath this cgroup. alive and thriving basically.nr_dying_descendantsโ this is the total number of dying descendant cgroups underneath this cgroup. a cgroup enters the dying state after being deleted. it remains in that state for an undefined period (which will depend on system load) while resources are freed before the cgroup is destroyed.
note that the presence of some cgroups in the dying state is normal, and is not indicative of any problem. so don't panic bestie, it's fine ๐
a process can't be made a member of a dying cgroup, and a dying cgroup can't be brought back to life. it's giving no resurrection arc. ๐
๐ฆ Limiting the number of descendant cgroups ๐ฆ
each cgroup in the v2 hierarchy contains the following files, which can be used to view and set limits on the number of descendant cgroups under that cgroup:
cgroup.max.depth(since Linux 4.14) โ this file defines a limit on the depth of nesting of descendant cgroups. a value of0in this file means that no descendant cgroups can be created. an attempt to create a descendant whose nesting level exceeds the limit fails (mkdir(2)fails with the errorEAGAIN). writing the string"max"to this file means that no limit is imposed. the default value in this file is"max". ๐cgroup.max.descendants(since Linux 4.14) โ this file defines a limit on the number of live descendant cgroups that this cgroup may have. an attempt to create more descendants than allowed by the limit fails (mkdir(2)fails with the errorEAGAIN). writing the string"max"to this file means that no limit is imposed. the default value in this file is"max". same energy, different axis bestie. โญ
๐ CGROUPS DELEGATION: DELEGATING A HIERARCHY TO A LESS PRIVILEGED USER ๐
okay bestie, let's talk delegation. in cgroups land, delegation means handing off the management of some lil subtree of the cgroup hierarchy to a nonprivileged user (that's the โจtrusted vibeโจ). so like, cgroups v1? it does support delegation based on file permissions in the cgroup hierarchy, BUT with way looser containment rules than v2 (we'll get into that below, don't worry). cgroups v2 though? it supports delegation with containment by explicit design. icon behavior, honestly. ๐
so the whole discussion here is focused on delegation in cgroups v2, with little notes about cgroups v1 differences sprinkled in along the way.
๐ธ Some Terminology (iykyk) ๐ธ
- delegater โ a privileged user (i.e.,
root) who owns a parent cgroup. the one doing the handing off. ๐ - delegatee โ a nonprivileged user who's getting the permissions to manage some subhierarchy under that parent cgroup.
- delegated subtree โ that subhierarchy the delegatee gets to play with. ๐
๐ฆ How To Actually Delegate ๐ฆ
to do the delegation, the delegater makes certain directories and files writable by the delegatee โ usually by changing the ownership of the objects to be the user ID of the delegatee. ๐
let's say we wanna delegate the hierarchy rooted at (for example) /dlgt_grp, and there aren't any child cgroups under it yet. here's what gets its ownership changed to the delegatee's user ID:
/dlgt_grpโ changing the ownership of the root of the subtree means that any new cgroups created under the subtree (and the files they contain) will also be owned by the delegatee. so convenient fr fr./dlgt_grp/cgroup.procsโ changing the ownership of this file means the delegatee can move processes into the root of the delegated subtree./dlgt_grp/cgroup.subtree_control(cgroups v2 only) โ changing the ownership of this file means the delegatee can enable controllers (the ones present in/dlgt_grp/cgroup.controllers) in order to further redistribute resources at lower levels in the subtree. (as an alternative to changing the ownership of this file, the delegater might instead add selected controllers to this file.)/dlgt_grp/cgroup.threads(cgroups v2 only) โ changing the ownership of this file is necessary if a threaded subtree is being delegated (see the description of "thread mode" below). this permits the delegatee to write thread IDs to the file. (the ownership of this file can also be changed when delegating a domain subtree, but currently this serves no purpose, since, as described below, it's not possible to move a thread between domain cgroups by writing its thread ID to thecgroup.threadsfile.)
in cgroups v1, the corresponding file that should instead be delegated is the tasks file. bestie don't forget the v1 difference!! ๐
IMPORTANT: the delegater should not change the ownership of any of the controller interfaces files (like pids.max, memory.high) in dlgt_grp. those files get used from the next level above the delegated subtree in order to distribute resources into the subtree, and the delegatee should not have permission to change the resources that are distributed into the delegated subtree. no sneaky resource-stealing energy allowed. ๐
see also the discussion of the /sys/kernel/cgroup/delegate file in sys_kernel_cgroup(5) for info about further delegatable files in cgroups v2. ๐ซถ
๐ซ After All That ๐ซ
once those steps are done, the delegatee can:
- create child cgroups within the delegated subtree (the cgroup subdirectories and the files they contain will be owned by the delegatee) ๐ฑ
- move processes between cgroups in the subtree
and if some controllers are present in dlgt_grp/cgroup.subtree_control, OR the ownership of that file was passed to the delegatee, then the delegatee can also control the further redistribution of the corresponding resources into the delegated subtree. main character energy, honestly. ๐
๐ฅ Cgroups v2 delegation: nsdelegate and cgroup namespaces ๐ฅ
starting with Linux 4.13, there's a second way to perform cgroup delegation in the cgroups v2 hierarchy. it's done by mounting or remounting the cgroup v2 filesystem with the nsdelegate mount option. โจ
for example, if the cgroup v2 filesystem is already mounted, we can remount it with the nsdelegate option like this:
mount -t cgroup2 -o remount,nsdelegate \
none /sys/fs/cgroup/unified
what this mount option does: it causes cgroup namespaces to automatically become delegation boundaries. ๐ค more specifically, these restrictions apply for processes inside the cgroup namespace:
- writes to controller interface files in the root directory of the namespace will fail with the error
EPERM. processes inside the cgroup namespace can still write to delegatable files in the root directory of the cgroup namespace (likecgroup.procsandcgroup.subtree_control), and can create subhierarchy underneath the root directory. slay. - attempts to migrate processes across the namespace boundary are denied (with the error
ENOENT). processes inside the cgroup namespace can still (subject to the containment rules described below) move processes between cgroups within the subhierarchy under the namespace root.
๐ธ why this is iconic ๐ธ
the ability to define cgroup namespaces as delegation boundaries makes cgroup namespaces way more useful. to understand why, suppose we already have one cgroup hierarchy that's been delegated to a nonprivileged user, cecilia, using the older delegation technique described above. ๐โโ๏ธ
now suppose cecilia wanted to further delegate a subhierarchy under the existing delegated hierarchy. (for example, the delegated hierarchy might be associated with an unprivileged container run by cecilia.) even if a cgroup namespace was employed, because both hierarchies are owned by the unprivileged user cecilia, these illegitimate actions could be performed (๐จ not okay ๐จ):
- a process in the inferior hierarchy could change the resource controller settings in the root directory of that hierarchy. (these resource controller settings are intended to allow control to be exercised from the parent cgroup; a process inside the child cgroup should not be allowed to modify them.)
- a process inside the inferior hierarchy could move processes into and out of the inferior hierarchy if the cgroups in the superior hierarchy were somehow visible.
employing the nsdelegate mount option prevents both of these possibilities. periodt. ๐
๐ฆ one lil caveat ๐ฆ
the nsdelegate mount option only has an effect when performed in the initial mount namespace; in other mount namespaces, the option is silently ignored. ๐ซ
Note: on some systems,systemd(1)automatically mounts the cgroup v2 filesystem. in order to experiment with thensdelegateoperation, it may be useful to boot the kernel with these command-line options:cgroup_no_v1=all systemd.legacy_systemd_cgroup_controllerthese options cause the kernel to boot with the cgroups v1 controllers disabled (meaning the controllers are available in the v2 hierarchy), and tellssystemd(1)not to mount and use the cgroup v2 hierarchy, so that the v2 hierarchy can be manually mounted with the desired options after boot-up. ๐
๐ Cgroup delegation containment rules ๐
there are some delegation containment rules that make sure the delegatee can move processes between cgroups within the delegated subtree, but can't move processes from outside the delegated subtree into the subtree or vice versa. no cross-contamination bestie. ๐ โโ๏ธ
a nonprivileged process (i.e., the delegatee) can write the PID of a "target" process into a cgroup.procs file only if all of the following are true:
- the writer has write permission on the
cgroup.procsfile in the destination cgroup. - the writer has write permission on the
cgroup.procsfile in the nearest common ancestor of the source and destination cgroups. note that in some cases, the nearest common ancestor may be the source or destination cgroup itself. โ ๏ธ this requirement is not enforced for cgroups v1 hierarchies, with the consequence that containment in v1 is less strict than in v2. (for example, in cgroups v1 the user that owns two distinct delegated subhierarchies can move a process between the hierarchies.) - if the cgroup v2 filesystem was mounted with the
nsdelegateoption, the writer must be able to see the source and destination cgroups from its cgroup namespace. ๐ - in cgroups v1: the effective UID of the writer (i.e., the delegatee) matches the real user ID or the saved set-user-ID of the target process. before Linux 4.11, this requirement also applied in cgroups v2. (this was a historical requirement inherited from cgroups v1 that was later deemed unnecessary, since the other rules suffice for containment in cgroups v2.)
Note: one consequence of these delegation containment rules is that the unprivileged delegatee can't place the first process into the delegated subtree; instead, the delegater must place the first process (a process owned by the delegatee) into the delegated subtree. so the delegater gotta do the kickoff. ๐
๐ง CGROUPS VERSION 2 THREAD MODE ๐ง
among the restrictions imposed by cgroups v2 that weren't present in cgroups v1 are these two:
- No thread-granularity control: all of the threads of a process must be in the same cgroup.
- No internal processes: a cgroup can't both have member processes and exercise controllers on child cgroups.
both of these restrictions were added because the lack of these restrictions had caused problems in cgroups v1. ๐ญ in particular, the cgroups v1 ability to allow thread-level granularity for cgroup membership made no sense for some controllers. (a notable example was the memory controller: since threads share an address space, it made no sense to split threads across different memory cgroups. like, why would you even.)
notwithstanding the initial design decision in cgroups v2, there were use cases for certain controllers โ notably the cpu controller โ for which thread-level granularity of control was meaningful and useful. to accommodate such use cases, Linux 4.14 added thread mode for cgroups v2. ๐
โจ thread mode allows the following โจ
- the creation of threaded subtrees in which the threads of a process may be spread across cgroups inside the tree. (a threaded subtree may contain multiple multithreaded processes.) ๐ณ
- the concept of threaded controllers, which can distribute resources across the cgroups in a threaded subtree.
- a relaxation of the "no internal processes rule", so that, within a threaded subtree, a cgroup can both contain member threads and exercise resource control over child cgroups. iconic honestly.
๐ฆ the cgroup.type file ๐ฆ
with the addition of thread mode, each nonroot cgroup now contains a new file, cgroup.type, that exposes โ and in some circumstances can be used to change โ the "type" of a cgroup. this file contains one of the following type values:
domainโ the basic girl of cgroup types โจ this is a normal v2 cgroup doing process-granularity control. If a process is in this cgroup, then all its threads are in the same cgroup, like, by definition. This is the default type, and it behaves exactly like the original cgroups v2 implementation did. classic, iconic, never left. ๐threadedโ bestie, this cgroup is a card-carrying member of a threaded subtree ๐ง you can add threads to it, and controllers can be enabled for it.domain threadedโ a domain cgroup that's serving as the root of a threaded subtree ๐ also known as the "threaded root". Yes she's got two titles, she earned them.domain invalidโ this is a cgroup sitting inside a threaded subtree in an "invalid" state ๐ processes can't be added to it, and controllers can't be enabled for it. The only things you can do are delete it, or convert it to athreadedcgroup by writing the string"threaded"to thecgroup.typefile.
The reason this "interim" type even exists (instead of the kernel just instantly flipping every cgroup under the threaded root to type threaded) is so that possible future extensions to the thread mode model can happen. foresight, honestly. ๐ฎ
๐ Threaded versus domain controllers ๐
With threads mode in the picture, cgroups v2 now splits resource controllers into two flavors:
- Threaded controllers: these support thread-granularity resource control, and they can be enabled inside threaded subtrees โ which means their controller-interface files show up inside the cgroups in the threaded subtree. As at Linux 4.19, the threaded controllers are:
cpu,perf_event, andpids. ๐ - Domain controllers: these only do process granularity ๐ from a domain controller's whole perspective, all threads of a process are always in the same cgroup. Domain controllers can't be enabled inside a threaded subtree. periodt.
๐ฆ Creating a threaded subtree ๐ฆ
There are two pathways that lead to a threaded subtree existing. The first one goes like this:
- We write the string
"threaded"to thecgroup.typefile of a cgroupy/zthat currently has the typedomain. This has the following effects:- The type of the cgroup
y/zbecomesthreaded. - The type of the parent cgroup,
y, becomesdomain threaded. That parent cgroup is the root of a threaded subtree (the "threaded root"). - All other cgroups under
ythat weren't already of typethreaded(because they were inside already-existing threaded subtrees under the new threaded root) get converted to typedomain invalid. And any cgroups created underyafterwards will also bedomain invalid.
- The type of the cgroup
- We write the string
"threaded"to each of thedomain invalidcgroups undery, converting them to the typethreaded. Once that's done, all threads under the threaded root have the typethreadedand the threaded subtree is fully usable โจ
Having to write "threaded" to every one of these cgroups is somewhat cumbersome, ngl โ but it's what allows for possible future extensions to the thread-mode model. it's giving "we did it for the plot". ๐ญ
The second way to make a threaded subtree:
- In an existing cgroup
zthat currently has the typedomain, we (1.1) enable one or more threaded controllers and (1.2) make a process a member ofz. (These two steps can be done in either order, no stress.) This has the following consequences:- The type of
zbecomesdomain threaded. - All of the descendant cgroups of
zthat weren't already of typethreadedget converted to typedomain invalid.
- The type of
- As before, we make the threaded subtree usable by writing the string
"threaded"to each of thedomain invalidcgroups underz, converting them to typethreaded.
One big consequence of both of these pathways: the threaded root cgroup can only be a parent to threaded (and domain invalid) cgroups. The threaded root cgroup can't be the parent of a domain cgroup, and a threaded cgroup can't have a sibling that's a domain cgroup. no mixing, we don't do that here. ๐ซ
๐ธ Using a threaded subtree ๐ธ
Inside a threaded subtree, threaded controllers can be enabled in each subgroup whose type has been changed to threaded โ and when you do that, the corresponding controller interface files appear in the children of that cgroup. slay. ๐
You move a process into a threaded subtree by writing its PID to the cgroup.procs file in one of the cgroups inside the tree. That makes all of the threads in the process members of the corresponding cgroup, and makes the process a member of the threaded subtree. From there you can spread the process's threads across the threaded subtree by writing their thread IDs (see gettid(2)) to the cgroup.threads files in different cgroups inside the subtree. The threads of a process must all reside in the same threaded subtree. ๐
Just like writing to cgroup.procs, some containment rules apply when writing to the cgroup.threads file:
- The writer must have write permission on the
cgroup.threadsfile in the destination cgroup. - The writer must have write permission on the
cgroup.procsfile in the common ancestor of the source and destination cgroups. (Sometimes that common ancestor is the source or destination cgroup itself, no biggie.) - The source and destination cgroups must be in the same threaded subtree. (Outside a threaded subtree, if you try to move a thread by writing its thread ID to the
cgroup.threadsfile in a differentdomaincgroup, it fails with the errorEOPNOTSUPP.)
The cgroup.threads file lives in every cgroup (including domain cgroups) and you can read it to find out the set of threads in that cgroup ๐ heads up though: the set of thread IDs you get when reading this file is not guaranteed to be ordered or free of duplicates.
The cgroup.procs file in the threaded root shows the PIDs of all processes that are members of the threaded subtree. The cgroup.procs files in the other cgroups in the subtree are not readable.
Domain controllers can't be enabled in a threaded subtree โ no controller-interface files appear inside cgroups underneath the threaded root. From a domain controller's point of view, threaded subtrees are literally invisible ๐ซฅ a multithreaded process inside a threaded subtree just looks like a process living in the threaded root cgroup to them.
And within a threaded subtree, the "no internal processes" rule does not apply: a cgroup can both contain member processes (or threads) and exercise controllers on child cgroups. wild, I know. ๐
๐ Rules for writing to cgroup.type and creating threaded subtrees ๐
A bunch of rules apply when you write to the cgroup.type file:
- Only the string
"threaded"may be written. In other words, the only explicit transition that's possible is converting adomaincgroup to typethreaded. - What writing
"threaded"does depends on the current value incgroup.type, like so:domainordomain threaded: start creating a threaded subtree (whose root is the parent of this cgroup) via the first of the pathways described above;domain invalid: convert this cgroup (which is inside a threaded subtree) to a usable, i.e.threaded, state;threaded: no effect at all, a total "no-op". ๐
- We can't write to a
cgroup.typefile if the parent's type isdomain invalid. In other words, the cgroups of a threaded subtree must be converted to thethreadedstate in a top-down manner. no cutting the line.
There are also some constraints that must be satisfied in order to create a threaded subtree rooted at the cgroup x:
- There can be no member processes in the descendant cgroups of
x. (The cgroupxitself can have member processes.) - No domain controllers may be enabled in
x'scgroup.subtree_controlfile.
If any of the above constraints is violated, then an attempt to write"threaded"to acgroup.typefile fails with the errorENOTSUP. ๐จ
๐ The "domain threaded" cgroup type ๐
Per those pathways above, the type of a cgroup can change to domain threaded in either of these cases:
- The string
"threaded"is written to a child cgroup. - A threaded controller is enabled inside the cgroup and a process is made a member of the cgroup.
A domain threaded cgroup x can revert back to the type domain if those conditions stop being true โ that is, if all threaded child cgroups of x are removed and either x no longer has threaded controllers enabled or no longer has member processes.
When a domain threaded cgroup x reverts to the type domain:
- All
domain invaliddescendants ofxthat aren't in lower-level threaded subtrees revert to the typedomain. - The root cgroups in any lower-level threaded subtrees revert to the type
domain threaded.
โจ Exceptions for the root cgroup โจ
The root cgroup of the v2 hierarchy gets special treatment: it can be the parent of both domain and threaded cgroups ๐ so if the string "threaded" is written to the cgroup.type file of one of the children of the root cgroup, then
- The type of that cgroup becomes
threaded. - The type of any descendants of that cgroup that aren't part of lower-level threaded subtrees changes to
domain invalid.
Note that in this case, there is no cgroup whose type becomes domain threaded. (Notionally, the root cgroup can be thought of as the threaded root for the cgroup whose type was changed to threaded.)
The whole point of this exceptional treatment for the root cgroup is to let a threaded cgroup that uses the cpu controller sit as high up in the hierarchy as possible, so the (small) cost of traversing the cgroup hierarchy stays as low as it can. efficiency queen. ๐
โญ The cgroups v2 "cpu" controller and realtime threads โญ
As at Linux 4.19, the cgroups v2 cpu controller does not support control of realtime threads โ specifically threads scheduled under any of the policies SCHED_FIFO, SCHED_RR, described SCHED_DEADLINE; see sched(7). So the cpu controller can be enabled in the root cgroup only if all realtime threads are in the root cgroup.
(If there are realtime threads in nonroot cgroups, then awrite(2)of the string"+cpu"to thecgroup.subtree_controlfile fails with the errorEINVAL.) ๐ซ
On some systems, systemd(1) puts certain realtime threads in nonroot cgroups in the v2 hierarchy. On those systems, bestie, those threads have to be moved to the root cgroup first before the cpu controller can be enabled. Order of operations, no skipping ๐
๐ ERRORS ๐
Here's the error that mount(2) can throw at you:
EBUSYโ someone tried to mount a cgroup version 1 filesystem but specified neither thename=option (which is how you mount a named hierarchy) nor a controller name (orall). So like, pick one?? You gotta give it something to work with ๐
๐ NOTES ๐
A child process made via fork(2) inherits its parent's cgroup memberships. No cap. And a process's cgroup memberships stick around across execve(2) too โ they do not get yeeted. ๐ซถ
Oh and heads up: the clone3(2) CLONE_INTO_CGROUP flag lets you create a child process that starts its whole life in a different version 2 cgroup than its parent. Baby's first cgroup, so to speak โจ
๐๏ธ The /proc files ๐๏ธ
/proc/cgroups(since Linux 2.6.24)
This file is the receipts for all the controllers that are compiled into the kernel. Here's what it looks like (I reformatted it for readability, you're welcome):
#subsys_name hierarchy num_cgroups enabled
cpuset 4 1 1
cpu 8 1 1
cpuacct 8 1 1
blkio 6 1 1
memory 3 1 1
devices 10 84 1
freezer 7 1 1
net_cls 9 1 1
perf_event 5 1 1
net_prio 9 1 1
hugetlb 0 1 0
pids 2 1 1
The fields, reading left to right:
- [1] โ The name of the controller. That's it, that's the field.
- [2] โ The unique ID of the cgroup hierarchy this controller is mounted on. If multiple cgroups v1 controllers are bound to the same hierarchy, they all show the same hierarchy ID here. And this value will be
0if:- the controller isn't mounted on a cgroups v1 hierarchy;
- the controller is bound to the cgroups v2 single unified hierarchy; or
- the controller is disabled (see below, bestie).
- [3] โ How many control groups in this hierarchy are using this controller.
- [4] โ This one's
1if the controller is enabled, or0if it's been disabled (via thecgroup_disablekernel command-line boot parameter).
/proc/pid/cgroup(since Linux 2.6.24)
This file describes the control groups that the process with the matching PID belongs to. And ngl, the info shown is different depending on whether you're dealing with cgroups version 1 or version 2 hierarchies ๐
For every cgroup hierarchy the process is a member of, there's one entry with three colon-separated fields:
hierarchy-ID:controller-list:cgroup-path
For example:
5:cpuacct,cpu,cpuset:/daemons
The colon-separated fields, left to right:
- [1] โ For cgroups version 1 hierarchies, this field holds a unique hierarchy ID number that you can match up to a hierarchy ID in
/proc/cgroups. For the cgroups version 2 hierarchy, this field just holds the value0. - [2] โ For cgroups version 1 hierarchies, this field has a comma-separated list of the controllers bound to the hierarchy. For the cgroups version 2 hierarchy, this field is empty. Nothin' in it. Void. ๐
- [3] โ This field has the pathname of the control group in the hierarchy that the process belongs to. That pathname is relative to the mount point of the hierarchy. iykyk.
๐ง The /sys/kernel/cgroup files ๐ง
See sys_kernel_cgroup(5). That's the whole assignment for this one ๐
๐ SEE ALSO ๐
prlimit(1), systemd(1), systemd-cgls(1), systemd-cgtop(1), clone(2), ioprio_set(2), perf_event_open(2), setrlimit(2), cgroup_namespaces(7), cpuset(7), namespaces(7), sched(7), user_namespaces(7)
And the kernel source file Documentation/admin-guide/cgroup-v2.rst. Go read it, it's giving lore ๐
and that's a wrap on the whole cgroups(7) saga bestie!! ๐๐โจ this one came from Linux man-pages 6.20, dated 2026-09-09 โ you're officially a cgroup girlie now, go forth and mount responsibly ๐ฆ๐
๐ฆ where this page comes from
- arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
- nixpkgs unstable: man-pages 6.20
- ๐ the page's license: Linux-man-pages-copyleft
- Copyright 2016-2017, Michael Kerrisk <mtk.manpages@gmail.com>
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms