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).
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:
Un modified applications, by eliminating syscall overheads, reducing image size and memory consumption, and by choosing efficient memory allocators.
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.
Feel free to ask questions, report issues, and meet new people.