genode/ports/src/noux/child.h
Norman Feske cbd4830977 Propagate new parent cap to forked process
This patch removes the hardcoded parent cap for the new child by
introducing a facility called 'poke' similar to copy-to-user. This
change makes the fork mechanism kernel-agnostic. The 'noux_fork'
test works on L4/Fiasco, OKL4, NOVA, Fiasco.OC, and L4ka::Pistachio.
Linux is not supported yet.
2012-02-23 10:42:11 +01:00

1025 lines
26 KiB
C++

/*
* \brief Noux child process
* \author Norman Feske
* \date 2011-02-17
*/
/*
* Copyright (C) 2011-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 _NOUX__CHILD_H_
#define _NOUX__CHILD_H_
/* Genode includes */
#include <init/child_policy.h>
#include <ram_session/connection.h>
#include <cpu_session/connection.h>
#include <base/signal.h>
#include <base/semaphore.h>
#include <cap_session/cap_session.h>
#include <os/attached_ram_dataspace.h>
/* Noux includes */
#include <file_descriptor_registry.h>
#include <vfs.h>
#include <signal_dispatcher.h>
#include <noux_session/capability.h>
#include <args.h>
#include <environment.h>
namespace Noux {
using namespace Genode;
/**
* Allocator for process IDs
*/
class Pid_allocator
{
private:
Lock _lock;
int _num_pids;
public:
Pid_allocator() : _num_pids(0) { }
int alloc()
{
Lock::Guard guard(_lock);
return _num_pids++;
}
};
/**
* Return singleton instance of PID allocator
*/
Pid_allocator *pid_allocator()
{
static Pid_allocator inst;
return &inst;
}
class Child;
bool is_init_process(Child *child);
void init_process_exited();
/**
* Signal context used for child exit
*/
class Child_exit_dispatcher : public Signal_dispatcher
{
private:
Child *_child;
public:
Child_exit_dispatcher(Child *child) : _child(child) { }
void dispatch()
{
if (is_init_process(_child)) {
PINF("init process exited");
/* trigger exit of main event loop */
init_process_exited();
}
/* XXX destroy child */
}
};
/**
* Signal context used for removing the child after having executed 'execve'
*/
class Child_execve_cleanup_dispatcher : public Signal_dispatcher
{
private:
Child *_child;
public:
Child_execve_cleanup_dispatcher(Child *child) : _child(child) { }
void dispatch()
{
PINF("execve cleanup dispatcher called");
destroy(env()->heap(), _child);
}
};
class Child : private Child_policy,
public Rpc_object<Session>,
public File_descriptor_registry
{
private:
int const _pid;
Signal_receiver *_sig_rec;
/**
* Semaphore used for implementing blocking syscalls, i.e., select
*/
Semaphore _blocker;
enum { MAX_NAME_LEN = 64 };
char _name[MAX_NAME_LEN];
Child_exit_dispatcher _exit_dispatcher;
Signal_context_capability _exit_context_cap;
Child_execve_cleanup_dispatcher _execve_cleanup_dispatcher;
Signal_context_capability _execve_cleanup_context_cap;
Cap_session * const _cap_session;
enum { STACK_SIZE = 4*1024*sizeof(long) };
Rpc_entrypoint _entrypoint;
/**
* Resources assigned to the child
*/
struct Resources
{
/*
* XXX not used yet
*/
struct Dataspace_destroyer
{
virtual void destroy(Dataspace_capability) = 0;
};
class Dataspace_registry;
class Dataspace_info : public Object_pool<Dataspace_info>::Entry
{
private:
size_t _size;
Dataspace_capability _ds_cap;
Dataspace_destroyer &_destroyer;
public:
Dataspace_info(Dataspace_capability ds_cap,
Dataspace_destroyer &destroyer)
:
Object_pool<Dataspace_info>::Entry(ds_cap),
_size(Dataspace_client(ds_cap).size()),
_ds_cap(ds_cap),
_destroyer(destroyer)
{ }
size_t size() const { return _size; }
Dataspace_capability ds_cap() const { return _ds_cap; }
/**
* Create shadow copy of dataspace
*
* \param ds_registry registry for keeping track of
* the new dataspace
* \param ep entrypoint used to serve the RPC
* interface of the new dataspace
* (used if the dataspace is a sub
* RM session)
* \return capability for the new dataspace
*/
virtual Dataspace_capability fork(Ram_session_capability ram,
Dataspace_destroyer &destroyer,
Dataspace_registry &ds_registry,
Rpc_entrypoint &ep) = 0;
/**
* Write data sequence into dataspace
*
* \param dst_offset destination offset within dataspace
* \param src data source buffer
* \param len length of source buffer in bytes
*/
virtual void poke(addr_t dst_offset, void const *src, size_t len) = 0;
Dataspace_destroyer &destroyer() { return _destroyer; }
};
class Dataspace_registry
{
private:
Object_pool<Dataspace_info> _pool;
public:
void insert(Dataspace_info *info)
{
_pool.insert(info);
}
void remove(Dataspace_info *info)
{
_pool.remove(info);
}
Dataspace_info *lookup_info(Dataspace_capability ds_cap)
{
return _pool.obj_by_cap(ds_cap);
}
};
struct Ram_dataspace_info : Dataspace_info,
List<Ram_dataspace_info>::Element
{
Ram_dataspace_info(Ram_dataspace_capability ds_cap,
Dataspace_destroyer &destroyer)
: Dataspace_info(ds_cap, destroyer) { }
Dataspace_capability fork(Ram_session_capability ram,
Dataspace_destroyer &destroyer,
Dataspace_registry &,
Rpc_entrypoint &)
{
size_t const size = Dataspace_client(ds_cap()).size();
Ram_dataspace_capability dst_ds;
try {
dst_ds = Ram_session_client(ram).alloc(size);
} catch (...) {
return Dataspace_capability();
}
void *src = 0;
try {
src = Genode::env()->rm_session()->attach(ds_cap());
} catch (...) { }
void *dst = 0;
try {
dst = Genode::env()->rm_session()->attach(dst_ds);
} catch (...) { }
if (src && dst)
memcpy(dst, src, size);
if (src) Genode::env()->rm_session()->detach(src);
if (dst) Genode::env()->rm_session()->detach(dst);
if (!src || !dst) {
Ram_session_client(ram).free(dst_ds);
return Dataspace_capability();
}
return dst_ds;
}
void poke(addr_t dst_offset, void const *src, size_t len)
{
if ((dst_offset >= size()) || (dst_offset + len > size())) {
PERR("illegal attemt to write beyond dataspace boundary");
return;
}
char *dst = 0;
try {
dst = Genode::env()->rm_session()->attach(ds_cap());
} catch (...) { }
if (src && dst)
memcpy(dst + dst_offset, src, len);
if (dst) Genode::env()->rm_session()->detach(dst);
}
};
struct Local_ram_session : Rpc_object<Ram_session>,
Dataspace_destroyer
{
List<Ram_dataspace_info> _list;
/*
* Track the RAM resources accumulated via RAM session
* allocations.
*/
size_t _used_quota;
Dataspace_registry &_registry;
/**
* Constructor
*/
Local_ram_session(Dataspace_registry &registry)
: _used_quota(0), _registry(registry) { }
/**
* Destructor
*/
~Local_ram_session()
{
Ram_dataspace_info *info = 0;
while ((info = _list.first()))
free(static_cap_cast<Ram_dataspace>(info->ds_cap()));
}
/***********************************
** Dataspace_destroyer interface **
***********************************/
/* XXX not used yet */
void destroy(Dataspace_capability ds)
{
free(static_cap_cast<Ram_dataspace>(ds));
}
/***************************
** Ram_session interface **
***************************/
Ram_dataspace_capability alloc(size_t size)
{
Ram_dataspace_capability ds_cap = env()->ram_session()->alloc(size);
Ram_dataspace_info *ds_info = new (env()->heap())
Ram_dataspace_info(ds_cap, *this);
_used_quota += ds_info->size();
_registry.insert(ds_info);
_list.insert(ds_info);
return ds_cap;
}
void free(Ram_dataspace_capability ds_cap)
{
Ram_dataspace_info *ds_info =
dynamic_cast<Ram_dataspace_info *>(_registry.lookup_info(ds_cap));
if (!ds_info) {
PERR("RAM free: dataspace lookup failed");
return;
}
_registry.remove(ds_info);
_list.remove(ds_info);
_used_quota -= ds_info->size();
env()->ram_session()->free(ds_cap);
Genode::destroy(env()->heap(), ds_info);
}
int ref_account(Ram_session_capability) { return 0; }
int transfer_quota(Ram_session_capability, size_t) { return 0; }
size_t quota() { return env()->ram_session()->quota(); }
size_t used() { return _used_quota; }
};
/**
* Used to record all RM attachements
*/
struct Local_rm_session : Rpc_object<Rm_session>
{
struct Region : List<Region>::Element
{
Dataspace_capability ds;
size_t size;
off_t offset;
addr_t local_addr;
Region(Dataspace_capability ds, size_t size,
off_t offset, addr_t local_addr)
:
ds(ds), size(size),
offset(offset), local_addr(local_addr)
{ }
/**
* Return true if region contains specified address
*/
bool contains(addr_t addr) const
{
return (addr >= local_addr)
&& (addr < local_addr + size);
}
};
Rm_connection _rm;
Dataspace_registry &_ds_registry;
List<Region> _regions;
Region *_lookup_region_by_addr(addr_t local_addr)
{
Region *curr = _regions.first();
for (; curr; curr = curr->next()) {
if (curr->contains(local_addr))
return curr;
}
return 0;
}
Local_rm_session(Dataspace_registry &ds_registry,
addr_t start = ~0UL, size_t size = 0)
: _rm(start, size), _ds_registry(ds_registry) { }
~Local_rm_session()
{
Region *curr = 0;
for (; curr; curr = curr->next())
detach(curr->local_addr);
}
/**
* Replay attachments onto specified RM session
*
* \param dst_ram backing store used for allocating the
* the copies of RAM dataspaces
*/
void replay(Ram_session_capability dst_ram,
Rm_session_capability dst_rm,
Dataspace_registry &ds_registry,
Rpc_entrypoint &ep)
{
/*
* XXX remove this
*/
struct Dummy_destroyer : Dataspace_destroyer
{
void destroy(Dataspace_capability) { }
};
static Dummy_destroyer dummy_destroyer;
Region *curr = _regions.first();
for (; curr; curr = curr->next()) {
Dataspace_capability ds;
Dataspace_info *info = _ds_registry.lookup_info(curr->ds);
if (info) {
/*
* XXX using 'this' as destroyer may be false,
* the destroyer should better correspond to
* dst_ram.
*/
ds = info->fork(dst_ram,
dummy_destroyer,
ds_registry,
ep);
if (!ds.valid())
PERR("replay: Error while forking dataspace");
/*
* XXX We could detect dataspaces that are
* attached more than once. For now, we
* create a new fork for each attachment.
*/
} else {
/*
* If the dataspace is not a RAM dataspace,
* assume that it's a ROM dataspace.
*
* XXX Handle ROM dataspaces explicitly. For
* once, we need to make sure that they
* remain available until the child process
* exits even if the parent process exits
* earlier. Furthermore, we would like to
* detect unexpected dataspaces.
*/
ds = curr->ds;
PWRN("replay: unknown dataspace type, assume ROM");
}
Rm_session_client(dst_rm).attach(ds, curr->size,
curr->offset,
true,
curr->local_addr);
}
}
void poke(addr_t dst_addr, void const *src, size_t len)
{
Region *region = _lookup_region_by_addr(dst_addr);
if (!region) {
PERR("poke: no region at 0x%lx", dst_addr);
return;
}
/*
* Test if start and end address occupied by the object
* type refers to the same region.
*/
if (region != _lookup_region_by_addr(dst_addr + len - 1)) {
PERR("attempt to write beyond region boundary");
return;
}
Dataspace_info *info = _ds_registry.lookup_info(region->ds);
if (!info) {
PERR("attempt to write to unknown dataspace type");
for (;;);
return;
}
if (region->offset) {
PERR("poke: writing to region with offset is not supported");
return;
}
info->poke(dst_addr - region->local_addr, src, len);
}
/**************************
** RM session interface **
**************************/
Local_addr attach(Dataspace_capability ds,
size_t size = 0, off_t offset = 0,
bool use_local_addr = false,
Local_addr local_addr = (addr_t)0)
{
if (size == 0)
size = Dataspace_client(ds).size();
/*
* XXX look if we can identify the specified dataspace.
* Is it a dataspace allocated via 'Local_ram_session'?
*/
local_addr = _rm.attach(ds, size, offset,
use_local_addr, local_addr);
/*
* Record attachement for later replay (needed during
* fork)
*/
_regions.insert(new (Genode::env()->heap())
Region(ds, size, offset, local_addr));
return local_addr;
}
void detach(Local_addr local_addr)
{
_rm.detach(local_addr);
Region *region = _lookup_region_by_addr(local_addr);
if (!region) {
PWRN("Attempt to detach unknown region at 0x%p",
(void *)local_addr);
return;
}
_regions.remove(region);
destroy(Genode::env()->heap(), region);
}
Pager_capability add_client(Thread_capability thread)
{
return _rm.add_client(thread);
}
void fault_handler(Signal_context_capability handler)
{
return _rm.fault_handler(handler);
}
State state()
{
return _rm.state();
}
Dataspace_capability dataspace()
{
return _rm.dataspace();
}
};
struct Sub_rm_dataspace_info : Dataspace_info
{
struct Dummy_destroyer : Dataspace_destroyer
{
void destroy(Dataspace_capability) { }
} _dummy_destroyer;
Local_rm_session &_sub_rm;
Sub_rm_dataspace_info(Local_rm_session &sub_rm)
:
Dataspace_info(sub_rm.dataspace(), _dummy_destroyer),
_sub_rm(sub_rm)
{ }
Dataspace_capability fork(Ram_session_capability ram,
Dataspace_destroyer &destoyer,
Dataspace_registry &ds_registry,
Rpc_entrypoint &ep)
{
Local_rm_session *new_sub_rm =
new Local_rm_session(ds_registry, 0, size());
Rm_session_capability rm_cap = ep.manage(new_sub_rm);
_sub_rm.replay(ram, rm_cap, ds_registry, ep);
ds_registry.insert(new Sub_rm_dataspace_info(*new_sub_rm));
return new_sub_rm->dataspace();
}
void poke(addr_t dst_offset, void const *src, size_t len)
{
if ((dst_offset >= size()) || (dst_offset + len > size())) {
PERR("illegal attemt to write beyond RM boundary");
return;
}
_sub_rm.poke(dst_offset, src, len);
}
};
/**
* Used to defer the execution of the process' main thread
*/
struct Local_cpu_session : Rpc_object<Cpu_session>
{
bool const forked;
Cpu_connection cpu;
Thread_capability main_thread;
Local_cpu_session(char const *label, bool forked)
: forked(forked), cpu(label) { }
Thread_capability create_thread(Name const &name)
{
/*
* Prevent any attempt to create more than the main
* thread.
*/
if (main_thread.valid()) {
PWRN("Invalid attempt to create a thread besides main");
while (1);
return Thread_capability();
}
main_thread = cpu.create_thread(name);
PINF("created main thread");
return main_thread;
}
void kill_thread(Thread_capability thread) {
cpu.kill_thread(thread); }
Thread_capability first() {
return cpu.first(); }
Thread_capability next(Thread_capability curr) {
return cpu.next(curr); }
int set_pager(Thread_capability thread,
Pager_capability pager) {
return cpu.set_pager(thread, pager); }
int start(Thread_capability thread, addr_t ip, addr_t sp)
{
if (forked) {
PINF("defer attempt to start thread at ip 0x%lx", ip);
return 0;
}
return cpu.start(thread, ip, sp);
}
void pause(Thread_capability thread) {
cpu.pause(thread); }
void resume(Thread_capability thread) {
cpu.resume(thread); }
void cancel_blocking(Thread_capability thread) {
cpu.cancel_blocking(thread); }
int state(Thread_capability thread, Thread_state *dst) {
return cpu.state(thread, dst); }
void exception_handler(Thread_capability thread,
Signal_context_capability handler) {
cpu.exception_handler(thread, handler); }
void single_step(Thread_capability thread, bool enable) {
cpu.single_step(thread, enable); }
/**
* Explicitly start main thread, only meaningful when
* 'forked' is true
*/
void start_main_thread(addr_t ip, addr_t sp)
{
cpu.start(main_thread, ip, sp);
}
};
Rpc_entrypoint &ep;
Dataspace_registry ds_registry;
Local_ram_session ram;
Local_cpu_session cpu;
Local_rm_session rm;
Resources(char const *label, Rpc_entrypoint &ep, bool forked)
:
ep(ep), ram(ds_registry), cpu(label, forked), rm(ds_registry)
{
ep.manage(&ram);
ep.manage(&rm);
ep.manage(&cpu);
}
~Resources()
{
ep.dissolve(&ram);
ep.dissolve(&rm);
ep.dissolve(&cpu);
}
} _resources;
/**
* Command line arguments
*/
Args_dataspace _args;
/**
* Environment variables
*/
Environment _env;
Vfs * const _vfs;
/**
* ELF binary
*/
Dataspace_capability const _binary_ds;
Genode::Child _child;
Service_registry * const _parent_services;
Init::Child_policy_enforce_labeling _labeling_policy;
Init::Child_policy_provide_rom_file _binary_policy;
Init::Child_policy_provide_rom_file _args_policy;
Init::Child_policy_provide_rom_file _env_policy;
enum { PAGE_SIZE = 4096, PAGE_MASK = ~(PAGE_SIZE - 1) };
enum { SYSIO_DS_SIZE = PAGE_MASK & (sizeof(Sysio) + PAGE_SIZE - 1) };
Attached_ram_dataspace _sysio_ds;
Sysio * const _sysio;
Session_capability const _noux_session_cap;
struct Local_noux_service : public Service
{
Genode::Session_capability _cap;
/**
* Constructor
*
* \param cap capability to return on session requests
*/
Local_noux_service(Genode::Session_capability cap)
: Service(service_name()), _cap(cap) { }
Genode::Session_capability session(const char *args) { return _cap; }
void upgrade(Genode::Session_capability, const char *args) { }
void close(Genode::Session_capability) { }
} _local_noux_service;
struct Local_sub_rm_service : public Service
{
Rpc_entrypoint &_ep;
Resources::Dataspace_registry &_ds_registry;
Local_sub_rm_service(Rpc_entrypoint &ep,
Resources::Dataspace_registry &ds_registry)
:
Service(Rm_session::service_name()),
_ep(ep), _ds_registry(ds_registry)
{ }
Genode::Session_capability session(const char *args)
{
addr_t start = Arg_string::find_arg(args, "start").ulong_value(~0UL);
size_t size = Arg_string::find_arg(args, "size").ulong_value(0);
Resources::Local_rm_session *rm =
new Resources::Local_rm_session(_ds_registry, start, size);
Genode::Session_capability cap = _ep.manage(rm);
_ds_registry.insert(new Resources::Sub_rm_dataspace_info(*rm));
return cap;
}
void upgrade(Genode::Session_capability, const char *args) { }
void close(Genode::Session_capability)
{
PWRN("Local_sub_rm_service::close not implemented, leaking memory");
}
} _local_sub_rm_service;
/**
* Exception type for failed file-descriptor lookup
*/
class Invalid_fd { };
Shared_pointer<Io_channel> _lookup_channel(int fd) const
{
Shared_pointer<Io_channel> channel = io_channel_by_fd(fd);
if (channel)
return channel;
throw Invalid_fd();
}
enum { ARGS_DS_SIZE = 4096 };
/**
* Let specified child inherit our file descriptors
*/
void _assign_io_channels_to(Child *child)
{
for (int fd = 0; fd < MAX_FILE_DESCRIPTORS; fd++)
if (fd_in_use(fd))
child->add_io_channel(io_channel_by_fd(fd), fd);
}
public:
/**
* Constructor
*
* \param forked false if the child is spawned directly from
* an executable binary (i.e., the init process,
* or children created via execve, or
* true if the child is a fork from another child
*/
Child(char const *name,
int pid,
Signal_receiver *sig_rec,
Vfs *vfs,
Args const &args,
char const *env,
Cap_session *cap_session,
Service_registry *parent_services,
Rpc_entrypoint &resources_ep,
bool forked)
:
_pid(pid),
_sig_rec(sig_rec),
_exit_dispatcher(this),
_exit_context_cap(sig_rec->manage(&_exit_dispatcher)),
_execve_cleanup_dispatcher(this),
_execve_cleanup_context_cap(sig_rec->manage(&_execve_cleanup_dispatcher)),
_cap_session(cap_session),
_entrypoint(cap_session, STACK_SIZE, "noux_process", false),
_resources(name, resources_ep, false),
_args(ARGS_DS_SIZE, args),
_env(env),
_vfs(vfs),
_binary_ds(forked ? Dataspace_capability()
: vfs->dataspace_from_file(name)),
_child(_binary_ds, _resources.ram.cap(), _resources.cpu.cap(),
_resources.rm.cap(), &_entrypoint, this),
_parent_services(parent_services),
_labeling_policy(_name),
_binary_policy("binary", _binary_ds, &_entrypoint),
_args_policy( "args", _args.cap(), &_entrypoint),
_env_policy( "env", _env.cap(), &_entrypoint),
_sysio_ds(Genode::env()->ram_session(), SYSIO_DS_SIZE),
_sysio(_sysio_ds.local_addr<Sysio>()),
_noux_session_cap(Session_capability(_entrypoint.manage(this))),
_local_noux_service(_noux_session_cap),
_local_sub_rm_service(_entrypoint, _resources.ds_registry)
{
_args.dump();
strncpy(_name, name, sizeof(_name));
}
~Child()
{
/* XXX _binary_ds */
_entrypoint.dissolve(this);
}
void start() { _entrypoint.activate(); }
void start_forked_main_thread(addr_t ip, addr_t sp, addr_t parent_cap_addr)
{
/* poke parent_cap_addr into child's address space */
Parent_capability const cap = _child.parent_cap();
_resources.rm.poke(parent_cap_addr, &cap, sizeof(cap));
/* start execution of new main thread at supplied trampoline */
_resources.cpu.start_main_thread(ip, sp);
}
Ram_session_capability ram() const { return _resources.ram.cap(); }
Rm_session_capability rm() const { return _resources.rm.cap(); }
Resources::Dataspace_registry &ds_registry() { return _resources.ds_registry; }
/****************************
** Child_policy interface **
****************************/
const char *name() const { return _name; }
Service *resolve_session_request(const char *service_name,
const char *args)
{
Service *service = 0;
/* check for local ROM file requests */
if ((service = _args_policy.resolve_session_request(service_name, args))
|| (service = _env_policy.resolve_session_request(service_name, args))
|| (service = _binary_policy.resolve_session_request(service_name, args)))
return service;
/* check for locally implemented noux service */
if (strcmp(service_name, Session::service_name()) == 0)
return &_local_noux_service;
/*
* Check for the creation of an RM session, which is used by
* the dynamic linker to manually manage a part of the address
* space.
*/
if (strcmp(service_name, Rm_session::service_name()) == 0)
return &_local_sub_rm_service;
return _parent_services->find(service_name);
}
void filter_session_args(const char *service,
char *args, size_t args_len)
{
_labeling_policy.filter_session_args(service, args, args_len);
}
void exit(int exit_value)
{
PINF("child %s exited with exit value %d", _name, exit_value);
Signal_transmitter(_exit_context_cap).submit();
}
Ram_session *ref_ram_session()
{
return &_resources.ram;
}
/****************************
** Noux session interface **
****************************/
Dataspace_capability sysio_dataspace()
{
return _sysio_ds.cap();
}
bool syscall(Syscall sc);
};
};
#endif /* _NOUX__CHILD_H_ */