Unikraft Build Process
We want Unikraft to be as lightweight as possible. For that matter, the build system is designed to take only the absolute necessary pieces of code and bind them togheter.
The lifecycle of the construction of a Unikraft unikernel includes several distinct steps:
- Configuring the Unikraft unikernel application with compile-time options;
- Fetching and preparing the source code for external libraries;
- Compiling the libraries and the core Unikraft code;
- Linking the final unikernel image.
The above steps are displayed in the diagram. The Unikraft unikernel targets a specific platform and hardware architecture, which are set during the configuration step of the lifecycle.
Unikraft is a configurable operating system, where each component can be modified, configured, according to the user’s needs.
This configuration is done using a version of Kconfig, through the Config.uk files.
In these files, options are added to enable libraries, applications and different components of the Unikraft core.
The user can then apply those configuration options, using
make menuconfig, which generates an internal configuration file that can be understood by the build system, .config.
Once configured, the Unikraft image can be built, using
make, and run, using the appropriate method (Linux ELF loader, qemu-kvm, xen, others).
Configuration can be done in 3 ways:
$ make menuconfig
Adding a dependency in Config.uk for a component, so that the dependency gets automatically selected when the component is enabled. This is done using
selectkeywords in Config.uk. The configuration gets loaded and the .config file is generated by running
$ make menuconfig
This type of configuration removes some configuration steps, but not all of them.
Writing the desired configuration in kraft.yaml. The configuration gets loaded and the .config file is generated by running
$ kraft configure
Makefiles and .config Files
Once the application is configured, symbols are defined (eg.
CONFIG_ARCH_X86_64) in .config.
Those symbols are usable both in the C code, to include certain functionalities, only if they were selected in the configuring process, and in the actual building process, to include / exclude source files, or whole libraries.
This last thing is done in Makefile.uk, where source code files are added to libraries.
During the build process, all the
Makefile.uk files (from the Unikraft core and external libraries) are evaluated, and the selected files are compiled and linked, to form the Unikraft image.
The Unikraft Core
The Unikraft core is the actual kernel code, and is comprised of several components:
- the architecture code: This defines behaviours and hardware interactions specific to the target architecture (x86_64, ARM, RISC-V). For example, for the x86_64 architecture, this component defines the usable registers, data types sizes and how Thread-Local Storage should happen.
- the platform code: This defines interaction with the underlying hardware, depending on whether a hypervisor is present or not, and which hypervisor is present. For example, if the KVM hypervisor is present, Unikraft will behave almost as if it runs bare-metal, needing to initialize the hardware components according to the manufacturer specifications. The difference from bare-metal is made only at the entry, where some information, like the memory layout, the available console, are supplied by the bootloader (Multiboot) and there’s no need to interact with the BIOS or UEFI. In the case of Xen, many of the hardware-related operations must be done through hypercalls, thus reducing the direct interaction of Unikraft with the hardware.
- internal libraries: These define behaviour independent of the hardware, like scheduling, networking, memory allocation, basic file systems. These libraries are the same for every platform or architecture, and rely on the platform code and the architecture code to perform the needed actions. The internal libraries differ from the external ones in the implemented functionalities. The internal ones define parts of the kernel, while the external ones define user-space level functionalities. For example, uknetdev and lwip are 2 libraries that define networking components. Uknetdev is an internal library that interacts with the network card and defines how packages are sent using it. Lwip is an external library that defines networking protocols, like IP, TCP, UDP. This library knows that the packages are somehow sent over the NIC, but it is not concerned how. That is the job of the kernel.
The core, toghether with the external libraries, applications and platform codes, form the final Unikernel.