diff --git a/.gitignore b/.gitignore index 0341fa9..23d320c 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,7 @@ build src/03-led-controller/led-controller src/04-multiprocessing/multiprocessing src/04-multiprocessing/cgroups +src/04-multiprocessing/max-cpu src/05-optimization/ex01/basic src/05-optimization/ex01/optimized src/05-optimization/ex02/optimized diff --git a/doc/lab04-multiprocessing/main.typ b/doc/lab04-multiprocessing/main.typ index f1172dc..c24737a 100644 --- a/doc/lab04-multiprocessing/main.typ +++ b/doc/lab04-multiprocessing/main.typ @@ -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 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. -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 |> 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++) { // Allocate a block of memory - blocks[i] = malloc(BLOCK_SIZE); - + blocks[i] = malloc(BLOCK_SIZE); + // [...] // 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? -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 # 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 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] +) + +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] +) \ No newline at end of file diff --git a/doc/lab04-multiprocessing/max-cpu.png b/doc/lab04-multiprocessing/max-cpu.png new file mode 100644 index 0000000..3b376d0 Binary files /dev/null and b/doc/lab04-multiprocessing/max-cpu.png differ diff --git a/doc/lab04-multiprocessing/shared-cpu.png b/doc/lab04-multiprocessing/shared-cpu.png new file mode 100644 index 0000000..f170ebf Binary files /dev/null and b/doc/lab04-multiprocessing/shared-cpu.png differ diff --git a/src/04-multiprocessing/Makefile b/src/04-multiprocessing/Makefile index 295dd75..bb405a7 100644 --- a/src/04-multiprocessing/Makefile +++ b/src/04-multiprocessing/Makefile @@ -1,11 +1,14 @@ - -cgroups: - $(MAKE) EXE=cgroups SRCS=cgroups.c all - process: $(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 ../appl.mk diff --git a/src/04-multiprocessing/justfile b/src/04-multiprocessing/justfile index 701a012..4532e3c 100644 --- a/src/04-multiprocessing/justfile +++ b/src/04-multiprocessing/justfile @@ -10,8 +10,12 @@ @cgroups: make cgroups +@max-cpu: + make max-cpu + @clean: rm -rf build rm -rf .obj rm -f -- multiprocessing rm -f -- cgroups + rm -f -- max-cpu diff --git a/src/04-multiprocessing/max-cpu.c b/src/04-multiprocessing/max-cpu.c new file mode 100644 index 0000000..8feb396 --- /dev/null +++ b/src/04-multiprocessing/max-cpu.c @@ -0,0 +1,70 @@ +#define _GNU_SOURCE + +#include +#include +#include + +#include +#include + +#include + +#include + + +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; +} diff --git a/src/04-multiprocessing/max-cpu.sh b/src/04-multiprocessing/max-cpu.sh new file mode 100755 index 0000000..861566c --- /dev/null +++ b/src/04-multiprocessing/max-cpu.sh @@ -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 diff --git a/src/04-multiprocessing/shared-cpu.sh b/src/04-multiprocessing/shared-cpu.sh new file mode 100755 index 0000000..adfb6d3 --- /dev/null +++ b/src/04-multiprocessing/shared-cpu.sh @@ -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