From b661459aca6d7e01b06a1ac0f81d3f92dded0ffa Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Thu, 1 Apr 2021 18:05:23 +0200 Subject: [PATCH] sandbox/init: parse config using 'List_model' This commit replaces the hand-crafted config processing by the use of the 'List_model' utility. This has the following advantages: - The parsing follows a common formalism that makes the code easier to maintain and to understand. Several parts of the code had to be changed (for the better) to make it fit the list model approach. E.g., the child states have become more expressive and logical. - In the common case, the XML data is traversed only once, which increases the parsing speed in dynamic scenarios. - The code becomes easier to optimize. In particular, the patch skips the re-evaluation of the session routing if no service is affected by the config change. The patch also revisits the init test by removing overly long sleep phases and extending a few sleep phases that were too short when executing the test on Qemu. Issue #4068 --- repos/os/lib/mk/sandbox.mk | 2 +- .../os/recipes/raw/test-init/test-init.config | 37 +- repos/os/src/init/target.mk | 2 +- repos/os/src/lib/sandbox/alias.h | 38 +- repos/os/src/lib/sandbox/child.cc | 108 ++-- repos/os/src/lib/sandbox/child.h | 152 +++-- repos/os/src/lib/sandbox/child_registry.h | 43 -- repos/os/src/lib/sandbox/config_model.cc | 402 +++++++++++++ repos/os/src/lib/sandbox/config_model.h | 283 +++++++++ repos/os/src/lib/sandbox/heartbeat.h | 20 +- repos/os/src/lib/sandbox/library.cc | 551 ++++++++---------- repos/os/src/lib/sandbox/server.cc | 53 +- repos/os/src/lib/sandbox/server.h | 22 +- repos/os/src/lib/sandbox/state_reporter.h | 13 +- repos/os/src/lib/sandbox/types.h | 20 +- repos/os/src/lib/sandbox/update_list_model.h | 69 +++ repos/os/src/lib/sandbox/utils.h | 23 +- repos/os/src/lib/sandbox/verbose.h | 2 +- 18 files changed, 1309 insertions(+), 531 deletions(-) create mode 100644 repos/os/src/lib/sandbox/config_model.cc create mode 100644 repos/os/src/lib/sandbox/config_model.h create mode 100644 repos/os/src/lib/sandbox/update_list_model.h diff --git a/repos/os/lib/mk/sandbox.mk b/repos/os/lib/mk/sandbox.mk index 9fe8dfab35..9e30fd1828 100644 --- a/repos/os/lib/mk/sandbox.mk +++ b/repos/os/lib/mk/sandbox.mk @@ -1,4 +1,4 @@ -SRC_CC = library.cc child.cc server.cc +SRC_CC = library.cc child.cc server.cc config_model.cc INC_DIR += $(REP_DIR)/src/lib/sandbox LIBS += base diff --git a/repos/os/recipes/raw/test-init/test-init.config b/repos/os/recipes/raw/test-init/test-init.config index 98e6ebf156..aa2ac2fe23 100644 --- a/repos/os/recipes/raw/test-init/test-init.config +++ b/repos/os/recipes/raw/test-init/test-init.config @@ -64,7 +64,7 @@ - + @@ -261,7 +261,7 @@ - + @@ -306,7 +306,7 @@ - + @@ -524,6 +524,7 @@ + @@ -551,7 +552,7 @@ - + @@ -759,7 +760,7 @@ - + @@ -838,7 +839,7 @@ - + @@ -1078,7 +1079,7 @@ - + @@ -1155,7 +1156,7 @@ - + @@ -1246,6 +1247,7 @@ + @@ -1266,6 +1268,7 @@ good-case tests. --> + @@ -1478,11 +1481,11 @@ - + - + @@ -1513,7 +1516,7 @@ - + @@ -1543,7 +1546,7 @@ - + @@ -1657,8 +1660,8 @@ - - + + @@ -1682,7 +1685,7 @@ - + @@ -1701,7 +1704,7 @@ - + @@ -1730,7 +1733,7 @@ - + diff --git a/repos/os/src/init/target.mk b/repos/os/src/init/target.mk index 279736b2d6..bc74bb5f0f 100644 --- a/repos/os/src/init/target.mk +++ b/repos/os/src/init/target.mk @@ -6,6 +6,6 @@ INC_DIR += $(PRG_DIR) CONFIG_XSD = config.xsd # statically link sandbox library to avoid dependency from sandbox.lib.so -SRC_CC += library.cc child.cc server.cc +SRC_CC += library.cc child.cc server.cc config_model.cc INC_DIR += $(REP_DIR)/src/lib/sandbox vpath %.cc $(REP_DIR)/src/lib/sandbox diff --git a/repos/os/src/lib/sandbox/alias.h b/repos/os/src/lib/sandbox/alias.h index d723365c69..7f05a5052d 100644 --- a/repos/os/src/lib/sandbox/alias.h +++ b/repos/os/src/lib/sandbox/alias.h @@ -19,34 +19,28 @@ namespace Sandbox { struct Alias; } -struct Sandbox::Alias : List::Element +struct Sandbox::Alias : List::Element, Noncopyable { - typedef String<128> Name; - typedef String<128> Child; + typedef Child_policy::Name Name; + typedef Child_policy::Name Child; - Name name; - Child child; + Name const name; - /** - * Exception types + Child child { }; /* defined by 'update' */ + + Alias(Name const &name) : name(name) { } + + class Child_attribute_missing : Exception { }; + + /* + * \throw Child_attribute_missing */ - class Name_is_missing : Exception { }; - class Child_is_missing : Exception { }; - - /** - * Constructor - * - * \throw Name_is_missing - * \throw Child_is_missing - */ - Alias(Genode::Xml_node alias) - : - name (alias.attribute_value("name", Name())), - child(alias.attribute_value("child", Child())) + void update(Xml_node const &alias) { + if (!alias.has_attribute("child")) + warning("alias node \"", name, "\" lacks child attribute"); - if (!name.valid()) throw Name_is_missing(); - if (!child.valid()) throw Child_is_missing(); + child = alias.attribute_value("child", Child()); } }; diff --git a/repos/os/src/lib/sandbox/child.cc b/repos/os/src/lib/sandbox/child.cc index 0a39c8cda9..6fc765dd5f 100644 --- a/repos/os/src/lib/sandbox/child.cc +++ b/repos/os/src/lib/sandbox/child.cc @@ -11,6 +11,7 @@ * under the terms of the GNU Affero General Public License version 3. */ +/* Genode includes */ #include /* local includes */ @@ -28,14 +29,14 @@ void Sandbox::Child::destroy_services() Sandbox::Child::Apply_config_result Sandbox::Child::apply_config(Xml_node start_node) { - if (_state == STATE_ABANDONED || _exited) + if (abandoned() || stuck() || restart_scheduled() || _exited) return NO_SIDE_EFFECTS; /* - * If the child's environment is incomplete, restart it to attempt - * the re-routing of its environment sessions. + * If the child was started but its environment is incomplete, mark it as + * being stuck in order to restart it once the environment changes. */ - { + if (_state != State::INITIAL) { bool env_log_exists = false, env_binary_exists = false; _child.for_each_session([&] (Session_state const &session) { Parent::Client::Id const id = session.id_at_client(); @@ -44,8 +45,8 @@ Sandbox::Child::apply_config(Xml_node start_node) }); if (!env_binary_exists || !env_log_exists) { - abandon(); - return MAY_HAVE_SIDE_EFFECTS; + _state = State::STUCK; + return NO_SIDE_EFFECTS; } } @@ -62,14 +63,24 @@ Sandbox::Child::apply_config(Xml_node start_node) if (start_node.differs_from(_start_node->xml())) { /* - * Start node changed - * + * The node may affect the availability or unavailability + * of dependencies. + */ + start_node.with_sub_node("route", [&] (Xml_node const &route) { + _start_node->xml().with_sub_node("route", [&] (Xml_node const &orig) { + if (route.differs_from(orig)) + _uncertain_dependencies = true; }); }); + + /* * 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) + _uncertain_dependencies = true; + if (config_was_present && !config_is_present) config_update = CONFIG_VANISHED; @@ -125,7 +136,10 @@ Sandbox::Child::apply_config(Xml_node start_node) * the binary's ROM session, triggering the restart of the * child. */ + Binary_name const orig_binary_name = _binary_name; _binary_name = _binary_from_xml(start_node, _unique_name); + if (orig_binary_name != _binary_name) + _uncertain_dependencies = true; _heartbeat_enabled = start_node.has_sub_node("heartbeat"); @@ -136,8 +150,8 @@ Sandbox::Child::apply_config(Xml_node 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. + * may in turn prompt the routing-check by 'evaluate_dependencies' + * to restart the child. */ switch (config_update) { case CONFIG_UNCHANGED: break; @@ -146,26 +160,39 @@ Sandbox::Child::apply_config(Xml_node start_node) 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; - } - } - if (provided_services_changed) - return MAY_HAVE_SIDE_EFFECTS; + return PROVIDED_SERVICES_CHANGED; return NO_SIDE_EFFECTS; } +void Sandbox::Child::evaluate_dependencies() +{ + bool any_route_changed = false; + bool any_route_unavailable = false; + + _child.for_each_session([&] (Session_state const &session) { + + switch (_route_valid(session)) { + case Route_state::VALID: break; + case Route_state::UNAVAILABLE: any_route_unavailable = true; break; + case Route_state::MISMATCH: any_route_changed = true; break; + } + }); + + _uncertain_dependencies = false; + + if (any_route_unavailable) { + _state = State::STUCK; + return; + } + + if (any_route_changed || stuck()) + _schedule_restart(); +} + + Sandbox::Ram_quota Sandbox::Child::_configured_ram_quota() const { size_t assigned = 0; @@ -338,7 +365,7 @@ void Sandbox::Child::report_state(Xml_generator &xml, Report_detail const &detai if (detail.ids()) xml.attribute("id", _id.value); - if (!_child.active()) + if (stuck() || _state == State::RAM_INITIALIZED) xml.attribute("state", "incomplete"); if (_exited) @@ -420,11 +447,29 @@ void Sandbox::Child::init(Pd_session &session, Pd_session_capability cap) size_t const initial_session_costs = session_alloc_batch_size()*_child.session_factory().session_costs(); - Ram_quota const ram_quota { _resources.effective_ram_quota().value > initial_session_costs - ? _resources.effective_ram_quota().value - initial_session_costs - : 0 }; + Ram_quota ram_quota { _resources.effective_ram_quota().value > initial_session_costs + ? _resources.effective_ram_quota().value - initial_session_costs + : 0 }; - Cap_quota const cap_quota { _resources.effective_cap_quota().value }; + Ram_quota avail_ram = _ram_limit_accessor.resource_limit(Ram_quota()); + + avail_ram = Genode::Child::effective_quota(avail_ram); + + if (ram_quota.value > avail_ram.value) { + warning(name(), ": configured RAM exceeds available RAM, proceed with ", avail_ram); + ram_quota = avail_ram; + } + + Cap_quota cap_quota { _resources.effective_cap_quota().value }; + + Cap_quota avail_caps = _cap_limit_accessor.resource_limit(avail_caps); + + avail_caps = Genode::Child::effective_quota(avail_caps); + + if (cap_quota.value > avail_caps.value) { + warning(name(), ": configured caps exceed available caps, proceed with ", avail_caps); + cap_quota = avail_caps; + } try { _env.pd().transfer_quota(cap, cap_quota); } catch (Out_of_caps) { @@ -718,8 +763,6 @@ Sandbox::Child::Child(Env &env, Default_route_accessor &default_route_accessor, Default_caps_accessor &default_caps_accessor, Name_registry &name_registry, - Ram_quota ram_limit, - Cap_quota cap_limit, Ram_limit_accessor &ram_limit_accessor, Cap_limit_accessor &cap_limit_accessor, Prio_levels prio_levels, @@ -739,8 +782,7 @@ Sandbox::Child::Child(Env &env, _name_registry(name_registry), _heartbeat_enabled(start_node.has_sub_node("heartbeat")), _resources(_resources_from_start_node(start_node, prio_levels, affinity_space, - default_caps_accessor.default_caps(), cap_limit)), - _resources_clamped_to_limit((_clamp_resources(ram_limit, cap_limit), true)), + default_caps_accessor.default_caps())), _parent_services(parent_services), _child_services(child_services), _local_services(local_services), diff --git a/repos/os/src/lib/sandbox/child.h b/repos/os/src/lib/sandbox/child.h index e0385616f6..11fca7ca63 100644 --- a/repos/os/src/lib/sandbox/child.h +++ b/repos/os/src/lib/sandbox/child.h @@ -78,10 +78,34 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup Id const _id; - enum State { STATE_INITIAL, STATE_RAM_INITIALIZED, STATE_ALIVE, - STATE_ABANDONED }; + enum class State { - State _state = STATE_INITIAL; + /* + * States modelling the child's boostrap phase + */ + INITIAL, RAM_INITIALIZED, ALIVE, + + /* + * The child is present in the config model but its bootstrapping + * permanently failed. + */ + STUCK, + + /* + * The child must be restarted because a fundamental dependency + * changed. While the child is in this state, it is still + * referenced by the config model. + */ + RESTART_SCHEDULED, + + /* + * The child is no longer referenced by config model and can + * safely be destructed. + */ + ABANDONED + }; + + State _state = State::INITIAL; Report_update_trigger &_report_update_trigger; @@ -94,6 +118,8 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup */ Version _version { _start_node->xml().attribute_value("version", Version()) }; + bool _uncertain_dependencies = false; + /* * True if the binary is loaded with ld.lib.so */ @@ -117,7 +143,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup if (name.valid()) return name; - warning("missint 'name' attribute in '' entry"); + warning("missing 'name' attribute in '' entry"); throw Missing_name_attribute(); } @@ -151,7 +177,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup bool _heartbeat_expected() const { /* don't expect heartbeats from a child that is not yet complete */ - return _heartbeat_enabled && (_state == STATE_ALIVE); + return _heartbeat_enabled && (_state == State::ALIVE); } /** @@ -193,9 +219,10 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup } }; + static Resources _resources_from_start_node(Xml_node start_node, Prio_levels prio_levels, Affinity::Space const &affinity_space, - Cap_quota default_cap_quota, Cap_quota) + Cap_quota default_cap_quota) { size_t cpu_quota_pc = 0; Number_of_bytes ram_bytes = 0; @@ -208,7 +235,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup Name const name = rsc.attribute_value("name", Name()); if (name == "RAM") { - ram_bytes = rsc.attribute_value("quantum", ram_bytes); + ram_bytes = rsc.attribute_value("quantum", ram_bytes); } if (name == "CPU") { @@ -231,29 +258,9 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup Resources _resources; - /** - * Print diagnostic information on misconfiguration - */ - void _clamp_resources(Ram_quota ram_limit, Cap_quota cap_limit) - { - if (_resources.assigned_ram_quota.value > ram_limit.value) { - warning(name(), " assigned RAM (", _resources.assigned_ram_quota, ") " - "exceeds available RAM (", ram_limit, ")"); - _resources.assigned_ram_quota = ram_limit; - } - - if (_resources.assigned_cap_quota.value > cap_limit.value) { - warning(name(), " assigned caps (", _resources.assigned_cap_quota, ") " - "exceed available caps (", cap_limit, ")"); - _resources.assigned_cap_quota = cap_limit; - } - } - Ram_quota _configured_ram_quota() const; Cap_quota _configured_cap_quota() const; - bool const _resources_clamped_to_limit; - using Local_service = Genode::Sandbox::Local_service_base; Registry &_parent_services; @@ -375,6 +382,8 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup _session_requester.trigger_update(); } + enum class Route_state { VALID, MISMATCH, UNAVAILABLE }; + /** * Return true if the policy results in the current route of the session * @@ -382,7 +391,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup * client session of a child, i.e., to determine whether the child must * be restarted. */ - bool _route_valid(Session_state const &session) + Route_state _route_valid(Session_state const &session) { try { Route const route = @@ -390,10 +399,12 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup session.client_label(), session.diag()); - return (session.service() == route.service) - && (route.label == session.label()); + bool const valid = (session.service() == route.service) + && (route.label == session.label()); + + return valid ? Route_state::VALID : Route_state::MISMATCH; } - catch (Service_denied) { return false; } + catch (Service_denied) { return Route_state::UNAVAILABLE; } } static Xml_node _provides_sub_node(Xml_node start_node) @@ -421,7 +432,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup service.name() == node.attribute_value("name", Service::Name())) exists = true; }); - return exists && !abandoned(); + return exists && !abandoned() && !restart_scheduled(); } void _add_service(Xml_node service) @@ -454,7 +465,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup */ bool _pd_alive() const { - return !abandoned() && !_exited; + return !abandoned() && !restart_scheduled() && !_exited; } void _destroy_services(); @@ -477,6 +488,19 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup } _sampled_state { }; + void _abandon_services() + { + _child_services.for_each([&] (Routed_service &service) { + if (service.has_id_space(_session_requester.id_space())) + service.abandon(); }); + } + + void _schedule_restart() + { + _state = State::RESTART_SCHEDULED; + _abandon_services(); + } + public: /** @@ -505,8 +529,6 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup Default_route_accessor &default_route_accessor, Default_caps_accessor &default_caps_accessor, Name_registry &name_registry, - Ram_quota ram_limit, - Cap_quota cap_limit, Ram_limit_accessor &ram_limit_accessor, Cap_limit_accessor &cap_limit_accessor, Prio_levels prio_levels, @@ -527,50 +549,55 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup Ram_quota ram_quota() const { return _resources.assigned_ram_quota; } Cap_quota cap_quota() const { return _resources.assigned_cap_quota; } - void initiate_env_pd_session() + void try_start() { - if (_state == STATE_INITIAL) { + if (_state == State::INITIAL) { _child.initiate_env_pd_session(); - _state = STATE_RAM_INITIALIZED; + _state = State::RAM_INITIALIZED; } - } - void initiate_env_sessions() - { - if (_state == STATE_RAM_INITIALIZED) { + /* + * Update the state if async env sessions have brought the child to + * life. Otherwise, we would wrongly call 'initiate_env_sessions()' + * another time. + */ + if (_state == State::RAM_INITIALIZED && _child.active()) + _state = State::ALIVE; + 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.alive()) - warning(name(), ": incomplete environment ", - session.service().name(), " session " - "(", session.label(), ")"); }); - - _state = STATE_ALIVE; + if (_child.active()) + _state = State::ALIVE; + else + _uncertain_dependencies = true; } } + /* + * Mark child as to be removed because its was dropped from the + * config model. Either node disappeared or 'restart_scheduled' + * was handled. + */ void abandon() { - _state = STATE_ABANDONED; - - _child_services.for_each([&] (Routed_service &service) { - if (service.has_id_space(_session_requester.id_space())) - service.abandon(); }); + _state = State::ABANDONED; + _abandon_services(); } void destroy_services(); void close_all_sessions() { _child.close_all_sessions(); } - bool abandoned() const { return _state == STATE_ABANDONED; } + bool abandoned() const { return _state == State::ABANDONED; } + + bool restart_scheduled() const { return _state == State::RESTART_SCHEDULED; } + + bool stuck() const { return _state == State::STUCK; } bool env_sessions_closed() const { return _child.env_sessions_closed(); } - enum Apply_config_result { MAY_HAVE_SIDE_EFFECTS, NO_SIDE_EFFECTS }; + enum Apply_config_result { PROVIDED_SERVICES_CHANGED, NO_SIDE_EFFECTS }; /** * Apply new configuration to child @@ -580,6 +607,15 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup */ Apply_config_result apply_config(Xml_node start_node); + bool uncertain_dependencies() const { return _uncertain_dependencies; } + + /** + * Validate that the routes of all existing sessions remain intact + * + * The child may become scheduled for restart or get stuck. + */ + void evaluate_dependencies(); + /* common code for upgrading RAM and caps */ template void _apply_resource_upgrade(QUOTA &, QUOTA, LIMIT_ACCESSOR const &); diff --git a/repos/os/src/lib/sandbox/child_registry.h b/repos/os/src/lib/sandbox/child_registry.h index 9b0cbfc03c..b13e7527fa 100644 --- a/repos/os/src/lib/sandbox/child_registry.h +++ b/repos/os/src/lib/sandbox/child_registry.h @@ -29,30 +29,8 @@ class Sandbox::Child_registry : public Name_registry, Child_list List _aliases { }; - bool _unique(const char *name) const - { - /* check for name clash with an existing child */ - List_element const *curr = first(); - for (; curr; curr = curr->next()) - if (curr->object()->has_name(name)) - return false; - - /* check for name clash with an existing alias */ - for (Alias const *a = _aliases.first(); a; a = a->next()) { - if (Alias::Name(name) == a->name) - return false; - } - - return true; - } - public: - /** - * Exception type - */ - class Alias_name_is_not_unique { }; - /** * Register child */ @@ -74,10 +52,6 @@ class Sandbox::Child_registry : public Name_registry, Child_list */ void insert_alias(Alias *alias) { - if (!_unique(alias->name.string())) { - error("alias name ", alias->name, " is not unique"); - throw Alias_name_is_not_unique(); - } _aliases.insert(alias); } @@ -89,22 +63,6 @@ class Sandbox::Child_registry : public Name_registry, Child_list _aliases.remove(alias); } - /** - * Return any of the registered children, or 0 if no child exists - */ - Child *any() - { - return first() ? first()->object() : 0; - } - - /** - * Return any of the registered aliases, or 0 if no alias exists - */ - Alias *any_alias() - { - return _aliases.first() ? _aliases.first() : 0; - } - template void for_each_child(FN const &fn) const { @@ -127,7 +85,6 @@ class Sandbox::Child_registry : public Name_registry, Child_list { 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()) { xml.node("alias", [&] () { xml.attribute("name", a->name); diff --git a/repos/os/src/lib/sandbox/config_model.cc b/repos/os/src/lib/sandbox/config_model.cc new file mode 100644 index 0000000000..0c48d2e977 --- /dev/null +++ b/repos/os/src/lib/sandbox/config_model.cc @@ -0,0 +1,402 @@ +/* + * \brief Internal model of the XML configuration + * \author Norman Feske + * \date 2021-04-02 + */ + +/* + * Copyright (C) 2021 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#include + +using namespace Sandbox; + + +struct Config_model::Node : Noncopyable, Interface, private List_model::Element +{ + friend class List_model; + friend class List; + + virtual bool matches(Xml_node const &) const = 0; + + virtual void update(Xml_node const &) = 0; + + virtual void apply_child_restart(Xml_node const &) { /* only implemented by 'Start_node' */ } + + virtual void trigger_start_child() { /* only implemented by 'Start_node' */ } +}; + + +struct Config_model::Parent_provides_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("parent-provides"); + } + + Parent_provides_model _model; + + Parent_provides_node(Allocator &alloc, Verbose const &verbose, + Parent_provides_model::Factory &factory) + : + _model(alloc, verbose, factory) + { } + + bool matches(Xml_node const &xml) const override { return type_matches(xml); } + + void update(Xml_node const &xml) override { _model.update_from_xml(xml); } +}; + + +struct Config_model::Default_route_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("default-route"); + } + + Allocator &_alloc; + Constructible &_default_route; + + Default_route_node(Allocator &alloc, Constructible &default_route) + : _alloc(alloc), _default_route(default_route) { } + + ~Default_route_node() { _default_route.destruct(); } + + bool matches(Xml_node const &xml) const override { return type_matches(xml); } + + void update(Xml_node const &xml) override + { + if (!_default_route.constructed() || _default_route->xml().differs_from(xml)) + _default_route.construct(_alloc, xml); + } +}; + + +struct Config_model::Default_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("default"); + } + + Cap_quota &_default_caps; + + Default_node(Cap_quota &default_caps) : _default_caps(default_caps) { } + + bool matches(Xml_node const &xml) const override { return type_matches(xml); } + + void update(Xml_node const &xml) override + { + _default_caps = Cap_quota { xml.attribute_value("caps", 0UL) }; + } +}; + + +struct Config_model::Affinity_space_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("affinity-space"); + } + + Constructible &_affinity_space; + + Affinity_space_node(Constructible &affinity_space) + : _affinity_space(affinity_space) { } + + ~Affinity_space_node() { _affinity_space.destruct(); } + + bool matches(Xml_node const &xml) const override { return type_matches(xml); } + + void update(Xml_node const &xml) override + { + _affinity_space.construct(xml.attribute_value("width", 1u), + xml.attribute_value("height", 1u)); + } +}; + + +struct Config_model::Start_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("start") || xml.has_type("alias"); + } + + Start_model _model; + + /* + * \throw Start_model::Factory::Creation_failed + */ + Start_node(Start_model::Factory &factory, Xml_node const &xml) + : _model(factory, xml) { } + + bool matches(Xml_node const &xml) const override + { + return type_matches(xml) && _model.matches(xml); + } + + void update(Xml_node const &xml) override + { + _model.update_from_xml(xml); + } + + void apply_child_restart(Xml_node const &xml) override + { + _model.apply_child_restart(xml); + } + + void trigger_start_child() override + { + _model.trigger_start_child(); + } +}; + + +struct Config_model::Report_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("report"); + } + + Version const &_version; + State_reporter &_state_reporter; + + Report_node(Version const &version, State_reporter &state_reporter) + : _version(version), _state_reporter(state_reporter) { } + + ~Report_node() + { + _state_reporter.apply_config(_version, Xml_node("")); + } + + bool matches(Xml_node const &xml) const override { return type_matches(xml); } + + void update(Xml_node const &xml) override + { + _state_reporter.apply_config(_version, xml); + } +}; + + +struct Config_model::Resource_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("resource"); + } + + enum class Category { RAM, CAP } const _category; + + class Unknown_resource_name : Exception { }; + + static Category _category_from_xml(Xml_node const &xml) + { + typedef String<16> Name; + Name const name = xml.attribute_value("name", Name()); + + if (name == "RAM") return Category::RAM; + if (name == "CAP") return Category::CAP; + + throw Unknown_resource_name(); + } + + Preservation &_keep; + + /* + * \throw Unknown_resource_name + */ + Resource_node(Preservation &keep, Xml_node xml) + : + _category(_category_from_xml(xml)), _keep(keep) + { } + + ~Resource_node() + { + switch (_category) { + case Category::RAM: _keep.ram = Preservation::default_ram(); break; + case Category::CAP: _keep.caps = Preservation::default_caps(); break; + } + } + + bool matches(Xml_node const &xml) const override + { + return type_matches(xml) && _category == _category_from_xml(xml); + } + + void update(Xml_node const &xml) override + { + switch (_category) { + + case Category::RAM: + { + Number_of_bytes keep { Preservation::default_ram().value }; + _keep.ram = { xml.attribute_value("preserve", keep) }; + break; + } + + case Category::CAP: + { + size_t keep = Preservation::default_caps().value; + _keep.caps = { xml.attribute_value("preserve", keep) }; + break; + } + } + } +}; + + +struct Config_model::Heartbeat_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("heartbeat"); + } + + Heartbeat &_heartbeat; + + Heartbeat_node(Heartbeat &heartbeat) : _heartbeat(heartbeat) { } + + ~Heartbeat_node() + { + _heartbeat.disable(); + } + + bool matches(Xml_node const &xml) const override { return type_matches(xml); } + + void update(Xml_node const &xml) override + { + _heartbeat.apply_config(xml); + } +}; + + +struct Config_model::Service_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("service"); + } + + Service_model::Factory &_factory; + + Service_model &_model; + + Service_node(Service_model::Factory &factory, Xml_node const &xml) + : _factory(factory), _model(factory.create_service(xml)) { } + + ~Service_node() { _factory.destroy_service(_model); } + + bool matches(Xml_node const &xml) const override + { + return type_matches(xml) && _model.matches(xml); + } + + void update(Xml_node const &xml) override { _model.update_from_xml(xml); } +}; + + +void Config_model::update_from_xml(Xml_node const &xml, + Allocator &alloc, + Reconstructible &verbose, + Version &version, + Preservation &preservation, + Constructible &default_route, + Cap_quota &default_caps, + Prio_levels &prio_levels, + Constructible &affinity_space, + Start_model::Factory &child_factory, + Parent_provides_model::Factory &parent_service_factory, + Service_model::Factory &service_factory, + State_reporter &state_reporter, + Heartbeat &heartbeat) +{ + /* config version to be reflected in state reports */ + version = xml.attribute_value("version", Version()); + + preservation.reset(); + + prio_levels = ::Sandbox::prio_levels_from_xml(xml); + + affinity_space.destruct(); + + verbose.construct(xml); + + class Unknown_element_type : Exception { }; + + auto destroy = [&] (Node &node) { Genode::destroy(alloc, &node); }; + + auto create = [&] (Xml_node const &xml) -> Node & + { + if (Parent_provides_node::type_matches(xml)) + return *new (alloc) + Parent_provides_node(alloc, *verbose, parent_service_factory); + + if (Default_route_node::type_matches(xml)) + return *new (alloc) Default_route_node(alloc, default_route); + + if (Default_node::type_matches(xml)) + return *new (alloc) Default_node(default_caps); + + if (Start_node::type_matches(xml)) + return *new (alloc) Start_node(child_factory, xml); + + if (Affinity_space_node::type_matches(xml)) + return *new (alloc) Affinity_space_node(affinity_space); + + if (Report_node::type_matches(xml)) + return *new (alloc) Report_node(version, state_reporter); + + if (Resource_node::type_matches(xml)) + return *new (alloc) Resource_node(preservation, xml); + + if (Heartbeat_node::type_matches(xml)) + return *new (alloc) Heartbeat_node(heartbeat); + + if (Service_node::type_matches(xml)) + return *new (alloc) Service_node(service_factory, xml); + + error("unknown config element type <", xml.type(), ">"); + throw Unknown_element_type(); + }; + + auto update = [&] (Node &node, Xml_node const &xml) { node.update(xml); }; + + try { + update_list_model_from_xml(_model, xml, create, destroy, update); + } + catch (Unknown_element_type) { + error("unable to apply complete configuration"); } + catch (Start_model::Factory::Creation_failed) { + error("child creation failed"); } +} + + +void Config_model::apply_children_restart(Xml_node const &xml) +{ + class Unexpected : Exception { }; + auto destroy = [&] (Node &) { }; + auto create = [&] (Xml_node const &) -> Node & { throw Unexpected(); }; + auto update = [&] (Node &node, Xml_node const &xml) + { + node.apply_child_restart(xml); + }; + + try { + update_list_model_from_xml(_model, xml, create, destroy, update); + } + catch (...) { }; +} + + +void Config_model::trigger_start_children() +{ + _model.for_each([&] (Node &node) { + node.trigger_start_child(); }); +} diff --git a/repos/os/src/lib/sandbox/config_model.h b/repos/os/src/lib/sandbox/config_model.h new file mode 100644 index 0000000000..ea19a5c165 --- /dev/null +++ b/repos/os/src/lib/sandbox/config_model.h @@ -0,0 +1,283 @@ +/* + * \brief Internal model of the XML configuration + * \author Norman Feske + * \date 2021-04-01 + */ + +/* + * Copyright (C) 2021 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _CONFIG_MODEL_H_ +#define _CONFIG_MODEL_H_ + +/* local includes */ +#include +#include + +namespace Sandbox { + + struct Parent_provides_model; + struct Start_model; + struct Service_model; + struct Config_model; +} + + +struct Sandbox::Parent_provides_model : Noncopyable +{ + struct Factory : Interface, Noncopyable + { + virtual Parent_service &create_parent_service(Service::Name const &) = 0; + }; + + Allocator &_alloc; + Verbose const &_verbose; + Factory &_factory; + + struct Node : Noncopyable, List_model::Element + { + Parent_service &service; + + Node(Factory &factory, Service::Name const &name) + : + service(factory.create_parent_service(name)) + { } + + ~Node() + { + /* + * The destruction of the 'Parent_service' is deferred to the + * handling of abandoned entries of the 'Registry'. + */ + service.abandon(); + } + + bool matches(Xml_node const &xml) const + { + return xml.attribute_value("name", Service::Name()) == service.name(); + }; + }; + + List_model _model { }; + + Parent_provides_model(Allocator &alloc, Verbose const &verbose, Factory &factory) + : + _alloc(alloc), _verbose(verbose), _factory(factory) + { } + + void update_from_xml(Xml_node const &xml) + { + bool first_log = true; + + auto create = [&] (Xml_node const &xml) -> Node & + { + Service::Name const name = xml.attribute_value("name", Service::Name()); + + if (_verbose.enabled()) { + if (first_log) + log("parent provides"); + + log(" service \"", name, "\""); + first_log = false; + } + + return *new (_alloc) Node(_factory, name); + }; + + auto destroy = [&] (Node &node) { Genode::destroy(_alloc, &node); }; + + auto update = [&] (Node &, Xml_node const &) { }; + + try { + update_list_model_from_xml(_model, xml, create, destroy, update); + } catch (...) { + error("unable to apply complete configuration"); + } + } +}; + + +struct Sandbox::Start_model : Noncopyable +{ + /* + * The 'Start_model' represents both '' nodes and '' nodes + * because both node types share the same name space. + */ + + typedef Child_policy::Name Name; + typedef Child::Version Version; + + static char const *start_type() { return "start"; } + static char const *alias_type() { return "alias"; } + + struct Factory : Interface + { + class Creation_failed : Exception { }; + + virtual bool ready_to_create_child(Name const &, Version const &) const = 0; + + /* + * \throw Creation_failed + */ + virtual Child &create_child(Xml_node const &start) = 0; + + virtual void update_child(Child &child, Xml_node const &start) = 0; + + /* + * \throw Creation_failed + */ + virtual Alias &create_alias(Name const &) = 0; + + virtual void destroy_alias(Alias &) = 0; + }; + + Name const _name; + Version const _version; + + Factory &_factory; + + bool _alias = false; + + struct { Child *_child_ptr = nullptr; }; + struct { Alias *_alias_ptr = nullptr; }; + + void _reset() + { + if (_child_ptr) _child_ptr->abandon(); + if (_alias_ptr) _factory.destroy_alias(*_alias_ptr); + + _child_ptr = nullptr; + _alias_ptr = nullptr; + } + + bool matches(Xml_node const &xml) const + { + return _name == xml.attribute_value("name", Name()) + && _version == xml.attribute_value("version", Version()); + } + + Start_model(Factory &factory, Xml_node const &xml) + : + _name(xml.attribute_value("name", Name())), + _version(xml.attribute_value("version", Version())), + _factory(factory) + { } + + ~Start_model() { _reset(); } + + void update_from_xml(Xml_node const &xml) + { + /* handle case where the node keeps the name but changes the type */ + + bool const orig_alias = _alias; + _alias = xml.has_type("alias"); + if (orig_alias != _alias) + _reset(); + + /* create alias or child depending of the node type */ + + if (_alias) { + + if (!_alias_ptr) + _alias_ptr = &_factory.create_alias(_name); + + } else { + + if (!_child_ptr && _factory.ready_to_create_child(_name, _version)) + _child_ptr = &_factory.create_child(xml); + } + + /* update */ + + if (_alias_ptr) + _alias_ptr->update(xml); + + if (_child_ptr) + _factory.update_child(*_child_ptr, xml); + } + + void apply_child_restart(Xml_node const &xml) + { + if (_child_ptr && _child_ptr->restart_scheduled()) { + + /* tear down */ + _child_ptr->abandon(); + _child_ptr = nullptr; + + /* respawn */ + update_from_xml(xml); + } + } + + void trigger_start_child() + { + if (_child_ptr) + _child_ptr->try_start(); + } +}; + + +struct Sandbox::Service_model : Interface, Noncopyable +{ + struct Factory : Interface, Noncopyable + { + virtual Service_model &create_service(Xml_node const &) = 0; + virtual void destroy_service(Service_model &) = 0; + }; + + virtual void update_from_xml(Xml_node const &) = 0; + + virtual bool matches(Xml_node const &) = 0; +}; + + +class Sandbox::Config_model : Noncopyable +{ + private: + + struct Node; + + List_model _model { }; + + struct Parent_provides_node; + struct Default_route_node; + struct Default_node; + struct Affinity_space_node; + struct Start_node; + struct Report_node; + struct Resource_node; + struct Heartbeat_node; + struct Service_node; + + public: + + typedef State_reporter::Version Version; + + void update_from_xml(Xml_node const &, + Allocator &, + Reconstructible &, + Version &, + Preservation &, + Constructible &, + Cap_quota &, + Prio_levels &, + Constructible &, + Start_model::Factory &, + Parent_provides_model::Factory &, + Service_model::Factory &, + State_reporter &, + Heartbeat &); + + void apply_children_restart(Xml_node const &); + + /* + * Call 'Child::try_start' for each child in start-node order + */ + void trigger_start_children(); +}; + +#endif /* _CONFIG_MODEL_H_ */ diff --git a/repos/os/src/lib/sandbox/heartbeat.h b/repos/os/src/lib/sandbox/heartbeat.h index 64ab915ca6..4f445508d0 100644 --- a/repos/os/src/lib/sandbox/heartbeat.h +++ b/repos/os/src/lib/sandbox/heartbeat.h @@ -64,23 +64,23 @@ class Sandbox::Heartbeat : Noncopyable _timer_handler(_env.ep(), *this, &Heartbeat::_handle_timer) { } - void apply_config(Xml_node config) + void disable() { - bool const enabled = config.has_sub_node("heartbeat"); + _timer.destruct(); + _rate_ms = 0; + } - _timer.conditional(enabled, _env); - - if (!enabled) { - _rate_ms = 0; - return; + void apply_config(Xml_node heartbeat) + { + if (!_timer.constructed()) { + _timer.construct(_env); + _timer->sigh(_timer_handler); } - unsigned const rate_ms = - config.sub_node("heartbeat").attribute_value("rate_ms", 1000UL); + unsigned const rate_ms = heartbeat.attribute_value("rate_ms", 1000UL); if (rate_ms != _rate_ms) { _rate_ms = rate_ms; - _timer->sigh(_timer_handler); _timer->trigger_periodic(_rate_ms*1000); } } diff --git a/repos/os/src/lib/sandbox/library.cc b/repos/os/src/lib/sandbox/library.cc index 5023c06ece..21e1245f08 100644 --- a/repos/os/src/lib/sandbox/library.cc +++ b/repos/os/src/lib/sandbox/library.cc @@ -5,7 +5,7 @@ */ /* - * Copyright (C) 2010-2020 Genode Labs GmbH + * Copyright (C) 2010-2021 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. @@ -20,12 +20,15 @@ #include #include #include +#include struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer, ::Sandbox::Child::Default_route_accessor, ::Sandbox::Child::Default_caps_accessor, ::Sandbox::Child::Ram_limit_accessor, - ::Sandbox::Child::Cap_limit_accessor + ::Sandbox::Child::Cap_limit_accessor, + ::Sandbox::Start_model::Factory, + ::Sandbox::Parent_provides_model::Factory { using Routed_service = ::Sandbox::Routed_service; using Parent_service = ::Sandbox::Parent_service; @@ -41,6 +44,9 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer, using Prio_levels = ::Sandbox::Prio_levels; using Ram_info = ::Sandbox::Ram_info; using Cap_info = ::Sandbox::Cap_info; + using Config_model = ::Sandbox::Config_model; + using Start_model = ::Sandbox::Start_model; + using Preservation = ::Sandbox::Preservation; Env &_env; Heap &_heap; @@ -50,77 +56,82 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer, Registry &_local_services; Child_registry _children { }; - Reconstructible _verbose { }; + /* + * Global parameters obtained from config + */ + Reconstructible _verbose { }; + Config_model::Version _version { }; + Constructible _default_route { }; + Cap_quota _default_caps { 0 }; + Prio_levels _prio_levels { }; + Constructible _affinity_space { }; + Preservation _preservation { }; - Constructible _default_route { }; - - Cap_quota _default_caps { 0 }; - - unsigned _child_cnt = 0; - - static Ram_quota _preserved_ram_from_config(Xml_node config) + Affinity::Space _effective_affinity_space() const { - Number_of_bytes preserve { 40*sizeof(long)*1024 }; - - config.for_each_sub_node("resource", [&] (Xml_node node) { - if (node.attribute_value("name", String<16>()) == "RAM") - preserve = node.attribute_value("preserve", preserve); }); - - return Ram_quota { preserve }; + return _affinity_space.constructed() ? *_affinity_space + : Affinity::Space { 1, 1 }; } - Ram_quota _preserved_ram { 0 }; - Cap_quota _preserved_caps { 0 }; + State_reporter _state_reporter; + + Heartbeat _heartbeat { _env, _children, _state_reporter }; + + /* + * Internal representation of the XML configuration + */ + Config_model _config_model { }; + + /* + * Variables for tracking the side effects of updating the config model + */ + bool _server_appeared_or_disappeared = false; + bool _state_report_outdated = false; + + unsigned _child_cnt = 0; Ram_quota _avail_ram() const { Ram_quota avail_ram = _env.pd().avail_ram(); - if (_preserved_ram.value > avail_ram.value) { + if (_preservation.ram.value > avail_ram.value) { error("RAM preservation exceeds available memory"); return Ram_quota { 0 }; } /* deduce preserved quota from available quota */ - return Ram_quota { avail_ram.value - _preserved_ram.value }; - } - - static Cap_quota _preserved_caps_from_config(Xml_node config) - { - size_t preserve = 20; - - config.for_each_sub_node("resource", [&] (Xml_node node) { - if (node.attribute_value("name", String<16>()) == "CAP") - preserve = node.attribute_value("preserve", preserve); }); - - return Cap_quota { preserve }; + return Ram_quota { avail_ram.value - _preservation.ram.value }; } Cap_quota _avail_caps() const { Cap_quota avail_caps { _env.pd().avail_caps().value }; - if (_preserved_caps.value > avail_caps.value) { + if (_preservation.caps.value > avail_caps.value) { error("Capability preservation exceeds available capabilities"); return Cap_quota { 0 }; } /* deduce preserved quota from available quota */ - return Cap_quota { avail_caps.value - _preserved_caps.value }; + return Cap_quota { avail_caps.value - _preservation.caps.value }; } /** * Child::Ram_limit_accessor interface */ - Ram_quota resource_limit(Ram_quota const &) const override { return _avail_ram(); } + Ram_quota resource_limit(Ram_quota const &) const override + { + return _avail_ram(); + } /** * Child::Cap_limit_accessor interface */ Cap_quota resource_limit(Cap_quota const &) const override { return _avail_caps(); } - void _handle_resource_avail() { } - + /** + * State_reporter::Producer interface + */ void produce_state_report(Xml_generator &xml, Report_detail const &detail) const override { if (detail.init_ram()) @@ -133,6 +144,9 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer, _children.report_state(xml, detail); } + /** + * State_reporter::Producer interface + */ Child::Sample_state_result sample_children_state() override { return _children.sample_state(); @@ -152,18 +166,57 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer, */ Cap_quota default_caps() override { return _default_caps; } - State_reporter _state_reporter; - - Heartbeat _heartbeat { _env, _children, _state_reporter }; - void _update_aliases_from_config(Xml_node const &); void _update_parent_services_from_config(Xml_node const &); - void _abandon_obsolete_children(Xml_node const &); void _update_children_config(Xml_node const &); void _destroy_abandoned_parent_services(); + void _destroy_abandoned_children(); Server _server { _env, _heap, _child_services, _state_reporter }; + /** + * Sandbox::Start_model::Factory + */ + Child &create_child(Xml_node const &) override; + + /** + * Sandbox::Start_model::Factory + */ + void update_child(Child &, Xml_node const &) override; + + /** + * Sandbox::Start_model::Factory + */ + Alias &create_alias(Child_policy::Name const &name) override + { + Alias &alias = *new (_heap) Alias(name); + _children.insert_alias(&alias); + return alias; + } + + /** + * Sandbox::Start_model::Factory + */ + void destroy_alias(Alias &alias) override + { + _children.remove_alias(&alias); + destroy(_heap, &alias); + } + + /** + * Sandbox::Start_model::Factory + */ + bool ready_to_create_child(Start_model::Name const &, + Start_model::Version const &) const override; + + /** + * Sandbox::Parent_provides_model::Factory + */ + Parent_service &create_parent_service(Service::Name const &name) override + { + return *new (_heap) Parent_service(_parent_services, _env, name); + } + Library(Env &env, Heap &heap, Registry &local_services, State_handler &state_handler) : @@ -180,52 +233,6 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer, }; -void Genode::Sandbox::Library::_update_parent_services_from_config(Xml_node const &config) -{ - Xml_node const node = config.has_sub_node("parent-provides") - ? config.sub_node("parent-provides") - : Xml_node(""); - - /* 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(); - }); - - /* used to prepend the list of new parent services with title */ - bool first_log = true; - - /* 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) ::Sandbox::Parent_service(_parent_services, _env, name); - if (_verbose->enabled()) { - if (first_log) - log("parent provides"); - log(" service \"", name, "\""); - first_log = false; - } - } - }); -} - - void Genode::Sandbox::Library::_destroy_abandoned_parent_services() { _parent_services.for_each([&] (Parent_service &service) { @@ -234,109 +241,8 @@ void Genode::Sandbox::Library::_destroy_abandoned_parent_services() } -void Genode::Sandbox::Library::_update_aliases_from_config(Xml_node const &config) +void Genode::Sandbox::Library::_destroy_abandoned_children() { - /* remove all known aliases */ - while (_children.any_alias()) { - ::Sandbox::Alias *alias = _children.any_alias(); - _children.remove_alias(alias); - destroy(_heap, alias); - } - - /* create aliases */ - config.for_each_sub_node("alias", [&] (Xml_node alias_node) { - - try { - _children.insert_alias(new (_heap) Alias(alias_node)); } - catch (Alias::Name_is_missing) { - warning("missing 'name' attribute in '' entry"); } - catch (Alias::Child_is_missing) { - warning("missing 'child' attribute in '' entry"); } - }); -} - - -void Genode::Sandbox::Library::_abandon_obsolete_children(Xml_node const &config) -{ - _children.for_each_child([&] (Child &child) { - - bool obsolete = true; - config.for_each_sub_node("start", [&] (Xml_node node) { - if (child.has_name (node.attribute_value("name", Child_policy::Name())) - && child.has_version(node.attribute_value("version", Child::Version()))) - obsolete = false; }); - - if (obsolete) - child.abandon(); - }); -} - - -void Genode::Sandbox::Library::_update_children_config(Xml_node const &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.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.abandoned() && 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 Genode::Sandbox::Library::apply_config(Xml_node const &config) -{ - bool update_state_report = false; - - _preserved_ram = _preserved_ram_from_config(config); - _preserved_caps = _preserved_caps_from_config(config); - - _verbose.construct(config); - _state_reporter.apply_config(config); - _heartbeat.apply_config(config); - - /* determine default route for resolving service requests */ - try { - _default_route.construct(_heap, config.sub_node("default-route")); } - catch (...) { } - - _default_caps = Cap_quota { 0 }; - try { - _default_caps = Cap_quota { config.sub_node("default") - .attribute_value("caps", 0UL) }; } - catch (...) { } - - Prio_levels const prio_levels = ::Sandbox::prio_levels_from_xml(config); - Affinity::Space const affinity_space = ::Sandbox::affinity_space_from_xml(config); - bool const space_defined = config.has_sub_node("affinity-space"); - - _update_aliases_from_config(config); - _update_parent_services_from_config(config); - _abandon_obsolete_children(config); - _update_children_config(config); - - /* kill abandoned children */ _children.for_each_child([&] (Child &child) { if (!child.abandoned()) @@ -345,7 +251,7 @@ void Genode::Sandbox::Library::apply_config(Xml_node const &config) /* make the child's services unavailable */ child.destroy_services(); child.close_all_sessions(); - update_state_report = true; + _state_report_outdated = true; /* destroy child once all environment sessions are gone */ if (child.env_sessions_closed()) { @@ -353,123 +259,174 @@ void Genode::Sandbox::Library::apply_config(Xml_node const &config) destroy(_heap, &child); } }); +} - _destroy_abandoned_parent_services(); - /* initial RAM and caps limit before starting new children */ - Ram_quota const avail_ram = _avail_ram(); - Cap_quota const avail_caps = _avail_caps(); +bool Genode::Sandbox::Library::ready_to_create_child(Start_model::Name const &name, + Start_model::Version const &version) const +{ + bool exists = false; - /* variable used to track the RAM and caps taken by new started children */ - Ram_quota used_ram { 0 }; - Cap_quota used_caps { 0 }; + unsigned num_abandoned = 0; + + _children.for_each_child([&] (Child const &child) { + if (child.name() == name && child.has_version(version)) { + if (child.abandoned()) + num_abandoned++; + else + exists = true; + } + }); + + /* defer child creation if corresponding child already exists */ + if (exists) + return false; + + /* prevent queuing up abandoned children with the same name */ + if (num_abandoned > 1) + return false; + + return true; +} + + +::Sandbox::Child &Genode::Sandbox::Library::create_child(Xml_node const &start_node) +{ + if (!_affinity_space.constructed() && start_node.has_sub_node("affinity")) + warning("affinity-space configuration missing, " + "but affinity defined for child ", + start_node.attribute_value("name", Child_policy::Name())); - /* create new children */ try { - config.for_each_sub_node("start", [&] (Xml_node start_node) { + Child &child = *new (_heap) + Child(_env, _heap, *_verbose, + Child::Id { ++_child_cnt }, _state_reporter, + start_node, *this, *this, _children, + *this, *this, _prio_levels, _effective_affinity_space(), + _parent_services, _child_services, _local_services); + _children.insert(&child); - bool exists = false; + if (start_node.has_sub_node("provides")) + _server_appeared_or_disappeared = true; - unsigned num_abandoned = 0; + _state_report_outdated = true; - typedef Child_policy::Name Name; - Name const child_name = start_node.attribute_value("name", Name()); - - _children.for_each_child([&] (Child const &child) { - if (child.name() == child_name) { - if (child.abandoned()) - num_abandoned++; - else - exists = true; - } - }); - - /* skip start node if corresponding child already exists */ - if (exists) - return; - - /* prevent queuing up abandoned children with the same name */ - if (num_abandoned > 1) - return; - - if (used_ram.value > avail_ram.value) { - error("RAM exhausted while starting child: ", child_name); - return; - } - - if (used_caps.value > avail_caps.value) { - error("capabilities exhausted while starting child: ", child_name); - return; - } - - if (!space_defined && start_node.has_sub_node("affinity")) { - warning("affinity-space configuration missing, " - "but affinity defined for child: ", child_name); - } - - try { - Child &child = *new (_heap) - Child(_env, _heap, *_verbose, - Child::Id { ++_child_cnt }, _state_reporter, - start_node, *this, *this, _children, - Ram_quota { avail_ram.value - used_ram.value }, - Cap_quota { avail_caps.value - used_caps.value }, - *this, *this, prio_levels, affinity_space, - _parent_services, _child_services, _local_services); - _children.insert(&child); - - update_state_report = true; - - /* account for the start XML node buffered in the child */ - size_t const metadata_overhead = start_node.size() + sizeof(Child); - - /* track used memory and RAM limit */ - used_ram = Ram_quota { used_ram.value - + child.ram_quota().value - + metadata_overhead }; - - used_caps = Cap_quota { used_caps.value - + child.cap_quota().value }; - } - catch (Rom_connection::Rom_connection_failed) { - /* - * The binary does not exist. An error message is printed - * by the Rom_connection constructor. - */ - } - catch (Out_of_ram) { - warning("memory exhausted during child creation"); } - catch (Out_of_caps) { - warning("local capabilities exhausted during child creation"); } - catch (Child::Missing_name_attribute) { - warning("skipped startup of nameless child"); } - catch (Region_map::Region_conflict) { - warning("failed to attach dataspace to local address space " - "during child construction"); } - catch (Region_map::Invalid_dataspace) { - warning("attempt to attach invalid dataspace to local address space " - "during child construction"); } - catch (Service_denied) { - warning("failed to create session during child construction"); } - }); + return child; } - catch (Xml_node::Nonexistent_sub_node) { error("no children to start"); } - catch (Xml_node::Invalid_syntax) { error("config has invalid syntax"); } - catch (Child_registry::Alias_name_is_not_unique) { } + catch (Rom_connection::Rom_connection_failed) { + /* + * The binary does not exist. An error message is printed + * by the Rom_connection constructor. + */ + } + catch (Out_of_ram) { + warning("memory exhausted during child creation"); } + catch (Out_of_caps) { + warning("local capabilities exhausted during child creation"); } + catch (Child::Missing_name_attribute) { + warning("skipped startup of nameless child"); } + catch (Region_map::Region_conflict) { + warning("failed to attach dataspace to local address space " + "during child construction"); } + catch (Region_map::Invalid_dataspace) { + warning("attempt to attach invalid dataspace to local address space " + "during child construction"); } + catch (Service_denied) { + warning("failed to create session during child construction"); } + + throw ::Sandbox::Start_model::Factory::Creation_failed(); +} + + +void Genode::Sandbox::Library::update_child(Child &child, Xml_node const &start) +{ + if (child.abandoned()) + return; + + switch (child.apply_config(start)) { + + case Child::NO_SIDE_EFFECTS: break; + + case Child::PROVIDED_SERVICES_CHANGED: + _server_appeared_or_disappeared = true; + _state_report_outdated = true; + break; + }; +} + + +void Genode::Sandbox::Library::apply_config(Xml_node const &config) +{ + _server_appeared_or_disappeared = false; + _state_report_outdated = false; + + _config_model.update_from_xml(config, + _heap, + _verbose, + _version, + _preservation, + _default_route, + _default_caps, + _prio_levels, + _affinity_space, + *this, *this, _server, + _state_reporter, + _heartbeat); /* - * Initiate RAM sessions of all new children + * After importing the new configuration, servers may have disappeared + * (STATE_ABANDONED) or become new available. + * + * Re-evaluate the dependencies of the existing children. + * + * - Stuck children (STATE_STUCK) may become alive. + * - Children with broken dependencies may have become stuck. + * - Children with changed dependencies need a restart. + * + * Children are restarted 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 child gets scheduled to be + * restarted in one iteration over all children. */ - _children.for_each_child([&] (Child &child) { - if (!child.abandoned()) - child.initiate_env_pd_session(); }); + while (true) { - /* - * Initiate remaining environment sessions of all new children - */ - _children.for_each_child([&] (Child &child) { - if (!child.abandoned()) - child.initiate_env_sessions(); }); + bool any_restart_scheduled = false; + + _children.for_each_child([&] (Child &child) { + + if (child.abandoned()) + return; + + if (child.restart_scheduled()) { + any_restart_scheduled = true; + return; + } + + if (_server_appeared_or_disappeared || child.uncertain_dependencies()) + child.evaluate_dependencies(); + + if (child.restart_scheduled()) + any_restart_scheduled = true; + }); + + /* + * Release resources captured by abandoned children before starting + * new children. The children must be started in the order of their + * start nodes for the assignment of slack RAM. + */ + _destroy_abandoned_parent_services(); + _destroy_abandoned_children(); + + _config_model.trigger_start_children(); + + if (any_restart_scheduled) + _config_model.apply_children_restart(config); + + if (!any_restart_scheduled) + break; + } + + _server.apply_updated_policy(); /* * (Re-)distribute RAM and capability quota among the children, given their @@ -480,9 +437,7 @@ void Genode::Sandbox::Library::apply_config(Xml_node const &config) _children.for_each_child([&] (Child &child) { child.apply_downgrade(); }); _children.for_each_child([&] (Child &child) { child.apply_upgrade(); }); - _server.apply_config(config); - - if (update_state_report) + if (_state_report_outdated) _state_reporter.trigger_immediate_report_update(); } diff --git a/repos/os/src/lib/sandbox/server.cc b/repos/os/src/lib/sandbox/server.cc index 39ea53a5f1..f2fbc3c625 100644 --- a/repos/os/src/lib/sandbox/server.cc +++ b/repos/os/src/lib/sandbox/server.cc @@ -21,19 +21,21 @@ /****************************** ** Sandbox::Server::Service ** - **********...*****************/ + ******************************/ -struct Sandbox::Server::Service +struct Sandbox::Server::Service : Service_model { + typedef Genode::Service::Name Name; + + Name const _name; + Registry::Element _registry_element; - Buffered_xml _service_node; - - typedef Genode::Service::Name Name; + Allocator &_alloc; Registry &_child_services; - Name const _name { _service_node.xml().attribute_value("name", Name()) }; + Constructible _service_node { }; /** * Constructor @@ -45,11 +47,28 @@ struct Sandbox::Server::Service Xml_node service_node, Registry &child_services) : + _name(service_node.attribute_value("name", Name())), _registry_element(services, *this), - _service_node(alloc, service_node), + _alloc(alloc), _child_services(child_services) { } + /** + * Service_model interface + */ + void update_from_xml(Xml_node const &service_node) override + { + _service_node.construct(_alloc, service_node); + } + + /** + * Service_model interface + */ + bool matches(Xml_node const &service_node) override + { + return _name == service_node.attribute_value("name", Name()); + } + /** * Determine route to child service for a given label according * to the node policy @@ -65,8 +84,11 @@ struct Sandbox::Server::Service Sandbox::Server::Route Sandbox::Server::Service::resolve_session_request(Session_label const &label) { + if (!_service_node.constructed()) + throw Service_denied(); + try { - Session_policy policy(label, _service_node.xml()); + Session_policy policy(label, _service_node->xml()); if (!policy.has_sub_node("child")) throw Service_denied(); @@ -365,13 +387,20 @@ void Sandbox::Server::_handle_session_requests() } -void Sandbox::Server::apply_config(Xml_node config) +Sandbox::Service_model &Sandbox::Server::create_service(Xml_node const &node) { - _services.for_each([&] (Service &service) { destroy(_alloc, &service); }); + return *new (_alloc) Service(_services, _alloc, node, _child_services); +} - config.for_each_sub_node("service", [&] (Xml_node node) { - new (_alloc) Service(_services, _alloc, node, _child_services); }); +void Sandbox::Server::destroy_service(Service_model &service) +{ + destroy(_alloc, &service); +} + + +void Sandbox::Server::apply_updated_policy() +{ /* * Construct mechanics for responding to our parent's session requests * on demand. diff --git a/repos/os/src/lib/sandbox/server.h b/repos/os/src/lib/sandbox/server.h index 44777c7cee..ce231ce28f 100644 --- a/repos/os/src/lib/sandbox/server.h +++ b/repos/os/src/lib/sandbox/server.h @@ -19,15 +19,17 @@ #include /* local includes */ -#include "types.h" -#include "service.h" -#include "state_reporter.h" +#include +#include +#include +#include namespace Sandbox { class Server; } class Sandbox::Server : Session_state::Ready_callback, - Session_state::Closed_callback + Session_state::Closed_callback, + public Service_model::Factory { private: @@ -113,7 +115,17 @@ class Sandbox::Server : Session_state::Ready_callback, _report_update_trigger(report_update_trigger) { } - void apply_config(Xml_node); + void apply_updated_policy(); + + /** + * Service_model::Factory + */ + Service_model &create_service(Xml_node const &) override; + + /** + * Service_model::Factory + */ + void destroy_service(Service_model &) override; }; #endif /* _LIB__SANDBOX__SERVER_H_ */ diff --git a/repos/os/src/lib/sandbox/state_reporter.h b/repos/os/src/lib/sandbox/state_reporter.h index abb65e0fd6..bc0d6d5ee1 100644 --- a/repos/os/src/lib/sandbox/state_reporter.h +++ b/repos/os/src/lib/sandbox/state_reporter.h @@ -29,6 +29,8 @@ class Sandbox::State_reporter : public Report_update_trigger { public: + typedef String<64> Version; + struct Producer : Interface { virtual void produce_state_report(Xml_generator &xml, @@ -53,7 +55,6 @@ class Sandbox::State_reporter : public Report_update_trigger uint64_t _report_period_ms = 0; /* version string from config, to be reflected in the report */ - typedef String<64> Version; Version _version { }; Constructible _timer { }; @@ -111,22 +112,18 @@ class Sandbox::State_reporter : public Report_update_trigger _producer.produce_state_report(xml, *_report_detail); } - void apply_config(Xml_node config) + void apply_config(Version const &version, Xml_node const &report) { - try { - Xml_node report = config.sub_node("report"); - + if (report.type() == "report") { _report_detail.construct(report); _report_delay_ms = report.attribute_value("delay_ms", 100UL); - } - catch (Xml_node::Nonexistent_sub_node) { + } else { _report_detail.construct(); _report_delay_ms = 0; } bool trigger_update = false; - Version const version = config.attribute_value("version", Version()); if (version != _version) { _version = version; trigger_update = true; diff --git a/repos/os/src/lib/sandbox/types.h b/repos/os/src/lib/sandbox/types.h index 7e54780b6f..5d8ee16c56 100644 --- a/repos/os/src/lib/sandbox/types.h +++ b/repos/os/src/lib/sandbox/types.h @@ -14,9 +14,9 @@ #ifndef _LIB__SANDBOX__TYPES_H_ #define _LIB__SANDBOX__TYPES_H_ -#include #include -#include +#include +#include namespace Sandbox { @@ -72,6 +72,22 @@ namespace Sandbox { .avail = pd.avail_caps() }; } + struct Preservation : private Noncopyable + { + static Ram_quota default_ram() { return { 40*sizeof(long)*1024 }; } + static Cap_quota default_caps() { return { 20 }; } + + Ram_quota ram { }; + Cap_quota caps { }; + + void reset() + { + ram = default_ram(); + caps = default_caps(); + } + + Preservation() { reset(); } + }; } #endif /* _LIB__SANDBOX__TYPES_H_ */ diff --git a/repos/os/src/lib/sandbox/update_list_model.h b/repos/os/src/lib/sandbox/update_list_model.h new file mode 100644 index 0000000000..2703986826 --- /dev/null +++ b/repos/os/src/lib/sandbox/update_list_model.h @@ -0,0 +1,69 @@ +/* + * \brief Convenience wrapper around 'List_model' + * \author Norman Feske + * \date 2021-04-01 + */ + +/* + * Copyright (C) 2021 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _UPDATE_LIST_MODEL_H_ +#define _UPDATE_LIST_MODEL_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include + +namespace Sandbox { + + template + static inline void update_list_model_from_xml(List_model &, + Xml_node const &, + CREATE_FN const &, + DESTROY_FN const &, + UPDATE_FN const &); +} + + +template +void Sandbox::update_list_model_from_xml(List_model &model, + Xml_node const &xml, + CREATE_FN const &create, + DESTROY_FN const &destroy, + UPDATE_FN const &update) +{ + struct Model_update_policy : List_model::Update_policy + { + CREATE_FN const &_create_fn; + DESTROY_FN const &_destroy_fn; + UPDATE_FN const &_update_fn; + + Model_update_policy(CREATE_FN const &create_fn, + DESTROY_FN const &destroy_fn, + UPDATE_FN const &update_fn) + : + _create_fn(create_fn), _destroy_fn(destroy_fn), _update_fn(update_fn) + { } + + void destroy_element(NODE &node) { _destroy_fn(node); } + + NODE &create_element(Xml_node xml) { return _create_fn(xml); } + + void update_element(NODE &node, Xml_node xml) { _update_fn(node, xml); } + + static bool element_matches_xml_node(NODE const &node, Xml_node xml) + { + return node.matches(xml); + } + } policy(create, destroy, update); + + model.update_from_xml(policy, xml); +} + +#endif /* _UPDATE_LIST_MODEL_H_ */ diff --git a/repos/os/src/lib/sandbox/utils.h b/repos/os/src/lib/sandbox/utils.h index a21fcca8ea..853cb6da0f 100644 --- a/repos/os/src/lib/sandbox/utils.h +++ b/repos/os/src/lib/sandbox/utils.h @@ -124,7 +124,8 @@ namespace Sandbox { /* count number of services with the specified name */ unsigned cnt = 0; services.for_each([&] (T const &service) { - cnt += (service.name() == name); }); + if (!service.abandoned()) + cnt += (service.name() == name); }); return cnt > 1; } @@ -164,7 +165,7 @@ namespace Sandbox { /** * Read priority-levels declaration from config */ - inline Prio_levels prio_levels_from_xml(Xml_node config) + inline Prio_levels prio_levels_from_xml(Xml_node const &config) { long const prio_levels = config.attribute_value("prio_levels", 0UL); @@ -233,24 +234,6 @@ namespace Sandbox { } catch (...) { return Location(0, 0, space.width(), space.height()); } } - - - /** - * Read affinity-space parameters from config - * - * If no affinity space is declared, construct a space with a single element, - * width and height being 1. If only one of both dimensions is specified, the - * other dimension is set to 1. - */ - inline Affinity::Space affinity_space_from_xml(Xml_node config) - { - try { - Xml_node node = config.sub_node("affinity-space"); - return Affinity::Space(node.attribute_value("width", 1), - node.attribute_value("height", 1)); - } catch (...) { - return Affinity::Space(1, 1); } - } } #endif /* _LIB__SANDBOX__UTILS_H_ */ diff --git a/repos/os/src/lib/sandbox/verbose.h b/repos/os/src/lib/sandbox/verbose.h index db0c7adb2e..ab3e6050a6 100644 --- a/repos/os/src/lib/sandbox/verbose.h +++ b/repos/os/src/lib/sandbox/verbose.h @@ -34,7 +34,7 @@ class Sandbox::Verbose : Genode::Noncopyable Verbose() { } - Verbose(Genode::Xml_node config) + Verbose(Xml_node const &config) : _enabled(config.attribute_value("verbose", false)) { } bool enabled() const { return _enabled; }