Das Problem mit kopierten Boot-Zeilen
Such nach Proxmox-Tuning, und du findest eine einzige lange GRUB_CMDLINE_LINUX-Zeile, als Einheit vorgestellt, ohne Hinweis darauf, welches Flag wohin gehört.
Das zählt mehr, als es klingt. Der Hypervisor und der Gast lösen entgegengesetzte Probleme.
Der Host will bestimmten Zugriff auf echte Hardware: Verhalten der IOMMU, PCIe-Link-Zustände, physische Ruhezustände. Der Gast will aufhören, überhaupt so zu tun, als hätte er Hardware — seine Timer sind Näherungen, seine Ruhezustände Erfindung, und seine Hänger sind meist der Scheduler eines anderen. Damit kann dasselbe Flag auf einer Seite richtig sein, auf der anderen sinnlos und gelegentlich schädlich.
Unten steht, wohin jedes tatsächlich gehört.
Zuerst: bearbeitest du überhaupt die richtige Datei?
Eine Proxmox-Installation auf ZFS-Root startet mit systemd-boot, wo /etc/default/grub von niemandem gelesen wird. Sie zu bearbeiten und neu zu starten erzeugt keine Änderung und keinen Fehler. Das ist eine ärgerliche Stunde.
proxmox-boot-tool status # tells you which bootloader is in use
# systemd-boot: edit /etc/kernel/cmdline, then
proxmox-boot-tool refresh
# GRUB: edit /etc/default/grub, then
update-grub
So oder so: prüfen statt annehmen.
cat /proc/cmdline
Und auf der GRUB-Seite nimm GRUB_CMDLINE_LINUX_DEFAULT, nicht GRUB_CMDLINE_LINUX. Letzteres gilt für jeden Boot-Eintrag, den Rettungseintrag eingeschlossen — und die Rettung ist genau der Moment, in dem du Standardverhalten willst und nicht abgeschaltete Mitigations und festgenagelte C-States.
Die Host-Zeile
IOMMU und Passthrough
iommu=pt amd_iommu=pgtbl_v2 pcie_acs_override=downstream,multifunction
iommu=pt versetzt die IOMMU in den Passthrough-Modus: Geräte, die VMs zugewiesen sind, werden übersetzt, Geräte des Hosts selbst umgehen die Übersetzung. Es ist echt und wird in arch/x86/kernel/pci-dma.c behandelt, das iommu_set_default_passthrough(true) aufruft. Der Kernel dokumentiert es als gleichwertig mit iommu.passthrough=1.
amd_iommu=on gibt es nicht. Das ist der am häufigsten kopierte nicht existierende Parameter in Proxmox-Anleitungen. Das parse_amd_iommu_options() des Kernels nimmt fullflush, force_enable, off, force_isolation, pgtbl_v1, pgtbl_v2, irtcachedis, nohugepages und v2_pgsizes_only. Alles andere landet hier:
pr_notice("Unknown option - '%s'\n", str);
AMD-Vi ist standardmäßig an, wenn die Firmware es angibt. Sieh in dein eigenes Protokoll, und du findest, dass der Parameter nie die Arbeit getan hat, die man ihm zuschrieb:
dmesg | grep -i "AMD-Vi\|Unknown option"
amd_iommu=pgtbl_v2 ist gültig — es wählt das v2-Format für DMA-Seitentabellen, das die Struktur der CPU-Seitentabellen mitnutzt statt AMDs eigener. Zwei Dinge dazu: die Dokumentation begrenzt es auf die DMA-API, also die Gerätedomänen des Hosts selbst und nicht die VFIO-Domänen für Passthrough; und es scheitert sicher, mit einer Protokollzeile, auf die du prüfen solltest:
if (amd_iommu_pgtable == PD_MODE_V2) {
if (!amd_iommu_v2_pgtbl_supported()) {
pr_warn("Cannot enable v2 page table for DMA-API. Fallback to v1.\n");
amd_iommu_pgtable = PD_MODE_V1;
}
}
Es lohnt sich also, das auf einem Knoten mit schwerem IO auf der Host-Seite zu messen, und es lohnt sich zu prüfen, dass du es wirklich bekommen hast.
pcie_acs_override=downstream,multifunction ist der Patch von Proxmox außerhalb des Kernel-Baums. Er teilt IOMMU-Gruppen auf, indem er eine Isolation behauptet, die die Hardware nicht angibt, und genau das macht Passthrough auf Consumer-Platinen möglich. Es heißt aber auch, dem Kernel etwas Unwahres über die Topologie zu erzählen. In Ordnung auf einer Kiste, deren Gästen du so viel traust wie dem Host. Sonst nicht in Ordnung. Mehr zum Warum steht in dem Beitrag über die IOMMU-Steuer.
Latenz und Zittern
pcie_aspm=off processor.max_cstate=1 amd_pstate=disable
pcie_aspm=off hält PCIe-Links aus Zuständen geringer Leistung heraus, damit ein eintreffendes IO nie darauf wartet, dass einer aufwacht. Es kostet ein paar Watt pro Link und nimmt einen Latenzausläufer weg, der schwer zu diagnostizieren ist. Siehe PCIe-ASPM und Passthrough.
processor.max_cstate=1 begrenzt die ACPI-Ruhe auf C1. Achte auf den Treiber: das ist das Stellrad von processor/acpi_idle, auf Intel brauchst du also zusätzlich intel_idle.max_cstate=1, weil intel_idle Vorrang hat. Auf AMD ist dieses hier das richtige.
Es gibt ein echtes Gegenargument. Tiefer Schlaf auf ruhenden Kernen ist das, was dem Paket thermischen und Leistungsspielraum gibt, die beschäftigten Kerne hochzutakten, alles bei C1 festzunageln kann also deine Spitzenfrequenz für einen Thread senken und gleichzeitig den Verbrauch im Ruhezustand heben. Auf einem latenzempfindlichen Host ist der Handel meist gut. Auf einem Host, der Durchsatz jagt, vielleicht nicht. Miss es, statt es zu erben.
amd_pstate=disable fällt auf acpi-cpufreq zurück. Es lohnt sich, die dokumentierten Alternativen zu kennen, bevor man danach greift: passive (der Treiber verlangt eine Leistungsstufe), active (der EPP-Treiber, der zu Leistung oder Effizienz neigt) und guided. active mit Neigung zur Leistung oder passive samt Governor performance bringt oft dieselbe Latenz und behält die feinere Steuerung von CPPC. Und wenn du es abschaltest, setz bewusst einen Governor — auf acpi-cpufreq mit schedutil zu landen kann ein Rückschritt sein.
Speicher
default_hugepagesz=1G hugepages=64
default_hugepagesz=1G allein reserviert nichts. Der Kernel dokumentiert es als Festlegung von „the size of the default HugeTLB page… the default hugetlb size used for shmget(), mmap() and mounting hugetlbfs“ — also der Größe der voreingestellten HugeTLB-Seite: eine Einheit, keine Zuweisung. Die Zuweisung kommt von hugepages=, dokumentiert als „Number of HugeTLB pages to allocate at boot“, die Zahl der beim Start zuzuweisenden Seiten.
Das zählt bei 1 GiB weit mehr als bei 2 MiB, weil zusammenhängende 1-GiB-Bereiche praktisch nicht zu bekommen sind, sobald der Host gelaufen ist und den Speicher zersplittert hat. Der Start ist deine einzige verlässliche Gelegenheit.
Dann muss der Gast mitmachen (hugepages: 1024 in der VM-Konfiguration). Reservierte Seiten, die niemand nutzt, sind nur Speicher, den du nicht zurückbekommst, und du verlierst Ballooning und KSM auf den VMs, die sie nutzen.
Der Handel mit der Sicherheit
mitigations=off
Das ist nicht ein Schalter. Der Kernel klappt es in eine Liste auf, und auf einem Hypervisor sind das die Einträge, auf die es ankommt:
l1tf=off mds=off mmio_stale_data=off kvm.nx_huge_pages=off
gather_data_sampling=off retbleed=off spec_rstack_overflow=off
nospectre_v2 nopti indirect_target_selection=off
Die Zusammenfassung des Kernels selbst lautet „improves system performance, but it may also expose users to several CPU vulnerabilities“ — es verbessert die Leistung des Systems, kann Nutzer aber auch mehreren CPU-Schwachstellen aussetzen. L1TF, MDS und MMIO Stale Data sind speziell Lecks vom Gast zum Host und vom Gast zum Gast, und kvm.nx_huge_pages ist die Mitigation gegen iTLB-Multihit in KVM selbst.
Vertretbar auf einer Kiste mit einem Mandanten, in der jeder Gast so viel Vertrauen hat wie der Host. Nicht vertretbar dort, wo Gäste nicht vertrauenswürdig sind oder verschiedenen Mandanten gehören. Und beachte, dass es sich mit pcie_acs_override stapelt: zwei unabhängige Isolationszusagen, in derselben Zeile entfernt. Es lohnt sich, das mit Absicht zu tun statt durch Erbschaft.
PCIe über USB4 ändert zwei davon
Kommen deine PCIe-Geräte über USB4 oder Thunderbolt — eine externe GPU oder ein NVMe-Gehäuse —, ändern sich zwei der Antworten von oben.
MPS-Tuning ist nicht mehr kostenlos
Auf festen Slots ist pci=pcie_bus_perf ein kleiner kostenloser Gewinn. Der Kernel beschreibt es so:
Set device MPS to the largest allowable MPS based on its parent bus. Also set MRRS (Max Read Request Size) to the largest supported value… for best performance.
Also: die MPS eines Geräts auf den größten anhand seines übergeordneten Busses zulässigen Wert setzen, und die MRRS ebenfalls auf den größten unterstützten Wert, für die beste Leistung.
Der Haken ist, dass es Bridges beim Start konfiguriert, aus der beim Start vorhandenen Topologie. Über USB4 ist Hot-Plug der normale Fall, und ein später hinzugefügtes Gerät kann eine kleinere MPS können als die, auf die die Bridge schon gesetzt wurde.
Der Kernel sagt das Stille laut, während er eine andere Politik anpreist:
pcie_bus_peer2peer— Set every device’s MPS to 128B, which every device is guaranteed to support… This also guarantees that hot-added devices will work.
Also: die MPS jedes Geräts auf 128 B setzen, was jedes Gerät garantiert kann — und das stellt auch sicher, dass im Betrieb hinzugefügte Geräte funktionieren.
Nur eine Politik trägt diese Zusicherung, und es ist die, die alles auf 128 Byte festnagelt — genau das, dem MaxPayloadSize-Tuning entkommen will. Für eine Hot-Plug-Topologie ist pcie_bus_safe (der größte Wert, den alle Geräte unter dem Root Complex können) oder einfach tune_off stehen zu lassen der sicherere Anfang. Der Switch im Tunnel begrenzt die erreichbare MPS ohnehin, die Decke war also nie deine, sie zu heben.
Der Sicherheitsstapel wird ernst
Externes PCIe heißt, dass jemand ein DMA-fähiges Gerät in deinen Hypervisor stecken kann. Die Thunderbolt-Dokumentation des Kernels ist da direkt:
…the connected devices can be DMA masters and thus read contents of the host memory without CPU and OS knowing about it. There are ways to prevent this by setting up an IOMMU but it is not always available for various reasons.
Also: die angeschlossenen Geräte können DMA-Master sein und damit den Inhalt des Host-Speichers lesen, ohne dass CPU und Betriebssystem davon wissen; es gibt Wege, das mit einer IOMMU zu verhindern, aber die ist aus verschiedenen Gründen nicht immer verfügbar.
Die IOMMU ist die Verteidigung. Zähl jetzt, was die Host-Zeile mit ihr macht. iommu=pt gibt Geräten des Hosts unübersetzte Identitätsdomänen, pcie_acs_override behauptet eine Isolation, die nicht da ist, und mitigations=off schaltet die Mitigations zur Gast-Isolation ab. Jedes ist allein vertretbar. Zusammen, auf einer Maschine mit einem körperlich erreichbaren USB4-Port, stapeln sie sich.
Prüf, wo du stehst:
cat /sys/bus/thunderbolt/devices/domain*/security # none | user | secure | dponly | usbonly
none neben dieser Boot-Zeile ist eine offene Tür. Sind die Ports für Leute erreichbar, denen du kein Root geben würdest, wäre iommu=pt das Erste, was ich überdenken würde.
Die Gast-Zeile
Das sind die, die in die VM gehören, und drei davon bedeuten hier etwas anderes als auf dem Host.
nmi_watchdog=0 softlockup_panic=0 cpuidle.off=1
cpuidle.off=1 schaltet das cpuidle-Subsystem ab. In einem Gast ist das nahe an kostenlos: die Ruhezustände des Gasts sind Emulation, und es gibt keinen physischen Kern, den man schlafen legen könnte, das Rahmenwerk bringt dir also nur Aufwachlatenz. Auf dem Host ist dasselbe Flag ein echter Handel um Strom und Boost-Spielraum, und es überlappt mit processor.max_cstate=1. Nur auf der Gast-Seite.
softlockup_panic=0 verhindert, dass ein Soft Lockup den Gast in eine Panik schickt. Das schützt in einer VM wirklich, denn ein Soft Lockup dort ist häufig nicht die Schuld des Gasts. Eine weggenommene vCPU sieht genau aus wie eine Aufgabe, die nicht abgeben will. Das ist derselbe Mechanismus, der dahintersteht, warum der Uhr einer VM nicht zu trauen ist. Prüf aber, ob du es brauchst. Auf den meisten Bauten ist es schon 0.
sysctl kernel.softlockup_panic
nmi_watchdog=0 ist das interessante, und es verdient mehr als eine Regel.
Die Watchdog-Frage
Es gibt zwei Melder, die eine Schwelle teilen:
watchdog_thresh=— Set the hard lockup detector stall duration threshold in seconds. The soft lockup detector threshold is set to twice the value. A value of 0 disables both. Default is 10 seconds.
Also: setzt die Schwelle für die Hängedauer des Hard-Lockup-Melders in Sekunden; die Schwelle des Soft-Lockup-Melders wird auf das Doppelte gesetzt; 0 schaltet beide ab; Vorgabe sind 10 Sekunden.
Ein Hard Lockup (nmi_watchdog) schlägt an, wenn eine CPU überhaupt keine Timer-Interrupts mehr annimmt. Ein Soft Lockup schlägt an, wenn eine Aufgabe eine CPU doppelt so lange belegt, ohne abzugeben.
In einem Gast ist es richtig, den Hard-Lockup-Melder abzuschalten. Eine weggenommene vCPU kann ihn ohne eigene Schuld auslösen, und die Arbeit des Melders mit Leistungszählern erzeugt VM-Exits für ein Signal, das nichts als Lärm war.
Auf dem Host ist es eine Ermessensfrage, und sie hängt davon ab, welchem Lärm du tatsächlich nachjagst. Überbuchung hungert Gäste aus, nicht den Host-Kernel — die physischen CPUs des Hosts nehmen weiter Interrupts an, wie voll die VMs auch sind. Der Protokolllärm, den ein beschäftigter Hypervisor wirft, sind also überwiegend Meldungen zu Soft Lockups und RCU-Hängern, nicht Berichte über Hard Lockups per NMI. Ist das der Lärm, wird nmi_watchdog=0 ihn nicht abstellen, und softlockup_panic=0 auch nicht — das stoppt die Panik, nicht die Meldungen.
Die zielgenauen Stellräder sind:
watchdog_thresh=30 # hard 30s, soft 60s — scale to taste
nowatchdog # honest single flag: disables both detectors
sysctl -w kernel.soft_watchdog=0 # runtime, keeps hard-lockup detection
Es gibt einen eigenen und besseren Grund, den Hard-Lockup-Melder auf einem beschäftigten Host abzuschalten, und er hat nichts mit Lärm zu tun: er verbraucht pro CPU einen Leistungszähler der Hardware. Deshalb nimmt der Parameter rNNN, um ein rohes Perf-Ereignis einzustellen. Machst du Profiling über die PMU oder betreibst du eine absichtlich überbuchte Kiste, in der du Latenzschwankung als Preis der Dichte schon angenommen hast, ist es ein vernünftiger Handel, diesen Zähler zurückzugeben — und kleine Hänger, für die du bewusst unterschrieben hast, sind keine Vorfälle.
Triff die Wahl bloß aus diesem Grund und nicht wegen des Lärms, denn nur einer von beiden stimmt.
consoleblank=0 tut nichts. Der Kernel dokumentiert die Abschaltzeit der Konsole als „A value of 0 disables the blank timer. Defaults to 0.“ — 0 schaltet den Abschalt-Timer ab, und die Vorgabe ist 0. Es ist schon aus. Harmlos, aber es ist der zweite verbreitete Parameter ohne Wirkung, und ihn mitzuschleppen lässt eine Zeile überlegt aussehen, wenn sie kopiert ist.
Kurzübersicht: wohin jedes Flag gehört
| Flag | Host | Gast | Anmerkungen |
|---|---|---|---|
iommu=pt | ja | nein | Der Host besitzt die IOMMU. Im Gast nur relevant bei geschachteltem Passthrough mit vIOMMU |
amd_iommu=pgtbl_v2 | ja | nein | DMA-API-Domänen des Hosts. Prüf, dass du v2 bekommen hast und nicht den v1-Rückfall |
amd_iommu=on | — | — | Keine gültige Option. Der Kernel protokolliert „Unknown option - ‘on’“ |
pcie_acs_override=… | ja | nein | Proxmox-Patch, nur Host-Topologie. Schwächt die Isolation von Entwurf her |
pcie_aspm=off | ja | nein | In einem Gast gibt es keine echten PCIe-Links; der Host besitzt den physischen Link |
pci=pcie_bus_perf | ja | nicht verlässlich | Der Host setzt die MPS auf der Leitung. Nimm stattdessen pcie_bus_safe, wenn Geräte über USB4 kommen |
processor.max_cstate=1 | ja | nein | Echte Ruhezustände gehören dem Host. Auf Intel intel_idle.max_cstate=1 ergänzen |
amd_pstate=disable | ja | nein | Gäste steuern die CPU-Frequenz nicht |
cpuidle.off=1 | mit Vorsicht | ja | Im Gast kostenlos. Auf dem Host kostet es Boost-Spielraum und überlappt mit max_cstate |
nmi_watchdog=0 | Ermessen | ja | Im Gast richtig. Auf dem Host: wegen des PMU-Zählers, nicht wegen des Lärms |
softlockup_panic=0 | nein | ja | Hänger im Gast kommen oft vom Scheduler des Hosts. Meist schon die Vorgabe |
consoleblank=0 | — | — | Wirkungslos. Die Kernel-Vorgabe ist schon 0 |
mitigations=off | beide | beide | Auf beiden gültig, mit anderer Risikorechnung: Lecks vom Gast zum Host auf dem Host, Prozess-Isolation im Gast |
default_hugepagesz + hugepages= | beide | beide | Host: er unterlegt VM-Speicher. Gast: eine Last in der VM, die sie will. Andere Zwecke, gleiche Flags |
watchdog_thresh= / nowatchdog | beide | beide | Host: Hänger ruhigstellen, die du angenommen hast. Gast: dem Melder war nie zu trauen |
Drei gehören wirklich auf beide Seiten, und es lohnt sich, genau zu sein, dass „beide“ nicht „aus demselben Grund“ heißt:
mitigations=offgeht es auf dem Host um Lecks vom Gast zum Host und vom Gast zum Gast. Im Gast geht es um Prozess-Isolation in dieser VM. Du kannst es vernünftig an einer Stelle abschalten und an der anderen nicht.- Hugepages unterlegen auf dem Host den Gast-Speicher; im Gast unterlegen sie eine Anwendung. Sie zweimal für denselben Speicher zu reservieren ist Verschwendung, entscheide also, welche Schicht sie will.
- Watchdog-Tuning ist auf dem Host eine Lärmentscheidung und im Gast eine Richtigkeitsentscheidung.
Alles andere gehört auf eine Seite oder die andere, und zwei davon sind überhaupt keine Wahl.
Die zwei Zeilen
Host, auf einer AMD-Kiste mit Passthrough und einem Mandanten:
GRUB_CMDLINE_LINUX_DEFAULT="quiet amd_iommu=pgtbl_v2 iommu=pt pcie_acs_override=downstream,multifunction pcie_aspm=off pci=pcie_bus_perf default_hugepagesz=1G hugepages=64 processor.max_cstate=1 amd_pstate=disable mitigations=off"
Tausch pci=pcie_bus_perf gegen pcie_bus_safe, wenn etwas über USB4 kommt. Lass mitigations=off weg, wenn nicht alle Gäste deine sind. Auf Intel ersetzt intel_iommu=on die AMD-Flags, und intel_idle.max_cstate=1 kommt zur C-State-Grenze dazu.
Linux-Gast:
GRUB_CMDLINE_LINUX_DEFAULT="quiet nmi_watchdog=0 softlockup_panic=0 cpuidle.off=1"
Und lass kvm-clock im Gast in Ruhe — erzwing nicht tsc oder hpet. Die paravirtuelle Uhr gibt es genau deshalb, weil die Zähler nicht deine sind. Das ist das ganze Argument in dem Beitrag über die Zeit in VMs.
Was man löschen sollte
Hast du eine Zeile aus einem Forumsbeitrag geerbt, sind diese zwei das Erste, was rausfliegt, denn sie kosten dich nichts und beweisen, dass die Zeile nie geprüft wurde:
amd_iommu=on— keine gültige Option; der Kernel protokolliert „Unknown option - ‘on’“ und macht weiterconsoleblank=0— schon die Vorgabe
Und prüf den Rest nach einem Neustart gegen /proc/cmdline. Jedes Flag auf dieser Zeile sollte eines sein, für das du einen Grund nennen kannst.
Kannst du nicht sagen, was ein Flag tut, ist es kein Tuning. Es ist Aberglaube. Und es wird von jemandem in den nächsten Aufbau kopiert, der dir vertraut.
Quellen
- Linux-Kernel — die Kommandozeilenparameter des Kernels —
mitigations=,default_hugepagesz=,hugepages=,watchdog_thresh=,nowatchdog,consoleblank=,processor.max_cstate=,amd_pstate=,amd_iommu=und diepci=pcie_bus_*-Politiken - Linux-Kernel-Quelltext —
drivers/iommu/amd/init.c—parse_amd_iommu_options()und die Prüfung der v2-Seitentabellenfähigkeit, die auf v1 zurückfällt - Linux-Kernel-Quelltext —
arch/x86/kernel/pci-dma.c—iommu=pt, dasiommu_set_default_passthrough()aufruft - Linux-Kernel — Thunderbolt — Sicherheitsstufen, und angeschlossene Geräte als DMA-Master
- Proxmox VE — System Administration —
proxmox-boot-tool statusund das Bearbeiten der Kernel-Kommandozeile für systemd-boot gegenüber GRUB