diff --git a/base-linux/src/base/env/platform_env.cc b/base-linux/src/base/env/platform_env.cc index a40df1b6bc..e37ee7b8f9 100644 --- a/base-linux/src/base/env/platform_env.cc +++ b/base-linux/src/base/env/platform_env.cc @@ -67,7 +67,7 @@ Platform_env::Local_parent::session(Service_name const &service_name, .ulong_value(~0); if (size == 0) - return Parent_client::session(service_name, args, affinity); + return Expanding_parent_client::session(service_name, args, affinity); Rm_session_mmap *rm = new (env()->heap()) Rm_session_mmap(true, size); @@ -75,7 +75,7 @@ Platform_env::Local_parent::session(Service_name const &service_name, return Session_capability::local_cap(rm); } - return Parent_client::session(service_name, args, affinity); + return Expanding_parent_client::session(service_name, args, affinity); } @@ -98,10 +98,10 @@ void Platform_env::Local_parent::close(Session_capability session) } -Platform_env::Local_parent::Local_parent(Parent_capability parent_cap) -: Parent_client(parent_cap) -{ -} +Platform_env::Local_parent::Local_parent(Parent_capability parent_cap, + Emergency_ram_reserve &reserve) +: Expanding_parent_client(parent_cap, reserve) +{ } /****************** @@ -143,7 +143,7 @@ static Parent_capability obtain_parent_cap() Platform_env::Local_parent &Platform_env::_parent() { - static Local_parent local_parent(obtain_parent_cap()); + static Local_parent local_parent(obtain_parent_cap(), *this); return local_parent; } @@ -153,7 +153,8 @@ Platform_env::Platform_env() Platform_env_base(static_cap_cast(_parent().session("Env::ram_session", "")), static_cap_cast(_parent().session("Env::cpu_session", "")), static_cap_cast (_parent().session("Env::pd_session", ""))), - _heap(Platform_env_base::ram_session(), Platform_env_base::rm_session()) + _heap(Platform_env_base::ram_session(), Platform_env_base::rm_session()), + _emergency_ram_ds(ram_session()->alloc(_emergency_ram_size())) { /* register TID and PID of the main thread at core */ cpu_session()->thread_id(parent()->main_thread_cap(), diff --git a/base-linux/src/base/env/platform_env.h b/base-linux/src/base/env/platform_env.h index a7d9233234..0f74d7e617 100644 --- a/base-linux/src/base/env/platform_env.h +++ b/base-linux/src/base/env/platform_env.h @@ -360,7 +360,7 @@ namespace Genode { /** * 'Platform_env' used by all processes except for core */ - class Platform_env : public Platform_env_base + class Platform_env : public Platform_env_base, public Emergency_ram_reserve { private: @@ -376,7 +376,7 @@ namespace Genode { * All requests that do not refer to the RM service are passed * through the real parent interface. */ - class Local_parent : public Parent_client + class Local_parent : public Expanding_parent_client { public: @@ -396,16 +396,25 @@ namespace Genode { * promote requests to non-local * services */ - Local_parent(Parent_capability parent_cap); + Local_parent(Parent_capability parent_cap, + Emergency_ram_reserve &); }; /** - * Obtain singleton instance of parent interface + * Return instance of parent interface */ - static Local_parent &_parent(); + Local_parent &_parent(); Heap _heap; + /* + * Emergency RAM reserve + * + * See the comment of '_fallback_sig_cap()' in 'env/env.cc'. + */ + constexpr static size_t _emergency_ram_size() { return 4*1024; } + Ram_dataspace_capability _emergency_ram_ds; + /************************************* ** Linux-specific helper functions ** @@ -432,6 +441,13 @@ namespace Genode { } + /************************************* + ** Emergency_ram_reserve interface ** + *************************************/ + + void release() { ram_session()->free(_emergency_ram_ds); } + + /******************* ** Env interface ** *******************/ diff --git a/base/src/base/env/env.cc b/base/src/base/env/env.cc index 7e85b863f8..5554b140a3 100644 --- a/base/src/base/env/env.cc +++ b/base/src/base/env/env.cc @@ -29,3 +29,49 @@ namespace Genode { return &_env; } } + + +static Genode::Signal_receiver *resource_sig_rec() +{ + static Genode::Signal_receiver sig_rec; + return &sig_rec; +} + + +Genode::Signal_context_capability +Genode::Expanding_parent_client::_fallback_sig_cap() +{ + static Signal_context _sig_ctx; + static Signal_context_capability _sig_cap; + + /* create signal-context capability only once */ + if (!_sig_cap.valid()) { + + /* + * Because the 'manage' function consumes meta data of the signal + * session, calling it may result in an 'Out_of_metadata' error. The + * 'manage' function handles this error by upgrading the session quota + * accordingly. However, this upgrade, in turn, may result in the + * depletion of the process' RAM quota. In this case, the process would + * issue a resource request to the parent. But in order to do so, the + * fallback signal handler has to be constructed. To solve this + * hen-and-egg problem, we allocate a so-called emergency RAM reserve + * immediately at the startup of the process as part of the + * 'Platform_env'. When initializing the fallback signal handler, these + * resources get released in order to ensure an eventual upgrade of the + * signal session to succeed. + * + * The corner case is tested by 'os/src/test/resource_request'. + */ + _emergency_ram_reserve.release(); + _sig_cap = resource_sig_rec()->manage(&_sig_ctx); + } + + return _sig_cap; +} + + +void Genode::Expanding_parent_client::_wait_for_resource_response() +{ + resource_sig_rec()->wait_for_signal(); +} diff --git a/base/src/base/env/platform_env.h b/base/src/base/env/platform_env.h index 05b2fb23c0..5780ba0e24 100644 --- a/base/src/base/env/platform_env.h +++ b/base/src/base/env/platform_env.h @@ -85,11 +85,11 @@ struct Genode::Expanding_cpu_session_client : Upgradeable_client +#include #include #include #include @@ -27,6 +28,7 @@ namespace Genode { class Expanding_rm_session_client; class Expanding_ram_session_client; class Expanding_cpu_session_client; + class Expanding_parent_client; } @@ -35,13 +37,23 @@ namespace Genode { * * If the function 'func' throws an exception of type 'EXC', the 'handler' * is called and the function call is retried. + * + * \param EXC exception type to handle + * \param func functor to execute + * \param handler exception handler executed if 'func' raised an exception + * of type 'EXC' + * \param attempts number of attempts to execute 'func' before giving up + * and reflecting the exception 'EXC' to the caller. If not + * specified, attempt infinitely. */ template -auto retry(FUNC func, HANDLER handler) -> decltype(func()) +auto retry(FUNC func, HANDLER handler, unsigned attempts = ~0U) -> decltype(func()) { - for (;;) + for (unsigned i = 0; attempts == ~0U || i < attempts; i++) try { return func(); } catch (EXC) { handler(); } + + throw EXC(); } @@ -64,6 +76,7 @@ struct Upgradeable_client : CLIENT char buf[128]; Genode::snprintf(buf, sizeof(buf), "ram_quota=%zd", quota); + Genode::env()->parent()->upgrade(_cap, buf); } }; @@ -74,12 +87,235 @@ struct Genode::Expanding_ram_session_client : Upgradeable_client(cap) { } - Ram_dataspace_capability alloc(size_t size, bool cached) + Ram_dataspace_capability alloc(size_t size, bool cached = true) { - return retry( - [&] () { return Ram_session_client::alloc(size, cached); }, - [&] () { upgrade_ram(8*1024); }); + /* + * If the RAM session runs out of quota, issue a resource request + * to the parent and retry. + */ + enum { NUM_ATTEMPTS = 2 }; + return retry( + [&] () { + /* + * If the RAM session runs out of meta data, upgrade the + * session quota and retry. + */ + return retry( + [&] () { return Ram_session_client::alloc(size, cached); }, + [&] () { upgrade_ram(8*1024); }); + }, + [&] () { + char buf[128]; + + /* + * The RAM service withdraws the meta data for the allocator + * from the RAM quota. In the worst case, a new slab block + * may be needed. To cover the worst case, we need to take + * this possible overhead into account when requesting + * additional RAM quota from the parent. + * + * Because the worst case almost never happens, we request + * a bit too much quota for the most time. + */ + enum { ALLOC_OVERHEAD = 1024U }; + Genode::snprintf(buf, sizeof(buf), "ram_quota=%zd", + size + ALLOC_OVERHEAD); + env()->parent()->resource_request(buf); + }, + NUM_ATTEMPTS); + } + + int transfer_quota(Ram_session_capability ram_session, size_t amount) + { + enum { NUM_ATTEMPTS = 2 }; + int ret = -1; + for (unsigned i = 0; i < NUM_ATTEMPTS; i++) { + + ret = Ram_session_client::transfer_quota(ram_session, amount); + if (ret != -3) break; + + /* + * The transfer failed because we don't have enough quota. Request + * the needed amount from the parent. + * + * XXX Let transfer_quota throw 'Ram_session::Quota_exceeded' + */ + char buf[128]; + Genode::snprintf(buf, sizeof(buf), "ram_quota=%zd", amount); + env()->parent()->resource_request(buf); + } + return ret; } }; + +struct Emergency_ram_reserve +{ + virtual void release() = 0; +}; + + +class Genode::Expanding_parent_client : public Parent_client +{ + private: + + /** + * Signal handler state + * + * UNDEFINED - No signal handler is effective. If we issue a + * resource request, use our built-in fallback + * signal handler. + * BLOCKING_DEFAULT - The fallback signal handler is effective. + * When using this handler, we block for a + * for a response to a resource request. + * CUSTOM - A custom signal handler was registered. Calls + * of 'resource_request' won't block. + */ + enum State { UNDEFINED, BLOCKING_DEFAULT, CUSTOM }; + State _state = { UNDEFINED }; + + /** + * Lock used to serialize resource requests + */ + Lock _lock; + + /** + * Return signal context capability for the fallback signal handler + */ + Signal_context_capability _fallback_sig_cap(); + + /** + * Block for resource response arriving at the fallback signal handler + */ + static void _wait_for_resource_response(); + + /** + * Emergency RAM reserve for constructing the fallback signal handler + * + * See the comment of '_fallback_sig_cap()' in 'env/env.cc'. + */ + Emergency_ram_reserve &_emergency_ram_reserve; + + public: + + Expanding_parent_client(Parent_capability cap, + Emergency_ram_reserve &emergency_ram_reserve) + : + Parent_client(cap), _emergency_ram_reserve(emergency_ram_reserve) + { } + + + /********************** + ** Parent interface ** + **********************/ + + Session_capability session(Service_name const &name, + Session_args const &args, + Affinity const &affinity) + { + enum { NUM_ATTEMPTS = 2 }; + return retry( + [&] () { return Parent_client::session(name, args, affinity); }, + [&] () { + + /* + * Request amount of session quota from the parent. + * + * XXX We could deduce the available quota of our + * own RAM session from the request. + */ + size_t const ram_quota = + Arg_string::find_arg(args.string(), "ram_quota") + .ulong_value(0); + + char buf[128]; + snprintf(buf, sizeof(buf), "ram_quota=%zd", ram_quota); + + resource_request(Resource_args(buf)); + }, + NUM_ATTEMPTS); + } + + void upgrade(Session_capability to_session, Upgrade_args const &args) + { + /* + * If the upgrade fails, attempt to issue a resource request twice. + * + * If the default fallback for resource-available signals is used, + * the first request will block until the resources are upgraded. + * The second attempt to upgrade will succeed. + * + * If a custom handler is installed, the resource quest will return + * immediately. The second upgrade attempt may fail too if the + * parent handles the resource request asynchronously. In this + * case, we escalate the problem to caller by propagating the + * 'Parent::Quota_exceeded' exception. Now, it is the job of the + * caller to issue (and respond to) a resource request. + */ + enum { NUM_ATTEMPTS = 2 }; + retry( + [&] () { Parent_client::upgrade(to_session, args); }, + [&] () { resource_request(Resource_args(args.string())); }, + NUM_ATTEMPTS); + } + + void resource_avail_sigh(Signal_context_capability sigh) + { + Lock::Guard guard(_lock); + + /* + * If signal hander gets de-installed, let the next call of + * 'resource_request' install the fallback signal handler. + */ + if (_state == CUSTOM && !sigh.valid()) + _state = UNDEFINED; + + /* + * Forward information about a custom signal handler and remember + * state to avoid blocking in 'resource_request'. + */ + if (sigh.valid()) { + _state = CUSTOM; + Parent_client::resource_avail_sigh(sigh); + } + } + + void resource_request(Resource_args const &args) + { + Lock::Guard guard(_lock); + + PLOG("resource_request: %s", args.string()); + + /* + * Issue request but don't block if a custom signal handler is + * installed. + */ + if (_state == CUSTOM) { + Parent_client::resource_request(args); + return; + } + + /* + * Install fallback signal handler not yet installed. + */ + if (_state == UNDEFINED) { + Parent_client::resource_avail_sigh(_fallback_sig_cap()); + _state = BLOCKING_DEFAULT; + } + + /* + * Issue resource request + */ + Parent_client::resource_request(args); + + /* + * Block until we get a response for the outstanding resource + * request. + */ + if (_state == BLOCKING_DEFAULT) + _wait_for_resource_response(); + } +}; + + #endif /* _PLATFORM_ENV_COMMON_H_ */ diff --git a/base/src/base/env/reload_parent_cap.cc b/base/src/base/env/reload_parent_cap.cc index 52700e27af..aa67e6f659 100644 --- a/base/src/base/env/reload_parent_cap.cc +++ b/base/src/base/env/reload_parent_cap.cc @@ -44,8 +44,9 @@ void Genode::Platform_env::reload_parent_cap(Native_capability::Dst dst, /* * Re-initialize 'Platform_env' members */ - _parent_client = Parent_client(Genode::parent_cap()); - _resources = Resources(_parent_client); + static_cast(_parent_client) = Parent_client(Genode::parent_cap()); + + _resources = Resources(_parent_client); /* * Keep information about dynamically allocated memory but use the new