André Almeida
March 20, 2019
Reading time:
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 truncate
command:
(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. pacstrap
, 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 ls
and 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
The 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.raw
file 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.
root
defines which disk/partition has the root file system. In our case, it will be the first storage device[1], the hard disk arch_disk.raw
;rw
that we want to read and write to disk;console
is to set the standard output of the kernel and of the PID 1;loglevel
is 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:
[core] xwayland=true
Run the 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 (guest)$ xclock inside weston result. |
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 passwd
:
(guest)$ passwd New password: Retype new password: passwd: password updated successfully
Let's configure 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
and 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 passwd
):
(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 -X
:
(host)$ ssh -X root@localhost -p 1337
And check the hours with xclock
! To exit, you can use CTRL+D.
Screen capture showing (host)$ ssh -X root@localhost -p 1337 and (guest)$
xclock result. |
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 shared_folder
and 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.
[1] 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 hdX
(where 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.
08/10/2024
Having multiple developers work on pre-merge testing distributes the process and ensures that every contribution is rigorously tested before…
15/08/2024
After rigorous debugging, a new unit testing framework was added to the backend compiler for NVK. This is a walkthrough of the steps taken…
01/08/2024
We're reflecting on the steps taken as we continually seek to improve Linux kernel integration. This will include more detail about the…
27/06/2024
With each board running a mainline-first Linux software stack and tested in a CI loop with the LAVA test framework, the Farm showcased Collabora's…
26/06/2024
WirePlumber 0.5 arrived recently with many new and essential features including the Smart Filter Policy, enabling audio filters to automatically…
12/06/2024
Part 3 of the cmtp-responder series with a focus on USB gadgets explores several new elements including a unified build environment with…
Comments (0)
Add a Comment