libc: use task switch in select()

This commit is contained in:
Christian Helmuth 2016-12-13 17:09:35 +01:00 committed by Norman Feske
parent 59c0796820
commit 0f6800b20f
5 changed files with 637 additions and 252 deletions

View File

@ -4,7 +4,7 @@
LIBS = libc-string libc-locale libc-stdlib libc-stdio libc-gen libc-gdtoa \
libc-inet libc-stdtime libc-regex libc-compat libc-setjmp libc-mem
LIBS += base vfs
LIBS += base vfs timeout
#
# Back end

View File

@ -1,52 +1,66 @@
/*
* \brief select() implementation
* \author Christian Prochaska
* \author Christian Helmuth
* \author Emery Hemingway
* \date 2010-01-21
*
* the 'select()' implementation is partially based on the lwip version as
* implemented in 'src/api/sockets.c'
*
* Note what POSIX states about select(): File descriptors associated with
* regular files always select true for ready to read, ready to write, and
* error conditions.
*/
/*
* Copyright (C) 2010-2013 Genode Labs GmbH
* Copyright (C) 2010-2017 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.
*/
/* Genode includes */
#include <base/log.h>
#include <os/timed_semaphore.h>
#include <util/reconstructible.h>
/* Libc includes */
#include <libc-plugin/plugin_registry.h>
#include <libc-plugin/plugin.h>
#include <sys/select.h>
#include <signal.h>
using namespace Libc;
#include "task.h"
namespace Libc {
struct Select_cb;
}
void (*libc_select_notify)() __attribute__((weak));
/** Description for a task waiting in select */
struct libc_select_cb
{
struct libc_select_cb *next;
int nfds;
int nready;
fd_set readset;
fd_set writeset;
fd_set exceptset;
/** don't signal the same semaphore twice: set to 1 when signalled */
int sem_signalled;
/** semaphore to wake up a task waiting for select */
Timed_semaphore *sem;
};
/** The global list of tasks waiting for select */
static struct libc_select_cb *select_cb_list;
static Libc::Select_cb *select_cb_list;
/** Description for a task waiting in select */
struct Libc::Select_cb
{
Select_cb *next; /* TODO genode list */
int const nfds;
int nready = 0;
fd_set readfds;
fd_set writefds;
fd_set exceptfds;
Select_cb(int nfds, fd_set const &readfds, fd_set const &writefds, fd_set const &exceptfds)
:
nfds(nfds), readfds(readfds), writefds(writefds), exceptfds(exceptfds)
{ }
};
static Genode::Lock &select_cb_list_lock()
@ -56,31 +70,36 @@ static Genode::Lock &select_cb_list_lock()
}
/* poll plugin select() functions */
/* input fds may not be NULL */
static int selscan(int nfds, fd_set *in_readfds, fd_set *in_writefds,
fd_set *in_exceptfds, fd_set *out_readfds,
fd_set *out_writefds, fd_set *out_exceptfds)
/**
* Poll plugin select() functions
*
* We iterate over all file descriptors in each list and count the number of
* ready descriptors. Output file-descriptor sets are cleared by this function
* (according to POSIX).
*/
static int selscan(int nfds,
fd_set *in_readfds, fd_set *in_writefds, fd_set *in_exceptfds,
fd_set *out_readfds, fd_set *out_writefds, fd_set *out_exceptfds)
{
int nready = 0;
/* zero timeout for polling of the plugins' select() functions */
struct timeval tv_0 = {0, 0};
struct timeval tv_0 = { 0, 0 };
/* temporary fd sets that are passed to the plugins */
int plugin_nready;
fd_set plugin_readfds;
fd_set plugin_writefds;
fd_set plugin_exceptfds;
int plugin_nready;
if (out_readfds)
FD_ZERO(out_readfds);
if (out_writefds)
FD_ZERO(out_writefds);
if (out_exceptfds)
FD_ZERO(out_exceptfds);
/* clear fd sets */
if (out_readfds) FD_ZERO(out_readfds);
if (out_writefds) FD_ZERO(out_writefds);
if (out_exceptfds) FD_ZERO(out_exceptfds);
for (Plugin *plugin = plugin_registry()->first(); plugin; plugin = plugin->next()) {
for (Libc::Plugin *plugin = Libc::plugin_registry()->first();
plugin;
plugin = plugin->next()) {
if (plugin->supports_select(nfds, in_readfds, in_writefds, in_exceptfds, &tv_0)) {
plugin_readfds = *in_readfds;
@ -115,163 +134,131 @@ static int selscan(int nfds, fd_set *in_readfds, fd_set *in_writefds,
/* this function gets called by plugin backends when file descripors become ready */
static void select_notify()
{
struct libc_select_cb *scb;
int nready;
bool resume_all = false;
Libc::Select_cb *scb;
int nready = 0;
fd_set tmp_readfds, tmp_writefds, tmp_exceptfds;
/* check for each waiting select() function if one of its fds is ready now
* and if so, wake this select() function up */
while (1) {
select_cb_list_lock().lock();
for (scb = select_cb_list; scb; scb = scb->next) {
if (scb->sem_signalled == 0) {
FD_ZERO(&tmp_readfds);
FD_ZERO(&tmp_writefds);
FD_ZERO(&tmp_exceptfds);
nready = selscan(scb->nfds, &scb->readset, &scb->writeset,
&scb->exceptset, &tmp_readfds, &tmp_writefds,
&tmp_exceptfds);
if (nready > 0)
break;
}
}
* and if so, wake all up */
Genode::Lock::Guard guard(select_cb_list_lock());
if (scb) {
scb->sem_signalled = 1;
scb->nready = nready;
scb->readset = tmp_readfds;
scb->writeset = tmp_writefds;
scb->exceptset = tmp_exceptfds;
scb->sem->up();
select_cb_list_lock().unlock();
} else {
select_cb_list_lock().unlock();
break;
for (scb = select_cb_list; scb; scb = scb->next) {
nready = selscan(scb->nfds,
&scb->readfds, &scb->writefds, &scb->exceptfds,
&tmp_readfds, &tmp_writefds, &tmp_exceptfds);
if (nready > 0) {
scb->nready = nready;
scb->readfds = tmp_readfds;
scb->writefds = tmp_writefds;
scb->exceptfds = tmp_exceptfds;
resume_all = true;
}
}
if (resume_all)
Libc::resume_all();
}
static void print(Genode::Output &output, timeval *tv)
{
if (!tv) {
print(output, "nullptr");
} else {
print(output, "{");
print(output, tv->tv_sec);
print(output, ",");
print(output, tv->tv_usec);
print(output, "}");
}
}
extern "C" int
__attribute__((weak))
_select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout)
_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *tv)
{
int nready;
fd_set in_readfds, in_writefds, in_exceptfds;
Genode::Alarm::Time msectimeout;
struct libc_select_cb select_cb;
struct libc_select_cb *p_selcb;
bool timed_out = false;
Genode::Constructible<Libc::Select_cb> select_cb;
/* initialize the select notification function pointer */
if (!libc_select_notify)
libc_select_notify = select_notify;
/* Protect ourselves searching through the list */
select_cb_list_lock().lock();
if (readfds) in_readfds = *readfds; else FD_ZERO(&in_readfds);
if (writefds) in_writefds = *writefds; else FD_ZERO(&in_writefds);
if (exceptfds) in_exceptfds = *exceptfds; else FD_ZERO(&in_exceptfds);
if (readfds)
in_readfds = *readfds;
else
FD_ZERO(&in_readfds);
if (writefds)
in_writefds = *writefds;
else
FD_ZERO(&in_writefds);
if (exceptfds)
in_exceptfds = *exceptfds;
else
FD_ZERO(&in_exceptfds);
{
Genode::Lock::Guard guard(select_cb_list_lock());
/* Go through each socket in each list to count number of sockets which
currently match */
nready = selscan(nfds, &in_readfds, &in_writefds, &in_exceptfds, readfds, writefds, exceptfds);
int const nready = selscan(nfds,
&in_readfds, &in_writefds, &in_exceptfds,
readfds, writefds, exceptfds);
/* If we don't have any current events, then suspend if we are supposed to */
if (!nready) {
/* return if any descripor is ready */
if (nready)
return nready;
if (timeout && (timeout->tv_sec) == 0 && (timeout->tv_usec == 0)) {
select_cb_list_lock().unlock();
if (readfds)
FD_ZERO(readfds);
if (writefds)
FD_ZERO(writefds);
if (exceptfds)
FD_ZERO(exceptfds);
/* return on zero-timeout */
if (tv && (tv->tv_sec) == 0 && (tv->tv_usec == 0))
return 0;
}
/* add our semaphore to list */
/* We don't actually need any dynamic memory. Our entry on the
* list is only valid while we are in this function, so it's ok
* to use local variables */
select_cb.nfds = nfds;
select_cb.readset = in_readfds;
select_cb.writeset = in_writefds;
select_cb.exceptset = in_exceptfds;
select_cb.sem_signalled = 0;
select_cb.sem = new (env()->heap()) Timed_semaphore(0);
/* Note that we are still protected */
/* Put this select_cb on top of list */
select_cb.next = select_cb_list;
select_cb_list = &select_cb;
/* suspend as we don't have any immediate events */
/* Now we can safely unprotect */
select_cb_list_lock().unlock();
select_cb.construct(nfds, in_readfds, in_writefds, in_exceptfds);
/* Now just wait to be woken */
if (!timeout) {
/* Wait forever */
select_cb.sem->down();
} else {
msectimeout = ((timeout->tv_sec * 1000) + ((timeout->tv_usec + 500)/1000));
try {
select_cb.sem->down(msectimeout);
} catch (Timeout_exception) {
timed_out = true;
}
}
/* add our callback to list */
select_cb->next = select_cb_list;
select_cb_list = &(*select_cb);
}
/* Take us off the list */
select_cb_list_lock().lock();
struct Timeout
{
timeval const *_tv;
bool const valid { _tv != nullptr };
unsigned long duration { valid ? _tv->tv_sec*1000 + _tv->tv_usec/1000 : 0UL };
if (select_cb_list == &select_cb)
select_cb_list = select_cb.next;
bool expired() const { return valid && duration == 0; };
Timeout(timeval *tv) : _tv(tv) { }
} timeout { tv };
do {
timeout.duration = Libc::suspend(timeout.duration);
} while (!timeout.expired() && select_cb->nready == 0);
{
Genode::Lock::Guard guard(select_cb_list_lock());
/* take us off the list */
if (select_cb_list == &(*select_cb))
select_cb_list = select_cb->next;
else
for (p_selcb = select_cb_list; p_selcb; p_selcb = p_selcb->next) {
if (p_selcb->next == &select_cb) {
p_selcb->next = select_cb.next;
for (Libc::Select_cb *p_selcb = select_cb_list;
p_selcb;
p_selcb = p_selcb->next) {
if (p_selcb->next == &(*select_cb)) {
p_selcb->next = select_cb->next;
break;
}
}
}
select_cb_list_lock().unlock();
if (timeout.expired())
return 0;
destroy(env()->heap(), select_cb.sem);
/* not timed out -> results have been stored in select_cb by select_notify() */
if (timed_out) {
if (readfds)
FD_ZERO(readfds);
if (writefds)
FD_ZERO(writefds);
if (exceptfds)
FD_ZERO(exceptfds);
return 0;
}
if (readfds) *readfds = select_cb->readfds;
if (writefds) *writefds = select_cb->writefds;
if (exceptfds) *exceptfds = select_cb->exceptfds;
/* not timed out -> results have been stored in select_cb by select_notify() */
nready = select_cb.nready;
if (readfds)
*readfds = select_cb.readset;
if (writefds)
*writefds = select_cb.writeset;
if (exceptfds)
*exceptfds = select_cb.exceptset;
} else
select_cb_list_lock().unlock();
return nready;
return select_cb->nready;
}

View File

@ -1,11 +1,12 @@
/*
* \brief User-level task based libc
* \brief Libc kernel for main and pthreads user contexts
* \author Christian Helmuth
* \author Emery Hemingway
* \date 2016-01-22
*/
/*
* Copyright (C) 2016 Genode Labs GmbH
* Copyright (C) 2016-2017 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.
@ -13,12 +14,15 @@
/* Genode includes */
#include <base/component.h>
#include <base/log.h>
#include <base/thread.h>
#include <base/rpc_server.h>
#include <base/rpc_client.h>
#include <base/heap.h>
#include <base/attached_rom_dataspace.h>
#include <vfs/dir_file_system.h>
#include <timer_session/connection.h>
#include <os/timer.h>
/* libc includes */
#include <libc/component.h>
@ -28,36 +32,22 @@
#include <base/internal/unmanaged_singleton.h>
#include "vfs_plugin.h"
#include "libc_init.h"
/* escape sequences for highlighting debug message prefixes */
#define LIBC_ESC_START "\033[32m"
#define LIBC_ESC_END "\033[0m"
#define P(...) \
do { \
int dummy; \
using namespace Genode; \
Hex ctx((addr_t)&dummy >> 20, Hex::OMIT_PREFIX); \
log(LIBC_ESC_START "[", ctx, "] ", \
__PRETTY_FUNCTION__, ":", __LINE__, \
LIBC_ESC_END " ", ##__VA_ARGS__); \
} while (0)
#include "task.h"
namespace Libc {
class Env_implementation;
class Task;
class Kernel;
class Pthreads;
class Timer;
class Timer_accessor;
class Timeout;
class Timeout_handler;
using Microseconds = Genode::Time_source::Microseconds;
}
struct Task_resume
{
GENODE_RPC(Rpc_resume, void, resume);
GENODE_RPC_INTERFACE(Rpc_resume);
};
class Libc::Env_implementation : public Libc::Env
{
private:
@ -154,17 +144,179 @@ class Libc::Env_implementation : public Libc::Env
};
struct Libc::Timer
{
::Timer::Connection _timer_connection;
Genode::Timer _timer;
Timer(Genode::Env &env)
:
_timer_connection(env),
_timer(_timer_connection, env.ep())
{ }
unsigned long curr_time() const
{
return _timer.curr_time().value/1000;
}
static Microseconds microseconds(unsigned long timeout_ms)
{
return Microseconds(1000*timeout_ms);
}
static unsigned long max_timeout()
{
return Genode::Timer::Microseconds::max().value/1000;
}
};
/**
* Libc task
* Interface for obtaining the libc-global timer instance
*
* The libc task represents the "kernel" of the libc-based application.
* The 'Timer' is instantiated on demand whenever the 'Timer_accessor::timer'
* method is first called. This way, libc-using components do not depend of a
* timer connection unless they actually use time-related functionality.
*/
struct Libc::Timer_accessor
{
virtual Timer &timer() = 0;
};
struct Libc::Timeout_handler
{
virtual void handle_timeout() = 0;
};
/*
* TODO curr_time wrapping
*/
struct Libc::Timeout
{
Libc::Timer_accessor &_timer_accessor;
Timeout_handler &_handler;
Genode::One_shot_timeout<Timeout> _timeout;
bool _expired = true;
unsigned long _absolute_timeout_ms = 0;
void _handle(Microseconds now)
{
_expired = true;
_absolute_timeout_ms = 0;
_handler.handle_timeout();
}
Timeout(Timer_accessor &timer_accessor, Timeout_handler &handler)
:
_timer_accessor(timer_accessor),
_handler(handler),
_timeout(_timer_accessor.timer()._timer, *this, &Timeout::_handle)
{ }
void start(unsigned long timeout_ms)
{
unsigned long const now = _timer_accessor.timer().curr_time();
_expired = false;
_absolute_timeout_ms = now + timeout_ms;
_timeout.start(_timer_accessor.timer().microseconds(timeout_ms));
}
unsigned long duration_left() const
{
unsigned long const now = _timer_accessor.timer().curr_time();
return _expired ? 0 : _absolute_timeout_ms - now;
}
};
struct Libc::Pthreads
{
struct Pthread : Timeout_handler
{
Genode::Lock lock { Genode::Lock::LOCKED };
Pthread *next { nullptr };
Timeout _timeout;
Pthread(Timer_accessor &timer_accessor, unsigned long timeout_ms)
: _timeout(timer_accessor, *this)
{
if (timeout_ms > 0)
_timeout.start(timeout_ms);
}
void handle_timeout()
{
lock.unlock();
}
};
Genode::Lock mutex;
Pthread *pthreads = nullptr;
Timer_accessor &timer_accessor;
Pthreads(Timer_accessor &timer_accessor)
: timer_accessor(timer_accessor) { }
void resume_all()
{
Genode::Lock::Guard g(mutex);
for (Pthread *p = pthreads; p; p = p->next)
p->lock.unlock();
}
unsigned long suspend_myself(unsigned long timeout_ms)
{
Pthread myself { timer_accessor, timeout_ms };
{
Genode::Lock::Guard g(mutex);
myself.next = pthreads;
pthreads = &myself;
}
myself.lock.lock();
{
Genode::Lock::Guard g(mutex);
/* address of pointer to next pthread allows to change the head */
for (Pthread **next = &pthreads; *next; next = &(*next)->next) {
if (*next == &myself) {
*next = myself.next;
break;
}
}
}
return timeout_ms > 0 ? myself._timeout.duration_left() : 0;
}
};
/* internal utility */
static void resumed_callback();
static void suspended_callback();
/**
* Libc "kernel"
*
* This class represents the "kernel" of the libc-based application
* Blocking and deblocking happens here on libc functions like read() or
* select(). This combines blocking of the VFS backend and other signal sources
* (e.g., timers). The libc task runs on the component thread and allocates a
* secondary stack for the application task. Context switching uses
* setjmp/longjmp.
*/
class Libc::Task : public Genode::Rpc_object<Task_resume, Libc::Task>
struct Libc::Kernel
{
private:
@ -173,43 +325,185 @@ class Libc::Task : public Genode::Rpc_object<Task_resume, Libc::Task>
Env_implementation _libc_env { _env, _heap };
Vfs_plugin _vfs { _libc_env, _heap };
/**
* Application context and execution state
*/
bool _app_runnable = true;
jmp_buf _app_task;
jmp_buf _kernel_context;
jmp_buf _user_context;
Genode::Thread &_myself = *Genode::Thread::myself();
Genode::Thread &_myself { *Genode::Thread::myself() };
void *_app_stack = {
void *_user_stack = {
_myself.alloc_secondary_stack(_myself.name().string(),
Component::stack_size()) };
/**
* Libc context
*/
jmp_buf _libc_task;
Genode::Reconstructible<Genode::Signal_handler<Kernel>> _resume_main_handler {
_env.ep(), *this, &Kernel::_resume_main };
void (*_original_suspended_callback)() = nullptr;
enum State { KERNEL, USER };
State _state = KERNEL;
struct Timer_accessor : Libc::Timer_accessor
{
Genode::Env &_env;
/*
* The '_timer' is constructed by whatever thread (main thread
* of pthread) that uses a time-related function first. Hence,
* the construction must be protected by a lock.
*/
Genode::Lock _lock;
Genode::Constructible<Timer> _timer;
Timer_accessor(Genode::Env &env) : _env(env) { }
Timer &timer() override
{
Lock::Guard guard(_lock);
if (!_timer.constructed())
_timer.construct(_env);
return *_timer;
}
};
Timer_accessor _timer_accessor { _env };
struct Main_timeout : Timeout_handler
{
Genode::Signal_context_capability _signal_cap;
Timer_accessor &_timer_accessor;
Constructible<Timeout> _timeout;
void _construct_timeout_once()
{
if (!_timeout.constructed())
_timeout.construct(_timer_accessor, *this);
}
Main_timeout(Timer_accessor &timer_accessor)
: _timer_accessor(timer_accessor)
{ }
void timeout(unsigned long timeout_ms, Signal_context_capability signal_cap)
{
_signal_cap = signal_cap;
_construct_timeout_once();
_timeout->start(timeout_ms);
}
unsigned long duration_left()
{
_construct_timeout_once();
return _timeout->duration_left();
}
void handle_timeout()
{
/*
* XXX I don't dare to call _resume_main() here as this switches
* immediately to the user stack, which would result in dead lock
* if the calling context holds any lock in the timeout
* implementation.
*/
Genode::Signal_transmitter(_signal_cap).submit();
}
};
Main_timeout _main_timeout { _timer_accessor };
Pthreads _pthreads { _timer_accessor };
/**
* Trampoline to application code
* Trampoline to application (user) code
*
* This function is called by the main thread.
*/
static void _app_entry(Task *);
static void _user_entry(Libc::Kernel *kernel)
{
Libc::Component::construct(kernel->_libc_env);
/* executed in the context of the main thread */
static void _resumed_callback();
/* returned from user - switch stack to libc and return to dispatch loop */
kernel->_switch_to_kernel();
}
bool _main_context() const { return &_myself == Genode::Thread::myself(); }
/**
* Utility to switch main context to kernel
*
* User context must be saved explicitly before this function is called
* to enable _switch_to_user() later.
*/
void _switch_to_kernel()
{
_state = KERNEL;
_longjmp(_kernel_context, 1);
}
/**
* Utility to switch main context to user
*
* Kernel context must be saved explicitly before this function is called
* to enable _switch_to_kernel() later.
*/
void _switch_to_user()
{
_state = USER;
_longjmp(_user_context, 1);
}
/* called from signal handler */
void _resume_main()
{
if (!_main_context() || _state != KERNEL) {
Genode::error(__PRETTY_FUNCTION__, " called from non-kernel context");
return;
}
if (!_setjmp(_kernel_context))
_switch_to_user();
}
unsigned long _suspend_main(unsigned long timeout_ms)
{
if (timeout_ms > 0)
_main_timeout.timeout(timeout_ms, *_resume_main_handler);
if (!_setjmp(_user_context))
_switch_to_kernel();
return timeout_ms > 0 ? _main_timeout.duration_left() : 0;
}
public:
Task(Genode::Env &env) : _env(env) { }
Kernel(Genode::Env &env) : _env(env) { }
~Task() { Genode::error(__PRETTY_FUNCTION__, " should not be executed!"); }
~Kernel() { Genode::error(__PRETTY_FUNCTION__, " should not be executed!"); }
/**
* Setup kernel context and run libc application main context
*
* This function is called by the component thread at component
* construction time.
*/
void run()
{
/* save continuation of libc task (incl. current stack) */
if (!_setjmp(_libc_task)) {
/* _setjmp() returned directly -> switch to app stack and launch component */
call_func(_app_stack, (void *)_app_entry, (void *)this);
if (!_main_context() || _state != KERNEL) {
Genode::error(__PRETTY_FUNCTION__, " called from non-kernel context");
return;
}
/* save continuation of libc kernel (incl. current stack) */
if (!_setjmp(_kernel_context)) {
/* _setjmp() returned directly -> switch to user stack and launch component */
_state = USER;
call_func(_user_stack, (void *)_user_entry, (void *)this);
/* never reached */
}
@ -218,79 +512,122 @@ class Libc::Task : public Genode::Rpc_object<Task_resume, Libc::Task>
}
/**
* Called in the context of the entrypoint via RPC
* Resume all contexts (main and pthreads)
*/
void resume()
void resume_all()
{
if (!_setjmp(_libc_task))
_longjmp(_app_task, 1);
Genode::Signal_transmitter(*_resume_main_handler).submit();
_pthreads.resume_all();
}
/**
* Called from the app context (by fork)
* Suspend this context (main or pthread)
*/
void schedule_suspend(void(*suspended_callback) ())
unsigned long suspend(unsigned long timeout_ms)
{
if (_setjmp(_app_task))
if (timeout_ms > _timer_accessor.timer().max_timeout())
Genode::warning("libc: limiting exceeding timeout of ",
timeout_ms, " ms to maximum of ",
_timer_accessor.timer().max_timeout(), " ms");
timeout_ms = min(timeout_ms, _timer_accessor.timer().max_timeout());
return _main_context() ? _suspend_main(timeout_ms)
: _pthreads.suspend_myself(timeout_ms);
}
/**
* Called from the main context (by fork)
*/
void schedule_suspend(void(*original_suspended_callback) ())
{
if (_state != USER) {
Genode::error(__PRETTY_FUNCTION__, " called from non-user context");
return;
}
_env.ep().schedule_suspend(suspended_callback, _resumed_callback);
/*
* We hook into suspend-resume callback chain to destruct and
* reconstruct parts of the kernel from the context of the initial
* thread, i.e., without holding any object locks.
*/
_original_suspended_callback = original_suspended_callback;
_env.ep().schedule_suspend(suspended_callback, resumed_callback);
/* switch to libc task, which will return to entrypoint */
_longjmp(_libc_task, 1);
if (!_setjmp(_user_context))
_switch_to_kernel();
}
/**
* Called from the context of the initial thread
* Called from the context of the initial thread (on fork)
*/
void resumed()
void entrypoint_suspended()
{
Genode::Capability<Task_resume> cap = _env.ep().manage(*this);
cap.call<Task_resume::Rpc_resume>();
_env.ep().dissolve(*this);
_resume_main_handler.destruct();
_original_suspended_callback();
}
/**
* Called from the context of the initial thread (after fork)
*/
void entrypoint_resumed()
{
_resume_main_handler.construct(_env.ep(), *this, &Kernel::_resume_main);
Genode::Signal_transmitter(*_resume_main_handler).submit();
}
};
/******************************
** Libc task implementation **
******************************/
extern "C" void wait_for_continue(void);
void Libc::Task::_app_entry(Task *task)
{
Libc::Component::construct(task->_libc_env);
/* returned from task - switch stack to libc and return to dispatch loop */
_longjmp(task->_libc_task, 1);
}
/**
* Libc kernel singleton
*
* The singleton is implemented with the unmanaged-singleton utility
* in Component::construct() to ensure it is never destructed
* like normal static global objects. Otherwise, the task object may be
* destructed in a RPC to Rpc_resume, which would result in a deadlock.
*/
static Libc::Kernel *kernel;
/**
* Libc task singleton
* Main context execution was suspended (on fork)
*
* The singleton is implemented with the unmanaged-singleton utility to ensure
* it is never destructed like normal static global objects. Otherwise, the
* task object may be destructed in a RPC to Rpc_resume, which would result in
* a deadlock.
* This function is executed in the context of the initial thread.
*/
static Libc::Task *task;
static void suspended_callback() { kernel->entrypoint_suspended(); }
void Libc::Task::_resumed_callback() { task->resumed(); }
/**
* Resume main context execution (after fork)
*
* This function is executed in the context of the initial thread.
*/
static void resumed_callback() { kernel->entrypoint_resumed(); }
namespace Libc {
/*******************
** Libc task API **
*******************/
void schedule_suspend(void (*suspended) ())
{
if (!task) {
error("libc task handling not initialized, needed for suspend");
return;
}
task->schedule_suspend(suspended);
void Libc::resume_all() { kernel->resume_all(); }
unsigned long Libc::suspend(unsigned long timeout_ms)
{
return kernel->suspend(timeout_ms);
}
void Libc::schedule_suspend(void (*suspended) ())
{
if (!kernel) {
error("libc kernel not initialized, needed for suspend");
return;
}
kernel->schedule_suspend(suspended);
}
@ -306,8 +643,8 @@ void Component::construct(Genode::Env &env)
/* pass Genode::Env to libc subsystems that depend on it */
Libc::init_dl(env);
task = unmanaged_singleton<Libc::Task>(env);
task->run();
kernel = unmanaged_singleton<Libc::Kernel>(env);
kernel->run();
}

View File

@ -0,0 +1,61 @@
/*
* \brief Libc-internal kernel API
* \author Christian Helmuth
* \author Emery Hemingway
* \date 2016-12-14
*
* TODO document libc tasking including
* - the initial thread (which is neither component nor pthread)
* - processes incoming signals and forwards to entrypoint
* - the main thread (which is the kernel and the main user context)
* - pthreads (which are pthread user contexts only)
*/
/*
* Copyright (C) 2016-2017 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 _LIBC__TASK_H_
#define _LIBC__TASK_H_
/* Genode includes */
#include <os/timeout.h>
namespace Libc {
/**
* Resume all user contexts
*
* This resumes the main user context as well as any pthread context.
*/
void resume_all();
/**
* Suspend the execution of the calling user context
*
* \param timeout_ms maximum time to stay suspended in milliseconds,
* 0 for infinite suspend
*
* \return remaining duration until timeout,
* 0 if the timeout expired
*
* The context could be running on the component entrypoint as main context
* or as separate pthread. This function returns after the libc kernel
* resumed the user context execution.
*/
unsigned long suspend(unsigned long timeout_ms = 0UL);
/**
* Suspend main user context and the component entrypoint
*
* This interface is solely used by the implementation of fork().
*/
void schedule_suspend(void (*suspended) ());
}
#endif /* _LIBC__TASK_H_ */

View File

@ -30,7 +30,7 @@ install_config {
<provides><service name="Terminal"/></provides>
</start>
<start name="noux">
<resource name="RAM" quantum="64M"/>
<resource name="RAM" quantum="1G"/>
<config verbose="yes" stdin="/null" stdout="/log" stderr="/log">
<fstab>
<null/> <log/>