genode/repos/demo/src/lib/launchpad/launchpad.cc

471 lines
13 KiB
C++

/*
* \brief Launchpad child management
* \author Norman Feske
* \author Markus Partheymueller
* \date 2006-09-01
*/
/*
* Copyright (C) 2006-2013 Genode Labs GmbH
* Copyright (C) 2012 Intel Corporation
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#include <base/env.h>
#include <base/child.h>
#include <base/sleep.h>
#include <base/service.h>
#include <base/snprintf.h>
#include <base/blocking.h>
#include <rom_session/connection.h>
#include <ram_session/connection.h>
#include <cpu_session/connection.h>
#include <os/config.h>
#include <timer_session/connection.h>
#include <launchpad/launchpad.h>
using namespace Genode;
/***************
** Launchpad **
***************/
Launchpad::Launchpad(unsigned long initial_quota)
:
_initial_quota(initial_quota),
_sliced_heap(env()->ram_session(), env()->rm_session())
{
/* names of services provided by the parent */
static const char *names[] = {
/* core services */
"CAP", "RAM", "RM", "PD", "CPU", "IO_MEM", "IO_PORT",
"IRQ", "ROM", "LOG", "SIGNAL",
/* services expected to got started by init */
"Nitpicker", "Init", "Timer", "PCI", "Block", "Nic", "Rtc",
0 /* null-termination */
};
for (unsigned i = 0; names[i]; i++)
_parent_services.insert(new (env()->heap())
Parent_service(names[i]));
}
/**
* Check if a program with the specified name already exists
*/
bool Launchpad::_child_name_exists(const char *name)
{
Launchpad_child *c = _children.first();
for ( ; c; c = c->List<Launchpad_child>::Element::next())
if (strcmp(c->name(), name) == 0)
return true;
return false;
}
/**
* Create a unique name based on the filename
*
* If a program with the filename as name already exists, we
* add a counting number as suffix.
*/
void Launchpad::_get_unique_child_name(const char *filename, char *dst, int dst_len)
{
Lock::Guard lock_guard(_children_lock);
char buf[64];
char suffix[8];
suffix[0] = 0;
for (int cnt = 1; true; cnt++) {
/* build program name composed of filename and numeric suffix */
snprintf(buf, sizeof(buf), "%s%s", filename, suffix);
/* if such a program name does not exist yet, we are happy */
if (!_child_name_exists(buf)) {
strncpy(dst, buf, dst_len);
return;
}
/* increase number of suffix */
snprintf(suffix, sizeof(suffix), ".%d", cnt + 1);
}
}
/**
* Process launchpad XML configuration
*/
void Launchpad::process_config()
{
using namespace Genode;
Xml_node config_node = config()->xml_node();
/*
* Iterate through all entries of the config file and create
* launchpad entries as specified.
*/
int launcher_cnt = 0;
for (unsigned i = 0; i < config_node.num_sub_nodes(); i++) {
Xml_node node = config_node.sub_node(i);
if (node.has_type("launcher"))
/* catch XML syntax errors within launcher node */
try {
/* read file name and default quote from launcher node */
Xml_node::Attribute filename_attr = node.attribute("name");
enum { MAX_NAME_LEN = 128 };
char *filename = (char *)env()->heap()->alloc(MAX_NAME_LEN);
if (!filename) {
printf("Error: Out of memory while processing configuration\n");
return;
}
filename_attr.value(filename, MAX_NAME_LEN);
Xml_node::Attribute ram_quota_attr = node.attribute("ram_quota");
Number_of_bytes default_ram_quota = 0;
ram_quota_attr.value(&default_ram_quota);
/*
* Obtain configuration for the child
*/
Dataspace_capability config_ds;
if (node.has_sub_node("configfile")
&& node.sub_node("configfile").has_attribute("name")) {
char name[128];
node.sub_node("configfile").attribute("name").value(name, sizeof(name));
Rom_connection config_rom(name);
config_rom.on_destruction(Rom_connection::KEEP_OPEN);
config_ds = config_rom.dataspace();
}
if (node.has_sub_node("config")) {
Xml_node config_node = node.sub_node("config");
/* allocate dataspace for config */
size_t const config_size = config_node.size();
config_ds = env()->ram_session()->alloc(config_size);
/* copy configuration into new dataspace */
char * const ptr = env()->rm_session()->attach(config_ds);
Genode::memcpy(ptr, config_node.addr(), config_size);
env()->rm_session()->detach(ptr);
}
/* add launchpad entry */
add_launcher(filename, default_ram_quota, config_ds);
launcher_cnt++;
} catch (...) {
printf("Warning: Launcher entry %d is malformed.\n",
launcher_cnt + 1);
}
else {
char buf[32];
node.type_name(buf, sizeof(buf));
printf("Warning: Ignoring unsupported tag <%s>.\n", buf);
}
}
}
Launchpad_child *Launchpad::start_child(const char *filename,
unsigned long ram_quota,
Genode::Dataspace_capability config_ds)
{
printf("starting %s with quota %lu\n", filename, ram_quota);
/* find unique name for new child */
char unique_name[64];
_get_unique_child_name(filename, unique_name, sizeof(unique_name));
printf("using unique child name \"%s\"\n", unique_name);
if (ram_quota > env()->ram_session()->avail()) {
PERR("Child's ram quota is higher than our available quota, using available quota");
ram_quota = env()->ram_session()->avail() - 256*1000;
}
size_t metadata_size = 4096*16 + sizeof(Launchpad_child);
if (metadata_size > ram_quota) {
PERR("Too low ram_quota to hold child metadata");
return 0;
}
ram_quota -= metadata_size;
/* lookup executable elf binary */
Dataspace_capability file_cap;
Rom_session_capability rom_cap;
try {
/*
* When creating a ROM connection for a non-existing file, the
* constructor of 'Rom_connection' throws a 'Parent::Service_denied'
* exception.
*/
Rom_connection rom(filename, unique_name);
rom.on_destruction(Rom_connection::KEEP_OPEN);
rom_cap = rom.cap();
file_cap = rom.dataspace();
} catch (...) {
printf("Error: Could not access file \"%s\" from ROM service.\n", filename);
return 0;
}
/* create ram session for child with some of our own quota */
Ram_connection ram;
ram.on_destruction(Ram_connection::KEEP_OPEN);
ram.ref_account(env()->ram_session_cap());
env()->ram_session()->transfer_quota(ram.cap(), ram_quota);
/* create cpu session for child */
Cpu_connection cpu(unique_name);
cpu.on_destruction(Cpu_connection::KEEP_OPEN);
if (!ram.cap().valid() || !cpu.cap().valid()) {
if (ram.cap().valid()) {
PWRN("Failed to create CPU session");
env()->parent()->close(ram.cap());
}
if (cpu.cap().valid()) {
PWRN("Failed to create RAM session");
env()->parent()->close(cpu.cap());
}
env()->parent()->close(rom_cap);
PERR("Our quota is %zd", env()->ram_session()->quota());
return 0;
}
Rm_connection rm;
rm.on_destruction(Rm_connection::KEEP_OPEN);
if (!rm.cap().valid()) {
PWRN("Failed to create RM session");
env()->parent()->close(ram.cap());
env()->parent()->close(cpu.cap());
env()->parent()->close(rom_cap);
return 0;
}
try {
Launchpad_child *c = new (&_sliced_heap)
Launchpad_child(unique_name, file_cap, ram.cap(),
cpu.cap(), rm.cap(), rom_cap,
&_cap_session, &_parent_services, &_child_services,
config_ds, this);
Lock::Guard lock_guard(_children_lock);
_children.insert(c);
add_child(unique_name, ram_quota, c, c->heap());
return c;
} catch (Cpu_session::Thread_creation_failed) {
PWRN("Failed to create child - Cpu_session::Thread_creation_failed");
} catch (...) {
PWRN("Failed to create child - unknown reason");
}
env()->parent()->close(rm.cap());
env()->parent()->close(ram.cap());
env()->parent()->close(cpu.cap());
env()->parent()->close(rom_cap);
return 0;
}
/**
* Watchdog-guarded child destruction mechanism
*
* During the destruction of a child, all sessions of the child are getting
* closed. A server, however, may refuse to answer a close call. We detect
* this case using a watchdog mechanism, unblock the 'close' call, and
* proceed with the closing the other remaining sessions.
*/
class Child_destructor_thread : Thread<2*4096>
{
private:
Launchpad_child *_curr_child; /* currently destructed child */
Allocator *_curr_alloc; /* child object'sallocator */
Lock _submit_lock; /* only one submission at a time */
Lock _activate_lock; /* submission protocol */
bool _ready; /* set if submission is completed */
int _watchdog_cnt; /* watchdog counter in milliseconds */
/**
* Thread entry function
*/
void entry() {
while (true) {
/* wait for next submission */
_activate_lock.lock();
/*
* Eventually long-taking operation that involves the
* closing of all session of the child. This procedure
* may need blocking cancellation to proceed in the
* case servers are unresponsive.
*/
try {
destroy(_curr_alloc, _curr_child);
} catch (Blocking_canceled) {
PERR("Suspicious cancellation");
}
_ready = true;
}
}
public:
/*
* Watchdog timer granularity in milliseconds. This value defined
* after how many milliseconds the watchdog is activated.
*/
enum { WATCHDOG_GRANULARITY_MS = 10 };
/**
* Constructor
*/
Child_destructor_thread() :
Thread("child_destructor"),
_curr_child(0), _curr_alloc(0),
_activate_lock(Lock::LOCKED),
_ready(true)
{
start();
}
/**
* Destruct child, coping with unresponsive servers
*
* \param alloc Child object's allocator
* \param child Child to destruct
* \param timeout_ms Maximum destruction time until the destructing
* thread gets waken up to give up the close call to
* an unreponsive server.
*/
void submit_for_destruction(Allocator *alloc, Launchpad_child *child,
Timer::Session *timer, int timeout_ms)
{
/* block until destructor thread is ready for new submission */
Lock::Guard _lock_guard(_submit_lock);
/* register submission values */
_curr_child = child;
_curr_alloc = alloc;
_ready = false;
_watchdog_cnt = 0;
/* wake up the destruction thread */
_activate_lock.unlock();
/*
* Now, the destruction thread attempts to close all the
* child's sessions. Check '_ready' flag periodically.
*/
while (!_ready) {
/* give the destruction thread some time to proceed */
timer->msleep(WATCHDOG_GRANULARITY_MS);
_watchdog_cnt += WATCHDOG_GRANULARITY_MS;
/* check if we reached the timeout */
if (_watchdog_cnt > timeout_ms) {
/*
* The destruction seems to got stuck, let's shake it a
* bit to proceed and reset the watchdog counter to give
* the next blocking operation a chance to execute.
*/
cancel_blocking();
_watchdog_cnt = 0;
}
}
}
};
/**
* Construct a timer session for the watchdog timer on demand
*/
static Timer::Session *timer_session()
{
static Timer::Connection timer;
return &timer;
}
/* construct child-destructor thread early - in case we run out of threads */
static Child_destructor_thread child_destructor;
/**
* Destruct Launchpad_child, cope with infinitely blocking server->close calls
*
* The arguments correspond to the 'Child_destructor_thread::submit_for_destruction'
* function.
*/
static void destruct_child(Allocator *alloc, Launchpad_child *child,
Timer::Session *timer, int timeout)
{
/* if no timer session was provided by our caller, we have create one */
if (!timer)
timer = timer_session();
child_destructor.submit_for_destruction(alloc, child, timer, timeout);
}
void Launchpad::exit_child(Launchpad_child *child,
Timer::Session *timer,
int session_close_timeout_ms)
{
remove_child(child->name(), child->heap());
Lock::Guard lock_guard(_children_lock);
_children.remove(child);
Rm_session_capability rm_session_cap = child->rm_session_cap();
Ram_session_capability ram_session_cap = child->ram_session_cap();
Cpu_session_capability cpu_session_cap = child->cpu_session_cap();
Rom_session_capability rom_session_cap = child->rom_session_cap();
const Genode::Server *server = child->server();
destruct_child(&_sliced_heap, child, timer, session_close_timeout_ms);
env()->parent()->close(rm_session_cap);
env()->parent()->close(cpu_session_cap);
env()->parent()->close(rom_session_cap);
env()->parent()->close(ram_session_cap);
/*
* The killed child may have provided services to other children.
* Since the server is dead by now, we cannot close its sessions
* in the cooperative way. Instead, we need to instruct each
* other child to forget about session associated with the dead
* server. Note that the 'child' pointer points a a no-more
* existing object. It is only used to identify the corresponding
* session. It must never by de-referenced!
*/
Launchpad_child *c = _children.first();
for ( ; c; c = c->Genode::List<Launchpad_child>::Element::next())
c->revoke_server(server);
}