Hace algún tiempo, ARM Holdings presentó las extensiones de virtualización para su arquitectura de procesadores, las cuales están presentes en algunos modelos de la familia Cortex, como el Cortex-A7 y Cortex-A15. Aunque se trata de una tecnología reciente, los hipervisores KVM y Xen cuentan ya con soporte para dichas extensiones, y permiten levantar SS.OO. invitados de forma similar a como lo hacen en x86.
Si bien es cierto que los SoCs (System-on-Chip) y las placas de desarrollo no invitan a desplegar un número importante de máquinas virtuales, dado el escaso número de cores y RAM que proveen, este supone un primer paso de cara a la estabilización de estas tecnologías, lo que permitirá que ya esté madura para cuando se popularicen los encapsulados de ARM destinados a servidores. Adicionalmente, es una estrategia interesante para poder correr otros SS.OO. (como la familia *BSD) en hardware ARM, sin tener que lidiar con la naturaleza extremadamente heterogénea del mismo.
En esta guía vamos a ver cómo habilitar la virtualización KVM sobre la placa de desarrollo Olinuxino-A20-Micro.
Sobre la placa Olinuxino-A20-Micro
Esta placa es uno de los últimos trabajos de Olimex, una empresa Europea especializada en circuitos electrónicos, comprometida con el Hardware Abierto. El corazón de esta placa es un SoC Allwinner A20 con procesador Cortex-A7 dual-core, con 1 GB de memoria RAM (lista completa de especificaciones).
A efectos prácticos, es una placa muy similar a la popular Cubieboard2, y la mayor parte de los pasos descritos en esta guía sirven para esta última, cambiando las referencias correspondientes.
Requisitos
- Una placa base Olinuxino-A20-MICRO (seguramente funcione también con una Olinuxino-A20-LIME, pero no lo he probado).
- Una tarjeta SD de, al menos, 4 GB.
- (OPCIONAL pero MUY recomendable) Una cable USB a puerto serie, para poder ver el arranque de la placa. El que vende Olimex (USB-Serial-Cable-F) funciona perfectamente.
- Un PC con una distribución reciente de GNU/Linux.
Paso 1: Instalar el compilador cruzado para ARM
Para poder compilar u-boot y el kernel, necesitamos un compilador cruzado para ARM con EABI y Hard Float. La mayor parte de distribuciones modernas tienen un sus repositorios los paquete necesarios. En nuestro caso, usando Ubuntu 14.04, el paquete principal se llama gcc-arm-linux-gnueabihf
:
slp@ubuntu1404:~$ sudo apt-get install gcc-arm-linux-gnueabihf
Paso 2: Compilar el cargador de arranque (u-boot)
Cuando iniciamos nuestra placa, el cargador inicial, integrado en la memoria del SoC, busca un cargador secundario a partir del bloque 8192 de la tarjeta SD.
La imágenes para tarjetas SD de Olimex ya tienen dicho cargador, pero necesitamos reemplazarlo por una versión que detecte cuando la CPU está modo HYP, y evite cambiar a SVC32.
Creamos un directorio para almacenar todos el software que vamos a compilar, y bajamos el código de su repositorio de git:
slp@ubuntu1404:~$ mkdir kvm-arm slp@ubuntu1404:~$ cd kvm-arm/ slp@ubuntu1404:~/kvm-arm$ git clone -b sunxi-next --depth=1 git://github.com/jwrdegoede/u-boot-sunxi.git Clonar en «u-boot-sunxi»... remote: Counting objects: 9155, done. remote: Compressing objects: 100% (7854/7854), done. remote: Total 9155 (delta 1708), reused 3512 (delta 1113) Receiving objects: 100% (9155/9155), 13.73 MiB | 1.58 MiB/s, done. Resolving deltas: 100% (1708/1708), done. Checking connectivity... hecho.
Y compilamos u-boot, especificando la configuración de nuestra placa y el prefijo del compilador cruzado:
slp@ubuntu1404:~/kvm-arm/u-boot-sunxi$ make CROSS_COMPILE=arm-linux-gnueabihf- A20-OLinuXino_MICRO_config Configuring for A20-OLinuXino_MICRO - Board: sun7i, Options: A20_OLINUXINO_M,CONS_INDEX=1,STATUSLED=226,SPL,SUNXI_EMAC slp@ubuntu1404:~/kvm-arm/u-boot-sunxi$ make CROSS_COMPILE=arm-linux-gnueabihf-
Si tenemos más de un procesador en el equipo en el que estamos compilando, no puede interesar añadir la opción -j4
para paralelizar la compilación.
Si todo ha ido bien, deberíamos tener un fichero con nombre u-boot-sunxi-with-spl.bin
, listo para ser grabado en la tarjeta SD.
Paso 3: Preparar el script de arranque
Para evitar tener que introducir los comandos manualmente, u-boot busca el fichero boot.scr
en varios dispositivos de bloques, y lo carga en caso de encontrarlo. Dicho fichero contiene la lista de comandos a ejecutar, en formato binario. A continuación, vamos a crear un nuevo script de arranque.
En el directorio base de u-boot-sunxi
, abrimos un editor de texto y creamos el fichero boot.cmd
, en el que pegamos el siguiente contenido:
setenv kernel_addr_r 0x46000000 # 8M setenv fdt_addr 0x49000000 # 2M setenv fdt_high 0xffffffff # Load fdt in place instead of relocating fatload mmc 0 ${kernel_addr_r} /uImage setenv bootargs "console=ttyS0,115200 ro root=/dev/mmcblk0p2 rootwait" fatload mmc 0 ${fdt_addr} /sun7i-a20-olinuxino-micro.dtb bootm ${kernel_addr_r} - ${fdt_addr}
Guardamos el fichero, y la compilamos con mkimage
:
slp@ubuntu1404:~/kvm-arm/u-boot-sunxi$ ./tools/mkimage -C none -A arm -T script -d boot.cmd boot.scr Image Name: Created: Wed Jul 23 13:48:35 2014 Image Type: ARM Linux Script (uncompressed) Data Size: 365 Bytes = 0.36 kB = 0.00 MB Load Address: 00000000 Entry Point: 00000000 Contents: Image 0: 357 Bytes = 0.35 kB = 0.00 MB
Ya tenemos listo el script de arranque binario en el fichero boot.scr
.
Paso 4: Compilar el kernel
Como suele ser habitual en el mundo ARM, el fabricante del SoC (Allwinner, en este caso) tras liberar una versión inicial del kernel con soporte para el modelo en cuestión, se desentiende del mismo y no invierte esfuerzo alguno en mantenerlo o intentar introducir los cambios en la versión oficial de Linux (denominada mainline). Eso ocasiona que el soporte en el kernel se quede atascado en versiones antiguas, que carecen de características nuevas como, en el caso que nos interesa, soporte para KVM.
Por fortuna, los voluntarios de la comunidad linux-sunxi han estado trabajando en enviar los cambios para soportar esta familia de SoCs en el kernel oficial, y la versión 3.16 tienen soporte para gran parte del hardware y, además, integra KVM.
Así pues, descargamos y descomprimimos la última versión disponible del kernel, que en el momento de escribir esto era la 3.16-rc6 (en teoría, cualquier versión superior debería funcionar igualmente, aunque no puedo asegurarlo):
slp@ubuntu1404:~/kvm-arm$ wget https://www.kernel.org/pub/linux/kernel/v3.x/testing/linux-3.16-rc6.tar.xz slp@ubuntu1404:~/kvm-arm$ tar xf linux-3.16-rc6.tar.xz
A continuación, generamos el fichero de configuración por defecto, y lanzamos el menú de configuración del kernel (si os falla este último paso, es muy posible que necesitéis instalar el paquete libncurses5-dev, o su equivalente en vuestra distribución):
slp@ubuntu1404:~/kvm-arm$ cd linux-3.16-rc6/ slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm sunxi_defconfig # # configuration written to .config # slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm menuconfig
En el menú de configuración, debemos activar las siguientes opciones:
General setup -> Control Group support
System Type -> Support for Large Physical Address Extension
Device Drivers -> Block devices -> Loopback device support
Virtualization
Virtualization -> Kernel-based Virtual Machine (KVM) support (NEW)
El resto, podemos dejarlo tal y como está. Al salir de menuconfig, recordad guardar la configuración.
Finalmente, lanzamos la compilación del kernel y los DTBs (ficheros de descripción del hardware):
slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ export PATH=$PATH:/home/slp/kvm-arm/u-boot-sunxi/tools slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- LOADADDR=0x40008000 uImage dtbs
Esto se tomará algún tiempo (menos de lo que suele costar compilar un kernel de x86, dado el menor número de componentes), y una vez finalizado, tendremos los ficheros que necesitamos, arch/arm/boot/zImage
y arch/arm/boot/dts/sun7i-a20-olinuxino-micro.dtb
.
Paso 5: Preparar la tarjeta SD
Ahora que tenemos todas las piezas, necesitamos preparar la tarjeta SD para que arranque con el nuevo kernel. Si ya teníamos instalada la imagen oficial por defecto de Olimex, no hace falta que la reemplacemos. En caso contrario, deberemos descargar y grabar la última imagen disponible en la página de Olinuxino-A20-MICRO.
Una vez que tengamos la tarjeta SD con la imagen oficial, escribimos el nuevo u-boot al principio de la misma:
slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ cd ~/kvm-arm slp@ubuntu1404:~/kvm-arm$ sudo dd if=u-boot-sunxi/u-boot-sunxi-with-spl.bin of=/dev/mmcblk0 bs=1k count=8
Seguidamente, montamos la primera partición de la tarjeta, y copiamos el script de arranque, el kernel, y el DTB:
slp@ubuntu1404:~/kvm-arm$ sudo mount /dev/mmcblk0p1 /mnt slp@ubuntu1404:~/kvm-arm$ sudo cp u-boot-sunxi/boot.scr /mnt slp@ubuntu1404:~/kvm-arm$ sudo cp linux-3.16-rc6/arch/arm/boot/uImage /mnt slp@ubuntu1404:~/kvm-arm$ sudo cp linux-3.16-rc6/arch/arm/boot/dts/sun7i-a20-olinuxino-micro.dtb /mnt slp@ubuntu1404:~/kvm-arm$ sudo umount /mnt
Con esto, ya deberíamos tener lista la tarjeta SD para el primer arranque.
Paso 6: Primer arranque e instalación de QEMU
Si estáis usando el cable USB a serie, al arrancar con la tarjeta preparada en el punto anterior deberíais ver los mensajes de los nuevos u-boot y kernel. En caso contrario, deberéis esperar uno o dos minutos, para luego intentar conectar por SSH.
Para poder lanzar nuestro primer guest, necesitamos una versión reciente de QEMU. La versión que viene con la imagen oficial, basada en Debian, es algo vieja, por lo que deberemos compilarla nosotros mismos.
Descargamos las dependencias de compilación para el paquete qemu actual:
slp@a20:~$ sudo apt-get build-dep qemu slp@a20:~$ sudo apt-get install libpixman-1-dev
A continuación, creamos un directorio para fuentes, descargamos QEMU 2.0 y lo descomprimimos:
slp@a20:~$ mkdir srcs slp@a20:~$ cd srcs slp@a20:~/srcs$ wget http://wiki.qemu-project.org/download/qemu-2.0.0.tar.bz2 --2014-07-24 06:28:00-- http://wiki.qemu-project.org/download/qemu-2.0.0.tar.bz2 Resolving wiki.qemu-project.org (wiki.qemu-project.org)... 140.211.15.109 Connecting to wiki.qemu-project.org (wiki.qemu-project.org)|140.211.15.109|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 12839647 (12M) [application/x-bzip2] Saving to: `qemu-2.0.0.tar.bz2' 100%[======================================>] 12,839,647 2.08M/s in 8.2s 2014-07-24 06:28:09 (1.49 MB/s) - `qemu-2.0.0.tar.bz2' saved [12839647/12839647] slp@a20:~/srcs$ tar xf qemu-2.0.0.tar.bz2
Configuramos la compilación indicando que queremos KVM (normalmente, el script de configuración lo activa automáticamente, pero de esta forma nos aseguramos que si no puede hacerlo por algún motivo, nos avise) y que nos produzca un binario sólo con soporte para emulación de ARM:
slp@a20:~/srcs$ cd qemu-2.0.0/ slp@a20:~/srcs/qemu-2.0.0$ ./configure --enable-kvm --target-list=arm-softmmu
Finalmente, lanzamos la compilación e instalación (por defecto, pondrá los binarios en /usr/local/bin):
slp@a20:~/srcs/qemu-2.0.0$ make slp@a20:~/srcs/qemu-2.0.0$ sudo make install
Paso 7: Compilando un kernel para el Guest
Cuando usamos QEMU con KVM, el hardware que se está simulando es una Versatile Express A15, una de las plataformas de referencia que provee la propia ARM Holdings. Por lo tanto, necesitaremos un kernel con soporte para dicha placa.
Volvemos al directorio de compilación que hemos empleado en el Paso 3, cargamos la configuración por defecto de Versatile Express A15 y lanzamos el menú de configuración:
slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_defconfig slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
Activamos las siguientes opciones:
General setup -> Configure standard kernel features (expert users)
General setup -> open by fhandle syscalls
Enable the block layer -> Support for large (2TB+) block devices and files
Y generamos una kernel en formato zImage:
slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage
Paso 8: Lanzando nuestro primer Guest
Además del kernel, necesitaremos también preparar un sistema raíz con las distribución que queremos emplear. Como vamos a emular una CPU con arquitectura ARMv7, la opción más óptima es que descarguemos una distribución compilada para earmv7hf. En esta guía, vamos a utilizar una imagen mínima (JeOS) de OpenSuSE, pero serviría cualquier otra que sea de nuestro gusto.
Creamos un directorio para contener los ficheros necesarios para el Guest:
slp@a20:~/srcs/qemu-2.0.0$ mkdir -p ~/kvm-arm/opensuse slp@a20:~/srcs/qemu-2.0.0$ cd ~/kvm-arm/opensuse slp@a20:~/kvm-arm/opensuse$
Copiamos el kernel que hemos compilado en el Paso 4, esta vez en formato zImage, y el DTB correspondiente para la placa Versatile Express A15:
slp@a20:~/kvm-arm/opensuse$ scp slp@10.x.x.x:~/kvm-arm/linux-3.16-rc6/arch/arm/boot/zImage . slp@10.x.x.x's password: zImage 100% 2558KB 2.5MB/s 00:00 slp@a20:~/kvm-arm/opensuse$ scp slp@10.x.x.x:~/kvm-arm/linux-3.16-rc6/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dtb . slp@10.x.x.x's password: vexpress-v2p-ca15-tc1.dtb 100% 12KB 12.0KB/s 00:00
Ahora creamos una imagen raw que contenga el userland de OpenSuSE:
slp@a20:~/kvm-arm/opensuse$ wget http://download.opensuse.org/ports/armv7hl/factory/images/openSUSE-Factory-ARM-JeOS.armv7-rootfs.armv7l-1.12.1-Build175.2.tbz slp@a20:~/kvm-arm/opensuse$ qemu-img create -f raw opensuse-factory.img 1G Formatting 'opensuse-factory.img', fmt=raw size=1073741824 slp@a20:~/kvm-arm/opensuse$ sudo losetup /dev/loop0 opensuse-factory.img [sudo] password for slp: slp@a20:~/kvm-arm/opensuse$ sudo mkfs.ext4 /dev/loop0 mke2fs 1.42.5 (29-Jul-2012) Discarding device blocks: done Filesystem label= OS type: Linux Block size=4096 (log=2) Fragment size=4096 (log=2) Stride=0 blocks, Stripe width=0 blocks 65536 inodes, 262144 blocks 13107 blocks (5.00%) reserved for the super user First data block=0 Maximum filesystem blocks=268435456 8 block groups 32768 blocks per group, 32768 fragments per group 8192 inodes per group Superblock backups stored on blocks: 32768, 98304, 163840, 229376 Allocating group tables: done Writing inode tables: done Creating journal (8192 blocks): done Writing superblocks and filesystem accounting information: done slp@a20:~/kvm-arm/opensuse$ sudo mount /dev/loop0 /mnt slp@a20:~/kvm-arm/opensuse$ sudo tar xf openSUSE-Factory-ARM-JeOS.armv7-rootfs.armv7l-1.12.1-Build175.2.tbz -C /mnt slp@a20:~/kvm-arm/opensuse$ sudo umount /mnt slp@a20:~/kvm-arm/opensuse$ sudo losetup -d /dev/loop0
Finalmente, lanzamos nuestro primer Guest:
slp@a20:~/kvm-arm/opensuse$ sudo qemu-system-arm -enable-kvm -m 512m -M vexpress-a15 -cpu host -kernel zImage -dtb vexpress-v2p-ca15-tc1.dtb -append "root=/dev/vda console=ttyAMA0 rootwait" -drive if=none,file=opensuse-factory.img,id=factory -device virtio-blk-device,drive=factory -net nic -net user -monitor null -serial stdio -nographic Booting Linux on physical CPU 0x0 Initializing cgroup subsys cpuset Linux version 3.16.0-rc6 (slp@ubuntu1404) (gcc version 4.8.2 (Ubuntu/Linaro 4.8.2-16ubuntu4) ) #12 SMP Thu Jul 24 11:48:10 CEST 2014 CPU: ARMv7 Processor [410fc074] revision 4 (ARMv7), cr=10c53c7d CPU: PIPT / VIPT nonaliasing data cache, VIPT aliasing instruction cache Machine model: V2P-CA15 Memory policy: Data cache writealloc PERCPU: Embedded 7 pages/cpu @9fbdf000 s7552 r8192 d12928 u32768 Built 1 zonelists in Zone order, mobility grouping on. Total pages: 130048 Kernel command line: root=/dev/vda console=ttyAMA0 rootwait PID hash table entries: 2048 (order: 1, 8192 bytes) Dentry cache hash table entries: 65536 (order: 6, 262144 bytes) Inode-cache hash table entries: 32768 (order: 5, 131072 bytes) Memory: 513156K/524288K available (4604K kernel code, 188K rwdata, 1284K rodata, 235K init, 150K bss, 11132K reserved) ... [ OK ] Started OpenSSH Daemon. [ OK ] Reached target Multi-User System. [ OK ] Reached target Graphical Interface. Welcome to openSUSE Factory "Bottle" - Kernel 3.16.0-rc6 (ttyAMA0). linux login:
Ya tenemos listo nuestro Guest para empezar a jugar con él. Las credenciales por defecto en esta imagen son “root/linux”:
linux login: root Password: linux Last login: Thu Jul 24 11:57:53 on ttyAMA0 Have a lot of fun... linux:~ # uname -a Linux linux 3.16.0-rc6 #12 SMP Thu Jul 24 11:48:10 CEST 2014 armv7l armv7l armv7l GNU/Linux linux:~ # cat /proc/cpuinfo processor : 0 model name : ARMv7 Processor rev 4 (v7l) Features : swp half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xc07 CPU revision : 4 Hardware : ARM-Versatile Express Revision : 0000 Serial : 0000000000000000
Conclusiones
Aunque la virtualización sobre ARM es todavía una tecnología emergente, hemos podido ver en esta guía que el software ya está preparado para recibir la nueva generación de procesadores de esta arquitectura orientados a entornos de servidor.
¿Conseguirá hacerse un hueco en un mundo dominado por Intel/AMD?