/* * \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 #include #include #include #include #include #include #include #include #include #include #include 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::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::Element::next()) c->revoke_server(server); }