El problema con las líneas de arranque copiadas

Busca ajuste de Proxmox y encontrarás una sola línea larga GRUB_CMDLINE_LINUX, presentada como una unidad, sin ninguna indicación de qué flags aplican dónde.

Eso importa más de lo que suena. El hipervisor y el invitado están resolviendo problemas opuestos.

El host quiere acceso determinista al hardware real: comportamiento del IOMMU, estados de enlace PCIe, estados de reposo físicos. El invitado quiere dejar de fingir que tiene hardware en absoluto — sus temporizadores son aproximaciones, sus estados de reposo son ficción, y sus atascos suelen ser el planificador de otro. Por eso, el mismo flag puede ser correcto en un lado, inútil en el otro, y de vez en cuando dañino.

Debajo está dónde va cada uno de verdad.

Dónde va cada flag de arranque del kernelSolo hostel hardware real vive aquíiommu=ptamd_iommu=pgtbl_v2pcie_acs_override=…pcie_aspm=offpci=pcie_bus_perf→ pcie_bus_safe si USB4processor.max_cstate=1+ intel_idle.max_cstate en Intelamd_pstate=disablepon un governor tambiénEl invitado no tiene enlaces PCIe,sin C-states y sin cpufreq.Solo invitadodeja de fingir que es hardwarecpuidle.off=1los estados de reposo del invitado son ficciónnmi_watchdog=0una vCPU desplanificada lo disparasoftlockup_panic=0el atasco fue culpa del hostDeja kvm-clock en paz.No fuerces tsc ni hpet —los contadores no son tuyos.En el host estos tres todossignifican algo distinto,y dos de ellos te cuestanalgo real.Ambos — razones distintasmismo flag, decisión apartemitigations=offhost: fugas de invitado a hostinvitado: aislamiento de procesosdefault_hugepagesz + hugepageshost: respalda la RAM del invitadoinvitado: respalda una aplicaciónelige una capa, no ambaswatchdog_thresh / nowatchdoghost: ruido que aceptasteinvitado: nunca de fiar«Ambos» no significa quedebas ponerlo en ambos sitios.Significa que la decisión tiene quetomarse dos veces.Ninguno — estos no hacen nadaamd_iommu=onno es una opción válida; el kernel registra «Unknown option - 'on'» y sigueconsoleblank=0ya es el valor por defecto del kernelSi una línea de arranque copiada contiene cualquiera de estos, nunca se verificó contra /proc/cmdline —que es la comprobación más barata disponible, y la que pilla el error de gestor de arranque equivocadotambién.
Los flags en circulación, ordenados. Dos de los populares no hacen nada en ningún lado, y la fila del watchdog es una verdadera cuestión de criterio y no una regla.

Primero: ¿estás siquiera editando el fichero correcto?

Una instalación de Proxmox sobre raíz ZFS arranca con systemd-boot, donde /etc/default/grub no lo lee nadie. Editarlo y reiniciar no produce cambio ni error. Esa es una hora 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

En cualquier caso, verifica en vez de suponer:

cat /proc/cmdline

Y del lado de GRUB, usa GRUB_CMDLINE_LINUX_DEFAULT, no GRUB_CMDLINE_LINUX. Este último aplica a cada entrada de arranque incluida la de recuperación — y la recuperación es justo cuando quieres comportamiento de fábrica, no mitigaciones desactivadas y C-states fijados.

La línea del host

IOMMU y passthrough

iommu=pt amd_iommu=pgtbl_v2 pcie_acs_override=downstream,multifunction

iommu=pt pone el IOMMU en modo passthrough: los dispositivos asignados a VM se traducen, los dispositivos nativos del host se saltan la traducción. Es real y se maneja en arch/x86/kernel/pci-dma.c, que llama a iommu_set_default_passthrough(true). El kernel lo documenta como equivalente a iommu.passthrough=1.

amd_iommu=on no existe. Este es el parámetro inexistente más copiado en las guías de Proxmox. El parse_amd_iommu_options() del kernel acepta fullflush, force_enable, off, force_isolation, pgtbl_v1, pgtbl_v2, irtcachedis, nohugepages y v2_pgsizes_only. Cualquier otra cosa acaba aquí:

pr_notice("Unknown option - '%s'\n", str);

AMD-Vi está activado por defecto cuando el firmware lo anuncia. Comprueba tu propio log y encontrarás que el parámetro nunca estuvo haciendo el trabajo que se le atribuía:

dmesg | grep -i "AMD-Vi\|Unknown option"

amd_iommu=pgtbl_v2 sí es válido — selecciona el formato de tabla de páginas DMA v2, que comparte la estructura de tabla de páginas de la CPU en vez de usar la propia de AMD. Dos cosas que saber: la documentación lo acota a la DMA-API, es decir los dominios de dispositivo propios del host y no los dominios VFIO usados para passthrough; y falla de forma segura con una línea de log que deberías buscar:

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;
    }
}

Así que vale la pena medirlo en un nodo con IO pesada del lado del host, y vale la pena verificar que de verdad lo conseguiste.

pcie_acs_override=downstream,multifunction es el parche fuera del árbol de Proxmox. Separa los grupos de IOMMU afirmando un aislamiento que el hardware no anuncia, que es lo que hace posible el passthrough en placas de consumo. También es, exactamente, decirle al kernel algo falso sobre la topología. Bien en una máquina cuyos invitados te fías de ellos tanto como del host. No bien de otro modo. Hay más sobre el porqué en el artículo del impuesto del IOMMU.

Latencia y jitter

pcie_aspm=off processor.max_cstate=1 amd_pstate=disable

pcie_aspm=off mantiene los enlaces PCIe fuera de los estados de bajo consumo para que una IO que llega nunca espere a que uno despierte. Cuesta unos pocos vatios por enlace y quita una cola de latencia difícil de diagnosticar. Ver ASPM de PCIe y passthrough.

processor.max_cstate=1 tapa el reposo ACPI en C1. Fíjate en el driver: este es el mando de processor/acpi_idle, así que en Intel necesitas intel_idle.max_cstate=1 también, porque intel_idle tiene prioridad. En AMD este es el correcto.

Hay un contraargumento real. El sueño profundo en los núcleos inactivos es lo que le da al paquete margen térmico y de energía para hacer boost en los ocupados, así que fijar todo en C1 puede bajar tu frecuencia pico de un solo hilo mientras sube el consumo en reposo. En un host sensible a la latencia ese trato suele valer la pena. En un host que persigue rendimiento puede que no. Mídelo en vez de heredarlo.

amd_pstate=disable recae en acpi-cpufreq. Vale la pena conocer las alternativas documentadas antes de echar mano de él: passive (el driver pide un nivel de rendimiento), active (el driver EPP, sesgando hacia rendimiento o eficiencia), y guided. active con un sesgo de rendimiento, o passive más el governor performance, a menudo consigue la misma latencia manteniendo el control más fino de CPPC. Y si lo desactivas, pon un governor a propósito — acabar en acpi-cpufreq con schedutil puede ser un paso atrás.

Memoria

default_hugepagesz=1G hugepages=64

default_hugepagesz=1G por sí solo no reserva nada. El kernel lo documenta como que fija «the size of the default HugeTLB page… the default hugetlb size used for shmget(), mmap() and mounting hugetlbfs» — una unidad, no una asignación. La asignación viene de hugepages=, documentado como «Number of HugeTLB pages to allocate at boot».

Eso importa mucho más para páginas de 1 GiB que de 2 MiB, porque las regiones contiguas de 1 GiB son efectivamente inobtenibles una vez que el host lleva un rato levantado y ha fragmentado la memoria. El arranque es tu única oportunidad fiable.

Luego el invitado tiene que optar por ellas (hugepages: 1024 en la configuración de la VM). Las páginas reservadas que nada usa son solo memoria que no puedes recuperar, y pierdes el ballooning y KSM en las VM que las usan.

El compromiso de seguridad

mitigations=off

Esto no es un solo interruptor. El kernel lo expande a una lista, y en un hipervisor estas son las entradas que importan:

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

El propio resumen del kernel es «improves system performance, but it may also expose users to several CPU vulnerabilities» — mejora el rendimiento del sistema, pero también puede exponer a los usuarios a varias vulnerabilidades de la CPU. L1TF, MDS y MMIO stale data son específicamente caminos de fuga de invitado a host y de invitado a invitado, y kvm.nx_huge_pages es la mitigación de iTLB-multihit dentro del propio KVM.

Defendible en una máquina de un solo inquilino donde cada invitado es tan de fiar como el host. No defendible donde los invitados no son de fiar o pertenecen a inquilinos distintos. Y fíjate en que se apila con pcie_acs_override: dos garantías de aislamiento independientes quitadas en la misma línea. Vale la pena hacerlo a propósito y no por herencia.

El PCIe sobre USB4 cambia dos de estos

Si tus dispositivos PCIe llegan sobre USB4 o Thunderbolt — una GPU externa o una caja NVMe — dos de las respuestas de arriba cambian.

El ajuste de MPS deja de ser gratis

En ranuras fijas, pci=pcie_bus_perf es una pequeña victoria gratis. El kernel lo describe como:

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.

La trampa es que configura los puentes en el arranque, a partir de la topología presente en el arranque. Sobre USB4 el hot-plug es el caso normal, y un dispositivo añadido después puede soportar un MPS más pequeño que aquel al que ya se puso el puente.

El kernel dice la parte callada en voz alta mientras anuncia una política distinta:

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.

Solo una política lleva esa garantía, y es la que fija todo a 128 bytes — exactamente lo que el ajuste de MaxPayloadSize se propone escapar. Para una topología de hot-plug, pcie_bus_safe (el mayor valor soportado por todos los dispositivos bajo el complejo raíz) o simplemente dejar tune_off es el punto de partida más seguro. El propio switch del túnel tapa el MPS alcanzable de todas formas, así que el techo nunca fue tuyo para subirlo.

Por qué el hot-plug cambia la respuesta de política de MPSEn el arranque, pcie_bus_perf configura el puente a partir de lo que puede verpuente — MPS 512dispositivo A · 512dispositivo B · 512presente en el arranqueañadido en caliente · solo 256desajustenada renegociadoEn un chasis eso nunca pasa — la topología en el arranque es la topología para siempre. En un puerto USB4es el caso normal.Las cuatro políticas, y cuál dice el kernel que es segura para hot-plugpcie_bus_tune_offdeja los valores de la BIOS en pazpcie_bus_safeel mayor valor que soportan todos los dispositivos bajo el complejo raízpcie_bus_perfel mayor que permite el bus padre, por dispositivo — más MRRSpcie_bus_peer2peer128 B en todas partes —"guarantees that hot-added devices will work"Solo una política lleva esa garantía, y es la que tira el tamaño de carga queestabas ajustando.
El puente se configura una vez, en el arranque, a partir de los dispositivos presentes entonces. Todo lo posterior tiene que vivir con la decisión — que va bien en un chasis y no va bien en un puerto.

El apilamiento de seguridad se pone serio

PCIe externo significa que alguien puede enchufar un dispositivo capaz de DMA en tu hipervisor. La documentación de Thunderbolt del kernel es directa al respecto:

…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.

El IOMMU es la defensa. Ahora cuenta lo que la línea del host le hace. iommu=pt da a los dispositivos propios del host dominios de identidad sin traducir, pcie_acs_override afirma un aislamiento que no está ahí, y mitigations=off desactiva las mitigaciones de aislamiento de invitados. Cada uno es defendible por sí solo. Juntos, en una máquina con un puerto USB4 físicamente alcanzable, se apilan.

Comprueba dónde estás:

cat /sys/bus/thunderbolt/devices/domain*/security   # none | user | secure | dponly | usbonly

none junto a esa línea de arranque es una puerta abierta. Si los puertos son alcanzables por gente a la que no le darías root, iommu=pt es lo primero que yo reconsideraría.

La línea del invitado

Estos son los que van dentro de la VM, y tres de ellos significan aquí algo distinto de lo que significarían en el host.

nmi_watchdog=0 softlockup_panic=0 cpuidle.off=1

cpuidle.off=1 desactiva el subsistema cpuidle. En un invitado eso es casi gratis: los estados de reposo del invitado son emulación, y no hay núcleo físico que dormir, así que todo lo que el framework te compra es latencia de despertar. En el host el mismo flag es un compromiso real de energía y margen de boost, y se solapa con processor.max_cstate=1. Solo del lado del invitado.

softlockup_panic=0 impide que un soft lockup entre en pánico en el invitado. Esto es genuinamente protector en una VM, porque un soft lockup ahí frecuentemente no es culpa del invitado. Una vCPU desplanificada tiene exactamente el mismo aspecto que una tarea que se negó a ceder. Ese es el mismo mecanismo detrás de por qué no se puede fiar del reloj de una VM. Comprueba si lo necesitas, de todas formas. Es 0 por defecto en la mayoría de las builds.

sysctl kernel.softlockup_panic

nmi_watchdog=0 es el interesante, y merece más que una regla.

La cuestión del watchdog

Hay dos detectores compartiendo un umbral:

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.

El hard lockup (nmi_watchdog) se dispara cuando una CPU deja de tomar interrupciones de temporizador del todo. El soft lockup se dispara cuando una tarea acapara una CPU el doble de tiempo sin planificar.

En un invitado, desactivar el detector de hard lockup es correcto. Una vCPU desplanificada puede dispararlo sin culpa propia, y el trabajo de contadores de rendimiento del detector causa salidas de VM por una señal que no era más que ruido.

En el host es una cuestión de criterio, y depende de qué ruido estés persiguiendo en realidad. El sobreaprovisionamiento hambrea a los invitados, no al kernel del host — las CPU físicas del host siguen tomando interrupciones por muy llenas que estén las VM. Así que el ruido de log que lanza un hipervisor ocupado son abrumadoramente mensajes de soft lockup y de bloqueo RCU, no informes de hard-lockup por NMI. Si ese es el ruido, nmi_watchdog=0 no lo silenciará, y softlockup_panic=0 tampoco — eso detiene el pánico, no los mensajes.

Los mandos dirigidos son:

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

Hay una razón aparte y mejor para desactivar el detector de hard lockup en un host ocupado, que no tiene nada que ver con el ruido: consume un contador de rendimiento de hardware por CPU. Por eso el parámetro acepta rNNN para configurar un evento de rendimiento en bruto. Si estás haciendo perfilado basado en PMU, o corriendo una máquina deliberadamente sobreaprovisionada donde ya has aceptado la varianza de latencia como el precio de la densidad, devolver ese contador es un trato razonable — y los pequeños atascos a los que conscientemente te has apuntado no son incidentes.

Solo toma la decisión por esa razón y no por la razón del ruido, porque solo una de ellas es cierta.

consoleblank=0 no hace nada. El kernel documenta el tiempo de espera del blanqueo de consola como «A value of 0 disables the blank timer. Defaults to 0.» — un valor de 0 desactiva el temporizador de blanqueo; por defecto es 0. Ya está apagado. Inofensivo, pero es el segundo parámetro en circulación común que no tiene efecto, y llevarlo hace que una línea parezca meditada cuando está copiada.

Referencia rápida: dónde va cada flag

FlagHostInvitadoNotas
iommu=ptsínoEl host posee el IOMMU. Solo aplica en un invitado si corres passthrough anidado con un vIOMMU
amd_iommu=pgtbl_v2sínoDominios DMA-API del host. Verifica que conseguiste v2 y no la vuelta a v1
amd_iommu=on——No es una opción válida. El kernel registra «Unknown option - ‘on’»
pcie_acs_override=…sínoParche de Proxmox, solo topología del host. Debilita el aislamiento por diseño
pcie_aspm=offsínoNo hay enlaces PCIe reales en un invitado; el host posee el enlace físico
pci=pcie_bus_perfsíno de forma fiableEl host pone el MPS en el cable. Usa pcie_bus_safe en su lugar si los dispositivos llegan sobre USB4
processor.max_cstate=1sínoLos estados de reposo reales son del host. Añade intel_idle.max_cstate=1 en Intel
amd_pstate=disablesínoLos invitados no controlan la frecuencia de la CPU
cpuidle.off=1con cuidadosíGratis en un invitado. En el host cuesta margen de boost y se solapa con max_cstate
nmi_watchdog=0criteriosíCorrecto en un invitado. En el host, hazlo por el contador PMU, no por el ruido
softlockup_panic=0nosíLos atascos del invitado son a menudo del planificador del host. Suele ser ya el valor por defecto
consoleblank=0——No hace nada. El valor por defecto del kernel ya es 0
mitigations=offambosambosVálido en cualquiera, cálculo de riesgo distinto: fugas de invitado a host en el host, aislamiento de procesos en el invitado
default_hugepagesz + hugepages=ambosambosHost: respaldar la memoria de la VM. Invitado: una carga dentro de la VM que las quiere. Propósitos distintos, mismos flags
watchdog_thresh= / nowatchdogambosambosHost: acallar atascos que aceptaste. Invitado: el detector nunca fue de fiar

Tres van genuinamente en ambos lados, y vale la pena ser exacto en que «ambos» no significa «por la misma razón»:

  • mitigations=off en el host va de fugas de invitado a host y de invitado a invitado. Dentro de un invitado va del aislamiento de procesos dentro de esa VM. Puedes razonablemente desactivarlo en un sitio y no en el otro.
  • Las hugepages en el host respaldan la RAM del invitado; en un invitado respaldan una aplicación. Reservarlas dos veces para la misma memoria es un desperdicio, así que decide qué capa las quiere.
  • El ajuste del watchdog es una decisión de ruido en el host y una decisión de corrección en el invitado.

Todo lo demás es de un lado o del otro, y dos de ellos no son elecciones en absoluto.

Las dos líneas

Host, en una máquina AMD haciendo passthrough, de un solo inquilino:

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"

Cambia pci=pcie_bus_perf por pcie_bus_safe si algo llega sobre USB4. Quita mitigations=off si los invitados no son todos tuyos. En Intel, intel_iommu=on reemplaza los flags de AMD e intel_idle.max_cstate=1 se une al tope de C-state.

Invitado Linux:

GRUB_CMDLINE_LINUX_DEFAULT="quiet nmi_watchdog=0 softlockup_panic=0 cpuidle.off=1"

Y deja kvm-clock en paz en el invitado — no fuerces tsc ni hpet. El reloj paravirtual existe precisamente porque los contadores no son tuyos. Ese es todo el argumento de el artículo de la medición de tiempo en la VM.

Qué borrar

Si heredaste una línea de un post de foro, estas dos son lo primero que quitar, porque no te cuestan nada y prueban que la línea nunca se probó:

  • amd_iommu=on — no es una opción válida; el kernel registra «Unknown option - ‘on’» y sigue
  • consoleblank=0 — ya es el valor por defecto

Y comprueba el resto contra /proc/cmdline después de un reinicio. Cada flag de esa línea debería ser uno del que puedas nombrar una razón.

Si no puedes decir qué hace un flag, no es ajuste. Es superstición. Y lo copiará en la siguiente build alguien que se fía de ti.

Referencias