Category:Unikraft Tutorial
Unikraft Tutorial
Testing Infrastructure
For this tutorial you need a Linux host running Xen and/or KVM in order to run Unikraft images. Please check the manual of your Linux distribution on how to install these environments. This tutorial expects that you have the essential build and debugging tools installed, that you have xl
toolstack installed and running when using Xen, and that you have qemu
installed when using KVM. Many distributions provide meta-packages for compilers and libraries required to build Linux kernel (Debian/Ubuntu: sudo apt-get install build-essential libncurses-dev
). Unikraft needs the same set of tools.
Cloning Repositories
You can easily build Unikraft Unikernels on your Linux host. If you have all tools and libraries installed to compile a Linux kernel you are ready to do this with Unikraft too. Alternatively, you can directly compile Unikraft on the testing infrastructure that you got access too. It has compiler, tools, and necessary libraries pre-installed.
A Unikraft build consists mostly of a combination of multiple repositories. We differentiate them into: (1) Unikraft, (2) external libraries, (3) application. The build system assumes these to be structured as follows:
my-workspace/ ├── apps/ │ └── helloworld/ │ └── httpreply/ ├── libs/ │ ├── lwip/ │ └── newlib/ └── unikraft/
Clone the following repositories with Git (git clone [URL] [DESTINATION PATH]
):
- Unikraft base repository directly under your workspace root:
- Unikraft: upstream repo
- External libraries into a `libs` subdirectory:
- newlib: upstream repo
- lwip: upstream repo
- Applications into a `apps` subdirectory:
- helloworld: upstream repo
- httpreply: upstream repo
Make sure that the directory structure under your workspace is exactly the same as shown in the overview ahead (e.g., call the folder of the cloned Unikraft repository unikraft
)
Your First Unikernel
Configuring
After you cloned the repos, go to the helloworld
application and run make menuconfig
to configure the build. Unikraft uses the same configuration system as the Linux kernel (Kconfig). We will build Unikraft images for Xen, KVM, and Linux, so the first step is to go to the Platform Configuration
option and make the following changes:
- select
Xen guest image
- select
KVM guest
- select
Linux user space
Under Library configuration
we also need to choose a scheduler: select ukschedcoop
.
Building
Afterwards, save your configuration and build the image by typing `make`. The build system will create three binaries, one for each platform:
$ ls -sh build/ [...] 88K helloworld_kvm-x86_64 40K helloworld_linuxu-x86_64 72K helloworld_xen-x86_64 [...]
Running
Let's execute our Unikernel.
- The easiest to run is the one that we built as a Linux user space application. It should execute on any Linux environment:
$ build/helloworld_linuxu-x86_64 Welcome to _ __ _____ __ _____ (_) /__ _______ _/ _/ /_ / // / _ \/ / '_// __/ _ `/ _/ __/ \_,_/_//_/_/_/\_\/_/ \_,_/_/ \__/ Titan 0.2~10ce3f2 Hello world!
- You can execute the KVM image (
helloworld_kvm-x86_64
) on the KVM host:
$ qemu-system-x86_64 -nographic -vga none -device sga -m 4 -kernel build/helloworld_kvm-x86_64
- For Xen you first need to create a VM configuration (save it under
helloworld.cfg
):
name = 'helloworld' vcpus = '1' memory = '4' kernel = 'build/helloworld_xen-x86_64'
Start the virtual machine with:
$ xl create -c helloworld.cfg
Modifying the Application
After Hello world!
is printed, our Unikernel shuts down right away. We do not have a chance to see that a VM was actually created, so let's modify the source code. Open main.c
in your favorite editor (nano
, vim
, emacs
) and add the following busy loop accordingly within the main
function:
for (;;);
Rebuild the images with make
and execute them. The shell prompt should not return. With a second shell your can check that the Unikernel is still executing:
- Use
top
orhtop
for Linux and KVM. - Use
xl top
in Xen.
Note: You can terminate the KVM and Linux Unikernel with CTRL
+ C
. On Xen it is CTRL
+ ]
.
External Libraries
The helloworld
application uses a very minimalistic libc
implementation called nolibc
. It is small but also quite limited in terms of functionality. nolibc
is part of the Unikraft base repository and because of this it is called an internal library. They are located within the lib
directory of Unikraft.
In order to enhance the functionality provided by Unikraft, external libraries can be added to the build. In the following we want to swap nolibc
with newlib, a standard libc implementation that you can find in various Linux distributions and embedded environments.
We need to add newlib to the library includes. Edit the Makefile
of the helloworld
application. Please type make properclean
before; this will delete the build directory (but not your configuration) and will force a full rebuild later.
diff --git a/Makefile b/Makefile --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ UK_ROOT ?= $(PWD)/../../unikraft UK_LIBS ?= $(PWD)/../../libs -LIBS := +LIBS := $(UK_LIBS)/newlib all: @make -C $(UK_ROOT) A=$(PWD) L=$(LIBS)
Run make menuconfig
, newlib
should now appear in the Library Configuration
menu. Select it, and select also vfscore
(unfortunately this dependency is currently not automatically selected in the upstream repository). Afterwards type make
. Unikraft's build system will download newlib's sources and build it together with all the other Unikraft libraries and application. Our newlib
repository consists only of glue code that is needed to port newlib
to Unikraft.
You will notice that the built Unikernels are bigger than before. Try to run them again.
Debugging
Debugging a Unikernel is similar to debugging an application. In the following we will use print-based debugging and GDB.
Print-based Debugging
Unikraft provides support for debug printing with the internal libukdebug
library. Code needs to be instrumented but the advantage is that the library automatically removes all print calls as soon as you disable debug printing.
We enable them by going to Library Configuration
and ukdebug
. Here we can change the logging level but also enable or disable various types of additional debugging features (e.g., assertions).
- Change
Kernel message level
to have theShow all types of messages
value - Enable
Print bottom address of stack in messages
- Enable
Enable debug messages globally
As a side note: When the last option is disabled, compilation units can enable debug messages individually. This is done by defining UK_DEBUG
as a preprocessor macro to the compiler command line. This allows selective debug investigation with the same instrumented code (uk_pr_debug()
). You can see an example in lwip's Makefile.uk
.
GDB
With support from the virtualization platform and its toolstack (e.g., qemu
on KVM, xl
on Xen), a gdb
process can be attached to the instances. In the following we will try it out with Xen.
To start with, we must ensure that debug symbols are generated when building Unikraft. For this you have to run `make menuconfig` again and go to the `Build Options` menu. The following needs to be changed:
Optimization level
: No optimizationsDebugging information
:
*Debug information level
:Level 3
- Unselect
Strip final image
Type make clean
and then make
: we want to make sure that every object file is rebuilt with debugging information.
Start the guest on a Xen host in paused state:
$ xl create -c -p helloworld.cfg
On a second terminal, you first figure out the ID of your just created guest:
$ xl list Name ID Mem VCPUs State Time(s) Domain-0 0 6144 1 r----- 257.5 helloworld 17 4 1 --p--- 0.0
you start executing the Xen debugger backend and attach it to the VM (replace the ID 17 with yours):
$ gdbsx -a 17 64 9999 Listening on port 9999
On a third terminal session, attach gdb
to the virtual machine. You can set a breakpoint to main()
and start the VM with continue
:
$ gdb --eval-command="target remote :9999" build/helloworld_xen-x86_64.dbg GNU gdb (Debian 7.12-6) 7.12.0.20161007-git Copyright (C) 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from helloworld_xen-x86_64...done. Remote debugging using :9999 0x0000000000000000 in _text () (gdb) br main Breakpoint 1 at 0x8a7d: main. (2 locations) (gdb) c Continuing. [...]
Code Your Own Library
Let's add some functionality to our unikernel. Create a directory libs/mylib
, this will be the root folder of your library.
As mentioned earlier, Unikraft uses Linux's kconfig system imported. In order to make your library selectable in the "menuconfig", create the file Config.uk
with the following content:
config LIBMYLIB bool "mylib: My awesome lib" default n
To test if it worked, we need to tell unikraft's build system to pick this library. Go back to your helloworld
application, and edit it its Makefile
. Earlier we added newlib to the LIBS
variable, let's now add the new library:
LIBS := $(UK_LIBS)/newlib:$(UK_LIBS)/mylib
Now if you run make menuconfig
, you should see your library under the "Library Configuration" sub-menu:
[ ] mylib: My awesome lib
Select it, exit the configuration menu, and save the changes. If you run make
right now, the build will produce an error about a missing Makefile.uk
:
make[1]: *** No rule to make target '/root/demo/libs/mylib/Makefile.uk'. Stop.
Go back to your library directory and create one with the following content:
# Register your lib to Unikraft's build system $(eval $(call addlib_s,libmylib,$(CONFIG_LIBMYLIB))) # Add library source code to compilation LIBMYLIB_SRCS-y += $(LIBMYLIB_BASE)/mylib.c # Extend the global include paths with library's folder CINCLUDES-$(CONFIG_LIBMYLIB) += -I$(LIBMYLIB_BASE)/include
And finally the library code mylib.c
:
#include <stdio.h> void libmylib_api_func(void) { printf("Hello from my awesome lib!\n"); }
include/mylib/mylib.h
:
#ifndef __LIB_MYLIB_H__ #define __LIB_MYLIB_H__ void libmylib_api_func(void); #endif /* __LIB_MYLIB_H__ */
Now in your helloworld's main.c
, add #include <mylib/mylib.h>
and call libmylib_api_func()
.
exportsyms.uk
Even though your library is working now, you might have noticed a warning message:
Warning: Definition of exported symbols for libmylib missing: /root/demo/libs/mylib/exportsyms.uk
This is because Unikraft
applies scoping to every library: only the symbols listed in exportsyms.uk
will be visible outside of the library. If the file is missing (our current case), all the symbols are exported, but the warning is produced.
Let's create this file in the root of `mylib` with only one line:
libmylib_api_func
If you compile the helloworld
application again, you will notice that the warning is gone. To get an impression of how scoping works, edit the exportsyms.uk
file again and add an underscore (or any other character which is valid to use in the symbol name) to your exported symbol:
libmylib_api_func_
Since libmylib_api_func
is not exported anymore, you should now get a compilation error:
sglist.c:(.text.startup+0x60): undefined reference to `libmylib_api_func'
Socket Example
As a last task, we are going to build a small webserver that replies with a single page. The server uses lwip
for creating a socket and to accept incoming connections. Go to the httpreply
application directory. Have a look at main.c
: it is a really short program and looks similar to what you would write as a user-space Linux program. Its dependencies are defined within Config.uk
. Having this, there is actually not much left to configure. Mandatory options are locked in make menuconfig
. All we need to do is to select our target platforms, select network drivers, save the config, and type make
.
For now, we support virtio for networking only (but more functionality is coming). You can enable the driver by going to the KVM platform configuration and selecting Virtio PCI device support
and Virtio Net device
.
The image can be started on the KVM host. Replace virbr0
with the name of your local bridge on your system and make sure you have a DHCP server listening there (e.g., dnsmasq
):
$ qemu-system-x86_64 -nographic -vga none -device sga -m 8 -netdev bridge,id=en0,br=br0 -device virtio-net-pci,netdev=en0 -kernel build/httpreply_kvm-x86_64
This Unikernel is requesting an IPv4 address via DHCP. In case you enabled ICMP
in the lwip
configuration, you should also be able to ping the host from a second terminal (replace the IP with yours):
$ ping 192.168.1.100
For debugging, you can also try to enable Debug messages
in lwip
. With this you can now have a deeper look into the network stack.
If networking is working well, you can use the text-based browser `lynx` (or any other that you like) to see the web page served on a second terminal (replace the IP with yours):
$ lynx 192.168.1.100:8123
This category currently contains no pages or media.