...

Linux IRQ-affinitet på multiprocessorsystem: Praktisk guide för optimal nätverksoptimering

Jag visar på ett praktiskt sätt hur IRQ-affinitet på multiprocessorsystem på ett målinriktat sätt kopplar nätverksavbrott till specifika CPU-kärnor, minskar latensen och ökar genomströmningen. Med tydliga steg, exempel och en tabell beskriver jag hur jag väljer hex-masker, tar hänsyn till NUMA och kopplar processer på rätt sätt.

Centrala punkter

  • IRQ-affinitet dirigerar hårdvaruavbrott till specifika processorer och minskar overhead.
  • CPU-affinitet Att fästa tjänster till samma kärnor gör att cachen förblir lokal.
  • NUMA Observera att adaptrar och kärnor ska användas på samma nod.
  • irqbalans avväga: automatisk fördelning eller manuell finjustering.
  • Övervakning och iterativ anpassning säkerställer stabila latenser.

Att förstå IRQ-affinitet: grunder och effekter

På Linux-värdar löser inkommande paket, disk-I/O och timers IRQ:er som kärnan fördelar mellan CPU-kärnorna. Jag anger via filerna /proc/irq//smp_affinity och .../smp_affinity_list, vilka processorer som får använda en källa. En bred standardmask verkar till en början flexibel, men vid hög belastning orsakar den cache-missar, kostsamma kontextbyten och spridda SoftIRQ:er över många Processorer. Jag kopplar kritiska köer från enskilda nätverkskort till definierade kärnor, avlastar flaskhalsar och håller datavägarna korta. Denna styrning förbättrar stabiliteten märkbart så snart många flöden är aktiva samtidigt.

Justera IRQ- och CPU-affinitet: Håll data lokalt

Jag kopplade framgångsrikt IRQ-Ansvaret för NIC-köerna i förhållande till vilken CPU de relevanta arbetarna tillhör. För detta ändamål kopplar jag webbserver- eller proxystrådar med hjälp av uppgifter eller . CPU-affinitet = i systemd till de kärnor som också hanterar RX/TX-avbrott. På så sätt förblir cache-raderna lokala och jag minimerar kommunikationen mellan CPU:erna, vilket Fördröjning utjämnar. Framför allt API-backends, realtidstjänster och virtualiserade stackar drar nytta av denna samstämmighet. Jag testar integrationen under produktionsbelastning tills flöden, SoftIRQ:er och användarutrymmet passar ihop på ett smidigt sätt.

Automatisk inställning kontra finjustering: att placera irqbalance på rätt plats

Tjänsten irqbalans fördelar avbrott automatiskt över tillgängliga kärnor, vilket fungerar bra på allround-servrar. I nätverkskonfigurationer med hög belastning hämmar dock denna fördelning cache-lokaliteten och försvårar riktad pinning. Jag begränsar eller inaktiverar irqbalance selektivt när vissa köer kräver fasta kärnor. Denna guide hjälper mig att få en grundläggande förståelse och hitta lämpliga profiler för Konfigurera irqbalance. I praktiken sköter den automatiska funktionen de icke-kritiska IRQ:erna, medan jag tilldelar de känsliga IRQ:erna manuellt.

Steg: Visa relevanta IRQ:er

Jag börjar med att ta en titt på /proc/avbrott och filtrera efter enhetsnamnet, till exempel ens192, eno1 eller . eth0. Moderna adaptrar skapar flera RX- och TX-köer, därför letar jag reda på en grupp IRQ-nummer som är tilldelade samma nätverkskort. Jag håller koll på räknarvärdena för att snabbt upptäcka flaskhalsar och prioritera att binda köer som utsätts för stor belastning. Jag granskar denna vy regelbundet under belastningstester för att säkerställa att tilldelningen fungerar på lång sikt. Dessutom kontrollerar jag drivrutinsbeteckningarna, eftersom de ger ledtrådar om RSS-funktioner och avlastning.

# Visa alla avbrott
cat /proc/interrupts

# Visa endast rader som gäller nätverkskort (exempel: ens192)
grep -i ens192 /proc/interrupts

Att använda NUMA-topologi på ett smart sätt

På värddatorer med flera noder flyttar jag helst IRQ:er till kärnorna i de NUMA-Nod där nätverkskortet är fysiskt anslutet. Jag kontrollerar detta med lscpu och numactl --hårdvara och markerar lämpliga CPU-uppsättningar för den senare maskbildningen. Processer som använder dessa nätverksvägar kopplar jag också till samma nod och ser till att de lagras lokalt via minnespolicyer Minne-Tilldelningar. På så sätt undviker jag kostsamma fjärråtkomster via QPI/UPI-länkarna. Denna disciplin ger snabbt mätbara fördelar i latensprov.

Att välja bitmasker på ett säkert sätt: Hex-logik i korthet

Filen smp_affinity accepterar hex-bitmasker som direkt motsvarar kärn-ID:n och täcker även stora system. Jag börjar ofta med enkla mönster: CPU0 är 0x1, CPU1 är 0x2, CPU2 är 0x4, CPU3 är 0x8 osv., medan 0xF omfattar kärnorna 0–3. På maskiner med många kärnor skriver jag flera 32-bitarsblock, åtskilda med kommatecken, så att Mask återger alla ID:n korrekt. Denna översikt hjälper mig att göra felfria tilldelningar och undvika oavsiktliga förflyttningar. Jag använder ofta följande tabell som minneshjälp.

CPU-nummer Bit (binär) Hex-mask Ledtråd
0 …0001 0x1 CPU0 Använd den ofta för att avlasta och endast i måttlig utsträckning.
1 …0010 0x2 IRQ till CPU1 pinnen.
2 …0100 0x4 Anslut IRQ till CPU2.
3 …1000 0x8 Anslut IRQ till CPU3.
0–3 …1111 0xF Om alla fyra kärnorna aktiveras ökar ofta latensen något.
0–7 11111111 0xFF Bred fördelning, vilket påverkar cache-lokaliteten negativt.

Ställa in IRQ-affinitet: så här kopplar jag köer till kärnor

Efter att jag har identifierat nätverkskortets kö-IRQ:er fördelar jag dem på dedikerade Kärnor som 1, 2 och 3, för att skala parallellbearbetningen på ett smidigt sätt. På så sätt förblir varje RX/TX-kö kopplad till sin kärna, vilket förhindrar överhörning och håller SoftIRQ-bearbetningen konsekvent. Under finjusteringen jämför jag genomströmning och latens tills fördelningen fungerar tillförlitligt. För en mer ingående introduktion hjälper mig en kort Praktisk guide med olika varianter beroende på adapter. Jag kör kommandona medvetet under underhållsfönster och säkrar dem med ett startskript.

# Exempel: koppla tre kö-IRQ:er till CPU1–3
echo 2 > /proc/irq/181/smp_affinity   # CPU1 (0x2)
echo 4 > /proc/irq/182/smp_affinity   # CPU2 (0x4)
echo 8 > /proc/irq/183/smp_affinity   # CPU3 (0x8)

Process-pinning: Placera tjänster på samma kärnor

Jag kopplar applikationsarbetare till dessa Processorer, som hanterar tillhörande IRQ:er så att datavägarna förblir korta. Under systemd använder jag CPUAffinity=1 2 3 eller börja en gång med taskset -c 1–3. För servrar med flera arbetare tilldelar jag fasta antal kärnor per arbetsgrupp för att undvika konkurrens. Denna tilldelning ger konstant lägre Fördröjningar, eftersom CPU-cacherna förblir välfyllda. Efter ändringar kontrollerar jag trådar, socklar och SoftIRQ:er med htop, ss och perf.

Nätverksoptimering: Koppla samman RSS, RPS/RFS och kärnparametrar

Många nätverkskort distribuerar paket via RSS om köer, som jag sedan kopplar till kärnor med IRQ-affinitet. Detaljer och hur det fungerar sammanfattar jag under Receive Side Scaling kompakt. Dessutom styr jag RPS/RFS så att SoftIRQ:er inte motverkar den hårda pinning-logiken. Parallellt justerar jag buffertarna via net.core.rmem_max och net.core.wmem_max och kontrollera TCP-alternativ som tcp_tidstämplar. Dessa komponenter bidrar till samma mål: låg latens vid hög Bithastighet.

Att tolka MSI-X och kölayout på rätt sätt

Moderna nätverkskort använder MSI-X och skapar egna IRQ:er för varje RX/TX-kö. Jag kontrollerar först hur många köer och kanaler drivrutinen för närvarande har aktiverat och anpassar detta till kärnbudgeten för den aktuella NUMA-noden. På så sätt förhindrar jag att för många köer trängs på för få kärnor eller att kapacitet däremot ligger outnyttjad.

Kontrollera och justera antalet köer och kanaler för #
ethtool -l ens192 # aktuella gränsvärden (RX/TX/combined)
ethtool -L ens192 combined 4  Aktivera t.ex. 4 köer för #

Kontrollera RSS-indirektion och hash-inställningar för #
ethtool -x ens192 # Visa indirektionstabell och hash-nyckel

Många drivrutiner ger IRQ:er beskrivande namn (t.ex. ens192-TxRx-0). Jag ser till att Indirection-tabellen stämmer överens med kärntilldelningen, så att flödena hamnar stabilt i „sina“ köer. Om hårdvarufördelningen avviker uppstår onödiga förflyttningar av SoftIRQ:erna.

Använda RPS/RFS och XPS på ett samordnat sätt

RPS/RFS kan fördela paket programvarumässigt över kärnorna – bra för nätverkskort utan många köer, men kontraproduktivt om jag redan gör en exakt bindning via RSS och IRQ-affinitet. Jag väljer därför medvetet: Antingen hård bindning via RSS+IRQ-affinitet och RPS av, eller ett fåtal hårdvaruköer och RPS riktat mot. Dessutom konfigurerar jag XPS för TX-riktningen så att utgående paket skickas från de „rätta“ kärnorna.

IF=ens192

# Inaktivera RPS helt (vid korrekt IRQ-pinnning via RSS)
for q in /sys/class/net/$IF/queues/rx-*/rps_cpus; do echo 0 > "$q"; done
echo 0 > /proc/sys/net/core/rps_sock_flow_entries

# Alternativ: Aktivera RPS selektivt (exempel: CPU:er 1–3)
for q in /sys/class/net/$IF/queues/rx-*/rps_cpus; do echo 0-0,0-0,0-0,0-0 > /dev/null; done
# Bättre: använd syntax liknande smp_affinity_list:
for q in /sys/class/net/$IF/queues/rx-*/rps_cpus; do echo 1-3 > "$q"; done
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
echo 32768 > /proc/sys/net/ipv4/tcp_rfs_sock_flow_entries

Ställ in # XPS så att det stämmer överens med de valda arbets-CPU:erna (TX-vägar)
for q in /sys/class/net/$IF/queues/tx-*; do echo 1-3 > "$q"/xps_cpus; done

Det är viktigt med samstämmighet: RX-IRQ-kärnan, ksoftirqd-belastningen och arbetartråden bör ligga på samma kärna (eller kärnpar). På så sätt försvinner många cross-core-bounce-effekter.

Beakta SMT/Hyper-Threading och kärnpar

På system med SMT är det ofta lämpligt att reservera en fysisk kärna för en RX-IRQ och placera den tillhörande arbetaren på Syskontråd att lägga ihop – eller medvetet separera, om arbetsbelastningen är beräkningsintensiv. Jag fastställer trådparen utifrån topologin och fattar sedan ett tydligt beslut istället för att fördelningen ska ske slumpmässigt.

Identifiera #-syskonpar
for c in /sys/devices/system/cpu/cpu*/topology/thread_siblings_list; do
  echo "$(basename "$(dirname "$c")") : $(cat "$c")"
done

Om jag fördelar RX-IRQ och användarutrymmesarbetare på samma fysiska kärna (olika SMT-trådar) uppnås god L1/L2-lokalitet, men vid CPU-bundna arbetsbelastningar kan det uppstå flaskhalsar. Alternativt fördelar jag IRQ på kärna X och arbetare på kärna Y i samma NUMA-nod för att uppnå verklig parallellitet. Jag testar båda varianterna med A/B-testning och väljer den som ger den mest stabila latensen.

Använd smp_affinity_list, effective_affinity och standardinställningarna

Förutom Hex-masker skriver jag gärna i smp_affinity_list, eftersom detta omfattar områden som 1-3,6,8-9 kan ställas in bekvämt. För att kontrollera detta tittar jag på effektiv_affinitet resp. effective_affinity_list, eftersom kärnan eller drivrutinerna kan utesluta vissa processorkärnor (t.ex. avstängda kärnor eller „hanterade avbrott“).

# Läsbar tilldelning
echo 1-3 > /proc/irq/181/smp_affinity_list

# Verifiera effektiv tilldelning
cat /proc/irq/181/effective_affinity_list

För att undvika att nya IRQ:er – eller sådana som tillkommit efter en omstart av drivrutiner – återigen sprids ut över ett stort område, ställer jag in följande vid behov /proc/irq/default_smp_affinity till ett lämpligt grundvärde (t.ex. alla kärnor i den relevanta NUMA-noden, men exklusive CPU0). Därefter överstyrer jag specifika kritiska IRQ:er på ett målinriktat sätt.

Säkerställa beständighet vid omstart och omladdning

Affinity-inställningarna är flyktiga. Jag säkerhetskopierar dem via en systemd-oneshot-enhet som körs efter målet för nätverksinitialisering, eller via ett litet skript som dynamiskt identifierar och kartlägger IRQ-listor. På så sätt bevaras tilldelningarna även efter kärnuppdateringar och länkåterställningar.

# /usr/local/sbin/net-irq-pin.sh (exempel)
#!/bin/bash
set -euo pipefail
IF=${1:-ens192}
CPUS="1-3"   # mål-CPU:er (välj NUMA-konsistenta)
for irq in $(grep -i "$IF" /proc/interrupts | awk '{print $1}' | tr -d ':'); do
  echo "$CPUS" > /proc/irq/$irq/smp_affinity_list || true
done

# systemd-enhet (skiss)
# /etc/systemd/system/net-irq-pin.service
[Unit]
Description=Fästa NIC-IRQ:er
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/net-irq-pin.sh ens192

[Install]
WantedBy=multi-user.target

Viktigt: Jag kör skriptet på nytt om drivrutinen har laddats om eller om antalet köer har ändrats, eftersom IRQ-numren då förskjuts.

Virtualisering och containrar: Att tänka på värd och gäst som en helhet

I KVM-miljöer monterar jag på Värd de fysiska NIC-IRQ:erna till kärnorna i den aktuella NUMA-noden. Parallellt ansluter jag vhost-nät‑Trådar och QEMU-processen (eller enskilda vCPU:er) placeras också där, så att datavägarna på värdsidan förblir korta. I Gäst Jag riktar vNIC:ernas IRQ-affinitet mot de vCPU:er som jag på värdsidan har kopplat ihop med fysiska kärnor. Container-arbetsbelastningar (cgroups/cpuset) drar nytta av detta om de tillåtna CPU:erna i containrarna överlappar värdens RX/TX-kärnor – annars uppstår onödiga fjärråtkomster.

Fördjupa analysen: Identifiera SoftIRQ:er, NAPI och köbildning

Förutom /proc/avbrott tittar jag in i /proc/softirqs, för att se om det finns mycket arbete inom ramen för ksoftirqd istället för att hanteras direkt i IRQ-hanteraren – ett tecken på en fortsatt hög belastning. Med napi_defer_hard_irqs (Kärnberoende) och genom att justera köallokeringarna reglerar jag hur aggressivt NAPI batchar. ethtool -S ger mig statistik per kö om bortfall, upptagna tillstånd och paketfrekvenser; här kan jag upptäcka obalanserade köer och justera affinitet eller RSS-indirektion därefter.

# Snabböversikt över SoftIRQ-fördelningen
cat /proc/softirqs | egrep 'NET_RX|NET_TX'

# Visa drivrutins- och köstatistik
ethtool -S ens192 | egrep -i 'rx|tx|drop|busy'

Praktik: Kartläggning för 4 köer på en nod

En typisk konfiguration som jag ofta använder: NIC på NUMA-nod 0 med 4 RSS-köer. Jag undviker CPU0 och kopplar köerna till CPU1–4. De tillhörande webb- eller proxy-arbetarna placerar jag också på 1–4, XPS tilldelar jag på samma sätt, RPS förblir avstängt. På så sätt får jag korta, konsekventa vägar i båda riktningarna.

IF=ens192
QUEUES=(181 182 183 184)   # Exempel på IRQ:er (fastställs i förväg)
CPUS="1-4"

Ställ in IRQ-affinitet och XPS för #
for i in ${!QUEUES[@]}; do
  echo "$CPUS" > /proc/irq/${QUEUES[$i]}/smp_affinity_list
done
for q in /sys/class/net/$IF/queues/tx-*; do echo "$CPUS" > "$q"/xps_cpus; done

# Fästa arbetare (systemd eller taskset)
# systemd: CPUAffinity=1 2 3 4
# Engångsinställning: taskset -c 1-4

Om belastningen fortsätter att öka kommer jag att öka antalet köer (ethtool -L) upp till det lämpliga antalet kärnor i noden och fördela dem strikt enligt ett igenkännbart mönster (t.ex. kö-ID → kärn-ID), så att flödena inte „flyttar sig“ över tid.

Bästa praxis för flerkärniga värddatorer

Jag avlastar CPU0, eftersom det ofta körs timers och kärnans interna tjänster där, som kan orsaka störningar under hög belastning. Därför placerar jag helst kritiska IRQ:er på andra kärnor och låter CPU0 endast hantera ett fåtal icke-kritiska källor. På NUMA-system är jag konsekvent och håller nätverkskort, IRQ:er, processer och minnesåtkomst på samma Nod. I miljöer med hög belastning separerar jag I/O-kärnor från applikationskärnor och isolerar dem vid behov. Jag följer upp alla ändringar med löpande mätningar och justerar tilldelningarna stegvis.

Ett mätbart tillvägagångssätt: analys, skript och reservplan

Innan jag gör några ändringar dokumenterar jag det nuvarande läget med mpstat, htop, /proc/avbrott och latensmätningar med iperf3. Jag skapar skript som automatiskt tillämpar affinitetsinställningarna efter en omstart eller när drivrutinerna laddas om. För återställningar har jag neutrala mallar redo, så att jag omedelbart kan återgå till tidigare inställningar vid fel. I staging-miljön testar jag belastningsprofiler som ligger så nära min produktionsmiljö som möjligt och upprepar Mätning efter varje justering. Först då aktiverar jag profilen permanent på målvärden.

Undvik vanliga stötestenar på ett smidigt sätt

För breda masker fördelar arbetet på för många Processorer och saktar ner cachen, medan för snäva masker överbelastar köerna. Glömda NUMA-särdrag genererar åtkomst till fjärrminne, vilket gör att svarstiderna varierar. Hård pinning kolliderar ibland med RPS/RFS-inställningar, därför kontrollerar jag SoftIRQ-lastfördelningen uttryckligen. Efter kärnuppdateringar eller drivrutinsbyten validerar jag alla IRQ-nummer på nytt, eftersom tilldelningarna kan ändras. Med försiktiga steg och tydlig Dokumentär förblir jag handlingsförmögen.

Kortfattat sammanfattat

Genom målinriktad IRQ-pinning kopplas nätverksavbrott till ett fåtal lämpliga Kärnor, minskar overhead och stabiliserar svarstiderna. Jag anpassar IRQ- och CPU-affinitet, tar hänsyn till NUMA och kontrollerar effekten med mätningar. Där automatik räcker låter jag irqbalance sköta sig själv, medan jag dirigerar kritiska köer till fasta kärnor. Med RSS, RPS/RFS och anpassade kärnparametrar når optimeringen sin fulla potential. Effekt. Den som följer dessa steg noggrant kan uppnå märkbart bättre nätverksprestanda på Linux-servrar med flera kärnor.

Aktuella artiklar

Serverrack med visualiserade Redis-anslutningar för PHP-applikationer
Databaser

Redis-anslutningspooling i PHP för maximal prestanda

Lär dig hur du använder Redis-anslutningspooler i PHP-applikationer för att minska latensen med phpredis, optimera cachen på webbhotellet och öka prestandan på lång sikt.