Remote procedure calls

Section Synchronous remote procedure calls (RPC) provides the high-level description of synchronous remote procedure calls (RPC).

RPC mechanism

The RPC mechanism consists of the following header files:

base/rpc.h

Contains the basic type definitions and utility macros to declare RPC interfaces.

base/rpc_args.h

Contains definitions of non-trivial argument types used for transferring strings and binary buffers. Its use by RPC interfaces is optional.

base/rpc_server.h

Contains the interfaces of the server-side RPC API. In particular, this part of the API consists of the Rpc_object class template.

base/rpc_client.h

Contains the API support for invoking RPC functions. It is complemented by the definitions in base/capability.h. The most important elements of the client-side RPC API are the Capability class template and Rpc_client, which is a convenience wrapper around Capability.

Each RPC interface is an abstract C++ interface, supplemented by a few annotations. For example:

 #include <session/session.h>
 #include <base/rpc.h>

 namespace Hello { struct Session; }

 struct Hello::Session : Genode::Session
 {
   static const char *service_name() { return "Hello"; }

   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);
 };

The macros GENODE_RPC and GENODE_RPC_INTERFACE are defined in base/rpc.h and 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 class. Each RPC function is represented as a type. In the 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.

Server side

The implementation of the RPC interface inherits the Rpc_object class template with the interface type as argument and implements the abstract RPC interface class.

The server-side RPC dispatching is performed by the compile-time-generated dispatch method of the Rpc_object class template, according to the type information found in the annotations of the Session interface.

Genode::Rpc_object_base

To make an instance of an RPC object invokable via RPC, it must be associated with an RPC entrypoint. By passing an RPC object to the Entrypoint::manage method, a new capability for the RPC object is created. The capability is tagged with the type of the RPC object.

Most server-side RPC interfaces are session interfaces. In contrast to plain RPC objects (like a Region_map), all sessions carry a client-provided budget of RAM and capabilities, and are associated with a client-specific label. The Session_object class template captures commonalities of this kind of RPC objects.

Genode::Session_object

Client side

At the client side, a capability can be invoked by the capability's call method template with the RPC functions type as template argument. The method arguments correspond to the RPC function arguments.

By convention, the Capability::call method is rarely used directly. Instead, each RPC interface is accompanied with a client-side implementation of the abstract interface where each RPC function is implemented by calling the Capability::call method. This convention is facilitated by the Rpc_client class template.

Genode::Rpc_client

Using this template, the client-side implementation of the example RPC interface looks as follows.

 #include <hello_session/hello_session.h>
 #include <base/rpc_client.h>

 namespace Hello { struct Session_client; }

 struct Hello::Session_client : Genode::Rpc_client<Session>
 {
   Session_client(Genode::Capability<Session> cap)
   : Genode::Rpc_client<Session>(cap) { }

   void say_hello()
   {
     call<Rpc_say_hello>();
   }

   int add(int a, int b)
   {
     return call<Rpc_add>(a, b);
   }
 };

For passing RPC arguments and results, the regular C++ type-conversion rules are in effect. If there is no valid type conversion, or if the number of arguments is wrong, or if the RPC type annotations are not consistent with the abstract interface, the error is detected at compile time.

Transferable argument types

The arguments specified to GENODE_RPC behave mostly as expected for a normal function call. But there are some notable differences:

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 likely 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 that takes 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.

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 fixed-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.

By default, all RPC arguments are input arguments, which are 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.

Throwing C++ exceptions across RPC boundaries

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 accepts an additional exc_type_list argument, which is a type list of exception types. Exception objects are not transferred as payload. The RPC mechanism propagates solely the information that the specific exception was raised. Hence, information provided with the thrown object will be lost when crossing an RPC boundary.

RPC interface inheritance

It is possible to extend existing RPC interfaces with additional RPC functions. Internally, such an RPC interface inheritance is realized by 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 ...)

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<Event::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 RPC interface functions

It is possible to declare RPC functions using GENODE_RPC, which do not exist as virtual functions in the abstract 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 the server-side 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.

Limitations of the RPC mechanism

The maximum number of RPC function arguments is limited to 7. If a function requires more arguments, it may be worthwhile to consider grouping some of them in a compound struct.

Root interface

Each service type is represented as an RPC object implementing the root interface. The server announces its service type by providing the service name and the capability of the service's root interface (announce function of the parent interface). Given the capability to the root interface, the parent is then able to create and destroy sessions.

Genode::Root

Genode::Typed_root

Because defining root interfaces for services follows a recurring pattern, there exists default template classes that implement the standard behaviour of the root interface for services with multiple clients (Root_component) and services with a single client (Static_root).

Genode::Root_component

Server-side policy handling

The Session_label and Session_policy utilities aid the implementation of the server-side policy-selection mechanism described in Section Server-side policy selection.

Genode::Session_label

Genode::Session_policy

Packet stream

The Packet_stream is the building block of the asynchronous transfer of bulk data (Section Asynchronous bulk transfer - packet streams). The public interface consists of the two class templates Packet_stream_source, and Packet_stream_sink. Both communication parties agree on a policy with regard to the organization of the communication buffer by specifying the same Packet_stream_policy as template argument.

The communication buffer consists of three parts, a submit queue, an acknowledgement queue, and a bulk buffer. The submit queue contains packets generated by the source to be processed by the sink. The acknowledgement queue contains packets that are processed and acknowledged by the sink. The bulk buffer contains the actual payload. The assignment of packets to bulk-buffer regions is performed by the source.

Genode::Packet_descriptor

Genode::Packet_stream_base

Genode::Packet_stream_source

Genode::Packet_stream_sink

Genode::Packet_stream_policy

In client-server scenarios, each client and server can play the role of either a source or a sink of packets. To ease the use of packet streams in such scenarios, the classes within the Packet_stream_rx and Packet_stream_tx namespaces provide ready-to-use building blocks to be aggregated in session interfaces.

Data transfer from server to client

Packet_stream_rx::Channel

Packet_stream_rx::Client

Data transfer from client to server

Packet_stream_tx::Channel

Packet_stream_tx::Client