diff --git a/repos/base-linux/src/lib/base/child_process.cc b/repos/base-linux/src/lib/base/child_process.cc index 773c778c7..cdb2523c5 100644 --- a/repos/base-linux/src/lib/base/child_process.cc +++ b/repos/base-linux/src/lib/base/child_process.cc @@ -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 */ diff --git a/repos/base/include/base/child.h b/repos/base/include/base/child.h index c5e826e67..d106103b7 100644 --- a/repos/base/include/base/child.h +++ b/repos/base/include/base/child.h @@ -217,7 +217,7 @@ class Genode::Child : protected Rpc_object, /** * Return capability of the initial thread */ - virtual Capability cap() = 0; + virtual Capability cap() const = 0; }; struct Initial_thread : Initial_thread_base @@ -242,102 +242,17 @@ class Genode::Child : protected Rpc_object, void start(addr_t) override; - Capability cap() { return _cap; } + Capability cap() const { return _cap; } }; /* child policy */ Child_policy &_policy; - /* sessions opened by the child */ - Id_space _id_space; + /* print error message with the child's name prepended */ + template + void _error(ARGS &&... args) { error(_policy.name(), ": ", args...); } - typedef Session_state::Args Args; - - template - struct Env_connection - { - typedef String<64> Label; - - Args const _args; - Service &_service; - Local_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 &id_space, - Id_space::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 cap() const { return _connection.cap(); } - }; - - Env_connection _ram { _policy, - _id_space, Parent::Env::ram(), _policy.name() }; - - Env_connection _pd { _policy, - _id_space, Parent::Env::pd(), _policy.name() }; - - Env_connection _cpu { _policy, - _id_space, Parent::Env::cpu(), _policy.name() }; - - Env_connection _log { _policy, - _id_space, Parent::Env::log(), _policy.name() }; - - Env_connection _binary { _policy, - _id_space, Parent::Env::binary(), _policy.binary_name() }; - - Constructible > _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, 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 _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; + + /* factory for dynamically created session-state objects */ + Constructible _session_factory; + + Constructible _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, ~Process(); }; - Process _process; + Constructible _process; + + /* + * The child's environment sessions + */ + + template + 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; + + /** + * 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::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 cap() const { return _connection.cap(); } + }; + + Env_connection _ram { *this, Env::ram(), _policy.name() }; + Env_connection _pd { *this, Env::pd(), _policy.name() }; + Env_connection _cpu { *this, Env::cpu(), _policy.name() }; + Env_connection _log { *this, Env::log(), _policy.name() }; + Env_connection _binary { *this, Env::binary(), _policy.binary_name() }; + + Constructible > _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, 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, * \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, */ 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, /** * 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, 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 diff --git a/repos/base/include/base/local_connection.h b/repos/base/include/base/local_connection.h index 21bc5aac2..cf79d3c00 100644 --- a/repos/base/include/base/local_connection.h +++ b/repos/base/include/base/local_connection.h @@ -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_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(); } }; diff --git a/repos/base/include/base/service.h b/repos/base/include/base/service.h index 3a932d7ee..4e9fd3663 100644 --- a/repos/base/include/base/service.h +++ b/repos/base/include/base/service.h @@ -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; } diff --git a/repos/base/src/lib/base/child.cc b/repos/base/src/lib/base/child.cc index 0f8928541..f09aef263 100644 --- a/repos/base/src/lib/base/child.cc +++ b/repos/base/src/lib/base/child.cc @@ -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(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) { + 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(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(); } diff --git a/repos/base/src/lib/base/child_process.cc b/repos/base/src/lib/base/child_process.cc index a952daa28..c09cf720f 100644 --- a/repos/base/src/lib/base/child_process.cc +++ b/repos/base/src/lib/base/child_process.cc @@ -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 */ diff --git a/repos/demo/include/launchpad/launchpad.h b/repos/demo/include/launchpad/launchpad.h index d6b5e3e2c..05206e295 100644 --- a/repos/demo/include/launchpad/launchpad.h +++ b/repos/demo/include/launchpad/launchpad.h @@ -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 ** diff --git a/repos/demo/src/lib/launchpad/launchpad.cc b/repos/demo/src/lib/launchpad/launchpad.cc index 8ff58f907..87e463af0 100644 --- a/repos/demo/src/lib/launchpad/launchpad.cc +++ b/repos/demo/src/lib/launchpad/launchpad.cc @@ -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); diff --git a/repos/os/src/drivers/platform/spec/x86/pci_session_component.h b/repos/os/src/drivers/platform/spec/x86/pci_session_component.h index 14ca90894..458741f44 100644 --- a/repos/os/src/drivers/platform/spec/x86/pci_session_component.h +++ b/repos/os/src/drivers/platform/spec/x86/pci_session_component.h @@ -213,13 +213,24 @@ class Platform::Session_component : public Genode::Rpc_object class Device_pd { + public: + + class Startup_failed : Genode::Exception { }; + private: enum { RAM_QUOTA = 190 * 4096 }; - Quota_reservation const _reservation; - Device_pd_policy _policy; - Genode::Child _child; + 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 _connection; public: @@ -229,7 +240,7 @@ class Platform::Session_component : public Genode::Rpc_object * * \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,