---
jupytext:
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.16.0
kernelspec:
  name: python3
  display_name: Python 3
---

# Le noyau Linux

```{code-cell} python
:tags: [hide-input]

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
import seaborn as sns
import pandas as pd

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
```

## Architecture du noyau

### Espace noyau et espace utilisateur

Le processeur moderne fonctionne selon un modèle à **anneaux de protection** (*privilege rings*). Sur l'architecture x86-64, deux niveaux sont utilisés en pratique :

- **Ring 0 (espace noyau, *kernel space*)** : code exécuté avec tous les privilèges. Accès direct au matériel, aux registres de contrôle du CPU, à toute la mémoire physique. Un bug dans ce code peut faire planter tout le système.
- **Ring 3 (espace utilisateur, *user space*)** : code des processus normaux (bash, nginx, Python...). Ne peut pas accéder directement au matériel. Tout accès aux ressources système passe par le noyau.

```{admonition} Isolation mémoire
:class: note
Chaque processus en espace utilisateur dispose de son propre **espace d'adressage virtuel** : il ne peut pas lire ni écrire la mémoire d'un autre processus. Le noyau maintient des **tables de pages** qui traduisent les adresses virtuelles en adresses physiques. Cette isolation est la base de la stabilité et de la sécurité du système.
```

### Les appels système (syscalls)

Un processus en espace utilisateur communique avec le noyau exclusivement via les **appels système** (*system calls*). Il s'agit d'une interface bien définie d'environ 330 entrées (sur x86-64 Linux) qui couvre :

- Accès aux fichiers : `open`, `read`, `write`, `close`, `stat`, `mmap`
- Gestion des processus : `fork`, `execve`, `wait`, `exit`, `clone`
- Réseau : `socket`, `bind`, `connect`, `sendto`, `recvfrom`
- Mémoire : `brk`, `mmap`, `munmap`, `mprotect`
- Divers : `getpid`, `kill`, `sigaction`, `ioctl`, `futex`

Mécaniquement, un appel système est déclenché par l'instruction `syscall` (x86-64) ou `int 0x80` (x86 32-bit). Le CPU bascule en Ring 0, exécute le code noyau correspondant, puis retourne en Ring 3 avec le résultat.

```{admonition} Strace — observer les syscalls
:class: tip
La commande `strace` intercepte et affiche tous les appels système d'un processus :
```bash
strace -e trace=openat,read,write ls /tmp
# ou pour attacher à un processus existant
strace -p <PID>
```

C'est un outil de diagnostic irremplaçable pour comprendre ce que fait réellement un programme.

### Noyau monolithique modulaire

Le noyau Linux est **monolithique** : tout le code noyau (gestion mémoire, scheduler, pilotes, systèmes de fichiers) s'exécute dans le même espace d'adressage privilégié. Contrairement à un micro-noyau (comme Mach ou L4), les communications internes ne passent pas par des IPC mais par des appels de fonctions directs — ce qui est beaucoup plus performant.

Mais il est aussi **modulaire** : des morceaux de code noyau (*modules*) peuvent être chargés et déchargés dynamiquement sans redémarrage. Un pilote de carte réseau, un pilote de système de fichiers, ou un filtre Netfilter peuvent être des modules séparés.

```{admonition} Avantages du modèle monolithique modulaire
:class: note
La performance est celle d'un noyau monolithique (pas de passage de messages). La flexibilité est celle d'un noyau modulaire : un serveur de production charge uniquement les modules dont il a besoin, réduisant la surface d'attaque et la consommation mémoire. Les distributions compilent typiquement la majorité des pilotes en modules plutôt qu'en statique dans le noyau.
```

## Modules du noyau

### Concepts de base

Un **module noyau** (*kernel module* ou *LKM*, Loadable Kernel Module) est un objet partagé (fichier `.ko`) qui s'insère dans l'espace noyau et peut enregistrer des pilotes, des hooks Netfilter, des systèmes de fichiers, etc.

Les modules sont stockés dans `/lib/modules/$(uname -r)/` et organisés par catégorie :

```bash
ls /lib/modules/$(uname -r)/kernel/
# crypto/  drivers/  fs/  lib/  mm/  net/  sound/  ...
```

### Commandes de gestion des modules

```bash
# Lister les modules chargés
lsmod
# Module                  Size  Used by
# nvidia               35000000  42
# snd_hda_intel          45056  3
# e1000e                233472  0

# Informations sur un module
modinfo e1000e
# filename:       /lib/modules/.../e1000e.ko
# description:    Intel(R) PRO/1000 Network Driver
# author:         Intel Corporation
# license:        GPL v2
# depends:        ptp
# parm:           InterruptThrottleRate:...

# Charger un module et ses dépendances
modprobe e1000e

# Charger avec des paramètres
modprobe usbcore autosuspend=2

# Décharger un module (si non utilisé)
modprobe -r e1000e

# Charger directement un fichier .ko (sans résoudre les dépendances)
insmod /path/to/module.ko

# Décharger directement
rmmod e1000e
```

```{admonition} modprobe vs insmod
:class: important
Toujours préférer `modprobe` à `insmod`. `modprobe` résout automatiquement les dépendances (en lisant `/lib/modules/$(uname -r)/modules.dep`) et charge les modules requis dans le bon ordre. `insmod` charge un seul fichier `.ko` sans gérer les dépendances.
```

### Chargement automatique au démarrage

Les modules listés dans `/etc/modules` sont chargés automatiquement par systemd au démarrage :

```bash
# /etc/modules — modules chargés au démarrage
loop
br_netfilter
ip_vs
```

Pour passer des paramètres à un module au chargement, on crée un fichier dans `/etc/modprobe.d/` :

```bash
# /etc/modprobe.d/intel-audio.conf
options snd-hda-intel model=auto power_save=1
```

### Dépendances entre modules

Le fichier `/lib/modules/$(uname -r)/modules.dep` décrit le graphe de dépendances. Il est généré par `depmod` après l'installation d'un nouveau noyau.

```{code-cell} python
:tags: [hide-input]

# Simulation d'un graphe de dépendances de modules noyau
# Représente les dépendances courantes du sous-système réseau

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns
import numpy as np

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

# Nœuds et positions (placement manuel pour la lisibilité)
modules = {
    "ip_tables":    (0.5, 0.9),
    "iptable_filter": (0.15, 0.7),
    "iptable_nat":  (0.5, 0.7),
    "iptable_mangle": (0.85, 0.7),
    "nf_conntrack": (0.5, 0.5),
    "nf_nat":       (0.3, 0.35),
    "nf_defrag_ipv4": (0.7, 0.35),
    "x_tables":     (0.5, 0.15),
}

# Arêtes : (source → dépend de destination)
edges = [
    ("iptable_filter", "ip_tables"),
    ("iptable_nat", "ip_tables"),
    ("iptable_mangle", "ip_tables"),
    ("ip_tables", "nf_conntrack"),
    ("iptable_nat", "nf_nat"),
    ("nf_nat", "nf_conntrack"),
    ("nf_conntrack", "nf_defrag_ipv4"),
    ("ip_tables", "x_tables"),
    ("iptable_filter", "x_tables"),
    ("iptable_nat", "x_tables"),
    ("iptable_mangle", "x_tables"),
]

fig, ax = plt.subplots(figsize=(9, 7))
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis("off")

# Dessiner les arêtes
for src, dst in edges:
    x0, y0 = modules[src]
    x1, y1 = modules[dst]
    ax.annotate("", xy=(x1, y1 + 0.04), xytext=(x0, y0 - 0.04),
                arrowprops=dict(arrowstyle="->", color="#888888",
                                lw=1.2, connectionstyle="arc3,rad=0.05"))

# Dessiner les nœuds
palette = sns.color_palette("muted", 4)
level_colors = {
    "ip_tables": palette[0],
    "iptable_filter": palette[1], "iptable_nat": palette[1], "iptable_mangle": palette[1],
    "nf_conntrack": palette[2], "nf_nat": palette[2], "nf_defrag_ipv4": palette[2],
    "x_tables": palette[3],
}

for mod, (x, y) in modules.items():
    color = level_colors.get(mod, "#cccccc")
    ax.add_patch(mpatches.FancyBboxPatch((x - 0.1, y - 0.04), 0.2, 0.08,
                                    boxstyle="round,pad=0.01",
                                    facecolor=color, edgecolor="white", linewidth=1.5,
                                    zorder=3))
    ax.text(x, y, mod, ha="center", va="center", fontsize=8,
            color="white", fontweight="bold", zorder=4)

legend_items = [
    mpatches.Patch(color=palette[0], label="Modules de table (entrée)"),
    mpatches.Patch(color=palette[1], label="Modules de chaîne"),
    mpatches.Patch(color=palette[2], label="Modules de suivi de connexion"),
    mpatches.Patch(color=palette[3], label="Infrastructure commune"),
]
ax.legend(handles=legend_items, loc="lower left", fontsize=9)
ax.set_title("Graphe de dépendances — sous-système iptables/netfilter", fontsize=11)
plt.show()
```

## Le système de fichiers virtuel `/proc`

### Qu'est-ce que `/proc` ?

`/proc` est un **pseudo-système de fichiers** (*procfs*) monté par le noyau. Il ne correspond à aucune donnée sur disque : ses fichiers sont générés à la volée par le noyau quand on les lit. Il expose en lecture (et parfois en écriture) l'état interne du noyau et de chaque processus.

```{admonition} /proc est une interface noyau
:class: important
Les fichiers dans `/proc` ne sont pas de vrais fichiers. Lire `/proc/meminfo` ne lit rien sur le disque : le noyau génère la réponse dynamiquement. C'est pourquoi leur taille apparente est 0 dans `ls -l`. Cette interface est stable et documentée dans `man 5 proc`.
```

Structure générale :

```
/proc/
├── <PID>/          → répertoire pour chaque processus
│   ├── cmdline     → ligne de commande
│   ├── status      → état (UID, GID, mémoire, signaux...)
│   ├── fd/         → descripteurs de fichiers ouverts
│   ├── maps        → mappings mémoire
│   └── net/        → état réseau du namespace
├── cpuinfo         → informations sur les processeurs
├── meminfo         → état de la mémoire
├── loadavg         → charge système
├── interrupts      → compteurs d'interruptions
├── net/            → état réseau global
├── sys/            → paramètres sysctl
└── cmdline         → paramètres passés au noyau
```

### Parse réel de `/proc/cpuinfo`

```{code-cell} python
# Parse réel de /proc/cpuinfo

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

def parse_cpuinfo(path="/proc/cpuinfo"):
    """Parse /proc/cpuinfo et retourne une liste de dicts (un par CPU logique)."""
    cpus = []
    current = {}
    with open(path, "r") as f:
        for line in f:
            line = line.strip()
            if not line:
                if current:
                    cpus.append(current)
                    current = {}
                continue
            if ":" in line:
                key, _, value = line.partition(":")
                current[key.strip()] = value.strip()
    if current:
        cpus.append(current)
    return cpus

cpus = parse_cpuinfo()

# Informations globales
print(f"=== Informations CPU ===")
print(f"Nombre de CPUs logiques   : {len(cpus)}")
print(f"Modèle                    : {cpus[0].get('model name', 'N/A')}")
print(f"Vendeur                   : {cpus[0].get('vendor_id', 'N/A')}")
print(f"Famille / Modèle          : {cpus[0].get('cpu family', '?')} / {cpus[0].get('model', '?')}")
print(f"Cœurs physiques           : {cpus[0].get('cpu cores', 'N/A')}")
print(f"Threads par cœur          : {len(cpus) // int(cpus[0].get('cpu cores', 1))}")
print(f"Cache L2                  : {cpus[0].get('cache size', 'N/A')}")
print(f"Fréquence actuelle (CPU0) : {float(cpus[0].get('cpu MHz', 0)):.0f} MHz")
print(f"BogoMIPS                  : {cpus[0].get('bogomips', 'N/A')}")

# Quelques flags importants
flags = set(cpus[0].get("flags", "").split())
features = {
    "Virtualisation (svm/vmx)": "svm" in flags or "vmx" in flags,
    "AVX2":                     "avx2" in flags,
    "AES-NI":                   "aes" in flags,
    "SSE4.2":                   "sse4_2" in flags,
    "Hyper-Threading":          len(cpus) > int(cpus[0].get("cpu cores", 1)),
}
print("\n=== Extensions CPU ===")
for feat, present in features.items():
    print(f"  {'✓' if present else '✗'} {feat}")
```

```{code-cell} python
:tags: [hide-input]

# Visualisation des fréquences par CPU logique

import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

cpu_ids = [int(c.get("processor", i)) for i, c in enumerate(cpus)]
freqs = [float(c.get("cpu MHz", 0)) for c in cpus]

fig, ax = plt.subplots(figsize=(9, 3.5))
colors = sns.color_palette("muted", len(cpu_ids))
bars = ax.bar([f"CPU{i}" for i in cpu_ids], freqs, color=colors, edgecolor="none")

for bar, freq in zip(bars, freqs):
    ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 20,
            f"{freq:.0f}", ha="center", va="bottom", fontsize=8)

ax.set_ylabel("Fréquence (MHz)")
ax.set_title(f"Fréquences actuelles des CPUs logiques — {cpus[0].get('model name', '')}")
ax.set_ylim(0, max(freqs) * 1.15)
ax.spines[["top", "right"]].set_visible(False)
plt.show()
```

### Parse réel de `/proc/meminfo`

```{code-cell} python
# Parse réel de /proc/meminfo + visualisation donut

import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

def parse_meminfo(path="/proc/meminfo"):
    """Parse /proc/meminfo et retourne un dict {clé: valeur_en_kB}."""
    mem = {}
    with open(path, "r") as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            parts = line.split()
            key = parts[0].rstrip(":")
            value = int(parts[1]) if len(parts) > 1 else 0
            mem[key] = value
    return mem

mem = parse_meminfo()

total   = mem.get("MemTotal", 0)
free    = mem.get("MemFree", 0)
buffers = mem.get("Buffers", 0)
cached  = mem.get("Cached", 0) + mem.get("SReclaimable", 0)
swap_total = mem.get("SwapTotal", 0)
swap_free  = mem.get("SwapFree", 0)

used    = total - free - buffers - cached
available = mem.get("MemAvailable", 0)

def kb_to_gib(kb):
    return kb / (1024 ** 2)

print(f"=== État de la mémoire ===")
print(f"Total RAM        : {kb_to_gib(total):.2f} GiB")
print(f"Utilisée         : {kb_to_gib(used):.2f} GiB  ({used/total*100:.1f}%)")
print(f"Buffers          : {kb_to_gib(buffers):.2f} GiB")
print(f"Cache (page+slab): {kb_to_gib(cached):.2f} GiB")
print(f"Libre            : {kb_to_gib(free):.2f} GiB")
print(f"Disponible       : {kb_to_gib(available):.2f} GiB")
print(f"Swap total       : {kb_to_gib(swap_total):.2f} GiB")
print(f"Swap utilisé     : {kb_to_gib(swap_total - swap_free):.2f} GiB  ({(swap_total-swap_free)/max(swap_total,1)*100:.1f}%)")
```

```{code-cell} python
:tags: [hide-input]

import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

# Donut chart de l'utilisation mémoire
palette = sns.color_palette("muted", 4)
labels = ["Utilisée", "Buffers", "Cache (pages+slab)", "Libre"]
sizes  = [used, buffers, cached, free]
colors = [palette[3], palette[1], palette[0], palette[2]]

fig, axes = plt.subplots(1, 2, figsize=(11, 4.5))

# Donut RAM
wedges, texts, autotexts = axes[0].pie(
    sizes, labels=labels, colors=colors,
    autopct=lambda p: f"{p:.1f}%\n({p/100*kb_to_gib(total):.2f}G)",
    startangle=90,
    wedgeprops={"width": 0.55},
    textprops={"fontsize": 9},
)
axes[0].set_title(f"RAM — {kb_to_gib(total):.1f} GiB total", fontsize=11)

# Donut Swap
swap_used_kb = swap_total - swap_free
swap_labels = ["Utilisé", "Libre"]
swap_sizes  = [swap_used_kb, swap_free]
swap_colors = [palette[3], palette[2]]
axes[1].pie(
    swap_sizes, labels=swap_labels, colors=swap_colors,
    autopct=lambda p: f"{p:.1f}%\n({p/100*kb_to_gib(swap_total):.2f}G)",
    startangle=90,
    wedgeprops={"width": 0.55},
    textprops={"fontsize": 9},
)
axes[1].set_title(f"Swap — {kb_to_gib(swap_total):.1f} GiB total", fontsize=11)

plt.suptitle("/proc/meminfo — état de la mémoire système", fontsize=12, fontweight="bold")
plt.show()
```

### Parse réel de `/proc/loadavg`

```{code-cell} python
# Parse réel de /proc/loadavg

with open("/proc/loadavg", "r") as f:
    content = f.read().strip()

parts = content.split()
load1, load5, load15 = float(parts[0]), float(parts[1]), float(parts[2])
running_procs, total_procs = parts[3].split("/")
last_pid = int(parts[4])

print(f"=== Charge système (/proc/loadavg) ===")
print(f"Contenu brut      : {content}")
print(f"Load average 1min : {load1}")
print(f"Load average 5min : {load5}")
print(f"Load average 15min: {load15}")
print(f"Processus actifs  : {running_procs} / {total_procs}")
print(f"Dernier PID       : {last_pid}")

# Interpretation
import os
ncpus = len(cpus)
print(f"\nNombre de CPUs logiques : {ncpus}")
print(f"Charge normalisée (1min): {load1/ncpus:.2f} par CPU")
if load1 / ncpus > 1.0:
    print("→ Système en surcharge (load > nCPUs)")
elif load1 / ncpus > 0.7:
    print("→ Charge élevée")
else:
    print("→ Charge normale")
```

```{admonition} Interpréter le load average
:class: tip
Un load average de `4.0` sur une machine à 4 CPUs signifie une utilisation à 100% — pas de surcharge. Sur une machine à 8 CPUs, `4.0` ne représente que 50% d'utilisation. La règle : comparez toujours le load average au nombre de CPUs logiques (`nproc`). Un ratio `load/nCPUs > 1` indique une file d'attente de processus et une latence croissante.
```

## Le système `/sys` et udev

### `/sys` — sysfs

`/sys` est un autre pseudo-système de fichiers, **sysfs**, qui expose la représentation interne du noyau des périphériques et pilotes. Contrairement à `/proc` (orienté processus), `/sys` est orienté matériel et suit une hiérarchie précise :

```
/sys/
├── bus/         → périphériques par type de bus (pci, usb, i2c...)
├── class/       → périphériques par classe (net, block, input...)
├── devices/     → arbre complet des périphériques
├── firmware/    → données ACPI/UEFI
├── kernel/      → paramètres internes du noyau
└── module/      → paramètres des modules chargés
```

Chaque entrée dans `/sys` est un fichier texte d'une ou quelques valeurs. Certains sont en lecture seule, d'autres en lecture-écriture :

```bash
# Lire la fréquence de scaling du CPU 0
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq

# Changer le gouverneur de fréquence (nécessite root)
# echo "performance" > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

# Lister les interfaces réseau
ls /sys/class/net/
# eth0  lo  wlan0

# Adresse MAC d'une interface
cat /sys/class/net/eth0/address

# Taille de la file d'attente d'émission
cat /sys/class/net/eth0/tx_queue_len
```

```{admonition} Écriture dans /sys — effets immédiats mais non persistants
:class: warning
Écrire dans `/sys` modifie le comportement du noyau immédiatement, sans redémarrage. Mais ces modifications ne survivent pas à un redémarrage. Pour rendre un changement persistant, il faut passer par `sysctl` (pour les paramètres de `/proc/sys`) ou par des règles udev.
```

### udev — gestionnaire de périphériques

**udev** est le gestionnaire de périphériques en espace utilisateur de Linux. Quand un périphérique est connecté (clé USB, disque, carte réseau...), le noyau génère un **événement uevent** et udev reçoit cet événement via un socket Netlink. udev :

1. Lit les règles dans `/etc/udev/rules.d/` et `/lib/udev/rules.d/`
2. Crée le nœud de périphérique approprié dans `/dev/`
3. Exécute des scripts ou commandes en réponse à l'événement

```bash
# Observer les événements udev en temps réel
udevadm monitor

# Informations sur un périphérique
udevadm info --query=all --name=/dev/sda

# Tester les règles sur un périphérique
udevadm test /sys/class/net/eth0

# Forcer le rechargement des règles
udevadm control --reload-rules
```

### Exemple de règle udev

Les règles udev sont des fichiers texte dans `/etc/udev/rules.d/`. Elles sont évaluées dans l'ordre lexicographique (d'où la convention de nommage `NN_nom.rules`) :

```bash
# /etc/udev/rules.d/99-usb-storage.rules

# Donner un nom stable à une clé USB spécifique
SUBSYSTEM=="block", ATTRS{idVendor}=="0781", ATTRS{idProduct}=="5581", \
  SYMLINK+="usb-sandisk"

# Exécuter un script quand un disque USB est branché
SUBSYSTEM=="block", KERNEL=="sd[b-z]", ACTION=="add", \
  RUN+="/usr/local/bin/notify-usb.sh"
```

```{admonition} Noms d'interfaces réseau stables
:class: note
Depuis systemd 197, les interfaces réseau reçoivent des noms stables basés sur leur emplacement matériel (`enp3s0`, `wlp2s0`) plutôt que des noms séquentiels (`eth0`, `wlan0`). Cette convention, gérée par udev, garantit que le renommage d'une interface au redémarrage n'impacte pas la configuration réseau.
```

```{admonition} udev et le débogage
:class: tip
Pour diagnostiquer un problème de règle udev, utilisez `udevadm test $(udevadm info --query=path --name=/dev/sdX)` : cette commande simule le traitement de l'événement et affiche toutes les règles qui s'appliquent, sans modifier l'état réel du système. Indispensable pour déboguer des règles de renommage ou de permissions.
```

## Paramètres sysctl

### Interface sysctl

**sysctl** est l'interface pour lire et modifier les paramètres du noyau en temps réel, exposés dans `/proc/sys/`. Ces paramètres contrôlent des aspects critiques du comportement du noyau : réseau, mémoire, sécurité, performances.

```bash
# Lister tous les paramètres
sysctl -a

# Lire un paramètre
sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 0

# Modifier temporairement (sans redémarrage)
sysctl -w net.ipv4.ip_forward=1

# Équivalent direct dans /proc/sys
cat /proc/sys/net/ipv4/ip_forward
# echo 1 > /proc/sys/net/ipv4/ip_forward  (root)
```

### Paramètres importants

**Réseau :**

| Paramètre | Valeur typique | Rôle |
|-----------|---------------|------|
| `net.ipv4.ip_forward` | 0/1 | Activer le routage IPv4 (nécessaire pour NAT) |
| `net.ipv4.tcp_syncookies` | 1 | Protection contre les attaques SYN flood |
| `net.ipv4.conf.all.rp_filter` | 1 | Validation de l'adresse source (protection spoofing) |
| `net.core.somaxconn` | 128 → 65535 | Taille max de la file d'attente d'accept() |
| `net.ipv4.tcp_fin_timeout` | 60 → 30 | Temps avant fermeture de connexion en état FIN_WAIT2 |

**Mémoire :**

| Paramètre | Valeur typique | Rôle |
|-----------|---------------|------|
| `vm.swappiness` | 60 → 10 | Aggressivité du recours au swap (0=minimiser, 100=agressif) |
| `vm.dirty_ratio` | 20 | % de RAM en dirty pages avant flush forcé |
| `vm.overcommit_memory` | 0/1/2 | Politique d'over-commit mémoire |
| `kernel.shmmax` | — | Taille max d'un segment de mémoire partagée |

**Sécurité :**

| Paramètre | Valeur typique | Rôle |
|-----------|---------------|------|
| `kernel.randomize_va_space` | 2 | ASLR — randomisation de l'espace d'adressage |
| `kernel.dmesg_restrict` | 1 | Restreindre l'accès à dmesg aux non-root |
| `kernel.kptr_restrict` | 2 | Masquer les adresses noyau dans /proc |
| `net.ipv4.conf.all.accept_redirects` | 0 | Refuser les redirections ICMP |

### Persistance avec `/etc/sysctl.conf`

Les modifications faites avec `sysctl -w` sont perdues au redémarrage. Pour les rendre permanentes :

```bash
# /etc/sysctl.conf (ou /etc/sysctl.d/99-custom.conf)
# Activer le routage
net.ipv4.ip_forward = 1

# Réduire l'aggressivité du swap (serveur DB)
vm.swappiness = 10

# Augmenter la file d'attente réseau
net.core.somaxconn = 65535

# Appliquer sans redémarrer
sysctl --system
```

```{admonition} Séparation des configurations sysctl
:class: tip
Préférez créer des fichiers séparés dans `/etc/sysctl.d/` plutôt que d'éditer `/etc/sysctl.conf`. La convention est `NN_description.conf` où `NN` est un nombre à deux chiffres. Les fichiers sont chargés dans l'ordre lexicographique, ce qui permet de gérer les priorités et d'organiser les paramètres par thème (réseau, mémoire, sécurité).
```

## Compilation d'un module noyau (théorie)

```{admonition} DKMS — gestion des modules tiers
:class: note
**DKMS** (*Dynamic Kernel Module Support*) automatise la recompilation des modules noyau tiers (pilotes NVIDIA, VirtualBox, ZFS...) lors des mises à jour du noyau. Sans DKMS, un module compilé pour le noyau `6.1.0-amd64` cesserait de fonctionner après une mise à jour vers `6.1.0-28-amd64`. DKMS est la solution standard pour distribuer des modules propriétaires ou non-mainline.
```

### Environnement de développement

Pour compiler un module noyau, il faut les en-têtes du noyau correspondant à la version installée :

```bash
# Debian/Ubuntu
apt install linux-headers-$(uname -r)

# Fedora/RHEL
dnf install kernel-devel-$(uname -r)

# Vérifier l'installation
ls /lib/modules/$(uname -r)/build
```

### Anatomie d'un module minimal

Un module noyau minimal en C implémente deux fonctions :

```c
/* hello.c — module noyau minimal */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Administrateur");
MODULE_DESCRIPTION("Module de démonstration");

static int __init hello_init(void)
{
    printk(KERN_INFO "hello: module chargé\n");
    return 0;  /* 0 = succès */
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "hello: module déchargé\n");
}

module_init(hello_init);
module_exit(hello_exit);
```

### Makefile pour module externe

```makefile
# Makefile
obj-m += hello.o

KDIR := /lib/modules/$(shell uname -r)/build

all:
 $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
 $(MAKE) -C $(KDIR) M=$(PWD) clean
```

```bash
# Compiler
make

# Charger
sudo insmod hello.ko

# Vérifier le message dans dmesg
dmesg | tail -2
# [12345.678] hello: module chargé

# Décharger
sudo rmmod hello

# Afficher les informations
modinfo hello.ko
```

```{admonition} Signature des modules et Secure Boot
:class: warning
Avec Secure Boot activé, les modules noyau non signés sont refusés au chargement. Pour signer un module personnalisé, il faut disposer d'une clé Machine Owner Key (MOK) enregistrée dans le firmware UEFI et utiliser `sign-file` fourni avec les en-têtes du noyau. Alternativement, désactivez Secure Boot en UEFI (déconseillé en production).
```

## Résumé

Le noyau Linux est le chef d'orchestre invisible de toute activité système. Ses mécanismes d'extension (modules), ses interfaces d'introspection (`/proc`, `/sys`) et ses paramètres de tuning (`sysctl`) forment un ensemble cohérent qui permet à l'administrateur d'observer, comprendre et modifier le comportement du système en temps réel.

```{admonition} Points clés à retenir
:class: important
- **Espace noyau / espace utilisateur** : séparation stricte garantie par le matériel. Tous les accès aux ressources passent par les syscalls.
- **Modules LKM** : étendent le noyau à la demande sans redémarrage. Toujours utiliser `modprobe` pour gérer les dépendances.
- **/proc** : interface textuelle vers l'état interne du noyau. Les fichiers sont générés à la volée ; `cpuinfo`, `meminfo`, `loadavg` sont les entrées les plus utiles au quotidien.
- **/sys** : hiérarchie orientée périphériques, base du travail d'udev pour peupler `/dev`.
- **sysctl** : tuning en temps réel du noyau. Les modifications persistantes vont dans `/etc/sysctl.d/`.
- **udev** : règles déclaratives pour nommer et configurer les périphériques à leur apparition.
```

```{admonition} Commandes de référence
:class: tip
```bash
lsmod                         # modules chargés
modinfo <module>              # informations sur un module
modprobe <module>             # charger avec dépendances
cat /proc/cpuinfo             # informations CPU
cat /proc/meminfo             # état mémoire
cat /proc/loadavg             # charge système
sysctl -a                     # tous les paramètres
sysctl -w vm.swappiness=10    # modifier temporairement
udevadm monitor               # surveiller les événements
strace -p <PID>               # tracer les syscalls d'un processus
```

```
