Read a Process's Cgroup Membership from /proc
You will finish with a reliable way to read the control group membership of a running process, interpret the three fields in each record, and check whether the host is presenting a cgroup v1 or v2 view. The examples use the local Linux man-pages package version 6.7-2, whose proc_pid_cgroup(5) page documents the file as available since Linux 2.6.24.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about ten minutes. You need a shell and a process ID. Reading the file is an ordinary, read-only operation and normally needs no elevated privileges. This guide does not move processes, create cgroups, write controller settings or restart services.
1. Choose the process to inspect
Start with your own shell. The special name self asks procfs for the process doing the read, so there is no race between finding a PID and inspecting it:
$ cat /proc/self/cgroup
0::/user.slice/user-1004.slice/[email protected]/session-42.scope
Your path will differ. A service, container or terminal session often has a different cgroup path, and a process can change groups while it is running. The path is not a universal label for the program. It describes the process's membership as seen from its cgroup namespace.
For a known PID, replace PID with digits from your own system:
$ PID=12345
$ test -r "/proc/$PID/cgroup" && cat "/proc/$PID/cgroup"
0::/system.slice/example.service
Keep the quotes around the path. They are harmless for a numeric PID and prevent accidental word splitting if you adapt the command for a different procfs path.
Checkpoint
You have selected a live PID and captured its current cgroup record. If the process exits, /proc/$PID/cgroup disappears and the command can fail with "No such file or directory". Select another live process rather than treating that as a cgroup error.
2. Parse the three colon-separated fields
Each cgroup entry has this shape:
hierarchy-ID:controller-list:cgroup-path
The first field identifies a cgroup v1 hierarchy. The second lists the controllers attached to that hierarchy, such as cpu or memory. The final field is the path of the process's cgroup relative to that hierarchy's root.
For example, a v1 record might look like this:
5:cpuacct,cpu,cpuset:/daemons
It says that the process is in the /daemons cgroup for a hierarchy with ID 5, where the listed controllers are grouped together. Do not infer a resource limit from the path alone. The limits and accounting settings live in the cgroup filesystem and in the controller files.
Do not split the line on every colon without allowing for exactly three fields. The controller list can contain commas, and the path is the value you need to preserve as a whole.
3. Recognise a cgroup v2 record
On a cgroup v2 system, the entry uses hierarchy ID 0 and an empty controller-list field. That produces two adjacent colons:
0::/user.slice/user-1004.slice/[email protected]/session-42.scope
The empty middle field does not mean that the process is outside a cgroup. It is the normal v2 representation of the single unified hierarchy. The path still tells you where the process is placed in that hierarchy.
Check the record without changing anything:
$ line=$(cat /proc/self/cgroup)
$ IFS=: read -r hierarchy controllers path <<< "$line"
$ printf 'hierarchy=%s\ncontrollers=%s\npath=%s\n' "$hierarchy" "$controllers" "$path"
hierarchy=0
controllers=
path=/user.slice/user-1004.slice/[email protected]/session-42.scope
This shell parsing example is only a display aid. It does not modify the process or its cgroup. If the first field is 0 and the second is empty, treat the result as a v2 record. If there are several lines, inspect each one: a host can expose v1 hierarchies, v2, or a combination depending on its kernel and configuration.
4. Compare two processes safely
To find out whether a worker shares your shell's cgroup, read both files and compare the text:
$ printf 'shell: '
$ cat /proc/self/cgroup
$ printf 'worker: '
$ cat "/proc/$PID/cgroup"
shell: 0::/user.slice/user-1004.slice/[email protected]/session-42.scope
worker: 0::/system.slice/example.service
Matching output means that the two reads reported the same hierarchy and path at those moments. It does not prove that their resource usage is identical, or that they share every security boundary. Cgroups organise processes for resource management and accounting; other kernel namespaces, permissions and service settings can still differ.
For a repeatable comparison, capture the files before interpreting them:
$ self_group=$(cat /proc/self/cgroup)
$ target_group=$(cat "/proc/$PID/cgroup")
$ [ "$self_group" = "$target_group" ] && echo 'same reported membership' || echo 'different reported membership'
There is no undo step because these commands only read procfs and set temporary shell variables. If the target PID is recycled between commands, repeat the check with a process that you know is still running.
5. Handle namespace and access surprises
A process in a cgroup namespace may see a path relative to that namespace's root rather than the host's full path. Two processes can therefore report different-looking paths even when they refer to related host-side groups. Conversely, a container may deliberately expose only the part of the hierarchy visible inside its namespace.
A missing file, a permission error or a disappearing PID is a reason to check the target, not to guess its membership. Verify the PID and retry:
$ kill -0 "$PID" 2>/dev/null && cat "/proc/$PID/cgroup" || echo 'PID is not available to this shell'
kill -0 does not send a signal, but it can still report failure when the PID does not exist or your user cannot address it. Reading another user's proc entry may also be restricted by the host's procfs and security configuration. Use elevated privileges only when your system administrator permits it and the inspection genuinely requires it. Do not turn a diagnostic read into a policy change by writing to cgroup files.
Done means
- You read
/proc/self/cgroupor the selected process's/proc/PID/cgroup. - You identified the hierarchy ID, controller list and cgroup path.
- You recognised
0::as the usual cgroup v2 record. - You accounted for a changing PID and a cgroup namespace before drawing conclusions.
- You made no persistent changes and needed no elevated privilege for the normal checks.