genode/repos/base/include/base/child.h
Norman Feske 3cc6df3116 base: tighten affinity handling
This patch improves the robustness of the CPU-affinity handling.

- The types in base/affinity.h received the accessors
  'Location::within(space)' and 'Affinity::valid', which alleviates
  the fiddling with coordinates when sanity checking the values,
  in init or core.

- The 'Affinity::Location::valid' method got removed because its
  meaning was too vague. For sanity checks of affinity configurations,
  the new 'within' method is approriate. In cases where only the x,y
  values are used for selecting a physical CPU (during thread creation),
  the validity check (width*height > 0) was not meaningful anyway.

- The 'Affinity::Location::from_xml' requires a 'Affinity::Space'
  as argument because a location always relates to the bounds of
  a specific space. This function now implements the selection of
  whole rows or columns, which has previously a feature of the
  sandbox library only.

- Whenever the sandbox library (init) encounters an invalid affinity
  configuration, it prints a warning message as a diagnostic aid.

- A new 'Affinity::unrestricted' function constructs an affinity that
  covers the whole affinity space. The named functions clarifies
  the meaning over the previous use of the default constructor.

- Core's CPU service denies session requests with an invalid
  affinity parameter. Previously, it would fall back to an
  unrestricted affinity.

Issue #4300
2021-12-17 15:06:38 +01:00

824 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/mutex.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 &,
Session::Diag) = 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 */
Mutex _yield_request_mutex { };
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()),
session_diag_from_args(_args.string()));
_env_service.construct(_child, route.service);
Affinity const affinity =
_child._policy.filter_session_affinity(Affinity::unrestricted());
_connection.construct(*_env_service, _child._id_space, _client_id,
_args, 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_ */