genode/os/doc/init.txt

348 lines
15 KiB
Plaintext
Raw Normal View History

2011-12-22 15:19:25 +00:00
======================================
Configuring the init process of Genode
======================================
Norman Feske
The Genode architecture facilitates the flexible construction of complex usage
scenarios out of Genode's process nodes 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 process 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 process nodes, 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 process. The configuration is described in a single XML file
called 'config' supplied via core's ROM service.
Configuration
#############
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 processes 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="CAP"/>
! <service name="LOG"/>
! <service name="SIGNAL"/>
2011-12-22 15:19:25 +00:00
! </parent-provides>
! <start name="timer">
! <resource name="RAM" quantum="1M"/>
! <provides> <service name="Timer"/> </provides>
! <route>
! <service name="CAP"> <parent/> </service>
! <service name="SIGNAL"> <parent/> </service>
2011-12-22 15:19:25 +00:00
! </route>
! </start>
! <start name="test-timer">
! <resource name="RAM" quantum="1M"/>
! <route>
! <service name="Timer"> <child name="timer"/> </service>
! <service name="LOG"> <parent/> </service>
! <service name="SIGNAL"> <parent/> </service>
2011-12-22 15:19:25 +00:00
! </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
CAP service and 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. To
implement this service, the timer requires a CAP session. In the routing table,
we define that a CAP session request gets delegated to init's parent. The
second process 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>
! <service name="SIGNAL"> <parent/> </service>
2011-12-22 15:19:25 +00:00
! </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 processes 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.
Advanced features
#################
In addition to the service routing facility described in the previous section,
the following features are worth noting:
Resource quota saturation
=========================
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.
Multiple instantiation of a single ELF binary
=============================================
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:
! <binary name="filename"/>
This way, the unique child names can be chosen independently from the
binary file name.
Nested configuration
====================
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="CAP"/>
! <service name="LOG"/>
! <service name="ROM"/>
! <service name="RAM"/>
! <service name="CPU"/>
! <service name="RM"/>
! <service name="PD"/>
! <service name="SIGNAL"/>
2011-12-22 15:19:25 +00:00
! </parent-provides>
! <start name="timer">
! <resource name="RAM" quantum="1M"/>
! <provides><service name="Timer"/></provides>
! <route>
! <service name="CAP"> <parent/> </service>
! <service name="SIGNAL"> <parent/> </service>
2011-12-22 15:19:25 +00:00
! </route>
! </start>
! <start name="init">
! <resource name="RAM" quantum="1M"/>
! <config>
! <parent-provides>
! <service name="Timer"/>
! <service name="LOG"/>
! <service name="SIGNAL"/>
2011-12-22 15:19:25 +00:00
! </parent-provides>
! <start name="test-timer">
! <resource name="RAM" quantum="1M"/>
! <route>
! <service name="Timer"> <parent/> </service>
! <service name="LOG"> <parent/> </service>
! <service name="SIGNAL"> <parent/> </service>
2011-12-22 15:19:25 +00:00
! </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="CAP"> <parent/> </service>
! <service name="CPU"> <parent/> </service>
! <service name="RM"> <parent/> </service>
! <service name="PD"> <parent/> </service>
! <service name="SIGNAL"> <parent/> </service>
2011-12-22 15:19:25 +00:00
! </route>
! </start>
! </config>
The services ROM, RAM, CPU, RM, and PD are required by the second-level
init instance to create the timer-test process.
As illustrated by this example, the use of the nested configuration feature
enables the construction of arbitrarily complex process 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>
Assigning subsystems to CPUs
============================
The assignment of subsystems to CPU nodes consists of two parts, the
definition of the affinity space dimensions as used for the init process, 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>
2011-12-22 15:19:25 +00:00
Priority support
================
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
process, 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).
Verbosity
=========
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.
Executing children in chroot environments on Linux
==================================================
On the Linux base platform, each process started by init can be assigned to
a chroot environment by specifying the new root location as 'root' attribute
to the corresponding '<start>' node. Root environments can be nested. The
root path of a nested init instance will be appended to the root path of
the outer instance.
When using the chroot mechanism, core will mirror the current working
directory within the chroot environment via the a bind mount operation. This
step is needed to enable execve to obtain the ELF binary of the new process.
In order to use the chroot mechanism when starting Genode's core as a non-root
user process, the core executable must be equipped with the 'CAP_SYS_ADMIN' and
'CAP_SYS_CHROOT' capabilities. 'CAP_SYS_ADMIN' is needed for bind mounting.
'CAP_SYS_CHROOT' is needed to perform the 'chroot' syscall:
! sudo setcap cap_sys_admin,cap_sys_chroot=ep core
For an example of using chroot, please refer to the 'os/run/chroot.run' script.
2011-12-22 15:19:25 +00:00
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.