Linux Cgroups
Every thought of how linux organizes processes in parent-child relationship and how resources are managed and controlled efficiently like: limiting CPU usage, memory,etc. Cgroup in linux is the same technology that empowers Dockers and containers and the interesting part is — you can try it yourself right now.
A Linux machine can have dozens of processes competing for the same CPU and memory. Without some way to organize them, one badly behaved program can consume far more than its fair share.
Linux cgroups, short for control groups, solve this by letting the kernel put processes into groups and apply resource rules to those groups. The idea is simple: instead of asking only “How much memory is this process using?”, Linux can also ask “How much memory is this whole group of processes allowed to use?”
This is one of the important pieces behind containers, but cgroups are useful even when you never run a container.
Why Does Linux Need Cgroups?
Imagine a university server shared by two groups:
- professors
- students
Both groups run programs on the same machine.
If everyone is allowed to use resources without any limits, one group could consume almost all of the CPU or memory and leave very little for everyone else.
So the administrator might decide:
University Server
CPU
├── Professors → larger share
└── Students → smaller share
Memory
├── Professors → larger limit
└── Students → smaller limitThe important part is that the administrator is controlling groups of work, not manually controlling every program one by one.
This is the simple version of the Linux kernel’s professor/student example: the same process can be classified differently for different resources. For example, a professor’s browser and a student’s browser might both be browsers, but their CPU or memory rules can be different depending on which group they belong to.
What Does a Cgroup Actually Do?
A cgroup gives the kernel two things:
- A group of processes to keep track of
- Rules about resources for that group
The resources are controlled by controllers.
Some important controllers include:
| Controller | What it controls |
|---|---|
cpu | CPU usage and scheduling |
memory | Memory usage and limits |
io | Block-device I/O |
pids | Number of processes |
The exact controls available depend on the Linux kernel and cgroup version.
So a useful mental model is:
Processes
↓
cgroup
↓
resource controllers
↓
CPU / Memory / I/O / Process countCgroups do not create another computer.
The processes are still ordinary Linux processes. They simply belong to a group that the kernel can control and account for.
How Does a Cgroup Work?
Think of a cgroup as a room with rules.
Suppose we create:
student-group
├── terminal
├── compiler
└── browserAll of those processes are still running on the same Linux machine.
But the kernel now knows:
These processes belong to
student-group.
We can then give that group a memory limit:
student-group
│ │
│ ├── terminal
│ ├── compiler
│ └── browser
│
▼
Memory limit
500 MBThe limit applies to the group’s processes together, rather than being a separate 500 MB allowance for every process.
Cgroups Are Hierarchical
Cgroups can also be nested:
system
└── university
├── professors
│ ├── teaching
│ └── research
└── students
├── projects
└── labsThis is useful when a large machine needs several levels of resource management.
A child group operates within the limits imposed by its parents.
What Happens to Child Processes?
A useful property of cgroups is that processes can bring their children into the same group.
For example:
terminal
│
├── compiler
└── test programIf the terminal is placed into a cgroup and it creates those child processes, they can inherit the cgroup membership.
This is important because otherwise a program could simply create another process and escape the resource accounting.
Controllers Are the Actual Resource Rules
The cgroup itself is mainly the grouping (processes) mechanism.
Controllers provide the resource-specific behavior.
cgroup
│
┌───────────┼───────────┐
▼ ▼ ▼
CPU Memory I/O
controller controller controller
│ │ │
▼ ▼ ▼
CPU rules RAM rules disk I/O rulesThis separation is why cgroups can control different kinds of resources without turning the whole feature into one giant rule system.
Where Do You Find Cgroups on Linux?
Modern Linux systems generally use cgroup v2, although some systems and software may still expose or use cgroup v1.
The two versions are different interfaces, so don’t mix their commands and files.
For this lesson, we’ll use cgroup v2.
You can check whether your system is using cgroup v2 with:
mount | grep cgroupA cgroup v2 mount normally looks similar to:
cgroup2 on /sys/fs/cgroup type cgroup2You can also inspect the filesystem:
ls /sys/fs/cgroupOn a cgroup v2 system, you’ll see files such as:
cgroup.procs
cgroup.controllers
cgroup.subtree_control
memory.current
memory.max
cpu.max
pids.maxThe names are useful because they make the purpose fairly obvious.
Why Are These Files There?
Cgroups expose their configuration through a virtual filesystem.
You create a cgroup by creating a directory inside this virtual filesystem.
For example:
sudo mkdir /sys/fs/cgroup/my-groupNow this directory is not an ordinary folder containing application files.
It represents a cgroup.
Inside it, the kernel exposes files for:
- which processes belong to the group
- which controllers are available
- resource limits
- current resource usage
- other cgroup settings
This is a recurring Linux idea:
filesystem interface
↓
kernel featureYou are reading and writing files, but the files represent kernel state.
Exercise: Let’s Limit Memory for a Process
Now let’s create a small cgroup ourselves.
We’ll use memory because it makes the effect easy to see.
Warning
This exercise changes kernel resource-control settings and normally requires sudo. Use a Linux machine where you have permission to create cgroups. The example assumes cgroup v2 is mounted at /sys/fs/cgroup.
Before Cgroup Implementation
We are using this simple C program that allocates 200MB and exits. Create memory.c for this:
Note
You don’t need to know C to understand the concept of Cgroups. This is just a demo program. It can be any program.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
size_t size = 200 * 1024 * 1024;
char *memory = malloc(size);
if (memory == NULL) {
perror("malloc");
return 1;
}
printf("HI Before memset!\n");
memset(memory, 0, size);
printf("Allocated and touched 200 MB. Press Enter to exit...\n");
getchar();
free(memory);
return 0;
}Compile and run:
gcc memory.c -o memory
./memoryThis should successfuly output:
HI Before memset!
Allocated and touched 200 MB. Press Enter to exit...You can press Enter to exit the program. What this shows is 200 MB is happily allocated and touched (written). Now with the Cgroup exercise below, we are going to restrict this usage to 100MB only.
Create a Memory-Limited Cgroup
First create the group:
sudo mkdir /sys/fs/cgroup/memory-demoNow give it a 100MB memory limit:
# Set RAM limit to 10MB
echo $((100 * 1024 * 1024)) | sudo tee /sys/fs/cgroup/memory-demo/memory.maxBecause OS can also make use of swap memory when main memory is not available, set swap memory limit anything below 100MB. This is because our C program is allocating 200MB but 100MB is allowed by main memory. Let me go with 99MB of hard limit for swap memory, so that in total will be 199MB which is still 1MB less than what is required.
# Force Swap limit to ZERO for this cgroup
echo $((99 * 1024 * 1024)) | sudo tee /sys/fs/cgroup/memory-demo/memory.swap.maxCheck the value:
cat /sys/fs/cgroup/memory-demo/memory.max
cat /sys/fs/cgroup/memory-demo/memory.swap.maxYou should see:
104857600
103809024The value is in bytes.
So we have:
memory-demo
│
|── memory.smax = 100 MB
└── memory.swap.max = 200 MBPut a Process Into the Cgroup
The cgroup.procs file contains the processes belonging to the cgroup.
To run a process inside the group, we can start a shell and then move that shell into the cgroup.
Important
We are moving shell to cgroup because we will be executing the C program from within the shell. This means our memory program will be child process of the shell and we already know child inherits the rule (cgroup membership) of parent.
First get the shell’s PID:
echo $$Then attach the current shell:
echo $$ | sudo tee /sys/fs/cgroup/memory-demo/cgroup.procsCheck:
cat /sys/fs/cgroup/memory-demo/cgroup.procsYou should see the shell’s PID.
From this point, processes created by that shell can be part of the cgroup as well.
Check the Memory Usage
You can see the group’s current memory usage with:
# main memory usage
cat /sys/fs/cgroup/memory-demo/memory.current
# swap memory usage
cat /sys/fs/cgroup/memory-demo/memory.swap.currentYou can also see the limit:
# max limit of main memory
cat /sys/fs/cgroup/memory-demo/memory.max
# max limit of swap memory
cat /sys/fs/cgroup/memory-demo/memory.swap.maxThese files are saying:
memory.current → how much memory the group is currently using
memory.max → how much memory the group is allowed to use
memory.swap.current → how much swap memory the group is currently using
memory.swap.max → how much swap memory the group is allowed to useCreate a Process That Uses a Lot of Memory
Now let’s see if our memory program will allocate 200MB or not:
./memoryYou will get:
HI Before memset!
KilledThe program was ok before
memseti.e. before it tries to write 200MB but when it tries,Cgrouppolicy doesn’t allow it.The program tries to keep the allocated memory alive until you press Enter. But our
Cgroupallows only about 100MB and swap is 99MB, combining both is only 199MB — still 1MB less than what’s requried and that was enough for the program to crash.
Warning
There may be different mechanism how system allocates memory depending on OS and how your programming language interacts with OS and the program may not always crash — but you get the idea.
Conceptually:
`./memory` process (child of the shell process)
│
▼
memory-demo cgroup
│
├── current usage
│
└── maximum = 100 MB or 199 MB with swap
│
▼
process asks for
~200 MB
│
▼
limit is reachedDepending on the rest of the system and the exact point at which the limit is reached, the process may be killed by the kernel’s memory controller rather than successfully reaching 200 MB.
That’s the important observation:
The process doesn’t get a private 100 MB machine. The cgroup tells the kernel that the group cannot exceed its configured memory limit.
Observe the Result
After the test, inspect:
cat /sys/fs/cgroup/memory-demo/memory.eventsYou may see counters such as:
low 0
high 0
max 829
oom 1
oom_kill 1
oom_group_kill 0Note
The exact values depend on what happened during the experiment. These counters are useful because they let you see that the memory controller actually encountered the configured limit.
memory.peak records the highest memory usage reached by the cgroup since it was created (or since the counter was last reset). Same thing applies for swap memory usage as memory.swap.peak. You can inspect these values:
# peak memory usage since creation
cat /sys/fs/cgroup/memory-demo/memory.peak
# peak swap memory usage since creation
cat /sys/fs/cgroup/memory-demo/memory.swap.peakI think you are already guessing the values:
104857600
103809024These are the same max limits you configured before and ./memory program explodes them all.
What Did We Actually Do?
The entire exercise was basically:
Create cgroup
↓
Set memory.max
↓
Put process in cgroup
↓
Process uses memory
↓
Kernel enforces the limitNotice that we never changed the C program to “understand” cgroups. The program doesn’t need to know. The kernel enforces the rule from outside the program.
What Else Can Cgroups Control?
Memory is only one example.
CPU
The CPU controller can control how much CPU time a group receives or how it is weighted relative to other groups.
Conceptually:
CPU
├── important-work → more CPU
└── background-work → less CPUThe exact controls in cgroup v2 are exposed through files such as cpu.max and cpu.weight.
Process Count
The pids controller can limit how many processes a group can create.
This is useful for preventing a runaway program from creating thousands of processes.
For example:
application-group
│
└── pids.max = 100The group cannot simply keep creating processes forever.
Disk I/O
The io controller can control or account for block-device I/O.
This is different from limiting how much disk space a process can store.
For example:
I/O limit
→ how quickly / how much data can be read or written
Disk capacity
→ how much data can actually be storedCgroups are primarily about controlling resources such as CPU, memory, I/O, and process count. They are not a general-purpose “give this process only 1 GB of disk space” mechanism.
That distinction matters because it is easy to confuse disk I/O limits with disk space quotas.
Why Do Containers Care About Cgroups?
Now the connection to containers becomes straightforward.
A container is still made from ordinary Linux processes.
For example:
Container
├── web server
├── worker
└── helper processWithout resource controls, those processes could compete with everything else on the host.
Cgroups let the container runtime put those processes into a group and give that group resource limits.
Conceptually:
Linux Host
│
├── Container A
│ ├── process
│ └── process
│ └── cgroup A
│ ├── memory limit
│ ├── CPU rules
│ └── process limit
│
└── Container B
├── process
└── process
└── cgroup B
├── memory limit
├── CPU rules
└── process limitThis is why a container can be given something like:
Memory: 512 MB
CPU: limited
PIDs: limitedwithout the application itself knowing anything about Docker or cgroups.
The container runtime configures the kernel; the kernel enforces the rules.
Cgroups vs Namespaces
This distinction is worth remembering because both are important to containers:
Namespaces
→ "What can this process see?"
Cgroups
→ "How much can this group use?"For example:
Namespace
→ hide or isolate processes
Cgroup
→ limit their memoryThey solve different problems and work together.
The Mental Model to Keep
Think of a cgroup as a resource-controlled room for processes.
Linux Host
│
┌──────────┴──────────┐
│ │
normal work cgroup room
│
┌──────────┼──────────┐
│ │ │
process process process
│ │ │
└──────────┼──────────┘
│
resource rules
├── CPU
├── memory
├── I/O
└── process countThe three ideas worth remembering are:
1. Cgroup = group of processes
2. Controllers = resource-specific rules
3. Kernel = enforces those rulesOr, even more simply:
Namespaces control what a process can see. Cgroups control how much a group of processes can use.
That combination is a major part of what makes Linux containers possible.
