base: apply routing policy to environment sessions

This patch changes the child-construction procedure to allow the routing
of environment sessions to arbitrary servers, not only to the parent.
In particular, it restores the ability to route the LOG session of the
child to a LOG service provided by a child of init. In principle, it
becomes possible to also route the immediate child's PD, CPU, and RAM
environment sessions in arbitrary ways, which simplifies scenarios that
intercept those sessions, e.g., the CPU sampler.

Note that the latter ability should be used with great caution because
init needs to interact with these sessions to create/destruct the child.
Normally, the sessions are provided by the parent. So init is safe at
all times. If they are routed to a child however, init will naturally
become dependent on this particular child. For the LOG session, this is
actually not a problem because even though the parent creates the LOG
session as part of the child's environment, it never interacts with the
session directly.

Fixes #2197
This commit is contained in:
Norman Feske 2016-12-12 17:40:55 +01:00
parent c450ddcb3d
commit 0d295f75a1
9 changed files with 270 additions and 149 deletions

View File

@ -63,12 +63,11 @@ Child::Process::Process(Dataspace_capability elf_ds,
Pd_session_capability pd_cap,
Pd_session &pd,
Ram_session &ram,
Initial_thread_base &initial_thread,
Initial_thread_base &,
Region_map &local_rm,
Region_map &remote_rm,
Parent_capability parent_cap)
:
initial_thread(initial_thread),
loaded_executable(elf_ds, ldso_ds, ram, local_rm, remote_rm, parent_cap)
{
/* skip loading when called during fork */

View File

@ -217,7 +217,7 @@ class Genode::Child : protected Rpc_object<Parent>,
/**
* Return capability of the initial thread
*/
virtual Capability<Cpu_thread> cap() = 0;
virtual Capability<Cpu_thread> cap() const = 0;
};
struct Initial_thread : Initial_thread_base
@ -242,102 +242,17 @@ class Genode::Child : protected Rpc_object<Parent>,
void start(addr_t) override;
Capability<Cpu_thread> cap() { return _cap; }
Capability<Cpu_thread> cap() const { return _cap; }
};
/* child policy */
Child_policy &_policy;
/* sessions opened by the child */
Id_space<Client> _id_space;
/* print error message with the child's name prepended */
template <typename... ARGS>
void _error(ARGS &&... args) { error(_policy.name(), ": ", args...); }
typedef Session_state::Args Args;
template <typename CONNECTION>
struct Env_connection
{
typedef String<64> Label;
Args const _args;
Service &_service;
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));
}
Env_connection(Child_policy &policy, Id_space<Parent::Client> &id_space,
Id_space<Parent::Client>::Id id, Label const &label = Label())
:
_args(_construct_args(policy, label)),
_service(policy.resolve_session_request(CONNECTION::service_name(), _args)),
_connection(_service, id_space, id, _args,
policy.filter_session_affinity(Affinity()))
{ }
typedef typename CONNECTION::Session_type SESSION;
SESSION &session() { return _connection.session(); }
Capability<SESSION> cap() const { return _connection.cap(); }
};
Env_connection<Ram_connection> _ram { _policy,
_id_space, Parent::Env::ram(), _policy.name() };
Env_connection<Pd_connection> _pd { _policy,
_id_space, Parent::Env::pd(), _policy.name() };
Env_connection<Cpu_connection> _cpu { _policy,
_id_space, Parent::Env::cpu(), _policy.name() };
Env_connection<Log_connection> _log { _policy,
_id_space, Parent::Env::log(), _policy.name() };
Env_connection<Rom_connection> _binary { _policy,
_id_space, Parent::Env::binary(), _policy.binary_name() };
Constructible<Env_connection<Rom_connection> > _linker { _policy,
_id_space, Parent::Env::linker(), _policy.linker_name() };
/* call 'Child_policy::init' methods for the environment sessions */
void _init_env_sessions()
{
_policy.init(_ram.session(), _ram.cap());
_policy.init(_cpu.session(), _cpu.cap());
_policy.init(_pd.session(), _pd.cap());
}
bool const _env_sessions_initialized = ( _init_env_sessions(), true );
Dataspace_capability _linker_dataspace()
{
try {
_linker.construct(_policy, _id_space,
Parent::Env::linker(), _policy.linker_name());
return _linker->session().dataspace();
}
catch (Parent::Service_denied) { return Rom_dataspace_capability(); }
}
/* heap for child-specific allocations using the child's quota */
Heap _heap;
/* factory for dynamically created session-state objects */
Session_state::Factory _session_factory { _heap };
Region_map &_local_rm;
Rpc_entrypoint &_entrypoint;
Parent_capability _parent_cap;
@ -351,15 +266,31 @@ class Genode::Child : protected Rpc_object<Parent>,
Lock _yield_request_lock;
Resource_args _yield_request_args;
Initial_thread _initial_thread { _cpu.session(), _pd.cap(), "initial" };
/* sessions opened by the child */
Id_space<Client> _id_space;
typedef Session_state::Args Args;
/*
* Members that are initialized not before the child's environment is
* complete.
*/
void _try_construct_env_dependent_members();
/* heap for child-specific allocations using the child's quota */
Constructible<Heap> _heap;
/* factory for dynamically created session-state objects */
Constructible<Session_state::Factory> _session_factory;
Constructible<Initial_thread> _initial_thread;
struct Process
{
class Missing_dynamic_linker : Exception { };
class Invalid_executable : Exception { };
Initial_thread_base &initial_thread;
struct Loaded_executable
{
/**
@ -426,7 +357,108 @@ class Genode::Child : protected Rpc_object<Parent>,
~Process();
};
Process _process;
Constructible<Process> _process;
/*
* The child's environment sessions
*/
template <typename CONNECTION>
struct Env_connection
{
typedef String<64> Label;
Args const _args;
Service &_service;
/*
* 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(), service.ram()),
_child(child), _service(service)
{ }
void initiate_request(Session_state &session) override
{
session.ready_callback = this;
session.async_client_notify = true;
_service.initiate_request(session);
}
/**
* Session_state::Ready_callback
*/
void session_ready(Session_state &session) override
{
_child._try_construct_env_dependent_members();
}
void wakeup() override { _service.wakeup(); }
} _env_service;
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())
:
_args(_construct_args(child._policy, label)),
_service(child._policy.resolve_session_request(_service_name(), _args)),
_env_service(child, _service),
_connection(_env_service, child._id_space, id, _args,
child._policy.filter_session_affinity(Affinity()))
{ }
typedef typename CONNECTION::Session_type SESSION;
SESSION &session() { return _connection.session(); }
Capability<SESSION> cap() const { return _connection.cap(); }
};
Env_connection<Ram_connection> _ram { *this, Env::ram(), _policy.name() };
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 &);
@ -444,20 +476,6 @@ class Genode::Child : protected Rpc_object<Parent>,
public:
/**
* Exception type
*
* The startup of the physical process of the child may fail if the
* ELF binary is invalid, if the ELF binary is dynamically linked
* but no dynamic linker is provided, if the creation of the initial
* thread failed, or if the RAM session of the child is exhausted.
* Each of those conditions will result in a diagnostic log message.
* But for the error handling, we only distinguish the RAM exhaustion
* from the other conditions and subsume the latter as
* 'Process_startup_failed'.
*/
class Process_startup_failed : public Exception { };
/**
* Constructor
*
@ -469,8 +487,6 @@ class Genode::Child : protected Rpc_object<Parent>,
* \throw Parent::Service_denied if the initial sessions for the
* child's environment could not be
* opened
* \throw Ram_session::Alloc_failed
* \throw Process_startup_failed
*/
Child(Region_map &rm, Rpc_entrypoint &entrypoint, Child_policy &policy);
@ -482,6 +498,22 @@ class Genode::Child : protected Rpc_object<Parent>,
*/
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(); }
/**
* RAM quota unconditionally consumed by the child's environment
*/
@ -506,7 +538,7 @@ class Genode::Child : protected Rpc_object<Parent>,
/**
* Return heap that uses the child's quota
*/
Allocator &heap() { return _heap; }
Allocator &heap() { return *_heap; }
Ram_session_capability ram_session_cap() const { return _ram.cap(); }
@ -516,7 +548,24 @@ class Genode::Child : protected Rpc_object<Parent>,
Cpu_session &cpu() { return _cpu.session(); }
Pd_session &pd() { return _pd .session(); }
Session_state::Factory &session_factory() { return _session_factory; }
/**
* Exception type
*/
class Inactive : Exception { };
/**
* Request factory for creating session-state objects
*
* \throw Inactive factory cannot by provided because the child it
* not yet completely initialized.
*/
Session_state::Factory &session_factory()
{
if (_session_factory.constructed())
return *_session_factory;
throw Inactive();
}
/**
* Instruct the child to yield resources

View File

@ -98,16 +98,18 @@ class Genode::Local_connection : Local_connection_base
* If session comes from a local service (e.g,. a virtualized
* RAM session, we return the reference to the corresponding
* component object, which can be called directly.
*
* Otherwise, if the session is provided
*/
if (_session_state.local_ptr)
return *static_cast<SESSION *>(_session_state.local_ptr);
/*
* The session is provided remotely. So return a client-stub
* for interacting with the session.
* The session is provided remotely. So return a client stub for
* interacting with the session. We construct the client object if
* we have a valid session capability.
*/
if (!_client.constructed() && _session_state.cap.valid())
_client.construct(cap());
if (_client.constructed())
return *_client;
@ -127,8 +129,7 @@ class Genode::Local_connection : Local_connection_base
Local_connection_base(service, id_space, id, args,
affinity, CONNECTION::RAM_QUOTA)
{
if (_session_state.cap.valid())
_client.construct(cap());
service.wakeup();
}
};

View File

@ -347,7 +347,7 @@ class Genode::Child_service : public Service
/*
* In contrast to local services and parent services, session-state
* objects for child services are owned by the server. This enables
* the server to asynchronouly respond to close requests when the
* the server to asynchronously respond to close requests when the
* client is already gone.
*/
Factory &_factory(Factory &) override { return _server_factory; }

View File

@ -205,7 +205,7 @@ Session_capability Child::session(Parent::Client::Id id,
Service &service = _policy.resolve_session_request(name.string(), argbuf);
Session_state &session =
create_session(_policy.name(), service, _session_factory,
create_session(_policy.name(), service, *_session_factory,
_id_space, id, argbuf, filtered_affinity);
session.ready_callback = this;
@ -482,7 +482,7 @@ void Child::deliver_session_cap(Server::Id id, Session_capability cap)
_policy.server_id_space().apply<Session_state>(id, [&] (Session_state &session) {
if (session.cap.valid()) {
error("attempt to assign session cap twice");
_error("attempt to assign session cap twice");
return;
}
@ -512,7 +512,12 @@ void Child::exit(int exit_value)
Thread_capability Child::main_thread_cap() const
{
return _process.initial_thread.cap();
/*
* The '_initial_thread' is always constructed when this function is
* called because the RPC call originates from the active child.
*/
return _initial_thread.constructed() ? _initial_thread->cap()
: Thread_capability();
}
@ -566,24 +571,72 @@ namespace {
}
void Child::_try_construct_env_dependent_members()
{
/* check if the environment sessions are complete */
if (!_ram.cap().valid() || !_pd .cap().valid() ||
!_cpu.cap().valid() || !_log.cap().valid() || !_binary.cap().valid())
return;
/*
* If the ROM-session request for the dynamic linker was granted but the
* response to the session request is still outstanding, we have to wait.
* Note that we proceed if the session request was denied by the policy,
* which may be the case when using a statically linked executable.
*/
if (_linker.constructed() && !_linker->cap().valid())
return;
/*
* Mark all environment sessions as handed out to prevent the triggering
* of signals by 'Child::session_sigh' for these sessions.
*/
_id_space.for_each<Session_state>([&] (Session_state &session) {
if (session.phase == Session_state::AVAILABLE)
session.phase = Session_state::CAP_HANDED_OUT; });
/* call 'Child_policy::init' methods for the environment sessions */
_policy.init(_ram.session(), _ram.cap());
_policy.init(_cpu.session(), _cpu.cap());
_policy.init(_pd.session(), _pd.cap());
_heap.construct(&_ram.session(), &_local_rm);
_session_factory.construct(*_heap);
try {
_initial_thread.construct(_cpu.session(), _pd.cap(), "initial");
_process.construct(_binary.session().dataspace(), _linker_dataspace(),
_pd.cap(), _pd.session(), _ram.session(),
*_initial_thread, _local_rm,
Child_address_space(_pd.session(), _policy).region_map(),
_parent_cap);
}
catch (Ram_session::Alloc_failed) { _error("RAM allocation failed during ELF loading"); }
catch (Cpu_session::Thread_creation_failed) { _error("unable to create initial thread"); }
catch (Cpu_session::Out_of_metadata) { _error("CPU session quota exhausted"); }
catch (Process::Missing_dynamic_linker) { _error("dynamic linker unavailable"); }
catch (Process::Invalid_executable) { _error("invalid ELF executable"); }
catch (Region_map::Attach_failed) { _error("ELF loading failed"); }
}
Child::Child(Region_map &local_rm,
Rpc_entrypoint &entrypoint,
Child_policy &policy)
try :
_policy(policy),
_heap(&_ram.session(), &local_rm),
_entrypoint(entrypoint),
_parent_cap(_entrypoint.manage(this)),
_process(_binary.session().dataspace(), _linker_dataspace(),
_pd.cap(), _pd.session(), _ram.session(), _initial_thread, local_rm,
Child_address_space(_pd.session(), _policy).region_map(),
_parent_cap)
{ }
catch (Cpu_session::Thread_creation_failed) { throw Process_startup_failed(); }
catch (Cpu_session::Out_of_metadata) { throw Process_startup_failed(); }
catch (Process::Missing_dynamic_linker) { throw Process_startup_failed(); }
catch (Process::Invalid_executable) { throw Process_startup_failed(); }
catch (Region_map::Attach_failed) { throw Process_startup_failed(); }
:
_policy(policy), _local_rm(local_rm), _entrypoint(entrypoint),
_parent_cap(_entrypoint.manage(this))
{
/*
* Issue environment-session request for obtaining the linker binary. We
* accept this request to fail. In this case, the child creation may still
* succeed if the binary is statically linked.
*/
try { _linker.construct(*this, Parent::Env::linker(), _policy.linker_name()); }
catch (Parent::Service_denied) { }
_try_construct_env_dependent_members();
}
Child::~Child()
@ -632,5 +685,16 @@ Child::~Child()
};
while (_id_space.apply_any<Session_state>(close_fn));
/*
* Make sure to destroy the users of the child's environment sessions
* before destructing those sessions. E.g., as the environment RAM session
* provides the backing store for the '_heap', we must not destroy the heap
* after the RAM session.
*/
_process.destruct();
_initial_thread.destruct();
_session_factory.destruct();
_heap.destruct();
}

View File

@ -192,7 +192,6 @@ Child::Process::Process(Dataspace_capability elf_ds,
Region_map &remote_rm,
Parent_capability parent_cap)
:
initial_thread(initial_thread),
loaded_executable(elf_ds, ldso_ds, ram, local_rm, remote_rm, parent_cap)
{
/* register parent interface for new protection domain */

View File

@ -115,8 +115,6 @@ class Launchpad_child : public Genode::Child_policy,
Genode::destroy(_child.heap(), &service); });
}
Genode::Allocator &heap() { return _child.heap(); }
/****************************
** Child_policy interface **

View File

@ -177,7 +177,7 @@ Launchpad_child *Launchpad::start_child(Launchpad_child::Name const &binary_name
Lock::Guard lock_guard(_children_lock);
_children.insert(c);
add_child(unique_name, ram_quota, *c, c->heap());
add_child(unique_name, ram_quota, *c, _heap);
return c;
} catch (...) {
@ -189,7 +189,7 @@ Launchpad_child *Launchpad::start_child(Launchpad_child::Name const &binary_name
void Launchpad::exit_child(Launchpad_child &child)
{
remove_child(child.name(), child.heap());
remove_child(child.name(), _heap);
Lock::Guard lock_guard(_children_lock);
_children.remove(&child);

View File

@ -213,6 +213,10 @@ class Platform::Session_component : public Genode::Rpc_object<Session>
class Device_pd
{
public:
class Startup_failed : Genode::Exception { };
private:
enum { RAM_QUOTA = 190 * 4096 };
@ -220,6 +224,13 @@ class Platform::Session_component : public Genode::Rpc_object<Session>
Quota_reservation const _reservation;
Device_pd_policy _policy;
Genode::Child _child;
void _check_child_started_up() const {
if (!_child.active())
throw Startup_failed(); }
bool const _active = (_check_child_started_up(), true);
Genode::Slave::Connection<Device_pd_connection> _connection;
public:
@ -229,7 +240,7 @@ class Platform::Session_component : public Genode::Rpc_object<Session>
*
* \throw Out_of_metadata session RAM does not suffice
* for creating device PD
* \throw Process_startup_failed by 'Child'
* \throw Startup_failed child could not be started
* \throw Parent::Service_denied by 'Slave::Connection'
*/
Device_pd(Genode::Region_map &local_rm,