mirror of
https://github.com/genodelabs/genode.git
synced 2025-02-20 09:46:20 +00:00
init: dynamic configuration
This patch lets init apply configuration changes to a running scenario in a differential way. Children are restarted if any of their session routes change, new children can be added to a running scenario, or children can deliberately be removed. Furthermore, the new version of init is able to propagate configuration changes (modifications of <config> nodes) to its children without restarting them.
This commit is contained in:
parent
bc236bbcde
commit
150c286f0e
@ -22,12 +22,13 @@
|
||||
|
||||
/* init includes */
|
||||
#include <init/verbose.h>
|
||||
#include <init/child_config.h>
|
||||
#include <init/child_policy.h>
|
||||
#include <init/report.h>
|
||||
|
||||
namespace Init {
|
||||
|
||||
class Abandonable;
|
||||
class Parent_service;
|
||||
class Buffered_xml;
|
||||
class Routed_service;
|
||||
class Name_registry;
|
||||
@ -37,8 +38,6 @@ namespace Init {
|
||||
using namespace Genode;
|
||||
using Genode::size_t;
|
||||
using Genode::strlen;
|
||||
|
||||
typedef Genode::Registered<Genode::Parent_service> Parent_service;
|
||||
}
|
||||
|
||||
|
||||
@ -226,8 +225,8 @@ namespace Init {
|
||||
|
||||
|
||||
template <typename T>
|
||||
inline Service *find_service(Registry<T> &services,
|
||||
Service::Name const &name)
|
||||
inline T *find_service(Registry<T> &services,
|
||||
Service::Name const &name)
|
||||
{
|
||||
T *service = nullptr;
|
||||
services.for_each([&] (T &s) {
|
||||
@ -253,6 +252,36 @@ namespace Init {
|
||||
}
|
||||
|
||||
|
||||
class Init::Abandonable
|
||||
{
|
||||
private:
|
||||
|
||||
bool _abandoned = false;
|
||||
|
||||
public:
|
||||
|
||||
void abandon() { _abandoned = true; }
|
||||
|
||||
bool abandoned() const { return _abandoned; }
|
||||
};
|
||||
|
||||
|
||||
class Init::Parent_service : public Genode::Parent_service, public Abandonable
|
||||
{
|
||||
private:
|
||||
|
||||
Registry<Parent_service>::Element _reg_elem;
|
||||
|
||||
public:
|
||||
|
||||
Parent_service(Registry<Parent_service> ®istry, Env &env,
|
||||
Service::Name const &name)
|
||||
:
|
||||
Genode::Parent_service(env, name), _reg_elem(registry, *this)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
class Init::Buffered_xml
|
||||
{
|
||||
private:
|
||||
@ -292,16 +321,20 @@ class Init::Buffered_xml
|
||||
/**
|
||||
* Init-specific representation of a child service
|
||||
*/
|
||||
class Init::Routed_service : public Child_service
|
||||
class Init::Routed_service : public Child_service, public Abandonable
|
||||
{
|
||||
public:
|
||||
|
||||
typedef Child_policy::Name Child_name;
|
||||
|
||||
struct Ram_accessor { virtual Ram_session_capability ram() const = 0; };
|
||||
|
||||
private:
|
||||
|
||||
Child_name _child_name;
|
||||
|
||||
Ram_accessor &_ram_accessor;
|
||||
|
||||
Registry<Routed_service>::Element _registry_element;
|
||||
|
||||
public:
|
||||
@ -316,17 +349,21 @@ class Init::Routed_service : public Child_service
|
||||
*/
|
||||
Routed_service(Registry<Routed_service> &services,
|
||||
Child_name const &child_name,
|
||||
Ram_accessor &ram_accessor,
|
||||
Id_space<Parent::Server> &server_id_space,
|
||||
Session_state::Factory &factory,
|
||||
Service::Name const &name,
|
||||
Ram_session_capability ram,
|
||||
Child_service::Wakeup &wakeup)
|
||||
:
|
||||
Child_service(server_id_space, factory, name, ram, wakeup),
|
||||
_child_name(child_name), _registry_element(services, *this)
|
||||
Child_service(server_id_space, factory, name,
|
||||
Ram_session_capability(), wakeup),
|
||||
_child_name(child_name), _ram_accessor(ram_accessor),
|
||||
_registry_element(services, *this)
|
||||
{ }
|
||||
|
||||
Child_name const &child_name() const { return _child_name; }
|
||||
|
||||
Ram_session_capability ram() const { return _ram_accessor.ram(); }
|
||||
};
|
||||
|
||||
|
||||
@ -387,6 +424,11 @@ class Init::Child : Child_policy, Child_service::Wakeup
|
||||
|
||||
Id const _id;
|
||||
|
||||
enum State { STATE_INITIAL, STATE_RAM_INITIALIZED, STATE_ALIVE,
|
||||
STATE_ABANDONED };
|
||||
|
||||
State _state = STATE_INITIAL;
|
||||
|
||||
Report_update_trigger &_report_update_trigger;
|
||||
|
||||
List_element<Child> _list_element;
|
||||
@ -525,10 +567,52 @@ class Init::Child : Child_policy, Child_service::Wakeup
|
||||
Registry<Parent_service> &_parent_services;
|
||||
Registry<Routed_service> &_child_services;
|
||||
|
||||
/**
|
||||
* Private child configuration
|
||||
*/
|
||||
Init::Child_config _config;
|
||||
struct Inline_config_rom_service : Abandonable, Dynamic_rom_session::Content_producer
|
||||
{
|
||||
typedef Local_service<Dynamic_rom_session> Service;
|
||||
|
||||
Child &_child;
|
||||
|
||||
Dynamic_rom_session _session { _child._env.ep().rpc_ep(),
|
||||
_child.ref_ram(), _child._env.rm(),
|
||||
*this };
|
||||
|
||||
Service::Single_session_factory _factory { _session };
|
||||
Service _service { _factory };
|
||||
|
||||
Inline_config_rom_service(Child &child) : _child(child) { }
|
||||
|
||||
/**
|
||||
* Dynamic_rom_session::Content_producer interface
|
||||
*/
|
||||
void produce_content(char *dst, Genode::size_t dst_len) override
|
||||
{
|
||||
Xml_node config = _child._start_node->xml().has_sub_node("config")
|
||||
? _child._start_node->xml().sub_node("config")
|
||||
: Xml_node("<config/>");
|
||||
|
||||
size_t const config_len = config.size();
|
||||
|
||||
if (config_len + 1 /* null termination */ >= dst_len)
|
||||
throw Buffer_capacity_exceeded();
|
||||
|
||||
/*
|
||||
* The 'config.size()' method returns the number of bytes of
|
||||
* the config-node content, which is not null-terminated. Since
|
||||
* 'Genode::strncpy' always null-terminates the result, the
|
||||
* last byte of the source string is not copied. Hence, it is
|
||||
* safe to add '1' to 'config_len' and thereby include the
|
||||
* last actual config-content character in the result.
|
||||
*/
|
||||
Genode::strncpy(dst, config.addr(), config_len + 1);
|
||||
}
|
||||
|
||||
void trigger_update() { _session.trigger_update(); }
|
||||
|
||||
Service &service() { return _service; }
|
||||
};
|
||||
|
||||
Constructible<Inline_config_rom_service> _config_rom_service;
|
||||
|
||||
Session_requester _session_requester;
|
||||
|
||||
@ -536,12 +620,18 @@ class Init::Child : Child_policy, Child_service::Wakeup
|
||||
* Policy helpers
|
||||
*/
|
||||
Init::Child_policy_handle_cpu_priorities _priority_policy;
|
||||
Init::Child_policy_provide_rom_file _config_policy;
|
||||
Init::Child_policy_redirect_rom_file _configfile_policy;
|
||||
Init::Child_policy_ram_phys _ram_session_policy;
|
||||
|
||||
Genode::Child _child { _env.rm(), _env.ep().rpc_ep(), *this };
|
||||
|
||||
struct Ram_accessor : Routed_service::Ram_accessor
|
||||
{
|
||||
Genode::Child &_child;
|
||||
Ram_accessor(Genode::Child &child) : _child(child) { }
|
||||
Ram_session_capability ram() const override {
|
||||
return _child.ram_session_cap(); }
|
||||
} _ram_accessor { _child };
|
||||
|
||||
/**
|
||||
* Child_service::Wakeup callback
|
||||
*/
|
||||
@ -550,6 +640,26 @@ class Init::Child : Child_policy, Child_service::Wakeup
|
||||
_session_requester.trigger_update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the policy results in the current route of the session
|
||||
*
|
||||
* This method is used to check if a policy change affects an existing
|
||||
* client session of a child, i.e., to determine whether the child must
|
||||
* be restarted.
|
||||
*/
|
||||
bool _route_valid(Session_state const &session)
|
||||
{
|
||||
try {
|
||||
Route const route =
|
||||
resolve_session_request(session.service().name(),
|
||||
session.client_label());
|
||||
|
||||
return (session.service() == route.service)
|
||||
&& (route.label == session.label());
|
||||
}
|
||||
catch (Parent::Service_denied) { return false; }
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
@ -591,11 +701,8 @@ class Init::Child : Child_policy, Child_service::Wakeup
|
||||
avail_slack_ram_quota(_env.ram().avail()), _verbose),
|
||||
_parent_services(parent_services),
|
||||
_child_services(child_services),
|
||||
_config(_env.ram(), _env.rm(), start_node),
|
||||
_session_requester(_env.ep().rpc_ep(), _env.ram(), _env.rm()),
|
||||
_priority_policy(_resources.prio_levels_log2, _resources.priority),
|
||||
_config_policy("config", _config.dataspace(), &_env.ep().rpc_ep()),
|
||||
_configfile_policy("config", _config.filename()),
|
||||
_ram_session_policy(_resources.constrain_phys)
|
||||
{
|
||||
if (_resources.ram_quota == 0)
|
||||
@ -624,13 +731,19 @@ class Init::Child : Child_policy, Child_service::Wakeup
|
||||
log(" provides service ", Cstring(name));
|
||||
|
||||
new (_alloc)
|
||||
Routed_service(child_services, this->name(),
|
||||
Routed_service(child_services, this->name(), _ram_accessor,
|
||||
_session_requester.id_space(),
|
||||
_child.session_factory(),
|
||||
name, _child.ram_session_cap(), *this);
|
||||
name, *this);
|
||||
}
|
||||
}
|
||||
catch (Xml_node::Nonexistent_sub_node) { }
|
||||
|
||||
/*
|
||||
* Construct inline config ROM service if "config" node is present.
|
||||
*/
|
||||
if (start_node.has_sub_node("config"))
|
||||
_config_rom_service.construct(*this);
|
||||
}
|
||||
|
||||
virtual ~Child()
|
||||
@ -645,6 +758,125 @@ class Init::Child : Child_policy, Child_service::Wakeup
|
||||
*/
|
||||
bool has_name(Child_policy::Name const &str) const { return str == name(); }
|
||||
|
||||
void initiate_env_ram_session()
|
||||
{
|
||||
if (_state == STATE_INITIAL) {
|
||||
_child.initiate_env_ram_session();
|
||||
_state = STATE_RAM_INITIALIZED;
|
||||
}
|
||||
}
|
||||
|
||||
void initiate_env_sessions()
|
||||
{
|
||||
if (_state == STATE_RAM_INITIALIZED) {
|
||||
|
||||
_child.initiate_env_sessions();
|
||||
|
||||
/* check for completeness of the child's environment */
|
||||
if (_verbose.enabled())
|
||||
_child.for_each_session([&] (Session_state const &session) {
|
||||
if (session.phase != Session_state::AVAILABLE)
|
||||
warning(name(), ": incomplete environment ",
|
||||
session.service().name(), " session "
|
||||
"(", session.label(), ")"); });
|
||||
|
||||
_state = STATE_ALIVE;
|
||||
}
|
||||
}
|
||||
|
||||
void abandon()
|
||||
{
|
||||
_state = STATE_ABANDONED;
|
||||
|
||||
_child_services.for_each([&] (Routed_service &service) {
|
||||
if (service.has_id_space(_session_requester.id_space()))
|
||||
service.abandon(); });
|
||||
}
|
||||
|
||||
bool abandoned() const { return _state == STATE_ABANDONED; }
|
||||
|
||||
enum Apply_config_result { MAY_HAVE_SIDE_EFFECTS, NO_SIDE_EFFECTS };
|
||||
|
||||
/**
|
||||
* Apply new configuration to child
|
||||
*
|
||||
* \throw Allocator::Out_of_memory unable to allocate buffer for new
|
||||
* config
|
||||
*/
|
||||
Apply_config_result apply_config(Xml_node start_node)
|
||||
{
|
||||
Child_policy &policy = *this;
|
||||
|
||||
if (_state == STATE_ABANDONED)
|
||||
return NO_SIDE_EFFECTS;
|
||||
|
||||
enum Config_update { CONFIG_APPEARED, CONFIG_VANISHED,
|
||||
CONFIG_CHANGED, CONFIG_UNCHANGED };
|
||||
|
||||
Config_update config_update = CONFIG_UNCHANGED;
|
||||
|
||||
/* import new start node if new version differs */
|
||||
if (start_node.size() != _start_node->xml().size() ||
|
||||
Genode::memcmp(start_node.addr(), _start_node->xml().addr(),
|
||||
start_node.size()) != 0)
|
||||
{
|
||||
/*
|
||||
* Start node changed
|
||||
*
|
||||
* Determine how the inline config is affected.
|
||||
*/
|
||||
char const * const tag = "config";
|
||||
bool const config_was_present = _start_node->xml().has_sub_node(tag);
|
||||
bool const config_is_present = start_node.has_sub_node(tag);
|
||||
|
||||
if (config_was_present && !config_is_present)
|
||||
config_update = CONFIG_VANISHED;
|
||||
|
||||
if (!config_was_present && config_is_present)
|
||||
config_update = CONFIG_APPEARED;
|
||||
|
||||
if (config_was_present && config_is_present) {
|
||||
|
||||
Xml_node old_config = _start_node->xml().sub_node(tag);
|
||||
Xml_node new_config = start_node.sub_node(tag);
|
||||
|
||||
if (Genode::memcmp(old_config.addr(), new_config.addr(),
|
||||
min(old_config.size(), new_config.size())))
|
||||
config_update = CONFIG_CHANGED;
|
||||
}
|
||||
|
||||
/* import new start node */
|
||||
_start_node.construct(_alloc, start_node);
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply change to '_config_rom_service'. This will
|
||||
* potentially result in a change of the "config" ROM route, which
|
||||
* may in turn prompt the routing-check below to abandon (restart)
|
||||
* the child.
|
||||
*/
|
||||
switch (config_update) {
|
||||
case CONFIG_UNCHANGED: break;
|
||||
case CONFIG_CHANGED: _config_rom_service->trigger_update(); break;
|
||||
case CONFIG_APPEARED: _config_rom_service.construct(*this); break;
|
||||
case CONFIG_VANISHED: _config_rom_service->abandon(); break;
|
||||
}
|
||||
|
||||
/* validate that the routes of all existing sessions remain intact */
|
||||
{
|
||||
bool routing_changed = false;
|
||||
_child.for_each_session([&] (Session_state const &session) {
|
||||
if (!_route_valid(session))
|
||||
routing_changed = true; });
|
||||
|
||||
if (routing_changed) {
|
||||
abandon();
|
||||
return MAY_HAVE_SIDE_EFFECTS;
|
||||
}
|
||||
}
|
||||
return NO_SIDE_EFFECTS;
|
||||
}
|
||||
|
||||
void report_state(Xml_generator &xml, Report_detail const &detail) const
|
||||
{
|
||||
xml.node("child", [&] () {
|
||||
@ -731,11 +963,41 @@ class Init::Child : Child_policy, Child_service::Wakeup
|
||||
Route resolve_session_request(Service::Name const &service_name,
|
||||
Session_label const &label) override
|
||||
{
|
||||
Service *service = nullptr;
|
||||
|
||||
/* check for "config" ROM request */
|
||||
if ((service = _config_policy.resolve_session_request_with_label(service_name, label)))
|
||||
return Route { *service, label };
|
||||
if (service_name == Rom_session::service_name() &&
|
||||
label.last_element() == "config") {
|
||||
|
||||
if (_config_rom_service.constructed() &&
|
||||
!_config_rom_service->abandoned())
|
||||
return Route { _config_rom_service->service(), label };
|
||||
|
||||
/*
|
||||
* \deprecated the support for the <configfile> tag will
|
||||
* be removed
|
||||
*/
|
||||
if (_start_node->xml().has_sub_node("configfile")) {
|
||||
|
||||
typedef String<50> Name;
|
||||
Name const rom =
|
||||
_start_node->xml().sub_node("configfile")
|
||||
.attribute_value("name", Name());
|
||||
|
||||
/* prevent infinite recursion */
|
||||
if (rom == "config") {
|
||||
error("configfile must not be named 'config'");
|
||||
throw Parent::Service_denied();
|
||||
}
|
||||
|
||||
return resolve_session_request(service_name,
|
||||
prefixed_label(name(), rom));
|
||||
}
|
||||
|
||||
/*
|
||||
* If there is neither an inline '<config>' nor a
|
||||
* '<configfile>' node present, we apply the regular session
|
||||
* routing to the "config" ROM request.
|
||||
*/
|
||||
}
|
||||
|
||||
/* check for "session_requests" ROM request */
|
||||
if (service_name == Rom_session::service_name()
|
||||
@ -774,9 +1036,14 @@ class Init::Child : Child_policy, Child_service::Wakeup
|
||||
|
||||
if (target.has_type("parent")) {
|
||||
|
||||
Parent_service *service = nullptr;
|
||||
|
||||
if ((service = find_service(_parent_services, service_name)))
|
||||
return Route { *service, target_label };
|
||||
|
||||
if (service && service->abandoned())
|
||||
throw Parent::Service_denied();
|
||||
|
||||
if (!service_wildcard) {
|
||||
warning(name(), ": service lookup for "
|
||||
"\"", service_name, "\" at parent failed");
|
||||
@ -790,10 +1057,16 @@ class Init::Child : Child_policy, Child_service::Wakeup
|
||||
Name server_name = target.attribute_value("name", Name());
|
||||
server_name = _name_registry.deref_alias(server_name);
|
||||
|
||||
Routed_service *service = nullptr;
|
||||
|
||||
_child_services.for_each([&] (Routed_service &s) {
|
||||
if (s.name() == Service::Name(service_name)
|
||||
&& s.child_name() == server_name)
|
||||
service = &s; });
|
||||
|
||||
if (service && service->abandoned())
|
||||
throw Parent::Service_denied();
|
||||
|
||||
if (service)
|
||||
return Route { *service, target_label };
|
||||
|
||||
@ -805,12 +1078,15 @@ class Init::Child : Child_policy, Child_service::Wakeup
|
||||
}
|
||||
|
||||
if (target.has_type("any-child")) {
|
||||
|
||||
if (is_ambiguous(_child_services, service_name)) {
|
||||
error(name(), ": ambiguous routes to "
|
||||
"service \"", service_name, "\"");
|
||||
throw Parent::Service_denied();
|
||||
}
|
||||
|
||||
Routed_service *service = nullptr;
|
||||
|
||||
if ((service = find_service(_child_services, service_name)))
|
||||
return Route { *service, target_label };
|
||||
|
||||
@ -825,21 +1101,16 @@ class Init::Child : Child_policy, Child_service::Wakeup
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
warning(name(), ": no route to service \"", service_name, "\"");
|
||||
}
|
||||
} catch (Xml_node::Nonexistent_sub_node) { }
|
||||
|
||||
if (!service)
|
||||
throw Parent::Service_denied();
|
||||
|
||||
return Route { *service };
|
||||
warning(name(), ": no route to service \"", service_name, "\"");
|
||||
throw Parent::Service_denied();
|
||||
}
|
||||
|
||||
void filter_session_args(Service::Name const &service,
|
||||
char *args, size_t args_len) override
|
||||
{
|
||||
_priority_policy. filter_session_args(service.string(), args, args_len);
|
||||
_configfile_policy. filter_session_args(service.string(), args, args_len);
|
||||
_ram_session_policy.filter_session_args(service.string(), args, args_len);
|
||||
}
|
||||
|
||||
@ -923,6 +1194,8 @@ class Init::Child : Child_policy, Child_service::Wakeup
|
||||
{
|
||||
_report_update_trigger.trigger_report_update();
|
||||
}
|
||||
|
||||
bool initiate_env_sessions() const override { return false; }
|
||||
};
|
||||
|
||||
#endif /* _INCLUDE__INIT__CHILD_H_ */
|
||||
|
@ -14,6 +14,8 @@
|
||||
#ifndef _INCLUDE__INIT__CHILD_CONFIG_H_
|
||||
#define _INCLUDE__INIT__CHILD_CONFIG_H_
|
||||
|
||||
#warning header is deprecated, used os/dynamic_rom_session.h instead
|
||||
|
||||
#include <util/xml_node.h>
|
||||
#include <base/attached_dataspace.h>
|
||||
#include <ram_session/ram_session.h>
|
||||
|
@ -49,7 +49,7 @@ append config {
|
||||
<config>
|
||||
|
||||
<!-- let init settle, processing the initially invalid config -->
|
||||
<sleep ms="250"/>
|
||||
<sleep ms="150"/>
|
||||
|
||||
|
||||
<message string="test state reporting"/>
|
||||
@ -89,6 +89,151 @@ append config {
|
||||
</node>
|
||||
</expect_init_state>
|
||||
|
||||
|
||||
<message string="routing to custom log service"/>
|
||||
|
||||
<init_config version="chained log services">
|
||||
<report ids="yes" requested="yes" provided="yes"/>
|
||||
<parent-provides>
|
||||
<service name="ROM"/>
|
||||
<service name="RAM"/>
|
||||
<service name="CPU"/>
|
||||
<service name="PD"/>
|
||||
<service name="LOG"/>
|
||||
</parent-provides>
|
||||
<start name="server">
|
||||
<binary name="dummy"/>
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<provides> <service name="LOG"/> </provides>
|
||||
<config> <log_service/> </config>
|
||||
<route> <any-service> <parent/> </any-service> </route>
|
||||
</start>
|
||||
<start name="indirect_server">
|
||||
<binary name="dummy"/>
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<provides> <service name="LOG"/> </provides>
|
||||
<config> <log_service/> </config>
|
||||
<route>
|
||||
<service name="LOG"> <child name="server"/> </service>
|
||||
<any-service> <parent/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
<start name="client">
|
||||
<binary name="dummy"/>
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<config> <log string="client started"/> </config>
|
||||
<route>
|
||||
<service name="LOG"> <child name="indirect_server"/> </service>
|
||||
<any-service> <parent/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
</init_config>
|
||||
<expect_log string="[init -> server] [indirect_server] [client] client started"/>
|
||||
<sleep ms="200"/>
|
||||
<expect_init_state>
|
||||
<node name="child">
|
||||
<attribute name="name" value="server"/>
|
||||
<attribute name="id" value="2"/>
|
||||
</node>
|
||||
<node name="child">
|
||||
<attribute name="name" value="client"/>
|
||||
<attribute name="id" value="4"/>
|
||||
</node>
|
||||
</expect_init_state>
|
||||
<sleep ms="150"/>
|
||||
|
||||
|
||||
<message string="changing route of indirect server"/>
|
||||
|
||||
<!-- Because the route to the LOG session of the 'indirect_server'
|
||||
is re-directed to the parent, the 'indirect_server' must be
|
||||
restarted. As the 'client' depends on the 'indirect_server'
|
||||
for its LOG session, the client must implicitly be restarted
|
||||
as well. -->
|
||||
|
||||
<init_config version="restarted indirect server">
|
||||
<report ids="yes" requested="yes" provided="yes"/>
|
||||
<parent-provides>
|
||||
<service name="ROM"/>
|
||||
<service name="RAM"/>
|
||||
<service name="CPU"/>
|
||||
<service name="PD"/>
|
||||
<service name="LOG"/>
|
||||
</parent-provides>
|
||||
<start name="server">
|
||||
<binary name="dummy"/>
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<provides> <service name="LOG"/> </provides>
|
||||
<config> <log_service/> </config>
|
||||
<route> <any-service> <parent/> </any-service> </route>
|
||||
</start>
|
||||
<start name="indirect_server">
|
||||
<binary name="dummy"/>
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<provides> <service name="LOG"/> </provides>
|
||||
<config> <log_service/> </config>
|
||||
<route> <any-service> <parent/> </any-service> </route>
|
||||
</start>
|
||||
<start name="client">
|
||||
<binary name="dummy"/>
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<config> <log string="client started"/> </config>
|
||||
<route>
|
||||
<service name="LOG"> <child name="indirect_server"/> </service>
|
||||
<any-service> <parent/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
</init_config>
|
||||
<!-- the output of the new 'client' does no longer go via 'server' -->
|
||||
<expect_log string="[init -> indirect_server] [client] client started"/>
|
||||
<sleep ms="150"/>
|
||||
<expect_init_state>
|
||||
<node name="child">
|
||||
<attribute name="name" value="server"/>
|
||||
<attribute name="id" value="2"/>
|
||||
</node>
|
||||
<node name="child">
|
||||
<attribute name="name" value="client"/>
|
||||
<attribute name="id" value="6"/> <!-- client was restarted -->
|
||||
</node>
|
||||
</expect_init_state>
|
||||
<sleep ms="100"/>
|
||||
|
||||
|
||||
<message string="update child config"/>
|
||||
|
||||
<init_config>
|
||||
<report ids="yes"/>
|
||||
<parent-provides>
|
||||
<service name="ROM"/> <service name="RAM"/>
|
||||
<service name="CPU"/> <service name="PD"/>
|
||||
<service name="LOG"/>
|
||||
</parent-provides>
|
||||
<start name="application">
|
||||
<binary name="dummy"/>
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<config version="Version A"/>
|
||||
<route> <any-service> <parent/> </any-service> </route>
|
||||
</start>
|
||||
</init_config>
|
||||
<expect_log string="[init -> application] config 1: Version A"/>
|
||||
<init_config>
|
||||
<report ids="yes"/>
|
||||
<parent-provides>
|
||||
<service name="ROM"/> <service name="RAM"/>
|
||||
<service name="CPU"/> <service name="PD"/>
|
||||
<service name="LOG"/>
|
||||
</parent-provides>
|
||||
<start name="application">
|
||||
<binary name="dummy"/>
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<config version="Version B"/>
|
||||
<route> <any-service> <parent/> </any-service> </route>
|
||||
</start>
|
||||
</init_config>
|
||||
<expect_log string="[init -> application] config 2: Version B"/>
|
||||
<sleep ms="100"/>
|
||||
|
||||
</config>
|
||||
<route>
|
||||
<service name="Report"> <child name="report_rom"/> </service>
|
||||
|
@ -16,17 +16,68 @@
|
||||
#include <base/registry.h>
|
||||
#include <base/component.h>
|
||||
#include <base/attached_rom_dataspace.h>
|
||||
#include <root/component.h>
|
||||
#include <timer_session/connection.h>
|
||||
#include <log_session/connection.h>
|
||||
|
||||
namespace Dummy {
|
||||
|
||||
struct Log_service;
|
||||
struct Log_connections;
|
||||
struct Main;
|
||||
using namespace Genode;
|
||||
}
|
||||
|
||||
|
||||
struct Dummy::Log_service
|
||||
{
|
||||
Env &_env;
|
||||
|
||||
Heap _heap { _env.ram(), _env.rm() };
|
||||
|
||||
struct Session_component : Rpc_object<Log_session>
|
||||
{
|
||||
Session_label const _label;
|
||||
|
||||
Session_component(Session_label const &label) : _label(label) { }
|
||||
|
||||
size_t write(String const &string) override
|
||||
{
|
||||
/* strip known line delimiter from incoming message */
|
||||
unsigned n = 0;
|
||||
Genode::String<16> const pattern("\033[0m\n");
|
||||
for (char const *s = string.string(); s[n] && pattern != s + n; n++);
|
||||
|
||||
typedef Genode::String<100> Message;
|
||||
Message const message("[", _label, "] ", Cstring(string.string(), n));
|
||||
log(message);
|
||||
|
||||
return strlen(string.string());
|
||||
}
|
||||
};
|
||||
|
||||
struct Root : Root_component<Session_component>
|
||||
{
|
||||
Root(Entrypoint &ep, Allocator &alloc) : Root_component(ep, alloc) { }
|
||||
|
||||
Session_component *_create_session(const char *args, Affinity const &) override
|
||||
{
|
||||
return new (md_alloc()) Session_component(label_from_args(args));
|
||||
}
|
||||
};
|
||||
|
||||
Root _root { _env.ep(), _heap };
|
||||
|
||||
Log_service(Env &env) : _env(env)
|
||||
{
|
||||
_env.parent().announce(_env.ep().manage(_root));
|
||||
log("created LOG service");
|
||||
}
|
||||
|
||||
~Log_service() { _env.ep().dissolve(_root); }
|
||||
};
|
||||
|
||||
|
||||
struct Dummy::Log_connections
|
||||
{
|
||||
Env &_env;
|
||||
@ -66,10 +117,28 @@ struct Dummy::Main
|
||||
|
||||
Attached_rom_dataspace _config { _env, "config" };
|
||||
|
||||
Constructible<Log_connections> _log_connections;
|
||||
unsigned _config_cnt = 0;
|
||||
|
||||
Main(Env &env) : _env(env)
|
||||
typedef String<50> Version;
|
||||
|
||||
Version _config_version;
|
||||
|
||||
Signal_handler<Main> _config_handler { _env.ep(), *this, &Main::_handle_config };
|
||||
|
||||
void _handle_config()
|
||||
{
|
||||
_config.update();
|
||||
|
||||
Version const version = _config.xml().attribute_value("version", Version());
|
||||
if (_config_cnt > 0 && version == _config_version)
|
||||
return;
|
||||
|
||||
_config_cnt++;
|
||||
_config_version = version;
|
||||
|
||||
if (_config_version.valid())
|
||||
log("config ", _config_cnt, ": ", _config_version);
|
||||
|
||||
_config.xml().for_each_sub_node([&] (Xml_node node) {
|
||||
|
||||
if (node.type() == "create_log_connections")
|
||||
@ -78,6 +147,9 @@ struct Dummy::Main
|
||||
if (node.type() == "destroy_log_connections")
|
||||
_log_connections.destruct();
|
||||
|
||||
if (node.type() == "log_service")
|
||||
_log_service.construct(_env);
|
||||
|
||||
if (node.type() == "sleep") {
|
||||
|
||||
if (!_timer.constructed())
|
||||
@ -90,9 +162,17 @@ struct Dummy::Main
|
||||
log(node.attribute_value("string", String<50>()));
|
||||
});
|
||||
}
|
||||
|
||||
Constructible<Log_connections> _log_connections;
|
||||
|
||||
Constructible<Log_service> _log_service;
|
||||
|
||||
Main(Env &env) : _env(env)
|
||||
{
|
||||
_config.sigh(_config_handler);
|
||||
_handle_config();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
void Component::construct(Genode::Env &env) { static Dummy::Main main(env); }
|
||||
|
@ -55,28 +55,6 @@ namespace Init {
|
||||
} catch (...) {
|
||||
return Affinity::Space(1, 1); }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read parent-provided services from config
|
||||
*/
|
||||
inline void determine_parent_services(Registry<Init::Parent_service> &services,
|
||||
Xml_node config, Allocator &alloc,
|
||||
bool verbose)
|
||||
{
|
||||
if (verbose)
|
||||
log("parent provides");
|
||||
|
||||
config.sub_node("parent-provides")
|
||||
.for_each_sub_node("service", [&] (Xml_node node) {
|
||||
|
||||
Service::Name name = node.attribute_value("name", Service::Name());
|
||||
|
||||
new (alloc) Init::Parent_service(services, name);
|
||||
if (verbose)
|
||||
log(" service \"", name, "\"");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -213,11 +191,27 @@ class Init::Child_registry : public Name_registry, Child_list
|
||||
return _aliases.first() ? _aliases.first() : 0;
|
||||
}
|
||||
|
||||
void report_state(Xml_generator &xml, Report_detail const &detail) const
|
||||
template <typename FN>
|
||||
void for_each_child(FN const &fn) const
|
||||
{
|
||||
Genode::List_element<Child> const *curr = first();
|
||||
for (; curr; curr = curr->next())
|
||||
curr->object()->report_state(xml, detail);
|
||||
fn(*curr->object());
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void for_each_child(FN const &fn)
|
||||
{
|
||||
Genode::List_element<Child> *curr = first(), *next = nullptr;
|
||||
for (; curr; curr = next) {
|
||||
next = curr->next();
|
||||
fn(*curr->object());
|
||||
}
|
||||
}
|
||||
|
||||
void report_state(Xml_generator &xml, Report_detail const &detail) const
|
||||
{
|
||||
for_each_child([&] (Child &child) { child.report_state(xml, detail); });
|
||||
|
||||
/* check for name clash with an existing alias */
|
||||
for (Alias const *a = _aliases.first(); a; a = a->next()) {
|
||||
@ -226,7 +220,6 @@ class Init::Child_registry : public Name_registry, Child_list
|
||||
xml.attribute("child", a->child);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -427,6 +420,11 @@ struct Init::Main : State_reporter::Producer, Child::Default_route_accessor
|
||||
Signal_handler<Main> _resource_avail_handler {
|
||||
_env.ep(), *this, &Main::_handle_resource_avail };
|
||||
|
||||
void _update_aliases_from_config();
|
||||
void _update_parent_services_from_config();
|
||||
void _abandon_obsolete_children();
|
||||
void _update_children_config();
|
||||
void _destroy_abandoned_parent_services();
|
||||
void _handle_config();
|
||||
|
||||
Signal_handler<Main> _config_handler {
|
||||
@ -444,15 +442,58 @@ struct Init::Main : State_reporter::Producer, Child::Default_route_accessor
|
||||
};
|
||||
|
||||
|
||||
void Init::Main::_handle_config()
|
||||
void Init::Main::_update_parent_services_from_config()
|
||||
{
|
||||
/* kill all currently running children */
|
||||
while (_children.any()) {
|
||||
Init::Child *child = _children.any();
|
||||
_children.remove(child);
|
||||
destroy(_heap, child);
|
||||
}
|
||||
Xml_node const node = _config.xml().has_sub_node("parent-provides")
|
||||
? _config.xml().sub_node("parent-provides")
|
||||
: Xml_node("<empty/>");
|
||||
|
||||
/* remove services that are no longer present in config */
|
||||
_parent_services.for_each([&] (Parent_service &service) {
|
||||
|
||||
Service::Name const name = service.name();
|
||||
|
||||
bool obsolete = true;
|
||||
node.for_each_sub_node("service", [&] (Xml_node service) {
|
||||
if (name == service.attribute_value("name", Service::Name())) {
|
||||
obsolete = false; }});
|
||||
|
||||
if (obsolete)
|
||||
service.abandon();
|
||||
});
|
||||
|
||||
if (_verbose->enabled())
|
||||
log("parent provides");
|
||||
|
||||
/* register new services */
|
||||
node.for_each_sub_node("service", [&] (Xml_node service) {
|
||||
|
||||
Service::Name const name = service.attribute_value("name", Service::Name());
|
||||
|
||||
bool registered = false;
|
||||
_parent_services.for_each([&] (Parent_service const &service) {
|
||||
if (service.name() == name)
|
||||
registered = true; });
|
||||
|
||||
if (!registered) {
|
||||
new (_heap) Init::Parent_service(_parent_services, _env, name);
|
||||
if (_verbose->enabled())
|
||||
log(" service \"", name, "\"");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void Init::Main::_destroy_abandoned_parent_services()
|
||||
{
|
||||
_parent_services.for_each([&] (Parent_service &service) {
|
||||
if (service.abandoned())
|
||||
destroy(_heap, &service); });
|
||||
}
|
||||
|
||||
|
||||
void Init::Main::_update_aliases_from_config()
|
||||
{
|
||||
/* remove all known aliases */
|
||||
while (_children.any_alias()) {
|
||||
Init::Alias *alias = _children.any_alias();
|
||||
@ -460,24 +501,6 @@ void Init::Main::_handle_config()
|
||||
destroy(_heap, alias);
|
||||
}
|
||||
|
||||
/* reset knowledge about parent services */
|
||||
_parent_services.for_each([&] (Init::Parent_service &service) {
|
||||
destroy(_heap, &service); });
|
||||
|
||||
_config.update();
|
||||
|
||||
_verbose.construct(_config.xml());
|
||||
_state_reporter.apply_config(_config.xml());
|
||||
|
||||
try { determine_parent_services(_parent_services, _config.xml(),
|
||||
_heap, _verbose->enabled()); }
|
||||
catch (...) { }
|
||||
|
||||
/* determine default route for resolving service requests */
|
||||
try {
|
||||
_default_route.construct(_heap, _config.xml().sub_node("default-route")); }
|
||||
catch (...) { }
|
||||
|
||||
/* create aliases */
|
||||
_config.xml().for_each_sub_node("alias", [&] (Xml_node alias_node) {
|
||||
|
||||
@ -488,11 +511,99 @@ void Init::Main::_handle_config()
|
||||
catch (Alias::Child_is_missing) {
|
||||
warning("missing 'child' attribute in '<alias>' entry"); }
|
||||
});
|
||||
}
|
||||
|
||||
/* create children */
|
||||
|
||||
void Init::Main::_abandon_obsolete_children()
|
||||
{
|
||||
_children.for_each_child([&] (Child &child) {
|
||||
|
||||
Child_policy::Name const name = child.name();
|
||||
|
||||
bool obsolete = true;
|
||||
_config.xml().for_each_sub_node("start", [&] (Xml_node node) {
|
||||
if (node.attribute_value("name", Child_policy::Name()) == name)
|
||||
obsolete = false; });
|
||||
|
||||
if (obsolete)
|
||||
child.abandon();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void Init::Main::_update_children_config()
|
||||
{
|
||||
for (;;) {
|
||||
|
||||
/*
|
||||
* Children are abandoned if any of their client sessions can no longer
|
||||
* be routed or result in a different route. As each child may be a
|
||||
* service, an avalanche effect may occur. It stops if no update causes
|
||||
* a potential side effect in one iteration over all chilren.
|
||||
*/
|
||||
bool side_effects = false;
|
||||
|
||||
_config.xml().for_each_sub_node("start", [&] (Xml_node node) {
|
||||
|
||||
Child_policy::Name const start_node_name =
|
||||
node.attribute_value("name", Child_policy::Name());
|
||||
|
||||
_children.for_each_child([&] (Child &child) {
|
||||
if (child.name() == start_node_name) {
|
||||
switch (child.apply_config(node)) {
|
||||
case Child::NO_SIDE_EFFECTS: break;
|
||||
case Child::MAY_HAVE_SIDE_EFFECTS: side_effects = true; break;
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!side_effects)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Init::Main::_handle_config()
|
||||
{
|
||||
_config.update();
|
||||
|
||||
_verbose.construct(_config.xml());
|
||||
_state_reporter.apply_config(_config.xml());
|
||||
|
||||
/* determine default route for resolving service requests */
|
||||
try {
|
||||
_default_route.construct(_heap, _config.xml().sub_node("default-route")); }
|
||||
catch (...) { }
|
||||
|
||||
_update_aliases_from_config();
|
||||
_update_parent_services_from_config();
|
||||
_abandon_obsolete_children();
|
||||
_update_children_config();
|
||||
|
||||
/* kill abandoned children */
|
||||
_children.for_each_child([&] (Child &child) {
|
||||
if (child.abandoned()) {
|
||||
_children.remove(&child);
|
||||
destroy(_heap, &child);
|
||||
}
|
||||
});
|
||||
|
||||
_destroy_abandoned_parent_services();
|
||||
|
||||
/* create new children */
|
||||
try {
|
||||
_config.xml().for_each_sub_node("start", [&] (Xml_node start_node) {
|
||||
|
||||
/* skip start node if corresponding child already exists */
|
||||
bool exists = false;
|
||||
_children.for_each_child([&] (Child const &child) {
|
||||
if (child.name() == start_node.attribute_value("name", Child_policy::Name()))
|
||||
exists = true; });
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_children.insert(new (_heap)
|
||||
Init::Child(_env, _heap, *_verbose,
|
||||
@ -526,6 +637,18 @@ void Init::Main::_handle_config()
|
||||
catch (Xml_node::Invalid_syntax) { error("config has invalid syntax"); }
|
||||
catch (Init::Child::Child_name_is_not_unique) { }
|
||||
catch (Init::Child_registry::Alias_name_is_not_unique) { }
|
||||
|
||||
/*
|
||||
* Initiate RAM sessions of all new children
|
||||
*/
|
||||
_children.for_each_child([&] (Child &child) {
|
||||
child.initiate_env_ram_session(); });
|
||||
|
||||
/*
|
||||
* Initiate remaining environment sessions of all new children
|
||||
*/
|
||||
_children.for_each_child([&] (Child &child) {
|
||||
child.initiate_env_sessions(); });
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user