base/env: Implementation of 'Expanding_parent'

This commit is contained in:
Norman Feske 2013-09-25 17:59:17 +02:00
parent ae40cb545c
commit 65c4246f95
6 changed files with 342 additions and 26 deletions

View File

@ -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<Ram_session>(_parent().session("Env::ram_session", "")),
static_cap_cast<Cpu_session>(_parent().session("Env::cpu_session", "")),
static_cap_cast<Pd_session> (_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(),

View File

@ -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 **
*******************/

View File

@ -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();
}

View File

@ -85,11 +85,11 @@ struct Genode::Expanding_cpu_session_client : Upgradeable_client<Genode::Cpu_ses
};
class Genode::Platform_env : public Genode::Env
class Genode::Platform_env : public Genode::Env, public Emergency_ram_reserve
{
private:
Parent_client _parent_client;
Expanding_parent_client _parent_client;
struct Resources
{
@ -115,7 +115,15 @@ class Genode::Platform_env : public Genode::Env
Resources _resources;
Heap _heap;
char _initial_junk[sizeof(addr_t) * 4096];
char _initial_heap_chunk[sizeof(addr_t) * 4096];
/*
* 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;
public:
@ -124,15 +132,23 @@ class Genode::Platform_env : public Genode::Env
*/
Platform_env()
:
_parent_client(Genode::parent_cap()),
_parent_client(Genode::parent_cap(), *this),
_resources(_parent_client),
_heap(&_resources.ram, &_resources.rm, Heap::UNLIMITED,
_initial_junk, sizeof(_initial_junk))
_initial_heap_chunk, sizeof(_initial_heap_chunk)),
_emergency_ram_ds(_resources.ram.alloc(_emergency_ram_size()))
{ }
void reload_parent_cap(Native_capability::Dst, long);
/*************************************
** Emergency_ram_reserve interface **
*************************************/
void release() { _resources.ram.free(_emergency_ram_ds); }
/*******************
** Env interface **
*******************/

View File

@ -17,6 +17,7 @@
#define _PLATFORM_ENV_COMMON_H_
#include <base/env.h>
#include <util/arg_string.h>
#include <parent/client.h>
#include <ram_session/client.h>
#include <rm_session/client.h>
@ -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 <typename EXC, typename FUNC, typename HANDLER>
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<Genode::Ram_ses
Expanding_ram_session_client(Ram_session_capability cap)
: Upgradeable_client<Genode::Ram_session_client>(cap) { }
Ram_dataspace_capability alloc(size_t size, bool cached)
Ram_dataspace_capability alloc(size_t size, bool cached = true)
{
return retry<Ram_session::Out_of_metadata>(
[&] () { 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<Ram_session::Quota_exceeded>(
[&] () {
/*
* If the RAM session runs out of meta data, upgrade the
* session quota and retry.
*/
return retry<Ram_session::Out_of_metadata>(
[&] () { 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<Parent::Quota_exceeded>(
[&] () { 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::Quota_exceeded>(
[&] () { 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_ */

View File

@ -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) = Parent_client(Genode::parent_cap());
_resources = Resources(_parent_client);
/*
* Keep information about dynamically allocated memory but use the new