Files
finit/doc/config/cgroups.md
T
Joachim Wiberg 1843c24cda cgroup: move PID 1 out of the init/ group
A cgroup holding processes cannot enable controllers for its children,
so init/ had to stay a leaf.  The hotplug helpers 10-hotplug.conf.in
places there ended up in groups where cpu.weight and friends could
never be set.

Keeping PID 1 in the root cgroup makes init/ a domain like the others.
It also unbreaks lxc-based runtimes: liblxc bases the container tree on
PID 1's cgroup and only special-cases systemd's init.scope/, so under
Finit it landed containers in init/, with no controllers available.

Issue #497

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-12 07:18:40 +02:00

245 lines
7.9 KiB
Markdown

Finit has two `cgroup` blocks for controlling resource allocation:
1. **Top-level definition**, at file scope: declares a group such as
`init`, `system`, or `user`, and its default settings.
cgroup system { cpu.weight = 9700 }
2. **Joining a group**, inside a service block: names the group this
service runs in, and may override settings for itself alone.
service foo {
cgroup maint { memory.max = 1G }
command = "/path/to/cmd"
}
> [!NOTE]
> Linux cgroups and details surrounding values are not explained in the
> Finit documentation. The Linux admin-guide covers this well:
> <https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html>
Top-level Cgroup Definition
----------------------------
**Syntax:** `cgroup NAME { settings }`
# Top-level cgroups and their default settings. All groups mandatory
# but more can be added, max 8 groups in total currently. The cgroup
# 'root' is also available, reserved for RT processes. Settings are
# as-is, only one shorthand 'mem.' exists, other than that it's the
# cgroup v2 controller default names.
cgroup init { cpu.weight = 100 }
cgroup user { cpu.weight = 100 }
cgroup system { cpu.weight = 9800 }
Adding an extra cgroup `maint/` will require you to adjust the weight of
the above three. We leave `init/` and `user/` as-is reducing weight of
`system/` to 9700.
cgroup system { cpu.weight = 9700 }
# Example extra cgroup 'maint'
cgroup maint { cpu.weight = 100 }
By default, the `system/` cgroup is selected for almost everything. The
`init/` cgroup is reserved for the early boot helpers Finit drives
itself, like udev and mdev. PID 1 runs in the root cgroup, since a
cgroup holding processes cannot delegate controllers to its children.
The `user/` cgroup is for local TTY logins spawned by getty.
Joining a Cgroup
----------------
**Syntax:** `cgroup NAME { settings }` (inside a service block)
Every block says which group it joins, by name. An empty block joins
without changing anything:
service foo {
cgroup maint {}
command = "/path/to/foo args"
}
service bar {
cgroup maint {}
command = "/path/to/bar args"
}
Both services run in the `maint/` cgroup. Settings inside the block
apply to that service alone:
service foo {
cgroup maint {
cpu.max = 10000
memory.max = 655360
}
command = "/path/to/foo args"
}
> [!NOTE]
> The legacy format has a standalone `cgroup.NAME` line that selects a
> group for every stanza following it in the file, and a `cgroup:options`
> form that applies settings to whichever group is current without
> naming it. Neither has an equivalent here, by design: a block that
> joins a group says so itself, so the group cannot depend on what came
> earlier in the file.
Every cgroup setting maps directly to cgroup v2 syntax, so `cpu.max`
maps to the file `/sys/fs/cgroup/maint/foo/cpu.max`. There is no
filtering, the one exception being the shorthand `mem.`, which expands
to `memory.`. If the file is not available, either the controller is
missing from your Linux kernel, or the name is misspelled.
### Overriding Cgroup Leaf Names
By default, the cgroup leaf directory name is derived from the service
configuration filename (without the `.conf` extension). For example, a
service defined in `system/10-hotplug.conf` would create a cgroup at
`/sys/fs/cgroup/system/10-hotplug/` by default.
To use a more descriptive name (recommended for clarity), set `name`
inside the cgroup block:
service udevd {
description = "Device event daemon"
cgroup system { name = "udevd" }
command = "/lib/systemd/systemd-udevd"
}
This creates the cgroup at `/sys/fs/cgroup/system/udevd/` instead.
`name` combines with any other setting:
service udevd {
description = "Device event daemon"
cgroup system {
name = "udevd"
cpu.max = 10000
}
command = "/lib/systemd/systemd-udevd"
}
Or with delegation:
service podman {
description = "Podman API"
runlevel = "2345"
user = "podman"
group = "podman"
cgroup containers {
name = "podman"
delegate = true
memory.max = 4G
}
command = "/usr/bin/podman system service"
}
A daemon using `SCHED_RR` currently needs to run outside the default
cgroups.
service rt {
description = "Real-Time process"
cgroup root {}
command = "/path/to/daemon arg"
}
Cgroup Delegation
-----------------
For services that need to create their own child cgroups (container runtimes
like Docker, Podman, systemd-nspawn, LXC), set `delegate`:
service dockerd {
description = "Docker daemon"
runlevel = "2345"
user = "dockerd"
group = "dockerd"
cgroup system { delegate = true }
command = "/usr/bin/dockerd"
}
This allows the container runtime to:
- Create child cgroups for containers
- Manage controller settings for containers
- Move processes between cgroups
When delegation is enabled, Finit:
1. Creates the service cgroup as a **domain group** (not a leaf)
2. Enables all available controllers in `cgroup.subtree_control`
3. Changes ownership of delegation files to the service user
4. Moves the service process to the cgroup root
5. Lets the container runtime manage its own subdirectories
**Requirements:**
- The service should specify `user` and `group` for proper ownership
- Controllers are delegated from the parent cgroup
**Example with additional config:**
service podman {
description = "Podman API"
runlevel = "2345"
user = "podman"
group = "podman"
cgroup containers {
delegate = true
memory.max = 4G
}
command = "/usr/bin/podman system service"
}
This delegates the cgroup while also setting a 4GB memory limit.
**Container template example:**
Here's a real-world example from [Infix OS](https://github.com/kernelkit/infix)
for running rootful podman container instances using delegation:
sysv container:%i {
description = "container %i"
runlevel = "2345"
reload-signal = "none"
pidfile = "/run/container:%i.pid"
stop-timeout = 30
log { priority = "local1" identity = "%i" }
exec-start-pre = "/usr/sbin/container"
exec-start-pre-timeout = 0
exec-cleanup = "/usr/sbin/container"
exec-cleanup-timeout = 0
cgroup system { delegate = true }
command = "container -n %i"
}
This template uses `sysv` type with delegation, demonstrating that cgroup
delegation works with different service types, not just `service`.
**Cgroup structure with delegation:**
Initially, the service process runs directly in the cgroup root:
/sys/fs/cgroup/system/container@web/
├── cgroup.procs (service PID - owned by service user)
├── cgroup.subtree_control (+cpu +memory +io - owned by service user)
└── (container children will be created here)
Once the container runtime creates child cgroups (e.g., `libpod-*/`), cgroups v2
enforces the "no internal processes" rule. When Finit detects this (`EBUSY` error),
it automatically creates a `supervisor/` subdirectory and moves service-related
processes there:
/sys/fs/cgroup/system/container@web/
├── cgroup.procs (empty)
├── cgroup.subtree_control (+cpu +memory +io)
├── supervisor/ (service processes)
│ └── cgroup.procs (conmon PIDs, etc.)
└── libpod-$HASH/ (container processes)
└── cgroup.procs (container PIDs)
This happens automatically - no configuration needed. Without delegation, the
cgroup would be a leaf and the container runtime could not create child cgroups.