➟ Kernel Temps Réel sur Raspberry Pi 4 : Guide et Tutoriel C++

Raspberry Pi 4 Temps Réel

Avez-vous déjà eu besoin de garantir des temps de réponse déterministes sur un Raspberry Pi ?

Que ce soit pour de la robotique, du contrôle industriel ou de la communication EtherCAT, un kernel temps réel est souvent indispensable. Dans cet article, je vous guide pas à pas pour installer un kernel RT_PREEMPT sur Raspberry Pi 4 et je vous propose un tutoriel C++ complet et pédagogique pour apprendre à programmer en temps réel.

Pourquoi le temps réel sur Raspberry Pi ?

Le Raspberry Pi 4 est une plateforme puissante et abordable, mais son kernel Linux standard n’offre aucune garantie de latence. Sous charge, une simple tâche périodique peut subir des retards de plusieurs millisecondes - inacceptable pour certaines applications :

  • Robotique : Contrôle de servomoteurs avec période de 1 ms
  • EtherCAT : Communication industrielle temps réel
  • Audio professionnel : Traitement avec latence ultra-faible
  • Acquisition de données : Échantillonnage à fréquence fixe

Le problème illustré

Voici ce qui se passe quand vous exécutez une tâche périodique de 1 ms sur un kernel standard sous charge :

Métrique Kernel Standard Kernel RT
Latence min ~10 µs ~10 µs
Latence max 2-5 ms < 50 µs
Latence moyenne ~150 µs ~15 µs
Prévisibilité Aucune Garantie

La différence est flagrante : le kernel RT réduit la latence maximale d’un facteur 50 à 100.

Qu’est-ce que RT_PREEMPT ?

RT_PREEMPT est un patch pour le kernel Linux qui le transforme en système temps réel “soft”. Il apporte :

  1. Préemption complète : Presque tout le code kernel devient préemptible
  2. Interruptions threadées : Les gestionnaires d’interruption s’exécutent comme des threads ordonnançables
  3. Priorités temps réel : Les threads SCHED_FIFO/RR préemptent tout le reste
  4. Horloges haute résolution : Timers précis à la microseconde

Depuis Ubuntu 24.04, le kernel RT est disponible directement dans les dépôts officiels pour Raspberry Pi !

Installation du Kernel RT

J’ai créé un script qui automatise toute la configuration. Voici ce qu’il fait :

1. Installation du package kernel RT

1
sudo apt install linux-raspi-realtime

2. Configuration des paramètres de boot

Le script ajoute ces paramètres essentiels dans /boot/firmware/cmdline.txt :

1
isolcpus=2,3 rcu_nocbs=2,3 nohz_full=2,3 preempt=full

Explication :

  • isolcpus=2,3 : Isole les CPUs 2 et 3 du scheduler général
  • rcu_nocbs=2,3 : Déplace les callbacks RCU hors de ces CPUs
  • nohz_full=2,3 : Désactive les ticks timer quand un seul thread tourne
  • preempt=full : Active la préemption complète

3. Configuration des limites utilisateur

Dans /etc/security/limits.conf :

1
2
3
4
* soft rtprio 99
* hard rtprio 99
* soft memlock unlimited
* hard memlock unlimited

4. Optimisations système

  • CPU governor en “performance”
  • Swap désactivé
  • Optimisations réseau

Installation rapide

1
2
3
4
5
6
7
8
9
# Télécharger le projet
git clone https://github.com/jeremydierx/rpi_rt_test.git
cd rpi_rt_test

# Exécuter le script d'installation
sudo ./scripts/setup_realtime_rpi.sh

# Redémarrer
sudo reboot

Tutoriel pratique en C++

Pour apprendre à utiliser les APIs temps réel, j’ai créé rt_tuto : un tutoriel C++ simple et commenté. Il s’agit d’un programme pédagogique, pas d’un outil de mesure de performance (pour cela, utilisez cyclictest).

Structure du tutoriel

Le programme suit une progression pédagogique en 3 étapes :

ÉTAPE 1 : Configuration Temps Réel
1️⃣ mlockall() - Verrouillage mémoire
2️⃣ sched_setscheduler() - SCHED_FIFO priorité 80
3️⃣ pthread_setaffinity_np() - CPU isolé 2

ÉTAPE 2 : Exécution Boucle Périodique
• clock_nanosleep(TIMER_ABSTIME) - Réveil précis
• Mesure de latence à chaque cycle
• 1000 itérations de 1 ms (~1 seconde)

ÉTAPE 3 : Analyse des Résultats
• Statistiques : min/max/moyenne/écart-type
• Histogramme visuel des latences
• Recommandations personnalisées

Les trois piliers du temps réel en C++

Voici les techniques clés utilisées dans le programme :

1. Verrouillage mémoire avec mlockall()

1
2
3
4
5
6
7
8
9
10
11
/**
* Verrouiller toute la mémoire en RAM pour éviter les page faults.
* Un page fault peut causer des latences de plusieurs millisecondes !
*
* MCL_CURRENT : Verrouille les pages actuellement mappées
* MCL_FUTURE : Verrouille aussi les futures allocations
*/
if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
std::cerr << "Erreur mlockall: " << strerror(errno) << std::endl;
return -1;
}

2. Configuration SCHED_FIFO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Passer en ordonnancement temps réel SCHED_FIFO.
*
* SCHED_FIFO : Le thread garde le CPU jusqu'à ce qu'il :
* - Se bloque (sleep, I/O, mutex)
* - Soit préempté par une priorité plus haute
*
* Priorité 80 : Assez haute pour nos tests, mais laisse de la marge
* pour d'éventuels threads système critiques.
*/
struct sched_param param;
param.sched_priority = 80; // Priorité temps réel (1-99)

if (sched_setscheduler(0, SCHED_FIFO, &param) != 0) {
std::cerr << "Erreur sched_setscheduler: " << strerror(errno) << std::endl;
return -1;
}

3. Affinage CPU (CPU Pinning)

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Forcer le thread à s'exécuter sur un CPU isolé.
*
* Combiné avec isolcpus=2,3 dans les paramètres de boot,
* cela garantit qu'aucune autre tâche ne viendra perturber
* notre thread temps réel.
*/
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(2, &cpuset); // CPU 2 est isolé

pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);

La boucle périodique

Le coeur du test est une boucle qui doit se réveiller exactement toutes les 1000 µs :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct timespec next_period;
clock_gettime(CLOCK_MONOTONIC, &next_period);

for (int i = 0; i < 10000; ++i) {
// Attendre jusqu'à l'instant absolu prévu
// TIMER_ABSTIME évite l'accumulation d'erreurs
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_period, NULL);

// Mesurer la latence réelle
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
uint64_t latency_ns = timespec_diff_ns(next_period, now);

// Préparer la prochaine période
next_period.tv_nsec += 1000000; // +1 ms
if (next_period.tv_nsec >= 1000000000) {
next_period.tv_sec++;
next_period.tv_nsec -= 1000000000;
}
}

Points importants :

  • CLOCK_MONOTONIC : Horloge qui ne saute jamais (contrairement à CLOCK_REALTIME)
  • TIMER_ABSTIME : Réveil à un instant absolu, pas relatif
  • Calcul de la prochaine période : Gestion propre du débordement des nanosecondes

Résultats

Tests avec cyclictest

Voici les résultats obtenus sur un Raspberry Pi 4 (4 GB) avec Ubuntu 24.04.1 LTS, après configuration du kernel temps réel :

Configuration de test :

  • CPU isolé : 2 (via isolcpus=2,3)
  • Priorité : SCHED_FIFO 80
  • Période : 1 ms
  • Durée : 10 000 itérations (~10 secondes)

Commande :

1
sudo cyclictest -t1 -p 80 -a 2 -m -i 1000 -l 10000 -q

Résultats :

Métrique Valeur
Latence minimale 18 µs
Latence moyenne 23 µs
Latence maximale 29 µs

Analyse de la distribution :

L’histogramme révèle une stabilité exceptionnelle :

  • 84% des mesures entre 23-24 µs (seulement 1 µs d’écart)
  • 100% des latences < 36 µs
  • Aucune latence au-delà de 40 µs
1
2
3
4
5
Histogramme (extrait) :
23 µs: ████████████████████████████████ 5678 (56.78%)
24 µs: ███████████████ 2767 (27.67%)
25-27 µs: 14.05%
> 28 µs: < 1.5%

Analyse

Avec une latence maximale de 29 µs, le Raspberry Pi 4 atteint un niveau de performance temps réel remarquable :

Excellent pour le temps réel strict (< 50 µs requis)
Distribution très serrée : 84% des mesures concentrées sur 1 µs d’écart
Déterminisme garanti : Aucune latence imprévisible

Ces résultats valident que la configuration est parfaite pour des applications exigeantes :

  • Contrôle moteur avec période de 1 ms
  • Communication EtherCAT (cycles typiques de 250 µs à 1 ms)
  • Acquisition de données synchronisées
  • Robotique temps réel

Cross-compilation depuis WSL2

Pour accélérer le développement, j’utilise la cross-compilation depuis WSL2. C’est 5 à 10 fois plus rapide que de compiler sur le Pi.

Installation de la toolchain

1
2
# Dans WSL2 Ubuntu
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu cmake

Compilation

1
2
3
4
5
6
# Avec le script fourni
./scripts/deploy.sh

# Vérifier le binaire
file bin/rt_test
# Sortie: ELF 64-bit LSB executable, ARM aarch64

Déploiement et exécution

1
2
3
4
5
6
# Copier sur le Pi
scp bin/rt_tuto ubuntu@raspberrypi:~/

# Se connecter et exécuter le tutoriel
ssh ubuntu@raspberrypi
sudo ./rt_tuto

Conclusion

Avec le kernel RT_PREEMPT et une configuration appropriée, le Raspberry Pi 4 devient capable de tâches temps réel avec des latences garanties sous les 100 µs. Les trois éléments clés sont :

  1. Kernel RT avec paramètres d’isolation CPU
  2. SCHED_FIFO avec priorité élevée
  3. mlockall() pour éviter les page faults

Le tutoriel complet en C++ (rt_tuto.cpp) est disponible sur GitHub : https://github.com/jeremydierx/rpi_rt_test

Le code est abondamment commenté et explique chaque API en détail. N’hésitez pas à l’utiliser comme base pour apprendre et développer vos propres projets temps réel !

Tests de stress avec cyclictest

Pour des tests de performance approfondis et stress tests, l’outil de référence est cyclictest (partie du package rt-tests) :

1
2
3
4
5
6
7
8
# Installation
sudo apt install rt-tests

# Test rapide (10 secondes)
sudo cyclictest -t1 -p 80 -a 2 -m -i 1000 -l 10000 -q

# Test approfondi (1 heure)
sudo cyclictest -t1 -p 80 -a 2 -m -i 1000 -l 3600000

Options importantes :

  • -t1 : 1 thread de test
  • -p 80 : Priorité SCHED_FIFO 80
  • -a 2 : Épingler sur CPU 2 (isolé)
  • -m : Verrouiller la mémoire
  • -i 1000 : Intervalle de 1000 µs (1 ms)
  • -l : Nombre d’itérations
  • -q : Mode silencieux (affiche seulement les résultats finaux)

Ressources

Jérémy @ Code Alchimie