/* * \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 #include #include #include #include #include #include #include #include #include #include #include #include #include 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"; } /** * Determine service to provide a session request * * \return service to be contacted for the new session * \deprecated * * \throw Service_denied */ virtual Service &resolve_session_request(Service::Name const &, Session_state::Args const &) { throw Service_denied(); } /** * 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 &) { /* \deprecated make pure virtual once the old version is gone */ throw Service_denied(); } /** * 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) { } /** * 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) { } class Nonexistent_id_space : Exception { }; /** * ID space for sessions provided by the child * * \throw Nonexistent_id_space */ virtual Id_space &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, 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 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 cap() const override { return _cap; } }; /* child policy */ Child_policy &_policy; /* print error message with the child's name prepended */ template 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 _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; static Child_policy::Route _resolve_session_request(Child_policy &, Service::Name const &, char const *); /* * Members that are initialized not before the child's environment is * complete. */ void _try_construct_env_dependent_members(); Constructible _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_session &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 { }; /* * The child's environment sessions */ template struct Env_connection { Child &_child; Id_space::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(Ram_session_capability to, Ram_quota amount) override { Ram_transfer::Account &from = _service; from.transfer(to, amount); } /** * Service (Ram_transfer::Account) interface */ Ram_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 { }; Constructible > _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()) : _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._resolve_session_request(_child._policy, _service_name(), _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 cap() const { return _connection.constructed() ? _connection->cap() : Capability(); } bool closed() const { return !_connection.constructed() || _connection->closed(); } void close() { if (_connection.constructed()) _connection->close(); } }; 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 &); void _discard_env_session(Id_space::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 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 void for_each_session(FN const &fn) const { _id_space.for_each(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()); } Ram_session_capability ram_session_cap() const { return _pd.cap(); } Pd_session_capability pd_session_cap() const { return _pd.cap(); } Parent_capability parent_cap() const { return cap(); } Ram_session &ram() { return _pd.session(); } Ram_session 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_ */