mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-22 15:02:25 +00:00
1290 lines
62 KiB
Plaintext
1290 lines
62 KiB
Plaintext
|
|
||
|
|
||
|
===============================================
|
||
|
Release notes for the Genode OS Framework 11.05
|
||
|
===============================================
|
||
|
|
||
|
Genode Labs
|
||
|
|
||
|
|
||
|
|
||
|
With our work on Genode 11.05, we pursued two missions, substantiating the
|
||
|
support for the base platforms introduced with the last release, and
|
||
|
reconsidering one of the most fundamental aspects of the framework, which is
|
||
|
inter-process communication. Besides these two main topics, we enjoyed working
|
||
|
on a number of experimental features such as GDB support that will hopefully
|
||
|
have far-reaching effects on how our framework is used.
|
||
|
|
||
|
Cross-kernel platform support is certainly one of the most distinctive features
|
||
|
that sets Genode apart from other operating-system development kits. With the
|
||
|
previous version 10.02, we had proudly announced having bumped the number of
|
||
|
supported base platforms to 8 different kernels. Since this release, the two
|
||
|
new base platforms received a lot of attention. We not only advanced the
|
||
|
support for the Fiasco.OC kernel to catch up featurewise with the other
|
||
|
platforms but went on with porting its most prominent application, namely
|
||
|
L4Linux, to Genode. L4Linux is a paravirtualized version of the Linux kernel
|
||
|
specifically developed to run as user-level application on top of Fiasco.OC.
|
||
|
Now L4Linux can be used with Genode on both x86 and ARM. The second addition to
|
||
|
the base platforms was our custom kernel implementation for the Xilinx
|
||
|
MicroBlaze architecture. For this platform, we have now activated the APIs for
|
||
|
creating user-level device drivers, and introduced a reference SoC for
|
||
|
executing Genode on the Xilinx Spartan-3A Starter Kit.
|
||
|
|
||
|
Getting inter-process communication right is possibly the most serious concern
|
||
|
of microkernel-based operating systems. When Genode was started in 2006, we
|
||
|
disregarded the time-tested standard solution of using interface description
|
||
|
languages and IDL compilers. Well, we never looked back. Genode devised the use
|
||
|
of standard C++ features combined with simple object-oriented design patterns.
|
||
|
Even though we regarded our approach as a great leap forward, it had some
|
||
|
inherent shortcomings. These were the lack of type safety, the need for
|
||
|
manually maintaining communication code, and the manual estimation of
|
||
|
communication-buffer sizes. The current release remedies all these shortcomings
|
||
|
with a brand new API for implementing procedure calls across process
|
||
|
boundaries. This API facilitates type safety and almost eliminates any manual
|
||
|
labour needed when implementing remote procedure calls between processes. Yet,
|
||
|
the concept still relies only on the C++ compiler with no need for additional
|
||
|
tools.
|
||
|
|
||
|
As the Genode developer community grows, we observe the rising need for a solid
|
||
|
debugging solution. The new release features our first step towards the use of
|
||
|
the GNU debugger within our framework. In addition to the progress on the
|
||
|
actual framework, we are steadily seeking ways to make Genode more easily
|
||
|
accessible to new developers. We have now added new ready-to-use scripts for
|
||
|
building, configuring, and test-driving a number of Genode features including
|
||
|
Qt4, lwIP, GDB, and L4Linux.
|
||
|
|
||
|
|
||
|
New API for type-safe inter-process communication
|
||
|
#################################################
|
||
|
|
||
|
Efficient and easy-to-use inter-process communication (IPC) is crucial for
|
||
|
multi-server operating systems because on such systems, almost all of the
|
||
|
functionally offered by a traditional monolithic kernel is provided by a crowd
|
||
|
of different user-level processes talking to each other. Whereas the L4 line of
|
||
|
microkernels took the lead in terms of IPC performance, the development of
|
||
|
applications for such platforms and dealing with the kernel mechanisms in
|
||
|
particular is not easy. Hence, for most microkernels, there exists tooling
|
||
|
support to hide the peculiarities of kernel mechanisms behind higher-level
|
||
|
interface description languages (IDL). However, in our past experience, the
|
||
|
introduction of an IDL compiler into the tool chain of a multi-server OS did
|
||
|
not only bring comfort, but also serious headaches. The two most prominent
|
||
|
problems are the unfortunate mixing of abstraction levels and the complexity of
|
||
|
the solution.
|
||
|
|
||
|
Even though IDL compilers are a time-tested solution for distributed systems,
|
||
|
we argue that applying them to kernel-level systems programming is misguided.
|
||
|
On the one hand, IDLs such as CORBA IDL suggest a lot power (e.g., the ability
|
||
|
to communicate arbitrarily complex data types), which microkernel-targeting IDL
|
||
|
compilers fail to deliver because of kernel interface constraints
|
||
|
(e.g., hard limits with regard to message sizes). On the other hand, IDL
|
||
|
per se misses expressions and functionality important to OS development such as
|
||
|
easy-to-use bindings to a systems programming language, fine-tuned resource
|
||
|
allocation, or the transfer of special IPC items. Therefore, most IDL compilers
|
||
|
used for microkernels sport various extensions or even do crazy things like
|
||
|
retrieving type definitions from C header files.
|
||
|
|
||
|
With the rich feature set demanded by application developers, some IDL
|
||
|
compilers have become extremely complex, i.e., comprising more than 60K lines
|
||
|
of code. Furthermore, the integration of an IDL compiler into the tool chain
|
||
|
implies build-system complexity. Also the stub codes generated by an IDL
|
||
|
compiler must be taken into consideration and raise the question of whether
|
||
|
they must by regarded as part of the trusted computing base and, therefore,
|
||
|
become subject to human review.
|
||
|
|
||
|
For these reasons, Genode dismissed the IDL approach in favor for a raw
|
||
|
C++-based alternative, fostering the use of the C++ streaming operators
|
||
|
combined with templates. The following paper provides a detailed discussion
|
||
|
on the subject:
|
||
|
|
||
|
:[http://genode-labs.com/publications/dynrpc-2007.pdf - A Case Study on the Cost and Benefit of Dynamic RPC Marshalling for Low-Level System Components]:
|
||
|
_SIGOPS OSR Special Issue on Secure Small-Kernel Systems, 2007_
|
||
|
|
||
|
In hindsight, leaving behind the IDL approach was the right decision. From a
|
||
|
developer's perspective, there is no need to comprehend two levels of
|
||
|
abstraction - one systems programming language should be enough. Genode's IPC
|
||
|
framework has raw and direct semantics without hidden magic. Still the IPC
|
||
|
framework is abstract enough to remain extremely portable. The same API works
|
||
|
seamless across 8 different kernels using different flavours of IPC mechanisms.
|
||
|
|
||
|
That said, our solution was never exempt from valid criticism, which we try
|
||
|
to remedy with the Genode version 11.05.
|
||
|
|
||
|
|
||
|
State of the art
|
||
|
================
|
||
|
|
||
|
Genode provides three ways of inter-process communication: signals, shared
|
||
|
memory, and synchronous remote procedure calls (RPC). In the following, only
|
||
|
remote procedure calls are discussed. An RPC in the context of Genode is a
|
||
|
function call to a remote process running on the same machine (contrarily to
|
||
|
the term RPC being used in the context of systems distributed over a network).
|
||
|
|
||
|
The state of the art is best explained by the example interface discussed
|
||
|
in the [http://genode.org/documentation/developer-resources/client_server_tutorial - Hello Tutorial].
|
||
|
On Genode, each RPC interface is represented by an abstract C++ class,
|
||
|
enriched by some bits of information shared by the caller and the callee.
|
||
|
|
||
|
! class Session
|
||
|
! {
|
||
|
! protected:
|
||
|
!
|
||
|
! enum Opcode { SAY_HELLO = 23, ADD = 42 };
|
||
|
!
|
||
|
! public:
|
||
|
!
|
||
|
! virtual void say_hello() = 0;
|
||
|
! virtual int add(int a, int b) = 0;
|
||
|
! };
|
||
|
|
||
|
On the callee side, each function is represented by a number (opcode). To let
|
||
|
both caller and callee talk about the same opcodes, the interface class hosts
|
||
|
an 'Opcode' enumeration with each value corresponding to one RPC function.
|
||
|
|
||
|
On the callee side, the interface is inherited by a so-called 'Server' class
|
||
|
with the purpose of dispatching incoming RPC requests and directing them to the
|
||
|
respective server-side implementation of the abstract RPC interface.
|
||
|
|
||
|
! struct Session_server : Server_object,
|
||
|
! Session
|
||
|
! {
|
||
|
! int dispatch(int op, Ipc_istream &is,
|
||
|
! Ipc_ostream &os)
|
||
|
! {
|
||
|
! switch(op) {
|
||
|
!
|
||
|
! case SAY_HELLO:
|
||
|
! say_hello();
|
||
|
! break;
|
||
|
!
|
||
|
! case ADD:
|
||
|
! {
|
||
|
! int a = 0, b = 0;
|
||
|
! is >> a >> b;
|
||
|
! os << add(a,b);
|
||
|
! break;
|
||
|
! }
|
||
|
!
|
||
|
! default:
|
||
|
! return -1;
|
||
|
! }
|
||
|
! return 0;
|
||
|
! }
|
||
|
! };
|
||
|
|
||
|
The 'Server' class is further inherited by the actual implementation of the
|
||
|
callee's functions. By using this class-hierarchy convention, the 'Server'
|
||
|
dispatch code can be reused by multiple implementations of the same interface.
|
||
|
|
||
|
The caller-side of the RPC interface is represented by a 'Client' class,
|
||
|
implementing the 'Session' interface using Genode's IPC streaming API, namely
|
||
|
an 'Ipc_client' object.
|
||
|
|
||
|
! class Session_client : public Session
|
||
|
! {
|
||
|
! protected:
|
||
|
!
|
||
|
! Msgbuf<256> _sndbuf, _rcvbuf;
|
||
|
! Ipc_client _ipc_client;
|
||
|
! Lock _lock;
|
||
|
!
|
||
|
! public:
|
||
|
!
|
||
|
! Session_client(Session_capability cap)
|
||
|
! : _ipc_client(cap, &_sndbuf, &_rcvbuf) { }
|
||
|
!
|
||
|
! void say_hello()
|
||
|
! {
|
||
|
! Lock::Guard guard(_lock);
|
||
|
! _ipc_client << SAY_HELLO << IPC_CALL;
|
||
|
! }
|
||
|
!
|
||
|
! int add(int a, int b)
|
||
|
! {
|
||
|
! Lock::Guard guard(_lock);
|
||
|
! int ret = 0;
|
||
|
! _ipc_client << ADD << a << b << IPC_CALL >> ret;
|
||
|
! return ret;
|
||
|
! }
|
||
|
! };
|
||
|
|
||
|
Even though this scheme is relatively easy to follow and served us well over
|
||
|
the years, it has several drawbacks:
|
||
|
|
||
|
:Consistency between 'Client' and 'Server' stub codes:
|
||
|
|
||
|
The developer is responsible to manually maintain the consistency between the
|
||
|
'Client' and 'Server' classes. For the mapping of opcodes to functions, the
|
||
|
naming convention of letting the enum names correlate to uppercase function
|
||
|
names is just fine. But there is no easy-to-follow convention for function
|
||
|
arguments. Care must be taken to let both 'Client' and 'Server' stream the
|
||
|
correct argument types in the same order. In practice, maintaining the
|
||
|
correlation between 'Client' and 'Server' stub code is not too hard because
|
||
|
the stub code is easy to comprehend and to test. However, in some cases,
|
||
|
errors slipped in and remained undetected for some time. For example, a
|
||
|
client inserting an 'int' value and a server extracting a 'long' value play
|
||
|
nicely together as long as they are executed on 32-bit machines. But on 64
|
||
|
bit, the communication breaks down.
|
||
|
|
||
|
:Manual dimensioning of message buffers:
|
||
|
|
||
|
The 'Ipc_client' message buffers must be dimensioned correctly. Choosing them
|
||
|
too small may lead to corrupted RPC arguments. Too large buffers waste
|
||
|
memory. Because arguments are differently sized on different architectures,
|
||
|
numerically specified buffer sizes are always wrong. Because expressing
|
||
|
the buffer size with a proper accumulation of 'sizeof()' values is awkward to
|
||
|
do manually, message buffers tend to get over dimensioned.
|
||
|
|
||
|
:Locking of message buffers:
|
||
|
|
||
|
Because one 'Client' object may be concurrently accessed by multiple threads,
|
||
|
precautions for thread safety must be taken by protecting the message buffers
|
||
|
with lock guards. Of course, the implementation effort is not too high, but
|
||
|
a missing lock guard can take hours to spot once a weird race condition occurs.
|
||
|
|
||
|
:Danger of using anonymous enums for defining opcodes:
|
||
|
|
||
|
The compiler is free to optimize the size of values of anonymous enums. Small
|
||
|
values may be represented as 'char' whereas larger values may use 'int'. On
|
||
|
the callee side, the opcode is always extracted into an 'int' variable.
|
||
|
Hence, the client must insert an 'int' value as well, which is not guaranteed
|
||
|
for anonymous enums. Unfortunately, the 'Opcode' type is never explicitly
|
||
|
used, so that a missing type name is not detected at compile time.
|
||
|
|
||
|
:Exception-support possible but labour intensive:
|
||
|
|
||
|
Several of Genode's interfaces indicate error conditions via C++ exceptions.
|
||
|
The propagation of exceptions via IPC is pretty straight forward. On the
|
||
|
callee-side, the dispatch code must catch each exception known to be thrown
|
||
|
from the implementation and translate each exception type to a unique return
|
||
|
value. If such a return value is received at the caller side, the 'Client'
|
||
|
stub code throws the respective exception. Similar to the streaming of
|
||
|
function arguments, the corresponding code is easy to craft, yet it must be
|
||
|
maintained manually.
|
||
|
|
||
|
|
||
|
Re-approaching the problem using template meta programming
|
||
|
==========================================================
|
||
|
|
||
|
When we introduced Genode's C++-stream-based dynamic RPC marshalling in 2006,
|
||
|
we were hinted by Michael Hohmuth to the possibility of automatic generation
|
||
|
of the stub code via recursive C++ templates. However, back then, neither Michael
|
||
|
nor we had the profound understanding of the programming technique required to
|
||
|
put this idea into practice. However, the idea kept spinning in our heads - until
|
||
|
today.
|
||
|
|
||
|
Last year, we finally realized a prototype implementation of this idea. To our
|
||
|
excitement, we discovered that this technique had the potential to remedy all
|
||
|
of the issues pointed out above. With the current release, this powerful
|
||
|
technique gets introduced into the Genode API. Because this new API would break
|
||
|
compatibility with our existing IPC and client-server APIs, we took the chance
|
||
|
to closely examine the use cases of these APIs, and re-consider their feature
|
||
|
sets. Our findings are:
|
||
|
|
||
|
* The distinction between the IPC API ('ipc.h') and the client-server API
|
||
|
('server.h') turned out to be slightly over designed. Originally, the IPC
|
||
|
API was meant as a mere abstraction to the low-level IPC mechanism of the
|
||
|
kernel (sending and receiving messages) whereas the server API adds the
|
||
|
object model. However, there is no single use case for the stand-alone use of
|
||
|
the IPC API except for a bunch of test cases specifically developed for the
|
||
|
IPC API implementations. Furthermore, half of the IPC API namely send-only
|
||
|
IPC and wait-only IPC remained unused, and on some base platforms (e.g.,
|
||
|
NOVA) even unsupported. Consequently, we see potential to simplify the IPC
|
||
|
API by sticking to raw function-call semantics.
|
||
|
|
||
|
* The use of C++ streams for marshalling/unmarshalling suggests an enormous
|
||
|
flexibility. E.g., by overloading the operators for specific types, complex
|
||
|
nested data structures could be transferred. However, this never happened -
|
||
|
for the good reason that we always strive to keep the RPC interfaces of OS
|
||
|
services as simple and straight-forward as possible. If the payload becomes
|
||
|
complex, we found that the use of synchronous RPCs should be reconsidered
|
||
|
anyway. For such use cases, shared memory is the way to go. On the other
|
||
|
hand, the possibility of overloading the stream operators turned out to be
|
||
|
extremely useful for handling platform-specific IPC payload, most prominently
|
||
|
kernel-protected capabilities on NOVA and Fiasco.OC. So we will stick with
|
||
|
the C++-stream based marshalling/unmarshalling.
|
||
|
|
||
|
* The inheritance of RPC interfaces is an important feature to support
|
||
|
platform-specific extensions to Genode's core services. For example, on
|
||
|
Linux, an extension to the 'Dataspace' interface provides additional
|
||
|
information about the file that is used as backing store. On OKL4, the
|
||
|
extension of core's PD services provides OKL4-specific functions that where
|
||
|
added to run OKLinux on Genode. Consequently, the support for interface
|
||
|
inheritance is a must.
|
||
|
|
||
|
* The typed capabilities introduced with Genode 8.11 formed an inheritance
|
||
|
hierarchy independent from the actual interfaces. By convention, typed
|
||
|
capabilities were tagged with their corresponding interface classes but their
|
||
|
inheritance relationship was explicitly expressed by an additional template
|
||
|
argument. For this reason, the definition of each capability type had to
|
||
|
be provided via a separate header file (named 'capability.h') for each
|
||
|
interface. It would be much nicer to just use the class relationships between
|
||
|
interfaces to infer the corresponding capability-type relationships.
|
||
|
|
||
|
* Allowing RPC functions to throw exceptions is crucial. In fact, our
|
||
|
goal is to design RPC interfaces in C++ style as far as possible. If
|
||
|
throwing an exception fits naturally into the API, the framework should
|
||
|
not stand in the way. Consequently, C++ exception support for the RPC
|
||
|
framework is a must.
|
||
|
|
||
|
* The separation of 'Server_activation' and 'Server_entrypoint' never
|
||
|
paid off. The 'Server_activation' represents the thread to be used
|
||
|
by a 'Server_entrypoint'. The original design of the NOVA hypervisor
|
||
|
envisioned the use of multiple "worker" activations to serve one entry point.
|
||
|
Our API tried to anticipate this kernel feature. In the meanwhile, two
|
||
|
reasons are speaking against this idea. No other kernel supports such a
|
||
|
feature, so using the feature by an application would spoil it's inter-kernel
|
||
|
portability. Second, even the NOVA developers disregarded this feature at a
|
||
|
later development stage. In summary, merging both 'Server_activation' and
|
||
|
'Server_entrypoint' looks like a good idea to simplify Genode's API.
|
||
|
|
||
|
Even though the revised RPC API promised to be a vast improvement over the
|
||
|
original IPC and client-server APIs, the risks of such a huge overhaul must be
|
||
|
considered as well. We are aware of developers with reservations about the use
|
||
|
of C++ template meta programming. It seems to be common sense that this
|
||
|
technique is some kind of witch craft, the code tends to be ugly, the compiler
|
||
|
takes ages to cut its teeth through the recursive templates, and the resulting
|
||
|
binaries become bloated and large. If any of these arguments had held true, we
|
||
|
would not have introduced this technique into Genode. Admittedly, the syntax of
|
||
|
template meta code is not always easy to comprehend but we believe that
|
||
|
elaborative comments in the code make even these parts approachable.
|
||
|
|
||
|
|
||
|
Introduction of the new API
|
||
|
===========================
|
||
|
|
||
|
The new RPC API completely replaces the formerly known IPC ('base/ipc.h')
|
||
|
and client-server ('base/server.h') APIs. It consists of the following
|
||
|
header files:
|
||
|
|
||
|
:'base/rpc.h':
|
||
|
Contains the basic type definitions and utility macros to declare RPC
|
||
|
interfaces. It does not depend on any other Genode API except for the
|
||
|
meta-programming utilities provided by 'util/meta.h'. Therefore, 'base/rpc.h'
|
||
|
does not pollute the namespace of the place where it is included.
|
||
|
|
||
|
:'base/rpc_args.h':
|
||
|
Contains definitions of non-trivial argument types used for transferring
|
||
|
strings and binary buffers. Its use by a RPC interface is entirely optional.
|
||
|
|
||
|
:'base/rpc_server.h':
|
||
|
Contains the interfaces of the server-side RPC API. This part of the API
|
||
|
consists of the 'Rpc_object' class template and the 'Rpc_entrypoint' class.
|
||
|
It entirely replaces the original 'base/server.h' API ('Rpc_object'
|
||
|
corresponds to the original 'Server_object', 'Rpc_entrypoint' corresponds to
|
||
|
the original 'Server_activation' and 'Server_entrypoint' classes.
|
||
|
|
||
|
:'base/rpc_client.h':
|
||
|
Contains the API support for invoking RPC functions. It is complemented by
|
||
|
the definitions in 'base/capability.h'. The most significant elements of the
|
||
|
client-side RPC API are the 'Capability' class template and 'Rpc_client',
|
||
|
which is a convenience wrapper around 'Capability'.
|
||
|
|
||
|
That sounds simple enough. Let's see how to use this API for the example of
|
||
|
Section [State of the art].
|
||
|
|
||
|
The RPC interface is still an abstract C++ interface, supplemented by some bits
|
||
|
of RPC-relevant information.
|
||
|
|
||
|
! #include <base/rpc.h>
|
||
|
!
|
||
|
! struct Session
|
||
|
! {
|
||
|
! virtual void say_hello() = 0;
|
||
|
! virtual int add(int a, int b) = 0;
|
||
|
!
|
||
|
! GENODE_RPC(Rpc_say_hello, void, say_hello);
|
||
|
! GENODE_RPC(Rpc_add, int, add, int, int);
|
||
|
! GENODE_RPC_INTERFACE(Rpc_say_hello, Rpc_add);
|
||
|
! };
|
||
|
|
||
|
Note that the 'Opcode' enum is gone. Instead there is an RPC interface
|
||
|
declaration using the 'GENODE_RPC' and 'GENODE_RPC_INTERFACE' macros. These
|
||
|
macros are defined in 'base/rpc.h' and have the purpose to enrich the interface
|
||
|
with type information. They are only used at compile time and have no effect on
|
||
|
the run time or the size of the interface class. Each RPC function is
|
||
|
represented as a type. In this example, the type meta data of the 'say_hello'
|
||
|
function is attached to the 'Rpc_say_hello' type within the scope of 'Session'.
|
||
|
The macro arguments are:
|
||
|
|
||
|
! GENODE_RPC(func_type, ret_type, func_name, arg_type ...)
|
||
|
|
||
|
The 'func_type' argument is an arbitrary type name (except for the type name
|
||
|
'Rpc_functions') used to refer to the RPC function, 'ret_type' is the return
|
||
|
type or 'void', 'func_name' is the name of the server-side function that
|
||
|
implements the RPC function, and the list of 'arg_type' arguments comprises the
|
||
|
RPC function argument types. The 'GENODE_RPC_INTERFACE' macro defines a type
|
||
|
called 'Rpc_functions' that contains the list of the RPC functions provided by
|
||
|
the RPC interface.
|
||
|
|
||
|
On the server side, the need for the 'Server' class has vanished. Instead, the
|
||
|
server-side implementation inherits 'Rpc_object' with the interface type as
|
||
|
arguments.
|
||
|
|
||
|
! #include <base/rpc_server.h>
|
||
|
!
|
||
|
! struct Component : Rpc_object<Session>
|
||
|
! {
|
||
|
! void say_hello()
|
||
|
! {
|
||
|
! ...
|
||
|
! }
|
||
|
!
|
||
|
! int add(int a, int b)
|
||
|
! {
|
||
|
! ...
|
||
|
! }
|
||
|
! };
|
||
|
|
||
|
The RPC dispatching is done by the 'Rpc_object' class template, according to
|
||
|
the type information that comes with the 'Session' interface.
|
||
|
|
||
|
On the client-side, there is still a '<interface>/client.h' file, but it has
|
||
|
become significantly shorter.
|
||
|
|
||
|
! #include <base/rpc_client.h>
|
||
|
!
|
||
|
! struct Session_client : Rpc_client<Session>
|
||
|
! {
|
||
|
! Session_client(Capability<Session> cap)
|
||
|
! : Rpc_client<Session>(cap) { }
|
||
|
!
|
||
|
! void say_hello() {
|
||
|
! call<Rpc_say_hello>(); }
|
||
|
!
|
||
|
! int add(int a, int b) {
|
||
|
! return call<Rpc_add>(a, b); }
|
||
|
! };
|
||
|
|
||
|
There are a few notable things. First, 'Capability' is now a template class
|
||
|
taking the interface type as argument. So in principle, there is no more a
|
||
|
pressing need to explicitly define a dedicated capability type for each
|
||
|
interface. Second, the message buffer declarations are gone. Message buffers
|
||
|
are dimensioned automatically at compile time. Third, there is no manual
|
||
|
application of the C++ stream operator. Instead, the 'call' function template
|
||
|
performs the correct marshalling and unmarshalling in a type-safe manner. Type
|
||
|
conversion rules correspond to the normal C++ type-conversion rules. So you can
|
||
|
actually pass a char value to a function taking an int value. If there is no
|
||
|
valid type conversion or the number of arguments is wrong, the error gets
|
||
|
detected at compile time. Finally, there no more any need for locking message
|
||
|
buffers. Very similar to the way, plain function calls work, the 'call'
|
||
|
mechanism allocates a correctly dimensioned message buffer on the stack of the
|
||
|
caller. The message buffer is like a call frame. By definition, a call frame
|
||
|
cannot be used by multiple thready concurrently because each thread has its own
|
||
|
stack.
|
||
|
|
||
|
|
||
|
Transferable argument types
|
||
|
===========================
|
||
|
|
||
|
The arguments specified to 'GENODE_RPC' behave mostly as expected by a normal
|
||
|
function call. But there are some notable differences to keep in mind:
|
||
|
|
||
|
:Value types:
|
||
|
Value types are supported for basic types and plain-old-data types
|
||
|
(self-sufficient structs or classes). The object data is transferred as such.
|
||
|
If the type is not self sufficient (it contains pointers or references), the
|
||
|
pointers and references are transferred as plain data, most certainly
|
||
|
pointing to the wrong thing in the callee's address space.
|
||
|
|
||
|
:Const references:
|
||
|
Const references behave like value types. The referenced object is
|
||
|
transferred to the server and a reference to the server-local copy is passed
|
||
|
to the server-side function. Note that in contrast to a normal function call
|
||
|
taking a reference argument, the size of the referenced object is accounted
|
||
|
for allocating the message buffer on the client side.
|
||
|
|
||
|
:Non-const references:
|
||
|
Non-const references are handled similar to const references. In addition the
|
||
|
server-local copy gets transferred back to the caller so that server-side
|
||
|
modifications of the object become visible to the client.
|
||
|
|
||
|
; Should we mention, that copy constructors/assignment opeerators of
|
||
|
; by-reference parameters may be called by the stream op, or do I miss
|
||
|
; something?
|
||
|
|
||
|
:Capabilities:
|
||
|
Capabilities can be transfered as values, const references, or non-const
|
||
|
references.
|
||
|
|
||
|
:Variable-length buffers:
|
||
|
There exists special support for passing binary buffers to RPC functions using
|
||
|
the 'Rpc_in_buffer' class template provided by 'base/rpc_args.h'. The maximum
|
||
|
size of the buffer must be specified as template argument. An 'Rpc_in_buffer'
|
||
|
object does not contain a copy of the data passed to the constructor, only a
|
||
|
pointer to the data. In contrast to a fix-sized object containing a copy of
|
||
|
the payload, the RPC framework does not transfer the whole object but only
|
||
|
the actually used payload.
|
||
|
|
||
|
:Pointers:
|
||
|
Pointers and const pointers are handled similar to references. The pointed-to
|
||
|
argument gets transferred and the server-side function is called with a
|
||
|
pointer to the local copy. *Note* that the semantics of specifying pointers
|
||
|
as arguments for RPC interface functions is not finalized. We may decide to
|
||
|
remove the support for pointers to avoid misconceptions about them (i.e.,
|
||
|
expecting 'char const *' to be handled as a null-terminated string, or
|
||
|
expecting pointers to be transferred as raw bits).
|
||
|
|
||
|
; IMO 'Type *out_param' fits better than 'Type &out_param' because of
|
||
|
; the copy constructor issue, right?
|
||
|
|
||
|
All types specified at RPC arguments or as return value must have a default
|
||
|
constructor.
|
||
|
|
||
|
By default, all RPC arguments are input arguments, which get transferred to the
|
||
|
server. The return type of the RPC function, if present, is an output-only
|
||
|
value. To avoid a reference argument from acting as both input- and output
|
||
|
argument, a const reference should be used. Some interfaces may prefer to
|
||
|
handle certain reference arguments as output-only, e.g., to query multiple
|
||
|
state variables from a server. In this case, the RPC direction can be defined
|
||
|
specifically for the type in question by providing a custom type trait
|
||
|
specialization for 'Trait::Rpc_direction' (see 'base/rpc.h').
|
||
|
|
||
|
|
||
|
Supporting advanced RPC use cases
|
||
|
=================================
|
||
|
|
||
|
Two advanced use cases are important to mention, throwing exceptions across RPC
|
||
|
boundaries and interface inheritance.
|
||
|
|
||
|
:C++ exceptions:
|
||
|
|
||
|
The propagation of C++ exceptions from the server to the client is supported
|
||
|
by a special variant of the 'GENODE_RPC' macro:
|
||
|
|
||
|
! GENODE_RPC_THROW(func_type, ret_type, func_name,
|
||
|
! exc_type_list, arg_type ...)
|
||
|
|
||
|
This macro features the additional 'exc_type_list' argument, which is a type
|
||
|
list of exception types. To see this feature at work, please refer to
|
||
|
Genode's base interfaces such as 'parent/parent.h'. Exception objects are not
|
||
|
transferred as payload - just the information that the specific exception was
|
||
|
raised. Hence, information provided with the thrown object will be lost
|
||
|
when crossing an RPC boundary.
|
||
|
|
||
|
:Interface inheritance:
|
||
|
|
||
|
The inheritance of RPC interfaces comes down to a concatenation of the
|
||
|
'Rpc_functions' type lists of both the base interface and the derived
|
||
|
interface. This use case is supported by a special version of the
|
||
|
'GENODE_RPC_INTERFACE' macro:
|
||
|
|
||
|
! GENODE_RPC_INTERFACE_INHERIT(base_interface,
|
||
|
! rpc_func ...)
|
||
|
|
||
|
The 'base_interface' argument is the type of the inherited interface. For an
|
||
|
example, please refer to 'linux_dataspace/linux_dataspace.h' as contained in
|
||
|
the 'base-linux' repository.
|
||
|
|
||
|
:Casting capability types:
|
||
|
|
||
|
For typed capabilities, the same type conversion rules apply as for pointers.
|
||
|
In fact, a typed capability pretty much resembles a typed pointer, pointing
|
||
|
to a remote object. Hence, assigning a specialized capability (e.g.,
|
||
|
'Capability<Input::Session>') to a base-typed capability (e.g.,
|
||
|
'Capability<Session>') is always valid. For the opposite case, a static cast
|
||
|
is needed. For capabilities, this cast is supported by
|
||
|
! static_cap_cast<INTERFACE>(cap)
|
||
|
|
||
|
In rare circumstances, mostly in platform-specific base code, a reinterpret
|
||
|
cast for capabilities is required. It allows to convert any capability to
|
||
|
another type:
|
||
|
! reinterpret_cap_cast<INTERFACE>(cap)
|
||
|
|
||
|
:Non-virtual interface functions:
|
||
|
|
||
|
It is possible to declare RPC functions using 'GENODE_RPC', which do not
|
||
|
exist as virtual functions in the interface class. In this case, the function
|
||
|
name specified as third argument to 'GENODE_RPC' is of course not valid for
|
||
|
the interface class but an alternative class can be specified as second
|
||
|
argument to 'Rpc_object'. This way, a server-side implementation may specify
|
||
|
its own class to direct the RPC function to a local (possibly non-virtual)
|
||
|
implementation. This feature is used to allow the RPC function to have a
|
||
|
slightly different semantic as the actual C++ interface function. For
|
||
|
example, an interface may contain a function taking a 'char const *' as
|
||
|
argument and expecting a null-terminated string. When specifying this type as
|
||
|
'GENODE_RPC' argument, the RPC framework will not know about the implied
|
||
|
string semantics and just transfer a single character. In this case, the
|
||
|
'GENODE_RPC' function may use a 'Rpc_in_buffer' (defined in 'rpc_args.h')
|
||
|
instead and refer to a differently named server-side function (e.g., using a
|
||
|
'_' prefix). On the server side, the 'Rpc_in_buffer' argument can then be
|
||
|
converted to the function interface expected by the real server function.
|
||
|
|
||
|
|
||
|
Typed capabilities, typed root interfaces
|
||
|
=========================================
|
||
|
|
||
|
The consistent use of typing 'Rpc_object', 'Capability', and 'Rpc_client' with
|
||
|
interface type has paved the way to further type-safety goodness. Since there
|
||
|
now is a 1:1 relationship between each 'Rpc_object' type and a 'Capability'
|
||
|
type, the 'Rpc_entrypoint' has become able to propagate this type information
|
||
|
through the 'manage' function. A capability returned by 'manage' is now
|
||
|
guaranteed to refer to the same interface as the 'Rpc_object' argument. If such
|
||
|
a capability is transferred as argument of an RPC function through the new
|
||
|
type-safe argument marshalling, the receiver will obtain the correct capability
|
||
|
type. The only current exception is the handling of session capabilities
|
||
|
transferred through the parent interface. But also this use case greatly
|
||
|
benefits from the now type-enriched capabilities.
|
||
|
|
||
|
For the propagation of session capabilities, there are two transitions visible
|
||
|
to the application developer: The way a service is announced at the parent and
|
||
|
the way a session is requested from the parent. For announcing a service,
|
||
|
the parent's 'announce' function is used, which takes the service name and
|
||
|
a root capability as argument.
|
||
|
! env()->parent()->announce(Hello::Session::service_name(),
|
||
|
! Root_capability(ep.manage(&root)));
|
||
|
With Genode 11.05, is has become possible to tag 'Root' interfaces with their
|
||
|
respective session types using the 'Typed_root' template defined in
|
||
|
'root/root.h'. By combining typed capabilities with typed root interfaces, the
|
||
|
'Parent' class has become able to provide a simplified 'announce' function,
|
||
|
taking only a root capability as argument and inferring the other information
|
||
|
needed:
|
||
|
! env()->parent()->announce(ep.manage(&root));
|
||
|
This way, the type of the root interface gets propagated through the 'manage'
|
||
|
function right into the 'Parent' interface.
|
||
|
|
||
|
The request of sessions from the parent is almost exclusively performed by
|
||
|
so-called 'Connection' objects, which are already typed in the original API.
|
||
|
|
||
|
|
||
|
Migration path
|
||
|
==============
|
||
|
|
||
|
The new RPC API is the most fundamental API change in Genode's history. In such
|
||
|
a case, breaking API compatibility is inevitable. The question is how to make
|
||
|
the migration path to the new API as smooth as possible. We are confident to
|
||
|
have found a pretty good answer to this question.
|
||
|
|
||
|
Immediate incompatibilities
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
For the time being, the new API complements the existing API so that code
|
||
|
relying on the IPC and client-server APIs will largely continue to work until
|
||
|
the old APIs will be removed with the Genode version 11.08. So the immediate
|
||
|
incompatibilities come down to the following:
|
||
|
|
||
|
* 'Capability' has become a template. The original untyped 'Capability' class
|
||
|
interface is available as 'Untyped_capability'. Within the 'base-<platform>'
|
||
|
repositories, the content of 'base/capability.h' moved over to
|
||
|
'base/native_types.h' and is now called 'Native_capability'.
|
||
|
'Untyped_capability' and 'Native_capability' are equivalent. The latter type
|
||
|
is meant to be used in low-level code that interacts with the
|
||
|
platform-specific capability members. In contrast, 'Untyped_capability' is
|
||
|
used in places where the type of the capability can be left unspecified. Both
|
||
|
types are rare in Genode's API and their use in application code is
|
||
|
discouraged. For now, the old 'Typed_capability' is equivalent to the new
|
||
|
'Capability'.
|
||
|
|
||
|
* To implement the strict consistency between interface hierarchies and
|
||
|
capability hierarchies, all session interfaces must be derived from
|
||
|
'Genode::Session' defined in 'session/session.h'. Only by adhering to this
|
||
|
rule, 'Capability<Your_session>' can be converted to 'Capability<Session>'.
|
||
|
|
||
|
To make the transition to the API as seamless as possible, the new API reuses
|
||
|
(inherits) parts of the original interfaces. E.g., 'Rpc_entrypoint' has
|
||
|
'Server_entrypoint' as base class. Also, the original 'Server_entrypoint' can
|
||
|
deal with typed capabilities.
|
||
|
|
||
|
Transition steps
|
||
|
~~~~~~~~~~~~~~~~
|
||
|
|
||
|
The steps required for the transition to the new API are almost contained
|
||
|
in the RPC interface's 'include/<interface>' directory.
|
||
|
|
||
|
:Modifications in '<interface>/<interface>.h':
|
||
|
* Include the header 'base/rpc.h'. For a session interface, include
|
||
|
the header 'session/session.h' instead.
|
||
|
* Remove the opcode definition.
|
||
|
* Add the 'GENODE_RPC' and 'GENODE_RPC_INTERFACE' declarations to
|
||
|
the interface class.
|
||
|
|
||
|
:Modifications in '<interface>/client.h>':
|
||
|
* Include the header '<rpc_client.h>', remove the headers
|
||
|
'base/lock.h', 'base/ipc.h'.
|
||
|
* Remove the member variables (message buffer, lock, ipc-client
|
||
|
object). Now that there are no longer any private members, you may decide
|
||
|
to turn the 'class' into a 'struct'.
|
||
|
* Inherit the client class from 'Rpc_client<INTERFACE>'
|
||
|
* Pass 'Capability<INTERFACE>' to the constructor of
|
||
|
'Rpc_client<INTERFACE>'.
|
||
|
* Replace the content of each interface function with
|
||
|
'call<RPC_FUNC>(args...)'.
|
||
|
|
||
|
:Modifications in '<interface>/server.h>':
|
||
|
In most cases, this file can be deleted.
|
||
|
|
||
|
:Modifications in the implementation:
|
||
|
Replace base class '<INTERFACE>_server' by base class
|
||
|
'Rpc_object<INTERFACE>'.
|
||
|
|
||
|
Because the abstract C++ interface of the RPC interface has not changed, client
|
||
|
code does not require any changes.
|
||
|
|
||
|
|
||
|
Migration of Genode's interfaces
|
||
|
================================
|
||
|
|
||
|
Our original plan envisioned the migration of all of the base repositories to
|
||
|
the new RPC API, and thereby test the concept with many representative use
|
||
|
cases including the application of advanced features outlined above. To our
|
||
|
delight, the transition to the new API went far more smoothly than anticipated,
|
||
|
motivating us to look at the 'os' interfaces as well - with great success. The
|
||
|
following interfaces have been converted to use the new API: 'Cap_session',
|
||
|
'Cpu_session', 'Foc_cpu_session', 'Dataspace', 'Linux_dataspace',
|
||
|
'Io_mem_session', 'Io_port_session', 'Irq_session', 'Log_session', 'Parent',
|
||
|
'Pd_session', 'Okl4_pd_session', 'Foc_pd_session', 'Ram_session', 'Rm_session',
|
||
|
'Rom_session', 'Root', 'Session', 'Signal_session', 'Framebuffer_session',
|
||
|
'Input_session', 'Loader_session', 'Nitpicker_session', 'Nitpicker_view',
|
||
|
'Pci_device', 'Pci_session', 'Timer_session', and 'Noux_session'. Additionally,
|
||
|
several process-local RPC interfaces (e.g., in core, timer, nitpicker) have been
|
||
|
converted. Each of those interfaces worked instantly after modification and
|
||
|
fixing eventual compile errors. This overly positive experience greatly
|
||
|
supports our confidence in the new technique. Our goal was to not change the
|
||
|
original C++ interfaces. For this reason, some interfaces still rely on
|
||
|
server-side wrappers of the 'Rpc_object' class template. Those wrappers are
|
||
|
called '<interface>/rpc_object.h'. With the next release, we are going to
|
||
|
remove them altogether. The only interfaces not yet migrated are the users of
|
||
|
Genode's packet stream interface such as 'Nic_session', 'Audio_out_session',
|
||
|
and 'Block_session'. The conversion of those is subject to the next release.
|
||
|
|
||
|
|
||
|
Limitations
|
||
|
===========
|
||
|
|
||
|
The *maximum number of RPC function arguments* is limited to 7.
|
||
|
If your function requires more arguments, you may consider grouping
|
||
|
some of them in a compound struct.
|
||
|
|
||
|
The *maximum number of RPC functions per interface* supported by the
|
||
|
'GENODE_RPC_INTERFACE' macro is limited to 9. In contrast to the limitation of
|
||
|
the number of function arguments, this limitation is unfortunate. Even in
|
||
|
core's base services, there is an interface ('cpu_session.h') exceeding this
|
||
|
limit. However, in cases like this, the limitation can be worked-around by
|
||
|
manually constructing the type list of RPC functions instead of using the
|
||
|
convenience macro:
|
||
|
! typedef Meta::Type_tuple<Rpc_create_thread,
|
||
|
! Meta::Type_tuple<Rpc_kill_thread,
|
||
|
! Meta::Empty> >
|
||
|
! Rpc_functions;
|
||
|
|
||
|
Both limitations exist because C++ does not support templates with variable
|
||
|
numbers of arguments. Our type-list implementation employed by the
|
||
|
'GENODE_RPC_INTERFACE' macro always takes a fixed number of arguments but
|
||
|
allows defaults for all of them. So the maximum number of arguments is
|
||
|
constrained. In C++0x, type lists are better supported, which will possibly
|
||
|
remove these limits and simplify the template code.
|
||
|
|
||
|
|
||
|
L4Linux
|
||
|
#######
|
||
|
|
||
|
L4Linux is a user-level variant of the Linux kernel that can be executed as
|
||
|
plain user-level program on the Fiasco.OC microkernel combined with the L4Re
|
||
|
userland. The L4Linux kernel uses a paravirtualization technique and provides
|
||
|
binary compatibility with the Linux kernel. Since 1997, L4Linux is developed
|
||
|
and maintained by the OS Group at the University of Technology Dresden. Thanks
|
||
|
to the timely tracking of the upstream Linux kernel by L4Linux main developer
|
||
|
Adam Lackorzynski, the L4Linux kernel is particularly valued for being up to
|
||
|
date with the current version of the Linux kernel. As of today, L4Linux
|
||
|
corresponds to the kernel version 2.6.38.
|
||
|
|
||
|
L4Linux is often regarded as one of the prime features of the Fiasco.OC
|
||
|
platform. Since Genode started to support Fiasco.OC with the previous release,
|
||
|
we desired to bring this virtualization solution to Genode running on this
|
||
|
kernel. Our L4Linux port is contained in the new 'ports-foc' repository.
|
||
|
Details about building and running L4Linux on Genode can be found in the
|
||
|
top-level README file within this repository.
|
||
|
|
||
|
To keep our changes to L4Linux as minimal as possible, most parts of our
|
||
|
port come in the form of a library, which emulates the subset of the L4Re
|
||
|
userland semantics expected by L4Linux. This library can be found at
|
||
|
'ports-foc/src/lib/l4lx'. At the current stage, the kernel command line is
|
||
|
defined at 'startup.c'. The L4Re emulation approach turned out to be very
|
||
|
efficient with regard to the preservation of original L4Linux code. Excluding
|
||
|
the Genode-specific stub drivers for input and framebuffer, our patch
|
||
|
('ports-foc/patches/l4lx_genode.patch') consists of merely 650 lines.
|
||
|
|
||
|
|
||
|
Base framework
|
||
|
##############
|
||
|
|
||
|
New support for template meta programming
|
||
|
=========================================
|
||
|
|
||
|
As part of the work on the new RPC framework, several utilities for template
|
||
|
meta programming have been created. These utilities are available at
|
||
|
'base/include/util/meta.h'. Currently, this header file comprises the following
|
||
|
functionality:
|
||
|
|
||
|
* Type traits for querying reference types, non-reference types, and stripping
|
||
|
constness from types
|
||
|
* Class templates for constructing type lists, namely 'Type_tuple' and
|
||
|
'Type_list'
|
||
|
* Template meta functions for working with type lists, e.g., 'Length',
|
||
|
'Index_of', 'Append', 'Type_at'
|
||
|
* N-Tuples aggregating members (both reference and plain-old-data members)
|
||
|
specified via a type list, called 'Ref_tuple_N' and 'Pod_tuple_N'
|
||
|
* Helper function templates for calling member functions using arguments
|
||
|
supplied in a N-tuple structure
|
||
|
* A helper for the partial specialization of member function templates, called
|
||
|
'Overload_selector'
|
||
|
|
||
|
To differentiate the meta-programming code from normal Genode APIs, all
|
||
|
utilities of 'util/meta.h' reside in a nested 'Meta' name space.
|
||
|
|
||
|
|
||
|
Thread state querying
|
||
|
=====================
|
||
|
|
||
|
As a prerequisite for realizing our GDB monitor experiment described in Section
|
||
|
[GDB monitor experiment], we implemented the 'Cpu_session::state()' function
|
||
|
for OKL4, L4ka::Pistachio, and Fiasco.OC. Furthermore, the CPU session
|
||
|
interface have been extended with the functions 'pause' and 'resume', which
|
||
|
allow to halt and resume the execution of threads created via the CPU session.
|
||
|
The 'pause' and 'resume' functions are implemented for OKL4 only.
|
||
|
|
||
|
|
||
|
Misc
|
||
|
====
|
||
|
|
||
|
* We generalized the former architecture-specific 'touch' functions for
|
||
|
accessing memory (ro or rw). The new version is available at
|
||
|
'base/include/util/touch.h'.
|
||
|
|
||
|
* The constructor interfaces of the 'Process' and 'Child' classes have changed
|
||
|
to accommodate the RM session capability for the new process as an argument.
|
||
|
Originally, the RM session was magically created by the 'Process' class by
|
||
|
acquiring a new RM session from 'env()->parent()'. With the new interface, a
|
||
|
parent that needs to virtualize the RM session of its child can supply a
|
||
|
custom RM-session capability.
|
||
|
|
||
|
|
||
|
Operating-system services and libraries
|
||
|
#######################################
|
||
|
|
||
|
Dynamic linker
|
||
|
==============
|
||
|
|
||
|
To support dynamic linking on all platforms including Fiasco.OC, we
|
||
|
revisited our dynamic loader and changed its mode of operation. In the past,
|
||
|
the dynamic loader was a statically linked program executed by the 'process'
|
||
|
library if a dynamic binary was supplied as 'Process' argument. Because, the
|
||
|
dynamic loader is a normal Genode process, it initialized its Genode
|
||
|
environment on startup, and requested the dynamic binary as well as the
|
||
|
required shared libraries from its parent via ROM sessions. Finally, the
|
||
|
dynamic linker called the startup code of the dynamically linked program. This
|
||
|
program, in turn, initialized again an environment. Consequently, dynamically
|
||
|
linked programs used to employ two 'Genode::env()' environments, each backed
|
||
|
with the same 'RAM', 'RM', and 'CPU' sessions. On most platforms this slightly
|
||
|
schizophrenic nature of dynamically linked programs worked without problems.
|
||
|
|
||
|
However, things became tricky on Fiasco.OC because on this kernel, the
|
||
|
environment contains parts that must be instantiated only once, namely the
|
||
|
allocator for kernel-capability selectors. Therefore, a way was desired to
|
||
|
remove the duplicated Genode environment. The solution is a scheme as used on
|
||
|
Linux. The dynamic linker is both, a shared library and a program. It contains
|
||
|
a single instance of the Genode environment. Each dynamic binary is linked
|
||
|
against the dynamic linker but not against the Genode base libraries that
|
||
|
normally provide the Genode environment. Now, each time the Genode environment
|
||
|
is referenced either by the dynamically linked program or another library, the
|
||
|
dynamic linker resolves the reference by returning its own symbols.
|
||
|
|
||
|
This architectural change is pretty far reaching and changes the way the
|
||
|
dynamic linker is handled by the build system and at runtime. The user-visible
|
||
|
changes are the following:
|
||
|
|
||
|
* The dynamic linker is not anymore a separate target. So the original
|
||
|
location at 'os/src/ldso' is no more.
|
||
|
|
||
|
* The new dynamic linker is called 'ld.lib.so' and resides in
|
||
|
'os/lib/ldso'.
|
||
|
|
||
|
* To ensure that the dynamic linker gets built before linking any dynamic
|
||
|
binary, each shared library is implicitly made dependent on 'ld.lib.so'.
|
||
|
The build system takes care of that during the build process. But it
|
||
|
is important to know that the 'ld.lib.so' must also be provided as boot
|
||
|
module.
|
||
|
|
||
|
* All programs that potentially create child processes must query the
|
||
|
dynamic linker with the new name 'ld.lib.so' instead of 'ldso'.
|
||
|
|
||
|
The new dynamic linker has been tested on OKL4 (both x86 and ARM),
|
||
|
L4ka::Pistachio, Linux (both x86_32 and x86_64), Codezero, NOVA, Fiasco.OC
|
||
|
(x86_32, x86_64, and ARM), and L4/Fiasco.
|
||
|
|
||
|
|
||
|
Utilities for implementing device drivers
|
||
|
=========================================
|
||
|
|
||
|
As the arsenal of native Genode device drivers grows, we observe code patterns
|
||
|
that are repeatedly used. To foster code reuse and minimize duplicated code, we
|
||
|
introduce the following new utilities and skeletons to the 'os' repository:
|
||
|
|
||
|
:'os/attached_io_mem_dataspace.h':
|
||
|
|
||
|
is a memory-mapped I/O dataspace that is ready to use immediately after
|
||
|
construction. This class wraps the creation of an IO_MEM connection, the
|
||
|
request of the IO_MEM session's dataspace, and the attachment of the
|
||
|
dataspace to the local address space. Even more important, this class takes
|
||
|
care of releasing these resources at destruction time.
|
||
|
|
||
|
:'os/attached_ram_dataspace.h':
|
||
|
|
||
|
was formerly known as 'os/ram_dataspace.h' works analogously to
|
||
|
'os/attached_io_mem_dataspace.h', but for RAM dataspaces. This is
|
||
|
very handy for allocating DMA buffers.
|
||
|
|
||
|
:'os/irq_activation.h':
|
||
|
|
||
|
contains a code pattern found in almost each device driver that handles
|
||
|
interrupts. An 'Irq_activation' is a thread that is associated with the IRQ
|
||
|
specified as constructor argument. Each time, an IRQ occurs, a callback
|
||
|
'handle_irq' is executed. Hence, a device driver implementing the callback
|
||
|
interface, can easily be connected to an IRQ.
|
||
|
|
||
|
:'nic/driver.h':
|
||
|
|
||
|
contains a set of interfaces to be used for implementing network device
|
||
|
drivers. The interfaces are designed in a way that enables the strict
|
||
|
separation of device-specific code and Genode-specific code. Note that
|
||
|
the interfaces are not yet finalized and lack some functions, in
|
||
|
particular those related to resource accounting.
|
||
|
|
||
|
:'nic/component.h':
|
||
|
|
||
|
contains ready-to-use glue code for integrating a network device driver into
|
||
|
Genode. The code takes care about implementing the 'Nic::Session_component'
|
||
|
and 'Nic::Root', parses session arguments and sets up the packet stream
|
||
|
between the client and the device driver. Note that this code is still in
|
||
|
flux and not yet optimized. Currently, only the new 'lan9118' driver makes
|
||
|
use of 'nic/component.h' but we are planning to move all other 'Nic' session
|
||
|
implementations over to this skeleton.
|
||
|
|
||
|
|
||
|
Device drivers
|
||
|
##############
|
||
|
|
||
|
Because of the growing number of platforms and devices supported by Genode, we
|
||
|
improved the consistent use of the Genode build specs mechanism. Each device
|
||
|
driver does now depend on a dedicated spec value, which can selectively be
|
||
|
enabled by each platform as needed. For example, the PCI driver does now
|
||
|
depend on the 'pci' spec value. This value is present in the build 'SPECS' of
|
||
|
the various microkernels running on x86 hardware but not on the Linux base
|
||
|
platform or ARM platforms.
|
||
|
|
||
|
New and improved device drivers are:
|
||
|
|
||
|
:PL110 display controller:
|
||
|
The framebuffer driver for the PL110 display controller has been moved
|
||
|
from 'os/src/platform/versatilepb' to 'os/src/drivers/framebuffer/pl110'.
|
||
|
The PL110 driver depends on the build spec 'pl110'.
|
||
|
|
||
|
:Lan9118 network interface:
|
||
|
The new NIC driver for Lan9118 is located at 'os/src/drivers/nic/lan9118/'.
|
||
|
This driver is built as 'nic_drv' when the build specs contain the
|
||
|
'lan9118' value. This is the case for the 'fiasco_pbxa9' platform. The driver
|
||
|
is known to work on Qemu, yet untested on real hardware.
|
||
|
|
||
|
:PL180 MMC and SDcard:
|
||
|
The new block driver for the PL180 MMC and SDcard is located at
|
||
|
'os/src/drivers/sdcard/'. It depends on the build specs value 'pl180'.
|
||
|
At the current stage, the driver contains the low-level code for the
|
||
|
device access but lacks the interfacing to Genode's 'Block_session'
|
||
|
interface.
|
||
|
|
||
|
:PL050 PS/2 input:
|
||
|
The interrupt handling of the PL050 driver has been improved,
|
||
|
IRQs are enabled only once, the IRQ pending bits are used to check
|
||
|
for availability of PS/2 packets. The PL050 driver depends on the
|
||
|
build spec value 'pl050'.
|
||
|
|
||
|
:VESA framebuffer:
|
||
|
The VESA driver has become functional on the x86_64 platform.
|
||
|
It depends on the build spec value 'vesa'.
|
||
|
|
||
|
|
||
|
Libraries and applications
|
||
|
##########################
|
||
|
|
||
|
Ready-to-use run scripts for standard scenarios
|
||
|
===============================================
|
||
|
|
||
|
On our mailing list, questions about using certain Genode components of various
|
||
|
base platforms, pop up at a regular basis. For example, how to use the lwIP
|
||
|
stack on a specific kernel. The answer to these kind of question depends on
|
||
|
several properties such as the used hardware platform or, when using Qemu, the
|
||
|
Qemu arguments. To make the exploration of various Genode features more
|
||
|
attractive, we have added the following run scripts that exercise the use
|
||
|
cases and document the steps required to build, configure, and integrate the
|
||
|
respective feature:
|
||
|
|
||
|
:'os/run/demo.run': builds and executes Genode's default demo scenario.
|
||
|
It should run out of the box from a fresh build directory.
|
||
|
|
||
|
:'libports/run/lwip.run': runs the 'lwip_httpsrv' example on Qemu, downloads a
|
||
|
website from the HTTP server, and validates the response. Make sure to have
|
||
|
the 'libc' and 'libports' repositories enabled in your 'build.conf'. The
|
||
|
'libports' repository must be prepared for 'lwip' ('make prepare PKG=lwip').
|
||
|
Furthermore, you will need a network driver ('nic_drv') as provided by the
|
||
|
'linux_drivers' repository.
|
||
|
|
||
|
:'ports/run/gdb_monitor.run': runs a test program as child of the new GDB
|
||
|
monitor, executed in Qemu. It then attaches a GDB session to the GDB monitor,
|
||
|
allowing the user to inspect the test program. In addition to the repositories
|
||
|
used by 'lwip.run', this run script further depends on the 'gdb' package
|
||
|
provided by the 'ports' repository.
|
||
|
|
||
|
:'qt4/run/qt4.run': runs the 'qt_launchpad' application, which allows the user
|
||
|
to manually start the Qt4 'textedit' program. Of course, the run script
|
||
|
depends on a prepared 'qt4' repository. Furthermore, Qt4 depends on the
|
||
|
libraries 'zlib', 'libpng', and 'freetype' provided by the 'libports'
|
||
|
repository.
|
||
|
|
||
|
:'ports/run/noux.run': compiles the GNU coreutils and wraps them into a tar
|
||
|
archive. It then runs the Noux environment with the tar archive as file
|
||
|
system and instructs Noux to execute the 'ls -Rla' command. The run script
|
||
|
depends on the 'libc', and 'ports' repositories. The 'ports' repository must
|
||
|
be prepared for the 'coreutils' package.
|
||
|
|
||
|
:'ports-okl4/run/lx_block.run': starts the OKLinux kernel on top of OKL4.
|
||
|
This run script must be slightly adapted to use a custom disk image.
|
||
|
By default, it expects a disk image called 'tinycore.img' and an initrd
|
||
|
called 'initrd.gz' in the '<build-dir>/bin/' directory.
|
||
|
|
||
|
:'ports-foc/run/l4linux.run': starts the L4Linux kernel on top of Fiasco.OC.
|
||
|
|
||
|
|
||
|
GDB monitor experiment
|
||
|
======================
|
||
|
|
||
|
Because there are repeated requests for a debugging solution for Genode
|
||
|
programs, we started exploring the use of GNU debugger (GDB) with Genode. The
|
||
|
approach is to run the program to debug (target) as a child process of a
|
||
|
so-called GDB monitor process. The GDB monitor allows the observation and
|
||
|
manipulation of the target program via a remote GDB TCP/IP connection. Our
|
||
|
immediate goal was to examine the mode of interaction between the GDB monitor
|
||
|
and GDB, and to determine the set of requirements a base platform must deliver
|
||
|
to make debugging possible.
|
||
|
|
||
|
The experiment was first conducted on OKL4 because this kernel provides an easy
|
||
|
access to register states of any thread using 'exregs'. Furthermore, in
|
||
|
contrast to most of the other base platforms, OKL4 features a way to suspend
|
||
|
and resume threads. Once, this initial goal was reached, we enabled parts of
|
||
|
the debugging facilities for other base platforms, namely L4/Fiasco,
|
||
|
L4ka::Pistachio, and Fiasco.OC.
|
||
|
|
||
|
:Usage:
|
||
|
|
||
|
To illustrate the use of GDB monitor, a ready-to-use run script is provided
|
||
|
at 'ports/run/gdb_monitor'. This run script executes a simple test program
|
||
|
within the GDB monitor. Once the program is running, a host GDB is started
|
||
|
in a new terminal window and connects to the target running inside Qemu.
|
||
|
In the run script, you will recognise the following things:
|
||
|
|
||
|
* A NIC driver must be built and started. Please make sure to have
|
||
|
a repository with a 'nic_drv' target enabled. E.g., on x86 platforms,
|
||
|
you may use the 'linux_drivers' repository.
|
||
|
|
||
|
* The GDB monitor reads the name of the target program from its Genode config:
|
||
|
! <config> <target name="..."/> </config>
|
||
|
|
||
|
* To connect a host GDB to the remote target running in Qemu, use the
|
||
|
following GDB command:
|
||
|
! target remote localhost:8181
|
||
|
|
||
|
:Current state, limitations:
|
||
|
|
||
|
First, it is important to highlight that the GDB monitor is an experiment and
|
||
|
not ready for real-world use. It has been tested on Fiasco.OC, L4/Fiasco,
|
||
|
OKL4, and L4ka::Pistachio on the x86_32 architecture. On these platforms,
|
||
|
GDB monitor can be used to examine the memory in the target program. However,
|
||
|
only on OKL4, the threads in the target program are halted. The observed memory
|
||
|
state may appear inconsistent on the other platforms. On all platforms, the
|
||
|
current stack pointer and program counter values can be inspected. On OKL4, a
|
||
|
backtrace can be printed. The running threads in the target program can be
|
||
|
listed ('info threads'), selected ('thread N'), and examined. Advanced
|
||
|
debugging features such as breakpoints and watchpoints as well as the access to
|
||
|
general-purpose registers are not implemented.
|
||
|
|
||
|
|
||
|
Platform support
|
||
|
################
|
||
|
|
||
|
Fiasco.OC
|
||
|
=========
|
||
|
|
||
|
With the previous Genode version 11.02, Fiasco.OC was introduced as new
|
||
|
base platform. The initial support contained all functionality needed to
|
||
|
execute the graphical Genode demo scenario on this kernel. However, some pieces
|
||
|
needed for more complex scenarios were missing, most importantly support for
|
||
|
the dynamic linker and the signalling framework. The dynamic linker is a
|
||
|
prerequisite for using the C runtime and all dependent libraries such as lwIP
|
||
|
and Qt4. The signalling framework is used by Genode's packet stream interface,
|
||
|
which in turn, is the basis for the 'Nic', 'Block', and 'Audio_out' interfaces.
|
||
|
|
||
|
The current release brings the Fiasco.OC base platform on par with the other
|
||
|
fully-supported platforms so that the complete Genode software stack becomes
|
||
|
available on this kernel.
|
||
|
|
||
|
Furthermore, we started to take advantage of Fiasco.OC's exceptional platform
|
||
|
support by enabling the use of the x86_64 architecture as well as the ARM
|
||
|
RealView PBX-A9 platform. For the latter platform, though, some parts of Genode
|
||
|
such as Qt4 and Noux are not yet available. To make the ARM RealView PBX-A9
|
||
|
platform usable, we introduced a number of new device drivers such as the PL050
|
||
|
input driver, Lan9118 network driver, and PL110 display driver. Using these
|
||
|
drivers, most of Genode's components including networking and graphics are
|
||
|
ready to use on the PBX-A9 platform. It should be noted, however, that the
|
||
|
device drivers have been developed and tested on Qemu only. They are untested on
|
||
|
real hardware. Their main purpose for now is to showcase how to create Genode
|
||
|
drivers for different device classes.
|
||
|
|
||
|
:Improved integration of 3rd-party kernel sources with Genode:
|
||
|
|
||
|
In the spirit of other repositories that incorporate 3rd-party code, the
|
||
|
'base-foc' repository comes with a new top-level Makefile that takes care of
|
||
|
downloading all the pieces needed for deploying Genode on Fiasco.OC. All that's
|
||
|
needed is issuing 'make prepare' from within the 'base-foc' repository.
|
||
|
When using this way of incorporating Fiasco.OC, the kernel can be built right
|
||
|
from the Genode build directory as created with the build-directory creation
|
||
|
tool at 'tool/builddir/create_builddir':
|
||
|
! make kernel
|
||
|
The kernel will be configured and built according to the platform as specified
|
||
|
to the 'create_builddir' tool. The kernel's build directory can be found at
|
||
|
the '<genode-build-dir>/kernel/fiasco.oc/'.
|
||
|
|
||
|
The kernel is accompanied by two user-level components, namely sigma0 and bootstrap.
|
||
|
Those components can be built in a similar fashion:
|
||
|
! make sigma0
|
||
|
! make bootstrap
|
||
|
For building sigma0 and bootstrap, the Genode build system invokes the L4Re
|
||
|
build system. The corresponding L4Re build directory can be found at
|
||
|
'<genode-build-dir>/l4/'. The kernel interfaces of Fiasco.OC as used by Genode
|
||
|
are installed to '<genode-build-dir>/include/'.
|
||
|
|
||
|
Alternatively to using the new way of integrating Fiasco.OC with Genode,
|
||
|
the location of the kernel binary and a custom L4Re build directory can be
|
||
|
explicitly specified in a file called '<genode-build-dir>/etc/foc.conf':
|
||
|
! L4_BUILD_DIR = <abs-path-to-l4re-build-dir>
|
||
|
! KERNEL = <abs-path-to-fiasco-kernel>
|
||
|
|
||
|
With the new integration approach, the make targets 'clean' and 'cleanall'
|
||
|
are no longer synonymous. The 'clean' target removes all Genode-specific
|
||
|
files from the build directory but keeps the Fiasco.OC and L4Re build
|
||
|
directories. In contrast, the 'cleanall' rule wipes everything.
|
||
|
|
||
|
:Small changes to 'base-foc':
|
||
|
|
||
|
* Core does now export Fiasco.OC's kernel info page (KIP) as ROM module.
|
||
|
* The thread library takes advantage of the user-defined part of the UTCB to
|
||
|
store the pointer to the 'Thread_base' object instead of using the stack
|
||
|
pointer as a key.
|
||
|
* Fiasco.OC's VCPU feature has been made accessible via an Fiasco.OC-specific
|
||
|
extension of core's PD and CPU session interfaces. The only user of these
|
||
|
extension as of today is L4Linux.
|
||
|
* Improved IRQ support for level triggered interrupts, increasing the
|
||
|
maximum number of supported interrupts to 256.
|
||
|
|
||
|
|
||
|
MicroBlaze
|
||
|
==========
|
||
|
|
||
|
Our custom kernel platform for the Xilinx MicroBlaze softcore CPU, which we
|
||
|
introduced with Genode 11.02, has been complemented with the core interfaces
|
||
|
needed for the implementation of user-level device drivers. Those interfaces
|
||
|
are the IRQ service and the IO_MEM service.
|
||
|
|
||
|
IRQ support
|
||
|
~~~~~~~~~~~
|
||
|
|
||
|
To accommodate core's IRQ service, the interface between the kernel-level and
|
||
|
user-level parts of core had to be extended with syscalls for managing and
|
||
|
handling interrupts. These syscalls are exclusively used by the interrupt
|
||
|
threads of core's IRQ services. They are not accessible from other user-level
|
||
|
programs.
|
||
|
|
||
|
:'irq_allocate(irq_number)':
|
||
|
associates the specified IRQ to the calling core thread. One thread
|
||
|
may associate itself with multiple IRQs by consecutive calls of this
|
||
|
syscall. However, the current implementation of core's IRQ service
|
||
|
employs one core thread per IRQ.
|
||
|
|
||
|
:'irq_free(irq_number)':
|
||
|
reverts the effect of 'irq_allocate'.
|
||
|
|
||
|
:'irq_wait()':
|
||
|
lets the calling thread block for any of the IRQs it is associated
|
||
|
with. When unblocked, the calling thread receives the information
|
||
|
about the occurred IRQ in its user-level thread-control block (UTCB).
|
||
|
|
||
|
Run environment, SoC for S3A Starter Kit
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
The initial version of the 'base-mb' platform was tied to a fixed work flow,
|
||
|
executing a predefined Genode scenario on Qemu. With the current release, the
|
||
|
build-system integration advanced towards the versatile usage pattern as found
|
||
|
on the other base platforms.
|
||
|
|
||
|
* The improved run environment supports the inclusion of arbitrary boot modules
|
||
|
into core's ROM service. The underlying mechanism has not changed though. The
|
||
|
ROM modules are aggregated via an assembly file called 'boot_modules.s' using
|
||
|
the 'incbin' directive. Because this file gets linked to core, core can be
|
||
|
booted as single-image on a target.
|
||
|
|
||
|
* In addition of using the MicroBlaze variant of Qemu to execute Genode,
|
||
|
support has been added use different targets. As a reference, a ready-to-use
|
||
|
SoC 'system.bit' file is provided for the Xilinx Spartan3A Starter Kit board.
|
||
|
|
||
|
You can get further inspiration to explore the 'base-mb' platform by studying
|
||
|
the new documentation to be found at 'base-mb/doc/'.
|
||
|
|
||
|
|
||
|
Build system and tools
|
||
|
######################
|
||
|
|
||
|
Genode does currently support 8 different kernel platforms. For each kernel,
|
||
|
different steps are required to download and install the kernel and to
|
||
|
supply the kernel headers to the Genode build system. Furthermore, the
|
||
|
ways of how the result of the Genode build process has to be integrated with
|
||
|
the boot mechanism of respective kernel differs a lot.
|
||
|
|
||
|
Hence, for each base platform, there exists a dedicated Wiki page describing
|
||
|
the manual steps to follow. In the case of Fiasco.OC, these steps are
|
||
|
particularly elaborative, making the use of this platform with Genode less
|
||
|
approachable than most of the others.
|
||
|
|
||
|
:New work flow for integrating 3rd-party kernel code:
|
||
|
|
||
|
To make the head start of using Fiasco.OC as simple as possible, we explored a
|
||
|
new way to integrate the 3rd-party kernel code with Genode. Similar to the
|
||
|
'make prepare' mechanism that we already use for the 'qt4', 'ports', and
|
||
|
'libports' repositories, we have added a top-level Makefile to 'base-foc' that
|
||
|
automates the preparation of all the 3rd-party code needed to use Genode with
|
||
|
the base platform. In the case of Fiasco.OC, this is the kernel code plus some
|
||
|
bits of the L4Re userland, namely sigma0, bootstrap, and l4sys. This
|
||
|
preparation mechanism is complemented by platform-specific pseudo targets that
|
||
|
enable the building of the 3rd-party code right from Genode's build directory.
|
||
|
To support this methodology, we added a hook into the Genode build system,
|
||
|
allowing a platform-specific initialization of the Genode build directory.
|
||
|
E.g., for creating symbolic links to kernel headers. These initial steps are
|
||
|
executed by a pseudo library called 'platform.mk'. This library is guaranteed to
|
||
|
be built prior all other libraries and targets. The new level of integration
|
||
|
greatly simplifies the use of Genode on Fiasco.OC. Hence, we are eager to apply
|
||
|
the same idea to the other base platforms as well.
|
||
|
|
||
|
:New naming scheme for platform-specific ports repositories:
|
||
|
|
||
|
The 'oklinux' repository is now called 'ports-okl4'. Thereby, we want to
|
||
|
facilitate a unified naming scheme for platform-specific 3rd party software.
|
||
|
E.g., the port of L4Linux resides in the new 'ports-foc' repository because it
|
||
|
is specific for the Fiasco.OC base platform.
|
||
|
|
||
|
:New convenience functions for run scripts:
|
||
|
|
||
|
To ease the creation of run scripts that are usable across different kernel and
|
||
|
hardware platforms, we have added new convenience functions to the 'run'
|
||
|
tool. The functions 'append_if' and 'lappend_if' are intended to be
|
||
|
used in combination with the 'have_spec' function to allow the easy
|
||
|
extension of the Genode config, Qemu parameters, and the list of boot
|
||
|
modules driven by 'SPECS' values. For a showcase, please refer to the
|
||
|
new 'os/run/demo.run' script.
|
||
|
|