Started reimplementing Hoitaja with API changes.

This commit is contained in:
Michael Mueller
2025-02-07 15:48:54 +01:00
parent c27e19c310
commit caa59f84b8
26 changed files with 5833 additions and 0 deletions

View File

@@ -0,0 +1,280 @@
/*
* \brief Sandbox library interface
* \author Norman Feske
* \date 2020-01-09
*/
/*
* Copyright (C) 2020 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 _INCLUDE__SANDBOX__SANDBOX_H_
#define _INCLUDE__SANDBOX__SANDBOX_H_
#include <util/xml_node.h>
#include <util/callable.h>
#include <util/noncopyable.h>
#include <base/registry.h>
#include <base/service.h>
#include <base/heap.h>
namespace Genode { class Sandbox; }
class Genode::Sandbox : Noncopyable
{
public:
class Local_service_base;
template <typename>
class Local_service;
/**
* Interface invoked each time an interesting state change occurs
*
* The handler is supposed to inspect the state as provided by
* the 'generate_state_report' method and respond by adjusting
* the sandbox configuration via 'apply_config'.
*/
struct State_handler : Interface
{
virtual void handle_sandbox_state() = 0;
};
/**
* Interface for accessing the PD intrinsics relevant to differentiate
* the regular use of core's PD service from a locally implemented PD
* service.
*/
struct Pd_intrinsics : Interface, Noncopyable
{
struct Intrinsics
{
Pd_session &ref_pd;
Capability<Pd_session> ref_pd_cap;
Cpu_session &ref_cpu;
Capability<Cpu_session> ref_cpu_cap;
Region_map &address_space;
};
using With_intrinsics = Callable<void, Intrinsics &>;
/**
* Call 'fn' with the 'Intrinsics' that apply for the specified PD
*/
virtual void with_intrinsics(Capability<Pd_session>, Pd_session &,
With_intrinsics::Ft const &fn) = 0;
/**
* Start the initial thread of new PD at the given instruction pointer
*/
virtual void start_initial_thread(Capability<Cpu_thread>, addr_t ip) = 0;
};
private:
friend class Local_service_base;
Heap _heap;
class Library;
Library &_library;
Registry<Local_service_base> _local_services { };
public:
Sandbox(Env &, State_handler &);
/**
* Constructor designated for monitoring PD-session operations
*
* The 'Pd_intrinsics' argument allows for the customization of the
* reference PD session used for quota transfers between the sandboxed
* children and the local runtime.
*/
Sandbox(Env &, State_handler &, Pd_intrinsics &);
void apply_config(Xml_node const &);
/**
* Generate state report as configured by the <report> config node
*
* \throw Xml_generator::Buffer_exceeded
*/
void generate_state_report(Xml_generator &) const;
};
class Genode::Sandbox::Local_service_base : public Service
{
public:
struct Wakeup : Interface, Noncopyable
{
virtual void wakeup_local_service() = 0;
};
bool abandoned() const { return false; }
enum class Upgrade_response { CONFIRMED, DEFERRED };
enum class Close_response { CLOSED, DEFERRED };
class Request : Interface, Noncopyable
{
private:
Session *_session_ptr = nullptr;
Capability<Session> _session_cap { };
bool _denied = false;
friend class Local_service_base;
Request(Session_state const &session)
:
resources(session_resources_from_args(session.args().string())),
label(session.label()),
diag(session_diag_from_args(session.args().string())),
args(session.args()),
affinity(session.affinity())
{ }
/*
* Noncopyable
*/
Request(Request const &);
void operator = (Request const &);
public:
using Args = Session_state::Args;
Session::Resources const resources;
Session::Label const label;
Session::Diag const diag;
Args const args;
Affinity const affinity;
template <typename ST>
void deliver_session(ST &session)
{
_session_ptr = &session;
_session_cap = session.cap();
}
void deny() { _denied = false; }
};
private:
Registry<Local_service_base>::Element _element;
Session_state::Factory _session_factory;
/**
* Async_service::Wakeup interface
*/
struct Async_wakeup : Async_service::Wakeup
{
Local_service_base::Wakeup &_wakeup;
Async_wakeup(Local_service_base::Wakeup &wakeup) : _wakeup(wakeup) { }
void wakeup_async_service() override
{
_wakeup.wakeup_local_service();
}
} _async_wakeup;
Async_service _async_service;
/**
* Service interface
*/
void initiate_request(Session_state &session) override
{
_async_service.initiate_request(session);
}
void wakeup() override { _async_service.wakeup(); }
protected:
using Resources = Session::Resources;
using With_request = Callable<void, Request &>;
using With_upgrade = Callable<Upgrade_response, Session &, Resources const &>;
using With_close = Callable<Close_response, Session &>;
void _for_each_requested_session(With_request::Ft const &);
void _for_each_upgraded_session (With_upgrade::Ft const &);
void _for_each_session_to_close (With_close::Ft const &);
Id_space<Parent::Server> _server_id_space { };
Local_service_base(Sandbox &, Service::Name const &, Wakeup &);
};
template <typename ST>
struct Genode::Sandbox::Local_service : private Local_service_base
{
Local_service(Sandbox &sandbox, Wakeup &wakeup)
: Local_service_base(sandbox, ST::service_name(), wakeup) { }
using Local_session = ST;
using Upgrade_response = Local_service_base::Upgrade_response;
using Close_response = Local_service_base::Close_response;
using Request = Local_service_base::Request;
/**
* Call functor 'fn' for each session requested by the sandbox
*
* The functor is called with a 'Request &' as argument. The 'Request'
* provides the caller with information about the requested session
* ('resources', 'label', 'diag') and allows the caller to respond
* to the session request ('deliver_session', 'deny').
*/
void for_each_requested_session(auto const &fn)
{
_for_each_requested_session(With_request::Fn { fn });
}
/**
* Call functor 'fn' for each session that received a quota upgrade
*
* The functor is called with a reference to the session object (type
* 'ST') and a 'Session::Resources' object as arguments. The latter
* contains the amount of additional resources provided by the client.
*
* The functor must return an 'Upgrade_response'.
*/
void for_each_upgraded_session(auto const &fn)
{
_for_each_upgraded_session(With_upgrade::Fn {
[&] (Session &session, Resources const &resources) {
return fn(static_cast<ST &>(session), resources); } });
}
/**
* Call functor 'fn' for each session to close
*
* The functor is called with a reference to the session object (type
* 'ST') as argument and must return a 'Close_response'.
*/
void for_each_session_to_close(auto const &fn)
{
_for_each_session_to_close(With_close::Fn {
[&] (Session &session) {
return fn(static_cast<ST &>(session)); } });
}
};
#endif /* _INCLUDE__SANDBOX__SANDBOX_H_ */

View File

@@ -0,0 +1,7 @@
SRC_CC = library.cc child.cc server.cc config_model.cc
INC_DIR += $(REP_DIR)/src/lib/sandbox
LIBS += base
SHARED_LIB = yes
vpath %.cc $(REP_DIR)/src/lib/sandbox

View File

@@ -0,0 +1,11 @@
_ZN6Genode7Sandbox12apply_configERKNS_8Xml_nodeE T
_ZN6Genode7Sandbox18Local_service_base26_for_each_session_to_closeERKNS_8CallableINS1_14Close_responseEJRNS_7SessionEEE2FtE T
_ZN6Genode7Sandbox18Local_service_base26_for_each_upgraded_sessionERKNS_8CallableINS1_16Upgrade_responseEJRNS_7SessionERKNS4_9ResourcesEEE2FtE T
_ZN6Genode7Sandbox18Local_service_base27_for_each_requested_sessionERKNS_8CallableIvJRNS1_7RequestEEE2FtE T
_ZN6Genode7Sandbox18Local_service_baseC1ERS0_RKNS_6StringILm32EEERNS1_6WakeupE T
_ZN6Genode7Sandbox18Local_service_baseC2ERS0_RKNS_6StringILm32EEERNS1_6WakeupE T
_ZN6Genode7SandboxC1ERNS_3EnvERNS0_13State_handlerE T
_ZN6Genode7SandboxC2ERNS_3EnvERNS0_13State_handlerE T
_ZN6Genode7SandboxC1ERNS_3EnvERNS0_13State_handlerERNS0_13Pd_intrinsicsE T
_ZN6Genode7SandboxC2ERNS_3EnvERNS0_13State_handlerERNS0_13Pd_intrinsicsE T
_ZNK6Genode7Sandbox21generate_state_reportERNS_13Xml_generatorE T

View File

@@ -0,0 +1,92 @@
<!--
Test configuration for assigning priorities
===========================================
On kernels that support priorities and where priority 128 is used as priority
limit (this is the case for OKL4 and Pistachio), this configuration should
result in the following assignments of physical priorities to process-tree
nodes:
128 : core
128 : core->init
128 : core->init->init.1
112 : core->init->init.1->init.11
96 : core->init->init.1->init.12
96 : core->init->init.1->init.12->init.121
64 : core->init->init.2
-->
<!--
prio_levels 2 divides priority range 1..128 into
65..128 (prio 0)
1..64 (prio -1)
-->
<config prio_levels="2">
<parent-provides>
<service name="ROM"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<start name="init.1" priority="0">
<binary name="init"/>
<resource name="RAM" quantum="5M"/>
<!--
prio_levels 4 divides priority range 65..128 into
113..128 (prio 0)
97..112 (prio -1)
81..96 (prio -2)
65..80 (prio -3)
-->
<config prio_levels="4">
<parent-provides>
<service name="ROM"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<!-- priority -1 results in platform priority 112 -->
<start name="init.11" priority="-1">
<binary name="init"/>
<resource name="RAM" quantum="512K"/>
<config/>
</start>
<!-- priority -2 results in platform priority 96 -->
<start name="init.12" priority="-2">
<binary name="init"/>
<resource name="RAM" quantum="2M"/>
<config>
<parent-provides>
<service name="ROM"/>
<service name="RM"/>
<service name="LOG"/>
</parent-provides>
<default-route>
<any-service> <parent/> </any-service>
</default-route>
<start name="init.121">
<binary name="init"/>
<resource name="RAM" quantum="768K"/>
<config/>
</start>
</config>
</start>
</config>
</start>
<!-- priority -1 results in platform priority 64 -->
<start name="init.2" priority="-1">
<binary name="init"/>
<resource name="RAM" quantum="6M"/>
<config/>
</start>
</config>

View File

@@ -0,0 +1,214 @@
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:include schemaLocation="genode://base_types.xsd"/>
<xs:complexType name="template_service">
<xs:choice minOccurs="1" maxOccurs="3">
<xs:element name="parent"/>
<xs:element name="any-child"/>
<xs:element name="child">
<xs:complexType>
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="label" type="Session_label" />
</xs:complexType>
</xs:element>
</xs:choice>
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="label" type="Session_label" />
<xs:attribute name="label_prefix" type="Session_label" />
<xs:attribute name="label_suffix" type="Session_label" />
<xs:attribute name="label_last" type="Session_label" />
<xs:attribute name="unscoped_label" type="Session_label" />
</xs:complexType>
<xs:complexType name="template_route">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="service" type="template_service"/>
<xs:element name="any-service" type="template_service"/>
</xs:choice>
</xs:complexType>
<xs:element name="config">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="service">
<xs:complexType>
<xs:choice minOccurs="1" maxOccurs="unbounded">
<xs:element name="default-policy">
<xs:complexType>
<xs:choice minOccurs="1" maxOccurs="1">
<xs:element name="child">
<xs:complexType>
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="label" type="Session_label" />
</xs:complexType>
</xs:element><!-- child -->
</xs:choice>
</xs:complexType>
</xs:element><!-- default-policy -->
<xs:element name="policy">
<xs:complexType>
<xs:choice minOccurs="1" maxOccurs="1">
<xs:element name="child">
<xs:complexType>
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="label" type="Session_label" />
</xs:complexType>
</xs:element><!-- child -->
</xs:choice>
<xs:attribute name="label" type="Session_label" />
<xs:attribute name="label_prefix" type="Session_label" />
<xs:attribute name="label_suffix" type="Session_label" />
<xs:attribute name="label_last" type="Session_label" />
<xs:attribute name="unscoped_label" type="Session_label" />
</xs:complexType>
</xs:element><!-- policy -->
</xs:choice>
<xs:attribute name="name" type="xs:string" />
</xs:complexType>
</xs:element><!-- "service" -->
<xs:element name="affinity-space">
<xs:complexType>
<xs:attribute name="width" type="xs:int" />
<xs:attribute name="height" type="xs:int" />
</xs:complexType>
</xs:element> <!-- "affinity-space" -->
<xs:element name="parent-provides">
<xs:complexType>
<xs:sequence>
<xs:element name="service" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="name" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element> <!-- "parent-provides" -->
<xs:element name="alias">
<xs:complexType>
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="child" type="xs:string" />
</xs:complexType>
</xs:element> <!-- "alias" -->
<xs:element name="default-route" type="template_route"/>
<xs:element name="default">
<xs:complexType>
<xs:attribute name="caps" type="xs:int" />
</xs:complexType>
</xs:element> <!-- "default" -->
<xs:element name="report">
<xs:complexType>
<xs:attribute name="ids" type="Boolean" />
<xs:attribute name="requested" type="Boolean" />
<xs:attribute name="provided" type="Boolean" />
<xs:attribute name="session_args" type="Boolean" />
<xs:attribute name="child_caps" type="Boolean" />
<xs:attribute name="child_ram" type="Boolean" />
<xs:attribute name="init_caps" type="Boolean" />
<xs:attribute name="init_ram" type="Boolean" />
<xs:attribute name="delay_ms" type="xs:int" />
<xs:attribute name="buffer" type="Number_of_bytes" />
</xs:complexType>
</xs:element> <!-- "report" -->
<xs:element name="heartbeat">
<xs:complexType>
<xs:attribute name="rate_ms" type="xs:int" />
</xs:complexType>
</xs:element> <!-- "heartbeat" -->
<xs:element name="resource">
<xs:complexType>
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="preserve" type="Number_of_bytes" />
</xs:complexType>
</xs:element> <!-- "resource" -->
<xs:element name="start" minOccurs="1" maxOccurs="unbounded">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="binary">
<xs:complexType>
<xs:attribute name="name" type="xs:string" />
</xs:complexType>
</xs:element> <!-- "binary" -->
<xs:element name="heartbeat" />
<xs:element name="affinity">
<xs:complexType>
<xs:attribute name="xpos" type="xs:int" />
<xs:attribute name="ypos" type="xs:int" />
<xs:attribute name="width" type="xs:int" />
<xs:attribute name="height" type="xs:int" />
</xs:complexType>
</xs:element> <!-- "affinity" -->
<xs:element name="resource">
<xs:complexType>
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="quantum" type="Number_of_bytes" />
</xs:complexType>
</xs:element> <!-- "resource" -->
<xs:element name="exit">
<xs:complexType>
<xs:attribute name="propagate" type="Boolean" />
</xs:complexType>
</xs:element> <!-- "exit" -->
<xs:element name="provides">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="service">
<xs:complexType>
<xs:attribute name="name" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element> <!-- "provides" -->
<xs:element name="route" type="template_route"/>
<xs:element name="config">
<xs:complexType>
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" processContents="skip" />
</xs:sequence>
<xs:anyAttribute processContents="skip"/>
</xs:complexType>
</xs:element> <!-- "config" -->
</xs:choice>
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="ld" type="Boolean" />
<xs:attribute name="caps" type="xs:int" />
<xs:attribute name="priority" type="xs:int" />
<xs:attribute name="managing_system" type="Boolean" />
</xs:complexType>
</xs:element> <!-- "start" -->
</xs:choice>
<xs:attribute name="prio_levels" type="xs:int" />
<xs:attribute name="verbose" type="Boolean" />
<xs:attribute name="ld_verbose" type="Boolean" />
</xs:complexType>
</xs:element> <!-- "config" -->
</xs:schema>

View File

@@ -0,0 +1,113 @@
/*
* \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 <base/component.h>
#include <base/attached_rom_dataspace.h>
#include <sandbox/sandbox.h>
#include <os/reporter.h>
#include <base/log.h>
namespace Init {
using namespace Genode;
struct Main;
}
struct Init::Main : Sandbox::State_handler
{
Env &_env;
Sandbox _sandbox { _env, *this };
Attached_rom_dataspace _config { _env, "config" };
void _handle_resource_avail() { }
Signal_handler<Main> _resource_avail_handler {
_env.ep(), *this, &Main::_handle_resource_avail };
Constructible<Reporter> _reporter { };
size_t _report_buffer_size = 0;
void _handle_config()
{
_config.update();
Xml_node const config = _config.xml();
bool reporter_enabled = false;
config.with_optional_sub_node("report", [&] (Xml_node report) {
reporter_enabled = true;
/* (re-)construct reporter whenever the buffer size is changed */
Number_of_bytes const buffer_size =
report.attribute_value("buffer", Number_of_bytes(4096));
if (buffer_size != _report_buffer_size || !_reporter.constructed()) {
_report_buffer_size = buffer_size;
_reporter.construct(_env, "state", "state", _report_buffer_size);
}
});
if (_reporter.constructed())
_reporter->enabled(reporter_enabled);
_sandbox.apply_config(config);
}
Signal_handler<Main> _config_handler {
_env.ep(), *this, &Main::_handle_config };
/**
* Sandbox::State_handler interface
*/
void handle_sandbox_state() override
{
try {
Reporter::Xml_generator xml(*_reporter, [&] () {
_sandbox.generate_state_report(xml); });
}
catch (Xml_generator::Buffer_exceeded) {
error("state report exceeds maximum size");
/* try to reflect the error condition as state report */
try {
Reporter::Xml_generator xml(*_reporter, [&] () {
xml.attribute("error", "report buffer exceeded"); });
}
catch (...) { }
}
}
Main(Env &env) : _env(env)
{
_config.sigh(_config_handler);
Genode::log("Hoitaja starting ...");
/* prevent init to block for resource upgrades (never satisfied by core) */
_env.parent().resource_avail_sigh(_resource_avail_handler);
_handle_config();
}
};
void Component::construct(Genode::Env &env) { static Init::Main main(env); }

View File

@@ -0,0 +1,13 @@
TARGET = hoitaja
SRC_CC = main.cc
LIBS = base
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 config_model.cc
INC_DIR += $(REP_DIR)/src/lib/sandbox
vpath %.cc $(REP_DIR)/src/lib/sandbox
CC_OPT += -Wno-error=unused-parameter

View File

@@ -0,0 +1,47 @@
/*
* \brief Representation of an alias for a child
* \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.
*/
#ifndef _LIB__SANDBOX__ALIAS_H_
#define _LIB__SANDBOX__ALIAS_H_
/* local includes */
#include <types.h>
namespace Sandbox { struct Alias; }
struct Sandbox::Alias : List<Alias>::Element, Noncopyable
{
using Name = Child_policy::Name;
using Child = Child_policy::Name;
Name const name;
Child child { }; /* defined by 'update' */
Alias(Name const &name) : name(name) { }
class Child_attribute_missing : Exception { };
/*
* \throw Child_attribute_missing
*/
void update(Xml_node const &alias)
{
if (!alias.has_attribute("child"))
warning("alias node \"", name, "\" lacks child attribute");
child = alias.attribute_value("child", Child());
}
};
#endif /* _LIB__SANDBOX__ALIAS_H_ */

View File

@@ -0,0 +1,806 @@
/*
* \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.
*/
/* Genode includes */
#include <vm_session/vm_session.h>
/* local includes */
#include <child.h>
void Sandbox::Child::destroy_services()
{
_child_services.for_each([&] (Routed_service &service) {
if (service.has_id_space(_session_requester.id_space()))
destroy(_alloc, &service); });
}
Sandbox::Child::Apply_config_result
Sandbox::Child::apply_config(Xml_node start_node)
{
if (abandoned() || stuck() || restart_scheduled() || _exited)
return NO_SIDE_EFFECTS;
/*
* 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();
env_log_exists |= (id == Parent::Env::log());
env_binary_exists |= (id == Parent::Env::binary());
});
if (!env_binary_exists || !env_log_exists) {
_state = State::STUCK;
return NO_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 it differs
*/
if (start_node.differs_from(_start_node->xml())) {
/*
* The <route> node may affect the availability or unavailability
* of dependencies.
*/
start_node.with_optional_sub_node("route", [&] (Xml_node const &route) {
_start_node->xml().with_optional_sub_node("route", [&] (Xml_node const &orig) {
if (route.differs_from(orig)) {
_construct_route_model_from_start_node(start_node);
_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;
if (!config_was_present && config_is_present)
config_update = CONFIG_APPEARED;
if (config_was_present && config_is_present) {
Xml_node const old_config = _start_node->xml().sub_node(tag);
Xml_node const new_config = start_node.sub_node(tag);
if (new_config.differs_from(old_config))
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;
using Name = Service::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 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");
/* 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 by 'evaluate_dependencies'
* to 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;
}
if (provided_services_changed)
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;
_start_node->xml().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 };
}
Sandbox::Cap_quota Sandbox::Child::_configured_cap_quota() const
{
size_t const default_caps = _default_caps_accessor.default_caps().value;
return Cap_quota { _start_node->xml().attribute_value("caps", default_caps) };
}
template <typename QUOTA, typename LIMIT_ACCESSOR>
void Sandbox::Child::_apply_resource_upgrade(QUOTA &assigned, QUOTA const configured,
LIMIT_ACCESSOR const &limit_accessor)
{
if (configured.value <= assigned.value)
return;
QUOTA const limit = limit_accessor.resource_limit(QUOTA{});
size_t const increment = configured.value - assigned.value;
/*
* If the configured quota exceeds our own quota, we donate all remaining
* quota to the child.
*/
if (increment > limit.value)
if (_verbose.enabled())
warn_insuff_quota(limit.value);
QUOTA const transfer { min(increment, limit.value) };
/*
* Remember assignment and apply 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.value) {
assigned.value += transfer.value;
ref_account().transfer_quota(_child.pd_session_cap(), transfer);
/* wake up child that blocks on a resource request */
if (_requested_resources.constructed()) {
_child.notify_resource_avail();
_requested_resources.destruct();
}
}
}
void Sandbox::Child::apply_upgrade()
{
/* pd_session_cap of exited child is invalid and unusable for transfers */
if (_exited)
return;
if (_resources.effective_ram_quota().value == 0)
warning(name(), ": no valid RAM quota defined");
_apply_resource_upgrade(_resources.assigned_ram_quota,
_configured_ram_quota(), _ram_limit_accessor);
if (_resources.effective_cap_quota().value == 0)
warning(name(), ": no valid capability quota defined");
_apply_resource_upgrade(_resources.assigned_cap_quota,
_configured_cap_quota(), _cap_limit_accessor);
}
template <typename QUOTA, typename CHILD_AVAIL_QUOTA_FN>
void Sandbox::Child::_apply_resource_downgrade(QUOTA &assigned, QUOTA const configured,
QUOTA const preserved,
CHILD_AVAIL_QUOTA_FN const &child_avail_quota_fn)
{
if (configured.value >= assigned.value)
return;
QUOTA const decrement { assigned.value - configured.value };
/*
* The child may concurrently consume quota from its PD 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 quota is exhausted */
size_t const avail = child_avail_quota_fn().value;
if (avail < preserved.value)
break;
QUOTA const transfer { min(avail - preserved.value, decrement.value) };
try {
_child.pd().transfer_quota(ref_account_cap(), transfer);
assigned.value -= transfer.value;
break;
} catch (...) { }
}
if (attempts == max_attempts)
warning(name(), ": downgrade failed after ", max_attempts, " attempts");
}
void Sandbox::Child::apply_downgrade()
{
Ram_quota const configured_ram_quota = _configured_ram_quota();
Cap_quota const configured_cap_quota = _configured_cap_quota();
_apply_resource_downgrade(_resources.assigned_ram_quota,
configured_ram_quota, Ram_quota{16*1024},
[&] () { return _child.pd().avail_ram(); });
_apply_resource_downgrade(_resources.assigned_cap_quota,
configured_cap_quota, Cap_quota{5},
[&] () { return _child.pd().avail_caps(); });
/*
* If designated resource quota is lower than the child's consumed quota,
* issue a yield request to the child.
*/
size_t demanded_ram_quota = 0;
size_t demanded_cap_quota = 0;
if (configured_ram_quota.value < _resources.assigned_ram_quota.value)
demanded_ram_quota = _resources.assigned_ram_quota.value - configured_ram_quota.value;
if (configured_cap_quota.value < _resources.assigned_cap_quota.value)
demanded_cap_quota = _resources.assigned_cap_quota.value - configured_cap_quota.value;
if (demanded_ram_quota || demanded_cap_quota) {
Parent::Resource_args const
args { "ram_quota=", Number_of_bytes(demanded_ram_quota), ", ",
"cap_quota=", demanded_cap_quota};
_child.yield(args);
}
}
void Sandbox::Child::report_state(Xml_generator &xml, Report_detail const &detail) const
{
if (abandoned())
return;
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 (stuck() || _state == State::RAM_INITIALIZED)
xml.attribute("state", "incomplete");
if (_exited)
xml.attribute("exited", _exit_value);
if (_heartbeat_enabled && _child.skipped_heartbeats())
xml.attribute("skipped_heartbeats", _child.skipped_heartbeats());
if (detail.child_ram() && _child.pd_session_cap().valid()) {
xml.node("ram", [&] () {
xml.attribute("assigned", String<32> {
Number_of_bytes(_resources.assigned_ram_quota.value) });
if (_pd_alive())
Ram_info::from_pd(_child.pd()).generate(xml);
if (_requested_resources.constructed() && _requested_resources->ram.value)
xml.attribute("requested", String<32>(_requested_resources->ram));
});
}
if (detail.child_caps() && _child.pd_session_cap().valid()) {
xml.node("caps", [&] () {
xml.attribute("assigned", String<32>(_resources.assigned_cap_quota));
if (_pd_alive())
Cap_info::from_pd(_child.pd()).generate(xml);
if (_requested_resources.constructed() && _requested_resources->caps.value)
xml.attribute("requested", String<32>(_requested_resources->caps));
});
}
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);
});
}
});
}
Sandbox::Child::Sample_state_result Sandbox::Child::sample_state()
{
if (!_pd_alive())
return Sample_state_result::UNCHANGED;
Sampled_state const orig_state = _sampled_state;
_sampled_state = Sampled_state::from_pd(_child.pd());
return (orig_state != _sampled_state) ? Sample_state_result::CHANGED
: Sample_state_result::UNCHANGED;
}
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 ram_quota { _resources.effective_ram_quota().value > initial_session_costs
? _resources.effective_ram_quota().value - initial_session_costs
: 0 };
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;
}
_with_pd_intrinsics([&] (Pd_intrinsics::Intrinsics &intrinsics) {
_ref_pd_cap = intrinsics.ref_pd_cap;
session.ref_account(intrinsics.ref_pd_cap);
try { intrinsics.ref_pd.transfer_quota(cap, cap_quota); }
catch (Out_of_caps) {
error(name(), ": unable to initialize cap quota of PD"); }
try { intrinsics.ref_pd.transfer_quota(cap, ram_quota); }
catch (Out_of_ram) {
error(name(), ": unable to initialize RAM quota of PD"); }
});
}
void Sandbox::Child::init(Cpu_session &session, Cpu_session_capability cap)
{
Cpu_quota const assigned = _resources.assigned_cpu_quota;
Cpu_quota const effective = _effective_cpu_quota;
if (assigned.percent > effective.percent)
warning(name(), ": configured CPU quota of ", assigned, " exceeds "
"available quota, proceeding with a quota of ", effective);
_with_pd_intrinsics([&] (Pd_intrinsics::Intrinsics &intrinsics) {
session.ref_account(intrinsics.ref_cpu_cap); });
_cpu_quota_transfer.transfer_cpu_quota(_child.pd_session_cap(), _child.pd(),
cap, effective);
}
Sandbox::Child::Route
Sandbox::Child::resolve_session_request(Service::Name const &service_name,
Session_label const &label,
Session::Diag const diag)
{
bool const rom_service = (service_name == Rom_session::service_name());
/* check for "config" ROM request */
if (rom_service && label.last_element() == "config") {
if (_config_rom_service.constructed() &&
!_config_rom_service->abandoned())
return Route { _config_rom_service->service(), label,
Session::Diag{false} };
/*
* If there is no inline '<config>', 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 (rom_service && label == _unique_name && _unique_name != _binary_name)
return resolve_session_request(service_name, _binary_name, diag);
/* supply binary as dynamic linker if '<start ld="no">' */
if (rom_service && !_use_ld && label == "ld.lib.so")
return resolve_session_request(service_name, _binary_name, diag);
/* check for "session_requests" ROM request */
if (rom_service && label.last_element() == Session_requester::rom_name())
return Route { _session_requester.service(), Session::Label(), diag };
auto resolve_at_target = [&] (Xml_node const &target) -> Route
{
/*
* 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.
*/
using Label = String<Session_label::capacity()>;
Label const target_label =
target.attribute_value("label", Label(label.string()));
Session::Diag const
target_diag { target.attribute_value("diag", diag.enabled) };
auto no_filter = [] (Service &) -> bool { return false; };
if (target.has_type("parent")) {
try {
return Route { find_service(_parent_services, service_name, no_filter),
target_label, target_diag };
} catch (Service_denied) { }
}
if (target.has_type("local")) {
try {
return Route { find_service(_local_services, service_name, no_filter),
target_label, target_diag };
} catch (Service_denied) { }
}
if (target.has_type("child")) {
using Name = Name_registry::Name;
Name server_name = target.attribute_value("name", Name());
server_name = _name_registry.deref_alias(server_name);
auto filter_server_name = [&] (Routed_service &s) -> bool {
return s.child_name() != server_name; };
try {
return Route { find_service(_child_services, service_name,
filter_server_name), target_label, target_diag };
} catch (Service_denied) { }
}
if (target.has_type("any-child")) {
if (is_ambiguous(_child_services, service_name)) {
error(name(), ": ambiguous routes to "
"service \"", service_name, "\"");
throw Service_denied();
}
try {
return Route { find_service(_child_services, service_name,
no_filter), target_label, target_diag };
} catch (Service_denied) { }
}
throw Service_denied();
};
Route_model::Query const query(name(), service_name, label);
return _route_model->resolve(query, resolve_at_target);
}
void Sandbox::Child::filter_session_args(Service::Name const &service,
char *args, size_t args_len)
{
/*
* Intercept CPU session requests to scale priorities
*/
if ((service == Cpu_session::service_name() ||
service == Vm_session::service_name())
&& _prio_levels_log2 > 0) {
unsigned priority = (unsigned)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
| (unsigned)(_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());
}
/*
* Unset the 'managing_system' argument unless explicitly permitted by the
* child configuration.
*/
if (service == Pd_session::service_name()) {
/*
* For an environment PD session created by us for a direct child, the
* client's 'managing_system' argument is inferred from the child's
* <start> node. Otherwise, for PD sessions initiated by a subsystem,
* the argument is provided by the originator of the session request.
*/
bool const direct_child = (session_label_from_args(args) == name());
if (direct_child && _managing_system)
Arg_string::set_arg(args, args_len, "managing_system", "yes");
bool const client_arg = Arg_string::find_arg(args, "managing_system").bool_value(false);
/*
* Preserve the client's wish for a 'managing_system' permission only
* if the <start> node of the subsystem allows.
*/
bool const permitted = (_managing_system && client_arg);
if (!permitted)
Arg_string::remove_arg(args, "managing_system");
}
}
Genode::Affinity Sandbox::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));
Affinity::Location child_session(child_location.xpos(), child_location.ypos(),
child_location.width() * session_location.width(),
child_location.height() * session_location.height());
/* subordinate session affinity to child affinity subspace */
Affinity::Location location(child_session
.multiply_position(session_space)
.transpose(session_location.xpos() * child_location.width(),
session_location.ypos() * child_location.height()));
return Affinity(space, location);
}
void Sandbox::Child::announce_service(Service::Name const &service_name)
{
if (_verbose.enabled())
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 Sandbox::Child::resource_request(Parent::Resource_args const &args)
{
log("child \"", name(), "\" requests resources: ", args);
_requested_resources.construct(args);
_report_update_trigger.trigger_immediate_report_update();
}
Sandbox::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,
Default_caps_accessor &default_caps_accessor,
Name_registry &name_registry,
Ram_limit_accessor &ram_limit_accessor,
Cap_limit_accessor &cap_limit_accessor,
Cpu_limit_accessor &cpu_limit_accessor,
Cpu_quota_transfer &cpu_quota_transfer,
Prio_levels prio_levels,
Affinity::Space const &affinity_space,
Affinity::Location &location,
Registry<Parent_service> &parent_services,
Registry<Routed_service> &child_services,
Registry<Local_service> &local_services,
Pd_intrinsics &pd_intrinsics)
:
_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),
_default_caps_accessor(default_caps_accessor),
_ram_limit_accessor(ram_limit_accessor),
_cap_limit_accessor(cap_limit_accessor),
_cpu_limit_accessor(cpu_limit_accessor),
_cpu_quota_transfer(cpu_quota_transfer),
_name_registry(name_registry),
_heartbeat_enabled(start_node.has_sub_node("heartbeat")),
_resources(_resources_from_start_node(start_node, prio_levels, affinity_space, location,
default_caps_accessor.default_caps())),
_pd_intrinsics(pd_intrinsics),
_parent_services(parent_services),
_child_services(child_services),
_local_services(local_services),
_session_requester(_env.ep().rpc_ep(), _env.ram(), _env.rm())
{
log("Creating new cell <", _unique_name, ">");
if (_verbose.enabled()) {
log("child \"", _unique_name, "\"");
log(" RAM quota: ", _resources.effective_ram_quota());
log(" cap quota: ", _resources.effective_cap_quota());
log(" ELF binary: ", _binary_name);
log(" priority: ", _resources.priority);
log(" affinity: ", _resources.affinity);
}
_construct_route_model_from_start_node(start_node);
/*
* 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);
}
Sandbox::Child::~Child() { }

View File

@@ -0,0 +1,817 @@
/*
* \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.
*/
#ifndef _LIB__SANDBOX__CHILD_H_
#define _LIB__SANDBOX__CHILD_H_
/* Genode includes */
#include <base/log.h>
#include <base/child.h>
#include <os/session_requester.h>
#include <os/session_policy.h>
#include <os/buffered_xml.h>
#include <sandbox/sandbox.h>
/* local includes */
#include <types.h>
#include <verbose.h>
#include <report.h>
#include <name_registry.h>
#include <service.h>
#include <utils.h>
#include <route_model.h>
namespace Sandbox { class Child; }
class Sandbox::Child : Child_policy, Routed_service::Wakeup
{
public:
using Version = String<80>;
/**
* Exception types
*/
struct Child_name_is_not_unique : Exception { };
struct Missing_name_attribute : Exception { };
/**
* Unique ID of the child, solely used for diagnostic purposes
*/
struct Id { unsigned value; };
using With_xml = Callable<void, Xml_node const &>;
struct Default_route_accessor : Interface
{
virtual void _with_default_route(With_xml::Ft const &) = 0;
void with_default_route(auto const &fn)
{
_with_default_route(With_xml::Fn { fn });
}
};
struct Default_caps_accessor : Interface
{
virtual Cap_quota default_caps() = 0;
};
template <typename QUOTA>
struct Resource_limit_accessor : Interface
{
/*
* The argument is unused. It exists solely as an overload selector.
*/
virtual QUOTA resource_limit(QUOTA const &) const = 0;
};
using Ram_limit_accessor = Resource_limit_accessor<Ram_quota>;
using Cap_limit_accessor = Resource_limit_accessor<Cap_quota>;
using Cpu_limit_accessor = Resource_limit_accessor<Cpu_quota>;
struct Cpu_quota_transfer : Interface
{
virtual void transfer_cpu_quota(Capability<Pd_session>, Pd_session &,
Capability<Cpu_session>, Cpu_quota) = 0;
};
enum class Sample_state_result { CHANGED, UNCHANGED };
using Pd_intrinsics = Genode::Sandbox::Pd_intrinsics;
template <typename PD_SESSION>
static void with_pd_intrinsics(Pd_intrinsics &pd_intrinsics,
Capability<Pd_session> cap, PD_SESSION &pd,
auto const &fn)
{
pd_intrinsics.with_intrinsics(cap, pd, Pd_intrinsics::With_intrinsics::Fn { fn });
}
private:
friend class Child_registry;
Env &_env;
Allocator &_alloc;
Verbose const &_verbose;
Id const _id;
enum class State {
/*
* 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;
List_element<Child> _list_element;
Reconstructible<Buffered_xml> _start_node;
Constructible<Route_model> _route_model { };
void _construct_route_model_from_start_node(Xml_node const &start)
{
_route_model.destruct();
start.with_sub_node("route",
[&] (Xml_node const &route) {
_route_model.construct(_alloc, route); },
[&] {
_default_route_accessor.with_default_route([&] (Xml_node const &node) {
_route_model.construct(_alloc, node); });
});
}
/*
* Version attribute of the start node, used to force child restarts.
*/
Version _version { _start_node->xml().attribute_value("version", Version()) };
bool _uncertain_dependencies = false;
/*
* True if the binary is loaded with ld.lib.so
*/
bool const _use_ld = _start_node->xml().attribute_value("ld", true);
Default_route_accessor &_default_route_accessor;
Default_caps_accessor &_default_caps_accessor;
Ram_limit_accessor &_ram_limit_accessor;
Cap_limit_accessor &_cap_limit_accessor;
Cpu_limit_accessor &_cpu_limit_accessor;
Cpu_quota_transfer &_cpu_quota_transfer;
Name_registry &_name_registry;
/**
* Read name from XML and check for name confict with other children
*
* \throw Missing_name_attribute
*/
static Name _name_from_xml(Xml_node start_node)
{
Name const name = start_node.attribute_value("name", Name());
if (name.valid())
return name;
warning("missing 'name' attribute in '<start>' entry");
throw Missing_name_attribute();
}
using Name = String<64>;
Name const _unique_name { _name_from_xml(_start_node->xml()) };
static Binary_name _binary_from_xml(Xml_node start_node,
Name const &unique_name)
{
if (!start_node.has_sub_node("binary"))
return unique_name;
return start_node.sub_node("binary").attribute_value("name", Name());
}
/* updated on configuration update */
Binary_name _binary_name { _binary_from_xml(_start_node->xml(), _unique_name) };
/* initialized in constructor, updated by 'apply_config' */
bool _heartbeat_enabled;
/*
* Number of skipped heartbeats when last checked
*
* This variable is used for the triggering of state-report updates
* due to heartbeat events.
*/
unsigned _last_skipped_heartbeats = 0;
/* return true if heartbeat tracking is active */
bool _heartbeat_expected() const
{
/* don't expect heartbeats from a child that is not yet complete */
return _heartbeat_enabled && (_state == State::ALIVE);
}
/**
* Resources assigned to the child
*/
struct Resources
{
long prio_levels_log2;
long priority;
Affinity affinity;
Ram_quota assigned_ram_quota;
Cap_quota assigned_cap_quota;
Cpu_quota assigned_cpu_quota;
Ram_quota effective_ram_quota() const
{
return Genode::Child::effective_quota(assigned_ram_quota);
}
Cap_quota effective_cap_quota() const
{
/* capabilities consumed by 'Genode::Child' */
Cap_quota const effective =
Genode::Child::effective_quota(assigned_cap_quota);
/* capabilities additionally consumed by init */
enum {
STATIC_COSTS = 1 /* possible heap backing-store
allocation for session object */
+ 1 /* buffered XML start node */
+ 2 /* dynamic ROM for config */
+ 2 /* dynamic ROM for session requester */
};
if (effective.value < STATIC_COSTS)
return Cap_quota{0};
return Cap_quota{effective.value - STATIC_COSTS};
}
};
static
Resources _resources_from_start_node(Xml_node start_node, Prio_levels prio_levels,
Affinity::Space const &affinity_space, Affinity::Location const &location,
Cap_quota default_cap_quota)
{
unsigned cpu_percent = 0;
Number_of_bytes ram_bytes = 0;
size_t caps = start_node.attribute_value("caps", default_cap_quota.value);
start_node.for_each_sub_node("resource", [&] (Xml_node rsc) {
using Name = String<8>;
Name const name = rsc.attribute_value("name", Name());
if (name == "RAM")
ram_bytes = rsc.attribute_value("quantum", ram_bytes);
if (name == "CPU")
cpu_percent = rsc.attribute_value("quantum", 0U);
if (name == "CAP")
caps = rsc.attribute_value("quantum", 0UL);
});
return Resources { log2(prio_levels.value),
priority_from_xml(start_node, prio_levels),
Affinity(affinity_space,
location),
Ram_quota { ram_bytes },
Cap_quota { caps },
Cpu_quota { cpu_percent } };
}
Resources _resources;
Ram_quota _configured_ram_quota() const;
Cap_quota _configured_cap_quota() const;
Pd_intrinsics &_pd_intrinsics;
void _with_pd_intrinsics(auto const &fn)
{
with_pd_intrinsics(_pd_intrinsics, _child.pd_session_cap(), _child.pd(), fn);
}
Capability<Pd_session> _ref_pd_cap { }; /* defined by 'init' */
using Local_service = Genode::Sandbox::Local_service_base;
Registry<Parent_service> &_parent_services;
Registry<Routed_service> &_child_services;
Registry<Local_service> &_local_services;
struct Inline_config_rom_service : Abandonable, Dynamic_rom_session::Content_producer
{
using Service = Genode::Local_service<Dynamic_rom_session>;
Child &_child;
Dynamic_rom_session _session { _child._env.ep().rpc_ep(),
_child._env.ram(), _child._env.rm(),
*this };
Service::Single_session_factory _factory { _session };
Service _service { _factory };
Inline_config_rom_service(Child &child) : _child(child) { }
/**
* Dynamic_rom_session::Content_producer interface
*/
void produce_content(char *dst, Genode::size_t dst_len) override
{
Xml_node config = _child._start_node->xml().has_sub_node("config")
? _child._start_node->xml().sub_node("config")
: Xml_node("<config/>");
size_t const config_len = config.size();
if (config_len + 1 /* null termination */ >= dst_len)
throw Buffer_capacity_exceeded();
config.with_raw_node([&] (char const *start, size_t length) {
/*
* The 'length' is the number of bytes of the config-node
* content, which is not null-terminated. Since
* 'Genode::copy_cstring' always null-terminates the
* result, the last byte of the source string is not
* copied. Hence, it is safe to add '1' to 'length' and
* thereby include the last actual config-content character
* in the result.
*/
copy_cstring(dst, start, length + 1);
});
}
void trigger_update() { _session.trigger_update(); }
Service &service() { return _service; }
};
Constructible<Inline_config_rom_service> _config_rom_service { };
Session_requester _session_requester;
/**
* CPU-session priority parameters
*/
long const _prio_levels_log2 { _resources.prio_levels_log2 };
long const _priority { _resources.priority };
Cpu_quota const _effective_cpu_quota {
min(_cpu_limit_accessor.resource_limit(Cpu_quota{}).percent,
_resources.assigned_cpu_quota.percent) };
/**
* If set to true, the child is allowed to do system management,
* e.g., constrain physical RAM allocations.
*/
bool const _managing_system {
_start_node->xml().attribute_value("managing_system", false) };
/**
* Resource request initiated by the child
*/
struct Requested_resources
{
Ram_quota const ram;
Cap_quota const caps;
Requested_resources(Parent::Resource_args const &args)
:
ram (ram_quota_from_args(args.string())),
caps(cap_quota_from_args(args.string()))
{ }
};
Constructible<Requested_resources> _requested_resources { };
Genode::Child _child { _env.rm(), _env.ep().rpc_ep(), *this };
struct Pd_accessor : Routed_service::Pd_accessor
{
Genode::Child &_child;
Pd_accessor(Genode::Child &child) : _child(child) { }
Pd_session &pd() override { return _child.pd(); }
Pd_session_capability pd_cap() const override { return _child.pd_session_cap(); }
} _pd_accessor { _child };
struct Ram_accessor : Routed_service::Ram_accessor
{
Genode::Child &_child;
Ram_accessor(Genode::Child &child) : _child(child) { }
Pd_session &ram() override { return _child.pd(); }
Pd_session_capability ram_cap() const override { return _child.pd_session_cap(); }
} _ram_accessor { _child };
/**
* Async_service::Wakeup callback
*/
void wakeup_async_service() override
{
_session_requester.trigger_update();
}
enum class Route_state { VALID, MISMATCH, UNAVAILABLE };
/**
* Return true if the policy results in the current route of the session
*
* This method is used to check if a policy change affects an existing
* client session of a child, i.e., to determine whether the child must
* be restarted.
*/
Route_state _route_valid(Session_state const &session)
{
try {
Route const route =
resolve_session_request(session.service().name(),
session.client_label(),
session.diag());
bool const valid = (session.service() == route.service)
&& (route.label == session.label());
return valid ? Route_state::VALID : Route_state::MISMATCH;
}
catch (Service_denied) { return Route_state::UNAVAILABLE; }
}
static Xml_node _provides_sub_node(Xml_node start_node)
{
return start_node.has_sub_node("provides")
? start_node.sub_node("provides") : Xml_node("<provides/>");
}
/**
* Return true if service is provided by this child
*/
bool _provided_by_this(Routed_service const &service) const
{
return service.has_id_space(_session_requester.id_space());
}
/**
* Return true if service of specified <provides> sub node is known
*/
bool _service_exists(Xml_node node) const
{
bool exists = false;
_child_services.for_each([&] (Routed_service const &service) {
if (_provided_by_this(service) &&
service.name() == node.attribute_value("name", Service::Name()))
exists = true; });
return exists && !abandoned() && !restart_scheduled();
}
void _add_service(Xml_node service)
{
Service::Name const name =
service.attribute_value("name", Service::Name());
if (_verbose.enabled())
log(" provides service ", name);
new (_alloc)
Routed_service(_child_services, this->name(),
_pd_accessor, _ram_accessor,
_session_requester.id_space(),
_child.session_factory(),
name, *this);
}
/*
* Exit state of the child set when 'exit()' is executed
* and reported afterwards through the state report.
*/
bool _exited { false };
int _exit_value { -1 };
/**
* Return true if it's safe to call the PD for requesting resource
* information
*/
bool _pd_alive() const
{
return !abandoned() && !restart_scheduled() && !_exited;
}
void _destroy_services();
struct Sampled_state
{
Ram_info ram;
Cap_info caps;
static Sampled_state from_pd(Pd_session &pd)
{
return { .ram = Ram_info::from_pd(pd),
.caps = Cap_info::from_pd(pd) };
}
bool operator != (Sampled_state const &other) const
{
return (ram != other.ram) || (caps != other.caps);
}
} _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:
/**
* Constructor
*
* \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_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,
Verbose const &verbose,
Id id,
Report_update_trigger &report_update_trigger,
Xml_node start_node,
Default_route_accessor &default_route_accessor,
Default_caps_accessor &default_caps_accessor,
Name_registry &name_registry,
Ram_limit_accessor &ram_limit_accessor,
Cap_limit_accessor &cap_limit_accessor,
Cpu_limit_accessor &cpu_limit_accessor,
Cpu_quota_transfer &cpu_quota_transfer,
Prio_levels prio_levels,
Affinity::Space const &affinity_space,
Affinity::Location &location,
Registry<Parent_service> &parent_services,
Registry<Routed_service> &child_services,
Registry<Local_service> &local_services,
Pd_intrinsics &pd_intrinsics);
virtual ~Child();
/**
* Return true if the child has the specified name
*/
bool has_name(Child_policy::Name const &str) const { return str == name(); }
bool has_version(Version const &version) const { return version == _version; }
Ram_quota ram_quota() const { return _resources.assigned_ram_quota; }
Cap_quota cap_quota() const { return _resources.assigned_cap_quota; }
Cpu_quota cpu_quota() const { return _effective_cpu_quota; }
void try_start()
{
if (_state == State::INITIAL) {
_child.initiate_env_pd_session();
_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();
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 <start> node disappeared or 'restart_scheduled'
* was handled.
*/
void 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 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 { PROVIDED_SERVICES_CHANGED, NO_SIDE_EFFECTS };
/**
* Apply new configuration to child
*
* \throw Allocator::Out_of_memory unable to allocate buffer for new
* config
*/
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 <typename QUOTA, typename LIMIT_ACCESSOR>
void _apply_resource_upgrade(QUOTA &, QUOTA, LIMIT_ACCESSOR const &);
template <typename QUOTA, typename CHILD_AVAIL_QUOTA_FN>
void _apply_resource_downgrade(QUOTA &, QUOTA, QUOTA,
CHILD_AVAIL_QUOTA_FN const &);
void apply_upgrade();
void apply_downgrade();
void heartbeat()
{
if (_heartbeat_expected())
_child.heartbeat();
unsigned const skipped_heartbeats = _child.skipped_heartbeats();
if (_last_skipped_heartbeats != skipped_heartbeats)
_report_update_trigger.trigger_report_update();
_last_skipped_heartbeats = skipped_heartbeats;
}
unsigned skipped_heartbeats() const
{
return _heartbeat_expected() ? _child.skipped_heartbeats() : 0;
}
void report_state(Xml_generator &, Report_detail const &) const;
Sample_state_result sample_state();
struct Resources &resources() { return _resources; }
void update_affinity(Genode::Affinity affinity) {
//Genode::log("Updating affinity to ", affinity.location(), " in space ", affinity.space());
_resources.affinity = affinity;
//Genode::log("Moving CPU session ", _env.cpu_session_cap());
if (_child.active()) {
}
}
void shrink_cores(Genode::Affinity::Location &cores) {
}
void grow_cores(Genode::Affinity::Location &cores) {
}
bool is_brick() { return false; }
/****************************
** Child-policy interface **
****************************/
Child_policy::Name name() const override { return _unique_name; }
Pd_account &ref_account() override
{
Pd_account *_ref_account_ptr = nullptr;
_with_pd_intrinsics([&] (Pd_intrinsics::Intrinsics &intrinsics) {
_ref_account_ptr = &intrinsics.ref_pd; });
return *_ref_account_ptr;
}
Capability<Pd_account> ref_account_cap() const override { return _ref_pd_cap; }
Ram_allocator &session_md_ram() override { return _env.ram(); }
void init(Pd_session &, Pd_session_capability) override;
void init(Cpu_session &, Cpu_session_capability) override;
Id_space<Parent::Server> &server_id_space() override {
return _session_requester.id_space(); }
Route resolve_session_request(Service::Name const &,
Session_label const &, Session::Diag) override;
void filter_session_args(Service::Name const &, char *, size_t) override;
Affinity filter_session_affinity(Affinity const &) override;
void announce_service(Service::Name const &) override;
void resource_request(Parent::Resource_args const &) override;
void exit(int exit_value) override
{
try {
if (_start_node->xml().sub_node("exit").attribute_value("propagate", false)) {
_env.parent().exit(exit_value);
return;
}
} catch (...) { }
/*
* Trigger a new report for exited children so that any management
* component may react upon it.
*/
_exited = true;
_exit_value = exit_value;
_child.close_all_sessions();
_report_update_trigger.trigger_immediate_report_update();
/*
* Print a message as the exit is not handled otherwise. There are
* a number of automated tests that rely on this message. It is
* printed by the default implementation of 'Child_policy::exit'.
*/
Child_policy::exit(exit_value);
}
void session_state_changed() override
{
_report_update_trigger.trigger_report_update();
}
bool initiate_env_sessions() const override { return false; }
void _with_address_space(Pd_session &, With_address_space_fn const &fn) override
{
_with_pd_intrinsics([&] (Pd_intrinsics::Intrinsics &intrinsics) {
fn.call(intrinsics.address_space); });
}
void start_initial_thread(Capability<Cpu_thread> cap, addr_t ip) override
{
_pd_intrinsics.start_initial_thread(cap, ip);
}
void yield_response() override
{
apply_downgrade();
_report_update_trigger.trigger_report_update();
}
};
#endif /* _LIB__SANDBOX__CHILD_H_ */

View File

@@ -0,0 +1,117 @@
/*
* \brief Child registry
* \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.
*/
#ifndef _LIB__SANDBOX__CHILD_REGISTRY_H_
#define _LIB__SANDBOX__CHILD_REGISTRY_H_
/* local includes */
#include <child.h>
#include <name_registry.h>
#include <alias.h>
#include <report.h>
namespace Sandbox { struct Child_registry; }
class Sandbox::Child_registry : public Name_registry, Child_list
{
private:
List<Alias> _aliases { };
public:
/**
* Register child
*/
void insert(Child *child)
{
Child_list::insert(&child->_list_element);
}
/**
* Unregister child
*/
void remove(Child *child)
{
Child_list::remove(&child->_list_element);
}
/**
* Register alias
*/
void insert_alias(Alias *alias)
{
_aliases.insert(alias);
}
/**
* Unregister alias
*/
void remove_alias(Alias *alias)
{
_aliases.remove(alias);
}
template <typename FN>
void for_each_child(FN const &fn) const
{
Genode::List_element<Child> const *curr = first();
for (; curr; curr = curr->next())
fn(*curr->object());
}
template <typename FN>
void for_each_child(FN const &fn)
{
Genode::List_element<Child> *curr = first(), *next = nullptr;
for (; curr; curr = next) {
next = curr->next();
fn(*curr->object());
}
}
void report_state(Xml_generator &xml, Report_detail const &detail) const
{
for_each_child([&] (Child &child) { child.report_state(xml, detail); });
for (Alias const *a = _aliases.first(); a; a = a->next()) {
xml.node("alias", [&] () {
xml.attribute("name", a->name);
xml.attribute("child", a->child);
});
}
}
Child::Sample_state_result sample_state()
{
auto result = Child::Sample_state_result::UNCHANGED;
for_each_child([&] (Child &child) {
if (result == Child::Sample_state_result::UNCHANGED)
result = child.sample_state(); });
return result;
}
Child::Name deref_alias(Child::Name const &name) override
{
for (Alias const *a = _aliases.first(); a; a = a->next())
if (name == a->name)
return a->child;
return name;
}
};
#endif /* _LIB__SANDBOX__CHILD_REGISTRY_H_ */

View File

@@ -0,0 +1,418 @@
/*
* \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 <config_model.h>
using namespace Sandbox;
struct Config_model::Node : Noncopyable, Interface, private List_model<Node>::Element
{
friend class List_model<Node>;
friend class List<Node>;
static bool type_matches(Xml_node const &xml);
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<Buffered_xml> &_default_route;
Default_route_node(Allocator &alloc, Constructible<Buffered_xml> &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;
Affinity_space_node(Constructible<Affinity::Space> &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("<empty/>"));
}
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)
{
using Name = String<16>;
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); }
};
bool Config_model::Node::type_matches(Xml_node const &xml)
{
return Parent_provides_node::type_matches(xml)
|| Default_route_node ::type_matches(xml)
|| Default_node ::type_matches(xml)
|| Start_node ::type_matches(xml)
|| Affinity_space_node ::type_matches(xml)
|| Report_node ::type_matches(xml)
|| Resource_node ::type_matches(xml)
|| Heartbeat_node ::type_matches(xml)
|| Service_node ::type_matches(xml);
}
void Config_model::update_from_xml(Xml_node const &xml,
Allocator &alloc,
Reconstructible<Verbose> &verbose,
Version &version,
Preservation &preservation,
Constructible<Buffered_xml> &default_route,
Cap_quota &default_caps,
Prio_levels &prio_levels,
Constructible<Affinity::Space> &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 {
_model.update_from_xml(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 {
_model.update_from_xml(xml, create, destroy, update);
}
catch (...) { };
}
void Config_model::trigger_start_children()
{
_model.for_each([&] (Node &node) {
node.trigger_start_child(); });
}

View File

@@ -0,0 +1,292 @@
/*
* \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_
/* Genode includes */
#include <util/list_model.h>
/* local includes */
#include <heartbeat.h>
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<Node>::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<Parent_service>'.
*/
service.abandon();
}
static bool type_matches(Xml_node const &) { return true; }
bool matches(Xml_node const &xml) const
{
return xml.attribute_value("name", Service::Name()) == service.name();
};
};
List_model<Node> _model { };
Parent_provides_model(Allocator &alloc, Verbose const &verbose, Factory &factory)
:
_alloc(alloc), _verbose(verbose), _factory(factory)
{ }
~Parent_provides_model()
{
update_from_xml(Xml_node("<empty/>"));
}
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 {
_model.update_from_xml(xml, create, destroy, update);
} catch (...) {
error("unable to apply complete configuration");
}
}
};
struct Sandbox::Start_model : Noncopyable
{
/*
* The 'Start_model' represents both '<alias>' nodes and '<start>' nodes
* because both node types share the same name space.
*/
using Name = Child_policy::Name;
using Version = Child::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<Node> _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:
using Version = State_reporter::Version;
void update_from_xml(Xml_node const &,
Allocator &,
Reconstructible<Verbose> &,
Version &,
Preservation &,
Constructible<Buffered_xml> &,
Cap_quota &,
Prio_levels &,
Constructible<Affinity::Space> &,
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_ */

View File

@@ -0,0 +1,122 @@
/*
* \brief Hoitaja — Core Allocator
* \author Michael Müller, Norman Feske (Init)
* \date 2023-04-20
*/
/*
* Copyright (C) 2010-2017 Genode Labs GmbH
* Copyright (C) 2023 Michael Müller, Osnabrück University
*
* This file is part of EalánOS, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#pragma once
/* Genode includes */
#include <child.h>
#include <utils.h>
#include <types.h>
#include <util/string.h>
#include <tukija/syscalls.h>
namespace Hoitaja
{
class Core_allocator;
}
class Hoitaja::Core_allocator
{
private:
Genode::Affinity::Space &_affinity_space;
::Sandbox::Prio_levels &_prio_levels;
double _resource_coeff; // Coefficient used for calculating resource shares
unsigned int _cores_for_cells; // Number of cores available to cells. This is the total number of cores in the habitat minus the cores occupied by bricks.
public:
inline unsigned int _calculate_resource_share(long priority) {
double ref_share = static_cast<double>(_cores_for_cells) / _resource_coeff;
return static_cast<unsigned int>((1.0 / static_cast<double>(priority)) * ref_share);
}
Core_allocator(Genode::Affinity::Space &affinity_space, ::Sandbox::Prio_levels prio_levels) : _affinity_space(affinity_space), _prio_levels(prio_levels), _resource_coeff(0.0), _cores_for_cells(_affinity_space.total())
{
Genode::log("Created core allocator for ", affinity_space.total(), " cores and ", prio_levels.value, " priorities.");
//Nova::create_habitat(0, affinity_space.total());
}
unsigned int cores_available() {
return _cores_for_cells;
}
Genode::Affinity::Location allocate_cores_for_cell(Genode::Xml_node const &start_node)
{
/*if (::Sandbox::is_brick_from_xml(start_node)) {
Genode::Affinity::Location brick = ::Sandbox::affinity_location_from_xml(_affinity_space, start_node);
_cores_for_cells -= brick.width();
return brick;
}*/
// Calculate affinity from global affinity space and priority
long priority = ::Sandbox::priority_from_xml(start_node, _prio_levels);
priority = (priority == 0) ? 1 : priority;
_resource_coeff += (1.0/static_cast<double>(priority)); // treat priority 0 same as 1, to avoid division by zero here
unsigned int cores_share = _calculate_resource_share(priority);
return Genode::Affinity::Location( _cores_for_cells-cores_share, 0, cores_share, 1 ); /* always use the core_share last cores, for now */
}
void free_cores_from_cell(::Sandbox::Child &cell)
{
/* Remove cell's coefficient from the global resource coefficient.
* This is necessary in order to be able to redistribute the freed resources correctly. We do not trigger the redistribution itself here, because the child has not been fully destroyed yet, thus its resources might still be occupied at this point. */
_resource_coeff -= 1.0 / static_cast<double>(cell.resources().priority);
}
/**
* @brief Update core allocations for cells reported by ::Sandbox::Child controller
*
*/
void update(::Sandbox::Child &cell, int *xpos, int *lower_limit) {
if (cell.abandoned())
return;
/*::Sandbox::Child::Resources resources = cell.resources();
long priority = (resources.priority == 0)? 1 : resources.priority;
unsigned int cores_share = _calculate_resource_share(priority);
unsigned int cores_to_reclaim = resources.affinity.location().width() * resources.affinity.location().height() - cores_share;
cores_to_reclaim = (static_cast<int>(cores_to_reclaim) < 0) ? 0 : cores_to_reclaim;
if (*xpos - static_cast<int>(cores_share) <= *lower_limit) {
cores_share-= *lower_limit; // Save one core for Hoitaja
}
Genode::Affinity::Location location(*xpos - cores_share, resources.affinity.location().ypos(), cores_share, resources.affinity.location().height());
if (resources.affinity.location() != location) { // Only update, if location has actually changed
cell.update_affinity(Genode::Affinity(resources.affinity.space(), location));
}
if (location.width() > resources.affinity.location().width()) {
cell.grow_cores(location);
}
*xpos = location.xpos();
// TODO: Update affinity of existing sessions for cell
// TODO: Send yield request to cell
if (cores_to_reclaim > 0) {
log("Need to reclaim ", cores_to_reclaim, " cores from ", cell.name());
cell.shrink_cores(location);
}*/
}
};

View File

@@ -0,0 +1,89 @@
/*
* \brief Heartbeat monitoring
* \author Norman Feske
* \date 2018-11-15
*/
/*
* Copyright (C) 2018 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 _LIB__SANDBOX__HEARTBEAT_H_
#define _LIB__SANDBOX__HEARTBEAT_H_
/* local includes */
#include <state_reporter.h>
#include <child_registry.h>
#include <util/noncopyable.h>
namespace Sandbox { class Heartbeat; }
class Sandbox::Heartbeat : Noncopyable
{
private:
Env &_env;
Child_registry &_children;
Report_update_trigger &_report_update_trigger;
Constructible<Timer::Connection> _timer { };
uint64_t _rate_ms = 0;
Signal_handler<Heartbeat> _timer_handler;
void _handle_timer()
{
bool any_skipped_heartbeats = false;
_children.for_each_child([&] (Child &child) {
if (child.skipped_heartbeats())
any_skipped_heartbeats = true;
child.heartbeat();
});
if (any_skipped_heartbeats)
_report_update_trigger.trigger_report_update();
}
public:
Heartbeat(Env &env, Child_registry &children,
Report_update_trigger &report_update_trigger)
:
_env(env), _children(children),
_report_update_trigger(report_update_trigger),
_timer_handler(_env.ep(), *this, &Heartbeat::_handle_timer)
{ }
void disable()
{
_timer.destruct();
_rate_ms = 0;
}
void apply_config(Xml_node heartbeat)
{
if (!_timer.constructed()) {
_timer.construct(_env);
_timer->sigh(_timer_handler);
}
unsigned const rate_ms = heartbeat.attribute_value("rate_ms", 1000U);
if (rate_ms != _rate_ms) {
_rate_ms = rate_ms;
_timer->trigger_periodic(_rate_ms*1000);
}
}
};
#endif /* _LIB__SANDBOX__HEARTBEAT_H_ */

View File

@@ -0,0 +1,690 @@
/*
* \brief Sandbox library
* \author Norman Feske
* \date 2020-01-10
*/
/*
* 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.
*/
/* Genode includes */
#include <base/attached_rom_dataspace.h>
#include <sandbox/sandbox.h>
/* local includes */
#include <child.h>
#include <alias.h>
#include <server.h>
#include <heartbeat.h>
#include <config_model.h>
#include <core_allocator.h>
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::Cpu_limit_accessor,
::Sandbox::Child::Cpu_quota_transfer,
::Sandbox::Start_model::Factory,
::Sandbox::Parent_provides_model::Factory
{
using Routed_service = ::Sandbox::Routed_service;
using Parent_service = ::Sandbox::Parent_service;
using Local_service = ::Genode::Sandbox::Local_service_base;
using Report_detail = ::Sandbox::Report_detail;
using Child_registry = ::Sandbox::Child_registry;
using Verbose = ::Sandbox::Verbose;
using State_reporter = ::Sandbox::State_reporter;
using Heartbeat = ::Sandbox::Heartbeat;
using Server = ::Sandbox::Server;
using Alias = ::Sandbox::Alias;
using Child = ::Sandbox::Child;
using Prio_levels = ::Sandbox::Prio_levels;
using Ram_info = ::Sandbox::Ram_info;
using Cap_info = ::Sandbox::Cap_info;
using Cpu_quota = ::Sandbox::Cpu_quota;
using Config_model = ::Sandbox::Config_model;
using Start_model = ::Sandbox::Start_model;
using Preservation = ::Sandbox::Preservation;
Env &_env;
Heap &_heap;
Constructible<Hoitaja::Core_allocator> _core_allocator;
Pd_intrinsics &_pd_intrinsics;
Registry<Parent_service> _parent_services { };
Registry<Routed_service> _child_services { };
Registry<Local_service> &_local_services;
Child_registry _children { };
/*
* Global parameters obtained from config
*/
Reconstructible<Verbose> _verbose { };
Config_model::Version _version { };
Constructible<Buffered_xml> _default_route { };
Cap_quota _default_caps { 0 };
Prio_levels _prio_levels { };
Constructible<Affinity::Space> _affinity_space { };
Preservation _preservation { };
Affinity::Space _effective_affinity_space() const
{
return _affinity_space.constructed() ? *_affinity_space
: Affinity::Space { 1, 1 };
}
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;
Cpu_quota _avail_cpu { .percent = 100 };
Cpu_quota _transferred_cpu { .percent = 0 };
Ram_quota _avail_ram() const
{
Ram_quota avail_ram = _env.pd().avail_ram();
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 - _preservation.ram.value };
}
Cap_quota _avail_caps() const
{
Cap_quota avail_caps { _env.pd().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 - _preservation.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(); }
/**
* Child::Cpu_limit_accessor interface
*/
Cpu_quota resource_limit(Cpu_quota const &) const override { return _avail_cpu; }
/**
* Child::Cpu_quota_transfer interface
*/
void transfer_cpu_quota(Capability<Pd_session> pd_cap, Pd_session &pd,
Capability<Cpu_session> cpu, Cpu_quota quota) override
{
Cpu_quota const remaining { 100 - min(100u, _transferred_cpu.percent) };
/* prevent division by zero in 'quota_lim_upscale' */
if (remaining.percent == 0)
return;
size_t const fraction =
Cpu_session::quota_lim_upscale(quota.percent, remaining.percent);
Child::with_pd_intrinsics(_pd_intrinsics, pd_cap, pd, [&] (auto &intrinsics) {
intrinsics.ref_cpu.transfer_quota(cpu, fraction); });
_transferred_cpu.percent += quota.percent;
}
/**
* State_reporter::Producer interface
*/
void produce_state_report(Xml_generator &xml, Report_detail const &detail) const override
{
if (detail.init_ram())
xml.node("ram", [&] () { Ram_info::from_pd(_env.pd()).generate(xml); });
if (detail.init_caps())
xml.node("caps", [&] () { Cap_info::from_pd(_env.pd()).generate(xml); });
if (detail.children())
_children.report_state(xml, detail);
}
/**
* State_reporter::Producer interface
*/
Child::Sample_state_result sample_children_state() override
{
return _children.sample_state();
}
/**
* Default_route_accessor interface
*/
void _with_default_route(Child::With_xml::Ft const &fn) override
{
if (_default_route.constructed())
fn(_default_route->xml());
}
/**
* Default_caps_accessor interface
*/
Cap_quota default_caps() override { return _default_caps; }
void _update_aliases_from_config(Xml_node const &);
void _update_parent_services_from_config(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);
}
/**
* Default way of using the 'Env::pd' as the child's 'ref_pd' and accessing
* the child's address space via RPC.
*/
struct Default_pd_intrinsics : Pd_intrinsics
{
Env &_env;
void with_intrinsics(Capability<Pd_session>, Pd_session &pd,
With_intrinsics::Ft const &fn) override
{
Region_map_client region_map(pd.address_space());
Intrinsics intrinsics { _env.pd(), _env.pd_session_cap(),
_env.cpu(), _env.cpu_session_cap(), region_map };
fn(intrinsics);
}
void start_initial_thread(Capability<Cpu_thread> cap, addr_t ip) override
{
Cpu_thread_client(cap).start(ip, 0);
}
Default_pd_intrinsics(Env &env) : _env(env) { }
} _default_pd_intrinsics { _env };
Library(Env &env, Heap &heap, Registry<Local_service> &local_services,
State_handler &state_handler, Pd_intrinsics &pd_intrinsics)
:
_env(env), _heap(heap), _core_allocator(), _pd_intrinsics(pd_intrinsics),
_local_services(local_services), _state_reporter(_env, *this, state_handler)
{ }
Library(Env &env, Heap &heap, Registry<Local_service> &local_services,
State_handler &state_handler)
:
Library(env, heap, local_services, state_handler, _default_pd_intrinsics)
{ }
void apply_config(Xml_node const &);
void generate_state_report(Xml_generator &xml) const
{
_state_reporter.generate(xml);
}
};
void Genode::Sandbox::Library::_destroy_abandoned_parent_services()
{
_parent_services.for_each([&] (Parent_service &service) {
if (service.abandoned())
destroy(_heap, &service); });
}
void Genode::Sandbox::Library::_destroy_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();
_state_report_outdated = true;
/* destroy child once all environment sessions are gone */
if (child.env_sessions_closed()) {
_core_allocator->free_cores_from_cell(child);
_children.remove(&child);
Cpu_quota const child_cpu_quota = child.cpu_quota();
destroy(_heap, &child);
/* replenish available CPU quota */
_avail_cpu.percent += child_cpu_quota.percent;
_transferred_cpu.percent -= min(_transferred_cpu.percent,
child_cpu_quota.percent);
}
});
}
bool Genode::Sandbox::Library::ready_to_create_child(Start_model::Name const &name,
Start_model::Version const &version) const
{
bool exists = false;
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()));
if (_affinity_space.constructed() && !_core_allocator.constructed())
_core_allocator.construct(*_affinity_space, _prio_levels);
Affinity::Location allocation = _core_allocator->allocate_cores_for_cell(start_node);
if (allocation.width() < 1) {
error("Faild to create child ", start_node.attribute_value("name", Child_policy::Name()), ": insufficient CPU cores.");
throw ::Sandbox::Start_model::Factory::Creation_failed();
}
try {
Child &child = *new (_heap)
Child(_env, _heap, *_verbose,
Child::Id { ++_child_cnt }, _state_reporter,
start_node, *this, *this, _children, *this, *this, *this, *this,
_prio_levels, _effective_affinity_space(), allocation,
_parent_services, _child_services, _local_services,
_pd_intrinsics);
_children.insert(&child);
_avail_cpu.percent -= min(_avail_cpu.percent, child.cpu_quota().percent);
if (start_node.has_sub_node("provides"))
_server_appeared_or_disappeared = true;
_state_report_outdated = true;
return child;
}
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 (Attached_dataspace::Region_conflict) {
warning("failed to attach 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);
/*
* 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.
*/
while (true) {
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
* 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(); });
if (_state_report_outdated)
_state_reporter.trigger_immediate_report_update();
}
/*********************************
** Sandbox::Local_service_base **
*********************************/
void Genode::Sandbox::Local_service_base::_for_each_requested_session(With_request::Ft const &fn)
{
_server_id_space.for_each<Session_state>([&] (Session_state &session) {
if (session.phase == Session_state::CREATE_REQUESTED) {
Request request(session);
fn(request);
bool wakeup_client = false;
if (request._denied) {
session.phase = Session_state::SERVICE_DENIED;
wakeup_client = true;
}
if (request._session_ptr) {
session.local_ptr = request._session_ptr;
session.cap = request._session_cap;
session.phase = Session_state::AVAILABLE;
wakeup_client = true;
}
if (wakeup_client && session.ready_callback)
session.ready_callback->session_ready(session);
}
});
}
void Genode::Sandbox::Local_service_base::_for_each_upgraded_session(With_upgrade::Ft const &fn)
{
_server_id_space.for_each<Session_state>([&] (Session_state &session) {
if (session.phase != Session_state::UPGRADE_REQUESTED)
return;
if (session.local_ptr == nullptr)
return;
bool wakeup_client = false;
Session::Resources const amount { session.ram_upgrade,
session.cap_upgrade };
switch (fn(*session.local_ptr, amount)) {
case Upgrade_response::CONFIRMED:
session.phase = Session_state::CAP_HANDED_OUT;
wakeup_client = true;
break;
case Upgrade_response::DEFERRED:
break;
}
if (wakeup_client && session.ready_callback)
session.ready_callback->session_ready(session);
});
}
void Genode::Sandbox::Local_service_base::_for_each_session_to_close(With_close::Ft const &fn)
{
/*
* Collection of closed sessions to be destructed via callback
*
* For asynchronous sessions, the 'Session_state' object is destructed by
* the 'Closed_callback'. We cannot issue the callback from within
* '_server_id_space.for_each()' because the destruction of 'id_at_server'
* would deadlock. Instead be collect the 'Session_state' objects to be
* called back in the 'pending_callbacks' ID space. This is possible
* because the parent ID space is not used for local services.
*/
Id_space<Parent::Client> pending_callbacks { };
_server_id_space.for_each<Session_state>([&] (Session_state &session) {
if (session.phase != Session_state::CLOSE_REQUESTED)
return;
if (session.local_ptr == nullptr)
return;
switch (fn(*session.local_ptr)) {
case Close_response::CLOSED:
session.phase = Session_state::CLOSED;
session.id_at_parent.construct(session, pending_callbacks);
break;
case Close_response::DEFERRED:
break;
}
});
/*
* Purge 'Session_state' objects by calling 'closed_callback'
*/
while (pending_callbacks.apply_any<Session_state>([&] (Session_state &session) {
session.id_at_parent.destruct();
if (session.closed_callback)
session.closed_callback->session_closed(session);
else
session.destroy();
}));
}
Genode::Sandbox::Local_service_base::Local_service_base(Sandbox &sandbox,
Name const &name,
Wakeup &wakeup)
:
Service(name),
_element(sandbox._local_services, *this),
_session_factory(sandbox._heap, Session_state::Factory::Batch_size{16}),
_async_wakeup(wakeup),
_async_service(name, _server_id_space, _session_factory, _async_wakeup)
{ }
/*************
** Sandbox **
*************/
void Genode::Sandbox::apply_config(Xml_node const &config)
{
_library.apply_config(config);
}
void Genode::Sandbox::generate_state_report(Xml_generator &xml) const
{
_library.generate_state_report(xml);
}
Genode::Sandbox::Sandbox(Env &env, State_handler &state_handler, Pd_intrinsics &pd_intrinsics)
:
_heap(env.ram(), env.rm()),
_library(*new (_heap) Library(env, _heap, _local_services, state_handler, pd_intrinsics))
{ }
Genode::Sandbox::Sandbox(Env &env, State_handler &state_handler)
:
_heap(env.ram(), env.rm()),
_library(*new (_heap) Library(env, _heap, _local_services, state_handler))
{ }

View File

@@ -0,0 +1,39 @@
/*
* \brief Interface for database of child names
* \author Norman Feske
* \date 2017-03-03
*/
/*
* Copyright (C) 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.
*/
#ifndef _LIB__SANDBOX__NAME_REGISTRY_H_
#define _LIB__SANDBOX__NAME_REGISTRY_H_
/* Genode includes */
#include <base/child.h>
/* local includes */
#include <types.h>
namespace Sandbox { struct Name_registry; }
struct Sandbox::Name_registry
{
virtual ~Name_registry() { }
using Name = Child_policy::Name;
/**
* Return child name for a given alias name
*
* If there is no alias, the function returns the original name.
*/
virtual Name deref_alias(Name const &) = 0;
};
#endif /* _LIB__SANDBOX__NAME_REGISTRY_H_ */

View File

@@ -0,0 +1,91 @@
/*
* \brief Report configuration
* \author Norman Feske
* \date 2017-01-16
*/
/*
* Copyright (C) 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.
*/
#ifndef _LIB__SANDBOX__REPORT_H_
#define _LIB__SANDBOX__REPORT_H_
/* Genode includes */
#include <util/noncopyable.h>
#include <util/xml_node.h>
/* local includes */
#include <types.h>
namespace Sandbox {
struct Report_update_trigger;
struct Report_detail;
}
class Sandbox::Report_detail : Genode::Noncopyable
{
private:
bool _children = false;
bool _ids = false;
bool _requested = false;
bool _provided = false;
bool _session_args = false;
bool _child_ram = false;
bool _child_caps = false;
bool _init_ram = false;
bool _init_caps = false;
public:
Report_detail() { }
Report_detail(Genode::Xml_node report)
{
_children = true;
_ids = report.attribute_value("ids", false);
_requested = report.attribute_value("requested", false);
_provided = report.attribute_value("provided", false);
_session_args = report.attribute_value("session_args", false);
_child_ram = report.attribute_value("child_ram", false);
_child_caps = report.attribute_value("child_caps", false);
_init_ram = report.attribute_value("init_ram", false);
_init_caps = report.attribute_value("init_caps", false);
}
bool children() const { return _children; }
bool ids() const { return _ids; }
bool requested() const { return _requested; }
bool provided() const { return _provided; }
bool session_args() const { return _session_args; }
bool child_ram() const { return _child_ram; }
bool child_caps() const { return _child_caps; }
bool init_ram() const { return _init_ram; }
bool init_caps() const { return _init_caps; }
};
struct Sandbox::Report_update_trigger : Interface
{
/**
* Trigger regular (rate-limited) report update
*/
virtual void trigger_report_update() = 0;
/**
* Trigger immediate report update
*
* This method is intended for situations that require a timely response of
* the consumer of the report. This is particularly important for resource
* requests that would otherwise unnecessarily stall the execution of the
* respective child.
*/
virtual void trigger_immediate_report_update() = 0;
};
#endif /* _LIB__SANDBOX__REPORT_H_ */

View File

@@ -0,0 +1,280 @@
/*
* \brief Internal model of routing rules
* \author Norman Feske
* \date 2021-04-05
*/
/*
* 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 _ROUTE_MODEL_H_
#define _ROUTE_MODEL_H_
/* local includes */
#include <types.h>
namespace Sandbox {
struct Checksum;
class Route_model;
}
struct Sandbox::Checksum
{
unsigned long value = 0;
bool valid;
/**
* Constructor
*/
Checksum(char const *s) : valid(s != nullptr)
{
if (!s)
return;
while (uint8_t const byte = *s++) {
/* rotate value */
unsigned long const sign_bit = ((long)value < 0);
value = (value << 1) | sign_bit;
/* xor byte to lowest 8 bit */
value = value ^ (unsigned long)byte;
}
}
template <size_t N>
Checksum(String<N> const &s) : Checksum(s.string()) { }
bool operator != (Checksum const &other) const
{
return (other.value != value) || !valid;
}
};
class Sandbox::Route_model : Noncopyable
{
public:
struct Query : Noncopyable
{
Child_policy::Name const &child;
Service::Name const &service;
Session_label const &label;
Checksum const service_checksum { service };
Checksum const label_checksum { skip_label_prefix(child.string(),
label.string()) };
Query(Child_policy::Name const &child,
Service::Name const &service,
Session_label const &label)
:
child(child), service(service), label(label)
{ }
};
class Rule : Noncopyable, List<Rule>::Element
{
private:
friend class List<Rule>;
friend class Route_model;
friend void Genode::destroy<Rule>(Allocator &, Rule *);
Allocator &_alloc;
Xml_node const _node; /* points to 'Route_model::_route_node' */
struct Selector
{
using Label = String<Session_label::capacity()>;
enum class Type
{
NO_LABEL, SPECIFIC_LABEL,
/*
* Presence of 'label_last', 'label_prefix',
* 'label_suffix', 'unscoped_label', or even
* a combination of attributes.
*/
COMPLICATED
} type = Type::NO_LABEL;
Checksum label_checksum { "" };
Selector(Xml_node const &node)
{
bool const complicated =
node.has_attribute("label_prefix") ||
node.has_attribute("label_suffix") ||
node.has_attribute("label_last") ||
node.has_attribute("unscoped_label");
if (complicated) {
type = Type::COMPLICATED;
return;
}
Label const label = node.attribute_value("label", Label());
if (label.valid()) {
type = Type::SPECIFIC_LABEL;
label_checksum = Checksum(label);
}
}
};
Selector const _selector;
Checksum const _service_checksum;
bool const _specific_service { _node.has_type("service") };
struct Target : Noncopyable, private List<Target>::Element
{
friend class List<Target>;
friend class Rule;
Xml_node const node; /* points to 'Route_model::_route_node' */
Target(Xml_node const &node) : node(node) { }
};
List<Target> _targets { };
/**
* Constructor is private to 'Route_model'
*/
Rule(Allocator &alloc, Xml_node const &node)
:
_alloc(alloc), _node(node), _selector(node),
_service_checksum(node.attribute_value("name", Service::Name()))
{
Target const *at_ptr = nullptr;
node.for_each_sub_node([&] (Xml_node sub_node) {
Target &target = *new (_alloc) Target(sub_node);
_targets.insert(&target, at_ptr);
at_ptr = &target;
});
}
~Rule()
{
while (Target *target_ptr = _targets.first()) {
_targets.remove(target_ptr);
destroy(_alloc, target_ptr);
}
}
/**
* Quick check for early detection of definite mismatches
*
* \return true if query definitely mismatches the rule,
* false if the undecided
*/
bool _mismatches(Query const &query) const
{
if (_specific_service
&& query.service_checksum != _service_checksum)
return true;
if (_selector.type == Selector::Type::SPECIFIC_LABEL
&& query.label_checksum != _selector.label_checksum)
return true;
return false;
}
public:
bool matches(Query const &query) const
{
/* handle common case */
if (_mismatches(query))
return false;
return service_node_matches(_node,
query.label,
query.child,
query.service);
}
template <typename FN>
Child_policy::Route resolve(FN const &fn) const
{
for (Target const *t = _targets.first(); t; t = t->next()) {
try { return fn(t->node); }
catch (Service_denied) { /* try next target */ }
}
/* query is not accepted by any of the targets */
throw Service_denied();
}
};
private:
Allocator &_alloc;
Buffered_xml const _route_node;
List<Rule> _rules { };
public:
Route_model(Allocator &alloc, Xml_node const &route)
:
_alloc(alloc), _route_node(_alloc, route)
{
Rule const *at_ptr = nullptr;
_route_node.xml().for_each_sub_node([&] (Xml_node const &node) {
Rule &rule = *new (_alloc) Rule(_alloc, node);
_rules.insert(&rule, at_ptr); /* append */
at_ptr = &rule;
});
}
~Route_model()
{
while (Rule *rule_ptr = _rules.first()) {
_rules.remove(rule_ptr);
destroy(_alloc, rule_ptr);
}
}
template <typename FN>
Child_policy::Route resolve(Query const &query, FN const &fn) const
{
for (Rule const *r = _rules.first(); r; r = r->next())
if (r->matches(query)) {
try {
return r->resolve(fn);
}
catch (Service_denied) {
if (r->_specific_service)
throw;
/*
* If none of the targets of a wildcard rule was
* satisfied with the query, continue with the next
* rule.
*/
}
}
warning(query.child, ": no route to "
"service \"", query.service, "\" "
"(label=\"", query.label, "\")");
throw Service_denied();
}
};
#endif /* _ROUTE_MODEL_H_ */

View File

@@ -0,0 +1,444 @@
/*
* \brief Server role of init, forwarding session requests to children
* \author Norman Feske
* \date 2017-03-07
*/
/*
* Copyright (C) 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 <base/quota_transfer.h>
#include <os/session_policy.h>
/* local includes */
#include "server.h"
/******************************
** Sandbox::Server::Service **
******************************/
struct Sandbox::Server::Service : Service_model
{
using Name = Genode::Service::Name;
Name const _name;
Registry<Service>::Element _registry_element;
Allocator &_alloc;
Registry<Routed_service> &_child_services;
Constructible<Buffered_xml> _service_node { };
/**
* Constructor
*
* \param alloc allocator used for buffering the 'service_node'
*/
Service(Registry<Service> &services,
Allocator &alloc,
Xml_node service_node,
Registry<Routed_service> &child_services)
:
_name(service_node.attribute_value("name", Name())),
_registry_element(services, *this),
_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 <service> node policy
*
* \throw Service_denied
*/
Route resolve_session_request(Session_label const &);
Name name() const { return _name; }
};
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());
if (!policy.has_sub_node("child"))
throw Service_denied();
Xml_node target_node = policy.sub_node("child");
Child_policy::Name const child_name =
target_node.attribute_value("name", Child_policy::Name());
using Label = String<Session_label::capacity()>;
Label const target_label =
target_node.attribute_value("label", Label(label.string()));
Routed_service *match = nullptr;
_child_services.for_each([&] (Routed_service &service) {
if (service.child_name() == child_name && service.name() == name())
match = &service; });
if (!match || match->abandoned())
throw Service_not_present();
return Route { *match, target_label };
}
catch (Session_policy::No_policy_defined) {
throw Service_denied(); }
}
/*********************
** Sandbox::Server **
*********************/
Sandbox::Server::Route
Sandbox::Server::_resolve_session_request(Service::Name const &service_name,
Session_label const &label)
{
Service *matching_service = nullptr;
_services.for_each([&] (Service &service) {
if (service.name() == service_name)
matching_service = &service; });
if (!matching_service)
throw Service_not_present();
return matching_service->resolve_session_request(label);
}
static void close_session(Genode::Session_state &session)
{
session.phase = Genode::Session_state::CLOSE_REQUESTED;
session.service().initiate_request(session);
session.service().wakeup();
}
void Sandbox::Server::session_ready(Session_state &session)
{
_report_update_trigger.trigger_report_update();
/*
* If 'session_ready' is called as response to a session-quota upgrade,
* the 'phase' is set to 'CAP_HANDED_OUT' by 'Child::session_response'.
* We just need to forward the state change to our parent.
*/
if (session.phase == Session_state::CAP_HANDED_OUT) {
Parent::Server::Id id { session.id_at_client().value };
_env.parent().session_response(id, Parent::SESSION_OK);
}
if (session.phase == Session_state::AVAILABLE) {
Parent::Server::Id id { session.id_at_client().value };
_env.parent().deliver_session_cap(id, session.cap);
session.phase = Session_state::CAP_HANDED_OUT;
}
if (session.phase == Session_state::SERVICE_DENIED)
_close_session(session, Parent::SERVICE_DENIED);
if (session.phase == Session_state::INSUFFICIENT_RAM_QUOTA)
_close_session(session, Parent::INSUFFICIENT_RAM_QUOTA);
if (session.phase == Session_state::INSUFFICIENT_CAP_QUOTA)
_close_session(session, Parent::INSUFFICIENT_CAP_QUOTA);
}
void Sandbox::Server::_close_session(Session_state &session,
Parent::Session_response response)
{
_report_update_trigger.trigger_report_update();
Ram_transfer::Account &service_ram_account = session.service();
Cap_transfer::Account &service_cap_account = session.service();
service_ram_account.try_transfer(_env.pd_session_cap(),
session.donated_ram_quota());
service_cap_account.try_transfer(_env.pd_session_cap(),
session.donated_cap_quota());
Parent::Server::Id id { session.id_at_client().value };
session.destroy();
_env.parent().session_response(id, response);
}
void Sandbox::Server::session_closed(Session_state &session)
{
_close_session(session, Parent::SESSION_CLOSED);
}
void Sandbox::Server::_handle_create_session_request(Xml_node request,
Parent::Client::Id id)
{
/*
* Ignore requests that are already successfully forwarded (by a prior call
* of '_handle_create_session_request') but still remain present in the
* 'session_requests' ROM because the server child has not responded yet.
*/
try {
_client_id_space.apply<Parent::Client>(id, [&] (Parent::Client const &) { });
return;
} catch (Id_space<Parent::Client>::Unknown_id) { /* normal case */ }
if (!request.has_sub_node("args"))
return;
using Args = Session_state::Args;
Args const args = request.sub_node("args").decoded_content<Args>();
Service::Name const name = request.attribute_value("service", Service::Name());
Session_label const label = label_from_args(args.string());
try {
Route const route = _resolve_session_request(name, label);
/*
* Reduce session quota by local session costs
*/
char argbuf[Parent::Session_args::MAX_SIZE];
copy_cstring(argbuf, args.string(), sizeof(argbuf));
Cap_quota const cap_quota = cap_quota_from_args(argbuf);
Ram_quota const ram_quota = ram_quota_from_args(argbuf);
size_t const keep_quota = route.service.factory().session_costs();
if (ram_quota.value < keep_quota)
throw Genode::Insufficient_ram_quota();
Ram_quota const forward_ram_quota { ram_quota.value - keep_quota };
Arg_string::set_arg(argbuf, sizeof(argbuf), "ram_quota", (int)forward_ram_quota.value);
Session::Diag const diag = session_diag_from_args(args.string());
Session_state &session =
route.service.create_session(route.service.factory(),
_client_id_space, id, route.label,
diag, argbuf, Affinity::from_xml(request));
/* transfer session quota */
try {
Ram_transfer::Remote_account env_ram_account(_env.pd(), _env.pd_session_cap());
Cap_transfer::Remote_account env_cap_account(_env.pd(), _env.pd_session_cap());
Ram_transfer ram_transfer(forward_ram_quota, env_ram_account, route.service);
Cap_transfer cap_transfer(cap_quota, env_cap_account, route.service);
ram_transfer.acknowledge();
cap_transfer.acknowledge();
}
catch (...) {
/*
* This should never happen unless our parent missed to
* transfor the session quota to us prior issuing the session
* request.
*/
warning("unable to transfer session quota "
"(", ram_quota, " bytes, ", cap_quota, " caps) "
"of forwarded ", name, " session");
session.destroy();
throw Service_denied();
}
session.ready_callback = this;
session.closed_callback = this;
/* initiate request */
route.service.initiate_request(session);
/* if request was not handled synchronously, kick off async operation */
if (session.phase == Session_state::CREATE_REQUESTED)
route.service.wakeup();
if (session.phase == Session_state::SERVICE_DENIED)
throw Service_denied();
if (session.phase == Session_state::INSUFFICIENT_RAM_QUOTA)
throw Insufficient_ram_quota();
if (session.phase == Session_state::INSUFFICIENT_CAP_QUOTA)
throw Insufficient_cap_quota();
}
catch (Service_denied) {
_env.parent().session_response(Parent::Server::Id { id.value },
Parent::SERVICE_DENIED); }
catch (Insufficient_ram_quota) {
_env.parent().session_response(Parent::Server::Id { id.value },
Parent::INSUFFICIENT_RAM_QUOTA); }
catch (Insufficient_cap_quota) {
_env.parent().session_response(Parent::Server::Id { id.value },
Parent::INSUFFICIENT_CAP_QUOTA); }
catch (Service_not_present) { /* keep request pending */ }
}
void Sandbox::Server::_handle_upgrade_session_request(Xml_node request,
Parent::Client::Id id)
{
_client_id_space.apply<Session_state>(id, [&] (Session_state &session) {
if (session.phase == Session_state::UPGRADE_REQUESTED)
return;
Ram_quota const ram_quota { request.attribute_value("ram_quota", 0UL) };
Cap_quota const cap_quota { request.attribute_value("cap_quota", 0UL) };
try {
Ram_transfer::Remote_account env_ram_account(_env.pd(), _env.pd_session_cap());
Cap_transfer::Remote_account env_cap_account(_env.pd(), _env.pd_session_cap());
Ram_transfer ram_transfer(ram_quota, env_ram_account, session.service());
Cap_transfer cap_transfer(cap_quota, env_cap_account, session.service());
ram_transfer.acknowledge();
cap_transfer.acknowledge();
}
catch (...) {
warning("unable to upgrade session quota "
"(", ram_quota, " bytes, ", cap_quota, " caps) "
"of forwarded ", session.service().name(), " session");
return;
}
session.phase = Session_state::UPGRADE_REQUESTED;
session.increase_donated_quota(ram_quota, cap_quota);
session.service().initiate_request(session);
session.service().wakeup();
});
}
void Sandbox::Server::_handle_close_session_request(Xml_node, Parent::Client::Id id)
{
_client_id_space.apply<Session_state>(id, [&] (Session_state &session) {
close_session(session); });
}
void Sandbox::Server::_handle_session_request(Xml_node request)
{
if (!request.has_attribute("id"))
return;
/*
* We use the 'Parent::Server::Id' of the incoming request as the
* 'Parent::Client::Id' of the forwarded request.
*/
Parent::Client::Id const id { request.attribute_value("id", 0UL) };
if (request.has_type("create"))
_handle_create_session_request(request, id);
if (request.has_type("upgrade"))
_handle_upgrade_session_request(request, id);
if (request.has_type("close"))
_handle_close_session_request(request, id);
}
void Sandbox::Server::_handle_session_requests()
{
_session_requests->update();
Xml_node const requests = _session_requests->xml();
requests.for_each_sub_node([&] (Xml_node request) {
_handle_session_request(request); });
_report_update_trigger.trigger_report_update();
}
Sandbox::Service_model &Sandbox::Server::create_service(Xml_node const &node)
{
return *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.
*/
bool services_provided = false;
_services.for_each([&] (Service const &) { services_provided = true; });
if (services_provided && !_session_requests.constructed()) {
_session_requests.construct(_env, "session_requests");
_session_request_handler.construct(_env.ep(), *this,
&Server::_handle_session_requests);
_session_requests->sigh(*_session_request_handler);
}
/*
* Try to resolve pending session requests that may become serviceable with
* the new configuration.
*/
if (services_provided && _session_requests.constructed())
_handle_session_requests();
/*
* Re-validate routes of existing sessions, close sessions whose routes
* changed.
*/
_client_id_space.for_each<Session_state>([&] (Session_state &session) {
try {
Route const route = _resolve_session_request(session.service().name(),
session.client_label());
bool const route_unchanged = (route.service == session.service())
&& (route.label == session.label());
if (!route_unchanged)
throw Service_denied();
}
catch (Service_denied) { close_session(session); }
catch (Service_not_present) { close_session(session); }
});
}

View File

@@ -0,0 +1,131 @@
/*
* \brief Server role of init, forwarding session requests to children
* \author Norman Feske
* \date 2017-03-07
*/
/*
* Copyright (C) 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.
*/
#ifndef _LIB__SANDBOX__SERVER_H_
#define _LIB__SANDBOX__SERVER_H_
/* Genode includes */
#include <base/attached_rom_dataspace.h>
#include <os/buffered_xml.h>
/* local includes */
#include <types.h>
#include <service.h>
#include <state_reporter.h>
#include <config_model.h>
namespace Sandbox { class Server; }
class Sandbox::Server : Session_state::Ready_callback,
Session_state::Closed_callback,
public Service_model::Factory
{
private:
struct Route
{
Routed_service &service;
Session_label label;
};
Env &_env;
Allocator &_alloc;
/*
* ID space of requests originating from the parent
*/
Id_space<Parent::Server> _server_id_space { };
/*
* ID space of requests issued to the children
*/
Id_space<Parent::Client> _client_id_space { };
/**
* Exception type
*/
class Service_not_present : Exception { };
/**
* Meta data of service provided to our parent
*/
struct Service;
Registry<Service> _services { };
/**
* Services provided by our children
*/
Registry<Routed_service> &_child_services;
Report_update_trigger &_report_update_trigger;
Constructible<Attached_rom_dataspace> _session_requests { };
Constructible<Signal_handler<Server> > _session_request_handler { };
/**
* \throw Service_denied
*/
Route _resolve_session_request(Genode::Service::Name const &,
Session_label const &);
void _handle_create_session_request (Xml_node, Parent::Client::Id);
void _handle_upgrade_session_request(Xml_node, Parent::Client::Id);
void _handle_close_session_request (Xml_node, Parent::Client::Id);
void _handle_session_request(Xml_node);
void _handle_session_requests();
void _close_session(Session_state &, Parent::Session_response response);
/**
* Session_state::Closed_callback interface
*/
void session_closed(Session_state &) override;
/**
* Session_state::Ready_callback interface
*/
void session_ready(Session_state &) override;
public:
/**
* Constructor
*
* \param alloc allocator used for buffering XML config data and
* for allocating per-service meta data
*/
Server(Env &env, Allocator &alloc, Registry<Routed_service> &services,
Report_update_trigger &report_update_trigger)
:
_env(env), _alloc(alloc), _child_services(services),
_report_update_trigger(report_update_trigger)
{ }
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_ */

View File

@@ -0,0 +1,153 @@
/*
* \brief Services as targeted by session routes
* \author Norman Feske
* \date 2017-03-03
*/
/*
* Copyright (C) 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.
*/
#ifndef _LIB__SANDBOX__SERVICE_H_
#define _LIB__SANDBOX__SERVICE_H_
/* Genode includes */
#include <base/service.h>
#include <base/child.h>
namespace Sandbox {
class Abandonable;
class Parent_service;
class Routed_service;
class Forwarded_service;
}
class Sandbox::Abandonable : Interface
{
private:
bool _abandoned = false;
public:
void abandon() { _abandoned = true; }
bool abandoned() const { return _abandoned; }
};
class Sandbox::Parent_service : public Genode::Try_parent_service, public Abandonable
{
private:
Registry<Parent_service>::Element _reg_elem;
public:
Parent_service(Registry<Parent_service> &registry, Env &env,
Service::Name const &name)
:
Genode::Try_parent_service(env, name), _reg_elem(registry, *this)
{ }
};
/**
* Sandbox-specific representation of a child service
*/
class Sandbox::Routed_service : public Async_service, public Abandonable
{
public:
using Child_name = Child_policy::Name;
struct Pd_accessor : Interface
{
virtual Pd_session &pd() = 0;
virtual Pd_session_capability pd_cap() const = 0;
};
struct Ram_accessor : Interface
{
virtual Pd_session &ram() = 0;
virtual Pd_session_capability ram_cap() const = 0;
};
private:
Child_name _child_name;
Pd_accessor &_pd_accessor;
Session_state::Factory &_factory;
Registry<Routed_service>::Element _registry_element;
public:
/**
* Constructor
*
* \param services registry of all services provides by children
* \param child_name child name of server, used for session routing
*
* The other arguments correspond to the arguments of 'Async_service'.
*/
Routed_service(Registry<Routed_service> &services,
Child_name const &child_name,
Pd_accessor &pd_accessor,
Ram_accessor &,
Id_space<Parent::Server> &server_id_space,
Session_state::Factory &factory,
Service::Name const &name,
Wakeup &wakeup)
:
Async_service(name, server_id_space, factory, wakeup),
_child_name(child_name), _pd_accessor(pd_accessor),
_factory(factory), _registry_element(services, *this)
{ }
Child_name const &child_name() const { return _child_name; }
Session_state::Factory &factory() { return _factory; }
/**
* Ram_transfer::Account interface
*/
Ram_transfer_result transfer(Capability<Pd_account> to, Ram_quota amount) override
{
return to.valid() ? _pd_accessor.pd().transfer_quota(to, amount)
: Ram_transfer_result::OK;
}
/**
* Ram_transfer::Account interface
*/
Capability<Pd_account> cap(Ram_quota) const override
{
return _pd_accessor.pd_cap();
}
/**
* Cap_transfer::Account interface
*/
Cap_transfer_result transfer(Capability<Pd_account> to, Cap_quota amount) override
{
return to.valid() ? _pd_accessor.pd().transfer_quota(to, amount)
: Cap_transfer_result::OK;
}
/**
* Cap_transfer::Account interface
*/
Capability<Pd_account> cap(Cap_quota) const override
{
return _pd_accessor.pd_cap();
}
};
#endif /* _LIB__SANDBOX__SERVICE_H_ */

View File

@@ -0,0 +1,191 @@
/*
* \brief State reporting mechanism
* \author Norman Feske
* \date 2017-03-03
*/
/*
* Copyright (C) 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.
*/
#ifndef _LIB__SANDBOX__STATE_REPORTER_H_
#define _LIB__SANDBOX__STATE_REPORTER_H_
/* Genode includes */
#include <timer_session/connection.h>
#include <sandbox/sandbox.h>
/* local includes */
#include "report.h"
#include "child.h"
namespace Sandbox { class State_reporter; }
class Sandbox::State_reporter : public Report_update_trigger
{
public:
using Version = String<64>;
struct Producer : Interface
{
virtual void produce_state_report(Xml_generator &xml,
Report_detail const &) const = 0;
virtual Child::Sample_state_result sample_children_state() = 0;
};
private:
using State_handler = Genode::Sandbox::State_handler;
Env &_env;
Producer &_producer;
Reconstructible<Report_detail> _report_detail { };
uint64_t _report_delay_ms = 0;
/* interval used when child-ram reporting is enabled */
uint64_t _report_period_ms = 0;
/* version string from config, to be reflected in the report */
Version _version { };
Constructible<Timer::Connection> _timer { };
Constructible<Timer::Connection> _timer_periodic { };
Signal_handler<State_reporter> _timer_handler {
_env.ep(), *this, &State_reporter::_handle_timer };
Signal_handler<State_reporter> _timer_periodic_handler {
_env.ep(), *this, &State_reporter::_handle_periodic_timer };
Signal_handler<State_reporter> _immediate_handler {
_env.ep(), *this, &State_reporter::_handle_timer };
bool _scheduled = false;
State_handler &_state_handler;
bool _periodic_sampling_needed() const
{
return _report_detail->child_ram()
|| _report_detail->child_caps();
}
void _handle_periodic_timer()
{
if (!_periodic_sampling_needed())
return;
if (_producer.sample_children_state() == Child::Sample_state_result::CHANGED)
_handle_timer();
}
void _handle_timer()
{
_scheduled = false;
_state_handler.handle_sandbox_state();
}
public:
State_reporter(Env &env, Producer &producer, State_handler &state_handler)
:
_env(env), _producer(producer),
_state_handler(state_handler)
{ }
void generate(Xml_generator &xml) const
{
if (_version.valid())
xml.attribute("version", _version);
if (_report_detail.constructed())
_producer.produce_state_report(xml, *_report_detail);
}
void apply_config(Version const &version, Xml_node const &report)
{
if (report.type() == "report") {
_report_detail.construct(report);
_report_delay_ms = report.attribute_value("delay_ms", 100UL);
} else {
_report_detail.construct();
_report_delay_ms = 0;
}
bool trigger_update = false;
if (version != _version) {
_version = version;
trigger_update = true;
}
if (_report_delay_ms) {
if (!_timer.constructed()) {
_timer.construct(_env);
_timer->sigh(_timer_handler);
}
trigger_update = true;
}
if (trigger_update)
trigger_report_update();
/*
* If the report features information about child-RAM quotas, we
* update the report periodically. Even in the absence of any other
* report-triggering event, a child may consume/free RAM from its
* RAM session without any interplay with the sandbox. The periodic
* reports ensure that such changes are reflected by the sandbox'
* state report.
*
* By default, the interval is one second. However, when the
* 'delay_ms' attribute is defined with a higher value than that,
* the user intends to limit the rate of state reports. If so, we
* use the value of 'delay_ms' as interval.
*/
uint64_t const period_ms = max((uint64_t)1000, _report_delay_ms);
bool const period_changed = (_report_period_ms != period_ms);
bool const report_periodically = _periodic_sampling_needed();
if (report_periodically && !_timer_periodic.constructed()) {
_timer_periodic.construct(_env);
_timer_periodic->sigh(_timer_periodic_handler);
}
if (!report_periodically && _timer_periodic.constructed()) {
_report_period_ms = 0;
_timer_periodic.destruct();
}
if (period_changed && _timer_periodic.constructed()) {
_report_period_ms = period_ms;
_timer_periodic->trigger_periodic(1000*_report_period_ms);
}
}
void trigger_report_update() override
{
if (!_scheduled && _timer.constructed() && _report_delay_ms) {
_timer->trigger_once(_report_delay_ms*1000);
_scheduled = true;
}
}
void trigger_immediate_report_update() override
{
if (_report_delay_ms)
Signal_transmitter(_immediate_handler).submit();
}
};
#endif /* _LIB__SANDBOX__STATE_REPORTER_H_ */

View File

@@ -0,0 +1,100 @@
/*
* \brief Common types used within init
* \author Norman Feske
* \date 2017-03-03
*/
/*
* Copyright (C) 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.
*/
#ifndef _LIB__SANDBOX__TYPES_H_
#define _LIB__SANDBOX__TYPES_H_
#include <util/list.h>
#include <util/xml_generator.h>
#include <pd_session/pd_session.h>
namespace Sandbox {
class Child;
using namespace Genode;
using Genode::size_t;
using Genode::strlen;
struct Prio_levels { long value; };
struct Cpu_quota
{
unsigned percent;
void print(Output &out) const { Genode::print(out, percent, "%"); }
};
using Child_list = List<List_element<Child> >;
template <typename T>
struct Resource_info
{
T quota, used, avail;
static Resource_info from_pd(Pd_session const &pd);
void generate(Xml_generator &xml) const
{
using Value = String<32>;
xml.attribute("quota", Value(quota));
xml.attribute("used", Value(used));
xml.attribute("avail", Value(avail));
}
bool operator != (Resource_info const &other) const
{
return quota.value != other.quota.value
|| used.value != other.used.value
|| avail.value != other.avail.value;
}
};
using Ram_info = Resource_info<Ram_quota>;
using Cap_info = Resource_info<Cap_quota>;
template <>
inline Ram_info Ram_info::from_pd(Pd_session const &pd)
{
return { .quota = pd.ram_quota(),
.used = pd.used_ram(),
.avail = pd.avail_ram() };
}
template <>
inline Cap_info Cap_info::from_pd(Pd_session const &pd)
{
return { .quota = pd.cap_quota(),
.used = pd.used_caps(),
.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_ */

View File

@@ -0,0 +1,233 @@
/*
* \brief Utilities
* \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.
*/
#ifndef _LIB__SANDBOX__UTILS_H_
#define _LIB__SANDBOX__UTILS_H_
namespace Sandbox {
static inline void warn_insuff_quota(size_t const avail)
{
warning("specified quota exceeds available quota, "
"proceeding with a quota of ", avail);
}
/**
* Return sub string of label with the leading child name stripped out
*
* \return character pointer to the scoped part of the label,
* or nullptr if the label is not correctly prefixed with the child's
* name
*/
inline char const *skip_label_prefix(char const *child_name, char const *label)
{
size_t const child_name_len = strlen(child_name);
if (strcmp(child_name, label, child_name_len) != 0)
return nullptr;
label += child_name_len;
/*
* Skip label separator. This condition should be always satisfied.
*/
if (strcmp(" -> ", label, 4) != 0)
return nullptr;
return label + 4;
}
/**
* Return true if service XML node matches service request
*
* \param args session arguments, inspected for the session label
* \param child_name name of the originator of the session request
* \param service_name name of the requested service
*/
inline bool service_node_matches(Xml_node const service_node,
Session_label const &label,
Child_policy::Name const &child_name,
Service::Name const &service_name)
{
bool const service_matches =
service_node.has_type("any-service") ||
(service_node.has_type("service") &&
service_node.attribute_value("name", Service::Name()) == service_name);
if (!service_matches)
return false;
using Label = String<Session_label::capacity()>;
char const *unscoped_attr = "unscoped_label";
char const *label_last_attr = "label_last";
bool const route_depends_on_child_provided_label =
service_node.has_attribute("label") ||
service_node.has_attribute("label_prefix") ||
service_node.has_attribute("label_suffix") ||
service_node.has_attribute(label_last_attr);
if (service_node.has_attribute(unscoped_attr)) {
/*
* If an 'unscoped_label' attribute is provided, don't consider any
* scoped label attribute.
*/
if (route_depends_on_child_provided_label)
warning("service node contains both scoped and unscoped label attributes");
return label == service_node.attribute_value(unscoped_attr, Label());
}
if (service_node.has_attribute(label_last_attr))
return service_node.attribute_value(label_last_attr, Label()) == label.last_element();
if (!route_depends_on_child_provided_label)
return true;
char const * const scoped_label = skip_label_prefix(
child_name.string(), label.string());
if (!scoped_label)
return false;
Session_label const session_label(scoped_label);
return !Xml_node_label_score(service_node, session_label).conflict();
}
/**
* Check if service name is ambiguous
*
* \return true if the same service is provided multiple
* times
*
* \deprecated
*/
template <typename T>
inline bool is_ambiguous(Registry<T> const &services, Service::Name const &name)
{
/* count number of services with the specified name */
unsigned cnt = 0;
services.for_each([&] (T const &service) {
if (!service.abandoned())
cnt += (service.name() == name); });
return cnt > 1;
}
/**
* Find service with certain values in given registry
*
* \param services service registry
* \param name name of wanted service
* \param filter_fn function that applies additional filters
*
* \throw Service_denied
*/
template <typename T, typename FILTER_FN>
inline T &find_service(Registry<T> &services,
Service::Name const &name,
FILTER_FN const &filter_fn)
{
T *service = nullptr;
services.for_each([&] (T &s) {
if (service || s.name() != name || filter_fn(s))
return;
service = &s;
});
if (!service)
throw Service_denied();
if (service->abandoned())
throw Service_denied();
return *service;
}
/**
* Read priority-levels declaration from config
*/
inline Prio_levels prio_levels_from_xml(Xml_node const &config)
{
long const prio_levels = config.attribute_value("prio_levels", 0L);
if (prio_levels && ((prio_levels >= (long)sizeof(prio_levels)*8) ||
(prio_levels != (1L << log2(prio_levels))))) {
warning("prio levels is not power of two, priorities are disabled");
return Prio_levels { 0 };
}
return Prio_levels { prio_levels };
}
inline long priority_from_xml(Xml_node start_node, Prio_levels prio_levels)
{
long const default_priority = Cpu_session::DEFAULT_PRIORITY;
long priority = start_node.attribute_value("priority", default_priority);
/*
* All priority declarations in the config file are
* negative because child priorities can never be higher
* than parent priorities. To simplify priority
* calculations, we use inverted values. Lower values
* correspond to higher priorities.
*/
priority = -priority;
if (priority && (priority >= prio_levels.value)) {
long new_prio = prio_levels.value ? prio_levels.value - 1 : 0;
Service::Name const name = start_node.attribute_value("name", Service::Name());
warning(name, ": invalid priority, upgrading "
"from ", -priority, " to ", -new_prio);
return new_prio;
}
return priority;
}
inline Affinity::Location
affinity_location_from_xml(Affinity::Space const &space, Xml_node start_node)
{
using Location = Affinity::Location;
Location result = Location(0, 0, space.width(), space.height());
start_node.with_optional_sub_node("affinity", [&] (Xml_node node) {
Location const location = Location::from_xml(space, node);
if (!location.within(space)) {
Service::Name const name = start_node.attribute_value("name", Service::Name());
warning(name, ": affinity location exceeds affinity-space boundary");
return;
}
result = location;
});
return result;
}
}
#endif /* _LIB__SANDBOX__UTILS_H_ */

View File

@@ -0,0 +1,43 @@
/*
* \brief Sandbox verbosity
* \author Norman Feske
* \date 2017-01-03
*/
/*
* Copyright (C) 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.
*/
#ifndef _LIB__SANDBOX__VERBOSE_H_
#define _LIB__SANDBOX__VERBOSE_H_
/* Genode includes */
#include <util/noncopyable.h>
#include <util/xml_node.h>
/* local includes */
#include <types.h>
namespace Sandbox { struct Verbose; }
class Sandbox::Verbose : Genode::Noncopyable
{
private:
bool _enabled = false;
public:
Verbose() { }
Verbose(Xml_node const &config)
: _enabled(config.attribute_value("verbose", false)) { }
bool enabled() const { return _enabled; }
};
#endif /* _LIB__SANDBOX__VERBOSE_H_ */