Release notes for the Genode OS Framework 9.08

Whereas the previous releases were focused on adding features to the framework,
the overall theme for the current release 9.08 was refinement. We took the
chance to revisit several parts of the framework that we considered as
interim solutions, and replaced them with solid and hopefully long-lasting
implementations. Specifically, we introduce a new lock implementation, a new
timer service, a platform-independent signalling mechanism, a completely
reworked startup code for all platforms, and thread-local storage support.
Even though some of the changes touches fundamental mechanisms, we managed
to keep the actual Genode API almost unmodified.

With regard to features, the release introduces initial support for dynamic
linking, a core extension to enable a user-level variant of Linux to run on the
OKL4 version of Genode, and support for super pages and write-combined I/O
memory access on featured L4 platforms.

The most significant change for the Genode Linux version is the grand unification with
the other base platforms. Now, the Linux version shares the same linker script
and most of the startup code with the supported L4 platforms. Thanks to our
evolved system-call bindings, we were further able to completely dissolve
Genode's dependency from Linux's glibc. Thereby, the Linux version of Genode is
on the track to become one of the lowest-complexity (in terms of source-code
complexity) Linux-kernel-based OSes available.

Base framework

New unified lock implementation

Since the first Genode release one year ago, the lock implementation had been
a known weak spot. To keep things simple, we employed a yielding spinlock
as basic synchronization primitive. All other thread-synchronization
mechanisms such as semaphores were based on this lock. In principle, the
yielding spinlock used to look like this:

The compare-exchange is an atomic operation that compares the current value
of _lock_variable to the value UNLOCKED, and, if equal, replaces the
value by LOCKED. If this operation succeeds, cmpxchg returns true, which
means that the lock acquisition succeeded. Otherwise, we know that the lock
is already owned by someone else, so we yield the CPU time to another thread.

Besides the obvious simplicity of this solution, it does require minimal
CPU time in the non-contention case, which we considered to be the common case. In
the contention case however, this implementation has a number of drawbacks.
First, the lock is not fair, one thread may be able to grab and release the
lock a number of times before another thread has the chance to be
scheduled at the right time to proceed with the lock acquisition if the lock
is free. Second, the lock does not block the acquiring thread but lets it
actively spin. This behavior consumes CPU time and slows down other threads that
do real work. Furthermore, this lock is incompatible with the use of thread
priorities. If the lock is owned by a low-priority thread and a high-priority
thread tries to acquire a lock, the high-priority thread keeps being active
after calling yield_cpu_time(). Therefore the lock owner starves and has no
chance to release the lock. This effect can be partially alleviated by replacing
yield_cpu_time() by a sleep function but this work-around implies higher
wake-up latencies.

Because we regarded this yielding spinlock as an intermediate solution since the
first release, we are happy to introduce a completely new implementation now.
The new implementation is based on a wait queue of lock applicants that are
trying to acquire the lock. If a thread detects that the lock is already
owned by another thread (lock holder), it adds itself into the wait queue
of the lock and calls a blocking system call. When the lock owner releases
the lock, it wakes up the next member of the lock's wait queue.
In the non-contention case, the lock remains as cheap as the yielding
spinlock. Because the new lock employs a fifo wait queue, the lock guarantees
fairness in the contention case. The implementation has two interesting points
worth noting. In order to make the wait-queue operations thread safe, we use a simple
spinlock within the lock for protecting the wait queue. In practice, we
measured that there is almost never contention for this spin lock as two
threads would need to acquire the lock at exactly the same time. Nevertheless,
the lock remains safe even for this case. Thanks to the use of the additional spinlock within
the lock, the lock implementation is extremely simple. The seconds interesting
aspect is the base mechanism for blocking and waking up threads such
that there is no race between detecting contention and blocking.
On Linux, we use sleep for blocking and SIGUSR1 to cancel the sleep operation.
Because Linux delivers signals to threads at kernel entry,
the wake-up signal gets reliably delivered even if it occurs prior
thread blocking. On OKL4 and Pistachio, we use the exchange-registers
(exregs) system call for both blocking and waking up threads. Because exregs
returns the previous thread state, the sender of the wake-up
signal can detect if the targeted thread is already in a blocking state.
If not, it helps the thread to enter the blocking state by a thread-switch
and then repeats the wake-up. Unfortunately, Fiasco does not support the
reporting of the previous thread state as exregs return value. On this kernel,
we have to stick with the yielding spinlock.

New Platform-independent signalling mechanism

The release 8.11 introduced an API for asynchronous notifications. Until
recently, however, we have not used this API to a large extend because it
was not supported on all platforms (in particular OKL4) and its implementation
was pretty heavy-weight. Until now signalling required one additional thread for each signal
transmitter and each signal receiver. The current release introduces a
completely platform-independent light-weight (in terms of the use of
threads) signalling mechanism based on a new core service called SIGNAL.
A SIGNAL session can be used to allocate multiple signal receivers, each
represented by a unique signal-receiver capability. Via such a capability,
signals can be submitted to the receiver's session. The owner of a SIGNAL
session can receive signals submitted to the receivers of this session
by calling the blocking wait_for_signal function. Based on this simple
mechanism, we have been able to reimplement Genode's signal API. Each
process creates one SIGNAL session at core and owns a dedicated thread
that blocks for signals submitted to any receiver allocated by the process.
Once, the signal thread receives a signal from core, it determines
the local signal-receiver context and dispatches the signal accordingly.

The new implementation of the signal API required a small refinement.
The original version allowed the specification of an opaque argument
at the creation time of a signal receiver, which had been delivered with
each signal submitted to the respective receiver. The new version replaces
this opaque argument with a C++ class called Signal_context. This allows
for a more object-oriented use of the signal API.

Generic support for thread-local storage

Throughout Genode we avoid relying on thread-local storage (TLS) and, in fact,
we had not needed such a feature while creating software solely using the
framework. However, when porting existing code to Genode, in particular Linux
device drivers and Qt-based applications, the need for TLS arises. For such
cases, we have now extended Genode's Thread class with generic TLS
support. The static function Thread_base::myself() returns a pointer to the
Thread_base object of the calling thread, which may be casted to a inherited
thread type (holding TLS information) as needed.

The Thread_base object is looked up by using the current stack pointer
as key into an AVL tree of registered stacks. Hence, the lookup traverses a
plain data structure and does not rely on platform-dependent CPU features
(such as gs segment-register TLS lookups on Linux).

Even though, Genode does provide a mechanism for TLS, we strongly discourage
the use of this feature when creating new code with the Genode API. A clean
C++ program never has to rely on side effects bypassing the programming
language. Instead, all context information needed by a function to operate,
should be passed to the function as arguments.

Core extensions to run Linux on top of Genode on OKL4

As announced on our road map, we are working on bringing a user-level variant
of the Linux kernel to Genode. During this release cycle, we focused on
enabling OKLinux aka Wombat to run on top of Genode. To run Wombat on Genode we
had to implement glue code between the wombat kernel code and the Genode API,
and slightly extend the PD service of core.

The PD-service extension is a great show case for implementing inheritance
of RPC interfaces on Genode. The extended PD-session interface resides
in base-okl4/include/okl4_pd_session and provides the following additional
functions:

Okl4::L4SpaceId_t space_id();
void space_pager(Thread_capability);

The space_id function returns the L4 address-space ID corresponding to
the PD session. The space_pager function can be used to set the
protection domain as pager and exception handler for the specified
thread. This function is used by the Linux kernel to register itself
as pager and exception handler for all Linux user processes.

In addition to the actual porting work, we elaborated on replacing the original
priority-based synchronization scheme with a different synchronization mechanism
based on OKL4's thread suspend/resume feature and Genode locks. This way, all
Linux threads and user processes run at the same priority as normal Genode
processes, which improves the overall (best-effort) performance and makes
Linux robust against starvation in the presence of a Genode process that is
active all the time.

At the current stage, we are able to successfully boot OKLinux on Genode and
start the X Window System. The graphics output and user input are realized
via custom stub drivers that use Genode's input and frame-buffer interfaces
as back ends.

We consider the current version as a proof of concept. It is not yet included
in the official release but we plan to make it a regular part of the official
Genode distribution with the next release.

Preliminary shared-library support

Our Qt4 port made the need for dynamically linked binaries more than evident.
Statically linked programs using the Qt4 library tend to grow far beyond 10MB
of stripped binary size. To promote the practical use of Qt4 on Genode, we
ported the dynamic linker from FreeBSD (part of libexec) to Genode.
The port consists of three parts

Building the ldso binary on Genode, using Genode's parent interface to
gain access to shared libraries and use Genode's address-space management
facilities to construct the address space of the dynamically loaded program.

Adding support for the detection of dynamically linked binaries, the starting
of ldso in the presence of a dynamically linked binary, and passing the
program's binary image to ldso.

Adding support for building shared libraries and dynamically linked
programs to the Genode build system.

At the current stage, we have completed the first two steps and are able to
successfully load and run dynamically linked Qt4 applications. Thanks to
dynamic linking, the binary size of Qt4 programs drops by an order of
magnitude. Apparently, the use of shared qt libraries already pays off when
using only two Qt4 applications.

You can find our port of ldso in the separate ldso repository. We will
finalize the build-system integration in the next weeks and plan to support
dynamic linking as regular feature as part of the os repository with the next
release.

Operating-system services and libraries

Improved handling of XML configuration data

Genode allows for configuring a whole process tree via a single configuration
file. Core provides the file named config as a ROM-session dataspace to the
init process. Init attaches the dataspace into its own address space and
reads the configuration data via a simple XML parser. The XML parser takes
a null-terminated string as input and provides functions for traversing the
XML tree. This procedure, however, is a bit flawed because init cannot
expect XML data provided as a dataspace to be null terminated. On most platforms,
this was no problem so far because boot modules, as provided by core's ROM
service, used to be padded with zeros. However, there are platforms, in particular
OKL4, that do not initialize the padding space between boot modules. In this
case, the actual XML data is followed by arbitrary bits but possibly no null
termination. Furthermore, there exists the corner case of using a config
file with a size of a multiple of 4096 bytes. In this case, the null termination
would be expected just at the beginning of the page beyond the dataspace.

There are two possible solutions for this problem: copying the content of
the config dataspace to a freshly allocated RAM dataspace and appending the
null termination, or passing a size-limit of the XML data to the XML parser.
We went for the latter solution to avoid the memory overhead of copying
configuration data just for appending the null termination. Making the XML
parser to respect a string-length boundary involved the following changes:

The strncpy function had to be made robust against source strings that are not
null-terminated. Strictly speaking, passing a source buffer without
null-termination violates the function interface because, by definition,
src is a string, which should always be null-terminated. The size
argument usually refers to the bound of the dst buffer. However, in our
use case, for the XML parser, the source string may not be properly terminated.
In this case, we want to ensure that the function does not read any characters
beyond src + size.

Adapted init to restrict the size of the config XML node to the file size
of the config file

Nitpicker GUI server

Avoid superfluous calls of framebuffer.refresh() to improve the overall
performance

Fixed stacking of views behind all others, but in front of the background.
This problem occurred when seamlessly running another window system as
Nitpicker client.

Misc

Alarm framework

Added next_deadline() function to the alarm framework. This function is
used by the timer server to program the next one-shot timer interrupt,
depending on the scheduled timeouts.

DDE Kit

Implemented dde_kit_thread_usleep() and dde_kit_thread_nsleep()

Removed unused/useless dde_kit_init_threads() function

Qt4

Added support for QProcess. This class can be used to start Genode
applications from within Qt applications in a Qt4-compatible way.

Device drivers

New single-threaded timer service

With the OKL4 support added with the previous release, the need for a new timer
service emerged. In contrast to the other supported kernels, OKL4 imposed two
restrictions, which made the old implementation unusable:

The kernel interface of OKL4 does not provide a time source. The kernel
uses a APIC timer internally to implement preemptive scheduling but, in
contrast to other L4 kernels that support IPC timeouts, OKL4 does not
expose wall-clock time to the user land. Therefore, the user land has to
provide a timer driver that programs a hardware timer, handles timer
interrupts, and makes the time source available to multiple clients.

OKL4 restricts the number of threads per address space according to a
global configuration value. By default, the current Genode version set
this value to 32. The old version of the timer service, however, employed
one thread for each timer client. So the number of timer clients was
severely limited.

Motivated by these observations, we created a completely new timer service that
dispatches all clients with a single thread and also supports different time
sources as back ends. For example, the back ends for Linux, L4/Fiasco, and
L4ka::Pistachio simulate periodic timer interrupts using Linux' nanosleep system
call - respective IPC timeouts. The OKL4 back end contains a PIT driver
and operates this timer device in one-shot mode.

To implement the timer server in a single-threaded manner, we used an
experimental API extension to Genode's server framework. Please note that we
regard this extension as temporary and will possible remove it with the next
release. The timer will then service its clients using the Genode's signal API.

Even though the timer service is a complete reimplementation, its interface
remains unmodified. So this change remains completely transparent at the API level.

VESA graphics driver

The previous release introduced a simple PCI-bus virtualization into the VESA
driver. At startup, the VESA driver uses the PCI bus driver to find a VGA card
and provides this single PCI device to the VESA BIOS via a virtual PCI bus. All
access to the virtualized PCI device are then handled locally by the VESA
driver. In addition to PCI access, some VESA BIOS implementations tend to use
the programmable interval timer (PIT) device at initialization time. Because we
do not want to permit the VESA BIOS to gain access to the physical timer
device, the VESA driver does now provide an extremely crippled virtual PIT.
Well, it is just enough to make all VESA BIOS implementations happy that we
tested.

On the feature side, we added support for VESA mode-list handling and a
default-mode fallback to the driver.

Misc

SDL-based frame buffer and input driver

For making the Linux version of Genode more usable, we complemented the
existing key-code translations from SDL codes to Genode key codes.

PS/2 mouse and keyboard driver

Improved robustness against ring-buffer overruns in cases where input events
are produced at a higher rate than they can be handled, in particular, if
there is no input client connected to the driver.

Platform-specific changes

Support for super pages

Previous Genode versions for the OKL4, L4ka::Pistachio, and L4/Fiasco kernels used
4K pages only. The most visible implication was a very noticeable delay during
system startup on L4ka::Pistachio and L4/Fiasco. This delay was caused by core
requesting the all physical memory from the root memory manager (sigma0) -
page by page. Another disadvantage of using 4K pages only, is the resulting TLB footprint
of large linear mappings such as the frame buffer. Updating a 10bit frame buffer
with a resolution of 1024x768 would touch 384 pages and thereby significantly
pollute the TLB.

This release introduces support for super pages for the L4ka::Pistachio and
L4/Fiasco versions of Genode. In contrast to normal 4K pages, a super page
describes a 4M region of virtual memory with a single entry in the page
directory. By supporting super pages in core, the overhead of the startup
protocol between core and sigma0 gets reduced by a factor of 1000.

Unfortunately, OKL4 does not support super pages such that this feature remains
unused on this platform. However, since OKL4 does not employ a root memory
manager, there is no startup delay anyway. Only the advantage of super pages
with regard to reduced TLB footprint is not available on this platform.

Support for write-combined access to I/O memory

To improve graphics performance, we added principle support for write combined I/O access
to the IO_MEM service of core. The creator of an IO_MEM session can now specify the
session argument "write_combined=yes" at session-creation time. Depending on the
actual base platform, core then tries to establish the correct page-table
attribute configuration when mapping the corresponding I/O dataspace. Setting
caching attributes differs for each kernel:

L4ka::Pistachio supports a MemoryControl system call, which allows for specifying
caching attributes for a core-local virtual address range. The attributes are
propagated to other processes when core specifies such a memory range
as source operand during IPC map operations. However, with the current version,
we have not yet succeeded to establish the right attribute setting, so the performance
improvement is not noticeable.

On L4/Fiasco, we fully implemented the use of the right attributes for marking
the frame buffer for write-combined access. This change significantly boosts
the graphics performance and, with regard to graphics performance, serves us
as the benchmark for the other kernels.

OKL4 v2 does not support x86 page attribute tables. So write-combined access
to I/O memory cannot be enabled.

On Linux, the IO_MEM service is not yet used because we still rely on libSDL
as hardware abstraction on this platform.

Unification of linker scripts and startup codes

During the last year, we consistently improved portability and the support for
different kernel platforms. By working on different platforms in parallel,
code duplications get detected pretty easily. The startup code was a steady
source for such duplications. We have now generalized and unified the startup
code for all platforms:

On all base platforms (Linux-x86_32, Linux-x86_64, OKL4, L4ka::Pistachio, and
L4/Fiasco) Genode now uses the same linker script for statically linked
binaries. Therefore, the linker script has now become part of the base
repository.

We unified the assembly startup code (crt0) for all three L4 platforms.
Linux has a custom crt0 code residing in base-linux/src/platform. For
the other platforms, the crt0 codes resides in the base/src/platform/
directory.

We factored out the platform-depending bits of the C++ startup code
(_main.cc) into platform-specific _main_helper.h files. The _main.cc
file has become generic and moved to base/src/platform.

Linux

With the past two releases, we successively reduced the dependency of the
Linux version of core from the glibc. Initially, this step had been
required to enable the use of our custom libc. For example, the mmap
function of our libc uses Genode primitives to map dataspace to the
local address space. The back end of the used Genode functions, in turn,
relied on Linux' mmap syscall. We cannot use syscall bindings provided
by the glibc for issuing the mmap syscall because the
binding would clash with our libc implementation of mmap. Hence we
started to define our own syscall bindings.

With the current version, the base system of Genode has become completely
independent of the glibc. Our custom syscall bindings for the x86_32 and
x86_64 architectures reside in base-linux/src/platform and consist of
35 relatively simple functions using a custom variant of the syscall
function. The only exception here is the clone system call, which requires
assembly resides in a separate file.

This last step on our way towards a glibc-free Genode on Linux pushes the
idea to only use the Linux kernel but no further Linux user infrastructure
to the max. However, it is still not entirely possible to build a Linux
based OS completely based on Genode. First, we have to set up the loopback
device to enable Genode's RPC communication over sockets. Second, we
still rely on libSDL as hardware abstraction and libSDL, in turn, relies
on the glibc.

Implications

Because the Linux version is now much in line with the other kernel platforms,
using custom startup code and direct system calls, we cannot support
host tool chains to compile this version of Genode anymore. Host tool chains, in
particular the C++ support library, rely on certain Linux features
such as thread-local storage via the gs segment registers. These things are
normally handled by the glibc but Genode leaves them uninitialized.
To build the Linux version of Genode, you have to use the official
Genode tool chain.

OKL4

The build process for Genode on OKL4 used to be quite complicated. Before
being able to build Genode, one had to build the original Iguana user land
of OKL4 because the Genode build system looked at the Iguana build directory
for the L4 headers actually used. We now have simplified this process by
not relying on the presence of the Iguana build directory anymore. All
needed header files are now shadowed from the OKL4 source tree
to an include location within Genode's build directory. Furthermore, we
build Iguana's boot-info library directly from within the Genode build system,
instead of linking the binary archive as produced by Iguana's build process.

Of course, to run Genode on OKL4, you still need to build the OKL4 kernel
but the procedure of building the Genode user land is now much easier.

Misc changes:

Fixed split of unmap address range into size2-aligned flexpages. The
unmap function did not handle dataspaces with a size of more than 4MB
properly.

Fixed line break in the console driver by appending a line feed to
each carriage return. This is in line with L4/Fiasco and L4ka::Pistachio,
which do the same trick when text is printed via their kernel debugger.

L4ka::Pistachio

The previous version of core on Pistachio assumed a memory split of 2GB/2GB
between userland and kernel. Now, core reads the virtual-memory layout from
the kernel information page and thereby can use up to 3GB of virtual memory.

Important: Because of the added support for super pages, the Pistachio
kernel must be built with the "new mapping database" feature enabled!

L4/Fiasco

Removed superfluous zeroing-out of the memory we get from sigma0. This change
further improves the startup performance of Genode on L4/Fiasco.

Build infrastructure

Tool chain

Bumped binutils version to 2.19.1

Support both x86_32 and x86_64

Made tool_chain's target directory customizable to enable building and
installing the tool chain with user privileges

Build system

Do not include dependency rules when cleaning. This change brings not
only a major speedup but it also prevents dependency rules from messing
with generic rules, in particular those defined in spec-okl4.mk.

Enable the use of -ffunction-sections combined with -gc-sections
by default and thereby reduce binary sizes by an average of 10-15%.

Because all base platforms, including Linux, now depend on the Genode tool
chain, the build system uses this tool chain as default. You can still
override the tool chain by creating a custom etc/tools.conf file
in your build directory.