genode/repos/os/include/init/child.h
Norman Feske cfdbccc5c2 Remove blocking calls from root and parent RPCs
This is a redesign of the root and parent interfaces to eliminate
blocking RPC calls.

- New session representation at the parent (base/session_state.h)
- base-internal root proxy mechanism as migration path
- Redesign of base/service.h
- Removes ancient 'Connection::KEEP_OPEN' feature
- Interface change of 'Child', 'Child_policy', 'Slave', 'Slave_policy'
- New 'Slave::Connection'
- Changed child-construction procedure to be compatible with the
  non-blocking parent interface and to be easier to use
- The child's initial LOG session, its binary ROM session, and the
  linker ROM session have become part of the child's envirenment.
- Session upgrading must now be performed via 'env.upgrade' instead
  of performing a sole RPC call the parent. To make RAM upgrades
  easier, the 'Connection' provides a new 'upgrade_ram' method.

Issue #2120
2016-11-25 16:06:42 +01:00

856 lines
26 KiB
C++

/*
* \brief Representation used for children of the init process
* \author Norman Feske
* \date 2010-05-04
*/
/*
* Copyright (C) 2010-2016 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _INCLUDE__INIT__CHILD_H_
#define _INCLUDE__INIT__CHILD_H_
/* Genode includes */
#include <pd_session/connection.h>
#include <ram_session/connection.h>
#include <cpu_session/connection.h>
#include <cap_session/connection.h>
#include <base/log.h>
#include <base/child.h>
#include <os/session_requester.h>
#include <os/session_policy.h>
/* init includes */
#include <init/child_config.h>
#include <init/child_policy.h>
namespace Init {
class Routed_service;
class Name_registry;
class Child_registry;
class Child;
using Genode::log;
using Genode::error;
using Genode::warning;
using Genode::Session_state;
using Genode::Xml_generator;
using Genode::Parent;
using Genode::Id_space;
typedef Genode::Registered<Genode::Parent_service> Parent_service;
}
/***************
** Utilities **
***************/
namespace Init {
extern bool config_verbose;
static void warn_insuff_quota(Genode::size_t const avail)
{
if (!config_verbose) { return; }
log("Warning: Specified quota exceeds available quota.");
log(" Proceeding with a quota of ", avail, ".");
}
inline long read_priority(Genode::Xml_node start_node, long prio_levels)
{
long priority = Genode::Cpu_session::DEFAULT_PRIORITY;
try { start_node.attribute("priority").value(&priority); }
catch (...) { }
/*
* All priority declarations in the config file are
* negative because child priorities can never be higher
* than parent priorities. To simplify priority
* calculations, we use inverted values. Lower values
* correspond to higher priorities.
*/
priority = -priority;
if (priority && (priority >= prio_levels)) {
long new_prio = prio_levels ? prio_levels-1 : 0;
char name[Genode::Service::Name::capacity()];
start_node.attribute("name").value(name, sizeof(name));
Genode::warning(Genode::Cstring(name), ": invalid priority, upgrading "
"from ", -priority, " to ", -new_prio);
return new_prio;
}
return priority;
}
inline Genode::Affinity::Location
read_affinity_location(Genode::Affinity::Space const &space,
Genode::Xml_node start_node)
{
typedef Genode::Affinity::Location Location;
try {
Genode::Xml_node node = start_node.sub_node("affinity");
/* if no position value is specified, select the whole row/column */
unsigned long const
default_width = node.has_attribute("xpos") ? 1 : space.width(),
default_height = node.has_attribute("ypos") ? 1 : space.height();
unsigned long const
width = node.attribute_value<unsigned long>("width", default_width),
height = node.attribute_value<unsigned long>("height", default_height);
long const x1 = node.attribute_value<long>("xpos", 0),
y1 = node.attribute_value<long>("ypos", 0),
x2 = x1 + width - 1,
y2 = y1 + height - 1;
/* clip location to space boundary */
return Location(Genode::max(x1, 0L), Genode::max(y1, 0L),
Genode::min((unsigned)(x2 - x1 + 1), space.width()),
Genode::min((unsigned)(y2 - y1 + 1), space.height()));
}
catch (...) { return Location(0, 0, space.width(), space.height()); }
}
/**
* Return amount of RAM that is currently unused
*/
static inline Genode::size_t avail_slack_ram_quota()
{
Genode::size_t const preserve = 148*1024;
Genode::size_t const avail = Genode::env()->ram_session()->avail();
return avail > preserve ? avail - preserve : 0;
}
/**
* Return sub string of label with the leading child name stripped out
*/
inline char const *skip_label_prefix(char const *child_name, char const *label)
{
Genode::size_t const child_name_len = Genode::strlen(child_name);
/*
* If the method was called with a valid "label" string, the
* following condition should be always satisfied. See the
* comment in 'service_node_args_condition_satisfied'.
*/
if (Genode::strcmp(child_name, label, child_name_len) == 0)
label += child_name_len;
/*
* If the original label was empty, the 'Child_policy_enforce_labeling'
* does not append a label separator after the child-name prefix. In
* this case, we resulting label is empty.
*/
if (*label == 0)
return label;
/*
* Skip label separator. This condition should be always satisfied.
*/
if (Genode::strcmp(" -> ", label, 4) == 0)
return label + 4;
Genode::warning("cannot skip label prefix while processing <if-arg>");
return label;
}
/**
* Return true if service XML node matches service request
*
* \param args session arguments, inspected for the session label
* \param child_name name of the originator of the session request
* \param service_name name of the requested service
*/
inline bool service_node_matches(Genode::Xml_node service_node, char const *args,
Genode::Child_policy::Name const &child_name,
Genode::Service::Name const &service_name)
{
bool const service_matches =
service_node.has_type("any-service") ||
(service_node.has_type("service") &&
service_node.attribute("name").has_value(service_name.string()));
if (!service_matches)
return false;
Genode::Session_label const session_label(skip_label_prefix(
child_name.string(), Genode::label_from_args(args).string()));
return !Genode::Xml_node_label_score(service_node, session_label).conflict();
}
/**
* Check if arguments satisfy the condition specified for the route
*/
inline bool service_node_args_condition_satisfied(Genode::Xml_node service_node,
Genode::Session_state::Args const &args,
Genode::Child_policy::Name const &child_name)
{
try {
Genode::Xml_node if_arg = service_node.sub_node("if-arg");
enum { KEY_MAX_LEN = 64, VALUE_MAX_LEN = 64 };
char key[KEY_MAX_LEN];
char value[VALUE_MAX_LEN];
if_arg.attribute("key").value(key, sizeof(key));
if_arg.attribute("value").value(value, sizeof(value));
char arg_value[VALUE_MAX_LEN];
Genode::Arg_string::find_arg(args.string(), key).string(arg_value, sizeof(arg_value), "");
/*
* Skip child-name prefix if the key is the process "label".
*
* Because 'filter_session_args' is called prior the call of
* 'resolve_session_request' from the 'Child::session' method,
* 'args' contains the filtered arguments, in particular the label
* prefixed with the child's name. For the 'if-args' declaration,
* however, we want to omit specifying this prefix because the
* session route is specific to the named start node anyway. So
* the prefix information is redundant.
*/
if (Genode::strcmp("label", key) == 0)
return Genode::strcmp(value, skip_label_prefix(child_name.string(), arg_value)) == 0;
return Genode::strcmp(value, arg_value) == 0;
} catch (...) { }
/* if no if-arg node exists, the condition is met */
return true;
}
/**
* Check if service name is ambiguous
*
* \return true if the same service is provided multiple
* times
*
* \deprecated
*/
template <typename T>
inline bool is_ambiguous(Genode::Registry<T> const &services,
Service::Name const &name)
{
/* count number of services with the specified name */
unsigned cnt = 0;
services.for_each([&] (T const &service) {
cnt += (service.name() == name); });
return cnt > 1;
}
template <typename T>
inline Genode::Service *find_service(Genode::Registry<T> &services,
Genode::Service::Name const &name)
{
T *service = nullptr;
services.for_each([&] (T &s) {
if (!service && (s.name() == name))
service = &s; });
return service;
}
}
/**
* Init-specific representation of a child service
*/
class Init::Routed_service : public Genode::Child_service
{
public:
typedef Genode::Child_policy::Name Child_name;
private:
Child_name _child_name;
Genode::Registry<Routed_service>::Element _registry_element;
public:
/**
* Constructor
*
* \param services registry of all services provides by children
* \param child_name child name of server, used for session routing
*
* The other arguments correspond to the arguments of 'Child_service'.
*/
Routed_service(Genode::Registry<Routed_service> &services,
Child_name const &child_name,
Id_space<Parent::Server> &server_id_space,
Session_state::Factory &factory,
Service::Name const &name,
Genode::Ram_session_capability ram,
Child_service::Wakeup &wakeup)
:
Child_service(server_id_space, factory, name, ram, wakeup),
_child_name(child_name), _registry_element(services, *this)
{ }
Child_name const &child_name() const { return _child_name; }
};
/**
* Interface for name database
*/
struct Init::Name_registry
{
virtual ~Name_registry() { }
typedef Genode::Child_policy::Name Name;
/**
* Check if specified name is unique
*
* \return false if name already exists
*/
virtual bool unique(const char *name) const = 0;
/**
* Return child name for a given alias name
*
* If there is no alias, the function returns the original name.
*/
virtual Name deref_alias(Name const &) = 0;
};
class Init::Child : Genode::Child_policy,
Genode::Child_service::Wakeup
{
public:
/**
* Exception type
*/
class Child_name_is_not_unique { };
private:
friend class Child_registry;
Genode::Env &_env;
Genode::List_element<Child> _list_element;
Genode::Xml_node _start_node;
Genode::Xml_node _default_route_node;
Name_registry &_name_registry;
/**
* Unique child name and file name of ELF binary
*/
struct Name
{
enum { MAX_NAME_LEN = 64 };
char file[MAX_NAME_LEN];
char unique[MAX_NAME_LEN];
/**
* Constructor
*
* Obtains file name and unique process name from XML node
*
* \param start_node XML start node
* \param registry registry tracking unique names
*/
Name(Genode::Xml_node start_node, Name_registry const &registry) {
try {
start_node.attribute("name").value(unique, sizeof(unique)); }
catch (Genode::Xml_node::Nonexistent_attribute) {
Genode::warning("missing 'name' attribute in '<start>' entry");
throw; }
/* check for a name confict with the other children */
if (!registry.unique(unique)) {
Genode::error("child name \"", Genode::Cstring(unique), "\" "
"is not unique");
throw Child_name_is_not_unique();
}
/* use name as default file name if not declared otherwise */
Genode::strncpy(file, unique, sizeof(file));
/* check for a binary declaration */
try {
Genode::Xml_node binary = start_node.sub_node("binary");
binary.attribute("name").value(file, sizeof(file));
} catch (...) { }
}
} _name;
struct Read_quota
{
Read_quota(Genode::Xml_node start_node,
Genode::size_t & ram_quota,
Genode::size_t & cpu_quota_pc,
bool & constrain_phys)
{
cpu_quota_pc = 0;
constrain_phys = false;
Genode::Number_of_bytes ram_bytes = 0;
try {
Genode::Xml_node rsc = start_node.sub_node("resource");
for (;; rsc = rsc.next("resource")) {
try {
if (rsc.attribute("name").has_value("RAM")) {
rsc.attribute("quantum").value(&ram_bytes);
constrain_phys = rsc.attribute_value("constrain_phys", false);
} else if (rsc.attribute("name").has_value("CPU")) {
rsc.attribute("quantum").value(&cpu_quota_pc); }
} catch (...) { }
}
} catch (...) { }
ram_quota = ram_bytes;
/*
* If the configured RAM quota exceeds our own quota, we donate
* all remaining quota to the child but we need to count in
* our allocation of the child meta data from the heap.
* Hence, we preserve some of our own quota.
*/
Genode::size_t const ram_avail = avail_slack_ram_quota();
if (ram_quota > ram_avail) {
ram_quota = ram_avail;
warn_insuff_quota(ram_avail);
}
}
};
/**
* Resources assigned to the child
*/
struct Resources : Read_quota
{
long prio_levels_log2;
long priority;
Genode::Affinity affinity;
Genode::size_t ram_quota;
Genode::size_t cpu_quota_pc;
bool constrain_phys;
Resources(Genode::Xml_node start_node, const char *label,
long prio_levels,
Genode::Affinity::Space const &affinity_space)
:
Read_quota(start_node, ram_quota, cpu_quota_pc, constrain_phys),
prio_levels_log2(Genode::log2(prio_levels)),
priority(read_priority(start_node, prio_levels)),
affinity(affinity_space,
read_affinity_location(affinity_space, start_node))
{
/* deduce session costs from usable ram quota */
ram_quota = Genode::Child::effective_ram_quota(ram_quota);
}
} _resources;
/*
* Entry point used for serving the parent interface and the
* locally provided ROM sessions for the 'config' and 'binary'
* files.
*/
enum { ENTRYPOINT_STACK_SIZE = 12*1024 };
Genode::Rpc_entrypoint _entrypoint;
Genode::Parent_service _env_ram_service { _env, Genode::Ram_session::service_name() };
Genode::Parent_service _env_cpu_service { _env, Genode::Cpu_session::service_name() };
Genode::Parent_service _env_pd_service { _env, Genode:: Pd_session::service_name() };
Genode::Parent_service _env_log_service { _env, Genode::Log_session::service_name() };
Genode::Parent_service _env_rom_service { _env, Genode::Rom_session::service_name() };
Genode::Registry<Parent_service> &_parent_services;
Genode::Registry<Routed_service> &_child_services;
/**
* Private child configuration
*/
Init::Child_config _config;
Genode::Session_requester _session_requester;
/**
* Policy helpers
*/
Init::Child_policy_enforce_labeling _labeling_policy;
Init::Child_policy_handle_cpu_priorities _priority_policy;
Init::Child_policy_provide_rom_file _config_policy;
Init::Child_policy_redirect_rom_file _configfile_policy;
Init::Child_policy_ram_phys _ram_session_policy;
Genode::Child _child { _env.rm(), _entrypoint, *this };
/**
* Child_service::Wakeup callback
*/
void wakeup_child_service() override
{
_session_requester.trigger_update();
}
public:
/**
* Constructor
*
* \throw Ram_session::Alloc_failed allocation of config buffer failed
* \throw Region_map::Attach_failed failed to temporarily attach
* config dataspace to local address
* space
*/
Child(Genode::Env &env,
Genode::Xml_node start_node,
Genode::Xml_node default_route_node,
Name_registry &name_registry,
long prio_levels,
Genode::Affinity::Space const &affinity_space,
Genode::Registry<Parent_service> &parent_services,
Genode::Registry<Routed_service> &child_services)
:
_env(env),
_list_element(this),
_start_node(start_node),
_default_route_node(default_route_node),
_name_registry(name_registry),
_name(start_node, name_registry),
_resources(start_node, _name.unique, prio_levels,
affinity_space),
_entrypoint(&_env.pd(), ENTRYPOINT_STACK_SIZE, _name.unique, false,
_resources.affinity.location()),
_parent_services(parent_services),
_child_services(child_services),
_config(_env.ram(), _env.rm(), start_node),
_session_requester(_entrypoint, _env.ram(), _env.rm()),
_labeling_policy(_name.unique),
_priority_policy(_resources.prio_levels_log2, _resources.priority),
_config_policy("config", _config.dataspace(), &_entrypoint),
_configfile_policy("config", _config.filename()),
_ram_session_policy(_resources.constrain_phys)
{
using namespace Genode;
if (_resources.ram_quota == 0)
Genode::warning("no valid RAM resource for child "
"\"", Genode::Cstring(_name.unique), "\"");
if (config_verbose) {
log("child \"", Genode::Cstring(_name.unique), "\"");
log(" RAM quota: ", _resources.ram_quota);
log(" ELF binary: ", Cstring(_name.file));
log(" priority: ", _resources.priority);
}
/*
* Determine services provided by the child
*/
try {
Xml_node service_node = start_node.sub_node("provides").sub_node("service");
for (; ; service_node = service_node.next("service")) {
char name[Genode::Service::Name::capacity()];
service_node.attribute("name").value(name, sizeof(name));
if (config_verbose)
log(" provides service ", Genode::Cstring(name));
new (_child.heap())
Routed_service(child_services, this->name(),
_session_requester.id_space(),
_child.session_factory(),
name, _child.ram_session_cap(), *this);
}
} catch (Xml_node::Nonexistent_sub_node) { }
}
virtual ~Child()
{
_child_services.for_each([&] (Routed_service &service) {
if (service.has_id_space(_session_requester.id_space()))
destroy(_child.heap(), &service); });
}
/**
* Return true if the child has the specified name
*/
bool has_name(Child_policy::Name const &str) const { return str == name(); }
/**
* Start execution of child
*/
void start() { _entrypoint.activate(); }
/****************************
** Child-policy interface **
****************************/
Child_policy::Name name() const override { return _name.unique; }
Binary_name binary_name() const override { return _name.file; }
Genode::Ram_session &ref_ram() override { return _env.ram(); }
Genode::Ram_session_capability ref_ram_cap() const override { return _env.ram_session_cap(); }
void init(Genode::Ram_session &session, Genode::Ram_session_capability cap) override
{
session.ref_account(Genode::env()->ram_session_cap());
Genode::env()->ram_session()->transfer_quota(cap, _resources.ram_quota);
}
void init(Genode::Cpu_session &session, Genode::Cpu_session_capability cap) override
{
using Genode::Cpu_session;
using Genode::size_t;
static size_t avail = Cpu_session::quota_lim_upscale( 100, 100);
size_t const need = Cpu_session::quota_lim_upscale(_resources.cpu_quota_pc, 100);
size_t need_adj = 0;
if (need > avail || avail == 0) {
warn_insuff_quota(Cpu_session::quota_lim_downscale(avail, 100));
need_adj = Cpu_session::quota_lim_upscale(100, 100);
avail = 0;
} else {
need_adj = Cpu_session::quota_lim_upscale(need, avail);
avail -= need;
}
session.ref_account(_env.cpu_session_cap());
_env.cpu().transfer_quota(cap, need_adj);
}
Genode::Id_space<Genode::Parent::Server> &server_id_space() override {
return _session_requester.id_space(); }
Genode::Service &resolve_session_request(Genode::Service::Name const &service_name,
Genode::Session_state::Args const &args) override
{
/* route environment session requests to the parent */
Genode::Session_label const label(Genode::label_from_args(args.string()));
if (label == name()) {
if (service_name == Genode::Ram_session::service_name()) return _env_ram_service;
if (service_name == Genode::Cpu_session::service_name()) return _env_cpu_service;
if (service_name == Genode::Pd_session::service_name()) return _env_pd_service;
if (service_name == Genode::Log_session::service_name()) return _env_log_service;
}
/* route initial ROM requests (binary and linker) to the parent */
if (service_name == Genode::Rom_session::service_name()) {
if (label.last_element() == binary_name()) return _env_rom_service;
if (label.last_element() == linker_name()) return _env_rom_service;
}
Genode::Service *service = nullptr;
/* check for "config" ROM request */
if ((service = _config_policy.resolve_session_request(service_name.string(), args.string())))
return *service;
/* check for "session_requests" ROM request */
if (service_name == Genode::Rom_session::service_name()
&& label.last_element() == Genode::Session_requester::rom_name())
return _session_requester.service();
try {
Genode::Xml_node route_node = _default_route_node;
try {
route_node = _start_node.sub_node("route"); }
catch (...) { }
Genode::Xml_node service_node = route_node.sub_node();
for (; ; service_node = service_node.next()) {
bool service_wildcard = service_node.has_type("any-service");
if (!service_node_matches(service_node, args.string(), name(), service_name))
continue;
if (!service_node_args_condition_satisfied(service_node, args, name()))
continue;
Genode::Xml_node target = service_node.sub_node();
for (; ; target = target.next()) {
if (target.has_type("parent")) {
if ((service = find_service(_parent_services, service_name)))
return *service;
if (!service_wildcard) {
warning(name(), ": service lookup for "
"\"", service_name, "\" at parent failed");
throw Genode::Parent::Service_denied();
}
}
if (target.has_type("child")) {
typedef Name_registry::Name Name;
Name server_name = target.attribute_value("name", Name());
server_name = _name_registry.deref_alias(server_name);
_child_services.for_each([&] (Routed_service &s) {
if (s.name() == Service::Name(service_name)
&& s.child_name() == server_name)
service = &s; });
if (service)
return *service;
if (!service_wildcard) {
Genode::warning(name(), ": lookup to child "
"server \"", server_name, "\" failed");
throw Genode::Parent::Service_denied();
}
}
if (target.has_type("any-child")) {
if (is_ambiguous(_child_services, service_name)) {
error(name(), ": ambiguous routes to "
"service \"", service_name, "\"");
throw Genode::Parent::Service_denied();
}
if ((service = find_service(_child_services, service_name)))
return *service;
if (!service_wildcard) {
warning(name(), ": lookup for service "
"\"", service_name, "\" failed");
throw Genode::Parent::Service_denied();
}
}
if (target.last())
break;
}
}
} catch (...) {
warning(name(), ": no route to service \"", service_name, "\"");
}
if (!service)
throw Genode::Parent::Service_denied();
return *service;
}
void filter_session_args(Service::Name const &service,
char *args, Genode::size_t args_len) override
{
_labeling_policy. filter_session_args(service.string(), args, args_len);
_priority_policy. filter_session_args(service.string(), args, args_len);
_configfile_policy. filter_session_args(service.string(), args, args_len);
_ram_session_policy.filter_session_args(service.string(), args, args_len);
}
Genode::Affinity filter_session_affinity(Genode::Affinity const &session_affinity) override
{
using namespace Genode;
Affinity::Space const &child_space = _resources.affinity.space();
Affinity::Location const &child_location = _resources.affinity.location();
/* check if no valid affinity space was specified */
if (session_affinity.space().total() == 0)
return Affinity(child_space, child_location);
Affinity::Space const &session_space = session_affinity.space();
Affinity::Location const &session_location = session_affinity.location();
/* scale resolution of resulting space */
Affinity::Space space(child_space.multiply(session_space));
/* subordinate session affinity to child affinity subspace */
Affinity::Location location(child_location
.multiply_position(session_space)
.transpose(session_location.xpos(),
session_location.ypos()));
return Affinity(space, location);
}
void announce_service(Genode::Service::Name const &service_name) override
{
log("child \"", name(), "\" announces service \"", service_name, "\"");
bool found = false;
_child_services.for_each([&] (Routed_service &service) {
if (service.has_id_space(_session_requester.id_space())
&& service.name() == service_name)
found = true; });
if (!found)
Genode::error(name(), ": illegal announcement of "
"service \"", service_name, "\"");
}
void resource_request(Genode::Parent::Resource_args const &args) override
{
log("child \"", name(), "\" requests resources: ", args.string());
Genode::size_t const requested_ram_quota =
Genode::Arg_string::find_arg(args.string(), "ram_quota")
.ulong_value(0);
if (avail_slack_ram_quota() < requested_ram_quota) {
Genode::warning("cannot respond to resource request - out of memory");
return;
}
/*
* XXX Synchronize quota transfers from/to env()->ram_session()
*
* If multiple children issue concurrent resource requests, the
* value reported by 'avail_slack_ram_quota' may be out of date
* when calling 'transfer_quota'.
*/
Genode::env()->ram_session()->transfer_quota(_child.ram_session_cap(),
requested_ram_quota);
/* wake up child that was starved for resources */
_child.notify_resource_avail();
}
void exit(int exit_value) override
{
try {
if (_start_node.sub_node("exit").attribute_value("propagate", false)) {
Genode::env()->parent()->exit(exit_value);
return;
}
} catch (...) { }
/*
* Print a message as the exit is not handled otherwise. There are
* a number of automated tests that rely on this message. It is
* printed by the default implementation of 'Child_policy::exit'.
*/
Child_policy::exit(exit_value);
}
};
#endif /* _INCLUDE__INIT__CHILD_H_ */