Added ability to dynamically start properly managed cells by issuing an RPC to Hoitaja.

This commit is contained in:
Michael Mueller
2025-04-17 15:48:18 +02:00
parent baa37c03d4
commit 1b76283593
14 changed files with 479 additions and 82 deletions

View File

@@ -0,0 +1,21 @@
#ifndef __EALANOS__INCLUDE__LAUNCHER__CLIENT_H_
#define __EALANOS__INCLUDE__LAUNCHER__CLIENT_H_
#include <base/rpc_client.h>
#include <ealanos/laucher/session.h>
namespace Ealan {
struct Launcher_client;
using Launcher_capability = Genode::Capability<Ealan::Launcher_session>;
}
struct Ealan::Launcher_client : Genode::Rpc_client<Ealan::Launcher_session>
{
explicit Launcher_client(Launcher_capability session) : Rpc_client<Launcher_session>(session) {}
void launch(Genode::String<640> start_node) override {
call<Rpc_launch>(start_node);
}
};
#endif // __EALANOS__INCLUDE__LAUNCHER__CLIENT_H_

View File

@@ -0,0 +1,37 @@
#ifndef __EALANOS__INCLUDE__LAUNCHER__COMPONENT_H_
#define __EALANOS__INCLUDE__LAUNCHER__COMPONENT_H_
#include <base/rpc_server.h>
#include <base/env.h>
#include <base/session_label.h>
#include <base/log.h>
#include <base/attached_rom_dataspace.h>
#include <util/xml_generator.h>
#include <root/component.h>
#include <base/session_object.h>
#include <sandbox/sandbox.h>
#include <ealanos/laucher/session.h>
namespace Ealan {
class Launcher_session_component;
class Launcher_root;
class Hoitaja;
}
class Ealan::Launcher_session_component : public Genode::Session_object<Ealan::Launcher_session>
{
private:
Genode::Entrypoint &_ep;
Ealan::Hoitaja &_hoitaja;
public:
template <typename... ARGS>
Launcher_session_component(Hoitaja &hoitaja, Genode::Entrypoint &ep, Genode::Session::Resources const &res, ARGS &&...args)
: Session_object(ep, res, args...), _ep(ep), _hoitaja(hoitaja) { Genode::log("Creating new launcher session"); }
void launch(Genode::String<640> start_node) override;
};
#endif // __EALANOS__INCLUDE__LAUNCHER__COMPONENT_H_

View File

@@ -0,0 +1,25 @@
#ifndef __EALANOS__INCLUDE__LAUNCHER__CONNECTION_H_
#define __EALANOS__INCLUDE__LAUNCHER__CONNECTION_H_
#include <base/rpc_args.h>
#include <base/connection.h>
#include <session/session.h>
#include <ealanos/laucher/client.h>
namespace Ealan {
struct Launcher_connection;
}
struct Ealan::Launcher_connection : Genode::Connection<Ealan::Launcher_session>, Launcher_client
{
Launcher_connection(Genode::Env &env, Genode::Affinity &affinity, Label const &label = Label())
: Connection<Launcher_session>(env, label, Genode::Ram_quota { RAM_QUOTA }, affinity, Args("")), Launcher_client(cap()) {}
void launch(Genode::String<640> start_node) override
{
Launcher_client::launch(start_node);
}
};
#endif // __EALANOS__INCLUDE__LAUNCHER__CONNECTION_H_

View File

@@ -0,0 +1,28 @@
#ifndef __EALANOS__INCLUDE__LAUNCHER__SESSION_H_
#define __EALANOS__INCLUDE__LAUNCHER__SESSION_H_
#include <base/rpc_args.h>
#include <session/session.h>
#include <util/xml_node.h>
namespace Ealan {
struct Launcher_session;
}
struct Ealan::Launcher_session : Genode::Session
{
static const char *service_name() { return "Launcher"; }
enum
{
CAP_QUOTA = 1,
RAM_QUOTA = 1024
};
virtual void launch(Genode::String<640> start_node) = 0;
GENODE_RPC(Rpc_launch, void, launch, Genode::String<640>);
GENODE_RPC_INTERFACE(Rpc_launch);
};
#endif // __EALANOS__INCLUDE__LAUNCHER__SESSION_H_

View File

@@ -26,6 +26,8 @@ namespace Sandbox {
class Child;
}
namespace Ealan { class Hoitaja; }
class Genode::Sandbox : Noncopyable
{
public:
@@ -81,6 +83,7 @@ class Genode::Sandbox : Noncopyable
private:
friend class Local_service_base;
friend class Ealan::Hoitaja;
Heap _heap;
@@ -112,7 +115,7 @@ class Genode::Sandbox : Noncopyable
*/
void generate_state_report(Xml_generator &) const;
void update(::Sandbox::Child &child);
Xml_node* update(::Sandbox::Child &child, Xml_node *config);
};
class Genode::Sandbox::Local_service_base : public Service

View File

@@ -0,0 +1,79 @@
set build_components {
core init hoitaja timer lib/ld lib/libm lib/libc lib/stdcxx lib/vfs app/launcher_test app/allocating_cell
}
build $build_components
create_boot_directory
install_config {
<config prio_levels="32" verbose="true">
<parent-provides>
<service name="LOG"/>
<service name="PD"/>
<service name="CPU"/>
<service name="ROM"/>
<service name="RAM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="CAP"/>
<service name="RM"/>
<service name="SIGNAL"/>
<service name="TRACE"/>
</parent-provides>
<default-route>
<any-service><parent/><any-child/></any-service>
</default-route>
<default caps="2000"/>
<affinity-space width="64" height="1"/>
<start name="timer">
<resource name="RAM" quantum="3M"/>
<provides><service name="Timer"/></provides>
<route>
<any-service><parent/><any-child/></any-service>
</route>
</start>
<start name="cell1" priority="-1">
<binary name="launcher"/>
<resource name="RAM" quantum="8M"/>
<config>
<vfs> <dir name="dev">
<log/>
<inline name="rtc">2022-07-20 14:30</inline>
</dir>
</vfs>
<libc stdout="/dev/log" stderr="/dev/log" rtc="/dev/rtc"/>
</config>
<route>
<service name="Launcher"><local/></service>
<any-service><parent/><any-child/></any-service>
</route>
</start>
<!--
<start name="cell2" priority="-2">
<binary name="empty_cell"/>
<resource name="RAM" quantum="3M"/>
</start>
-->
</config>
}
build_boot_image [build_artifacts]
append qemu_args " -nographic "
set succeeded 0
set failed 0
for {set i 0} { $i < 3000 } { incr i } {
if { ! [catch {run_genode_until ".*Changing cell .* affinity to .*0,0,32.*" 120} result ] } {
incr succeeded
puts "\n $succeeded of 3000 succeeded"
kill_spawned $qemu_spawn_id
} else {
incr failed
kill_spawned $qemu_spawn_id
}
}
puts "\n $succeeded of $i succeeded. $failed of $i failed."

View File

@@ -0,0 +1,65 @@
#include <libc/component.h>
#include <base/log.h>
#include <ealanos/laucher/connection.h>
#include <base/thread.h>
#include <base/env.h>
#include <timer_session/connection.h>
#define Cell(name) static const char *name = \
"<start name=\"" #name "\" priority=\"-1\">\n" \
" <binary name=\"allocating_cell\"/>\n" \
" <resource name=\"RAM\" quantum=\"128M\"/>\n" \
" <config>\n" \
" <vfs> <dir name=\"dev\">\n" \
" <log/>\n" \
" <inline name=\"rtc\">2022-07-20 14:30</inline>\n" \
" </dir>\n" \
" </vfs>\n" \
" <libc stdout=\"/dev/log\" stderr=\"/dev/log\" rtc=\"/dev/rtc\"/>\n" \
" </config>\n" \
"</start>\n";
Cell(allocator1);
Cell(allocator2);
Cell(allocator3);
Cell(allocator4);
Cell(allocator5);
Cell(allocator6);
Cell(allocator7);
Cell(allocator8);
static const char *cells[8] = {allocator1, allocator2, allocator3, allocator4, allocator5, allocator6, allocator7, allocator8};
void Libc::Component::construct(Libc::Env &env)
{
Genode::Thread *myself = Genode::Thread::myself();
Genode::Affinity::Location loc = myself->affinity();
Genode::Affinity affinity(env.cpu().affinity_space(), loc);
Timer::Connection _timer{env};
Libc::with_libc([&]()
{
Ealan::Launcher_connection _launcher{env, affinity};
for (int i = 0; i < 8; i++) {
bool repeat = false;
do
{
try {
Genode::Xml_node xml(cells[i]);
_launcher.launch(xml);
} catch (Genode::Xml_attribute::Invalid_syntax) {
Genode::error("Invalid XML start node");
} catch (Genode::Ipc_error) {
repeat = true;
}
} while (repeat);
//_timer.msleep(1000);
}
Genode::log("Launched dummy"); });
while(true)
;
}

View File

@@ -0,0 +1,7 @@
SRC_CC += launcher
TARGET = launcher
INC_DIR += $(call select_from_repositories,src/lib/libc)/spec/x86_64
INC_DIR += $(call select_from_repositories,src/lib/libc)
LIBS = base libc stdcxx

View File

@@ -19,110 +19,193 @@
#include <base/log.h>
#include <tukija/syscall-generic.h>
#include <child.h>
namespace Init {
#include <ealanos/laucher/component.h>
namespace Ealan {
using namespace Genode;
struct Main;
struct Hoitaja;
}
struct Init::Main : Genode::Sandbox::State_handler
class Ealan::Hoitaja : Genode::Sandbox::State_handler, Genode::Sandbox::Local_service_base::Wakeup
{
Env &_env;
private:
Env &_env;
Genode::Sandbox _sandbox { _env, *this };
Genode::Sandbox _sandbox { _env, *this };
Attached_rom_dataspace _config { _env, "config" };
Attached_rom_dataspace _config { _env, "config" };
void _handle_resource_avail() { }
Genode::Xml_node *_habitat_config{nullptr};
Signal_handler<Main> _resource_avail_handler {
_env.ep(), *this, &Main::_handle_resource_avail };
char *_config_dataspace{nullptr};
Constructible<Reporter> _reporter { };
void _handle_resource_avail() { }
size_t _report_buffer_size = 0;
Signal_handler<Hoitaja> _resource_avail_handler {
_env.ep(), *this, &Hoitaja::_handle_resource_avail };
void _handle_config()
{
_config.update();
Constructible<Reporter> _reporter { };
Xml_node const config = _config.xml();
Genode::Rpc_entrypoint _ep{&_env.pd(), 4096, "launcher_ep", Genode::Affinity::Location(0, 0, 1, 1)};
bool reporter_enabled = false;
config.with_optional_sub_node("report", [&] (Xml_node report) {
Genode::Entrypoint _launcher_ep{_env, 4*4096, "launcher", Genode::Affinity::Location()};
reporter_enabled = true;
Genode::Sliced_heap _md_alloc{_env.ram(), _env.rm()};
/* (re-)construct reporter whenever the buffer size is changed */
Number_of_bytes const buffer_size =
report.attribute_value("buffer", Number_of_bytes(4096));
Genode::Heap _heap{_env.ram(), _env.rm()};
if (buffer_size != _report_buffer_size || !_reporter.constructed()) {
_report_buffer_size = buffer_size;
_reporter.construct(_env, "state", "state", _report_buffer_size);
using Launcher_service = Genode::Sandbox::Local_service<Ealan::Launcher_session_component>;
Launcher_service _launcher_service{_sandbox, *this};
size_t _report_buffer_size = 0;
void _handle_config()
{
_config.update();
if (_habitat_config) {
delete _habitat_config;
delete _config_dataspace;
}
});
if (_reporter.constructed())
_reporter->enabled(reporter_enabled);
_config_dataspace = static_cast<char*>(_heap.alloc(_config.size()*32));
Genode::memcpy(_config_dataspace, _config.local_addr<char*>(), _config.size());
_habitat_config = new (_sandbox._heap) Genode::Xml_node(_config_dataspace);
_sandbox.apply_config(config);
}
Xml_node const config = *_habitat_config;
Signal_handler<Main> _config_handler {
_env.ep(), *this, &Main::_handle_config };
bool reporter_enabled = false;
config.with_optional_sub_node("report", [&] (Xml_node report) {
/**
* Sandbox::State_handler interface
*/
void handle_sandbox_state() override
{
try {
Reporter::Xml_generator xml(*_reporter, [&] () {
_sandbox.generate_state_report(xml); });
reporter_enabled = true;
/* (re-)construct reporter whenever the buffer size is changed */
Number_of_bytes const buffer_size =
report.attribute_value("buffer", Number_of_bytes(4096));
if (buffer_size != _report_buffer_size || !_reporter.constructed()) {
_report_buffer_size = buffer_size;
_reporter.construct(_env, "state", "state", _report_buffer_size);
}
});
if (_reporter.constructed())
_reporter->enabled(reporter_enabled);
_sandbox.apply_config(config);
}
catch (Xml_generator::Buffer_exceeded) {
error("state report exceeds maximum size");
Signal_handler<Hoitaja> _config_handler {
_env.ep(), *this, &Hoitaja::_handle_config };
/* try to reflect the error condition as state report */
try {
Hoitaja(const Hoitaja &);
Hoitaja &operator=(const Hoitaja &);
public:
/**
* Sandbox::State_handler interface
*/
void handle_sandbox_state() override
{
Genode::log("Sandbox state changed");
try
{
Reporter::Xml_generator xml(*_reporter, [&] () {
xml.attribute("error", "report buffer exceeded"); });
_sandbox.generate_state_report(xml); });
}
catch (Xml_generator::Buffer_exceeded) {
error("state report exceeds maximum size");
/* try to reflect the error condition as state report */
try {
Reporter::Xml_generator xml(*_reporter, [&] () {
xml.attribute("error", "report buffer exceeded"); });
}
catch (...) { }
}
catch (...) { }
}
}
void handle_child_state(::Sandbox::Child &child) override {
try {
Genode::log("Updating sandbox state");
_sandbox.update(child);
} catch (Genode::Quota_guard<Genode::Cap_quota>::Limit_exceeded) {
Genode::log("Caps exceeded while handling child state");
_env.parent().exit(1);
void handle_child_state(::Sandbox::Child &child) override {
bool repeat = false;
do
{
try {
Genode::log("Updating state of child ", child.name());
_habitat_config = _sandbox.update(child, _habitat_config);
Genode::log("Updated config length:", _habitat_config->content_size());
}
catch (Genode::Quota_guard<Genode::Cap_quota>::Limit_exceeded)
{
Genode::log("Caps exceeded while handling child state");
_env.parent().exit(1);
}
catch (Genode::Ipc_error)
{
Genode::error("Failed to update child state for <", child.name(), ">");
repeat = true;
}
} while (repeat);
}
}
Main(Env &env) : _env(env)
{
_config.sigh(_config_handler);
void wakeup_local_service() override {
_launcher_service.for_each_requested_session([&](Launcher_service::Request &req)
{ req.deliver_session(*new (_md_alloc) Ealan::Launcher_session_component(*this, _env.ep() , req.resources, "", req.diag)); });
}
Genode::log("Hoitaja starting ...");
void add_cell_from_xml(const char *start_node){
char *dest = nullptr;
/* prevent init to block for resource upgrades (never satisfied by core) */
_env.parent().resource_avail_sigh(_resource_avail_handler);
_habitat_config->with_raw_content([&](char const *content, Genode::size_t)
{ dest = const_cast<char*>(content); });
dest += _habitat_config->content_size();
Tukija::Tip const *tip = Tukija::Tip::tip();
Genode::log("Found topology model of size ", tip->length, " at ", static_cast<const void *>(tip));
Genode::memcpy(dest, start_node, Genode::strlen(start_node));
dest += Genode::strlen(start_node);
Genode::memcpy(dest, "</config>", sizeof("</config>"));
_handle_config();
}
_sandbox._heap.free(_habitat_config, sizeof(Xml_node));
_habitat_config = new (_sandbox._heap) Xml_node(_config_dataspace);
bool repeat = false;
do {
try {
_sandbox.apply_config(*_habitat_config);
} catch (Genode::Ipc_error) {
Genode::error("IPC error while creating cell. Retrying.");
repeat = true;
}
} while (repeat);
}
Hoitaja(Env &env) : _env(env)
{
_config.sigh(_config_handler);
Genode::log("Hoitaja starting ...");
/* prevent init to block for resource upgrades (never satisfied by core) */
_env.parent().resource_avail_sigh(_resource_avail_handler);
Tukija::Tip const *tip = Tukija::Tip::tip();
Genode::log("Found topology model of size ", tip->length, " at ", static_cast<const void *>(tip));
_handle_config();
}
};
void Ealan::Launcher_session_component::launch(Genode::String<640> start_node)
{
_hoitaja.add_cell_from_xml(start_node.string());
}
void Component::construct(Genode::Env &env) { static Init::Main main(env); }
void Component::construct(Genode::Env &env) { static Ealan::Hoitaja main(env); }

View File

@@ -779,7 +779,7 @@ Sandbox::Child::Child(Env &env,
_local_services(local_services),
_session_requester(_env.ep().rpc_ep(), _env.ram(), _env.rm()),
_habitat(habitat){
log("Creating new cell <", _unique_name, ">");
//log("Creating new cell <", _unique_name, ">");
if (_verbose.enabled()) {
log("child \"", _unique_name, "\"");
log(" RAM quota: ", _resources.effective_ram_quota());

View File

@@ -739,12 +739,12 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
struct Resources &resources() { return _resources; }
void update(Genode::Affinity affinity) {
Genode::log("Updating affinity to ", affinity.location(), " in space ", affinity.space());
//Genode::log("Updating affinity to ", affinity.location(), " in space ", affinity.space());
_resources.affinity = affinity;
//Genode::log("Moving CPU session ", _env.cpu_session_cap());
if (_child.active()) {
Ealan::Cell_client cell(_cell_cap);
Genode::log("Updating cell ", _cell_cap);
//Genode::log("Updating cell ", _cell_cap);
cell.update(affinity);
}
}

View File

@@ -257,6 +257,7 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer,
*/
Parent_service &create_parent_service(Service::Name const &name) override
{
Genode::log("Creating parent service ", name);
return *new (_heap) Parent_service(_parent_services, _env, name);
}
@@ -309,13 +310,56 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer,
void maintain_cells();
void update(Child &child) {
Genode::Xml_node* update(Child &child, Genode::Xml_node *config) {
if (child.exited()) {
_children.remove(&child);
_core_allocator->free_cores_from_cell(child);
Genode::log("Starting new maintenance cycle");
/* Remove child from config */
try {
/* Find XML node for the child */
Xml_node node = config->sub_node("start");
while (node.attribute_value<Genode::Child_policy::Name>("name", Genode::Child_policy::Name()) != child.name())
{
node = node.next("start");
}
/* Get pointer to start of node in its parent's buffer and its length */
char *node_ptr = nullptr;
Genode::size_t len = 0;
node.with_raw_node([&](char const *ptr, Genode::size_t size)
{ node_ptr = const_cast<char*>(ptr); len = size; });
char *node_end = node_ptr + len;
/* Determine start and end address of config's buffer */
Genode::size_t config_len = config->size();
char *config_ptr = nullptr;
config->with_raw_node([&](char const *ptr, Genode::size_t)
{ config_ptr = const_cast<char*>(ptr); });
char *config_end = config_ptr + config_len;
/* Determine length of the tail after the child's start node */
Genode::size_t tail_len = static_cast<Genode::size_t>(config_end - node_end);
/* Remove child's start node with memmove */
Genode::memmove(node_ptr-1, node_end, tail_len);
*(node_ptr + tail_len) = '\0';
/* Reparse changed buffer */
Xml_node *new_config = new (_heap) Xml_node(config_ptr, config_len - len);
_heap.free(config, sizeof(Xml_node));
config = new_config;
}
catch (Genode::Xml_node::Nonexistent_sub_node)
{
Genode::error("Could not find child's start node");
return config;
};
Genode::log("Removed child ", child.name());
apply_config(*config);
maintain_cells();
}
return config;
}
};
@@ -504,7 +548,7 @@ void Genode::Sandbox::Library::apply_config(Xml_node const &config)
_children.for_each_child([&] (Child &child) {
if (child.abandoned())
if (child.abandoned())
return;
if (child.restart_scheduled()) {
@@ -557,7 +601,6 @@ void Genode::Sandbox::Library::maintain_cells()
int lower_limit = _affinity_space->total() - _core_allocator->cores_available();
_children.for_each_child([&](Child &child)
{
log(child.name(), " ram: ", child.ram_quota());
if (!(child.is_brick()))
_core_allocator->update(child, &xpos, &lower_limit); });
}
@@ -688,8 +731,9 @@ Genode::Sandbox::Local_service_base::Local_service_base(Sandbox &sandbox,
_session_factory(sandbox._heap, Session_state::Factory::Batch_size{16}),
_async_wakeup(wakeup),
_async_service(name, _server_id_space, _session_factory, _async_wakeup)
{ }
{
Genode::log("Adding local service ", name);
}
/*************
** Sandbox **
@@ -706,9 +750,9 @@ void Genode::Sandbox::generate_state_report(Xml_generator &xml) const
_library.generate_state_report(xml);
}
void Genode::Sandbox::update(::Sandbox::Child &child)
Genode::Xml_node* Genode::Sandbox::update(::Sandbox::Child &child, Genode::Xml_node *config)
{
_library.update(child);
return _library.update(child, config);
}
Genode::Sandbox::Sandbox(Env &env, State_handler &state_handler, Pd_intrinsics &pd_intrinsics)

View File

@@ -145,19 +145,23 @@ namespace Sandbox {
FILTER_FN const &filter_fn)
{
T *service = nullptr;
services.for_each([&] (T &s) {
services.for_each([&](T &s)
{
if (service || s.name() != name || filter_fn(s))
return;
service = &s;
});
service = &s; });
if (!service)
if (!service) {
Genode::log("Service ", name, " not found");
throw Service_denied();
}
if (service->abandoned())
{
Genode::log("Service ", name, " abandoned.");
throw Service_denied();
}
return *service;
}

View File

@@ -7,6 +7,7 @@
<xs:choice minOccurs="1" maxOccurs="3">
<xs:element name="parent"/>
<xs:element name="any-child"/>
<xs:element name="local"/>
<xs:element name="child">
<xs:complexType>
<xs:attribute name="name" type="xs:string" />