genode/doc/release_notes-14-05.txt

1124 lines
53 KiB
Plaintext

===============================================
Release notes for the Genode OS Framework 14.05
===============================================
Genode Labs
With Genode version 14.05, we address two problems that are fundamental for
the scalability of the framework. The first problem is the way how Genode
interoperates with existing software. A new concept for integrating 3rd-party
source code with the framework makes the porting and use of software that
is maintained outside the Genode source tree easier and more robust than ever.
The rationale and the new concept are explained in Section
[Management of ported 3rd-party source code].
The second problem is concerned about how programs that are built atop a C
runtime (as is the case for most 3rd-party software) interact with the Genode
world. Section [Per-process virtual file systems] describes how we
consolidated many special-purpose solutions into one coherent design of using
process-local virtual file systems.
In line with our road map, we put forward our storage-related agenda by enabling
the use of NetBSD's cryptographic device driver (CGD) on Genode. Thereby, we
continue our engagement with the rump kernel that we started to embrace with
version 14.02. Section [Block-level encryption using CGD] explains the
use of CGD as a Genode component.
Apart from those infrastructural improvements, the release cycle has focused
on the NOVA and base-hw platforms. On NOVA, we are happy to have enabled
static real-time priorities, which make the kernel much more appealing
for the designated use for a general-purpose OS. Furthermore, we intensified
our work on VirtualBox on NOVA by enabling guest-addition support and
improving stability and performance. The NOVA-related improvements are
covered by Sections [VirtualBox on NOVA] and [NOVA microhypervisor].
The development of our custom base-hw kernel platform for the ARM architecture
goes full steam ahead. With the added support for multiple processors, base-hw
can finally leverage the CPU resources of modern ARM platforms. Furthermore,
we largely redesigned the memory management to avoid the need to maintain
identity mappings, which makes the kernel more robust. Section
[Execution on bare hardware (base-hw)] explains those developments in detail.
Finally, we enhanced the driver support for x86-based platforms by enabling
USB 3.0 in our Linux device-driver environment
Section [USB 3.0 for x86-based platforms] outlines the steps we had to take.
Management of ported 3rd-party source code
##########################################
Without the wealth of existing open-source software, Genode would be of little
use. We regularly combine the work of more than 70 open-source projects with
the framework. The number is steadily growing because each Genode user longs
for different features.
Since version 11.08, we employed a common way of integrating 3rd-party
software with Genode, which came in the form of a makefile per source-code
repository. Each of those makefiles offered "prepare" and "clean" rules
that automated the downloading and integration of 3rd-party code.
The introduced automatism was a big relief for our work flows.
Since then, the amount of 3rd-party code ported to Genode has been steadily
increasing. It eventually reached a complexity that became hard to manage
using the original mechanism.
In order to make Genode easier to conquer for new users and more
enjoyable for regular developers, we had to reconsider the way of how
3rd-party code is integrated with the framework.
We identified the following limitations of the existing approach:
* From the viewpoint of Genode users, the most inconvenient limitation was
the lack of proper error messages when a port was not prepared beforehand.
Instead, the build system produced confusing error messages when unable to
find the source code. According to the trouble-shooting requests on our
mailing list, the missing preparation of 3rd-party code seems to be the
most prominent road block for new users.
* Still, when having prepared all required 3rd-party ports, the prepared
version may become outdated when using Genode over time. Eventually the
build process will expect a different version of the 3rd-party code than the
one prepared. This happens particularly when switching between branches. In
some cases the version of the 3rd-party code is updated quite often (e.g.,
base-nova). The build system could not detect such inconsistencies and
consequently responded with arcane error messages, or even worse, produced
binaries with unexpected runtime behaviour.
* There are many source-code repositories that deal with downloading and
integrating 3rd-party code in different ways, namely libports, ports,
ports-foc, base-<kernel>, dde_ipxe, dde_rump, dde_linux, dde_oss, qt4. Even
though all makefiles contained in those repositories used to contain the
"prepare" and "clean" rules, they were not consistent with regard to the
handling of corner cases, to the updating of packages, and with the use of
additional arguments ("PKG="). Moreover, the individual port-description
files (_<repository>/ports/*.mk_) files found in the ports and libports
repositories contained a lot of boiler-plate content such as the rules for
downloading files via wget, or the rules for checking signatures. Such
duplicated code tends to degrade in quality and consistence over time,
affecting the user experience and maintenance costs in a negative way.
* The downloaded archives and the extracted 3rd-party code used to reside
within the respective repositories (in the _download/_ and _contrib/_
subdirectories). This made the use of search tools like grep very inefficient
when attempting to search in Genode's source code while excluding 3rd-party
sources. For this reason, most regular Genode developers have crafted some
special shell aliases for filtered search operations. But this should not be
the way to go.
* During the "make prepare" step, most ports of libraries used to create a
bunch of symlinks within _<rep-dir/include/_ that pointed to the respective
header files within _<rep-dir>/contrib/_. Effectively, this step touched
Genode's source tree, which was bad in two ways. First, the portions of the
source tree installed by the "make prepare" mechanism had to be blacklisted
in Genode's .gitignore file. And second, executing the port-specific "make
clean" rules was quite dangerous because those rules operated on the source
tree.
The way forward
===============
The points above made the need for a changed source-tree structure apparent.
Traditionally, all of Genode's source-code repositories alongside the _tool/_
and _doc/_ directories were located at the root of the tree structure:
! tool/
! doc/
! base/
! base-okl4/Makefile
! download/
! include/
! lib/
! src/
! os/
! ...
Repositories that incorporated 3rd-party code (e.g., base-okl4 as depicted
above) hosted a makefile for the preparation, a _download/_ directory for the
downloaded 3rd-party source code, and a _contrib/_ directory for the extracted
source code. There was no notion of common tools that would work across
repositories.
With Genode 14.05, we move all repositories to a _repos/_ directory:
! tool/
! doc/
! repos/
! base/
! base-okl4/
! os/
! ...
! contrib/
Downloaded 3rd-party source code resides outside of the actual repository at
the central 'contrib/' directory. By using this structure, we achieve
the following:
* Working with grep within the repositories works very efficient because
downloaded and extracted 3rd-party code are no longer in the way. They
reside next to the repositories.
* In contrast to the original situation where we had no convention about
the location of source-code repositories, tools can rely on a convention
now. Being located at a known position within the tree, the tools for
creating build directories and for managing ports become aware of the
location of the repositories as well as the central _contrib/_ directory.
* Adding a supplemental repository is pretty intuitive: Just clone a git
repository into _repos/_.
* Tutorials that describe the use of Genode could benefit from the introduced
convention as they could suggest creating build directories at the top
level, which no longer interferes with the location of the source-code
repositories. This would make those tutorials a bit easier to follow.
* The create_builddir tool can create build directories at sensible default
locations. E.g., when 'create_builddir' is called with nova_x86_64 as
argument but with no BUILD_DIR argument, the tool will create a build
directory _build/nova_x86_64/_ by default. This way, we reinforce a useful
convention about the naming and location of build directories that will ease
the support of Genode users.
* Storing all build directories and downloaded 3rd-party source code somewhere
outside the Genode source tree, let's say on different disk partitions, can
be easily accomplished by creating a symbolic link for each of the _build/_
and _contrib/_ directory.
Of course, changing the source-tree structure at the top-level was no
light-hearted decision. In particular, it raised the question of how to
deal with topic branches that were branched off a Genode version with the
old layout. During the transition, we observed the following patterns
to deal with that problem:
* Git can deal well with patches that change existing files, even if the
file location has changed. For simple patches, e.g., small bug fixes,
cherry-picking those individual commits to a current branch works quite
well.
* If a commit adds new files, the files will naturally end up at the
location specified in the patch, i.e., somewhere outside of the _repos/_
directory. You will have to manually move them to the correct location using
'git mv' and squash the resulting rename commit onto the original commit
using 'git rebase -i'.
* For migrating a series of complex commits to the new layout, we use
'git format-patch' to obtain a patch series for the topic branch, prefix
the original pathnames with "repos/" using 'sed', and apply the result
using 'git am'.
Unification of the ports management
===================================
With the new source-tree layout in place, we could pursue a new take on
unifying the management of ported 3rd-party source code. The new solution,
which is very much inspired by the fabulous
[http://nixos.org/nix - Nix package manager] comes in the form of new tools to
be found at 'tool/ports/'.
Note that even though the port mechanism described herein looks a bit like
"package management", it covers a different problem. The problem covered here
is the integration of existing 3rd-party source code with the Genode source
tree. Packaging, on the other hand, would provide a means to distribute
self-contained portions of the Genode source tree including their respective
3rd-party counterparts as separate packages. Package management is not
addressed yet.
The new tools capture all ports present in the repositories located under
_repos/_. Using them is as simple as follows:
:Obtain a list of available ports:
! tool/ports/list
:Download and install a port:
! tool/ports/prepare_port <port-name>
The prepare_port tool will scan all repositories for the specified port and
install the port into _contrib/_. Each version of an installed
port resides in a dedicated subdirectory within the _contrib/_ directory.
The port-specific directory is called port directory. It is named
_<port-name>-<fingerprint>_. The _<fingerprint>_ uniquely identifies
the version of the port (it is a SHA1 hash of the ingredients of the
port). If two versions of the same port are installed, each of them will
have a different fingerprint. So they end up in different directories.
Within a source-code repository, a port is represented by two files, a
_<port-name>.port_ and a _<port-name>.hash_ file. Both files reside at the
_ports/_ subdirectory of the corresponding repository. The
_<port-name>.port_ file is the port description, which declares the
ingredients of the port, e.g., the archives to download and the patches to apply.
The _<port-name>.hash_ file contains the fingerprint of the corresponding
port description, thereby uniquely identifying a version of the port
as expected by the checked-out Genode version.
So how does Genode's build system find the source code for a given port?
If the build system encounters a target that incorporates
ported source code, it looks up the respective _<port-name>.hash_ file in the
repositories as specified in the build configuration. The fingerprint found in
the hash file is used to construct the path to the port directory under
_contrib/_. If that lookup fails, a meaningful error is printed. Any number of
versions of the same port can be installed at the same time. I.e., when
switching Git branches that use different versions of the same port, the build
system automatically finds the right port version as expected by the currently
active branch.
For step-by-step instructions on how to add a port using the new mechanism,
please refer to the updated porting guide:
:Genode Porting Guide:
[http://genode.org/documentation/developer-resources/porting]
:Known limitations:
* There is no garbage collection of stale ports, yet. Each time when a port
gets updated, a new version will be created within the _contrib/_ directory.
However, the subdirectories can safely be deleted manually to regain
disk space. In the worst case, if you deleted a port that is in use,
the build system will let you know.
* Even though some port files are equipped with information about
cryptographic signatures, those signatures are not checked yet. However,
each downloaded archive is checked against a known-good hash value declared
in the port description so that the integrity of downloaded files is
checked. But as illustrated by the signature declarations in the
port descriptions, we plan to increase the confidence by enabling
signature checks in addition to the hash-sum checks.
* Dependencies between ports are not covered by port descriptions, yet.
:Transition to the new mechanism:
We have reworked the majority of the more than 70 existing ports to the new
mechanism. The only ports not covered so far are base-codezero, qt5, gcc, gdb,
and qt4. During the next release cycle, we will keep the original "make
prepare" mechanism as a front end intact. So the "make prepare" instructions
as found in many tutorials will still work. But under the hood, "make prepare"
just invokes the new _tool/ports/prepare_port_ tool.
Block-level encryption using CGD
################################
The need for protection of personal data is becoming generally
accepted in the information age. Especially, against the background of
ubiquitous storage devices in smart phones, notebooks, and tablet
computers, which may go missing easily.
There are several different approaches to prevent unauthorized access
to data storage. For example, data could be encrypted on a per file
basis (e.g. EncFS or PEFS). Thereby each file is encrypted using a
cipher but stored on a regular file system besides unencrypted files.
Beyond this approach, it is also common to encrypt data on the lower
block-device layer. With block-level encryption, each block on the
storage device is encrypted respectively decrypted when written to or read
from the device (e.g., TrueCrypt, FreeBSD's geli(8), Linux LUKS). On
top of this cryptographic storage device, a regular file system may be
used.
Additionally, it is desirable to access the
encrypted data from various operating systems. In our case, we want to
use the data from Genode as well as from our current development
platform Linux.
In Genode 14.02, we introduced a port of the NetBSD based rump kernels
to leverage file-system implementations, e.g., ext2. Beside file
systems, NetBSD itself also offers block-level encryption in form of
its cryptographic disk-driver _cgd(4)_. In line with our roadmap, we
enabled the cryptographic-device driver in our rump-kernels port as a
first step to explore block-level encryption on Genode.
:[https://www.netbsd.org/docs/guide/en/chap-cgd.html]:
NetBSD cryptographic-device driver (CGD)
The heart of our CGD port is the _rump_cgd_ server, which encapsulates
the rump kernels and the cgd device. The server uses a block session to
get access to an existing block device and, in return, provides a
block session to its client. Each block written or read by the client
is transparently encrypted resp. decrypted by the server with a given
key. This enables us to seamlessly integrate CGD into Genode's existing
infrastructure.
To ease the use, the server interface is modelled after the interface
of _cgdconfig(8)_. This implies that the key must have the same format
as used by _cgdconfig_, which means the key is a base64-encoded
string. The first 4 bytes of the key string denote the actual length
of the key in bits (these 4 bytes are stored in big endian order). For
now, we only support the use of a stored key. However, we plan to add
the use of passphrases in relation with keys later.
Currently, _rump_cgd_ is only able to _configure_ a _cgd_ device but
can not generate the configuration itself. A configuration or rather a
working key may be generated by using the new _tool/rump_ script. The
used cipher is hard-coded to _aes-cbc_ with a key size of 256 bit at
the moment. Note, the server serves only one client as it
transparently encrypts/decrypts one back-end block session. Though
_rump_cgd_ is currently limited with regard to the used cipher and the
way key input is handled, we plan to extend this
rump-kernel-based component step by step in the future.
If you want to get some hands on with CGD, the first step is to
prepare a raw encrypted and ext2-formatted partition image by using
the 'tool/rump' script
! dd if=/dev/urandom of=/path/to/image
! rump -c /path/to/image # key is printed to stdout
! rump -c -k <key> -f -F ext2fs /path/to/image
To use this disk image, the following config snippet can be used
! <start name="rump_cgd">
! <resource name="RAM" quantum="8M"/>
! <provides><service name="Block"/></provides>
! <config action="configure">
! <params>
! <method>key</method>
! <key>AAABAJhpB2Y2UvVjkFdlP4m44449Pi3A/uW211mkanSulJo8</key>
! </params>
! </config>
! <route>
! <service name="Block"> <child name="ahci"/> </service>
! <any-service> <parent/> <any-child/> </any-service>
! </route>
! </start>
Note, we explicitly route the block-session requests for the
underlying block device to the AHCI driver.
The block service provided by _rump_cgd_, in turn, is used by a file-system
server.
! <start name="rump_fs">
! <resource name="RAM" quantum="16M"/>
! <provides><service name="File_system"/></provides>
! <config fs="ext2fs">
! <policy label="" root="/" writeable="yes"/>
! </config>
! <route>
! <service name="Block"> <child name="rump_cgd"/> </service>
! <any-service> <parent/> <any-child/> </any-service>
! </route>
! </start>
Currently, the key to access the cryptographically secured device must
be specified before using the device. Implementing a mechanism which
asks for the key on the first attempt is in the works.
By using the rump kernels and the cryptographic-device driver, we are
able to use block-level encryption on Genode and on Linux.
In Linux case, we depend on _rumprun_, which can
run unmodified NetBSD userland tools on top of the rump kernels to
manage the cgd device. To ease this task, we provide the
aforementioned _rump_ wrapper script.
:[https://github.com/rumpkernel/rumprun]: Rumprun
Since the rump script covers the most common use cases for the tools,
the script is comparatively extensive, hence giving a short tutorial
is reasonable.
:Format a disk image with Ext2:
First, prepare the actual image file
! dd if=/dev/zero of=/path/to/image bs=1M count=128
Second, use _tool/rump_ to format the disk image:
! rump -f -F ext2fs /path/to/image
Afterwards the file system just created may be populated with the
contents of another directory by executing
! rump -F ext2fs -p /path/to/source /path/to/image
To list the contents of the image run
! rump -F ext2fs -l /path/to/image
:Create an encrypted disk image:
Creating a cryptographic-disk image based on cgd(4) is done by
executing the following command
! rump -c /path/to/image
This will generate a key that may be used to decrypt the image later
on. Since this command will only generate a key and _not_ initialize
the disk image, it is highly advised to prepare the disk image by
using _/dev/urandom_ instead of _/dev/zero_. In other words, only new
blocks later written to the disk image are encrypted on the fly. In
addition while generating the key, a temporary configuration file will
be created. Although this file has proper permissions, it may leak the
generated key if it is created on persistent storage. To specify a
more secure directory, the '-t' option can be used:
! rump -c -t /path/to/secure/directory /path/to/image
It is advised to carefully select an empty directory because the specified
directory is removed at after completion.
Decrypting the disk image requires the key generated in the previous
step:
! rump -c -k <key> /path/to/image
For now this key has to be specified as command line argument. This is
an issue if the shell, which is used, is maintaining a history of
executed commands.
For the sake of completeness let us put all examples together by creating an
encrypted ext2 image that will contain all files of Genode's _demo_
scenario:
! dd if=/dev/urandom of=/tmp/demo.img bs=1M count=16
! rump -c /tmp/demo.img # key is printed to stdout
! rump -c -k <key> -f -F ext2fs -d /dev/rcgd0a /tmp/demo.img
! rump -c -k <key> -F ext2fs -p $(BUILD_DIR)/var/run/demo /tmp/demo.img
To check if the image was populated successfully, execute the
following:
! rump -c -k <key> -F ext2fs -l /tmp/demo.img
More detailed information about the options and arguments of
this tool can be obtained by running:
! rump -h
Since _tool/rump_ just utilizes the rump kernels running on the host
system to do its duty, there is a script called _tool/rump_cgdconf_
that extracts the key from a 'cgdconfig(8)' generated configuration
file and is also able to generate such a file from a given key.
Thereby, we try to accommodate the interoperability between the general
rump-kernel-based tools and the _rump_cgd_ server used on Genode.
Per-process virtual file systems
################################
Our C runtime served us quite well over the years. At its core, it has a
flexible plugin architecture that allows us to combine different back ends
such as the lwIP socket API (using libc_lwip_nic_dhcp), using LOG as stdout
(via libc_log), or using a ROM dataspace as a file (via libc_rom). Recently
however, the original design has started to show its limitations:
Although there is the libc_fs plugin that allows a program to access files
from a file-system server, there is no way to allow a program to access
two different file-system servers. For example, if a web server wants to
obtain its configuration and the website content from two different file
systems.
Beside the lack of features of individual libc plugins, there are
problems stemming from combining multiple plugins.
For example, there is the libc_block plugin that makes a block session
accessible as a
pseudo block device named "/dev/blkdev". However, when combined with the
libc_fs plugin, it is not defined which of the two plugins will respond to
requests for a file with this name.
As a quick and dirty work-around, the libc_fs plugin
explicitly black-lists "/dev/blkdev". The need for such a work-around
hints at a deficiency of the overall design.
In general, if multiple plugins are combined, there is no consistent
virtual file-system structure exposed via getdirentries.
Another inconvenience is a missing concept for handling standard input
and output. Most programs use
libc_log to direct stdout to the LOG service. But what if we want to
direct the output of such a program to a terminal? Granted, there
exists the terminal_log server to translate a LOG session to a
terminal session but it would be much nicer to have this flexibility
at the C-runtime level.
Finally, when looking at the implementation of the plugins, it becomes
apparent that many of them look similar. We have to admit that there are quite
a few dusty corners where duplicated code has been accumulated over the years.
That said, the semantic details (e.g., the quality of error handling) differ
from plugin to plugin. Seeing the number of file systems (and thereby the
number of added libc plugins) grow, it became clear that our original
design would make the situation even worse.
On the other hand, we have gathered overly positive experiences with the
virtual file-system implementation of our Noux runtime, which is an
environment for running Unix software on Genode. The VFS as implemented for
Noux supports stacked file systems (similar to union mounts) of various
types. It is stable and complete enough to run our tool chain to build Genode
on Genode. Wouldn't it be a good idea to reuse the Noux VFS for the normal
libc? With the current release cycle, we pursued this line of thoughts.
The first step was transplanting the VFS code from the Noux runtime to a
free-standing library. The most substantial
change was the decoupling of the VFS interfaces from the types provided by
Noux. All those types had been moved to the VFS library. In the process
of reshaping the Noux VFS into a library, several existing pseudo file systems
received a welcome clean-up, and some new ones were added. In particular,
there is a new "log" file system for writing data to a LOG session, a "rom"
file system for reading ROM modules, and an "inline" file system for
reading data defined within the VFS configuration.
The second step was the addition of a new libc_vfs plugin to the C runtime.
This plugin makes the VFS library available to libc-using programs via the
original libc plugin interface. It translates the types and functions of the
VFS library to the types and functions of the C library. At this point, it was
an optional plugin. As the VFS was meant to replace the various existing plugins
instead of accompanying them, the next challenge was to revisit all the
users of the various libc plugins and adapting them to use the libc_vfs
plugin instead. This was, by far, the more elaborative step. More than 50
programs and their respective run scripts had to be adapted and tested.
However, this process was very satisfying because we could see how the
new VFS plugin satisfies all the use cases formerly accommodated by a zoo
of special plugins.
As the last step, we could retire several libc plugins such as libc_rom,
libc_block, libc_log, and libc_fs and merge the libc_vfs into the libc.
Technically, it is still a plugin, but it is always present.
:How has the libc changed?:
Each libc-using program can be configured with a program-local virtual
file system as illustrated by the following example:
! <config>
! ...
! <libc stdin="/dev/null" stdout="/dev/log" stderr="/dev/log">
! <vfs>
! <dir name="dev">
! <log/>
! <null/>
! </dir>
! <dir name="etc">
! <dir name="lighttpd">
! <inline name="lighttpd.conf">
! ...
! </inline>
! </dir>
! </dir>
! <dir name="website">
! <tar name="website.tar"/>
! </dir>
! </vfs>
! </libc>
! </config>
Here you see a lighttpd server that serves a website coming from a TAR
archive (which is obtained from a ROM module named "website.tar"). There
are two pseudo devices "/dev/log" and "/dev/null", to which the
"stdin", "stdout", and "stderr" attributes refer. The "log" file system
consists of a single node that represents a LOG session. The web server
configuration is supplied inline as part of the configuration. (BTW, you can
try out a very similar scenario using the 'ports/genode_org.run' script)
The VFS implementation resides at 'os/include/vfs/'. This is where you
can see the file-system types that are available (look for
_*_file_system.h_ files). Because the same code is used by Noux, we have
one unified and coherent VFS implementation throughout the framework now.
There are two things needed to adapt your work to the change.
* Remove the use of the libc_{rom, block, log, fs} plugins from your
target description files. Those plugins are no more. As of now,
the VFS is still internally a plugin, but it is always included with
the libc.
* Configure the VFS of your libc-using program in your run script. For
most former users of the sole libc_log plugin, this configuration
looks like this:
! <config>
! <libc stdout="/dev/log" stderr="/dev/log">
! <vfs> <dir name="dev"> <log/> </dir> </vfs>
! </libc>
! </config>
For former users of other plugins, there are the 'block', 'rom',
and 'fs' file-system types available.
:Feature set and limitations:
As of now, the following file-system types are supported:
:dir: represents a directory, which, in turn, can host multiple file
systems.
:block: accesses a block session. The label of the session can be configured
via the "label" attribute.
:fs: accesses a file-system server via a file-system session. The session
label can be defined via the "label" attribute.
:inline: provides the content of the configuration node as the content of
a read-only file.
:log: represents a pseudo device for writing to a LOG session. This type
is useful for redirecting stdout to a LOG service such as the one provided
by core.
:null and zero: represent pseudo devices similar to _/dev/null_ and
_/dev/zero_ on Unix.
:rom: makes a ROM module available as a read-only file. If the name of
the ROM module differs from the node name, the module name can be
expressed by the "label" attribute.
:tar: obtains a TAR archive as ROM module and makes its content available
as a file system. The name of the ROM module corresponds to the
name of the tar node.
:terminal: is a pseudo device that accesses a terminal session. The
session can be labeled using the "label" attribute.
There are still two major limitations: First, select is not supported yet.
That means that programs cannot block for I/O (such as reading from a
terminal). Because of this limitation, we still keep the libc_terminal around,
which supports select. As the second limitation, the VFS interface performs
read and write operations as synchronous requests. This is inherited from the
Noux implementation. It goes without saying that we plan to change it to
support non-blocking operations. But this step is not taken yet.
Revised session interfaces
==========================
The session interfaces for framebuffer and file-system access underwent
the following minor changes.
:Framebuffer session:
We simplified the framebuffer-session interface by removing the
'Framebuffer::Session::release()' method. This step makes the mode-change
protocol consistent with the way the ROM-session interface handles
ROM-module changes. That is, the client acknowledges the release of its
current dataspace by requesting a new dataspace via the
'Framebuffer::Session::dataspace()' method.
To enable framebuffer clients to synchronize their operations with the
display frequency, the session interface received the new 'sync_sigh'
function. Using this function, a client can register a handler for
receiving display-synchronization events. As of now, no framebuffer
service implements this feature in a useful way. But this will change
in the upcoming release cycle when we overhaul Genode's GUI stack.
:File-system session:
Until now, there was no exception type for the condition where a symbolic link was
created on a file system w/o symlink support, e.g., FAT. The
corresponding file-system server (ffat_fs) used to return a negative handle
as a work-around. Hence, we added 'Permission_denied' to the list of
exceptions thrown by 'File_system::Session::symlink' to handle this case in
a clean way.
Ported 3rd-party software
#########################
VirtualBox on NOVA
==================
With Genode 14.02, we successfully executed more than seven
guest-operating systems, including MS Windows 7, on top of Genode/NOVA. Based
on this proof of concept, we invested significant efforts to stabilize
and extend our port of VirtualBox during the last three months. We
also paid attention to user friendliness (i.e., features) by enabling
support for guest-additions.
Regarding stability, one issue we encountered has been occasional
synchronization problems during the early VMM bootstrap phase. Several
internal threads in the VMM are started concurrently, like the timer
thread, emulation thread (EMT), virtual CPU handler thread, hard-disk
thread, and user-interface front-end thread. Some of these threads are
favoured regarding their execution over others according to their
importance. VirtualBox expresses this by host-specific mechanisms like
priorities and nice levels of the host operating system. For Genode,
we implemented this specific part accordingly by using multiple Genode
CPU sessions.
The next working field was the emulation code and the code for
handling VM exits, which have been executed by two different threads.
We chose this structure in the original port to satisfy the following
specific characteristics of the underlying NOVA kernel. The emulation
code is provided by VirtualBox and is started as a pthread (EMT
thread). In contrast, the hardware accelerated vCPU thread is running
solely in the context of the VM in guest mode. When a VM exit happens,
the exit is reflected by an IPC message sent through a NOVA portal and
received by a vCPU handler thread running in our port of the
VirtualBox VMM. This thread must be a NOVA _worker_ thread, one which
has no scheduling context (SC) associated. The emulation thread
however is a _global_ thread with an associated SC.
Using two separate threads and synchronization points between them
enabled us in the first release of the port to quickly make progress,
which led to the successful execution of Windows guests. Now, one goal was
to merge both threads in order to avoid thread-context switching costs
between them. Also, we wanted to get rid of transferring the state
between vCPU handler and emulation thread back and forth including all
that ugly synchronization code. For that purpose, we changed the
startup of the emulation code: We first setup the vCPU handler thread
and then start the vCPU in the VM. Hereafter, the VM exits immediately
via a NOVA specific vCPU startup exception and the vCPU handler thread
gets in control. The vCPU handler thread then actually starts
executing the VirtualBox specific emulation code (originally executed
by the EMT thread). Now the vCPU handler thread and the VirtualBox EMT
thread are physically one execution context. Whenever the emulation
code decides to switch to hardware accelerated mode, the vCPU handler
thread can directly setup the transfer of the VM state from the
VirtualBox emulation mode into the state fields of the vCPU of the
guest.
Additionally, we had to re-adjust the memory management of our port to
meet requirements expected by VirtualBox. For some internal data
structures, VirtualBox saves a pointer to a memory location not just
as absolute pointer, but instead splits this pointer into a
process-absolute base and a base-local offset. These structures can
thereby be shared over different protection domains where the base
pointer typically differs (shared memory attached at different
addresses). For the Genode port, we actually don't need this shared
memory features, however, we had to recognize that the space for the
offset value is a signed integer (int32_t). On a 64bit host, this
feature caused trouble if the distance of two memory pointers was
larger than 31 bit (2 GiB). Fortunately, each memory-allocation
request for such data structures comes with a type field, which we can
use to make sure that all allocations per type are located within a 2
GiB virtual range.
Finally, we optimized the VM exits marginally and now try to avoid
entering the emulation mode during a recall VM exit. If we detect that
an IRQ is pending by the VMM models during the recall VM-exit
handling, we inject the IRQ directly into the VM instead of changing
into the VirtualBox emulation mode by default.
Regarding our keen endeavor to enable VirtualBox's guest additions, we
started by enabling the VMMDev PCI pseudo device, which is the basis
for VMM-specific hypercalls executed by guest systems. Beside basic
functions (e.g., software version reporting from host to guest and
vice versa) also complex communication protocols can be implemented by
storing request structures in guest-physical memory and passing their
addresses to the VMMDev request I/O port. The communication mechanism
in VirtualBox is called host-guest-communication manager (HGCM) and
provides host services to the enlightened guest-operating system.
Among the available services, the most interesting service for us was
support for _shared folders_ to exchange data between Genode and the
guest OS. Now, we are able to configure shares in VirtualBox, which
are mapped to VFS directories. For example
! <start name="virtualbox">
! ...
! <config>
! ...
! <libc> <vfs> <dir name="ram"> <fs label="ram" /> </vfs> </libc>
! <share host="/ram/miezekatze" guest="miezekatze" />
! ...
! </config>
! <route>
! <service name="File_system">
! <if-arg key="label" value="ram" /> <child name="ram_fs"/>
! </service>
! ...
! </route>
! </start>
configures one shared folder _miezekatze_, which is backed by a VFS
mount to a pre-populated RAM file system.
Furthermore, we integrated the guest-pointer device with the
Nitpicker pointer and connected the real-time clock VMM model to our
RTC-device driver. Both features are enabled by default and need no
further configuration. Currently, both Nitpicker and the guest OS
draw the mouse pointers on screen. We will improve this in the future
as the guest informs about GUI state via distinct pointer shapes.
During our development, we updated our port to VirtualBox 4.2.24 with the
rough plan to go for 4.3 during the rest of the year.
Ported libraries
================
We updated OpenSSL to version 1.0.1g, which contains a fix for the
heart-bleed bug.
Furthermore, we enabled OpenSSL and curl for the ARM architecture.
Device drivers
##############
USB 3.0 for x86-based platforms
===============================
Having support for USB 3.0 or XHCI host controllers on the Exynos 5 platform
since mid 2013, we decided it was about time to enable USB 3.0 on x86
platforms. Because XHCI is a standardized interface, which is also exposed by
the Exynos 5 host controller, the enablement was relatively straight forward.
The major open issue for x86 was the missing connection of the USB controller
to the PCI bus. For this, we ported the XHCI-PCI part from Linux and connected
it with the internal-PCI driver of our _dde_linux_ environment. This step
enabled basic XHCI support for x86 platforms. Unfortunately, there seems not
to be a single USB 3.0 controller without quirks. Thus, we tested some PCI
cards and notebooks and added controller-specific quirks as needed. These
quirks may not cover all current production chips though.
We also enabled and tested the HID, storage, and network profiles for USB 3.0,
where the supported network chip is, as for Exynos 5, the ASIX AX88179
Gigabit-Ethernet Adapter.
Platforms
#########
Execution on bare hardware (base-hw)
====================================
Multi-processor support
~~~~~~~~~~~~~~~~~~~~~~~
When we started to contemplate the support for symmetric multiprocessing
within the base-hw kernel, a plenty of fresh influences on this subject
floated around in our minds. Most notably, the NOVA port of Genode recently
obtained SMP support in the course of a prototypically comparison of different
models for inter-processor communication. In addition to the very insightful
conclusions of this evaluation, our knowledge about other kernel projects and
their way to SMP went in. In general, this showed us that the subject - if
addressed too ambitious - may boast lots of complex stabilization problems, and
coping with them easily draws down SMP efficiency in the aftermath.
Against this backdrop, we decided - as so often in the evolution of the base-hw
kernel - to pick the easiest-to-reach and easiest-to-grasp solution first with
preliminary disregard to secondary requirements like scalability. As the
base-hw kernel is single-threaded on uniprocessor systems, it was obvious to
maintain one kernel thread per SMP processor and, as far as possible, let them
all work in a similar way. To moreover keep the code base of the kernel as
unmodified as possible while introducing SMP, access to kernel objects get
fully serialized by one global spin lock. Therewith, we had a very minimalistic
starting point for what shall emerge on the kernel side.
Likewise, we started with a feature set narrowed to only the essentials on the
user side, prohibiting thread migration, any kind of inter-processor
communication, and also the unmapping of dataspaces, as this would have
raised the need for synchronization of TLBs. While thread migration
is still an open issue, means of inter-processor communication and TLB
synchronization were added successively after having the basics work stable.
First of all, the startup code of the kernel had to be adapted. The simple
uniprocessor instantiation was split into three phases: At the very beginning,
the primary processor runs alone and initializes everything that is needed for
calling a simple C function, which then prepares and performs the activation of
the other processors. For each processor, the program provides a dedicated
piece of memory for the local kernel stack to live in. Now each processor
goes through the second (the asynchronous multiprocessor) phase, initializing
its local caches and its memory-management unit. This is a basic prerequisite
for spin locks to behave globally coherent, which also implies that memory
accesses at this level can't be synchronized. Therefore, the first
initialization phase prepares everything in such a way, that the second phase
can be done without writing to global memory. As soon as the processors are
done with the second phase, they acquire the global spin lock that protects all
kernel data. This way, all processors consecutively pass the third
initialization phase that handles all remaining drivers and kernel objects.
This is the last time the primary processor plays a special role by doing all
the work that isn't related to processor-local resources. Afterwards the
processors can proceed to the main function that is called on every kernel
pass.
Another main challenge was the mode-transition assembler code path that
performs both
transitions from a processor exception to the call of the kernel-main function
and from the return of the kernel-main function back to the user
space. As this can't be synchronized, all corresponding data must be provided
per processor. This brought in additional offset calculations, which were a
little tricky to achieve without polluting the user state. But after we managed
to do so, the kernel was already able to handle user threads on different
processors as long as they didn't interact with each other.
When it came to synchronous and asynchronous inter-processor communication,
we enjoyed a big benefit of our approach. Due to fully serializing all kernel
code paths, none of the communication models had changed with SMP. Thanks to
the cache coherence of ARM hardware, even shared memory amongst processors
isn't a problem. The only difference is that now a processor may change the
schedule of another processor by unblocking one of its threads on communication
feedback. This may rescind the current scheduling choice of the other
processor. To avoid lags in this case, we let the unaware processor trap into
an IPI. As the IPI sender doesn't have to wait for an answer, this isn't a big
deal neither conceptually nor according to performance.
The last problem we had to solve for common Genode scenarios was the coherency
of the TLBs. When unmapping a dataspace at one processor, the corresponding
TLB entries must be invalidated on all processors, which - at least on
ARM systems - can be done processor-local only. Thus we needed a protocol to
broadcast the operation. First, we decided to leave it to the user land to
reserve a worker thread at each processor and synchronize between them. This
way, we didn't have to modify the kernel back end that was responsible for
updating the caches back in uniprocessor mode. Unfortunately, the revised
memory management explained in Section [Sparsely populated core address space]
relies on unmap operations at the startup of user threads, which led us into a
chicken-and-egg situation. Therefore, the broadcasting was moved from the
userland into the kernel. If a user thread now asks the kernel to update the
TLBs, the kernel blocks the thread and informs all processors. The last
processor that completes the operation unblocks the user thread. If this
unblocking happens remotely, the kernel acts exactly the same as described
above in the user-communication model. This way, the kernel never blocks itself
but only the thread that requests a TLB update.
Given that all kernel operations are lightweight non-blocking operations, we
assume that there is little contention for the global kernel lock. So we hope
that the simple SMP model will perform well for the foreseeable future where
we will have to accommodate only a handful of processors. If this assumption
turns out to be wrong, or if the kernel should scale to large-scale SMP
systems one day, we still have the choice to advance to a more sophisticated
approach without much backpedaling.
Sparsely populated core address space
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As the base-hw platform started as an experiment, its memory management was
built pretty straight forward. All physical memory of the
corresponding hardware was mapped to the virtual memory-address space of
the kernel/core one-by-one. This approach comes with several limitations:
* The amount of physical memory that can be used is limited to a maximum
of 4GB on 32-bit ARM platforms
* Several classes of potential memory bugs within base-hw's core may remain
undetected (i.e., dangling pointers)
* A static mapping of the core/kernel code within a dedicated, restricted area
of the address space of all tasks is impossible. Although, this might be
valuable to minimize runtime overhead of interrupts, and page faults.
* As all physical RAM is mapped into core/kernel's address space as
cacheable memory, in general it is impossible to map a portion of RAM with
other caching attributes, as the cache is working with physical addresses
on ARM. This caused problems when dealing with DMA memory, or when sharing
uncached memory between TrustZone's secure and normal world in the past.
These limitations are resolved as only memory actually used by base-hw's
core/kernel is mapped on demand now. Moreover, the mapping from physical to
virtual isn't necessarily one-by-one anymore.
NOVA microhypervisor
====================
In line with most L4 kernels, the NOVA microhypervisor supports
priority-based round robin scheduling. However, on Genode we did not
leverage this feature. The reason was simple: We had no use for
priorities on NOVA until now. This changes when we are heading towards
using Genode on a daily basis to perform our work. On live Genode
systems, we want to prioritize particular workloads over others.
Admittedly, we also wanted to postpone the solution of one challenging
technical issue beside just enabling priority configuration.
The NOVA kernel supports the creation of threads with and without a
scheduling context attached. Scheduling contexts define a time
quantum, a budget, and a priority. The scheduler uses contexts to
decide which activity runs next on the CPU. Therefore, a thread
without a scheduling context attached can be executed only if a thread
with a scheduling context transfers the context during IPC or during
an exception implicitly for the time of the request. The transfer of
the scheduling context implicitly defines the thread's current
priority level. As a consequence, entrypoint threads inherit the
priority of client threads and may run on completely different
priority levels than other threads in the same process. Unfortunately,
the described behavior interferes with the invariant, which is
required for Genode's yielding spinlock implementation: All threads of one
process are running at the same priority level. Otherwise, the system
may end up in a live lock. Although, the user-level yielding spinlock
implementation is used solely to protect some few instructions in the
lock implementation, the live-lock bears a high risk for the system.
To overcome this issue in base-nova, we replaced the generic yielding
spinlock implementation with a NOVA specific helping lock. So,
lower-priority threads potentially holding the helping-lock get lent
the scheduling context of a higher-priority lock applicant and thereby
can finish the critical section. The core idea is to store the identity of the
lock holder in form of an execution-context capability in the lock
variable. Other lock applicants use the stored capability and instruct
the kernel to help the lock holder with their own scheduling context.
Consequently, the lock-holder thread will run on the budget of the
scheduling context obtained by the helping thread and, therefore,
implicitly at the inherited priority level. The lock holder will
instruct the kernel to pass back the lent scheduling context to the
applicant when leaving the critical section.
We had to extend the NOVA syscall interface to express that a thread
wants to pass its current scheduling context explicitly to another
thread if and only if both threads belong to the same process and CPU.
On reschedule, the context implicitly returns to the lending thread.
Additionally, a thread may request an explicit reschedule in order to
return a lent scheduling context obtained from another thread.
The current solution enables Genode to make use of NOVA's static priorities.
Another unrelated NOVA extension is the ability for a thread to yield
the CPU. The context gets enqueued at the end of the run queue without
refreshing the left budget.
Build system and tools
######################
Build system
============
Sometimes software requires custom tools that are used to generate source
code or other ingredients for the build process, for example IDL compilers.
Such tools won't be executed on top of Genode but on the host platform
during the build process. Hence, they must be compiled with the tool chain
installed on the host, not the Genode tool chain. The Genode build system
received new support for building such host tools as a side effect of building
a library or a target.
Even though it is possible to add the tool compilation step to a regular build
description file, it is recommended to introduce a dedicated pseudo library
for building such tools.
This way, the rules for building host tools are kept separate from rules that
refer to Genode programs. By convention, the pseudo library should be named
_<package>_host_tools_ and the host tools should be built at
_<build-dir>/tool/<package>/_. With _<package>_, we refer to the name of the
software package the tool belongs to, e.g., qt5 or mupdf. To build a tool
named _<tool>_, the pseudo library contains a custom make rule like the
following:
! $(BUILD_BASE_DIR)/tool/<package>/<tool>:
! $(MSG_BUILD)$(notdir $@)
! $(VERBOSE)mkdir -p $(dir $@)
! $(VERBOSE)...build commands...
To let the build system trigger the rule, add the custom target to the
'HOST_TOOLS' variable:
! HOST_TOOLS += $(BUILD_BASE_DIR)/tool/<package>/<tool>
Once the pseudo library for building the host tools is in place, it can be
referenced by each target or library that relies on the respective tools via
the 'LIBS' declaration. The tool can be invoked by referring to
'$(BUILD_BASE_DIR)/tool/<package>/tool'.
For an example of using custom host tools, please refer to the mupdf package
found within the libports repository. During the build of the mupdf library,
two custom tools fontdump and cmapdump are invoked. The tools are built via
the _lib/mk/mupdf_host_tools.mk_ library-description file. The actual mupdf
library (_lib/mk/mupdf.mk_) has the pseudo library 'mupdf_host_tools' listed
in its 'LIBS' declaration and refers to the tools relative to
'$(BUILD_BASE_DIR)'.
Rump-kernel tools
=================
During our work on porting the cryptographic-device driver to Genode,
we identified the need for tools to process block-device and
file-system images on our development machines. For this purpose, we
added the rump-kernel-based tools, which are used for preparing and
populating disk images as well as creating cgd(4)-based cryptographic
disk devices.
The rump-tool chain can be built (similar to building GCC for Genode)
by executing _tool/tool_chain_rump build_. Afterwards, the tools can
be installed via _tool/tool_chain_rump install_ to the default install
location _/usr/local/genode-rump_. As mentioned in
[Block-level encryption using CGD], instead of using the tools
directly, we added the wrapper shell script _tool/rump_.