diff --git a/repos/os/run/init.run b/repos/os/run/init.run index b022f21297..18c7d29274 100644 --- a/repos/os/run/init.run +++ b/repos/os/run/init.run @@ -456,7 +456,7 @@ append config { - + @@ -467,6 +467,236 @@ append config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/repos/os/src/app/dummy/main.cc b/repos/os/src/app/dummy/main.cc index 7526ad3f16..b010eab292 100644 --- a/repos/os/src/app/dummy/main.cc +++ b/repos/os/src/app/dummy/main.cc @@ -24,6 +24,8 @@ namespace Dummy { struct Log_service; struct Log_connections; + struct Ram_consumer; + struct Resource_yield_handler; struct Main; using namespace Genode; } @@ -109,6 +111,64 @@ struct Dummy::Log_connections }; +struct Dummy::Ram_consumer +{ + size_t _amount = 0; + + Ram_dataspace_capability _ds_cap; + + Ram_session &_ram; + + Ram_consumer(Ram_session &ram) : _ram(ram) { } + + void release() + { + if (!_amount) + return; + + log("release ", Number_of_bytes(_amount), " bytes of memory"); + _ram.free(_ds_cap); + + _ds_cap = Ram_dataspace_capability(); + _amount = 0; + } + + void consume(size_t amount) + { + if (_amount) + release(); + + log("consume ", Number_of_bytes(amount), " bytes of memory"); + _ds_cap = _ram.alloc(amount); + _amount = amount; + } +}; + + +struct Dummy::Resource_yield_handler +{ + Env &_env; + + Ram_consumer &_ram_consumer; + + void _handle_yield() + { + log("got yield request"); + _ram_consumer.release(); + _env.parent().yield_response(); + } + + Signal_handler _yield_handler { + _env.ep(), *this, &Resource_yield_handler::_handle_yield }; + + Resource_yield_handler(Env &env, Ram_consumer &ram_consumer) + : _env(env), _ram_consumer(ram_consumer) + { + _env.parent().yield_sigh(_yield_handler); + } +}; + + struct Dummy::Main { Env &_env; @@ -125,6 +185,10 @@ struct Dummy::Main Signal_handler
_config_handler { _env.ep(), *this, &Main::_handle_config }; + Ram_consumer _ram_consumer { _env.ram() }; + + Constructible _resource_yield_handler; + void _handle_config() { _config.update(); @@ -150,6 +214,12 @@ struct Dummy::Main if (node.type() == "log_service") _log_service.construct(_env); + if (node.type() == "consume_ram") + _ram_consumer.consume(node.attribute_value("amount", Number_of_bytes())); + + if (node.type() == "handle_yield_requests") + _resource_yield_handler.construct(_env, _ram_consumer); + if (node.type() == "sleep") { if (!_timer.constructed()) diff --git a/repos/os/src/init/child.cc b/repos/os/src/init/child.cc index a9faa91e7b..90680b6333 100644 --- a/repos/os/src/init/child.cc +++ b/repos/os/src/init/child.cc @@ -15,7 +15,8 @@ #include -Init::Child::Apply_config_result Init::Child::apply_config(Xml_node start_node) +Init::Child::Apply_config_result +Init::Child::apply_config(Xml_node start_node) { Child_policy &policy = *this; @@ -156,6 +157,109 @@ Init::Child::Apply_config_result Init::Child::apply_config(Xml_node start_node) } +static Init::Ram_quota assigned_ram_from_start_node(Genode::Xml_node start_node) +{ + using namespace Init; + + size_t assigned = 0; + + start_node.for_each_sub_node("resource", [&] (Xml_node resource) { + if (resource.attribute_value("name", String<8>()) == "RAM") + assigned = resource.attribute_value("quantum", Number_of_bytes()); }); + + return Ram_quota { assigned }; +} + + +void Init::Child::apply_ram_upgrade() +{ + Ram_quota const assigned_ram_quota = assigned_ram_from_start_node(_start_node->xml()); + + if (assigned_ram_quota.value <= _resources.assigned_ram_quota.value) + return; + + size_t const increase = assigned_ram_quota.value + - _resources.assigned_ram_quota.value; + size_t const limit = _ram_limit_accessor.ram_limit().value; + size_t const transfer = min(increase, limit); + + if (increase > limit) + warning(name(), ": assigned RAM exceeds available RAM"); + + /* + * Remember assignment and apply RAM upgrade to child + * + * Note that we remember the actually transferred amount as the + * assigned amount. In the case where the value is clamped to to + * the limit, the value as given in the config remains diverged + * from the assigned value. This way, a future config update will + * attempt the completion of the upgrade if memory become + * available. + */ + if (transfer) { + _resources.assigned_ram_quota = + Ram_quota { _resources.assigned_ram_quota.value + transfer }; + + _check_resource_constraints(_ram_limit_accessor.ram_limit()); + + ref_ram().transfer_quota(_child.ram_session_cap(), transfer); + } +} + + +void Init::Child::apply_ram_downgrade() +{ + Ram_quota const assigned_ram_quota = assigned_ram_from_start_node(_start_node->xml()); + + if (assigned_ram_quota.value >= _resources.assigned_ram_quota.value) + return; + + size_t const decrease = _resources.assigned_ram_quota.value + - assigned_ram_quota.value; + + /* + * The child may concurrently consume quota from its RAM session, + * causing the 'transfer_quota' to fail. For this reason, we repeatedly + * attempt the transfer. + */ + unsigned max_attempts = 4, attempts = 0; + for (; attempts < max_attempts; attempts++) { + + /* give up if the child's available RAM is exhausted */ + size_t const preserved = 16*1024; + size_t const avail = _child.ram().avail(); + + if (avail < preserved) + break; + + size_t const transfer = min(avail - preserved, decrease); + + if (_child.ram().transfer_quota(ref_ram_cap(), transfer) == 0) { + _resources.assigned_ram_quota = + Ram_quota { _resources.assigned_ram_quota.value - transfer }; + break; + } + } + + if (attempts == max_attempts) + warning(name(), ": RAM downgrade failed after ", max_attempts, " attempts"); + + /* + * If designated RAM quota is lower than the child's consumed RAM, issue + * a yield request to the child. + */ + if (assigned_ram_quota.value < _resources.assigned_ram_quota.value) { + + size_t const demanded = _resources.assigned_ram_quota.value + - assigned_ram_quota.value; + + Parent::Resource_args const args { "ram_quota=", Number_of_bytes(demanded) }; + _child.yield(args); + } + +} + + void Init::Child::report_state(Xml_generator &xml, Report_detail const &detail) const { xml.node("child", [&] () { @@ -174,6 +278,10 @@ void Init::Child::report_state(Xml_generator &xml, Report_detail const &detail) if (detail.child_ram() && _child.ram_session_cap().valid()) { xml.node("ram", [&] () { + + xml.attribute("assigned", String<32> { + Number_of_bytes(_resources.assigned_ram_quota.value) }); + /* * The const cast is needed because there is no const * accessor for the RAM session of the child. @@ -215,8 +323,8 @@ void Init::Child::init(Ram_session &session, Ram_session_capability cap) size_t const initial_session_costs = session_alloc_batch_size()*_child.session_factory().session_costs(); - size_t const transfer_ram = _resources.effective_ram_quota.value > initial_session_costs - ? _resources.effective_ram_quota.value - initial_session_costs + size_t const transfer_ram = _resources.effective_ram_quota().value > initial_session_costs + ? _resources.effective_ram_quota().value - initial_session_costs : 0; if (transfer_ram) _env.ram().transfer_quota(cap, transfer_ram); @@ -511,6 +619,7 @@ Init::Child::Child(Env &env, Xml_node start_node, Default_route_accessor &default_route_accessor, Name_registry &name_registry, + Ram_quota ram_limit, Ram_limit_accessor &ram_limit_accessor, long prio_levels, Affinity::Space const &affinity_space, @@ -525,13 +634,14 @@ Init::Child::Child(Env &env, _ram_limit_accessor(ram_limit_accessor), _name_registry(name_registry), _resources(_resources_from_start_node(start_node, prio_levels, affinity_space)), + _resources_checked((_check_resource_constraints(ram_limit), true)), _parent_services(parent_services), _child_services(child_services), _session_requester(_env.ep().rpc_ep(), _env.ram(), _env.rm()) { if (_verbose.enabled()) { log("child \"", _unique_name, "\""); - log(" RAM quota: ", Number_of_bytes(_resources.effective_ram_quota.value)); + log(" RAM quota: ", Number_of_bytes(_resources.effective_ram_quota().value)); log(" ELF binary: ", _binary_name); log(" priority: ", _resources.priority); } diff --git a/repos/os/src/init/child.h b/repos/os/src/init/child.h index 73183798b0..558e9988c0 100644 --- a/repos/os/src/init/child.h +++ b/repos/os/src/init/child.h @@ -125,9 +125,13 @@ class Init::Child : Child_policy, Child_service::Wakeup long priority; Affinity affinity; Ram_quota assigned_ram_quota; - Ram_quota effective_ram_quota; size_t cpu_quota_pc; bool constrain_phys; + + Ram_quota effective_ram_quota() const + { + return Ram_quota { Genode::Child::effective_ram_quota(assigned_ram_quota.value) }; + } }; Resources _resources_from_start_node(Xml_node start_node, long prio_levels, @@ -157,20 +161,17 @@ class Init::Child : Child_policy, Child_service::Wakeup Affinity(affinity_space, affinity_location_from_xml(affinity_space, start_node)), Ram_quota { ram_bytes }, - Ram_quota { Genode::Child::effective_ram_quota(ram_bytes) }, cpu_quota_pc, constrain_phys }; } Resources _resources; - void _check_resource_constraints() + void _check_resource_constraints(Ram_quota ram_limit) { - if (_resources.effective_ram_quota.value == 0) + if (_resources.effective_ram_quota().value == 0) warning("no valid RAM RESOURCE for child \"", _unique_name, "\""); - Ram_quota const ram_limit = _ram_limit_accessor.ram_limit(); - /* * If the configured RAM quota exceeds our own quota, we donate * all remaining quota to the child. @@ -183,7 +184,7 @@ class Init::Child : Child_policy, Child_service::Wakeup } } - bool const _resources_checked = (_check_resource_constraints(), true); + bool const _resources_checked; Registry &_parent_services; Registry &_child_services; @@ -335,11 +336,19 @@ class Init::Child : Child_policy, Child_service::Wakeup /** * Constructor * - * \param alloc allocator solely used for configuration-dependent - * allocations. It is not used for allocations on behalf - * of the child's behavior. + * \param alloc allocator solely used for configuration- + * dependent allocations. It is not used for + * allocations on behalf of the child's + * behavior. * - * \throw Allocator::Out_of_memory could not buffer the XML start node + * \param ram_limit maximum amount of RAM to be consumed at + * creation time. + * + * \param ram_limit_accessor interface for querying the available + * RAM, used for dynamic RAM balancing at + * runtime. + * + * \throw Allocator::Out_of_memory could not buffer the XML start node */ Child(Env &env, Allocator &alloc, @@ -349,6 +358,7 @@ class Init::Child : Child_policy, Child_service::Wakeup Xml_node start_node, Default_route_accessor &default_route_accessor, Name_registry &name_registry, + Ram_quota ram_limit, Ram_limit_accessor &ram_limit_accessor, long prio_levels, Affinity::Space const &affinity_space, @@ -411,6 +421,9 @@ class Init::Child : Child_policy, Child_service::Wakeup */ Apply_config_result apply_config(Xml_node start_node); + void apply_ram_upgrade(); + void apply_ram_downgrade(); + void report_state(Xml_generator &xml, Report_detail const &detail) const; @@ -460,6 +473,12 @@ class Init::Child : Child_policy, Child_service::Wakeup } bool initiate_env_sessions() const override { return false; } + + void yield_response() override + { + apply_ram_downgrade(); + _report_update_trigger.trigger_report_update(); + } }; #endif /* _SRC__INIT__CHILD_H_ */ diff --git a/repos/os/src/init/main.cc b/repos/os/src/init/main.cc index 6dca3e2114..415ae8f651 100644 --- a/repos/os/src/init/main.cc +++ b/repos/os/src/init/main.cc @@ -54,12 +54,25 @@ struct Init::Main : State_reporter::Producer, Child::Default_route_accessor, return Ram_quota { preserve }; } - Ram_quota _ram_limit { 0 }; + Ram_quota _avail_ram() + { + Ram_quota const preserved_ram = _preserved_ram_from_config(_config.xml()); + + Ram_quota avail_ram { _env.ram().avail() }; + + 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 }; + } /** * Child::Ram_limit_accessor interface */ - Ram_quota ram_limit() override { return _ram_limit; } + Ram_quota ram_limit() override { return _avail_ram(); } void _handle_resource_avail() { } @@ -242,6 +255,9 @@ void Init::Main::_handle_config() _default_route.construct(_heap, _config.xml().sub_node("default-route")); } catch (...) { } + long 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(); @@ -257,20 +273,8 @@ void Init::Main::_handle_config() _destroy_abandoned_parent_services(); - Ram_quota const preserved_ram = _preserved_ram_from_config(_config.xml()); - - Ram_quota avail_ram { _env.ram().avail() }; - - if (preserved_ram.value > avail_ram.value) { - error("RAM preservation exceeds available memory"); - return; - } - - /* deduce preserved quota from available quota */ - avail_ram = Ram_quota { avail_ram.value - preserved_ram.value }; - /* initial RAM limit before starting new children */ - _ram_limit = Ram_quota { avail_ram.value }; + Ram_quota const avail_ram = _avail_ram(); /* variable used to track the RAM taken by new started children */ Ram_quota used_ram { 0 }; @@ -297,9 +301,9 @@ void Init::Main::_handle_config() Init::Child &child = *new (_heap) Init::Child(_env, _heap, *_verbose, Init::Child::Id { ++_child_cnt }, _state_reporter, - start_node, *this, _children, *this, - prio_levels_from_xml(_config.xml()), - affinity_space_from_xml(_config.xml()), + start_node, *this, _children, + Ram_quota { avail_ram.value - used_ram.value }, + *this, prio_levels, affinity_space, _parent_services, _child_services); _children.insert(&child); @@ -310,7 +314,6 @@ void Init::Main::_handle_config() used_ram = Ram_quota { used_ram.value + child.ram_quota().value + metadata_overhead }; - _ram_limit = Ram_quota { avail_ram.value - used_ram.value }; } catch (Rom_connection::Rom_connection_failed) { /* @@ -346,6 +349,15 @@ void Init::Main::_handle_config() */ _children.for_each_child([&] (Child &child) { child.initiate_env_sessions(); }); + + /* + * (Re-)distribute RAM among the childen, given their resource assignments + * and the available slack memory. We first apply possible downgrades to + * free as much memory as we can. This memory is then incorporated in the + * subsequent upgrade step. + */ + _children.for_each_child([&] (Child &child) { child.apply_ram_downgrade(); }); + _children.for_each_child([&] (Child &child) { child.apply_ram_upgrade(); }); } diff --git a/repos/os/src/test/init/main.cc b/repos/os/src/test/init/main.cc index 55d2ff29a7..83f6c1319e 100644 --- a/repos/os/src/test/init/main.cc +++ b/repos/os/src/test/init/main.cc @@ -56,6 +56,11 @@ static inline bool Test::xml_attribute_matches(Xml_node condition, Xml_node node return (size_t)node.attribute_value(name.string(), Number_of_bytes()) > value; } + if (condition.has_attribute("lower")) { + size_t const value = condition.attribute_value("lower", Number_of_bytes()); + return (size_t)node.attribute_value(name.string(), Number_of_bytes()) < value; + } + error("missing condition in node"); return false; }