We're hiring!
*

Bootstraping a minimal Arch Linux image

André Almeida avatar

André Almeida
March 20, 2019

Share this post:

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

Creating a disk

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

Installing Arch Linux

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.

Using QEMU and your kernel

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;
    • You can learn more about kernel parameters here;
  • --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+

Connecting to the internet

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

Displaying graphical windows

If you want to debug graphical applications inside your virtual machine, you may follow one of the methods:

  • QEMU display: use QEMU display in graphical mode with a GUI compositor;
  • SSH with XForwarding: use your own terminal to SSH into the VM and then display the content using your host's display.

QEMU display

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 <code>(guest)$ xclock</code> inside weston result.
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.

XForwarding

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 <code>(host)$ ssh -X root@localhost -p 1337</code> and <code>(guest)$ 
xclock</code> result.
Screen capture showing (host)$ ssh -X root@localhost -p 1337 and (guest)$ xclock result.

Shared folder between host and guest

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

Conclusion

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.

Comments (0)


Add a Comment






Allowed tags: <b><i><br>Add a new comment:


Search the newsroom

Latest Blog Posts

Automatic regression handling and reporting for the Linux Kernel

14/03/2024

In continuation with our series about Kernel Integration we'll go into more detail about how regression detection, processing, and tracking…

Almost a fully open-source boot chain for Rockchip's RK3588!

21/02/2024

Now included in our Debian images & available via our GitLab, you can build a complete, working BL31 (Boot Loader stage 3.1), and replace…

What's the latest with WirePlumber?

19/02/2024

Back in 2022, after a series of issues were found in its design, I made the call to rework some of WirePlumber's fundamentals in order to…

DRM-CI: A GitLab-CI pipeline for Linux kernel testing

08/02/2024

Continuing our Kernel Integration series, we're excited to introduce DRM-CI, a groundbreaking solution that enables developers to test their…

Persian Rug, Part 4 - The limitations of proxies

23/01/2024

This is the fourth and final part in a series on persian-rug, a Rust crate for interconnected objects. We've touched on the two big limitations:…

How to share code between Vulkan and Gallium

16/01/2024

One of the key high-level challenges of building Mesa drivers these days is figuring out how to best share code between a Vulkan driver…

Open Since 2005 logo

We use cookies on this website to ensure that you get the best experience. By continuing to use this website you are consenting to the use of these cookies. To find out more please follow this link.

Collabora Ltd © 2005-2024. All rights reserved. Privacy Notice. Sitemap.