Qué es la virtualización: hipervisores y la extensión del hardware
Cómo se fabrica la ilusión de una máquina entera dentro de otra: virtualización completa frente a paravirtualización, el teorema de Popek y Goldberg y el pecado original de x86, la distinción entre hipervisor de tipo 1 y de tipo 2, y cómo Intel VT-x y AMD-V añadieron un modo de privilegio nuevo que convierte al kernel de Linux en un hipervisor legítimo.
Una máquina virtual es la mentira más elaborada que cuenta un sistema operativo: convence a otro kernel entero de que posee una CPU, una memoria y unos discos que en realidad no existen, sino que son fragmentos prestados de la máquina real, multiplexados sin que el invitado lo note. Durante treinta años esa mentira fue cara —había que interpretar o reescribir el código del invitado en caliente— hasta que Intel y AMD grabaron en el silicio un modo de privilegio nuevo, por debajo del anillo 0, pensado para el hipervisor. Desde entonces el invitado corre a velocidad casi nativa y el kernel de Linux puede ser, él mismo, el hipervisor. Este nivel diseca esa mentira.
- Distinguir virtualización completa de paravirtualización y el problema clásico de x86.
- Separar el hipervisor de tipo 1 del de tipo 2 y ubicar a Linux entre ambos.
- Comprender la extensión de hardware: modos root y non-root,
VMCS/VMCBy el ciclo VM entry/VM exit. - Entender por qué el kernel de Linux puede convertirse él mismo en hipervisor.
Trap-and-emulate y el pecado original de x86
La receta clásica para virtualizar una CPU se llama trap-and-emulate: dejas correr al invitado directamente sobre el procesador real mientras solo ejecute instrucciones inocuas, y colocas al hipervisor en el anillo 0 mientras el kernel invitado corre desprivilegiado en el anillo 1 o 3. Cuando el invitado intenta una instrucción sensible —tocar la tabla de páginas, deshabilitar interrupciones, leer un registro de control— la CPU dispara una excepción, el hipervisor la atrapa y la emula. El invitado cree que mandó sobre el hardware; en realidad pidió permiso sin saberlo.
En 1974 Popek y Goldberg formalizaron cuándo una arquitectura admite este truco: es virtualizable si el conjunto de sus instrucciones sensibles es un subconjunto de sus instrucciones privilegiadas, es decir, si todo lo que puede alterar el estado global de la máquina atrapa al ejecutarse sin privilegio. x86 violaba el teorema de forma flagrante. Robin e Irvine catalogaron en el año 2000 diecisiete instrucciones problemáticas —SGDT, SIDT, SLDT, SMSW, PUSHF, POPF, entre otras— que son sensibles pero no privilegiadas: en el anillo 3 no atrapan, simplemente se comportan distinto y en silencio, dejando al invitado ver el estado real del anfitrión o fallar sin aviso.
Sin trap-and-emulate limpio hubo dos escuelas. VMware recurrió a la traducción binaria dinámica: reescribía en caliente el flujo de instrucciones del kernel invitado, sustituyendo las diecisiete traidoras por secuencias que sí atrapaban. Xen eligió la paravirtualización: modificar el código fuente del invitado para que no ejecutara jamás una instrucción problemática, sino que pidiera el servicio con una llamada explícita al hipervisor, una hypercall.
Completa frente a paravirtual
De esa bifurcación nacen los dos grandes modelos que aún hoy conviven. En la virtualización completa el invitado no sabe que está virtualizado: se le presenta hardware que cree real —una tarjeta de red Intel e1000, un controlador IDE, una BIOS— y el hipervisor emula cada registro de esos dispositivos. La ventaja es la compatibilidad absoluta: arranca un sistema operativo sin tocar una línea de su código. El coste es la lentitud, porque cada acceso a un registro emulado es una trampa que cruza al hipervisor.
En la paravirtualización el invitado coopera. Sabe que vive dentro de una máquina virtual y, en lugar de fingir que habla con hardware real, usa un protocolo diseñado para la ocasión: virtio para los dispositivos (nivel 53.4), hypercalls para las operaciones privilegiadas. Renuncia a la ignorancia a cambio de velocidad. El Linux moderno mezcla ambos: virtualización completa para la CPU y la memoria, gracias al hardware, y paravirtualización para la E/S, gracias a virtio. Lo mejor de cada mundo.
Para cooperar, el invitado primero ha de saber que está virtualizado, y el hardware se lo confiesa. El bit 31 de ECX en la hoja 1 de CPUID —reservado y siempre a cero en una máquina real— lo enciende el hipervisor, y una hoja dedicada, 0x40000000, devuelve una firma que revela quién hospeda:
/* un invitado descubre si corre virtualizado y bajo qué hipervisor */
static void detectar_hipervisor(void)
{
u32 eax, sig[3];
if (!(cpuid_ecx(1) & (1u << 31))) /* bit "hypervisor present" */
return; /* corremos sobre metal desnudo */
cpuid(0x40000000, &eax, &sig[0], &sig[1], &sig[2]);
/* sig deletrea la firma: KVMKVMKVM, Microsoft Hv, XenVMMXenVMM ... */
}
Esa firma es la que consultan los drivers virtio y las rutinas de paravirtualización del kernel para activarse solo cuando hay un anfitrión con quien cooperar. Un invitado sin hipervisor debajo lee ese bit a cero y sigue tratando a la máquina como real.
Hipervisor de tipo 1 y de tipo 2
La segunda gran taxonomía clasifica dónde vive el hipervisor. El tipo 1, o bare metal, corre directamente sobre el hardware desnudo, sin sistema operativo anfitrión debajo: es el primer software que arranca y él mismo planifica las máquinas virtuales. Xen, VMware ESXi y Microsoft Hyper-V son de tipo 1. El tipo 2, o hosted, corre como un programa más sobre un sistema operativo convencional, que sigue gestionando el hardware; el hipervisor es solo otra aplicación con privilegios. VirtualBox y QEMU con su intérprete TCG son de tipo 2.
Linux con KVM desafía la dicotomía. No es un programa sobre el sistema operativo, porque KVM es parte del kernel; pero tampoco es un hipervisor desnudo, porque debajo hay un Linux completo con sus procesos, su planificador y sus drivers. Se le suele llamar tipo 1.5 o híbrido: el kernel de Linux, sin dejar de ser un sistema operativo de propósito general, se transforma en un hipervisor de tipo 1 en cuanto un proceso abre /dev/kvm. La misma máquina que sirve tu navegador puede, en el mismo instante, planificar el vcpu de una máquina virtual como si fuera un hilo más.
Tipo 1 bare metal
Corre directo sobre el hardware desnudo, sin sistema operativo debajo. Es el primer software que arranca y planifica él mismo las máquinas virtuales. Xen, VMware ESXi, Hyper-V.
Tipo 2 hosted
Corre como un programa más sobre un sistema operativo anfitrión que sigue gestionando el hardware. VirtualBox, QEMU con el intérprete TCG.
Híbrido: Linux con KVM
El kernel de propósito general se vuelve hipervisor de tipo 1 al abrir /dev/kvm, sin renunciar a ser un sistema operativo completo con sus procesos y drivers.
La extensión de hardware: VT-x y AMD-V
Lo que rescató a x86 del pecado original fue añadir, hacia 2005, un modo de privilegio enteramente nuevo por debajo del anillo 0. Intel lo llamó VT-x; AMD, AMD-V (o SVM). La idea es idéntica: partir la ejecución en dos mundos ortogonales a los anillos clásicos. El modo root es donde vive el hipervisor; el modo non-root es donde corre el invitado. Y aquí está la magia: dentro del modo non-root el kernel invitado ejecuta en su propio anillo 0, con todos sus privilegios aparentes intactos, pero cualquier instrucción sensible que el hipervisor elija provoca un VM exit que devuelve el control al modo root. Popek y Goldberg quedan satisfechos por hardware: ahora todas las instrucciones sensibles pueden atrapar.
El estado de cada CPU virtual se guarda en una estructura en memoria: el VMCS (Virtual Machine Control Structure) en Intel, el VMCB (Virtual Machine Control Block) en AMD. Contiene los registros del invitado, los del anfitrión a los que volver, y un mapa de bits que decide qué eventos causan VM exit. El hipervisor entra al invitado con VMLAUNCH la primera vez y VMRESUME después —el conjunto de la operación es el VM entry— y lee o escribe el VMCS con VMREAD y VMWRITE. Antes de todo ello, activa la extensión con VMXON. Comprobar si la CPU la ofrece es un solo CPUID:
/* arch/x86: ¿expone esta CPU la virtualización asistida por hardware? */
static bool cpu_tiene_vmx(void)
{
u32 ecx = cpuid_ecx(1); /* hoja 1 de CPUID, registro ECX */
return ecx & (1u << 5); /* bit 5 = VMX en Intel */
}
static bool cpu_tiene_svm(void)
{
u32 ecx = cpuid_ecx(0x80000001); /* hoja extendida de AMD */
return ecx & (1u << 2); /* bit 2 = SVM en AMD */
}
Desde el espacio de usuario la señal equivalente está en las banderas de la CPU:
# vmx = Intel VT-x, svm = AMD-V; si aparece, el hardware puede virtualizar
grep -Eo 'vmx|svm' /proc/cpuinfo | sort -u
lscpu | grep -i virtualization # Virtualization: VT-x
El ciclo completo de vida de una CPU virtual es este vaivén entre los dos mundos: el hipervisor prepara el VMCS, hace VM entry, el invitado corre a velocidad de metal hasta que tropieza con algo que el hipervisor quiso interceptar, se produce el VM exit, el hipervisor lo atiende y vuelve a entrar.
sequenceDiagram participant H as Hipervisor en modo root participant G as Invitado en modo non-root H->>G: VM entry con VMLAUNCH o VMRESUME Note over G: el invitado corre casi a velocidad nativa G->>H: VM exit por instruccion sensible o interrupcion Note over H: KVM lee el VMCS, emula y decide H->>G: VM entry de nuevo
Un invitado puede, a su vez, ser hipervisor de sus propios invitados: es la virtualización anidada. El hardware solo ofrece un nivel real de modo root, así que KVM emula el VMCS del invitado-hipervisor y mezcla sus controles con los suyos antes de cada VM entry. Funciona, es más lento, y es como se ejecutan hoy los runners de CI o los laboratorios de kernel dentro de la nube.
Detente antes de seguir, porque acabas de cerrar un círculo que llevabas cincuenta niveles trazando. Un kernel existe para una sola razón profunda: multiplexar un hardware único entre muchos programas que se creen dueños de él, dándole a cada proceso la ilusión de una CPU propia, una memoria propia, unos dispositivos propios. Todo lo que has estudiado —el planificador que reparte la CPU, la memoria virtual que finge un espacio de direcciones privado, el VFS que unifica discos dispares— es esa ilusión sostenida con esfuerzo. El hipervisor no es una tecnología nueva: es ese mismo concepto aplicado un nivel más arriba. Donde el kernel multiplexa la máquina entre procesos, el hipervisor multiplexa la máquina entre kernels. Donde el proceso se cree dueño de la CPU, el kernel invitado se cree dueño de la máquina. La memoria virtual que da a cada proceso su espacio se vuelve la paginación anidada que da a cada invitado su memoria física falsa. La trampa que atrapa una llamada al sistema se vuelve el VM exit que atrapa una instrucción privilegiada. Por eso el kernel de Linux puede ser hipervisor sin contradecirse: no está haciendo algo ajeno a su naturaleza, sino aplicando su naturaleza a un huésped de rango superior. La virtualización revela que “kernel” e “hipervisor” no son dos cosas, sino la misma idea —dar a muchos la ilusión de poseer lo que es de uno— iterada sobre sí misma. Y como toda recursión, solo termina cuando se toca el hardware real, el único que no está fingiendo.
- Ejecuta
grep -Eo 'vmx|svm' /proc/cpuinfo | sort -uy determina si tu CPU es Intel o AMD según la bandera que aparezca. - Explica en tres líneas por qué las diecisiete instrucciones problemáticas de x86 impedían el trap-and-emulate clásico, y da un ejemplo concreto con
SIDT. - Clasifica VirtualBox, VMware ESXi y Linux+KVM como tipo 1, tipo 2 o híbrido, y justifica cada caso en una frase.
- Argumenta por qué la virtualización completa es más compatible pero más lenta que la paravirtualización, y por qué Linux moderno combina ambas.
- Razona qué papel juega el
VMCSen el ciclo VM entry/VM exit y por qué debe existir una copia por cada CPU virtual, no una por máquina.