Affinité des interruptions (IRQ)#
Pourquoi#
Isoler des cœurs CPU (chapitres précédents) ne sert à rien si les interruptions matérielles critiques, le PPS et la réception réseau, continuent d’être traitées n’importe où, en concurrence avec d’autres tâches. Ce chapitre route explicitement chaque interruption vers un cœur dédié.
Comment#
Script de routage#
mkdir -p /opt/affinity
cat > /opt/affinity/affinity.sh <<'EOF'
#!/bin/bash
# Routage matériel des interruptions :
# - PPS (GPIO) -> Core 3, priorité RT FIFO 99
# - end0 (NIC) -> Core 2, priorité RT FIFO 90
set -u
NET_IF="end0"
PPS_IRQ=$(awk '/pps/ {print $1}' /proc/interrupts | sed 's/://')
NET_IRQ=$(awk -v iface="$NET_IF" '$0 ~ iface {print $1}' /proc/interrupts | sed 's/://' | head -n 1)
# --- PPS -> Core 3, FIFO 99 ---
if [ -n "$PPS_IRQ" ]; then
echo 8 > /proc/irq/"$PPS_IRQ"/smp_affinity
PPS_PID=$(pgrep -f "irq/${PPS_IRQ}-")
[ -n "$PPS_PID" ] && chrt -f -p 99 "$PPS_PID"
fi
# --- end0 -> Core 2, FIFO 90 ---
if [ -n "$NET_IRQ" ]; then
echo 4 > /proc/irq/"$NET_IRQ"/smp_affinity
# rx-usecs/tx-usecs à 0 désactive la coalescence d'interruptions de la
# carte réseau (par défaut, le contrôleur retarde volontairement la
# remontée d'une IRQ pour regrouper plusieurs paquets et réduire la
# charge CPU) : ici, on préfère une IRQ par paquet, immédiate, quitte à
# augmenter le nombre d'interruptions, puisque ce cœur 2 lui est
# entièrement dédié et n'a rien d'autre à faire
ethtool -C "$NET_IF" rx-usecs 0 tx-usecs 0 2>/dev/null || true
NET_PID=$(pgrep -f "irq/${NET_IRQ}-")
[ -n "$NET_PID" ] && chrt -f -p 90 "$NET_PID"
fi
EOF
chmod +x /opt/affinity/affinity.shLe masque smp_affinity est un bitmask hexadécimal : 4 correspond au
cœur 2, 8 au cœur 3.
Service systemd et persistance#
cat > /etc/systemd/system/affinity.service <<'EOF'
[Unit]
Description=Routage materiel des interruptions (PPS core3, reseau core2)
After=network.target
[Service]
Type=oneshot
ExecStart=/opt/affinity/affinity.sh
RemainAfterExit=true
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now affinity.serviceUne renégociation du lien Ethernet réinitialise les masques, une règle udev rejoue le script automatiquement :
cat > /etc/udev/rules.d/90-end0-affinity.rules <<'EOF'
ACTION=="change", SUBSYSTEM=="net", KERNEL=="end0", RUN+="/opt/affinity/affinity.sh"
EOF
udevadm control --reload-rulesErreurs à éviter#
Le thread IRQ de l’interface réseau ne porte pas le nom de l’interface. Sur Raspberry Pi 5, le pilote de la puce réseau enregistre son interruption sous un nom interne historique (
eth%d) avant le renommage de l’interface enend0parudev. Le thread kernel s’appelle doncirq/<N>-eth%d, pasirq/<N>-end0. Un script qui chercheraitpgrep -f "irq/.*-end0"ne trouvera jamais rien. Toujours cibler par numéro d’IRQ (déterminé via/proc/interrupts), jamais par nom d’interface supposé.
irqbalancetoujours installé. S’il n’a pas été purgé au chapitre Mises à jour, il va continuellement redistribuer les IRQ entre cœurs, annulant l’effet de ce script au fil du temps.- Oublier la règle udev de persistance. Sans elle, une simple
déconnexion/reconnexion du câble réseau (ou un
NetworkManagerqui redémarre l’interface) réinitialise le masquesmp_affinityà sa valeur par défaut, et l’IRQ réseau repart en libre circulation entre cœurs.
Vérifier#
# Cartographie en direct des IRQ (Ctrl+C pour quitter)
watch -n 1 -d 'grep -E "pps|end0" /proc/interrupts'
# Affinité effective
cat /proc/irq/$(awk '/pps/ {print $1}' /proc/interrupts | tr -d ':')/smp_affinity_list
cat /proc/irq/$(awk '/end0/ {print $1}' /proc/interrupts | head -1 | tr -d ':')/smp_affinity_list
# Doit retourner respectivement "3" et "2"
# Confirmer qu'aucun thread userland ordinaire n'est planifié sur 2 ou 3
ps -eLo psr,comm | awk '$1==2 || $1==3' | sort -u
# Ne doit lister que les threads kernel IRQ concernés (et idle)📚 Pour aller plus loin
Le noyau PREEMPT_RT (chapitre Noyau temps réel) rend chaque gestionnaire d’interruption “threadé”, c’est-à-dire ordonnançable individuellement, avec sa propre priorité
SCHED_FIFO. C’est ce mécanisme qui permet ici de donner à l’IRQ du PPS la priorité99(la plus haute possible sur ce système), garantissant qu’elle préempte absolument tout le reste dès qu’une impulsion arrive, y compris les autres threads temps réel comme celui de chrony (priorité98, cf. chapitre Chrony).