Compiling a custom FreeBSD kernel for Parallels VirtualMachine May 13 2020

I use Parallels on macOS to spin virtual machines for learning purposes. Lately, I've been interested in FreeBSD, so I wanted to create a slimmed-down version of the FreeBSD Kernel for my VM. And that is the topic of this post, how to create a custom FreeBSD Kernel for a Parallels virtual machine. It is not limited to Parallels tho. You can easily use the content of this note to build any custom Kernel.

So let's get started.

The Kernel source code

You can find the kernel source on:

1
/usr/src/

If you didn't select the option to download the source during the installation of FreeBSD, you'll have to download it from the Internet. There is a GitHub mirror https://github.com/freebsd/freebsd that you could clone. Or you can use SVN and download it following the instructions here using Svnlite.

If you are used to building projects using Makefiles, you'll feel right at home. I encourage you to have a look at the Makefile. It has a lot of useful information. But to save you time, the compilation of the kernel will expect your configuration, either by default searching for /sys/<arch>/conf/GENERIC or if you specify it.

You can get your computer architecture by using the uname(1) command:

1
2
3
$ uname -m
#In my case
amd64

Before we attempt to compile our kernel, we'll need to define the options that our custom kernel will support. So let's do that.

Basing our setup on the GENERIC configuration

FreeBSD supports many architectures. You can take a gander at the /sys directory. This directory includes configurations for each of the supported architectures. My computer has an Intel processor, so I'll base my configuration on the amd64 architecture. Inside that directory, you can find the generic configuration, and I'll use that as our starting point:

1
/sys/amd64/conf/GENERIC

I'll make a copy called PARALLELS.

1
cp GENERIC PARALLELS

If you have a look at that file or the one for your architecture, you'll find options, devices, and variables that specify the configuration that will define your custom Kernel once you compile it.

The first lines of my PARALLELS (The copy I made form the GENERIC config), looks like this:

1
2
cpu       HAMMER
ident      GENERIC

I'll leave the same CPU identifier, and will change the name that will identify my custom kernel. I'll change it to PARALLELS. The identifier helps when I want to verify which kernel I'm using at the moment. I can identify it as PARALLELS using sysctl(8):

1
$ sysctl kern.ident

Ok, we continue down the file commenting out all the options that you don't need. If you are unsure about an option, you have a few choices:

After all the configuration options, you come to the device section. Here is where it gets interesting, and we can chop a lot of drivers that we don't use. If we follow the advice on this link by Allan Jude, we should be able to remove SCI, FireWire, and USB device drivers, also all network adapters but ed and miibus. Well, that wouldn't work for me. When removing devices, you should make sure that you are not removing drivers that you use. You should check /var/run/dmesg.boot, where you can see all the detected hardware. For me, I had an em network card:

1
2
3
4
5
6
7
...
em0: <Intel(R) PRO/1000 Network Connection> port 0xd240-0xd25f mem 0xd0000000-0xd001ffff at device 5.0 on pci0
em0: Using 1024 TX descriptors and 1024 RX descriptors
em0: Ethernet address: 00:1c:42:ed:e6:2e
em0: link state changed to UP
em0: netmap queues/slots: TX 1/1024, RX 1/1024
....

Do a quick scan in your dmesg.boot and check that you are leaving all the drivers that your hardware requires. Also, make sure that your driver doesn't depend on another driver:

1
2
3
# PCI/PCI-X/PCIe Ethernet NICs that use iflib infrastructure
device     iflib
device     em           # Intel PRO/1000 Gigabit Ethernet Family

If I comment out iflib I won't be able to compile because em depends on iflib.

If you keep scrolling down, you'll reach the virtualisation section. Because I'm explicitly using Parallels then I can remove the other virtualisation devices and options:

1
2
HyperV - Microsoft
Xen - Xen HyperVisor HVM - https://xenproject.org/

So what's left is Virtio and from https://kb.parallels.com/4948

1
Virtio network adapter is the fastest card. However, it works only in Linux and BSD guest operating systems. It is a default adapter for Linux-based OSes.

So I'll leave virtio and comment out the rest:

1
2
3
4
5
6
7
# HyperV drivers and enhancement support
#device     hyperv         # HyperV drivers

# Xen HVM Guest Optimizations
# NOTE: XENHVM depends on xenpci. They must be added or removed together.
#options    XENHVM         # Xen HVM kernel infrastructure
#device     xenpci         # Xen HVM Hypervisor services driver

Alright, after we have all our configuration ready in our PARALLELS configuration file, we can compile our kernel!

But before, let's be smart and create a backup of our current kernel. I'm confident that I did a good job researching the options and devices, but I've made mistakes in the past (Trust but verify).

Creating a backup of our current kernel

Our kernel is located at /boot/kernel so a simple copy should be fine:

1
2
cd /boot
cp -a kernel kernel.2020-05-13

I named mine kernel.2020-05-13 but you can name it whatever you want(e.g. kernel.good).

We are going to add our backup to the list of possible kernels to load for the system loader. If anything goes wrong, we can go back to our working kernel.

1
2
#We can edit /boot/loader.conf manualy or use syrc(8)
sysrc -f /boot/loader.conf KERNELS="kernel kernel.old kernel.2020-05-13"

When we compile and install the kernel, the script creates an automatic backup as kernel.old, but if we'll be in a cycle of compile/install until we find the correct configuration, kernel.old can be rewritten with any of our attempts and not our working kernel.

Ok, finally let's compile and install.

Compiling and installing our custom Kernel

Now is when everything comes together. Go to the kernel source, and run buildkernel:

1
2
cd /usr/src
make KERNCONF=PARALLELS buildkernel

If we forego the KERNCONF variable the script will search for GENERIC.

If you got any errors, read the message carefully and go back and fix your PARALLELS configuration file accordingly.

If everything went fine and the compilation completes successfully, we install the kernel:

1
2
make KERNCONF=PARALLELS installkernel
#This moves the current /boot/kernel to /boot/kernel.od and moves our newly created one to /boot/kernel

And that's it! We can reboot and test.

If the kernel doesn't work, you can go to the boot menu select the working version of your kernel and figure out what went wrong.

Final Thoughts

Creating a custom Kernel is not a complicated task, it mostly takes time to research the correct options and drivers for your particular case. You might be wondering "how much space I saved?" after creating my custom kernel specially tailored for Parallels. Well, here is the answer:

1
2
3
$ du -sh /boot/kernel*
 67M  kernel
 75M  kernel.good

12M! The things we do for 12M. If we go only for size is not that much, but it was fun. And the less code we have in our system, the smaller the surface area for bugs. If you want to have a look at the PARALLELS configuration file I used, you can find it in this GitHub Gist.

Remember that your hardware might be different, so have a look at /var/run/dmesg.boot to check your hardware.

Alright, that's it for today.


** If you want to check what else I'm currently doing, be sure to follow me on twitter @rderik or subscribe to the newsletter. If you want to send me a direct message, you can send it to derik@rderik.com.