Servers

Server code is code that provides components in the form described by those components' interfaces, employing interprocess communications mechanisms.

  1. Introduction
  2. Server Header Files
  3. C Language Servers
    1. Compound Interfaces
    2. Operation Implementations
  4. C++ Language Servers
    1. Compound Interfaces
    2. Operation Implementations
  5. Exposing Objects as Servers
  6. Example Code

Introduction

The following example interface, resident in a file called calc.idl, will be used to illustrate the mechanisms described in this document:

interface Calc
{
  void add(in int left, in int right, out int result);
  void subtract(in int left, in int right, out int result);
  void multiply(in int left, in int right, out int result);
  void divide(in int numerator, in int denominator, out int result);
};

To expose this interface to other programs, a server program would do the following:

The sections below describe how this is done in the different supported programming languages.

Note that the mechanism by which interface descriptions are processed for use in programs is described in the L4Re Support document. The idl manual page provides details for developers wishing to use the tool directly.

Server Header Files

Given the existence of generated files for the interface, a program would include the server header file:

#include "calc_server.h"

To expose components as servers, a libipc header file is also needed:

#include <ipc/server.h>

C Language Servers

An object representing a server will have the Calc type. This encapsulates a reference to the object state and a reference to the interface details.

Since the aim in implementing a server is to provide access to state information held within a process, the object state reference will refer to a location holding such information via a pointer member.

ref_Calc ref = {.ptr=0};

Here, a value of 0 is used because the interface operations are stateless (they act like plain functions):

The Calc object is initialised using the chosen reference value and a reference to interface information:

Calc obj = {.ref=ref, .iface=&server_iface_Calc};

Unlike in the client initialisation where a predefined interface already exists, it is up to the component implementer to define the functions that support the exposed interface. For example:

iface_Calc server_iface_Calc = {
  .add=calc_add,
  .subtract=calc_subtract,
  .multiply=calc_multiply,
  .divide=calc_divide,
  .pow=calc_pow
  };

The Calc object is then associated with a server capability and invoked when incoming messages are directed towards it.

Compound Interfaces

When supporting multiple interfaces, the general initialisation resembles that shown above. Firstly, the object state reference is defined, as in this example involving a reference suitable for a CalcCounter object:

ref_CalcCounter ref = {.ptr=&counter};

Here, the pointer member employs the address of a counter. This is accessed in the appropriate operation function.

The object itself is populated in the same way as shown above:

CalcCounter obj = {.ref=ref, .iface=&server_iface_CalcCounter};

The principal difference involves the interface details. Since a compound interface is exposed, there must be a way to address the individual interfaces, and this is done using members employing a specific naming convention:

iface_CalcCounter server_iface_CalcCounter = {
  .to_Calc=&server_iface_Calc,
  .to_Counter=&server_iface_Counter
  };

Here, the to_Calc member provides a reference to suitable details for the Calc interface, and the to_Counter member provides the corresponding reference for the Counter interface.

Operation Implementations

Implementations of operations employ a signature where the first parameter is the object state reference value. This permits access to the state information and allows functions to update the state of several objects independently:

long counter_increment(ref_Counter _self, int *result)
{
  int *counter = (int *) (_self.ptr);

  *counter = *counter + 1;
  *result = *counter;
  return L4_EOK;
}

Here, the pointer member is used to access the information referenced when the object was initialised.

C++ Language Servers

An object representing the component will have the Calc type. An object providing a specific implementation of the component might have a type called server_Calc and be initialised as follows:

server_Calc obj;

Such an object type would need to be a subtype of Calc and would resemble the following:

class server_Calc : public Calc
{
public:
  long add(int left, int right, int *result);
  long subtract(int left, int right, int *result);
  long multiply(int left, int right, int *result);
  long divide(int numerator, int denominator, int *result);
};

The server_Calc object is then associated with a server capability and invoked when incoming messages are directed towards it.

Compound Interfaces

When supporting multiple interfaces, the general initialisation resembles that shown above, as in this example involving a specific object type derived from the CalcCounter type:

server_CalcCounter obj;

Similarly, a definition of this type is required supporting the range of operations associated with all of the individual interfaces:

class server_CalcCounter : public CalcCounter
{
  int counter = 0;

public:
  long add(int left, int right, int *result);
  long subtract(int left, int right, int *result);
  long multiply(int left, int right, int *result);
  long divide(int numerator, int denominator, int *result);
  long increment(int *result);
};

Operation Implementations

Since C++ manages access to object state transparently, the way of accessing such state is more straightforward:

long server_CalcCounter::increment(int *result)
{
  counter = counter + 1;
  *result = counter;
  return L4_EOK;
}

Exposing Objects as Servers

In L4Re, component objects can be made available to other programs by associating them with interprocess communication (IPC) "gate" capabilities and then waiting for incoming messages.

Given an existing capability accessible via the program environment, a component can be exposed to other programs as follows:

ipc_server_bind("server", (l4_umword_t) &obj, &server);

Here, the server variable is of type l4_cap_idx_t (indicating a capability). The named capability is obtained and assigned to the variable, with the object itself presented to label incoming messages, indicating which IPC gate was involved in delivering the message.

The details of waiting for messages, testing the label, and directing requests to the component are dealt with by another convenience function:

ipc_server_loop(Calc_expected_items, &obj, (ipc_server_handler_type) handle_Calc);

Here, the following parameters are specified:

Example Code

The pkg/idl4re-examples directory contains some example packages for L4Re that feature some of the above code and demonstrate the techniques involved. The idl4re-examples directory can be copied into the L4Re distribution's pkg directory and built as follows:

make O=mybuild S=pkg/idl4re-examples

The mybuild directory name should be adjusted to match your own choice of build output directory.