DocsReleasesCommunityGuidesBlog

Architecture

Unikraft uses modularity to enable specialization, splitting OS functionality into fine-grained components that only communicate across well-defined API boundaries.

In contrast to classical OS work, which can be roughly split between monolithic kernels (with great performance) versus microkernels that provide great isolation between OS components (at the expense of performance), our work embraces both the monolithic design (no protection between components) and the modularity that microkernels advocated.

Our key observation is that we can obtain performance via careful API design and static linking, rather than short-circuiting API boundaries for performance. To achieve the overarching principle of modularity, Unikraft consists of two main components:

  • Micro-libraries are software components which implement one of the core Unikraft APIs; we differentiate them from libraries in that they have minimal dependencies and can be arbitrarily small, e.g., a scheduler. All micro-libraries that implement the same API are interchangeable. One such API contains multiple memory allocators that all implement the ukalloc interface. In addition, Unikraft supports libraries that can provide functionality from external library projects (OpenSSL, musl, Protobuf, etc.), applications (SQLite, Redis, etc.), or even platforms (e.g., Solo5, Firecracker, Raspberry Pi 3).

  • Build system which provides a Kconfig-based menu for users to select which micro-libraries to use in an application build, for them to select which platform(s) and CPU architectures to target, and even configure individual micro-libraries if desired. The build system then compiles all of the micro-libraries, links them, and produces one binary per selected platform.

The figure below shows Unikraft's architecture. All components are micro-libraries that have their own Makefile and Kconfig configuration files (Config.uk), and so can be added to the unikernel build independently of each other. APIs are also micro-libraries that can be easily enabled or disabled via a Kconfig menu; unikernels can thus compose which APIs to choose to best cater to an application's needs (e.g., an RCP-style application might turn off the uksched API in order to implement a high performance, run-to-completion event loop).

Overview of Unikraft's architecture. All components are micro-libraries that have their own Makefile and Kconfig configuration files, and so can be added to the unikernel build independently of each other.  APIs are also micro-libraries that can be easily enabled or disabled via a Kconfig menu; unikernels can thus compose which APIs to choose to best cater to an application's need.
Figure 1Overview of Unikraft's architecture. All components are micro-libraries that have their own Makefile and Kconfig configuration files, and so can be added to the unikernel build independently of each other. APIs are also micro-libraries that can be easily enabled or disabled via a Kconfig menu; unikernels can thus compose which APIs to choose to best cater to an application's need.

Unikraft's architecture also includes components that add POSIX support, making it relatively easy to support existing applications. Unikraft can improve the performance of applications in two ways:

  1. Un modified applications, by eliminating syscall overheads, reducing image size and memory consumption, and by choosing efficient memory allocators.

  2. Specialization, by adapting applications to take advantage of lower level APIs wherever performance is critical (e.g., a database application seeking high disk I/O throughput).

The ability to easily swap components in and out, and to plug applications in at different levels presents application developers with a wide range of optimization possibilities. To begin with, unmodified applications (e.g. "Hello World" and NGINX) can use the posix-compatibility layer with musl (1️ in the figure above) or nolibc, transparently getting low boot times, lower memory consumption and improved throughput because of the lack of syscall overheads, as Unikraft syscalls are effectively function calls.

Likewise, the application developer can easily select an appropriate memory allocator via ukalloc (6️) to obtain maximum performance, or to use multiple different ones within the same unikernel (e.g., a simple, fast memory allocator for the boot code, and a standard one for the application itself).

Developers interested in fast boot times could further optimize the unikernel by providing their own boot code (5️) to comply with the ukboot API; in Unikaft EuroSys'21 paper we show experiments with two boot code micro-libraries, one with static memory pages and one with dynamic ones, showing the trade-off between boot time and memory allocation flexibility.

For network-bound applications, the developers can use the standard socket interface (2️) or the lower level, higher performance uknetdev API (7️) in order to significantly improve throughput; we will discuss this API in greater detail below. Similarly, disk-bound applications such as databases can follow a standard path through the vfscore micro-library (3️), or optimize throughput by oding against the ukblock API (8️). Schedulers are also pluggable (4️), and each CPU core can run a different scheduler.

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.

Edit this page on GitHub

Connect with the community

Feel free to ask questions, report issues, and meet new people.

Join us on Discord!
®

Getting Started

What is a unikernel?Install CLI companion toolUnikraft InternalsRoadmap

© 2023  The Unikraft Authors. All rights reserved. Documentation distributed under CC BY-NC 4.0.