1
0

feat(lab04): add cgroups CPU

This commit is contained in:
2026-05-28 20:53:41 +02:00
parent 64b8cad953
commit 8aa79f62b7
9 changed files with 325 additions and 10 deletions

1
.gitignore vendored
View File

@@ -60,6 +60,7 @@ build
src/03-led-controller/led-controller src/03-led-controller/led-controller
src/04-multiprocessing/multiprocessing src/04-multiprocessing/multiprocessing
src/04-multiprocessing/cgroups src/04-multiprocessing/cgroups
src/04-multiprocessing/max-cpu
src/05-optimization/ex01/basic src/05-optimization/ex01/basic
src/05-optimization/ex01/optimized src/05-optimization/ex01/optimized
src/05-optimization/ex02/optimized src/05-optimization/ex02/optimized

View File

@@ -98,13 +98,13 @@ SIGINT received
The @multiprocessus shows the PID and the core of the processus and they can be compared to the output of the executable before. The @multiprocessus shows the PID and the core of the processus and they can be compared to the output of the executable before.
The child processus has the PID 273 and the core 0. The parent processus has th PID 274 and the core 1. The child processus has the PID 273 and the core 0. The parent processus has th PID 274 and the core 1.
== CGroups == CGroups memory
The goal of this part is to understand how to use cgroups to limit the resources of a process. We will initially focus on memory, but cgroups can also be used to limit CPU, I/O, and other ressources. The goal of this part is to understand how to use cgroups to limit the resources of a process. We will initially focus on memory, but cgroups can also be used to limit CPU, I/O, and other ressources.
For limit the memory usage of a process, we cans use the `memory` subsystem of cgroups. We use cgroup v1 with our Nanopi. For limit the memory usage of a process, we cans use the `memory` subsystem of cgroups. We use cgroup v1 with our Nanopi.
We must first mount a temporary filesystem for cgroups: We must first mount a temporary filesystem for cgroups:
```bash ```bash
|> mount -t tmpfs none /sys/fs/cgroup |> mount -t tmpfs none /sys/fs/cgroup
``` ```
@@ -138,8 +138,8 @@ We can then run our test program that allocates memory in a loop and see what ha
for (i = 0; i < NUM_BLOCKS; i++) { for (i = 0; i < NUM_BLOCKS; i++) {
// Allocate a block of memory // Allocate a block of memory
blocks[i] = malloc(BLOCK_SIZE); blocks[i] = malloc(BLOCK_SIZE);
// [...] // [...]
// check if failed and error, clean and exit // check if failed and error, clean and exit
@@ -176,7 +176,7 @@ It's possible to modify this behavior in several ways:
=== How to watch the memory usage? === How to watch the memory usage?
We can monitor the memory usage of a cgroup by reading it directly from the file in the specific cgroups: We can monitor the memory usage of a cgroup by reading it directly from the file in the specific cgroups:
```bash ```bash
# Current memory usage in bytes # Current memory usage in bytes
@@ -187,3 +187,87 @@ We can monitor the memory usage of a cgroup by reading it directly from the file
|> cat /sys/fs/cgroup/memory/0/memory.max_usage_in_bytes |> cat /sys/fs/cgroup/memory/0/memory.max_usage_in_bytes
20971520 20971520
``` ```
== CGroups CPU
To check this part, we need a tiny program that consumes CPU with at least two process.
The following program creates a child process that performs CPU intensive work, while the parent process also performs CPU intensive work. We can then use cgroups to limit the CPU usage of one of the processes and observe the effect.
```c
int main() {
pid_t pid = fork();
if (pid == 0) {
cpu_intensive_work("Child process");
exit(0);
} else {
cpu_intensive_work("Parent process");
wait(NULL);
return 0;
}
}
```
Based on previous exercice, we should already have mounted the cgroup filesystem.
```bash
|> mount -t tmpfs none /sys/fs/cgroup
```
We can then create and mount the cgroup filesystem for the `cpuset` subsystem
```bash
# Create a directory for the cpuset cgroup
|> mkdir /sys/fs/cgroup/cpuset
# Mount the cgroup filesystem with cpuset
|> mount -t cgroup -o cpu,cpuset cpuset /sys/fs/cgroup/cpuset
```
Now we had the prerequirements, we can create 2 groupes. One for each of our running programme. With the following command, we attribute on ore more CPU to each group (`cpuset.cpus`). I'm not sure about the `cpuset.mems` file, but it seems to be related to memory nodes. It's definetly a topic that should be explored more in depth, but for now, we set to `0` as specified in the lab instructions.
```bash
# Create and allocate CPU for programme "low"
|> mkdir /sys/fs/cgroup/cpuset/low
|> echo 1 > /sys/fs/cgroup/cpuset/low/cpuset.cpus
|> echo 0 > /sys/fs/cgroup/cpuset/low/cpuset.mems
# Create and allocate CPU for programme "high"
|> mkdir /sys/fs/cgroup/cpuset/high
|> echo 2,3 > /sys/fs/cgroup/cpuset/high/cpuset.cpus
|> echo 0 > /sys/fs/cgroup/cpuset/high/cpuset.mems
```
We can then open 2 shells and run the test program in each of them, while adding the programme to the corresponding cgroup:
```bash
# In the first shell, add it on the "low" cgroup and run the test program
|> . ./max-cpu.sh low
# In the second shell, add it on the "high" cgroup and run the test program
|> . ./max-cpu.sh high
```
We see on @max-cpu that as expected, both process in program _low_ is limited to CPU 1, while the programm _high_ is using CPU 2 and 3, one for each process.
#figure(
image("max-cpu.png"),
caption: [CPU usage of the two programmes with dedicated resources]
)<max-cpu>
To share resources at 75% and 25%, we can use the `cpu.shares` file in the `cpu` cgroup. We attribute a value 3 time high for the _high_ group than for the _low_ group.
```bash
|> echo 75 > /sys/fs/cgroup/cpu/high/cpu.shares
|> echo 25 > /sys/fs/cgroup/cpu/low/cpu.shares
```
Then running the test program in each shell, we see on @shared-cpu that the _high_ process is limited to 75% of the CPU, while the _low_ process is limited to 25%.
```bash
# In the first shell, add it on the "low" cgroup and run the test program
|> . ./shared-cpu.sh low
# In the second shell, add it on the "high" cgroup and run the test program
|> . ./shared-cpu.sh high
```
#figure(
image("shared-cpu.png"),
caption: [CPU usage of the two programmes with shared resources]
)<shared-cpu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

@@ -1,11 +1,14 @@
cgroups:
$(MAKE) EXE=cgroups SRCS=cgroups.c all
process: process:
$(MAKE) EXE=multiprocessing SRCS=process.c all $(MAKE) EXE=multiprocessing SRCS=process.c all
cgroups:
$(MAKE) EXE=cgroups SRCS=cgroups.c all
max-cpu:
$(MAKE) EXE=max-cpu SRCS=max-cpu.c all
# Include the standard application Makefile for the CSEL1 labs # Include the standard application Makefile for the CSEL1 labs
include ../appl.mk include ../appl.mk

View File

@@ -10,8 +10,12 @@
@cgroups: @cgroups:
make cgroups make cgroups
@max-cpu:
make max-cpu
@clean: @clean:
rm -rf build rm -rf build
rm -rf .obj rm -rf .obj
rm -f -- multiprocessing rm -f -- multiprocessing
rm -f -- cgroups rm -f -- cgroups
rm -f -- max-cpu

View File

@@ -0,0 +1,70 @@
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sched.h>
int fork(void);
volatile unsigned long counter = 0;
void cpu_intensive_work(const char *process_name) {
printf("%s (PID: %d) starting CPU-intensive work on CPU %d\n", process_name, getpid(), sched_getcpu());
while (1) {
counter++;
counter = (counter * counter + counter) % (counter * 1023);
}
}
void print_usage(const char *prog) {
fprintf(stderr, "Usage: %s [--single] [--dual]\n", prog);
fprintf(stderr, " --single Run single process (default)\n");
fprintf(stderr, " --dual Run two processes\n");
}
int main(int argc, char *argv[]) {
int use_dual = 0;
if (argc > 1) {
if (strcmp(argv[1], "--dual") == 0) {
use_dual = 1;
} else if (strcmp(argv[1], "--single") == 0) {
use_dual = 0;
} else {
fprintf(stderr, "Unknown option: %s\n", argv[1]);
print_usage(argv[0]);
return 1;
}
}
if (use_dual) {
// Dual-process mode
pid_t pid = fork();
if (pid == 0) {
cpu_intensive_work("Child process");
exit(0);
} else if (pid > 0) {
cpu_intensive_work("Parent process");
wait(NULL);
return 0;
} else {
perror("fork failed");
return 1;
}
} else {
// Single-process mode
cpu_intensive_work("Process");
}
return 0;
}

View File

@@ -0,0 +1,57 @@
#!/bin/sh
usage() {
echo "Usage: $0 {init|high|low}"
echo " init - Initialize cgroup filesystem and cpuset groups"
echo " high - Run ./max-cpu --dual in the 'high' cgroup (CPUs 2,3)"
echo " low - Run ./max-cpu --dual in the 'low' cgroup (CPU 1)"
exit 1
}
if [ $# -ne 1 ]; then
usage
fi
case "$1" in
init)
echo "Initializing cgroup filesystem..."
# Mount tmpfs for cgroup
mount -t tmpfs none /sys/fs/cgroup 2>/dev/null
# Create and mount cpuset cgroup
mkdir -p /sys/fs/cgroup/cpuset
mount -t cgroup -o cpu,cpuset cpuset /sys/fs/cgroup/cpuset 2>/dev/null
# Create "low" cgroup and allocate CPU 1
mkdir -p /sys/fs/cgroup/cpuset/low
echo 1 > /sys/fs/cgroup/cpuset/low/cpuset.cpus
echo 0 > /sys/fs/cgroup/cpuset/low/cpuset.mems
# Create "high" cgroup and allocate CPUs 2,3
mkdir -p /sys/fs/cgroup/cpuset/high
echo 2,3 > /sys/fs/cgroup/cpuset/high/cpuset.cpus
echo 0 > /sys/fs/cgroup/cpuset/high/cpuset.mems
echo "Cgroup initialization complete."
echo " - low group: CPU 1"
echo " - high group: CPUs 2,3"
;;
high)
echo "Running ./max-cpu --dual in 'high' cgroup (CPUs 2,3)..."
echo $$ > /sys/fs/cgroup/cpuset/high/tasks
./max-cpu --dual
;;
low)
echo "Running ./max-cpu --dual in 'low' cgroup (CPU 1)..."
echo $$ > /sys/fs/cgroup/cpuset/low/tasks
./max-cpu --dual
;;
*)
echo "Unknown command: $1"
usage
;;
esac

View File

@@ -0,0 +1,96 @@
#!/bin/sh
usage() {
echo "Usage: $0 {init|high|low}"
echo " ./shared-cpu.sh init - Initialize cgroup filesystem with cpu.shares groups"
echo " . ./shared-cpu.sh high - Run ./max-cpu --single in 'high' cgroup (75% CPU share on CPU 0)"
echo " . ./shared-cpu.sh low - Run ./max-cpu --single in 'low' cgroup (25% CPU share on CPU 0)"
exit 1
}
if [ $# -ne 1 ]; then
usage
fi
# Helper to check if a directory is already mounted
is_mounted() {
mount | grep -q " $1 "
}
case "$1" in
init)
echo "Initializing cgroup filesystem for CPU shares..."
# Mount tmpfs for cgroup if not already mounted
if ! is_mounted /sys/fs/cgroup; then
echo "Mounting /sys/fs/cgroup..."
mount -t tmpfs none /sys/fs/cgroup || {
echo "ERROR: Failed to mount /sys/fs/cgroup"
exit 1
}
else
echo "/sys/fs/cgroup is already mounted."
fi
# Create and mount cpuset cgroup if not already mounted
mkdir -p /sys/fs/cgroup/cpuset
if ! is_mounted /sys/fs/cgroup/cpuset; then
echo "Mounting cpuset/cpu cgroup..."
mount -t cgroup -o cpu,cpuset cpuset /sys/fs/cgroup/cpuset || {
echo "ERROR: Failed to mount cpuset cgroup! Perhaps 'cpu' and 'cpuset' are already mounted separately elsewhere?"
exit 1
}
else
echo "/sys/fs/cgroup/cpuset is already mounted."
fi
# Create "low" cgroup with 25% share
echo "Configuring 'low' cgroup..."
mkdir -p /sys/fs/cgroup/cpuset/low
echo 0 > /sys/fs/cgroup/cpuset/low/cpuset.cpus || echo "WARNING: Failed to write cpuset.cpus for low cgroup"
echo 0 > /sys/fs/cgroup/cpuset/low/cpuset.mems || echo "WARNING: Failed to write cpuset.mems for low cgroup"
echo 25 > /sys/fs/cgroup/cpuset/low/cpu.shares || echo "WARNING: Failed to write cpu.shares for low cgroup"
# Create "high" cgroup with 75% share
echo "Configuring 'high' cgroup..."
mkdir -p /sys/fs/cgroup/cpuset/high
echo 0 > /sys/fs/cgroup/cpuset/high/cpuset.cpus || echo "WARNING: Failed to write cpuset.cpus for high cgroup"
echo 0 > /sys/fs/cgroup/cpuset/high/cpuset.mems || echo "WARNING: Failed to write cpuset.mems for high cgroup"
echo 75 > /sys/fs/cgroup/cpuset/high/cpu.shares || echo "WARNING: Failed to write cpu.shares for high cgroup"
echo "Cgroup initialization complete."
echo " - low group: 25 shares (25% CPU on CPU 0)"
echo " - high group: 75 shares (75% CPU on CPU 0)"
;;
high)
echo "Running ./max-cpu --single in 'high' cgroup (75% CPU share)..."
if [ ! -f /sys/fs/cgroup/cpuset/high/tasks ]; then
echo "ERROR: Cgroup is not initialized or mounted! Run './shared-cpu.sh init' first."
exit 1
fi
echo $$ > /sys/fs/cgroup/cpuset/high/tasks || {
echo "ERROR: Failed to add shell process ($$) to high cgroup!"
exit 1
}
./max-cpu --single
;;
low)
echo "Running ./max-cpu --single in 'low' cgroup (25% CPU share)..."
if [ ! -f /sys/fs/cgroup/cpuset/low/tasks ]; then
echo "ERROR: Cgroup is not initialized or mounted! Run './shared-cpu.sh init' first."
exit 1
fi
echo $$ > /sys/fs/cgroup/cpuset/low/tasks || {
echo "ERROR: Failed to add shell process ($$) to low cgroup!"
exit 1
}
./max-cpu --single
;;
*)
echo "Unknown command: $1"
usage
;;
esac