Le noyau Linux#
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.
Isolation mémoire
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,mmapGestion des processus :
fork,execve,wait,exit,cloneRéseau :
socket,bind,connect,sendto,recvfromMémoire :
brk,mmap,munmap,mprotectDivers :
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.
Strace — observer les syscalls
La commande strace intercepte et affiche tous les appels système d’un processus :
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.
Avantages du modèle monolithique modulaire
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 :
ls /lib/modules/$(uname -r)/kernel/
# crypto/ drivers/ fs/ lib/ mm/ net/ sound/ ...
Commandes de gestion des modules#
# 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
modprobe vs insmod
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 :
# /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/ :
# /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.
# 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.
/proc est une interface noyau
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#
# 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}")
=== Informations CPU ===
Nombre de CPUs logiques : 8
Modèle : AMD Ryzen 5 2500U with Radeon Vega Mobile Gfx
Vendeur : AuthenticAMD
Famille / Modèle : 23 / 17
Cœurs physiques : 4
Threads par cœur : 2
Cache L2 : 512 KB
Fréquence actuelle (CPU0) : 1437 MHz
BogoMIPS : 3992.26
=== Extensions CPU ===
✓ Virtualisation (svm/vmx)
✓ AVX2
✓ AES-NI
✓ SSE4.2
✓ Hyper-Threading
# 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#
# 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}%)")
=== État de la mémoire ===
Total RAM : 6.68 GiB
Utilisée : 3.92 GiB (58.7%)
Buffers : 0.15 GiB
Cache (page+slab): 2.44 GiB
Libre : 0.17 GiB
Disponible : 2.41 GiB
Swap total : 7.63 GiB
Swap utilisé : 2.44 GiB (32.0%)
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#
# 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")
=== Charge système (/proc/loadavg) ===
Contenu brut : 0.66 0.54 0.39 4/1378 70890
Load average 1min : 0.66
Load average 5min : 0.54
Load average 15min: 0.39
Processus actifs : 4 / 1378
Dernier PID : 70890
Nombre de CPUs logiques : 8
Charge normalisée (1min): 0.08 par CPU
→ Charge normale
Interpréter le load average
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 :
# 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
Écriture dans /sys — effets immédiats mais non persistants
É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 :
Lit les règles dans
/etc/udev/rules.d/et/lib/udev/rules.d/Crée le nœud de périphérique approprié dans
/dev/Exécute des scripts ou commandes en réponse à l’événement
# 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) :
# /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"
Noms d’interfaces réseau stables
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.
udev et le débogage
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.
# 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 |
|---|---|---|
|
0/1 |
Activer le routage IPv4 (nécessaire pour NAT) |
|
1 |
Protection contre les attaques SYN flood |
|
1 |
Validation de l’adresse source (protection spoofing) |
|
128 → 65535 |
Taille max de la file d’attente d’accept() |
|
60 → 30 |
Temps avant fermeture de connexion en état FIN_WAIT2 |
Mémoire :
Paramètre |
Valeur typique |
Rôle |
|---|---|---|
|
60 → 10 |
Aggressivité du recours au swap (0=minimiser, 100=agressif) |
|
20 |
% de RAM en dirty pages avant flush forcé |
|
0/1/2 |
Politique d’over-commit mémoire |
|
— |
Taille max d’un segment de mémoire partagée |
Sécurité :
Paramètre |
Valeur typique |
Rôle |
|---|---|---|
|
2 |
ASLR — randomisation de l’espace d’adressage |
|
1 |
Restreindre l’accès à dmesg aux non-root |
|
2 |
Masquer les adresses noyau dans /proc |
|
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 :
# /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
Séparation des configurations sysctl
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)#
DKMS — gestion des modules tiers
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 :
# 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 :
/* 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
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
# 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
Signature des modules et Secure Boot
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.
Points clés à retenir
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
modprobepour 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,loadavgsont 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.
Commandes de référence
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