Configuring the init component of Genode
The Genode architecture facilitates the flexible construction of complex usage scenarios out of Genode's components used as generic building blocks. Thanks to the strictly hierarchic and, at the same time, recursive structure of Genode, a parent has full control over the way, its children interact with each other and with the parent. The init component plays a special role in that picture. At boot time, it gets started by core, gets assigned all physical resources, and controls the execution of all further components, which can be further instances of init. Init's policy is driven by a configuration file, which declares a number of children, their relationships, and resource assignments. This document describes the configuration mechansism to steer the policy of the init component. The configuration is described in a single XML file called config supplied via core's ROM service.
At the parent-child interface, there are two operations that are subject to policy decisions of the parent, the child announcing a service and the child requesting a service. If a child announces a service, the parent is up to decide if and how to make this service accessible to its other children. When a child requests a service, the parent may deny the session request, delegate the request to its own parent, implement the requested service locally, or open a session at one of its other children. This decision may depend on the requested service or session-construction arguments provided by the child. Apart from assigning resources to children, the central element of the policy implemented in the parent is a set of rules to route session requests. Therefore, init's configuration concept is laid out around components and the routing of session requests. The concept is best illustrated by an example (the following config file can be used on Linux):
<config> <parent-provides> <service name="LOG"/> </parent-provides> <start name="timer"> <resource name="RAM" quantum="1M"/> <provides> <service name="Timer"/> </provides> </start> <start name="test-timer"> <resource name="RAM" quantum="1M"/> <route> <service name="Timer"> <child name="timer"/> </service> <service name="LOG"> <parent/> </service> </route> </start> </config>
First, there is the declaration of services provided by the parent of the configured init instance. In this case, we declare that the parent provides a LOG service. For each child to start, there is a <start> node describing resource assignments, declaring services provided by the child, and holding a routing table for session requests originating from the child. The first child is called "timer" and implements the "Timer" service. The second component called "test-timer" is a client of the timer service. In its routing table, we see that requests for "Timer" sessions should be routed to the "timer" child whereas requests for "LOG" sessions should be delegated to init's parent. Per-child service routing rules provide a flexible way to express arbitrary client-server relationships. For example, service requests may be transparently mediated through special policy components acting upon session-construction arguments. There might be multiple children implementing the same service, each addressed by different routing tables. If there is no valid route to a requested service, the service is denied. In the example above, the routing tables act effectively as a whitelist of services the child is allowed to use.
In practice, usage scenarios become more complex than the basic example, increasing the size of routing tables. Furthermore, in many practical cases, multiple children may use the same set of services, and require duplicated routing tables within the configuration. In particular during development, the elaborative specification of routing tables tend to become an inconvenience. To alleviate this problem, there are two mechanisms, wildcards and a default route. Instead of specifying a list of single service routes targeting the same destination, the wildcard <any-service> becomes handy. For example, instead of specifying
<route> <service name="ROM"> <parent/> </service> <service name="RAM"> <parent/> </service> <service name="RM"> <parent/> </service> <service name="PD"> <parent/> </service> <service name="CPU"> <parent/> </service> </route>
the following shortcut can be used:
<route> <any-service> <parent/> </any-service> </route>
The latter version is not as strict as the first one because it permits the child to create sessions at the parent, which were not whitelisted in the elaborative version. Therefore, the use of wildcards is discouraged for configuring untrusted components. Wildcards and explicit routes may be combined as illustrated by the following example:
<route> <service name="LOG"> <child name="nitlog"/> </service> <any-service> <parent/> </any-service> </route>
The routing table is processed starting with the first entry. If the route matches the service request, it is taken, otherwise the remaining routing-table entries are visited. This way, the explicit service route of "LOG" sessions to "nitlog" shadows the LOG service provided by the parent.
To emulate the traditional init policy, which allowed a child to use services provided by arbitrary other children, there is a further wildcard called <any-child>. Using this wildcard, such a policy can be expressed as follows:
<route> <any-service> <parent/> </any-service> <any-service> <any-child/> </any-service> </route>
This rule would delegate all session requests referring to one of the parent's services to the parent. If no parent service matches the session request, the request is routed to any child providing the service. The rule can be further reduced to:
<route> <any-service> <parent/> <any-child/> </any-service> </route>
Potential ambiguities caused by multiple children providing the same service are detected automatically. In this case, the ambiguity must be resolved using an explicit route preceding the wildcards.
To reduce the need to specify the same routing table for many children in one configuration, there is a <default-route> mechanism. The default route is declared within the <config> node and used for each <start> entry with no <route> node. In particular during development, the default route becomes handy to keep the configuration tidy and neat.
The combination of explicit routes and wildcards is designed to scale well from being convenient to use during development towards being highly secure at deployment time. If only explicit rules are present in the configuration, the permitted relationships between all components are explicitly defined and can be easily verified. Note however that the degree those rules are enforced at the kernel-interface level depends on the used base platform.
In addition to the service routing facility described in the previous section, the following features are worth noting:
If a specified resource (i.e., RAM quota) exceeds the available resources. The available resources are assigned completely to the child. This makes it possible to assign all remaining resources to the last child by simply specifying an overly large quantum.
Each <start> node requires a unique name attribute. By default, the value of this attribute is used as file name for obtaining the ELF binary at the parent's ROM service. If multiple instances of the same ELF binary are needed, the binary name can be explicitly specified using a <binary> sub node of the <start> node:
This way, the unique child names can be chosen independently from the binary file name.
Each <start> node can host a <config> sub node. The content of this sub node is provided to the child when a ROM session for the file name "config" is requested. Thereby, arbitrary configuration parameters can be passed to the child. For example, the following configuration starts timer-test within an init instance within another init instance. To show the flexibility of init's service routing facility, the "Timer" session of the second-level timer-test child is routed to the timer service started at the first-level init instance.
<config> <parent-provides> <service name="LOG"/> <service name="ROM"/> <service name="RAM"/> <service name="CPU"/> <service name="RM"/> <service name="PD"/> </parent-provides> <start name="timer"> <resource name="RAM" quantum="1M"/> <provides><service name="Timer"/></provides> </start> <start name="init"> <resource name="RAM" quantum="1M"/> <config> <parent-provides> <service name="Timer"/> <service name="LOG"/> </parent-provides> <start name="test-timer"> <resource name="RAM" quantum="1M"/> <route> <service name="Timer"> <parent/> </service> <service name="LOG"> <parent/> </service> </route> </start> </config> <route> <service name="Timer"> <child name="timer"/> </service> <service name="LOG"> <parent/> </service> <service name="ROM"> <parent/> </service> <service name="RAM"> <parent/> </service> <service name="CPU"> <parent/> </service> <service name="RM"> <parent/> </service> <service name="PD"> <parent/> </service> </route> </start> </config>
The services ROM, RAM, CPU, RM, and PD are required by the second-level init instance to create the timer-test component.
As illustrated by this example, the use of the nested configuration feature enables the construction of arbitrarily complex component trees via a single configuration file.
Alternatively to specifying all nested configurations in a single config file, any sub configuration can be placed in a separate file specified via the configfile node. For example:
<start name="nitpicker"> <resource name="RAM" quantum="1M"/> <configfile name="nitpicker.config"/> </start>
The assignment of subsystems to CPU nodes consists of two parts, the definition of the affinity space dimensions as used for the init component, and the association sub systems with affinity locations (relative to the affinity space). The affinity space is configured as a sub node of the config node. For example, the following declaration describes an affinity space of 4x2:
<config> ... <affinity-space width="4" height="2" /> ... </config>
Subsystems can be constrained to parts of the affinity space using the <affinity> sub node of a <start> entry:
<config> ... <start name="loader"> <affinity xpos="0" ypos="1" width="2" height="1" /> ... </start> ... </config>
The number of CPU priorities to be distinguished by init can be specified with prio_levels attribute of the <config> node. The value must be a power of two. By default, no priorities are used. To assign a priority to a child component, a priority value can be specified as priority attribute of the corresponding <start> node. Valid priority values lie in the range of
-prio_levels + 1 (maximum priority degradation) to 0 (no priority degradation).
To ease the debugging, init can be directed to print various status information as LOG output. To enable the verbose mode, assign the value "yes" to the verbose attribute of the <config> node.
A component can notify its parent about its graceful exit via the exit RPC function of the parent interface. By default, init responds to such a notification from one of its children by merely printing a log message but ignores it otherwise. However, there are scenarios where the exit of a particular child should result in the exit of the entire init component. To propagate the exit of a child to the parent of init, start nodes can host the optional sub node <exit> with the attribute propagate set to "yes".
<config> <start name="noux"> <exit propagate="yes"/> ... </start> </config>
The exit value specified by the exiting child is forwarded to init's parent.
Using the configuration concept
To get acquainted with the configuration format, there are two example configuration files located at os/src/init/, which are both ready-to-use with the Linux version of Genode. Both configurations produce the same scenario but they differ in the way policy is expressed. The explicit_routing configuration is an example for the elaborative specification of all service routes. All service requests not explicitly specified are denied. So this policy is a whitelist enforcing mandatory access control on each session request. The example illustrates well that such a elaborative specification is possible in an intuitive manner. However, it is pretty comprehensive. In cases where the elaborative specification of service routing is not fundamentally important, in particular during development, the use of wildcards can help to simplify the configuration. The wildcard example demonstrates the use of a default route for session-request resolution and wildcards. This variant is less strict about which child uses which service. For development, its simplicity is beneficial but for deployment, we recommend to remove wildcards (<default-route>, <any-child>, and <any-service>) altogether. The absence of such wildcards is easy to check automatically to ensure that service routes are explicitly whitelisted.
Further configuration examples can be found in the os/config/ directory.