init: respond to RAM-quota changes in config

This patch improves init's dynamic reconfigurability with respect to
adjustments of the RAM quota assigned to the children.

If the RAM quota is decreased, init withdraws as much quota from the
child's RAM session as possible. If the child's RAM session does not
have enough available quota, a resource-yield request is issued to
the child. Cooparative children may respond to such a request by
releasing memory.

If the RAM quota is increased, the child's RAM session is upgraded.
If the configuration exceeds init's available RAM, init re-attempts
the upgrade whenever new slack memory becomes available (e.g., by
disappearing other children).
This commit is contained in:
Norman Feske 2017-03-06 15:19:35 +01:00 committed by Christian Helmuth
parent b3e5357cf1
commit 06943f413d
6 changed files with 481 additions and 35 deletions

View File

@ -456,7 +456,7 @@ append config {
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<sleep ms="250"/>
<sleep ms="350"/>
<!-- wait until both children are started -->
<expect_init_state>
<node name="child"> <attribute name="name" value="regular"/> </node>
@ -467,6 +467,236 @@ append config {
</expect_init_state>
<sleep ms="100"/>
<message string="test RAM-quota adjustments"/>
<init_config>
<report child_ram="yes"/>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="test">
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<config version="initial" />
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<expect_log string="[init -> test] config 1: initial"/>
<sleep ms="150"/>
<expect_init_state>
<node name="child"> <attribute name="name" value="test"/>
<node name="ram">
<attribute name="assigned" value="1M"/>
<attribute name="quota" lower="1M"/>
</node>
</node>
</expect_init_state>
<!-- increase RAM quota of child by 3 MiB -->
<init_config>
<report init_ram="yes" child_ram="yes"/>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="test">
<binary name="dummy"/>
<resource name="RAM" quantum="4M"/>
<config version="upgraded" />
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<expect_log string="[init -> test] config 2: upgraded"/>
<sleep ms="150"/>
<expect_init_state>
<node name="child"> <attribute name="name" value="test"/>
<node name="ram">
<attribute name="assigned" value="4M"/>
<attribute name="quota" higher="3M"/>
</node>
</node>
</expect_init_state>
<!-- start second child consuming all slack memory -->
<init_config>
<report init_ram="yes" child_ram="yes"/>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="test">
<binary name="dummy"/>
<resource name="RAM" quantum="4M"/>
<config/>
<route> <any-service> <parent/> </any-service> </route>
</start>
<start name="greedy">
<binary name="dummy"/>
<resource name="RAM" quantum="1G"/>
<config version="started"/>
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<expect_log string="[init -> greedy] config 1: started"/>
<sleep ms="150"/>
<expect_init_state>
<node name="ram"> <attribute name="avail" lower="1M"/> </node>
</expect_init_state>
<!-- attempt to upgrade the 'test' child to 8 MiB, hitting the RAM limit -->
<init_config>
<report init_ram="yes" child_ram="yes"/>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="test">
<binary name="dummy"/>
<resource name="RAM" quantum="8M"/>
<config version="upgrade impossible"/>
<route> <any-service> <parent/> </any-service> </route>
</start>
<start name="greedy">
<binary name="dummy"/>
<resource name="RAM" quantum="1G"/>
<config version="started"/>
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<expect_log string="[init -> test] config 4: upgrade impossible"/>
<sleep ms="150"/>
<expect_init_state>
<node name="child"> <attribute name="name" value="test"/>
<node name="ram">
<attribute name="assigned" lower="8M"/>
</node>
</node>
</expect_init_state>
<!-- kill greedy child, now the upgrade of 'test' can be completed -->
<init_config>
<report init_ram="yes" child_ram="yes"/>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="test">
<binary name="dummy"/>
<resource name="RAM" quantum="8M"/>
<config version="upgraded to 8 MiB"/>
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<expect_log string="[init -> test] config 5: upgraded to 8 MiB"/>
<sleep ms="150"/>
<expect_init_state>
<node name="child"> <attribute name="name" value="test"/>
<node name="ram">
<attribute name="assigned" value="8M"/>
<attribute name="quota" higher="7M"/>
</node>
</node>
</expect_init_state>
<!-- reduce quota -->
<init_config>
<report init_ram="yes" child_ram="yes"/>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="test">
<binary name="dummy"/>
<resource name="RAM" quantum="4M"/>
<config version="downgraded to 4 MiB"/>
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<expect_log string="[init -> test] config 6: downgraded to 4 MiB"/>
<sleep ms="150"/>
<expect_init_state>
<node name="child"> <attribute name="name" value="test"/>
<node name="ram">
<attribute name="assigned" value="4M"/>
<attribute name="quota" lower="4M"/>
</node>
</node>
</expect_init_state>
<!-- let child consume quota -->
<init_config>
<report init_ram="yes" child_ram="yes" delay_ms="250"/>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="test">
<binary name="dummy"/>
<resource name="RAM" quantum="4M"/>
<config version="consume 2 MiB">
<handle_yield_requests/>
<consume_ram amount="2M"/>
</config>
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<expect_log string="[init -> test] config 7: consume 2 MiB"/>
<expect_log string="[init -> test] consume 2M bytes of memory"/>
<sleep ms="500"/>
<expect_init_state>
<node name="child"> <attribute name="name" value="test"/>
<node name="ram">
<attribute name="avail" lower="2M"/>
</node>
</node>
</expect_init_state>
<!-- reduce child quota by 2M, triggering a resource-yield request -->
<init_config>
<report init_ram="yes" child_ram="yes" delay_ms="250"/>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="test">
<binary name="dummy"/>
<resource name="RAM" quantum="2M"/>
<config version="consume 2 MiB">
<handle_yield_requests/>
<consume_ram amount="2M"/>
</config>
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<expect_log string="[init -> test] got yield request"/>
<expect_log string="[init -> test] release 2M bytes of memory"/>
<sleep ms="150"/>
<expect_init_state>
<node name="child"> <attribute name="name" value="test"/>
<node name="ram"> <attribute name="quota" lower="2M"/> </node>
</node>
</expect_init_state>
<sleep ms="500"/>
<expect_init_state>
<node name="child"> <attribute name="name" value="test"/>
<node name="ram">
<attribute name="assigned" value="2M"/>
<attribute name="quota" lower="2M"/>
</node>
</node>
</expect_init_state>
</config>
<route>
<service name="Report"> <child name="report_rom"/> </service>

View File

@ -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<Resource_yield_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<Main> _config_handler { _env.ep(), *this, &Main::_handle_config };
Ram_consumer _ram_consumer { _env.ram() };
Constructible<Resource_yield_handler> _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())

View File

@ -15,7 +15,8 @@
#include <child.h>
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);
}

View File

@ -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_service> &_parent_services;
Registry<Routed_service> &_child_services;
@ -335,9 +336,17 @@ 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.
*
* \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
*/
@ -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_ */

View File

@ -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(); });
}

View File

@ -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 <attribute> node");
return false;
}