Client/Server Communication

Client/Server Communication#

This section is mainly about the IPC framework that is available within the core of L4Re. The task of the IPC framework is to provide convenient functionality to write and use RPC-based communication, i.e. between a client and a server.

In this terminology, a client is using a service provided by a server. Using a service means to exchange a message with the server, i.e., a client sending a message to the server and receiving an answer message from the server.

The L4Re IPC framework employs C++ techniques to provide all the code for both client-side and server-side usage. Based on interface definitions it provides code from marshalling and unmarshalling the data to be transferred as well as client side function stubs and server side dispatching functionality.

The interfaces provided by a server are specified in C++ and added annotation provided by the L4Re IPC framework.

For the purpose of describing the mechanisms we use a simple example where a calc server provides two calculation function for clients.

The interface definition looks like this:

struct Calc : L4::Kobject_t<Calc, L4::Kobject, 0x44>
{
  L4_INLINE_RPC(int, sub, (l4_uint32_t a, l4_uint32_t b, l4_uint32_t *res));
  L4_INLINE_RPC(int, neg, (l4_uint32_t a, l4_uint32_t *res));
  typedef L4::Typeid::Rpcs<sub_t, neg_t> Rpcs;
};

Here we see that the Calc interface has two functions: sub and neg that take parameters. Values are returned via res. The int return values of the individual functions are used for return codes.

A client can use those interfaces like this, in a bare minimum variant:

uint32_t result;
server->sub(8, 5, &result);

While overall it looks natural, calling a function with some parameters, a questions remains: Where does ‘server’ come from?

server is a capability to points to the server. The client program needs to get this capability from the environment, i.e., it needs to be made available through the client program startup.

In L4Re capabilities from the environment are named, and thus we can get the capability like this:

L4::Cap<Calc> server = L4Re::Env::env()->get_cap<Calc>("calc_server");

Here we ask the client’s environment to put the capability named calc_server into the variable server.

Putting this together, and adding error checking, a whole block of code could look like this:

L4::Cap<Calc> server = L4Re::Env::env()->get_cap<Calc>("calc_server");
if (!server.is_valid())
  {
    printf("Could not get server capability!\n");
    return 1;
  }

uint32_t result;
if (server->sub(8, 5, &result))
  {
    printf("Error talking to server\n");
    return 1;
  }
printf("Result of subtract call: %d\n", result);

Now to the server side. Let us first look at the implementation of the function’s functionality. The sub and neg calls need functions on the server side that are called upon the client’s request, that get the parameters, and return a result. This looks like this:

class Calculation_server : public L4::Epiface_t<Calculation_server, Calc>
{
public:
  int op_sub(Calc::Rights, l4_uint32_t a, l4_uint32_t b, l4_uint32_t &res)
  {
    res = a - b;
    return 0;
  }

  int op_neg(Calc::Rights, l4_uint32_t a, l4_uint32_t &res)
  {
    res = -a;
    return 0;
  }
};

The functions are implemented in the scope of a Calculation_server call and are prefixed with op_. The class is derived from Calc, which defines the interfaces.

Next, we need to hook up the Calculation_server class into a server loop. A server loop is the core way of working for a server. It waits from client requests, serves them by dispatching to the class’s op_-functions, and waits again. This is a server loop.

A server loop is defined and instantiated like this:

L4Re::Util::Registry_server<> server;
Calculation_server calc;

// Register calculation server
if (!server.registry()->register_obj(&calc, "calc_server").is_valid())
  {
    printf("Could not register my service, is there a 'calc_server' in the caps table?\n");
    return 1;
  }

server.loop();

This code block instantiates a Calculation_server object and a server object, registers the calc object with the framework and connects it to the IPC channel named calc_server.

It finally enters the server loop. Now, a client sending a request will be dispatched to the Calculation_server object, one of the op_-function will be called and the result will be returned.