doc: proofread
This commit is contained in:
@@ -35,6 +35,7 @@
|
||||
date: date,
|
||||
tableof: tableof,
|
||||
)
|
||||
#show link: set text(fill: blue.darken(60%))
|
||||
#v(5em)
|
||||
#infobox()[
|
||||
The repository for this labs can be found at the following address:
|
||||
@@ -45,16 +46,13 @@
|
||||
|
||||
//-------------------------------------
|
||||
// Content
|
||||
//
|
||||
//
|
||||
|
||||
#include "lab03-silly_led/main.typ"
|
||||
#include "lab04-multiprocessing/main.typ"
|
||||
#include "lab05-optimization/main.typ"
|
||||
|
||||
|
||||
//--------------------------------------
|
||||
|
||||
|
||||
//-------------------------------------
|
||||
// Glossary
|
||||
//
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
= Linux System Programming
|
||||
|
||||
This laboratory implements a user-space application for the NanoPi NEO Plus2 that controls the blinking frequency of the status LED using three push buttons. The main goal was to replace a CPU-intensive busy loop with an event-driven design.
|
||||
This laboratory implements a user-space application for the NanoPi NEO Plus2 that controls the blinking frequency of the status #gls("led", long: false) using three push buttons. The main goal was to replace a #gls("cpu", long: false)-intensive busy loop with an event-driven design.
|
||||
|
||||
|
||||
== Design
|
||||
The application is based on multithreading: one thread handles the LED timing, while another handles button events. GPIOs are accessed through sysfs, which allows the LED and buttons to be managed as file descriptors. A key design choice was to centralize all events with a single epoll instance, so both timer events and button events can be processed efficiently.
|
||||
The application is based on multithreading: one thread handles the #gls("led", long: false) timing, while another handles button events. #gls("gpio", long: false) are accessed through #gls("sysfs", long: false), which allows the #gls("led", long: false) and buttons to be managed as file descriptors. A key design choice was to centralize all events with a single #gls("epoll", long: false) instance, so both timer events and button events can be processed efficiently.
|
||||
|
||||
The timer thread use only 1 timer and set the initial time on every cycle. That allow to allocate only once the timer and avoid memory fragmentation. The button thread write write the next time to sleep on a shared variable, and the timer thread read this variable to set the next time to sleep. Since we have only one provider of this variable, we don't need to use a mutex to protect it.
|
||||
The timer thread use only 1 timer and set the initial time on every cycle. That allow to allocate only once in the timer and avoid memory fragmentation. The button thread writes the next time to sleep on a shared variable, and the timer thread read this variable to set the next time to sleep. Since we have only one provider of this variable, we don't need to use a mutex to protect it.
|
||||
|
||||
|
||||
All logs are done using the syslog at info level:
|
||||
@@ -18,15 +18,15 @@ All logs are done using the syslog at info level:
|
||||
// LOG_USER to specify the log facility (what type of programme)
|
||||
openlog("CSEL Logs", LOG_PID, LOG_USER);
|
||||
|
||||
// Then log what you want:
|
||||
// Then log what you want:
|
||||
syslog(LOG_INFO, "Start logging silly led-controller"); // INFO level
|
||||
```
|
||||
|
||||
== Difficulties
|
||||
The most difficult part was understanding the GPIO mapping between the physical pins and the sysfs GPIO numbers. All can be found in the #link("https://linux-sunxi.org/GPIO", [*sunxi driver*]) which is the driver for GPIO.
|
||||
The most difficult part was understanding the #gls("gpio", long: false) mapping between the physical pins and the #gls("sysfs", long: false) #gls("gpio", long: false) numbers. All can be found in the #link("https://linux-sunxi.org/GPIO", [*sunxi driver*]) which is the driver for #gls("gpio", long: false).
|
||||
|
||||
== Results
|
||||
We can demonstracte that the application works in an efficient than the silly led controller given:
|
||||
We can demonstrate that the application works in an efficient than the silly #gls("led", long: false) controller given:
|
||||
|
||||
#table(
|
||||
columns: (1fr, 1fr),
|
||||
@@ -35,16 +35,15 @@ We can demonstracte that the application works in an efficient than the silly le
|
||||
[
|
||||
#figure(
|
||||
image("test-silly.png", height: 10em),
|
||||
caption:[Run silly led controller on nanopi]
|
||||
caption:[Run silly #gls("led", long: false) controller on NanoPi]
|
||||
)<fig-silly>
|
||||
|
||||
|
||||
],[
|
||||
#figure(
|
||||
image("test-epoll.png", height: 10em),
|
||||
caption:[Run epoll led controller on nanopi]
|
||||
caption:[Run #gls("epoll", long: false) #gls("led", long: false) controller on NanoPi]
|
||||
)<fig-epoll>
|
||||
]
|
||||
)
|
||||
|
||||
We can see the difference between @fig-silly and @fig-epoll. One is using a core at 100% and the other one not.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
== Process, signals, and communication
|
||||
|
||||
The aim of this laboratory is to create a child process from the parent with `fork()`. Then, each processus executes the same code until they are killed. This happens the same when programming GPU with CUDA or OpenMP. The different processus are differenciated by the PID (Process ID).
|
||||
The aim of this laboratory is to create a child process from the parent with `fork()`. Then, each processus executes the same code until they are killed. This happens the same when programming #gls("gpu", long: false) with #gls("cuda", long: false) or #gls("openmp", long: false). The different processus are differenciated by the #gls("pid", long: false).
|
||||
|
||||
The child must communicate with the parents with a `socketpair`:
|
||||
```c
|
||||
@@ -95,21 +95,21 @@ SIGINT received
|
||||
)<multiprocessus>
|
||||
|
||||
|
||||
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 @multiprocessus shows the #gls("pid", long: false) and the core of the processus and they can be compared to the output of the executable before.
|
||||
The child processus has the #gls("pid", long: false) 273 and the core 0. The parent processus has the #gls("pid", long: false) 274 and the core 1.
|
||||
|
||||
== CGroups memory
|
||||
== #gls("cgroups", long: false) 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 #gls("cgroups", long: false) to limit the resources of a process. We will initially focus on memory, but #gls("cgroups", long: false) can also be used to limit #gls("cpu", long: false), #gls("io", long: false), 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 #gls("cgroups", long: false). We use #gls("cgroups", long: false) v1 with our Nanopi.
|
||||
|
||||
We must first mount a temporary filesystem for cgroups:
|
||||
We must first mount a temporary filesystem for #gls("cgroups", long: false):
|
||||
```bash
|
||||
|> mount -t tmpfs none /sys/fs/cgroup
|
||||
```
|
||||
|
||||
We can the create a directory for the memory cgroup, mount the cgroup filesystem with memory, and create a subdirectory for our cgroup:
|
||||
We can the create a directory for the memory #gls("cgroups", long: false), mount the #gls("cgroups", long: false) filesystem with memory, and create a subdirectory for our #gls("cgroups", long: false):
|
||||
|
||||
```bash
|
||||
# Create a directory for the memory cgroup
|
||||
@@ -122,7 +122,7 @@ We can the create a directory for the memory cgroup, mount the cgroup filesystem
|
||||
|> mkdir /sys/fs/cgroup/memory/0
|
||||
```
|
||||
|
||||
We can then add the current process to this memory cgroups and set a memory limit of 20 MiB:
|
||||
We can then add the current process to this memory #gls("cgroups", long: false) and set a memory limit of 20 #gls("mib", long: false):
|
||||
|
||||
```bash
|
||||
# Add the current process to the memory cgroup
|
||||
@@ -148,7 +148,7 @@ for (i = 0; i < NUM_BLOCKS; i++) {
|
||||
}
|
||||
```
|
||||
|
||||
We can use the `cgroups.sh` script in `04-multiprocessing` to set up the cgroup and run the test program, but we need to run with the actual context, so we need to execute the script with `.`:
|
||||
We can use the `cgroups.sh` script in `04-multiprocessing` to set up the #gls("cgroups", long: false) and run the test program, but we need to run with the actual context, so we need to execute the script with `.`:
|
||||
|
||||
```bash
|
||||
|> just cgroups # Build the test program
|
||||
@@ -156,27 +156,27 @@ We can use the `cgroups.sh` script in `04-multiprocessing` to set up the cgroup
|
||||
|> ./cgroups # Run the test program that allocates memory in a loop
|
||||
```
|
||||
|
||||
=== What is the behavior of the command `echo $$ > ...` on cgroups?
|
||||
=== What is the behavior of the command `echo $$ > ...` on #gls("cgroups", long: false)?
|
||||
|
||||
The `$$` represent the current process ID (PID). When we execute the command `echo $$ > /sys/fs/cgroup/memory/0/tasks`, we are writing the PID of the current process into the `tasks` file of the specified cgroup. This action effectively assigns the process to that cgroup, meaning that it will now be subject to the resource limits and policies defined for that cgroup.
|
||||
The `$$` represent the current #gls("pid", long: false). When we execute the command `echo $$ > /sys/fs/cgroup/memory/0/tasks`, we are writing the #gls("pid", long: false) of the current process into the `tasks` file of the specified #gls("cgroups", long: false). This action effectively assigns the process to that #gls("cgroups", long: false), meaning that it will now be subject to the resource limits and policies defined for that #gls("cgroups", long: false).
|
||||
|
||||
|
||||
=== What is the behavior of the memory subsystem when the memory quota is exhausted? Can we modify it? If yes, how?
|
||||
|
||||
For this nanopi, we use cgroup v1, so the relevant file is `memory.limit_in_bytes`. When a process within a cgroup exceeds the memory limit defined by `memory.limit_in_bytes`, the Linux kernel will attempt to reclaim memory. If it cannot reclaim enough memory, it will invoke the Out Of Memory (OOM) killer to terminate processes within that cgroup to free up memory.
|
||||
For this nanopi, we use #gls("cgroups", long: false) v1, so the relevant file is `memory.limit_in_bytes`. When a process within a #gls("cgroups", long: false) exceeds the memory limit defined by `memory.limit_in_bytes`, the Linux kernel will attempt to reclaim memory. If it cannot reclaim enough memory, it will invoke the #gls("oom", long: false) killer to terminate processes within that #gls("cgroups", long: false) to free up memory.
|
||||
|
||||
It's possible to modify this behavior in several ways:
|
||||
|
||||
+ Use "Soft Limits" (Specific to cgroup v1)
|
||||
+ Use "Soft Limits" (Specific to #gls("cgroups", long: false) v1)
|
||||
In addition to a hard limit (`memory.limit_in_bytes`), you can set a soft limit (`memory.soft_limit_in_bytes`).
|
||||
*Behavior:* The kernel will not kill the process if the soft limit is exceeded, unless the entire system is low on global memory. If global memory is low, the kernel will start reclaiming memory from groups that exceed their soft limit.
|
||||
|
||||
+ Adjust the OOM Killer Priority Score
|
||||
If we specify an OOM score adjustement for the process. By modifying the file `/proc/[PID]/oom_score_adj` with the value `-1000`, we can make the process almost "immune" to the OOM Killer.
|
||||
+ Adjust the #gls("oom", long: false) Killer Priority Score
|
||||
If we specify an #gls("oom", long: false) score adjustement for the process. By modifying the file `/proc/[PID]/oom_score_adj` with the value `-1000`, we can make the process almost "immune" to the #gls("oom", long: false) Killer.
|
||||
|
||||
=== 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 #gls("cgroups", long: false) by reading it directly from the file in the specific #gls("cgroups", long: false):
|
||||
|
||||
```bash
|
||||
# Current memory usage in bytes
|
||||
@@ -188,9 +188,9 @@ We can monitor the memory usage of a cgroup by reading it directly from the file
|
||||
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.
|
||||
== #gls("cgroups", long: false) CPU
|
||||
To check this part, we need a tiny program that consumes #gls("cpu", long: false) with at least two process.
|
||||
The following program creates a child process that performs #gls("cpu", long: false) intensive work, while the parent process also performs #gls("cpu", long: false) intensive work. We can then use #gls("cgroups", long: false) to limit the #gls("cpu", long: false) usage of one of the processes and observe the effect.
|
||||
```c
|
||||
int main() {
|
||||
pid_t pid = fork();
|
||||
@@ -206,12 +206,12 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
Based on previous exercice, we should already have mounted the cgroup filesystem.
|
||||
Based on previous exercice, we should already have mounted the #gls("cgroups", long: false) filesystem.
|
||||
```bash
|
||||
|> mount -t tmpfs none /sys/fs/cgroup
|
||||
```
|
||||
|
||||
We can then create and mount the cgroup filesystem for the `cpuset` subsystem
|
||||
We can then create and mount the #gls("cgroups", long: false) filesystem for the `cpuset` subsystem
|
||||
```bash
|
||||
# Create a directory for the cpuset cgroup
|
||||
|> mkdir /sys/fs/cgroup/cpuset
|
||||
@@ -220,7 +220,7 @@ We can then create and mount the cgroup filesystem for the `cpuset` subsystem
|
||||
|> 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.
|
||||
Now we had the prerequirements, we can create 2 groupes. One for each of our running programme. With the following command, we attribute one or more #gls("cpu", long: false) 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"
|
||||
@@ -234,7 +234,7 @@ Now we had the prerequirements, we can create 2 groupes. One for each of our run
|
||||
|> 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:
|
||||
We can then open 2 shells and run the test program in each of them, while adding the programme to the corresponding #gls("cgroups", long: false):
|
||||
```bash
|
||||
# In the first shell, add it on the "low" cgroup and run the test program
|
||||
|> . ./max-cpu.sh low
|
||||
@@ -243,7 +243,7 @@ We can then open 2 shells and run the test program in each of them, while adding
|
||||
|> . ./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.
|
||||
We see on @max-cpu that as expected, both process in program _low_ is limited to #gls("cpu", long: false) 1, while the programm _high_ is using #gls("cpu", long: false) 2 and 3, one for each process.
|
||||
|
||||
#figure(
|
||||
image("max-cpu.png"),
|
||||
@@ -257,7 +257,7 @@ To share resources at 75% and 25%, we can use the `cpu.shares` file in the `cpu`
|
||||
|> 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%.
|
||||
Then running the test program in each shell, we see on @shared-cpu that the _high_ process is limited to 75% of the #gls("cpu", long: false), 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
|
||||
@@ -270,4 +270,4 @@ Then running the test program in each shell, we see on @shared-cpu that the _hig
|
||||
#figure(
|
||||
image("shared-cpu.png"),
|
||||
caption: [CPU usage of the two programmes with shared resources]
|
||||
)<shared-cpu>
|
||||
)<shared-cpu>
|
||||
|
||||
@@ -27,6 +27,108 @@
|
||||
description: "Rust is a modern systems programming language focused on safety, speed, and concurrency. It prevents common programming errors such as null pointer dereferencing and data races at compile time, making it a preferred choice for performance-critical applications.",
|
||||
group: "Programming Language"
|
||||
),
|
||||
(
|
||||
key: "csel",
|
||||
short: "CSEL",
|
||||
long: "Conception de Systèmes Embarqués sous Linux",
|
||||
description: "Embedded Linux Systems Design course at HES-SO, covering kernel development, driver programming, and system optimization.",
|
||||
group: "Course"
|
||||
),
|
||||
(
|
||||
key: "cpu",
|
||||
short: "CPU",
|
||||
long: "Central Processing Unit",
|
||||
description: "The primary component of a computer that performs most of the processing inside the computer, executing instructions of computer programs.",
|
||||
group: "Hardware"
|
||||
),
|
||||
(
|
||||
key: "led",
|
||||
short: "LED",
|
||||
long: "Light Emitting Diode",
|
||||
description: "A semiconductor light source that emits light when current flows through it.",
|
||||
group: "Hardware"
|
||||
),
|
||||
(
|
||||
key: "gpio",
|
||||
short: "GPIO",
|
||||
plural: "GPIOs",
|
||||
long: "General-Purpose Input/Output",
|
||||
description: "Uncommitted digital signal pins on an integrated circuit or electronic circuit board whose behavior can be programmed as input or output at runtime.",
|
||||
group: "Hardware"
|
||||
),
|
||||
(
|
||||
key: "pid",
|
||||
short: "PID",
|
||||
plural: "PIDs",
|
||||
long: "Process Identifier",
|
||||
description: "A unique number assigned by the operating system kernel to identify an active process.",
|
||||
group: "Operating System"
|
||||
),
|
||||
(
|
||||
key: "gpu",
|
||||
short: "GPU",
|
||||
long: "Graphics Processing Unit",
|
||||
description: "A specialized electronic circuit designed to accelerate graphics rendering and parallel computing tasks.",
|
||||
group: "Hardware"
|
||||
),
|
||||
(
|
||||
key: "cuda",
|
||||
short: "CUDA",
|
||||
long: "Compute Unified Device Architecture",
|
||||
description: "A parallel computing platform and application programming interface model created by NVIDIA.",
|
||||
group: "Programming API"
|
||||
),
|
||||
(
|
||||
key: "openmp",
|
||||
short: "OpenMP",
|
||||
long: "Open Multi-Processing",
|
||||
description: "An application programming interface that supports multi-platform shared-memory multiprocessing programming.",
|
||||
group: "Programming API"
|
||||
),
|
||||
(
|
||||
key: "io",
|
||||
short: "I/O",
|
||||
long: "Input/Output",
|
||||
description: "The communication between an information processing system (such as a computer) and the outside world.",
|
||||
group: "Computer Science"
|
||||
),
|
||||
(
|
||||
key: "oom",
|
||||
short: "OOM",
|
||||
long: "Out of Memory",
|
||||
description: "A state of computer operation where no additional memory can be allocated, often leading to the invocation of an OOM killer to terminate processes.",
|
||||
group: "Operating System"
|
||||
),
|
||||
(
|
||||
key: "sysfs",
|
||||
short: "sysfs",
|
||||
long: "System Filesystem",
|
||||
description: "A virtual pseudo-filesystem provided by the Linux kernel that exports information about hardware, device drivers, and kernel subsystems to user space.",
|
||||
group: "Operating System"
|
||||
),
|
||||
(
|
||||
key: "epoll",
|
||||
short: "epoll",
|
||||
long: "Event Poll",
|
||||
description: "A scalable Linux I/O event notification facility designed to monitor multiple file descriptors with high efficiency.",
|
||||
group: "Operating System"
|
||||
),
|
||||
(
|
||||
key: "cgroups",
|
||||
short: "cgroups",
|
||||
plural: "cgroups",
|
||||
long: "Control Groups",
|
||||
description: "A Linux kernel feature that limits, polices, and isolates resource usage (such as CPU, memory, and disk I/O) for groups of processes.",
|
||||
group: "Operating System"
|
||||
),
|
||||
(
|
||||
key: "mib",
|
||||
short: "MiB",
|
||||
plural: "MiBs",
|
||||
long: "Mebibyte",
|
||||
description: "A unit of digital information equal to 1,048,576 bytes (2^20 bytes).",
|
||||
group: "Computer Science"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -54,6 +156,7 @@
|
||||
show-all: false,
|
||||
// disable the back ref at the end of the descriptions
|
||||
disable-back-references: false,
|
||||
shorthands: ("plural", "capitalize", "capitalize-plural", "short", "long"),
|
||||
)
|
||||
]}
|
||||
]}
|
||||
|
||||
Reference in New Issue
Block a user