Hello world

This section introduces the steps needed to create and execute a simple custom component that prints a hello-world message.

Using a custom source-code repository

In principle, it would be possible to add a new component to one of the existing source-code repositories found at <genode-dir>/repos/. However, unless the component is meant to be incorporated into upstream development of the Genode project, it is generally recommended to keep custom code separate from Genode's code base. This eases future updates to new versions of Genode and allows you to pick a revision-control system of your choice.

The new repository must appear within the <genode-dir>/repos/ directory. This can be achieved by either hosting it as a subdirectory or by creating a symbolic link that points to an arbitrary location of your choice. For now, let us host a new source-code repository called "lab" directly within the repos/ directory.

 cd <genode-dir>
 mkdir repos/lab

The lab repository will contain the source code and build rules for a single component as well as a run script for executing the component within Genode. Component source code reside in a src/ subdirectory. By convention, the src/ directory contains further subdirectories for hosting different types of components, in particular server (services and protocol stacks), drivers (hardware-device drivers), and app (applications). For the hello-world component, an appropriate location would be _src/app/hello/_:

 mkdir -p repos/lab/src/app/hello

Source code and build description

The hello/ directory contains both the source code and the build description of the component. The main part of each component typically resides in a file called main.cc. Hence, for a hello-world program, we have to create the repos/lab/src/app/hello/main.cc file with the following content:

 #include <base/component.h>
 #include <base/log.h>

 void Component::construct(Genode::Env &)
 {
   Genode::log("Hello world");
 }

The base/component.h header contains the interface each component must implement. The construct function is called by the component's execution environment to initialize the component. The interface to the execution environment is passed as argument. This interface allows the application code to interact with the outside world. The simple example above merely produces a log message. The log function is defined in the base/log.h header.

The component does not exit after the construct function returns. Instead, it becomes ready to respond to requests or signals originating from other components. The example above does not interact with other components though. Hence, it will just keep waiting infinitely.

Please note that there exists a recommended coding style for genuine Genode components. If you consider submitting your work to the upstream development of the project, please pay attention to these common guidelines.

Coding-style guidelines

https://genode.org/documentation/developer-resources/coding_style

The source file main.cc is accompanied by a build-description file called target.mk. It contains the declarations for the source files, the libraries used by the component, and the name of the component. Create the file repos/lab/src/app/hello/target.mk with the following content:

 TARGET = hello
 SRC_CC = main.cc
 LIBS  += base

Building the component

With the build-description file in place, it is time to build the new component, for example from within the x86_64 build directory as created in Section A simple system scenario. To aid the build system to find the component, we have to extend the build configuration <build-dir>/etc/build.conf by appending the following line:

 REPOSITORIES += $(GENODE_DIR)/repos/lab

By adding this line, the build system will consider our custom source-code repository. To build the component, issue the following command:

 make app/hello

This step compiles the main.cc file and links the executable ELF binary called "hello". The result can be found in the <build-dir>/app/hello/ subdirectory.

Defining a system scenario

For testing the component, we need to define a system scenario that incorporates the component. As mentioned in Section A simple system scenario, such a description has the form of a run script. To equip the lab repository with a run script, we first need to create a lab/run/ subdirectory:

 mkdir <genode-dir>/repos/lab/run

Within this directory, we create the file <genode-dir>/repos/lab/run/hello.run with the following content:

build "core init app/hello"
create_boot_directory
install_config {
 <config>
   <parent-provides>
     <service name="LOG"/>
     <service name="PD"/>
     <service name="CPU"/>
     <service name="ROM"/>
   </parent-provides>
   <default-route>
     <any-service> <parent/> </any-service>
   </default-route>
   <default caps="100"/>
   <start name="hello">
     <resource name="RAM" quantum="10M"/>
   </start>
 </config>
}
build_boot_image "core ld.lib.so init hello"
append qemu_args "-nographic -m 64"
run_genode_until {Hello world.*\n} 10

This run script performs the following steps:

  1. It builds the components core, init, and app/hello.

  2. It creates a fresh boot directory at <build-dir>/var/run/hello. This directory contains all files that will end up in the final boot image.

  3. It creates a configuration for the init component. The configuration starts the hello component as the only child of init. Session requests originating from the hello component will always be directed towards the parent of init, which is core. The <default> node declares that each component may consume up to 100 capabilities.

  4. It assembles a boot image with the executable ELF binaries core, ld.lib.so (the dynamic linker), init, and hello. The binaries are picked up from the <build-dir>/bin/ subdirectory.

  5. It instructs Qemu (if used) to disable the graphical output.

  6. It triggers the execution of the system scenario and watches the log output for the given regular expression. The execution ends when the log output appears or after a timeout of 10 seconds.

The run script can be executed from within the build directory via the command:

 make run/hello KERNEL=linux

After the boot output of the used kernel, the scenario will produce the following output:

 [init -> test-printf] Hello world

 Run script execution successful.

The label within the brackets at the start of each line identifies the component where the message originated from. The final line is printed by the run tool after it successfully matched the log output against the regular expression specified to the run_genode_until command.

Responding to external events

Most non-trivial components respond to external events such as user input, timer events, device interrupts, the arrival of new data, or RPC requests issued by other components.

The following example presents the typical skeleton of such a component. The construct function merely creates an object representing the application as a static local variable. The actual component code lives inside the Main class.

 #include <base/component.h>
 #include <base/log.h>
 #include <timer_session/connection.h>

 namespace Hello { struct Main; }

 struct Hello::Main
 {
   Genode::Env &_env;

   Timer::Connection _timer { _env };

   void _handle_timeout()
   {
     log("woke up at ", _timer.elapsed_ms(), " ms");
   }

   Genode::Signal_handler<Main> _timeout_handler {
     _env.ep(), *this, &Main::_handle_timeout };

   Main(Genode::Env &env) : _env(env)
   {
     _timer.sigh(_timeout_handler);
     _timer.trigger_periodic(1000*1000);
     Genode::log("component constructed");
   }
 };

 void Component::construct(Genode::Env &env)
 {
   static Hello::Main main(env);
 }

First, note the Hello namespace. As a good practice, component code typically lives in a namespace. The component-specific namespace may incorporate other namespaces - in particular the Genode namespace - without polluting the global scope.

The constructor of the Main object takes the Genode environment as argument and stores it as the reference member variable _env. The member variable is prefixed with an underscore to highlight the fact that it is private to the Main class. In principle, Main could be a class with _env being part of the private section, but as Main is the top-level class of the component that is not accessed by any other parts of the program, we use a struct for brevity while still maintaining the convention to prefix private members with an underscore character. When spotting the use of such a prefixed variable in the code, we immediately see that it is part of the code's object context, not being an argument or a local variable.

By aggregating a Timer::Connection as a member variable, the Main object requests a session to a timer service at construction time. As this session request requires an interaction with the outside world, the _env needs to be passed to the _timer constructor.

In order to respond to events from the timer, the Main class hosts a _timeout_handler object. Its constructor arguments refer to the object and a method to be executed whenever an event occurs. The timeout handler object is registered at the _timer as the recipient of timeout events via the sigh method. Finally, the timer is instructed to trigger timeout events at a rate of 1 second.

The following remarks are worth noting:

To execute the new version of the component, we need to slightly modify the run script.

build "core init drivers/timer app/hello"
create_boot_directory
install_config {
 <config>
   <parent-provides>
     <service name="LOG"/>
     <service name="PD"/>
     <service name="CPU"/>
     <service name="ROM"/>
   </parent-provides>
   <default-route>
     <any-service> <parent/> <any-child/> </any-service>
   </default-route>
   <default caps="100"/>
   <start name="timer">
     <resource name="RAM" quantum="1M"/>
     <provides> <service name="Timer"/> </provides>
   </start>
   <start name="hello">
     <resource name="RAM" quantum="10M"/>
   </start>
 </config>
}
build_boot_image "core ld.lib.so init timer hello"
append qemu_args "-nographic -m 64"
run_genode_until forever

The modifications are as follows:

When starting the run script, we can observe the periodic activation of the component in the log output:

[init] child "timer" announces service "Timer"
[init -> hello] component constructed
[init -> hello] woke up at 12 ms
[init -> hello] woke up at 1008 ms
[init -> hello] woke up at 2005 ms
...