Le problème des lignes de démarrage copiées
Cherchez du réglage Proxmox et vous trouverez une seule longue ligne GRUB_CMDLINE_LINUX, présentée comme un tout, sans indication de quel drapeau s’applique où.
Cela compte plus qu’il n’y paraît. L’hyperviseur et l’invité résolvent des problèmes opposés.
L’hôte veut un accès déterministe au vrai matériel : comportement de l’IOMMU, états de lien PCIe, états d’inactivité physiques. L’invité veut cesser de faire semblant d’avoir du matériel du tout — ses horloges sont des approximations, ses états d’inactivité sont une fiction, et ses blocages sont d’ordinaire l’ordonnanceur d’un autre. À ce titre, le même drapeau peut être correct d’un côté, sans objet de l’autre, et parfois nuisible.
Voici où chacun va réellement.
D’abord : éditez-vous seulement le bon fichier ?
Une installation Proxmox sur racine ZFS démarre avec systemd-boot, où /etc/default/grub n’est lu par personne. L’éditer et redémarrer ne produit ni changement ni erreur. Voilà une heure frustrante.
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
Dans un cas comme dans l’autre, vérifiez plutôt que de supposer :
cat /proc/cmdline
Et côté GRUB, utilisez GRUB_CMDLINE_LINUX_DEFAULT, pas GRUB_CMDLINE_LINUX. Ce dernier s’applique à chaque entrée de démarrage, y compris la récupération — et la récupération est justement le moment où vous voulez le comportement d’origine, pas les mitigations désactivées et les C-states épinglés.
La ligne de l’hôte
IOMMU et passthrough
iommu=pt amd_iommu=pgtbl_v2 pcie_acs_override=downstream,multifunction
iommu=pt met l’IOMMU en mode passthrough : les périphériques assignés aux VM sont traduits, les périphériques natifs de l’hôte contournent la traduction. C’est réel et c’est géré dans arch/x86/kernel/pci-dma.c, qui appelle iommu_set_default_passthrough(true). Le noyau le documente comme équivalent à iommu.passthrough=1.
amd_iommu=on n’existe pas. C’est le paramètre inexistant le plus copié des guides Proxmox. Le parse_amd_iommu_options() du noyau accepte fullflush, force_enable, off, force_isolation, pgtbl_v1, pgtbl_v2, irtcachedis, nohugepages et v2_pgsizes_only. Tout le reste atterrit ici :
pr_notice("Unknown option - '%s'\n", str);
AMD-Vi est activé par défaut quand le micrologiciel l’annonce. Vérifiez votre propre journal et vous trouverez que le paramètre n’a jamais fait le travail qu’on lui prêtait :
dmesg | grep -i "AMD-Vi\|Unknown option"
amd_iommu=pgtbl_v2 est valide — il sélectionne le format de table de pages DMA v2, qui partage la structure de table de pages du CPU plutôt que d’utiliser celle propre à AMD. Deux choses à savoir : la documentation le cantonne à la DMA-API, c’est-à-dire aux domaines de périphériques propres à l’hôte plutôt qu’aux domaines VFIO utilisés pour le passthrough ; et il échoue en sûreté avec une ligne de journal que vous devriez chercher :
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;
}
}
Il vaut donc d’être mesuré sur un nœud à IO hôte lourd, et de vérifier que vous l’avez bien eu.
pcie_acs_override=downstream,multifunction est le correctif hors-arbre de Proxmox. Il découpe les groupes IOMMU en affirmant une isolation que le matériel n’annonce pas, ce qui est ce qui rend le passthrough possible sur les cartes grand public. C’est aussi, très exactement, dire au noyau quelque chose de faux sur la topologie. Bien sur une machine dont vous faites autant confiance aux invités qu’à l’hôte. Pas bien autrement. Il y a plus sur le pourquoi dans l’article sur la taxe IOMMU.
Latence et gigue
pcie_aspm=off processor.max_cstate=1 amd_pstate=disable
pcie_aspm=off garde les liens PCIe hors des états de basse consommation pour qu’une IO qui arrive n’attende jamais qu’un lien se réveille. Cela coûte quelques watts par lien et retire une queue de latence difficile à diagnostiquer. Voir PCIe ASPM et passthrough.
processor.max_cstate=1 plafonne l’inactivité ACPI à C1. Notez le pilote : c’est le bouton processor/acpi_idle, donc sur Intel il vous faut aussi intel_idle.max_cstate=1, parce que intel_idle a priorité. Sur AMD, c’est le bon.
Il y a un vrai contre-argument. Le sommeil profond des cœurs inactifs est ce qui donne au boîtier la marge thermique et électrique pour pousser les cœurs occupés, donc épingler tout à C1 peut baisser votre fréquence de pointe mono-thread tout en augmentant la consommation à vide. Sur un hôte sensible à la latence, ce compromis en vaut d’ordinaire la peine. Sur un hôte qui court après le débit, peut-être pas. Mesurez-le plutôt que d’en hériter.
amd_pstate=disable retombe sur acpi-cpufreq. Il vaut de connaître les alternatives documentées avant d’y recourir : passive (le pilote demande un niveau de performance), active (le pilote EPP, penchant vers la performance ou l’efficacité), et guided. active avec un biais performance, ou passive plus le gouverneur performance, obtient souvent la même latence tout en gardant le contrôle plus fin de CPPC. Et si vous le désactivez, réglez un gouverneur délibérément — atterrir sur acpi-cpufreq avec schedutil peut être un pas en arrière.
Mémoire
default_hugepagesz=1G hugepages=64
default_hugepagesz=1G seul ne réserve rien. Le noyau le documente comme fixant « the size of the default HugeTLB page… the default hugetlb size used for shmget(), mmap() and mounting hugetlbfs » — une unité, pas une allocation. L’allocation vient de hugepages=, documenté comme « Number of HugeTLB pages to allocate at boot ».
Cela compte bien plus pour les pages de 1 Gio que de 2 Mio, parce que des régions contiguës de 1 Gio sont en pratique introuvables une fois que l’hôte a tourné et fragmenté la mémoire. Le démarrage est votre seule chance fiable.
Ensuite l’invité doit s’y inscrire (hugepages: 1024 dans la configuration de la VM). Des pages réservées que rien n’utilise ne sont que de la mémoire que vous ne pouvez pas récupérer, et vous perdez le ballooning et KSM sur les VM qui s’en servent.
Le compromis de sécurité
mitigations=off
Ce n’est pas un seul interrupteur. Le noyau le déploie en une liste, et sur un hyperviseur voici les entrées qui comptent :
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
Le propre résumé du noyau est « improves system performance, but it may also expose users to several CPU vulnerabilities ». L1TF, MDS et MMIO stale data sont spécifiquement des voies de fuite invité-vers-hôte et invité-vers-invité, et kvm.nx_huge_pages est la mitigation iTLB-multihit au sein de KVM lui-même.
Défendable sur une machine mono-locataire où chaque invité est aussi digne de confiance que l’hôte. Pas défendable là où les invités ne sont pas fiables ou appartiennent à des locataires différents. Et notez que cela s’empile avec pcie_acs_override : deux garanties d’isolation indépendantes retirées sur la même ligne. À faire exprès plutôt que par héritage.
Le PCIe sur USB4 change deux de ces réponses
Si vos périphériques PCIe arrivent par USB4 ou Thunderbolt — un GPU externe ou un boîtier NVMe — deux des réponses ci-dessus changent.
Le réglage MPS cesse d’être gratuit
Sur des emplacements fixes, pci=pcie_bus_perf est un petit gain gratuit. Le noyau le décrit comme :
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.
Le hic est qu’il configure les ponts au démarrage, à partir de la topologie présente au démarrage. Sur USB4, le branchement à chaud est le cas normal, et un périphérique ajouté plus tard peut prendre en charge un MPS plus petit que celui auquel le pont a déjà été réglé.
Le noyau dit la partie discrète tout haut en annonçant une politique différente :
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.
Une seule politique porte cette garantie, et c’est celle qui épingle tout à 128 octets — exactement ce que le réglage de MaxPayloadSize cherche à fuir. Pour une topologie à branchement à chaud, pcie_bus_safe (la plus grande valeur prise en charge par tous les périphériques sous le complexe racine) ou simplement laisser tune_off est le point de départ le plus sûr. Le commutateur propre du tunnel plafonne de toute façon le MPS atteignable, donc le plafond n’a jamais été à vous de le lever.
L’empilement de sécurité devient sérieux
Le PCIe externe veut dire que quelqu’un peut brancher un périphérique capable de DMA dans votre hyperviseur. La documentation Thunderbolt du noyau est directe là-dessus :
…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.
L’IOMMU est la défense. Comptez maintenant ce que la ligne de l’hôte lui fait. iommu=pt donne aux périphériques possédés par l’hôte des domaines d’identité non traduits, pcie_acs_override affirme une isolation qui n’existe pas, et mitigations=off désactive les mitigations d’isolation d’invités. Chacun est défendable seul. Ensemble, sur une machine avec un port USB4 physiquement atteignable, ils s’empilent.
Vérifiez où vous en êtes :
cat /sys/bus/thunderbolt/devices/domain*/security # none | user | secure | dponly | usbonly
none à côté de cette ligne de démarrage est une porte ouverte. Si les ports sont atteignables par des gens à qui vous ne donneriez pas root, iommu=pt est la première chose que je reconsidérerais.
La ligne de l’invité
Voici celles qui vont à l’intérieur de la VM, et trois d’entre elles veulent dire quelque chose de différent ici que sur l’hôte.
nmi_watchdog=0 softlockup_panic=0 cpuidle.off=1
cpuidle.off=1 désactive le sous-système cpuidle. Dans un invité, c’est quasi gratuit : les états d’inactivité de l’invité sont de l’émulation, et il n’y a pas de cœur physique à endormir, donc tout ce que le cadre vous achète, c’est de la latence de réveil. Sur l’hôte, le même drapeau est un vrai compromis de consommation et de marge de boost, et il chevauche processor.max_cstate=1. Côté invité seulement.
softlockup_panic=0 empêche un soft lockup de faire paniquer l’invité. C’est vraiment protecteur dans une VM, parce qu’un soft lockup y est souvent pas la faute de l’invité. Un vCPU déprogrammé ressemble exactement à une tâche qui a refusé de céder. C’est le même mécanisme que derrière pourquoi l’horloge d’une VM n’est pas fiable. Vérifiez tout de même si vous en avez besoin. C’est 0 par défaut sur la plupart des versions.
sysctl kernel.softlockup_panic
nmi_watchdog=0 est le plus intéressant, et il mérite plus qu’une règle.
La question du watchdog
Il y a deux détecteurs qui partagent un seul seuil :
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.
Le hard lockup (nmi_watchdog) se déclenche quand un CPU cesse tout à fait de prendre les interruptions d’horloge. Le soft lockup se déclenche quand une tâche accapare un CPU deux fois plus longtemps sans se réordonnancer.
Dans un invité, désactiver le détecteur de hard lockup est juste. Un vCPU déprogrammé peut le déclencher sans faute de sa part, et le travail du détecteur sur les compteurs de perf cause des sorties de VM pour un signal qui n’était que du bruit.
Sur l’hôte c’est un arbitrage, et il dépend du bruit que vous poursuivez réellement. Le sur-provisionnement affame les invités, pas le noyau de l’hôte — les CPU physiques de l’hôte continuent de prendre les interruptions quelle que soit la densité des VM. Donc le bruit de journal qu’un hyperviseur chargé jette est massivement des messages de soft lockup et de blocage RCU, pas des rapports de hard-lockup NMI. Si c’est ça le bruit, nmi_watchdog=0 ne le fera pas taire, et softlockup_panic=0 non plus — cela arrête la panique, pas les messages.
Les boutons ciblés sont :
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
Il y a une raison distincte et meilleure de désactiver le détecteur de hard lockup sur un hôte chargé, qui n’a rien à voir avec le bruit : il consomme un compteur de performance matériel par CPU. C’est pourquoi le paramètre accepte rNNN pour configurer un événement perf brut. Si vous faites du profilage basé sur le PMU, ou faites tourner une machine délibérément sur-provisionnée où vous avez déjà accepté la variance de latence comme le prix de la densité, rendre ce compteur est un compromis raisonnable — et les petits blocages auxquels vous avez consciemment souscrit ne sont pas des incidents.
Faites juste le choix pour cette raison-là plutôt que pour la raison du bruit, parce qu’une seule des deux est vraie.
consoleblank=0 ne fait rien. Le noyau documente le délai d’extinction de la console comme « A value of 0 disables the blank timer. Defaults to 0. » Il est déjà coupé. Inoffensif, mais c’est le deuxième paramètre en circulation courante sans effet, et le traîner fait paraître une ligne réfléchie quand elle est copiée.
Référence rapide : où va chaque drapeau
| Drapeau | Hôte | Invité | Notes |
|---|---|---|---|
iommu=pt | oui | non | L’hôte possède l’IOMMU. Ne vaut dans un invité que si vous faites du passthrough imbriqué avec une vIOMMU |
amd_iommu=pgtbl_v2 | oui | non | Domaines DMA-API de l’hôte. Vérifiez que vous avez eu v2 et non le repli v1 |
amd_iommu=on | — | — | Pas une option valide. Le noyau journalise « Unknown option - ‘on’ » |
pcie_acs_override=… | oui | non | Correctif Proxmox, topologie hôte seulement. Affaiblit l’isolation par conception |
pcie_aspm=off | oui | non | Il n’y a pas de vrais liens PCIe dans un invité ; l’hôte possède le lien physique |
pci=pcie_bus_perf | oui | pas de façon fiable | L’hôte fixe le MPS sur le fil. Utilisez plutôt pcie_bus_safe si des périphériques arrivent par USB4 |
processor.max_cstate=1 | oui | non | Les vrais états d’inactivité sont ceux de l’hôte. Ajoutez intel_idle.max_cstate=1 sur Intel |
amd_pstate=disable | oui | non | Les invités ne contrôlent pas la fréquence du CPU |
cpuidle.off=1 | avec précaution | oui | Gratuit dans un invité. Sur l’hôte il coûte de la marge de boost et chevauche max_cstate |
nmi_watchdog=0 | au cas par cas | oui | Juste dans un invité. Sur l’hôte, faites-le pour le compteur PMU, pas pour le bruit |
softlockup_panic=0 | non | oui | Les blocages d’invité viennent souvent de l’ordonnanceur de l’hôte. D’ordinaire déjà le défaut |
consoleblank=0 | — | — | Sans effet. Le défaut du noyau est déjà 0 |
mitigations=off | les deux | les deux | Valide de part et d’autre, calcul de risque différent : fuites invité-vers-hôte sur l’hôte, isolation de processus dans l’invité |
default_hugepagesz + hugepages= | les deux | les deux | Hôte : sous-tend la mémoire de la VM. Invité : une charge dans la VM qui les veut. Buts différents, mêmes drapeaux |
watchdog_thresh= / nowatchdog | les deux | les deux | Hôte : faire taire des blocages que vous avez acceptés. Invité : le détecteur n’a jamais été digne de confiance |
Trois vont vraiment des deux côtés, et il vaut d’être précis que « les deux » ne veut pas dire « pour la même raison » :
mitigations=offsur l’hôte concerne la fuite invité-vers-hôte et invité-vers-invité. Dans un invité, il concerne l’isolation de processus au sein de cette VM. Vous pouvez raisonnablement le désactiver à un endroit et pas à l’autre.- Les hugepages sur l’hôte sous-tendent la RAM de l’invité ; dans un invité, elles sous-tendent une application. Les réserver deux fois pour la même mémoire est du gaspillage, donc décidez quelle couche les veut.
- Le réglage du watchdog est une décision de bruit sur l’hôte et une décision de justesse dans l’invité.
Tout le reste est d’un côté ou de l’autre, et deux d’entre eux ne sont pas des choix du tout.
Les deux lignes
Hôte, sur une machine AMD faisant du passthrough, mono-locataire :
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"
Échangez pci=pcie_bus_perf contre pcie_bus_safe si quoi que ce soit arrive par USB4. Enlevez mitigations=off si les invités ne sont pas tous les vôtres. Sur Intel, intel_iommu=on remplace les drapeaux AMD et intel_idle.max_cstate=1 rejoint le plafond de C-state.
Invité Linux :
GRUB_CMDLINE_LINUX_DEFAULT="quiet nmi_watchdog=0 softlockup_panic=0 cpuidle.off=1"
Et laissez kvm-clock tranquille dans l’invité — ne forcez pas tsc ni hpet. L’horloge paravirtuelle existe précisément parce que les compteurs ne sont pas à vous. C’est tout l’argument de l’article sur la mesure du temps en VM.
Ce qu’il faut supprimer
Si vous avez hérité d’une ligne d’un billet de forum, ces deux-là sont les premières choses à retirer, parce qu’elles ne vous coûtent rien et prouvent que la ligne n’a jamais été testée :
amd_iommu=on— pas une option valide ; le noyau journalise « Unknown option - ‘on’ » et continueconsoleblank=0— déjà le défaut
Et vérifiez le reste contre /proc/cmdline après un redémarrage. Chaque drapeau de cette ligne devrait être un dont vous pouvez nommer la raison.
Si vous ne pouvez pas dire ce que fait un drapeau, ce n’est pas du réglage. C’est de la superstition. Et il sera copié dans le prochain montage par quelqu’un qui vous fait confiance.
Références
- Linux kernel — the kernel’s command-line parameters —
mitigations=,default_hugepagesz=,hugepages=,watchdog_thresh=,nowatchdog,consoleblank=,processor.max_cstate=,amd_pstate=,amd_iommu=et les politiquespci=pcie_bus_* - Linux kernel source —
drivers/iommu/amd/init.c—parse_amd_iommu_options(), et la vérification de capacité de table de pages v2 qui retombe sur v1 - Linux kernel source —
arch/x86/kernel/pci-dma.c—iommu=ptappelantiommu_set_default_passthrough() - Linux kernel — Thunderbolt — les niveaux de sécurité, et les périphériques connectés comme maîtres DMA
- Proxmox VE — System Administration —
proxmox-boot-tool status, l’édition de la ligne de commande noyau pour systemd-boot contre GRUB