genode/repos/os/src/init/child.cc

667 lines
20 KiB
C++

/*
* \brief Child representation
* \author Norman Feske
* \date 2010-05-04
*/
/*
* 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.
*/
/* local includes */
#include <child.h>
Init::Child::Apply_config_result
Init::Child::apply_config(Xml_node start_node)
{
if (_state == STATE_ABANDONED)
return NO_SIDE_EFFECTS;
/*
* If the child's environment is incomplete, restart it to attempt
* the re-routing of its environment sessions.
*/
if (!_child.active()) {
abandon();
return MAY_HAVE_SIDE_EFFECTS;
}
bool provided_services_changed = false;
enum Config_update { CONFIG_APPEARED, CONFIG_VANISHED,
CONFIG_CHANGED, CONFIG_UNCHANGED };
Config_update config_update = CONFIG_UNCHANGED;
/* import new start node if new version differs */
if (start_node.size() != _start_node->xml().size() ||
Genode::memcmp(start_node.addr(), _start_node->xml().addr(),
start_node.size()) != 0)
{
/*
* Check for a change of the version attribute, force restart
* if the version changed.
*/
if (_version != start_node.attribute_value("version", Version())) {
abandon();
return MAY_HAVE_SIDE_EFFECTS;
}
/*
* Start node changed
*
* Determine how the inline config is affected.
*/
char const * const tag = "config";
bool const config_was_present = _start_node->xml().has_sub_node(tag);
bool const config_is_present = start_node.has_sub_node(tag);
if (config_was_present && !config_is_present)
config_update = CONFIG_VANISHED;
if (!config_was_present && config_is_present)
config_update = CONFIG_APPEARED;
if (config_was_present && config_is_present) {
Xml_node old_config = _start_node->xml().sub_node(tag);
Xml_node new_config = start_node.sub_node(tag);
if (Genode::memcmp(old_config.addr(), new_config.addr(),
min(old_config.size(), new_config.size())))
config_update = CONFIG_CHANGED;
}
/*
* Import updated <provides> node
*
* First abandon services that are no longer present in the
* <provides> node. Then add services that have newly appeared.
*/
_child_services.for_each([&] (Routed_service &service) {
if (!_provided_by_this(service))
return;
typedef Service::Name Name;
Name const name = service.name();
bool still_provided = false;
_provides_sub_node(start_node)
.for_each_sub_node("service", [&] (Xml_node node) {
if (name == node.attribute_value("name", Name()))
still_provided = true; });
if (!still_provided) {
service.abandon();
provided_services_changed = true;
}
});
_provides_sub_node(start_node).for_each_sub_node("service",
[&] (Xml_node node) {
if (_service_exists(node))
return;
_add_service(node);
provided_services_changed = true;
});
/*
* Import new binary name. A change may affect the route for
* the binary's ROM session, triggering the restart of the
* child.
*/
_binary_name = _binary_from_xml(start_node, _unique_name);
/* import new start node */
_start_node.construct(_alloc, start_node);
}
/*
* Apply change to '_config_rom_service'. This will
* potentially result in a change of the "config" ROM route, which
* may in turn prompt the routing-check below to abandon (restart)
* the child.
*/
switch (config_update) {
case CONFIG_UNCHANGED: break;
case CONFIG_CHANGED: _config_rom_service->trigger_update(); break;
case CONFIG_APPEARED: _config_rom_service.construct(*this); break;
case CONFIG_VANISHED: _config_rom_service->abandon(); break;
}
/* validate that the routes of all existing sessions remain intact */
{
bool routing_changed = false;
_child.for_each_session([&] (Session_state const &session) {
if (!_route_valid(session))
routing_changed = true; });
if (routing_changed) {
abandon();
return MAY_HAVE_SIDE_EFFECTS;
}
}
if (provided_services_changed)
return MAY_HAVE_SIDE_EFFECTS;
return NO_SIDE_EFFECTS;
}
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_ram_constraints(_ram_limit_accessor.ram_limit());
ref_ram().transfer_quota(_child.ram_session_cap(), Ram_quota{transfer});
/* wake up child that blocks on a resource request */
if (_requested_resources.constructed()) {
_child.notify_resource_avail();
_requested_resources.destruct();
}
}
}
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_ram().value;
if (avail < preserved)
break;
size_t const transfer = min(avail - preserved, decrease);
try {
_child.ram().transfer_quota(ref_ram_cap(), Ram_quota{transfer});
_resources.assigned_ram_quota =
Ram_quota { _resources.assigned_ram_quota.value - transfer };
break;
} catch (...) { }
}
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", [&] () {
xml.attribute("name", _unique_name);
xml.attribute("binary", _binary_name);
if (_version.valid())
xml.attribute("version", _version);
if (detail.ids())
xml.attribute("id", _id.value);
if (!_child.active())
xml.attribute("state", "incomplete");
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.
*/
auto &nonconst_child = const_cast<Genode::Child &>(_child);
generate_ram_info(xml, nonconst_child.ram());
if (_requested_resources.constructed())
xml.attribute("requested", String<32> {
Number_of_bytes(_requested_resources->ram.value) });
});
}
Session_state::Detail const
session_detail { detail.session_args() ? Session_state::Detail::ARGS
: Session_state::Detail::NO_ARGS};
if (detail.requested()) {
xml.node("requested", [&] () {
_child.for_each_session([&] (Session_state const &session) {
xml.node("session", [&] () {
session.generate_client_side_info(xml, session_detail); }); }); });
}
if (detail.provided()) {
xml.node("provided", [&] () {
auto fn = [&] (Session_state const &session) {
xml.node("session", [&] () {
session.generate_server_side_info(xml, session_detail); }); };
_session_requester.id_space().for_each<Session_state const>(fn);
});
}
});
}
void Init::Child::init(Ram_session &session, Ram_session_capability cap)
{
session.ref_account(_env.ram_session_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
: 0;
if (transfer_ram)
_env.ram().transfer_quota(cap, Ram_quota{transfer_ram});
}
void Init::Child::init(Cpu_session &session, Cpu_session_capability cap)
{
static size_t avail = Cpu_session::quota_lim_upscale( 100, 100);
size_t const need = Cpu_session::quota_lim_upscale(_resources.cpu_quota_pc, 100);
size_t need_adj = 0;
if (need > avail || avail == 0) {
warn_insuff_quota(Cpu_session::quota_lim_downscale(avail, 100));
need_adj = Cpu_session::quota_lim_upscale(100, 100);
avail = 0;
} else {
need_adj = Cpu_session::quota_lim_upscale(need, avail);
avail -= need;
}
session.ref_account(_env.cpu_session_cap());
_env.cpu().transfer_quota(cap, need_adj);
}
Init::Child::Route Init::Child::resolve_session_request(Service::Name const &service_name,
Session_label const &label)
{
/* check for "config" ROM request */
if (service_name == Rom_session::service_name() &&
label.last_element() == "config") {
if (_config_rom_service.constructed() &&
!_config_rom_service->abandoned())
return Route { _config_rom_service->service(), label };
/*
* \deprecated the support for the <configfile> tag will
* be removed
*/
if (_start_node->xml().has_sub_node("configfile")) {
typedef String<50> Name;
Name const rom =
_start_node->xml().sub_node("configfile")
.attribute_value("name", Name());
/* prevent infinite recursion */
if (rom == "config") {
error("configfile must not be named 'config'");
throw Parent::Service_denied();
}
return resolve_session_request(service_name,
prefixed_label(name(), rom));
}
/*
* If there is neither an inline '<config>' nor a
* '<configfile>' node present, we apply the regular session
* routing to the "config" ROM request.
*/
}
/*
* Check for the binary's ROM request
*
* The binary is requested as a ROM with the child's unique
* name ('Child_policy::binary_name' equals 'Child_policy::name').
* If the binary name differs from the child's unique name,
* we resolve the session request with the binary name as label.
* Otherwise the regular routing is applied.
*/
if (service_name == Rom_session::service_name() &&
label == _unique_name && _unique_name != _binary_name)
return resolve_session_request(service_name, _binary_name);
/* check for "session_requests" ROM request */
if (service_name == Rom_session::service_name()
&& label.last_element() == Session_requester::rom_name())
return Route { _session_requester.service() };
try {
Xml_node route_node = _default_route_accessor.default_route();
try {
route_node = _start_node->xml().sub_node("route"); }
catch (...) { }
Xml_node service_node = route_node.sub_node();
for (; ; service_node = service_node.next()) {
bool service_wildcard = service_node.has_type("any-service");
if (!service_node_matches(service_node, label, name(), service_name))
continue;
Xml_node target = service_node.sub_node();
for (; ; target = target.next()) {
/*
* Determine session label to be provided to the server
*
* By default, the client's identity (accompanied with the a
* client-provided label) is presented as session label to the
* server. However, the target node can explicitly override the
* client's identity by a custom label via the 'label'
* attribute.
*/
typedef String<Session_label::capacity()> Label;
Label const target_label =
target.attribute_value("label", Label(label.string()));
if (target.has_type("parent")) {
Parent_service *service = nullptr;
if ((service = find_service(_parent_services, service_name)))
return Route { *service, target_label };
if (service && service->abandoned())
throw Parent::Service_denied();
if (!service_wildcard) {
warning(name(), ": service lookup for "
"\"", service_name, "\" at parent failed");
throw Parent::Service_denied();
}
}
if (target.has_type("child")) {
typedef Name_registry::Name Name;
Name server_name = target.attribute_value("name", Name());
server_name = _name_registry.deref_alias(server_name);
Routed_service *service = nullptr;
_child_services.for_each([&] (Routed_service &s) {
if (s.name() == Service::Name(service_name)
&& s.child_name() == server_name)
service = &s; });
if (service && service->abandoned())
throw Parent::Service_denied();
if (service)
return Route { *service, target_label };
if (!service_wildcard) {
warning(name(), ": lookup to child "
"server \"", server_name, "\" failed");
throw Parent::Service_denied();
}
}
if (target.has_type("any-child")) {
if (is_ambiguous(_child_services, service_name)) {
error(name(), ": ambiguous routes to "
"service \"", service_name, "\"");
throw Parent::Service_denied();
}
Routed_service *service = nullptr;
if ((service = find_service(_child_services, service_name)))
return Route { *service, target_label };
if (!service_wildcard) {
warning(name(), ": lookup for service "
"\"", service_name, "\" failed");
throw Parent::Service_denied();
}
}
if (target.last())
break;
}
}
} catch (Xml_node::Nonexistent_sub_node) { }
warning(name(), ": no route to service \"", service_name, "\"");
throw Parent::Service_denied();
}
void Init::Child::filter_session_args(Service::Name const &service,
char *args, size_t args_len)
{
/*
* Intercept CPU session requests to scale priorities
*/
if (service == "CPU" && _prio_levels_log2 > 0) {
unsigned long priority = Arg_string::find_arg(args, "priority").ulong_value(0);
/* clamp priority value to valid range */
priority = min((unsigned)Cpu_session::PRIORITY_LIMIT - 1, priority);
long discarded_prio_lsb_bits_mask = (1 << _prio_levels_log2) - 1;
if (priority & discarded_prio_lsb_bits_mask)
warning("priority band too small, losing least-significant priority bits");
priority >>= _prio_levels_log2;
/* assign child priority to the most significant priority bits */
priority |= _priority*(Cpu_session::PRIORITY_LIMIT >> _prio_levels_log2);
/* override priority when delegating the session request to the parent */
String<64> value { Hex(priority) };
Arg_string::set_arg(args, args_len, "priority", value.string());
}
/*
* Remove phys_start and phys_size RAM-session arguments unless
* explicitly permitted by the child configuration.
*/
if (service == "RAM" && !_constrain_phys) {
Arg_string::remove_arg(args, "phys_start");
Arg_string::remove_arg(args, "phys_size");
}
}
Genode::Affinity Init::Child::filter_session_affinity(Affinity const &session_affinity)
{
Affinity::Space const &child_space = _resources.affinity.space();
Affinity::Location const &child_location = _resources.affinity.location();
/* check if no valid affinity space was specified */
if (session_affinity.space().total() == 0)
return Affinity(child_space, child_location);
Affinity::Space const &session_space = session_affinity.space();
Affinity::Location const &session_location = session_affinity.location();
/* scale resolution of resulting space */
Affinity::Space space(child_space.multiply(session_space));
/* subordinate session affinity to child affinity subspace */
Affinity::Location location(child_location
.multiply_position(session_space)
.transpose(session_location.xpos(),
session_location.ypos()));
return Affinity(space, location);
}
void Init::Child::announce_service(Service::Name const &service_name)
{
log("child \"", name(), "\" announces service \"", service_name, "\"");
bool found = false;
_child_services.for_each([&] (Routed_service &service) {
if (service.has_id_space(_session_requester.id_space())
&& service.name() == service_name)
found = true; });
if (!found)
error(name(), ": illegal announcement of "
"service \"", service_name, "\"");
}
void Init::Child::resource_request(Parent::Resource_args const &args)
{
log("child \"", name(), "\" requests resources: ", args);
_requested_resources.construct(args);
_report_update_trigger.trigger_report_update();
}
Init::Child::Child(Env &env,
Allocator &alloc,
Verbose const &verbose,
Id id,
Report_update_trigger &report_update_trigger,
Xml_node start_node,
Default_route_accessor &default_route_accessor,
Name_registry &name_registry,
Ram_quota ram_limit,
Ram_limit_accessor &ram_limit_accessor,
Prio_levels prio_levels,
Affinity::Space const &affinity_space,
Registry<Parent_service> &parent_services,
Registry<Routed_service> &child_services)
:
_env(env), _alloc(alloc), _verbose(verbose), _id(id),
_report_update_trigger(report_update_trigger),
_list_element(this),
_start_node(_alloc, start_node),
_default_route_accessor(default_route_accessor),
_ram_limit_accessor(ram_limit_accessor),
_name_registry(name_registry),
_resources(_resources_from_start_node(start_node, prio_levels, affinity_space)),
_resources_checked((_check_ram_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: ", _resources.effective_ram_quota());
log(" ELF binary: ", _binary_name);
log(" priority: ", _resources.priority);
}
/*
* Determine services provided by the child
*/
_provides_sub_node(start_node)
.for_each_sub_node("service",
[&] (Xml_node node) { _add_service(node); });
/*
* Construct inline config ROM service if "config" node is present.
*/
if (start_node.has_sub_node("config"))
_config_rom_service.construct(*this);
}
Init::Child::~Child()
{
_child_services.for_each([&] (Routed_service &service) {
if (service.has_id_space(_session_requester.id_space()))
destroy(_alloc, &service); });
}