mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-29 01:58:55 +00:00
c2f23de2e7
This patch is needed to use children as services in a dynamic way (closing and reopening sessions).
576 lines
16 KiB
C++
576 lines
16 KiB
C++
/*
|
|
* \brief Representation used for children of the init process
|
|
* \author Norman Feske
|
|
* \date 2010-05-04
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2010-2012 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 <ram_session/connection.h>
|
|
#include <cpu_session/connection.h>
|
|
#include <cap_session/connection.h>
|
|
#include <base/printf.h>
|
|
#include <base/child.h>
|
|
|
|
/* init includes */
|
|
#include <init/child_config.h>
|
|
#include <init/child_policy.h>
|
|
|
|
namespace Init {
|
|
|
|
extern bool config_verbose;
|
|
|
|
|
|
/***************
|
|
** Utilities **
|
|
***************/
|
|
|
|
inline long read_priority(Genode::Xml_node start_node)
|
|
{
|
|
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.
|
|
*/
|
|
return -priority;
|
|
}
|
|
|
|
|
|
inline Genode::size_t read_ram_quota(Genode::Xml_node start_node)
|
|
{
|
|
Genode::Number_of_bytes ram_quota = 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_quota);
|
|
}
|
|
} catch (...) { }
|
|
}
|
|
} catch (...) { }
|
|
|
|
/*
|
|
* If the configured 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.
|
|
*/
|
|
if (ram_quota > Genode::env()->ram_session()->avail() - 128*1024) {
|
|
ram_quota = Genode::env()->ram_session()->avail() - 128*1024;
|
|
if (config_verbose)
|
|
Genode::printf("Warning: Specified quota exceeds available quota.\n"
|
|
" Proceeding with a quota of %zd bytes.\n",
|
|
(Genode::size_t)ram_quota);
|
|
}
|
|
return ram_quota;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return true if service XML node matches the specified service name
|
|
*/
|
|
inline bool service_node_matches(Genode::Xml_node service_node, const char *service_name)
|
|
{
|
|
if (service_node.has_type("any-service"))
|
|
return true;
|
|
|
|
return service_node.has_type("service")
|
|
&& service_node.attribute("name").has_value(service_name);
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if arguments satisfy the condition specified for the route
|
|
*/
|
|
inline bool service_node_args_condition_satisfied(Genode::Xml_node service_node,
|
|
const char *args)
|
|
{
|
|
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, key).string(arg_value, sizeof(arg_value), "");
|
|
return Genode::strcmp(value, arg_value) == 0;
|
|
} catch (...) { }
|
|
|
|
/* if no if-arg node exists, the condition is met */
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Init-specific representation of a child service
|
|
*
|
|
* For init, we introduce this 'Service' variant that distinguishes two
|
|
* phases, declared and announced. A 'Routed_service' object is created
|
|
* when a '<provides>' declaration is found in init's configuration.
|
|
* At that time, however, no children including the server do yet exist.
|
|
* If, at this stage, a client tries to open a session to this service,
|
|
* the client get enqueued in a list of applicants and blocked. When
|
|
* the server officially announces its service and passes over the root
|
|
* capability, the 'Routed_service' enters the announced stage and any
|
|
* applicants get unblocked.
|
|
*/
|
|
class Routed_service : public Genode::Service
|
|
{
|
|
private:
|
|
|
|
Genode::Root_capability _root;
|
|
bool _announced;
|
|
Genode::Server *_server;
|
|
|
|
struct Applicant : public Genode::Cancelable_lock,
|
|
public Genode::List<Applicant>::Element
|
|
{
|
|
Applicant() : Cancelable_lock(Genode::Lock::LOCKED) { }
|
|
};
|
|
|
|
Genode::Lock _applicants_lock;
|
|
Genode::List<Applicant> _applicants;
|
|
|
|
public:
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* \param name name of service
|
|
* \param server server providing the service
|
|
*/
|
|
Routed_service(const char *name,
|
|
Genode::Server *server)
|
|
: Service(name), _announced(false), _server(server) { }
|
|
|
|
Genode::Server *server() const { return _server; }
|
|
|
|
void announce(Genode::Root_capability root)
|
|
{
|
|
Genode::Lock::Guard guard(_applicants_lock);
|
|
|
|
_root = root;
|
|
_announced = true;
|
|
|
|
/* wake up aspiring clients */
|
|
for (Applicant *a; (a = _applicants.first()); ) {
|
|
_applicants.remove(a);
|
|
a->unlock();
|
|
}
|
|
}
|
|
|
|
Genode::Session_capability session(const char *args)
|
|
{
|
|
/*
|
|
* This function is called from the context of the client's
|
|
* activation thread. If the service is not yet announced,
|
|
* we let the client block.
|
|
*/
|
|
_applicants_lock.lock();
|
|
if (!_announced) {
|
|
Applicant myself;
|
|
_applicants.insert(&myself);
|
|
_applicants_lock.unlock();
|
|
myself.lock();
|
|
} else
|
|
_applicants_lock.unlock();
|
|
|
|
Genode::Session_capability cap;
|
|
try { cap = Genode::Root_client(_root).session(args); }
|
|
catch (Genode::Root::Invalid_args) { throw Invalid_args(); }
|
|
catch (Genode::Root::Unavailable) { throw Unavailable(); }
|
|
catch (Genode::Root::Quota_exceeded) { throw Quota_exceeded(); }
|
|
|
|
if (!cap.valid())
|
|
throw Unavailable();
|
|
|
|
return cap;
|
|
}
|
|
|
|
void upgrade(Genode::Session_capability sc, const char *args)
|
|
{
|
|
Genode::Root_client(_root).upgrade(sc, args);
|
|
}
|
|
|
|
void close(Genode::Session_capability sc)
|
|
{
|
|
Genode::Root_client(_root).close(sc);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Interface for name database
|
|
*/
|
|
struct Name_registry
|
|
{
|
|
virtual ~Name_registry() { }
|
|
|
|
/**
|
|
* Check if specified name is unique
|
|
*
|
|
* \return false if name already exists
|
|
*/
|
|
virtual bool is_unique(const char *name) const = 0;
|
|
|
|
/**
|
|
* Find server with specified name
|
|
*/
|
|
virtual Genode::Server *lookup_server(const char *name) const = 0;
|
|
};
|
|
|
|
|
|
class Child_registry;
|
|
|
|
|
|
class Child : Genode::Child_policy
|
|
{
|
|
private:
|
|
|
|
friend class Child_registry;
|
|
|
|
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) {
|
|
PWRN("Missing 'name' attribute in '<start>' entry.\n");
|
|
throw; }
|
|
|
|
/* check for a name confict with the other children */
|
|
if (!registry->is_unique(unique)) {
|
|
PERR("Child name \"%s\" is not unique", unique);
|
|
class Child_name_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;
|
|
|
|
/**
|
|
* Resources assigned to the child
|
|
*/
|
|
struct Resources
|
|
{
|
|
long prio_levels_log2;
|
|
long priority;
|
|
Genode::size_t ram_quota;
|
|
Genode::Ram_connection ram;
|
|
Genode::Cpu_connection cpu;
|
|
Genode::Rm_connection rm;
|
|
|
|
Resources(Genode::Xml_node start_node, const char *label,
|
|
long prio_levels_log2)
|
|
:
|
|
prio_levels_log2(prio_levels_log2),
|
|
priority(read_priority(start_node)),
|
|
ram_quota(read_ram_quota(start_node)),
|
|
ram(label),
|
|
cpu(label, priority*(Genode::Cpu_session::PRIORITY_LIMIT >> prio_levels_log2))
|
|
{
|
|
/* deduce session costs from usable ram quota */
|
|
Genode::size_t session_donations = Genode::Rm_connection::RAM_QUOTA +
|
|
Genode::Cpu_connection::RAM_QUOTA +
|
|
Genode::Ram_connection::RAM_QUOTA;
|
|
|
|
if (ram_quota > session_donations)
|
|
ram_quota -= session_donations;
|
|
else ram_quota = 0;
|
|
|
|
ram.ref_account(Genode::env()->ram_session_cap());
|
|
Genode::env()->ram_session()->transfer_quota(ram.cap(), 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;
|
|
|
|
/**
|
|
* ELF binary
|
|
*/
|
|
Genode::Rom_connection _binary_rom;
|
|
|
|
/**
|
|
* Private child configuration
|
|
*/
|
|
Init::Child_config _config;
|
|
|
|
/**
|
|
* Each child of init can act as a server
|
|
*/
|
|
Genode::Server _server;
|
|
|
|
Genode::Child _child;
|
|
|
|
Genode::Service_registry *_parent_services;
|
|
Genode::Service_registry *_child_services;
|
|
|
|
/**
|
|
* 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_provide_rom_file _binary_policy;
|
|
Init::Child_policy_redirect_rom_file _configfile_policy;
|
|
|
|
public:
|
|
|
|
Child(Genode::Xml_node start_node,
|
|
Genode::Xml_node default_route_node,
|
|
Name_registry *name_registry,
|
|
long prio_levels_log2,
|
|
Genode::Service_registry *parent_services,
|
|
Genode::Service_registry *child_services,
|
|
Genode::Cap_session *cap_session)
|
|
:
|
|
_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_log2),
|
|
_entrypoint(cap_session, ENTRYPOINT_STACK_SIZE, _name.unique, false),
|
|
_binary_rom(_name.file, _name.unique),
|
|
_config(_resources.ram.cap(), start_node),
|
|
_server(_resources.ram.cap()),
|
|
_child(_binary_rom.dataspace(), _resources.ram.cap(),
|
|
_resources.cpu.cap(), _resources.rm.cap(), &_entrypoint, this),
|
|
_parent_services(parent_services),
|
|
_child_services(child_services),
|
|
_labeling_policy(_name.unique),
|
|
_priority_policy(_resources.prio_levels_log2, _resources.priority),
|
|
_config_policy("config", _config.dataspace(), &_entrypoint),
|
|
_binary_policy("binary", _binary_rom.dataspace(), &_entrypoint),
|
|
_configfile_policy("config", _config.filename())
|
|
{
|
|
using namespace Genode;
|
|
|
|
if (_resources.ram_quota == 0)
|
|
PWRN("no valid RAM resource for child \"%s\"", _name.unique);
|
|
|
|
if (config_verbose) {
|
|
Genode::printf("child \"%s\"\n", _name.unique);
|
|
Genode::printf(" RAM quota: %zd\n", _resources.ram_quota);
|
|
Genode::printf(" ELF binary: %s\n", _name.file);
|
|
Genode::printf(" priority: %ld\n", _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::MAX_NAME_LEN];
|
|
service_node.attribute("name").value(name, sizeof(name));
|
|
|
|
if (config_verbose)
|
|
Genode::printf(" provides service %s\n", name);
|
|
|
|
child_services->insert(new (_child.heap())
|
|
Routed_service(name, &_server));
|
|
|
|
}
|
|
} catch (Xml_node::Nonexistent_sub_node) { }
|
|
}
|
|
|
|
virtual ~Child() { }
|
|
|
|
/**
|
|
* Return true if the child has the specified name
|
|
*/
|
|
bool has_name(const char *n) const { return !Genode::strcmp(name(), n); }
|
|
|
|
Genode::Server *server() { return &_server; }
|
|
|
|
/**
|
|
* Start execution of child
|
|
*/
|
|
void start() { _entrypoint.activate(); }
|
|
|
|
|
|
/****************************
|
|
** Child-policy interface **
|
|
****************************/
|
|
|
|
const char *name() const { return _name.unique; }
|
|
|
|
Genode::Service *resolve_session_request(const char *service_name,
|
|
const char *args)
|
|
{
|
|
Genode::Service *service = 0;
|
|
|
|
/* check for config file request */
|
|
if ((service = _config_policy.resolve_session_request(service_name, args)))
|
|
return service;
|
|
|
|
/* check for binary file request */
|
|
if ((service = _binary_policy.resolve_session_request(service_name, args)))
|
|
return 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, service_name))
|
|
continue;
|
|
|
|
if (!service_node_args_condition_satisfied(service_node, args))
|
|
continue;
|
|
|
|
Genode::Xml_node target = service_node.sub_node();
|
|
for (; ; target = target.next()) {
|
|
|
|
if (target.has_type("parent")) {
|
|
service = _parent_services->find(service_name);
|
|
if (service)
|
|
return service;
|
|
|
|
if (!service_wildcard) {
|
|
PWRN("%s: service lookup for \"%s\" at parent failed", name(), service_name);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (target.has_type("child")) {
|
|
char server_name[Name::MAX_NAME_LEN];
|
|
server_name[0] = 0;
|
|
target.attribute("name").value(server_name, sizeof(server_name));
|
|
|
|
Genode::Server *server = _name_registry->lookup_server(server_name);
|
|
if (!server)
|
|
PWRN("%s: invalid route to non-existing server \"%s\"", name(), server_name);
|
|
|
|
service = _child_services->find(service_name, server);
|
|
if (service)
|
|
return service;
|
|
|
|
if (!service_wildcard) {
|
|
PWRN("%s: lookup to child service \"%s\" failed", name(), service_name);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (target.has_type("any-child")) {
|
|
if (_child_services->is_ambiguous(service_name)) {
|
|
PERR("%s: ambiguous routes to service \"%s\"", name(), service_name);
|
|
return 0;
|
|
}
|
|
service = _child_services->find(service_name);
|
|
if (service)
|
|
return service;
|
|
|
|
if (!service_wildcard) {
|
|
PWRN("%s: lookup for service \"%s\" failed", name(), service_name);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (target.is_last())
|
|
break;
|
|
}
|
|
}
|
|
} catch (...) {
|
|
PWRN("%s: no route to service \"%s\"", name(), service_name);
|
|
}
|
|
return service;
|
|
}
|
|
|
|
void filter_session_args(const char *service,
|
|
char *args, Genode::size_t args_len)
|
|
{
|
|
_labeling_policy.filter_session_args(service, args, args_len);
|
|
_priority_policy.filter_session_args(service, args, args_len);
|
|
_configfile_policy.filter_session_args(service, args, args_len);
|
|
}
|
|
|
|
bool announce_service(const char *service_name,
|
|
Genode::Root_capability root,
|
|
Genode::Allocator *alloc,
|
|
Genode::Server *server)
|
|
{
|
|
if (config_verbose)
|
|
Genode::printf("child \"%s\" announces service \"%s\"\n",
|
|
name(), service_name);
|
|
|
|
Genode::Service *s = _child_services->find(service_name, &_server);
|
|
Routed_service *rs = dynamic_cast<Routed_service *>(s);
|
|
if (!s || !rs) {
|
|
PERR("%s: illegal announcement of service \"%s\"", name(), service_name);
|
|
return false;
|
|
}
|
|
|
|
rs->announce(root);
|
|
return true;
|
|
}
|
|
};
|
|
}
|
|
|
|
#endif /* _INCLUDE__INIT__CHILD_H_ */
|