March 20, 2019
In this tutorial, we'll look at how to create a functional and simple Arch Linux (from an existing Arch Linux installation) virtual machine image, that can have network access, display graphical windows and share a folder with the host.
A virtual machine is useful in a lot of development scenarios, but it's particularly essential in Linux Kernel development. It can be really time-consuming to install the kernel on your own system and then needing to reboot the machine just to see if your
printk is working. This is why this topic was already covered here, with Ezequiel explaining how to use virtme and Frédéric showing how to setup a minimal Debian to use with QEMU-KVM.
As already said by my colleagues, there's no need to install a complete system from scratch, using a installation disc. If you've already used to Arch Linux, you probably know that the distro slogan is "Keep it Simple". So let's try not to suffer (too much) in order to get a kernel development environment.
Since we're going to work with two systems at the same time, let's explicitly state in which machine you should run the command:
(host)$ # for you real machine (guest)$ # for the virtual machine
We will need a disk to store our new OS in. Hopefully, we have no need for a physical device, a file will do the work. It's up to you how much space you'll allocate, but I recommend a minimum of 4 Gb. We're going to create a 5G sparse file (a file that allocates space as needed) to be our disk, using the
(host)$ truncate -s 5G arch_disk.raw
If you check the size with
du -h arch_disk.raw, it's going to say
0 (because we haven't used it yet), but if you run
du -h --apparent-size arch_disk.raw you can see the maximum size the file may expand.
Let's add a file system at this file. This means that this file will be ready to contain files and folders, and will contain the new file system as its data. This will make our disk look and behave as a single partition.
(host)$ mkfs.ext4 arch_disk.raw
Since we have a file that represents a partition, we can mount it. Create a directory to be the mounting point and mount it:
(host)$ mkdir mnt (host)$ sudo mount arch_disk.raw mnt
Now that we have a disk, let's place an initial Arch system, just like when we are installing Arch. Install these packages:
(host)$ pacman -S arch-install-scripts qemu
The first package has some scripts that are really helpful to install Arch Linux (e.g.
arch-chroot) and the second one is the QEMU emulator.
Just remember to check if you are using a nice mirror on top of your mirrorlist file (
/etc/pacman.d/mirrorlist). This will speedup your download.
Now, let's transform that formatted partition into a functional system:
(host)$ sudo pacstrap mnt base base-devel
This will create the directory structure and install the basic packages. You can navigate through
mnt/ and see an entire file system there, and even change the root to this new system:
(host)$ sudo arch-chroot mnt
If you use
pwd you will see that you are definitely in a guest system. If you use
uname -r you can see the kernel version was installed on your host system:
(guest)$ ls bin dev home lib64 mnt proc run srv tmp var boot etc lib lost+found opt root sbin sys usr (guest)$ ls home/ (guest)$ pwd / (guest)$ uname -r 4.20.7-arch1-1-ARCH (guest)$ ls home/
Use CTRL+D to exit from the guest and use
sudo umount mnt to umount the disk. Let's use a custom kernel now.
Now, for the next steps, you will need a compiled and functional Linux Kernel. You already have one, since you are running a GNU/Linux distribution, but if you still don't have a custom kernel to make experiments, you can easily get one like this:
(host)$ git clone --depth=1 git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git (host)$ cd linux (host)$ make x86_64_defconfig (host)$ make kvm_guest.config (host)$ make -j8
git command will clone Linus' tree source code of the kernel into your machine. With the argument
--depth=1, you won't clone all the commit history of the kernel and the download will be faster. If you want to have the history, remove this argument. The
make ...config commands will create a basic kernel with virtualization powers.
make -j8 can take some time and some CPU usage. The
-j8 arg will create 8 jobs to compile the kernel, change it according to the number of CPUs in your machine.
Let's play with QEMU. We are going to use the following arguments when calling it:
-hda disk.raw: specify that the
arch_disk.rawfile should be provided as the first hard disk in the emulated system;
-m 4G: amount of RAM we are going to loan to the virtual machine;
-nographic: QEMU will run on the terminal instead of a graphical window;
-kernel your_kernel_dir/arch/x86_64/boot/bzImage: define the file where your kernel is. You can also use the kernel installed on your machine;
-smp 1: how many virtual CPUs QEMU will use;
-append "root=/dev/sda rw console=ttyS0 loglevel=5": kernel parameters.
rootdefines which disk/partition has the root file system. In our case, it will be the first storage device, the hard disk
rwthat we want to read and write to disk;
consoleis to set the standard output of the kernel and of the PID 1;
loglevelis to set how much log the kernel will output to the console and 7 is the highest and it will display all the kernel messages in the console prompt;
--enable-kvm: this is to enable hardware acceleration to virtualization.
Feel free to change these parameters to suit your environment. Now run it all together:
(host)$ qemu-system-x86_64 -hda arch_disk.raw -m 4G -nographic -kernel \ your_kernel_dir/.../bzImage -append "root=/dev/sda rw console=ttyS0 loglevel=5" \ --enable-kvm
When the prompt displays
archlinux login:, just type
root and enter. Use
uname -r again to check which kernel you are running. When you are done, use CTRL+a then x to exit:
archlinux login: root (guest)$ uname -r 5.0.0-rc1+
You may want to install some packages on your virtual machine or perform some network tests, but as it stands, you can't reach the internet:
(guest)$ ping collabora.com ping: collabora.com: Temporary failure in name resolution
Let's use our host as a network bridge; login in our virtual machine with root user and then:
(guest)$ systemctl enable dhcpcd (guest)$ systemctl start dhcpcd
Every time your system boots, it'll start to run a DHCP service and connects to the internet using a "virtual wire" to the host.
(guest)$ ping collabora.com PING collabora.com(gin.collabora.co.uk (2a00:1098:0:82:1000:25:2eeb:e3f6)) 56 data bytes
If you want to debug graphical applications inside your virtual machine, you may follow one of the methods:
The first difference here is that we're going to remove the
-nographic argument from QEMU. Run it now and it should open a new window.
Now, make sure to have this module enabled at your kernel:
DRM_BOCHS, a driver to help us displaying graphical content in QEMU. This means that if you open the
.config file at your kernel source directory (the same place you ran
make -j8), it should have this line:
CONFIG_DRM_BOCHS=y. You need to recompile the kernel each time you change it's configuration.
We need software to manage the windows and display them on the screen, so we are going to install a Wayland compositor:
(guest)$ pacman -S weston xorg-server-xwayland xorg-fonts-type1 xorg-xclock
Create a file
~/.config/weston.ini in guest and add this:
weston command in guest, and the graphical interface should appear. You can use your mouse to open the
weston-terminal at the top left corner. If your mouse is moving oddly, make sure your window zoom is set to the "Best fit" option in the QEMU window bar. Inside the terminal, run
xclock and you should be able to check the hours inside your virtual machine. When you're done, you can use Ctrl+Alt+Backspace to quit Weston.
|Screen capture showing
This is just the basic setup using QEMU graphical mode. To improve performance and usability (like to have clipboard and multimonitor support) one can use a QEMU front end (like
virt-manager) or SPICE to expand your options and features between guest and host. Check the ArchWiki to learn more about those options.
This is a way to display graphical interfaces running in a remote host in the local machine.This means that the X server on your guest machine will forward the graphical input/output to the X client on your host machine. You can keep the argument
-nographic in QEMU. Let's get some packages that will help us (you need to install
xorg-auth in both machines, the host and guest):
(host)$ sudo pacman -S xorg-xauth
(guest)$ pacman -S xorg-xauth xorg-xclock openssh xorg-fonts-type1
As in the last section, our goal here is to run the application
xclock, a simple graphical clock as a proof of concept. If you try to run it now, this should happen:
(guest)$ xclock Error: Can't open display:
That's why we are going to use SSH, to help us get the graphical output. The default TCP port of SSH is 22, but probably your localhost already reserved this port. Besides this, the IP address QEMU gives to your VM usually isn't routable. So, let's map our guest 22 to another port, let's say 1337, and expose this port using the
hostfwd argument. This can be done with these additional QEMU flags:
-net nic: creates a basic network card;
-net user,hostfwd=tcp::1337-:22: maps port host's 1337 to guest's 22.
If you try to access the machine now via
ssh (with QMEU running with this new parameters), it won't be possible yet:
(host)$ ssh root@localhost -p 1337 ssh_exchange_identification: read: Connection reset by peer
In the guest, if you run
systemctl status sshd, you can see that it isn't running. Let's configure
sshd before running it. Let's edit guest's
/etc/ssh/sshd_config to ensure that you have those lines uncommented and edited:
PermitRootLogin yes # allows root login with password via ssh X11Forwarding yes # allows XForwarding
We don't have a password right now for root, so we can't login using a password. To create a password, use the command
(guest)$ passwd New password: Retype new password: passwd: password updated successfully
sshd to run at boot and to start now:
(guest)$ systemctl enable sshd (guest)$ systemctl start sshd (guest)$ systemctl status sshd ... Active: active (running) ... ...
If you try again, you can now login with your password.
You might not want to type the password every time you want to login. There's an optional way of bypassing this: adding a SSH public key to the server. Check your
~/.ssh/ on your host directory for the files
id_rsa.pub. This means that you already have a public-private SSH key pair. If you don't have a pair yet, you can generate one with:
(host)$ ssh-keygen -t rsa -b 4096
The first question the command will ask is where your key should be placed. You can set it in the default directory. The second question is about the password to secure your private key. If you intend to reuse this key somewhere, I strongly suggest you use a strong password here. This protects your private key if someone has access to it. Otherwise, just leave it blank.
Now that we have a key, let's add to the guest with the following command (when it asks for the password, just type the one you have created with the command
(host)$ ssh-copy-id root@localhost -p 1337
Try now and you should be logged in without asking for a password:
(host)$ ssh root@localhost -p 1337 Last login: Mon Feb 18 17:57:15 2019 from 10.0.2.2 (guest)$
If you don't have a
.Xauthority file on your home folder, it will prompt a warning, but don't worry: after the warning, the file will be created. Access the virtual machine using
ssh with the argument
(host)$ ssh -X root@localhost -p 1337
And check the hours with
xclock! To exit, you can use CTRL+D.
|Screen capture showing
Copy-pasting is definitely not the best way to send a file to your guest machine. Hopefully, we can easily solve this problem by sharing a folder between machines.
If you ran
make x86_64_defconfig and
make kvm_guest.config before the kernel compilation, you should already have the required modules enabled. If you get errors, please make sure your kernel has the following options enabled:
(host)$ grep 'VIRTIO_PCI=\|NET_9P=\|9P_FS=\|NET_9P_V\|IG_PCI=' .config CONFIG_NET_9P=y CONFIG_NET_9P_VIRTIO=y CONFIG_PCI=y CONFIG_VIRTIO_PCI=y CONFIG_9P_FS=y
If something looks
# CONFIG_XXX is not set, you should enable it.
Now, elect a folder to be your shared holder, for example
/home/user/shared. Then, we need to add more arguments to our QEMU command:
-fsdev local,id=fs1,path=/home/user/shared,security_model=none: this will add a new file system device to our emulation. Make sure to put the right directory at
path. Don't worry about
security_model=none, this argument will let the permission of creating/modifying files inside the guest as if was created by the host user.
-device virtio-9p-pci,fsdev=fs1,mount_tag=shared_folder: this defines the name and type of the virtual device.
We need to edit our guest
/etc/fstab. It should look like this:
# Static information about the filesystems. # See fstab(5) for details. # <file system> <dir> <type> <options> <dump> <pass> shared_folder /root/host_folder 9p trans=virtio 0 0
This determines the mounting pointing of the shared folder. As long you are consistent, you can choose whatever name for
host_folder. Reboot the guest machine and then:
(guest)$ ls /root/ host_folder
You may also want your kernel to have a custom name, it may be useful for you to organize your versions. You can do this changing the
LOCALVERSION value at
menuconfig or simply running
make LOCALVERSION=, e.g.:
(host)$ make LOCALVERSION=-VM ... (host)$ qemu-system-x86_64 ... ... (guest)$ uname -r 5.0.0-rc1-VM
Now you can easily hack and test your kernel! I recommend you read this section of the ArchWiki, as you'll find some cool tips to improve your VM performance. You may also want to create scripts and alias to not deal with all the flags, that will definitively make your life easier.
 The observant will notice that the argument used by QEMU to specify the first drive is
hda, which results in the kernel enumerating a drive as
sda. Historically IDE drives were labelled
X is an increasing drive letter), but for quite a while it has been typical for IDE drives to be accessed via an emulation layer in the SCSI subsystem, which labels drive
sdX. Also in contrast to how most physical hard drives are used, we have not created a boot partition and partitions in the virtual drive, instead treating the whole device as a partition, and thus lacking the numerical suffix we would typically see when referring to specific partitions.
Did you know you could run a permissively-licensed MTP implementation with minimal dependencies on an embedded device? Here's a step-by-step…
Earlier this year, the Rust compiler gained support for LLVM source-base code coverage. In this post we'll explain how to setup a CI job…
Over the past few months, I've been working on a side project to improve Meson sub-project support. The best stress test is to build projects…
The most complete automated testing and continuous integration tool for the Linux kernel continues to evolve at a rapid pace. Here's a look…
In the embedded world, many modern SoCs such as the ST Microelectronics STM32MP1 now include coprocessor cores which can be used for a wide…
Our recent efforts on the Hantro kernel driver have resulted in the addition of H.264 decoding support and multiple performance improvements.…