Creating your first Genode application Björn Döbel and Norman Feske Abstract ######## This section will give you a step-by-step introduction for writing your first little client-server application using the Genode OS Framework. We will create a server that provides two functions to its clients and a client that uses these functions. The code samples in this section are not necessarily complete. You can download the complete tutorial source code from the link at the bottom of this page. Prerequisites ############# We assume that you know how to write code and have read: Norman Feske and Christian Helmuth: "Design of the Genode OS Architecture", _TU Dresden technical report TUD-FI06-07, Dresden, Germany, December 2006_. [http://genode-labs.com/publications/bastei-design-2006.pdf] so that you have a basic understanding of what Genode is and how things work. Of course, you will also need to check out Genode before going any further. Setting up the build environment ################################ The Genode build system enables developers to create software in different repositories that don't need to interfere with the rest of the Genode tree. We will do this for our example now. In the Genode root directory, we create the following subdirectory structure: ! hello_tutorial ! hello_tutorial/include ! hello_tutorial/include/hello_session ! hello_tutorial/src ! hello_tutorial/src/hello ! hello_tutorial/src/hello/server ! hello_tutorial/src/hello/client In the remaining document when referring to non-absolute directories, these are local to 'hello_tutorial'. Now we tell the Genode build system, that there is a new repository. Therefore we add the path to our new repository to 'build/etc/build.conf': ! REPOSITORIES += /path/to/your/hello_tutorial Later we will place build description files into the tutorial subdirectories so that the build system can figure out what is needed to build your applications. You can then build these apps from the 'build' directory using one of the following commands: ! make hello ! make hello/server ! make hello/client The first command builds both the client and the server whereas the latter two commands build only the specific target respectively. Defining an interface ##################### In our example we are going to implement a server providing two functions: :'void say_hello()': makes the server print "Hello world." :'int add(int a, int b)': adds two integers and returns the result. The interface of a Genode service is called a _session_. We will define it as a C++ class in 'include/hello_session/hello_session.h' !#include !#include ! !namespace Hello { ! ! struct Session : public 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); ! }; !} As a good practice, we place the Hello service into a dedicated namespace. The _Hello::Session_ class defines the public interface for our service as well as the meta information that Genode needs to perform remote procedure calls (RPC) accross process boundaries. Furthermore, we use the interface to specify the name of the service by using the 'service_name' function. This function will later be used by both the server for announcing the service at its parent and the client for requesting the creation of a "Hello" session. The 'GENODE_RPC' macro is used to declare an RPC function. Its first argument is a type name that is used to refer to the RPC function. The type name can be choosen freely. However, it is a good practice to prefix the type name with 'Rpc_'. The remaining arguments are the return type of the RPC function, the server-side name of the RPC implementation, and the function arguments. The 'GENODE_RPC_INTERFACE' macros declares the list of RPC functions that the RPC interface is comprised of. Under the hood, the 'GENODE_RPC*' macros enrich the compound class with the type information used to automatically generate the RPC communication code at compile time. They do not add any members to the 'Session' struct. Writing server code ################### Now let's write a server providing the interface defined by _Hello::Session_. We will put all of this code in 'src/hello/server/main.cc' Implementing the server side ============================ We place the implementation of the session interface into a class called 'Session_component' derived from the 'Rpc_object' class template. By instantiating this template class with the session interface as argument, the 'Session_component' class gets equipped with the communication code that will make the server's functions accessible via RPC. !#include !#include !#include ! !namespace Hello { ! ! struct Session_component : Genode::Rpc_object ! { ! void say_hello() { ! PDBG("I am here... Hello."); } ! ! int add(int a, int b) { ! return a + b; } ! }; !} Getting ready to start ====================== The server component won't help us much as long as we don't use it in a server application. Starting a service with Genode works as follows: * Open a CAP session to our parent, so that we are able to create capabilities. * Create and announce a root capability to our parent. * When a client requests our service, the parent invokes the root capability to create session objects and session capabilities. These are then used by the client to communicate with the server. The class 'Hello::Root_component' is derived from Genode's 'Root_component' class template. This class defines a '_create_session' method which is called each time a client wants to establish a connection to the server. This function is responsible for parsing the parameter string the client hands over to the server and creating a 'Hello::Session_component' object from these parameters. !#include !#include ! !namespace Hello { ! ! class Root_component : public Genode::Root_component ! { ! protected: ! ! Session_component *_create_session(const char *args) ! { ! PDBG("creating hello session."); ! return new (md_alloc()) Session_component(); ! } ! ! public: ! ! Root_component(Genode::Rpc_entrypoint *ep, ! Genode::Allocator *allocator) ! : Genode::Root_component(ep, allocator) ! { ! PDBG("Creating root component."); ! } ! }; !} Now we only need a main method that announces the service to our parent: !#include !#include ! !using namespace Genode; ! !int main(void) !{ ! /* ! * Get a session for the parent's capability service, so that we ! * are able to create capabilities. ! */ ! Cap_connection cap; ! ! /* ! * A sliced heap is used for allocating session objects - thereby we ! * can release objects separately. ! */ ! static Sliced_heap sliced_heap(env()->ram_session(), ! env()->rm_session()); ! ! /* ! * Create objects for use by the framework. ! * ! * An 'Rpc_entrypoint' is created to announce our service's root ! * capability to our parent, manage incoming session creation ! * requests, and dispatch the session interface. The incoming RPC ! * requests are dispatched via a dedicated thread. The 'STACK_SIZE' ! * argument defines the size of the thread's stack. The additional ! * string argument is the name of the entry point, used for ! * debugging purposes only. ! */ ! enum { STACK_SIZE = 4096 }; ! static Rpc_entrypoint ep(&cap, STACK_SIZE, "hello_ep"); ! ! static Hello::Root_component hello_root(&ep, &sliced_heap); ! env()->parent()->announce(ep.manage(&hello_root)); ! ! /* ! * We are done with this and only act upon client requests now. ! */ ! sleep_forever(); ! ! return 0; !} Making it fly ============= In order to run our application, we need to perform two more steps: Tell the Genode build system that we want to build 'hello_server'. Therefore we create a 'target.mk' file in 'src/hello/server': ! TARGET = hello_server ! SRC_CC = main.cc ! LIBS = base To tell the init process to start the new program, we have to add a '' entry to init's 'config' file, which is located at 'build/bin/config'. ! ! ! ! ! ! ! ! ! ! ! ! ! ! For information about the configuring the init process, please refer to [http://genode.org/documentation/developer-resources/init]. Now rebuild 'core', 'init', and 'hello/server', go to 'build/bin', run './core'. Writing client code ################### In the next part we are going to have a look at the client-side implementation. The most basic steps here are: * Get a capability for the "Hello" service from our parent * Invoke RPCs via the obtained capability A client object =============== We will encapsulate the Genode IPC interface in a 'Hello::Session_client' class. This class derives from 'Hello:Session' and implements a client-side object. Therefore edit 'include/hello_session/client.h': !#include !#include !#include ! !namespace Hello { ! ! struct Session_client : Genode::Rpc_client ! { ! Session_client(Genode::Capability cap) ! : Genode::Rpc_client(cap) { } ! ! void say_hello() ! { ! PDBG("Saying Hello."); ! call(); ! } ! ! int add(int a, int b) ! { ! return call(a, b); ! } ! }; !} A 'Hello::Session_client' object takes a 'Capability' as constructor argument. This capability is tagged with the session type and gets passed to the inherited 'Rpc_client' class. This class contains the client-side communication code via the 'call' template function. The template argument for 'call' is the RPC type as declared in the session interface. Client implementation ===================== The client-side implementation using the 'Hello::Session_client' object is pretty straightforward. We request a capability for the Hello service from our parent. This call blocks as long as the service has not been registered at the parent. Afterwards, we create a 'Hello::Session_client' object with it and invoke calls. In addition, we use the Timer service that comes with Genode. This server enables us to sleep for a certain amount of milliseconds. Put this code into 'src/hello/client/main.cc': !#include !#include !#include !#include ! !using namespace Genode; ! !int main(void) !{ ! Capability h_cap = ! env()->parent()->session("foo, ram_quota=4K"); ! ! Hello::Session_client h(h_cap); ! ! Timer::Connection timer; ! ! while (1) { ! h.say_hello(); ! timer.msleep(1000); ! ! int foo = h.add(2,5); ! PDBG("Added 2 + 5 = %d", foo); ! timer.msleep(1000); ! } ! ! return 0; !} Compared to the creation of the Timer session, the creation of "Hello" session looks rather inconvenient and takes multiple lines of code. For this reason, it is a good practice to supply a convenience wrapper for creating sessions as used for the timer session. This wrapper is also the right place to for documenting session-construction arguments and assembling the argument string. By convention, the wrapper is called 'connection.h' and placed in the directory of the session interface. For our case, the file 'include/hello_session/connection.h' looks like this: !#include !#include ! !namespace Hello { ! ! struct Connection : Genode::Connection, Session_client ! { ! Connection() ! : ! /* create session */ ! Genode::Connection(session("foo, ram_quota=4K")), ! ! /* initialize RPC interface */ ! Session_client(cap()) { } ! }; !} With the 'Connection' class in place, we can now use Hello sessions by just instantiating 'Hello::Connection' objects and invoke functions directly on such an object. For example: !Hello::Connection hello; !int foo = hello.add(2, 5); Ready, set, go... ================= Add a 'target.mk' file with the following content to 'src/hello/client/': ! TARGET = hello_client ! SRC_CC = main.cc ! LIBS = base Extend your 'config' file as follows. # Add the 'SIGNAL' service to the '' section: ! # Add start entries for 'Timer' service and hello client: ! ! ! ! ! ! ! Build 'drivers/timer', and 'hello/client', go to 'build/bin', and run './core' again. You have now successfully implemented your first Genode client-server scenario.