genode/repos/base/include/base/child.h

819 lines
24 KiB
C++

/*
* \brief Child creation framework
* \author Norman Feske
* \date 2006-07-22
*/
/*
* Copyright (C) 2006-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 _INCLUDE__BASE__CHILD_H_
#define _INCLUDE__BASE__CHILD_H_
#include <base/rpc_server.h>
#include <base/heap.h>
#include <base/service.h>
#include <base/lock.h>
#include <base/local_connection.h>
#include <base/quota_guard.h>
#include <util/arg_string.h>
#include <region_map/client.h>
#include <pd_session/connection.h>
#include <cpu_session/connection.h>
#include <log_session/connection.h>
#include <rom_session/connection.h>
#include <parent/capability.h>
namespace Genode {
struct Child_policy;
struct Child;
}
/**
* Child policy interface
*
* A child-policy object is an argument to a 'Child'. It is responsible for
* taking policy decisions regarding the parent interface. Most importantly,
* it defines how session requests are resolved and how session arguments
* are passed to servers when creating sessions.
*/
struct Genode::Child_policy
{
typedef String<64> Name;
typedef String<64> Binary_name;
typedef String<64> Linker_name;
virtual ~Child_policy() { }
/**
* Name of the child used as the child's label prefix
*/
virtual Name name() const = 0;
/**
* ROM module name of the binary to start
*/
virtual Binary_name binary_name() const { return name(); }
/**
* ROM module name of the dynamic linker
*/
virtual Linker_name linker_name() const { return "ld.lib.so"; }
/**
* Routing destination of a session request
*/
struct Route
{
Service &service;
Session::Label const label;
Session::Diag const diag;
};
/**
* Determine service and server-side label for a given session request
*
* \return routing and policy-selection information for the session
*
* \throw Service_denied
*/
virtual Route resolve_session_request(Service::Name const &,
Session_label const &) = 0;
/**
* Apply transformations to session arguments
*/
virtual void filter_session_args(Service::Name const &,
char * /*args*/, size_t /*args_len*/) { }
/**
* Register a service provided by the child
*/
virtual void announce_service(Service::Name const &) { }
/**
* Apply session affinity policy
*
* \param affinity affinity passed along with a session request
* \return affinity subordinated to the child policy
*/
virtual Affinity filter_session_affinity(Affinity const &affinity)
{
return affinity;
}
/**
* Exit child
*/
virtual void exit(int exit_value)
{
log("child \"", name(), "\" exited with exit value ", exit_value);
}
/**
* Reference PD session
*
* The PD session returned by this method is used for session cap-quota
* and RAM-quota transfers.
*/
virtual Pd_session &ref_pd() = 0;
virtual Pd_session_capability ref_pd_cap() const = 0;
/**
* Respond to the release of resources by the child
*
* This method is called when the child confirms the release of
* resources in response to a yield request.
*/
virtual void yield_response() { }
/**
* Take action on additional resource needs by the child
*/
virtual void resource_request(Parent::Resource_args const &) { }
/**
* Initialize the child's CPU session
*
* The function may install an exception signal handler or assign CPU quota
* to the child.
*/
virtual void init(Cpu_session &, Capability<Cpu_session>) { }
/**
* Initialize the child's PD session
*
* The function must define the child's reference account and transfer
* the child's initial RAM and capability quotas. It may also install a
* region-map fault handler for the child's address space
* ('Pd_session::address_space');.
*/
virtual void init(Pd_session &, Capability<Pd_session>) { }
class Nonexistent_id_space : Exception { };
/**
* ID space for sessions provided by the child
*
* \throw Nonexistent_id_space
*/
virtual Id_space<Parent::Server> &server_id_space() { throw Nonexistent_id_space(); }
/**
* Notification hook invoked each time a session state is modified
*/
virtual void session_state_changed() { }
/**
* Granularity of allocating the backing store for session meta data
*
* Session meta data is allocated from 'ref_pd'. The first batch of
* session-state objects is allocated at child-construction time.
*/
virtual size_t session_alloc_batch_size() const { return 16; }
/**
* Return true to create the environment sessions at child construction
*
* By returning 'false', it is possible to create 'Child' objects without
* routing of their environment sessions at construction time. Once the
* routing information is available, the child's environment sessions
* must be manually initiated by calling 'Child::initiate_env_sessions()'.
*/
virtual bool initiate_env_sessions() const { return true; }
/**
* Return region map for the child's address space
*
* \param pd the child's PD session capability
*
* By default, the function returns a 'nullptr'. In this case, the 'Child'
* interacts with the address space of the child's PD session via RPC calls
* to the 'Pd_session::address_space'.
*
* By overriding the default, those RPC calls can be omitted, which is
* useful if the child's PD session (including the PD's address space) is
* virtualized by the parent. If the virtual PD session is served by the
* same entrypoint as the child's parent interface, an RPC call to 'pd'
* would otherwise produce a deadlock.
*/
virtual Region_map *address_space(Pd_session &) { return nullptr; }
/**
* Return true if ELF loading should be inhibited
*/
virtual bool forked() const { return false; }
};
/**
* Implementation of the parent interface that supports resource trading
*
* There are three possible cases of how a session can be provided to
* a child: The service is implemented locally, the session was obtained by
* asking our parent, or the session is provided by one of our children.
*
* These types must be differentiated for the quota management when a child
* issues the closing of a session or transfers quota via our parent
* interface.
*
* If we close a session to a local service, we transfer the session quota
* from our own account to the client.
*
* If we close a parent session, we receive the session quota on our own
* account and must transfer this amount to the session-closing child.
*
* If we close a session provided by a server child, we close the session
* at the server, transfer the session quota from the server's RAM session
* to our account, and subsequently transfer the same amount from our
* account to the client.
*/
class Genode::Child : protected Rpc_object<Parent>,
Session_state::Ready_callback,
Session_state::Closed_callback
{
private:
struct Initial_thread_base : Interface
{
/**
* Start execution at specified instruction pointer
*/
virtual void start(addr_t ip) = 0;
/**
* Return capability of the initial thread
*/
virtual Capability<Cpu_thread> cap() const = 0;
};
struct Initial_thread : Initial_thread_base
{
private:
Cpu_session &_cpu;
Thread_capability _cap;
public:
typedef Cpu_session::Name Name;
/**
* Constructor
*
* \throw Cpu_session::Thread_creation_failed
* \throw Out_of_ram
* \throw Out_of_caps
*/
Initial_thread(Cpu_session &, Pd_session_capability, Name const &);
~Initial_thread();
void start(addr_t) override;
Capability<Cpu_thread> cap() const override { return _cap; }
};
/* child policy */
Child_policy &_policy;
/* print error message with the child's name prepended */
template <typename... ARGS>
void _error(ARGS &&... args) { error(_policy.name(), ": ", args...); }
Region_map &_local_rm;
Capability_guard _parent_cap_guard;
/* signal handlers registered by the child */
Signal_context_capability _resource_avail_sigh { };
Signal_context_capability _yield_sigh { };
Signal_context_capability _session_sigh { };
Signal_context_capability _heartbeat_sigh { };
/* arguments fetched by the child in response to a yield signal */
Lock _yield_request_lock { };
Resource_args _yield_request_args { };
/* number of unanswered heartbeat signals */
unsigned _outstanding_heartbeats = 0;
/* sessions opened by the child */
Id_space<Client> _id_space { };
/* allocator used for dynamically created session state objects */
Sliced_heap _session_md_alloc { _policy.ref_pd(), _local_rm };
Session_state::Factory::Batch_size const
_session_batch_size { _policy.session_alloc_batch_size() };
/* factory for dynamically created session-state objects */
Session_state::Factory _session_factory { _session_md_alloc,
_session_batch_size };
typedef Session_state::Args Args;
/*
* Members that are initialized not before the child's environment is
* complete.
*/
void _try_construct_env_dependent_members();
Constructible<Initial_thread> _initial_thread { };
struct Process
{
class Missing_dynamic_linker : Exception { };
class Invalid_executable : Exception { };
enum Type { TYPE_LOADED, TYPE_FORKED };
struct Loaded_executable
{
/**
* Initial instruction pointer of the new process, as defined
* in the header of the executable.
*/
addr_t entry { 0 };
/**
* Constructor parses the executable and sets up segment
* dataspaces
*
* \param local_rm local address space, needed to make the
* segment dataspaces temporarily visible in
* the local address space to initialize their
* content with the data from the 'elf_ds'
*
* \throw Region_map::Region_conflict
* \throw Region_map::Invalid_dataspace
* \throw Invalid_executable
* \throw Missing_dynamic_linker
* \throw Out_of_ram
* \throw Out_of_caps
*/
Loaded_executable(Type type,
Dataspace_capability ldso_ds,
Ram_allocator &ram,
Region_map &local_rm,
Region_map &remote_rm,
Parent_capability parent_cap);
} loaded_executable;
/**
* Constructor
*
* \throw Missing_dynamic_linker
* \throw Invalid_executable
* \throw Region_map::Region_conflict
* \throw Region_map::Invalid_dataspace
* \throw Out_of_ram
* \throw Out_of_caps
*
* On construction of a protection domain, the initial thread is
* started immediately.
*
* The 'type' 'TYPE_FORKED' creates an empty process. In this case,
* all process initialization steps except for the creation of the
* initial thread must be done manually, i.e., as done for
* implementing fork.
*/
Process(Type type,
Dataspace_capability ldso_ds,
Pd_session &pd,
Initial_thread_base &initial_thread,
Region_map &local_rm,
Region_map &remote_rm,
Parent_capability parent);
~Process();
};
Constructible<Process> _process { };
/*
* The child's environment sessions
*/
template <typename CONNECTION>
struct Env_connection
{
Child &_child;
Id_space<Parent::Client>::Id const _client_id;
typedef String<64> Label;
Args const _args;
/*
* The 'Env_service' monitors session responses in order to attempt
* to 'Child::_try_construct_env_dependent_members()' on the
* arrival of environment sessions.
*/
struct Env_service : Service, Session_state::Ready_callback
{
Child &_child;
Service &_service;
Env_service(Child &child, Service &service)
:
Genode::Service(CONNECTION::service_name()),
_child(child), _service(service)
{ }
/*
* The 'Local_connection' may call 'initial_request' multiple
* times. We use 'initial_request' as a hook to transfer the
* session quota to an async service but we want to transfer
* the session quota only once. The '_first_request' allows
* us to distinguish the first from subsequent calls.
*/
bool _first_request = true;
void initiate_request(Session_state &session) override
{
session.ready_callback = this;
session.async_client_notify = true;
_service.initiate_request(session);
/*
* If the env session is provided by an async service,
* transfer the session resources.
*/
bool const async_service =
session.phase == Session_state::CREATE_REQUESTED;
if (_first_request && async_service
&& _service.name() != Pd_session::service_name()) {
Ram_quota const ram_quota { CONNECTION::RAM_QUOTA };
Cap_quota const cap_quota { CONNECTION::CAP_QUOTA };
if (cap(ram_quota).valid())
_child._policy.ref_pd().transfer_quota(cap(ram_quota), ram_quota);
if (cap(cap_quota).valid())
_child._policy.ref_pd().transfer_quota(cap(cap_quota), cap_quota);
_first_request = false;
}
if (session.phase == Session_state::SERVICE_DENIED)
error(_child._policy.name(), ": environment ",
CONNECTION::service_name(), " session denied "
"(", session.args(), ")");
/*
* If the env session is provided by an async service,
* we have to wake up the server when closing the env
* session.
*/
if (session.phase == Session_state::CLOSE_REQUESTED)
_service.wakeup();
}
/**
* Session_state::Ready_callback
*/
void session_ready(Session_state &) override
{
_child._try_construct_env_dependent_members();
}
/**
* Service (Ram_transfer::Account) interface
*/
void transfer(Pd_session_capability to, Ram_quota amount) override
{
Ram_transfer::Account &from = _service;
from.transfer(to, amount);
}
/**
* Service (Ram_transfer::Account) interface
*/
Pd_session_capability cap(Ram_quota) const override
{
Ram_transfer::Account &to = _service;
return to.cap(Ram_quota());
}
/**
* Service (Cap_transfer::Account) interface
*/
void transfer(Pd_session_capability to, Cap_quota amount) override
{
Cap_transfer::Account &from = _service;
from.transfer(to, amount);
}
/**
* Service (Cap_transfer::Account) interface
*/
Pd_session_capability cap(Cap_quota) const override
{
Cap_transfer::Account &to = _service;
return to.cap(Cap_quota());
}
void wakeup() override { _service.wakeup(); }
bool operator == (Service const &other) const override
{
return _service == other;
}
};
Constructible<Env_service> _env_service { };
Constructible<Local_connection<CONNECTION> > _connection { };
/**
* Construct session arguments with the child policy applied
*/
Args _construct_args(Child_policy &policy, Label const &label)
{
/* copy original arguments into modifiable buffer */
char buf[Session_state::Args::capacity()];
buf[0] = 0;
/* supply label as session argument */
if (label.valid())
Arg_string::set_arg_string(buf, sizeof(buf), "label", label.string());
/* apply policy to argument buffer */
policy.filter_session_args(CONNECTION::service_name(), buf, sizeof(buf));
return Session_state::Args(Cstring(buf));
}
static char const *_service_name() { return CONNECTION::service_name(); }
Env_connection(Child &child, Id_space<Parent::Client>::Id id,
Label const &label = Label())
:
_child(child), _client_id(id),
_args(_construct_args(child._policy, label))
{ }
/**
* Initiate routing and creation of the environment session
*/
void initiate()
{
/* don't attempt to initiate env session twice */
if (_connection.constructed())
return;
try {
Child_policy::Route const route =
_child._policy.resolve_session_request(_service_name(),
label_from_args(_args.string()));
_env_service.construct(_child, route.service);
_connection.construct(*_env_service, _child._id_space, _client_id,
_args, _child._policy.filter_session_affinity(Affinity()),
route.label, route.diag);
}
catch (Service_denied) {
error(_child._policy.name(), ": ", _service_name(), " "
"environment session denied (", _args.string(), ")"); }
}
typedef typename CONNECTION::Session_type SESSION;
SESSION &session() { return _connection->session(); }
SESSION const &session() const { return _connection->session(); }
Capability<SESSION> cap() const {
return _connection.constructed() ? _connection->cap()
: Capability<SESSION>(); }
bool closed() const { return !_connection.constructed() || _connection->closed(); }
void close() { if (_connection.constructed()) _connection->close(); }
};
Env_connection<Pd_connection> _pd { *this, Env::pd(), _policy.name() };
Env_connection<Cpu_connection> _cpu { *this, Env::cpu(), _policy.name() };
Env_connection<Log_connection> _log { *this, Env::log(), _policy.name() };
Env_connection<Rom_connection> _binary { *this, Env::binary(), _policy.binary_name() };
Constructible<Env_connection<Rom_connection> > _linker { };
Dataspace_capability _linker_dataspace()
{
return _linker.constructed() ? _linker->session().dataspace()
: Rom_dataspace_capability();
}
void _revert_quota_and_destroy(Session_state &);
void _discard_env_session(Id_space<Parent::Client>::Id);
Close_result _close(Session_state &);
/**
* Session_state::Ready_callback
*/
void session_ready(Session_state &session) override;
/**
* Session_state::Closed_callback
*/
void session_closed(Session_state &) override;
template <typename UNIT>
static UNIT _effective_quota(UNIT requested_quota, UNIT env_quota)
{
if (requested_quota.value < env_quota.value)
return UNIT { 0 };
return UNIT { requested_quota.value - env_quota.value };
}
public:
/**
* Constructor
*
* \param rm local address space, usually 'env.rm()'
* \param entrypoint entrypoint used to serve the parent interface of
* the child
* \param policy policy for the child
*
* \throw Service_denied the initial sessions for the child's
* environment could not be established
*/
Child(Region_map &rm, Rpc_entrypoint &entrypoint, Child_policy &policy);
/**
* Destructor
*
* On destruction of a child, we close all sessions of the child to
* other services.
*/
virtual ~Child();
/**
* Return true if the child has been started
*
* After the child's construction, the child is not always able to run
* immediately. In particular, a session of the child's environment
* may still be pending. This method returns true only if the child's
* environment is completely initialized at the time of calling.
*
* If all environment sessions are immediately available (as is the
* case for local services or parent services), the return value is
* expected to be true. If this is not the case, one of child's
* environment sessions could not be established, e.g., the ROM session
* of the binary could not be obtained.
*/
bool active() const { return _process.constructed(); }
/**
* Initialize the child's PD session
*/
void initiate_env_pd_session();
/**
* Trigger the routing and creation of the child's environment session
*
* See the description of 'Child_policy::initiate_env_sessions'.
*/
void initiate_env_sessions();
/**
* Return true if the child is safe to be destroyed
*
* The child must not be destroyed until all environment sessions
* are closed at the respective servers. Otherwise, the session state,
* which is kept as part of the child object may be gone before
* the close request reaches the server.
*/
bool env_sessions_closed() const
{
if (_linker.constructed() && !_linker->closed()) return false;
/*
* Note that the state of the CPU session remains unchecked here
* because the eager CPU-session destruction does not work on all
* kernels (search for KERNEL_SUPPORTS_EAGER_CHILD_DESTRUCTION).
*/
return _log.closed() && _binary.closed();
}
/**
* Quota unconditionally consumed by the child's environment
*/
static Ram_quota env_ram_quota()
{
return { Cpu_connection::RAM_QUOTA + Pd_connection::RAM_QUOTA +
Log_connection::RAM_QUOTA + 2*Rom_connection::RAM_QUOTA };
}
static Cap_quota env_cap_quota()
{
return { Cpu_connection::CAP_QUOTA + Pd_connection::CAP_QUOTA +
Log_connection::CAP_QUOTA + 2*Rom_connection::CAP_QUOTA +
1 /* parent cap */ };
}
void close_all_sessions();
template <typename FN>
void for_each_session(FN const &fn) const
{
_id_space.for_each<Session_state const>(fn);
}
/**
* Deduce env session costs from usable RAM quota
*/
static Ram_quota effective_quota(Ram_quota quota)
{
return _effective_quota(quota, env_ram_quota());
}
/**
* Deduce env session costs from usable cap quota
*/
static Cap_quota effective_quota(Cap_quota quota)
{
return _effective_quota(quota, env_cap_quota());
}
Pd_session_capability pd_session_cap() const { return _pd.cap(); }
Parent_capability parent_cap() const { return cap(); }
Ram_allocator &ram() { return _pd.session(); }
Ram_allocator const &ram() const { return _pd.session(); }
Cpu_session &cpu() { return _cpu.session(); }
Pd_session &pd() { return _pd.session(); }
Pd_session const &pd() const { return _pd.session(); }
/**
* Request factory for creating session-state objects
*/
Session_state::Factory &session_factory() { return _session_factory; }
/**
* Instruct the child to yield resources
*
* By calling this method, the child will be notified about the
* need to release the specified amount of resources. For more
* details about the protocol between a child and its parent,
* refer to the description given in 'parent/parent.h'.
*/
void yield(Resource_args const &args);
/**
* Notify the child about newly available resources
*/
void notify_resource_avail() const;
/**
* Notify the child to give a lifesign
*/
void heartbeat();
/**
* Return number of missing heartbeats since the last response from
* the child
*/
unsigned skipped_heartbeats() const;
/**********************
** Parent interface **
**********************/
void announce(Service_name const &) override;
void session_sigh(Signal_context_capability) override;
Session_capability session(Client::Id, Service_name const &,
Session_args const &, Affinity const &) override;
Session_capability session_cap(Client::Id) override;
Upgrade_result upgrade(Client::Id, Upgrade_args const &) override;
Close_result close(Client::Id) override;
void exit(int) override;
void session_response(Server::Id, Session_response) override;
void deliver_session_cap(Server::Id, Session_capability) override;
Thread_capability main_thread_cap() const override;
void resource_avail_sigh(Signal_context_capability) override;
void resource_request(Resource_args const &) override;
void yield_sigh(Signal_context_capability) override;
Resource_args yield_request() override;
void yield_response() override;
void heartbeat_sigh(Signal_context_capability) override;
void heartbeat_response() override;
};
#endif /* _INCLUDE__BASE__CHILD_H_ */