/* * \brief Child handling * \author Norman Feske * \date 2013-10-05 */ /* * Copyright (C) 2013-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__CLI_MONITOR__CHILD_H_ #define _INCLUDE__CLI_MONITOR__CHILD_H_ /* Genode includes */ #include #include #include #include #include #include #include #include /* CLI-monitor includes */ #include namespace Cli_monitor { class Child_base; } class Cli_monitor::Child_base : public Genode::Child_policy { public: /* * XXX derive donated quota from information to be provided by * the used 'Connection' interfaces */ enum { DONATED_RAM_QUOTA = 128*1024 }; class Quota_exceeded : public Genode::Exception { }; typedef Genode::size_t size_t; typedef Genode::Cap_quota Cap_quota; typedef Genode::Registered Parent_service; private: Genode::Env &_env; Ram &_ram; Genode::Allocator &_alloc; Genode::Session_label const _label; Binary_name const _binary_name; Genode::Pd_session_capability _ref_pd_cap; Genode::Pd_session &_ref_pd; Cap_quota _cap_quota; size_t _ram_quota; size_t _ram_limit; struct Parent_services : Genode::Registry { Genode::Allocator &_alloc; Parent_services(Genode::Allocator &alloc) : _alloc(alloc) { } ~Parent_services() { for_each([&] (Parent_service &s) { Genode::destroy(_alloc, &s); }); } } _parent_services { _alloc }; enum { ENTRYPOINT_STACK_SIZE = 12*1024 }; Genode::Rpc_entrypoint _entrypoint; Genode::Child_policy_dynamic_rom_file _config_policy; /** * If set to true, immediately withdraw resources yielded by the child */ bool _withdraw_on_yield_response = false; /** * Arguments of current resource request from the child */ Genode::Parent::Resource_args _resource_args { }; Genode::Signal_context_capability _yield_response_sigh_cap; Genode::Signal_context_capability _exit_sig_cap; /* true if child is scheduled for destruction */ bool _exited = false; Genode::Child _child; Genode::Service &_matching_service(Genode::Service::Name const &name, Genode::Session_label const &label) { Genode::Service *service = nullptr; /* check for config file request */ if ((service = _config_policy.resolve_session_request(name, label))) return *service; /* populate session-local parent service registry on demand */ _parent_services.for_each([&] (Parent_service &s) { if (s.name() == name) service = &s; }); if (service) return *service; return *new (_alloc) Parent_service(_parent_services, _env, name); } public: /** * Constructor * * \param ref_ram used as reference account for the child'd RAM * session and for allocating the backing store * for the child's configuration * \param alloc allocator used to fill parent-service registry * on demand */ Child_base(Genode::Env &env, Ram &ram, Genode::Allocator &alloc, Name const &label, Binary_name const &binary_name, Genode::Pd_session &ref_pd, Genode::Pd_session_capability ref_pd_cap, Genode::Region_map &local_rm, Cap_quota cap_quota, Genode::size_t ram_quota, Genode::size_t ram_limit, Genode::Signal_context_capability yield_response_sig_cap, Genode::Signal_context_capability exit_sig_cap) : _env(env), _ram(ram), _alloc(alloc), _label(label), _binary_name(binary_name), _ref_pd_cap (ref_pd_cap), _ref_pd (ref_pd), _cap_quota(cap_quota), _ram_quota(ram_quota), _ram_limit(ram_limit), _entrypoint(&ref_pd, ENTRYPOINT_STACK_SIZE, _label.string(), false), _config_policy(local_rm, "config", _entrypoint, &_env.ram()), _yield_response_sigh_cap(yield_response_sig_cap), _exit_sig_cap(exit_sig_cap), _child(local_rm, _entrypoint, *this) { } Genode::Session_label label() const { return _label; } void configure(char const *config, size_t config_len) { _config_policy.load(config, config_len); } void start() { _entrypoint.activate(); } /** * Issue yield request to the child */ void yield(size_t amount, bool greedy) { if (requested_ram_quota()) return; /* resource request in flight */ char buf[128]; Genode::snprintf(buf, sizeof(buf), "ram_quota=%ld", amount); _withdraw_on_yield_response = greedy; _child.yield(buf); } /** * Return amount of RAM currently requested by the child */ size_t requested_ram_quota() const { return Genode::Arg_string::find_arg(_resource_args.string(), "ram_quota").ulong_value(0); } /** * Withdraw quota from the child * * \throw Ram::Transfer_quota_failed */ void withdraw_ram_quota(size_t amount) { if (!amount) return; _ram.withdraw_from(_child.pd_session_cap(), amount); _ram_quota -= amount; } /** * Upgrade quota of child * * \throw Ram::Transfer_quota_failed */ void upgrade_ram_quota(size_t amount) { _ram.transfer_to(_child.pd_session_cap(), amount); _ram_quota += amount; /* wake up child if resource request is in flight */ size_t const req = requested_ram_quota(); if (req && _child.pd().avail_ram().value >= req) { _child.notify_resource_avail(); /* clear request state */ _resource_args = Genode::Parent::Resource_args(""); } } /** * Try to respond to a current resource request issued by the child * * This method evaluates the conditions, under which a resource * request can be answered: There must be enough room between the * current quota and the configured limit, and there must be enough * slack memory available. If both conditions are met, the quota * of the child gets upgraded. */ void try_response_to_resource_request() { size_t const req = requested_ram_quota(); if (!req) return; /* no resource request in flight */ /* * Respond to the current request if the requested quota fits * within the limit and if there is enough free quota available. */ if (req <= _ram.status().avail && req + _ram_quota <= _ram_limit) { try { upgrade_ram_quota(req); } catch (Ram::Transfer_quota_failed) { } } } /** * Set limit for on-demand RAM quota expansion */ void ram_limit(size_t limit) { _ram_limit = limit; try_response_to_resource_request(); } struct Ram_status { size_t quota = 0, limit = 0, xfer = 0, used = 0, avail = 0, req = 0; Ram_status() { } Ram_status(size_t quota, size_t limit, size_t xfer, size_t used, size_t avail, size_t req) : quota(quota), limit(limit), xfer(xfer), used(used), avail(avail), req(req) { } }; /** * Return RAM quota status of the child * * XXX should be a const method, but the 'Pd_session' accessors * are not const */ Ram_status ram_status() { return Ram_status(_ram_quota, _ram_limit, _ram_quota - _child.pd().ram_quota().value, _child.pd().used_ram().value, _child.pd().avail_ram().value, requested_ram_quota()); } /** * Return true if child exited and should be destructed */ bool exited() const { return _exited; } /**************************** ** Child_policy interface ** ****************************/ Name name() const override { return _label; } Binary_name binary_name() const override { return _binary_name; } Genode::Pd_session_capability ref_pd_cap() const override { return _ref_pd_cap; } Genode::Pd_session &ref_pd() override { return _ref_pd; } void init(Genode::Pd_session &session, Genode::Pd_session_capability cap) override { session.ref_account(_ref_pd_cap); _ref_pd.transfer_quota(cap, _cap_quota); _ref_pd.transfer_quota(cap, Genode::Ram_quota{_ram_quota}); } Route resolve_session_request(Genode::Service::Name const &name, Genode::Session_label const &label) override { return Route { .service = _matching_service(name, label), .label = label, .diag = Genode::Session::Diag() }; } void yield_response() { if (_withdraw_on_yield_response) { enum { RESERVE = 4*1024*1024 }; size_t amount = _child.pd().avail_ram().value < RESERVE ? 0 : _child.pd().avail_ram().value - RESERVE; /* try to immediately withdraw freed-up resources */ try { withdraw_ram_quota(amount); } catch (Ram::Transfer_quota_failed) { } } /* propagate yield-response signal */ Genode::Signal_transmitter(_yield_response_sigh_cap).submit(); } void resource_request(Genode::Parent::Resource_args const &args) { _resource_args = args; try_response_to_resource_request(); } void exit(int exit_value) override { Genode::log("subsystem \"", name(), "\" exited with value ", exit_value); _exited = true; /* trigger destruction of the child */ Genode::Signal_transmitter(_exit_sig_cap).submit(); } }; #endif /* _INCLUDE__CLI_MONITOR__CHILD_H_ */