9.2.2.1.2.1.4. Otros aspectos

Reservamos este epígrafe para aspectos variados que no requieren excesivo desarrollo, pero pueden resultar muy útiles.

9.2.2.1.2.1.4.1. Procesadores

Para especificar el número de procesadores puede usarse la opción -smp:

$ qemu-system-vga -smp 2 -hda disco.qcw

9.2.2.1.2.1.4.2. Firmware de la placa base

Por defecto, al virtualizar plataformas x86, QEmu utiliza como firmware la BIOS de 16 bits proporcionada por el proyecto SeaBIOS. Esto es suficiente en la mayoría de los casos (especialmente Linux), pero cuando el huésped debe correr on sistema operativo que lo exija (el caso de Windows 11) o quieren precisamente hacerse pruebas sobre el arranque, surge la necesidad de que el huésped arranque un firmware EFI.

Para utilizar EFI basta con indicarle un archivo que contenga el firmware apropiado que habrá de descargarse o instalarse primero en el anfitrión. El usado habitualmente es OVMF para el que existe el paquete ovmf en Debian:

# apt install ovmf

Con el paquete instalado, es tan fácil cambiar el firmware como añadir:

-bios /usr/share/ovmf/OVMF.fd

donde la ruta es el lugar donde el paquete almacena el firmware.

Nota

Para emular la NVRAM, OVMF crear un archivo dentro de la partición ESP denominado NvVars.

Es fundamental tener presente que al cargar este firmware, deja de tener efecto la secuencia de arranque establecida según lo expuesto en el arranque básico. Para manipular la secuencia es necesario pulsar F2 durante el arranque de la máquina virtual, lo cual permitirá entrar en un entorno desde el cuál puede escogerse qué sistema operativo arrancar o alterar permanentemente el orden.

Nota

La secuencia de arranque EFI también puede manipularse desde un Linux huésped utilizando la orden efibootmgr. Hay unos pocos ejemplos de uso en este epígrafe del manual.

9.2.2.1.2.1.4.3. Soporte para vhost-net

La mejora en el rendimiento que supone virtio-net sólo será efectiva si puede comunicarse directamente con el núcleo del anfitrión a través de su módulo vhost-net. Para ello es necesario asegurarse de que el módulo se cargará durante el arranque del anfitrión[1]:

# echo "vhost_net" >> /etc/modules

Y asegurarnos, además, de que el dispositivo de caracteres que crea (/dev/vhost-net) es accesible por el usuario que utiliza QEmu, para lo cual podemos valernos un mecanismo ya utilizado para otros casos:

# cat >> /etc/udev/rules.d/55-qemuperm.rules
KERNEL=="vhost-net", ACTION=="add", GROUP="qemusers", MODE="0660"

que concede permisos al grupo qemusers.

Con estos mimbres ya podremos hacer uso del módulo. Recordemos que, usando el dispositivo virtio-net, utilizábamos una interfaz macvtap0 para dejar la interfaz como adaptador puente así:

-netdev tap,id=nic,fd=3

Pues bien, ahora la tendremos que utilizar así:

-netdev tap,id=nic,fd=3,vhost=on,vhostfd=4

donde 4 es el descriptor de archivo usado a cuya entrada/salida redirigiremos /dev/vhost-net. Por tanto, la orden quedaría ahora así:

$ qemu-system-vga -hda disco.qcw -device virtio-net,netdev=nic,mac=$(</sys/class/net/macvtap0/address) \
   -netdev tap,id=nic,fd=3,vhost=on,vhostfd=4 3<>/dev/tap$(</sys/class/net/macvtap0/ifindex) 4<>/dev/vhost-net

En el caso de una interfaz TAP, hay más diferencias. Propusimos:

-netdev bridge,id=nic,br=br0

El problema es que bridge no admite vhost ni vhostfd, así que tenemos que echar mano de tap. La línea equivalente es:

-netdev tap,id=nic,br=br0,helper=/usr/lib/qemu/qemu-bridge-helper

en que hay explícitamente que especificar el helper. Escrito así, sí podemos hacer el añadido:

-netdev tap,id=nic,br=br0,helper=/usr/lib/qemu/qemu-bridge-helper,vhost=on,vhostfd=4

con lo que la orden completa quedaría así:

$ qemu-system-vga -hda disco.qcw -device virtio-net,netdev=nic \
   -netdev tap,id=nic,br=br0,helper=/usr/lib/qemu/qemu-bridge-helper,vhost=on,vhostfd=4 4<>/dev/vhost-net

9.2.2.1.2.1.4.4. virtio-scsi

Es un driver bastante eficiente que emula completamente una controladora SCSI. Para utilizarlo con un este disco:

-blockdev "driver=file,node-name=f1,filename=disco.qcw" -blockdev "driver=qcow2,node-name=hdd,file=f1"

podemos añadir lo siguiente:

-device virtio-scsi,id=scsi0 -device scsi-hd,drive=hdd,bus=scsi0.0,channel=0,scsi-id=0,lun=0

donde podemos ir cambiando el valor de lun, si añadimos más discos; o scsi-hd por scsi-cd, si de lo que se trata es de una unidad óptica. De este modo, una máquina con una disco y un dispositivo óptico podríamos arrancarla así:

$ qemu-system-vga -device virtio-scsi,id=scsi0 \
   -blockdev "driver=file,node-name=f1,filename=disco.qcw" -blockdev "driver=qcow2,node-name=hdd,file=f1" \
   -device scsi-hd,drive=hdd,bus=scsi0.0,channel=0,scsi-id=0,lun=0,bootindex=1 \
   -blockdev "driver=file,node-name=cdrom,filename=gparted.iso" \
   -device scsi-cd,drive=cdrom,bus=scsi0.0,channel=0,scsi-id=0,lun=1,bootindex=0

Puede obtenerse mejoras en el rendimiento añadiendo 4 colas:

-device virtio-scsi,id=scsi0,num_queues=4

y, tanto para este driver como para virtio-blk, hay mejora también si se define el objeto:

-object iothread,id=iothread0

y se añade su uso a virtio-scsi (o virtio-blk):

-device virtio-scsi,id=scsi0,iothread=iothread0

9.2.2.1.2.1.4.5. USB

Para habilitar USB en el huésped es necesario arrancar la máquina con:

-device qemu-xhci[2]

Este epígrafe, no obstante, está dedicado a describir cómo hacer que un dispositivo USB conectado al anfitrión aparezca directamente en el huésped. Necesitaremos dos cosas:

  1. Si se usa QEmu como usuario sin privilegios, permitir que éste tenga permisos de escritura. Esto se hace de modo análogo a como se permite la escritura en dispositivos TAP de caracteres: definiendo un grupo qemusers donde se encuentren los usuarios que virtualicen y añadiendo una regla para la creación de dispositivos USB:

    # cat >> /etc/udev/rules.d/55-qemuperm.rules
    SUBSYSTEM=="usb", ACTION=="add", GROUP="qemusers", MODE="0660"
    
  2. Pasar al huésped el dispositivo USB que queremos tener disponible en él, para lo cual tenemos que identificar en el anfitrión tal dispositivo:

    $ lsusb
    [...]
    Bus 001 Device 007: ID abcd:1234 Unknown UDisk
    [...]
    

    Supongamos que el dispositivo es éste. Si queremos tener una salida más prolija de sus características, podemos hacer:

    $ lsusb -v -s 1:7
    [...]
        idVendor           0xabcd Unknown
        idProduct          0x1234
    [...]
    

    donde 1:7 son el número de bus y dispositivo que se han observado en la salida sucinta primera. Consultado estos datos podemos pasar la gestión del dispositivo USB al huésped añadiendo:

    -device usb-host,hostbus=1,hostaddr=7[3]

    Estos dos números (1 y 7) son cambiantes. Una alternativa es identificar el dispositivo con su código de vendedor y producto que sí son fijos y, por tanto, no exigirán que los consultemos cada vez que conectamos el dispositivo al equipo:

    -device usb-host,vendorid=0xabcd,productid=0x1234

    Nota

    El dispositivo USB no estará disponible en el anfitrión, mientras esté siendo gestionado por el huésped.

En definitiva, que si queremos que un huésped se haga cargo de la gestión de un dispositivo USB (con identificador de vendedor 0xabcd e identicador de producto 0x1234), deberemos arrancarlo así:

$ qemu-system-vga -hda disco.qcw -device qemu-xhci -device usb-host,vendorid=0xabcd,productid=0x1234

9.2.2.1.2.1.4.6. Directorio compartido

Un aspecto muy importante de la virtualización es tener algún mecanismo eficiente para compartir archivos entre el anfitrión y el huésped. Esto, en principio y siempre que el huésped tenga conectividad de red con el anfitrión, puede llevarse a cabo haciendo uso de un sistema de archivos en red como NFS o CIFS, pero no es eficiente. La mejor solución es utilizar virtio-fs, que está pensado específicamente para este propósito.

Para compartir un directorio del anfitrión debemos antes utilizar en él el demonio virtiofsd como administrador:

# /usr/lib/qemu/virtiofsd --socket-path=/var/run/vm1.sock -o source=/var/lib/share --socket-group=qemusers

donde estamos suponiendo, como llevamos haciendo esta ahora, que qemusers es un grupo al que pertenecen los usuarios que arrancan máquinas virtuales de QEmu.

Hecho lo cual, podemos arrancar el huésped, que usará el socket creado por la orden para comunicarse con el anfitrión:

$ qemu-system-vga -hda disco.qcw -chardev socket,id=char0,path=/var/run/vm1.sock \
   -device vhost-user-fs-pci,chardev=char0,tag=anfitrion,queue-size=1024 \
   -object memory-backend-file,id=mem,size=512M,mem-path=/dev/shm,share=on -numa node,memdev=mem

«anfitrion» es una etiqueta que se utilizará cuando en el huésped queramos montar el directorio compartido:

# mount -t virtiofs anfitrion /mnt

9.2.2.1.2.1.4.7. Compactación de discos

La intención del epígrafe es lograr un disco en formato QCOW2 con el menor tamaño posible, propósito que requiere tres tareas:

  1. Eliminar los archivos innecesarios.

  2. Rellenar con ceros los bloques del sistema de archivos que en algún momento se hubieran escrito y se liberaran posteriormente.

  3. Comprimir la imagen.

Para ello tomemos una imagen llamada disco.qcw y obremos sobre ella. Las dos primeras tareas requieren acceso a su contenido, para lo cual lo mejor es montar el disco en vez de arrancar el sistema huésped:

# modprobe nbd max_part=63
# qemu-nbd -c /dev/nbd0 disco.qcw
# partx -a /dev/nbd0

Esto debería dejar disponibles las particiones de la imagen (/dev/nbd0p1, /dev/nbd0p2, etc.). El siguiente paso es ir montando uno a uno los sistemas de archivos para realizar sobre ellos las dos primeras acciones: borrar lo innecesario y rellenar con ceros. Lo primero es trivial y lo segundo depende de cuál sea el sistema de archivos:

  • Si es ext4, podemos usar zerofree (incluido en el paquete del mismo nombre):

    # zerofree -v /dev/nbd0p1
    
  • Si es NTFS, podemos usar ntfswipe (incluido en el paquete ntfs-3g):

    # ntfswipe -uvb0 /dev/nbd0p1
    

Una vez que hayamos terminado con todos los sistemas de archivos, deberemos desmontar la imagen de disco:

# qemu-nbd -d /dev/nbd0

Y, finalmente, hacer una conversión de la imagen para comprimirla:

$ mv disco{,_z}.qcw
$ qemu-img convert -c -p -f qcow2 disco_z.qcw -O qcow2 disco.qcw
$ rm -f disco_z.qcw

donde la opción -c es la que obra la compresión.

9.2.2.1.2.1.4.8. Cifrado de discos

El formato QCOW2 ofrece la posibilidad de cifrar las propias imágenes de disco, de manera que su contenido esté bloqueado mientras no se conozca cuál es la contraseña usada para su cifrado.

Para ello, lo primero es crear una imagen de disco cifrada:

$ qemu-img create -f qcow2 --object "secret,id=pass,data=micontraseña" \
   -o "encrypt.format=luks,encrypt.key-secret=pass" cifrado.qcw 4G

Hecho esto, la imagen estará cifrada, por lo que cada vez que quiera usarse deberá proporcionarse la contraseña. Así, para montar el disco:

# qemu-nbd -c /dev/nbd0 --object "secret,id=pass,data=micontraseña" \
   --image-opts "driver=qcow2,encrypt.format=luks,encrypt.key-secret=pass,file.filename=cifrado.qcw"

y para usarla en una máquina virtual:

$ qemu-system-vga -object "secret,id=pass,data=micontraseña" -blockdev "driver=file,node-name=disco,filename=cifrado.qcw" \
   -blockdev "driver=qcow2,encrypt.format=luks,encrypt.key-secret=pass,node-name=hdd,file=disco" -device "virtio-blk,drive=hdd"

9.2.2.1.2.1.4.9. Chip TPM

Puede ser muy interesante disponer en el huésped de un chip TPM:

  1. Si el anfitrión es un Linux y dispone de chip, podemos utilizar el chip también en el huésped añadiendo las siguientes opciones:

    -device tpm-tis,tpmdev=tpm0 -tpmdev passthrough,id=tpm0

  2. Si no dispone del chip, aún podemos emularlo del siguiente modo:

    1. Instalamos en el anfitrión el software apropiado:

      # apt install swtpm-tools
      
    2. Creamos un directorio donde almacenar los estados del TPM:

      $  mkdir vmtpm0
      
    3. Lanzamos el software que crea un socket para la comunicación[4]:

      $ swtpm socket --tpmstate dir=vmtpm0 --ctrl type=unixio,path=vmtpm0/swtpm-sock
      
    4. Una vez hechos estos preparativos, ya podremos arrancar máquinas virtuales con un chip virtual TPM añadiendo lo siguiente:

      -chardev socket,id=chrtpm,path=vmtpm0/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0

      donde el socket es el creado por swtpm. Si todo ha ido bien, el huésped debería disponer de un dispositivo /dev/tpm0. Además, al apagar la máquina, swtpm también completará su ejecución.

9.2.2.1.2.1.4.10. Pausado

Supongamos que de una máquina arrancada con:

$ qemu-system-vga -hda disco.qcw -device virtio-net,netdev=nic -netdev user,id=nic

quiere pausarse su ejecución. Para ello, basta con acudir al monitor y ejecutar la orden:

(qemu) stop

Si se quiere volver a poner en ejecución, basta con:

(qemu) cont

Esto proceder, no obstante, sólo es válido mientras no se aborte la ejecución de la máquina. Si nuestra intención, es pausar la máquina, abortar la ejecución y tiempo más adelante volver a arrancar la máquina con el estado justo que tenía cuando se pausó es necesario más:

(qemu) stop
(qemu) migrate "exec:xz -c > estado_disco.xz"
(qemu) quit

Con esto habremos no solo parado la máquina, sino guardado su estado en el archivo estado_disco.xz. Si al reiniciar la máquina queremos recuperar el estado de ejecución, deberemos arrancar la máquina exactamente del mismo modo:

$ qemu-system-vga -hda disco.qcw -device virtio-net,netdev=nic -netdev user,id=nic \
   -incoming "exec:xz -dc estado_disco.xz"

pero añadiendo la opción -incoming. Obviamente, la máquina se encontrará pausada, porque fue así como estaba cuando guardamos su estado, así que será necesario abrir el monitor y:

(qemu) cont

Notas al pie