/* * \brief Init component * \author Norman Feske * \date 2010-04-27 */ /* * Copyright (C) 2010-2017 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. */ /* Genode includes */ #include #include /* local includes */ #include #include #include #include namespace Init { struct Main; } struct Init::Main : State_reporter::Producer, Child::Default_route_accessor, Child::Default_caps_accessor, Child::Ram_limit_accessor, Child::Cap_limit_accessor { Env &_env; Registry _parent_services { }; Registry _child_services { }; Child_registry _children { }; Heap _heap { _env.ram(), _env.rm() }; Attached_rom_dataspace _config { _env, "config" }; Xml_node _config_xml = _config.xml(); Reconstructible _verbose { _config_xml }; Constructible _default_route { }; Cap_quota _default_caps { 0 }; unsigned _child_cnt = 0; static Ram_quota _preserved_ram_from_config(Xml_node config) { 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 }; } Ram_quota _avail_ram() const { Ram_quota const preserved_ram = _preserved_ram_from_config(_config_xml); Ram_quota avail_ram = _env.pd().avail_ram(); if (preserved_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 }; } Cap_quota _avail_caps() const { Cap_quota const preserved_caps = _preserved_caps_from_config(_config_xml); Cap_quota avail_caps { _env.pd().avail_caps().value }; if (preserved_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 }; } /** * Child::Ram_limit_accessor interface */ 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() { } void produce_state_report(Xml_generator &xml, Report_detail const &detail) const override { if (detail.init_ram()) xml.node("ram", [&] () { generate_ram_info (xml, _env.pd()); }); if (detail.init_caps()) xml.node("caps", [&] () { generate_caps_info(xml, _env.pd()); }); if (detail.children()) _children.report_state(xml, detail); } /** * Default_route_accessor interface */ Xml_node default_route() override { return _default_route.constructed() ? _default_route->xml() : Xml_node(""); } /** * Default_caps_accessor interface */ Cap_quota default_caps() override { return _default_caps; } State_reporter _state_reporter { _env, *this }; Heartbeat _heartbeat { _env, _children, _state_reporter }; Signal_handler
_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
_config_handler { _env.ep(), *this, &Main::_handle_config }; Server _server { _env, _heap, _child_services, _state_reporter }; Main(Env &env) : _env(env) { _config.sigh(_config_handler); /* prevent init to block for resource upgrades (never satisfied by core) */ _env.parent().resource_avail_sigh(_resource_avail_handler); _handle_config(); } }; void Init::Main::_update_parent_services_from_config() { Xml_node const node = _config_xml.has_sub_node("parent-provides") ? _config_xml.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) Init::Parent_service(_parent_services, _env, name); if (_verbose->enabled()) { if (first_log) log("parent provides"); log(" service \"", name, "\""); first_log = false; } } }); } 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(); _children.remove_alias(alias); destroy(_heap, alias); } /* create aliases */ _config_xml.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 Init::Main::_abandon_obsolete_children() { _children.for_each_child([&] (Child &child) { bool obsolete = true; _config_xml.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 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.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 Init::Main::_handle_config() { bool update_state_report = false; _config.update(); _config_xml = _config.xml(); _verbose.construct(_config_xml); _state_reporter.apply_config(_config_xml); _heartbeat.apply_config(_config_xml); /* determine default route for resolving service requests */ try { _default_route.construct(_heap, _config_xml.sub_node("default-route")); } catch (...) { } _default_caps = Cap_quota { 0 }; try { _default_caps = Cap_quota { _config_xml.sub_node("default") .attribute_value("caps", 0UL) }; } catch (...) { } Prio_levels const prio_levels = prio_levels_from_xml(_config_xml); Affinity::Space const affinity_space = affinity_space_from_xml(_config_xml); _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()) return; /* make the child's services unavailable */ child.destroy_services(); child.close_all_sessions(); update_state_report = true; /* destroy child once all environment sessions are gone */ if (child.env_sessions_closed()) { _children.remove(&child); 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(); /* variable used to track the RAM and caps taken by new started children */ Ram_quota used_ram { 0 }; Cap_quota used_caps { 0 }; /* create new children */ try { _config_xml.for_each_sub_node("start", [&] (Xml_node start_node) { bool exists = false; unsigned num_abandoned = 0; _children.for_each_child([&] (Child const &child) { if (child.name() == start_node.attribute_value("name", Child_policy::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 childen"); return; } if (used_caps.value > avail_caps.value) { error("capabilities exhausted while starting childen"); return; } try { Init::Child &child = *new (_heap) Init::Child(_env, _heap, *_verbose, Init::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); _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(Init::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"); } }); } catch (Xml_node::Nonexistent_sub_node) { error("no children to start"); } catch (Xml_node::Invalid_syntax) { error("config has invalid syntax"); } catch (Init::Child_registry::Alias_name_is_not_unique) { } /* * Initiate RAM sessions of all new children */ _children.for_each_child([&] (Child &child) { if (!child.abandoned()) child.initiate_env_pd_session(); }); /* * Initiate remaining environment sessions of all new children */ _children.for_each_child([&] (Child &child) { if (!child.abandoned()) child.initiate_env_sessions(); }); /* * (Re-)distribute RAM and capability quota among the childen, given their * resource assignments and the available slack memory. We first apply * possible downgrades to free as much resources as we can. These resources * are then incorporated in the subsequent upgrade step. */ _children.for_each_child([&] (Child &child) { child.apply_downgrade(); }); _children.for_each_child([&] (Child &child) { child.apply_upgrade(); }); _server.apply_config(_config_xml); if (update_state_report) _state_reporter.trigger_immediate_report_update(); } void Component::construct(Genode::Env &env) { static Init::Main main(env); }