init: service forwarding

This patch equips init with the ability to act as a server that forwards
session requests to its children. Session requests can be routed
depending of the requested service type and the session label
originating from init's parent.

The feature is configured by one or multiple <service> nodes hosted in
init's <config> node. The routing policy is selected by via the regular
server-side policy-selection mechanism, for example:

<config>
  ...
  <service name="LOG">
    <policy label="noux">
      <child name="terminal_log" label="important"/>
    </policy>
    <default-policy> <child name="nitlog"/> </default-policy>
  </service>
  ...
</config>

Each policy node must have a <child> sub node, which denotes name of the
server with the 'name' attribute. The optional 'label' attribute defines
the session label presented to the server, analogous to how the
rewriting of session labels works in session routes. If not specified,
the client-provided label is presented to the server as is.

Fixes #2247
This commit is contained in:
Norman Feske 2017-03-08 10:21:49 +01:00 committed by Christian Helmuth
parent 1489791d5e
commit 1fde4d638c
10 changed files with 648 additions and 11 deletions

View File

@ -743,6 +743,118 @@ append config {
</expect_init_state>
<message string="forward session request to children"/>
<init_config>
<report child_ram="yes"/>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="service_init">
<binary name="init"/>
<resource name="RAM" quantum="10M"/>
<provides> <service name="LOG"/> </provides>
<config>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<service name="LOG">
<default-policy> <child name="server"/> </default-policy>
</service>
<start name="server">
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<provides> <service name="LOG"/> </provides>
<config> <log_service verbose="yes"/> </config>
<route> <any-service> <parent/> </any-service> </route>
</start>
</config>
<route> <any-service> <parent/> </any-service> </route>
</start>
<start name="test">
<binary name="dummy"/>
<resource name="RAM" quantum="4M"/>
<config version="initial">
<create_log_connections count="1" ram_upgrade="3M"/>
<destroy_log_connections/>
</config>
<route>
<service name="LOG"> <child name="service_init"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
</init_config>
<expect_log string="[init -> service_init -> server] created LOG service"/>
<expect_log string="[init -> service_init -> server] opening session with label test"/>
<expect_log string="[init -> service_init -> server] [test] going to create 1 LOG connections"/>
<expect_log string="[init -> service_init -> server] opening session with label test -> 0"/>
<expect_log string="[init -> service_init -> server] [test] upgrade connection 0"/>
<expect_log string="[init -> service_init -> server] received session quota upgrade"/>
<expect_log string="[init -> service_init -> server] [test] created all LOG connections"/>
<expect_log string="[init -> service_init -> server] closing session with label test -> 0"/>
<expect_log string="[init -> service_init -> server] [test] destroyed all LOG connections"/>
<sleep ms="150"/>
<!-- after closing the LOG session, the session quota is returned to
the 'test' client -->
<expect_init_state>
<node name="child"> <attribute name="name" value="test"/>
<node name="ram"> <attribute name="avail" higher="3M"/> </node>
</node>
</expect_init_state>
<!-- change init service policy such that the route of the
remaining LOG session of 'test' is no longer valid. We
expect the 'service_init' to issue a close request to the
embedded server -->
<init_config>
<report child_ram="yes"/>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="service_init">
<binary name="init"/>
<resource name="RAM" quantum="10M"/>
<provides> <service name="LOG"/> </provides>
<config>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="server">
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<provides> <service name="LOG"/> </provides>
<config> <log_service verbose="yes"/> </config>
<route> <any-service> <parent/> </any-service> </route>
</start>
</config>
<route> <any-service> <parent/> </any-service> </route>
</start>
<start name="test">
<binary name="dummy"/>
<resource name="RAM" quantum="4M"/>
<config version="initial">
<create_log_connections count="1" ram_upgrade="3M"/>
<destroy_log_connections/>
</config>
<route>
<service name="LOG"> <child name="service_init"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
</init_config>
<expect_log string="[init -> service_init -> server] closing session with label test"/>
<sleep ms="100"/>
<message string="test complete"/>
</config>
@ -786,5 +898,5 @@ build_boot_image $boot_modules
append qemu_args " -nographic "
run_genode_until {.*child "test-init" exited with exit value 0.*} 60
run_genode_until {.*child "test-init" exited with exit value 0.*} 120

View File

@ -37,11 +37,27 @@ struct Dummy::Log_service
Heap _heap { _env.ram(), _env.rm() };
bool const _verbose;
struct Session_component : Rpc_object<Log_session>
{
Session_label const _label;
Session_component(Session_label const &label) : _label(label) { }
bool const _verbose;
Session_component(Session_label const &label, bool verbose)
:
_label(label), _verbose(verbose)
{
if (_verbose)
log("opening session with label ", _label);
}
~Session_component()
{
if (_verbose)
log("closing session with label ", _label);
}
size_t write(String const &string) override
{
@ -60,17 +76,33 @@ struct Dummy::Log_service
struct Root : Root_component<Session_component>
{
Root(Entrypoint &ep, Allocator &alloc) : Root_component(ep, alloc) { }
Ram_session &_ram;
bool const _verbose;
Root(Entrypoint &ep, Allocator &alloc, Ram_session &ram, bool verbose)
:
Root_component(ep, alloc), _ram(ram), _verbose(verbose)
{ }
Session_component *_create_session(const char *args, Affinity const &) override
{
return new (md_alloc()) Session_component(label_from_args(args));
return new (md_alloc()) Session_component(label_from_args(args), _verbose);
}
void _upgrade_session(Session_component *, const char *args) override
{
size_t const ram_quota =
Arg_string::find_arg(args, "ram_quota").ulong_value(0);
if (_ram.avail() >= ram_quota)
log("received session quota upgrade");
}
};
Root _root { _env.ep(), _heap };
Root _root { _env.ep(), _heap, _env.ram(), _verbose };
Log_service(Env &env) : _env(env)
Log_service(Env &env, bool verbose) : _env(env), _verbose(verbose)
{
_env.parent().announce(_env.ep().manage(_root));
log("created LOG service");
@ -94,10 +126,21 @@ struct Dummy::Log_connections
{
unsigned const count = node.attribute_value("count", 0UL);
Number_of_bytes const ram_upgrade =
node.attribute_value("ram_upgrade", Number_of_bytes());
log("going to create ", count, " LOG connections");
for (unsigned i = 0; i < count; i++)
new (_heap) Connection(_connections, _env, Session_label { i });
for (unsigned i = 0; i < count; i++) {
Connection *connection =
new (_heap) Connection(_connections, _env, Session_label { i });
if (ram_upgrade > 0) {
log("upgrade connection ", i);
connection->upgrade_ram(ram_upgrade);
}
}
log("created all LOG connections");
}
@ -212,7 +255,7 @@ struct Dummy::Main
_log_connections.destruct();
if (node.type() == "log_service")
_log_service.construct(_env);
_log_service.construct(_env, node.attribute_value("verbose", false));
if (node.type() == "consume_ram")
_ram_consumer.consume(node.attribute_value("amount", Number_of_bytes()));

View File

@ -14,6 +14,9 @@
#ifndef _SRC__INIT__BUFFERED_XML_H_
#define _SRC__INIT__BUFFERED_XML_H_
/* Genode includes */
#include <util/xml_node.h>
namespace Init { class Buffered_xml; }

View File

@ -20,6 +20,7 @@
#include <child.h>
#include <alias.h>
#include <state_reporter.h>
#include <server.h>
namespace Init { struct Main; }
@ -109,6 +110,8 @@ struct Init::Main : State_reporter::Producer, Child::Default_route_accessor,
Signal_handler<Main> _config_handler {
_env.ep(), *this, &Main::_handle_config };
Server _server { _env, _heap, _child_services, _state_reporter };
Main(Env &env) : _env(env)
{
_config.sigh(_config_handler);
@ -358,6 +361,8 @@ void Init::Main::_handle_config()
*/
_children.for_each_child([&] (Child &child) { child.apply_ram_downgrade(); });
_children.for_each_child([&] (Child &child) { child.apply_ram_upgrade(); });
_server.apply_config(_config.xml());
}

341
repos/os/src/init/server.cc Normal file
View File

@ -0,0 +1,341 @@
/*
* \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 <os/session_policy.h>
/* local includes */
#include <server.h>
/***************************
** Init::Server::Service **
***************************/
struct Init::Server::Service
{
Registry<Service>::Element _registry_element;
Buffered_xml _service_node;
typedef Genode::Service::Name Name;
Registry<Routed_service> &_child_services;
Name const _name { _service_node.xml().attribute_value("name", Name()) };
/**
* 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)
:
_registry_element(services, *this),
_service_node(alloc, service_node),
_child_services(child_services)
{ }
/**
* Determine route to child service for a given label according
* to the <service> node policy
*
* \throw Parent::Service_denied
*/
Route resolve_session_request(Session_label const &);
Name name() const { return _name; }
};
Init::Server::Route
Init::Server::Service::resolve_session_request(Session_label const &label)
{
try {
Session_policy policy(label, _service_node.xml());
if (!policy.has_sub_node("child"))
throw Parent::Service_denied();
Xml_node target_node = policy.sub_node("child");
Child_policy::Name const child_name =
target_node.attribute_value("name", Child_policy::Name());
typedef String<Session_label::capacity()> Label;
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)
match = &service; });
if (!match || match->abandoned())
throw Parent::Service_denied();
return Route { *match, target_label };
}
catch (Session_policy::No_policy_defined) {
throw Parent::Service_denied(); }
}
/******************
** Init::Server **
******************/
Init::Server::Route
Init::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 Parent::Service_denied();
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 Init::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;
}
}
void Init::Server::session_closed(Session_state &session)
{
_report_update_trigger.trigger_report_update();
Parent::Server::Id id { session.id_at_client().value };
_env.parent().session_response(id, Parent::SESSION_CLOSED);
Ram_session_client(session.service().ram())
.transfer_quota(_env.ram_session_cap(), session.donated_ram_quota());
session.destroy();
}
void Init::Server::_handle_create_session_request(Xml_node request,
Parent::Client::Id id)
{
if (!request.has_sub_node("args"))
return;
typedef Session_state::Args 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];
strncpy(argbuf, args.string(), sizeof(argbuf));
size_t const ram_quota = Arg_string::find_arg(argbuf, "ram_quota").ulong_value(0);
size_t const keep_quota = route.service.factory().session_costs();
if (ram_quota < keep_quota)
throw Genode::Service::Quota_exceeded();
size_t const forward_ram_quota = ram_quota - keep_quota;
Arg_string::set_arg(argbuf, sizeof(argbuf), "ram_quota", forward_ram_quota);
Session_state &session =
route.service.create_session(route.service.factory(),
_client_id_space, id,
route.label, argbuf, Affinity());
/* transfer session quota */
if (_env.ram().transfer_quota(route.service.ram(), ram_quota)) {
/*
* 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) "
"of forwarded ", name, " session");
session.destroy();
throw Parent::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::INVALID_ARGS)
throw Parent::Service_denied();
if (session.phase == Session_state::QUOTA_EXCEEDED)
throw Genode::Service::Quota_exceeded();
}
catch (Parent::Service_denied) {
_env.parent().session_response(Parent::Server::Id { id.value }, Parent::INVALID_ARGS); }
catch (Genode::Service::Quota_exceeded) {
_env.parent().session_response(Parent::Server::Id { id.value }, Parent::QUOTA_EXCEEDED); }
}
void Init::Server::_handle_upgrade_session_request(Xml_node request,
Parent::Client::Id id)
{
_client_id_space.apply<Session_state>(id, [&] (Session_state &session) {
size_t const ram_quota = request.attribute_value("ram_quota", 0UL);
session.phase = Session_state::UPGRADE_REQUESTED;
if (_env.ram().transfer_quota(session.service().ram(), ram_quota)) {
warning("unable to upgrade session quota (", ram_quota, " bytes) "
"of forwarded ", session.service().name(), " session");
return;
}
session.increase_donated_quota(ram_quota);
session.service().initiate_request(session);
session.service().wakeup();
});
}
void Init::Server::_handle_close_session_request(Xml_node request,
Parent::Client::Id id)
{
_client_id_space.apply<Session_state>(id, [&] (Session_state &session) {
close_session(session); });
}
void Init::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 Init::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();
}
void Init::Server::apply_config(Xml_node config)
{
_services.for_each([&] (Service &service) { destroy(_alloc, &service); });
config.for_each_sub_node("service", [&] (Xml_node node) {
new (_alloc) Service(_services, _alloc, node, _child_services); });
/*
* 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);
if (_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 Parent::Service_denied();
}
catch (Parent::Service_denied) {
close_session(session); }
});
}

112
repos/os/src/init/server.h Normal file
View File

@ -0,0 +1,112 @@
/*
* \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 _SRC__INIT__SERVER_H_
#define _SRC__INIT__SERVER_H_
/* Genode includes */
#include <base/attached_rom_dataspace.h>
/* local includes */
#include <types.h>
#include <service.h>
#include <buffered_xml.h>
#include <state_reporter.h>
namespace Init { class Server; }
class Init::Server : Session_state::Ready_callback,
Session_state::Closed_callback
{
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 of init
*/
Id_space<Parent::Client> _client_id_space;
/**
* 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 Parent::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();
/**
* 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_config(Xml_node);
};
#endif /* _SRC__INIT__SERVER_H_ */

View File

@ -14,10 +14,15 @@
#ifndef _SRC__INIT__SERVICE_H_
#define _SRC__INIT__SERVICE_H_
/* Genode includes */
#include <base/service.h>
#include <base/child.h>
namespace Init {
class Abandonable;
class Parent_service;
class Routed_service;
class Forwarded_service;
}
@ -68,6 +73,8 @@ class Init::Routed_service : public Child_service, public Abandonable
Ram_accessor &_ram_accessor;
Session_state::Factory &_factory;
Registry<Routed_service>::Element _registry_element;
public:
@ -91,12 +98,21 @@ class Init::Routed_service : public Child_service, public Abandonable
Child_service(server_id_space, factory, name,
Ram_session_capability(), wakeup),
_child_name(child_name), _ram_accessor(ram_accessor),
_registry_element(services, *this)
_factory(factory), _registry_element(services, *this)
{ }
Child_name const &child_name() const { return _child_name; }
Ram_session_capability ram() const { return _ram_accessor.ram(); }
/**
* Return factory for creating/destroying session-state objects
*
* This accessor is solely meant to be used by 'Forwarded_service' to
* allocate session-state objects for sessions requested by init's
* parent.
*/
Session_state::Factory &factory() { return _factory; }
};
#endif /* _SRC__INIT__SERVICE_H_ */

View File

@ -14,9 +14,13 @@
#ifndef _SRC__INIT__STATE_REPORTER_H_
#define _SRC__INIT__STATE_REPORTER_H_
/* Genode includes */
#include <os/reporter.h>
#include <timer_session/connection.h>
/* local includes */
#include <report.h>
namespace Init { class State_reporter; }
class Init::State_reporter : public Report_update_trigger

View File

@ -1,4 +1,4 @@
TARGET = init
SRC_CC = main.cc child.cc
SRC_CC = main.cc child.cc server.cc
LIBS = base
INC_DIR += $(PRG_DIR)

View File

@ -15,6 +15,7 @@
#define _SRC__INIT__TYPES_H_
#include <util/string.h>
#include <util/list.h>
namespace Init {