diff --git a/repos/libports/lib/mk/libc.mk b/repos/libports/lib/mk/libc.mk
index b4d6dcdd81..e75ed40526 100644
--- a/repos/libports/lib/mk/libc.mk
+++ b/repos/libports/lib/mk/libc.mk
@@ -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
diff --git a/repos/libports/src/lib/libc/select.cc b/repos/libports/src/lib/libc/select.cc
index 3fc5c587b5..51f54e56db 100644
--- a/repos/libports/src/lib/libc/select.cc
+++ b/repos/libports/src/lib/libc/select.cc
@@ -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
-#include
+#include
+/* Libc includes */
#include
#include
-
#include
#include
-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 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;
}
diff --git a/repos/libports/src/lib/libc/task.cc b/repos/libports/src/lib/libc/task.cc
index 6e1923282f..870ac6b7c8 100644
--- a/repos/libports/src/lib/libc/task.cc
+++ b/repos/libports/src/lib/libc/task.cc
@@ -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
+#include
#include
#include
#include
#include
#include
#include
+#include
+#include
/* libc includes */
#include
@@ -28,36 +32,22 @@
#include
#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;
+
+ 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
+struct Libc::Kernel
{
private:
@@ -173,43 +325,185 @@ class Libc::Task : public Genode::Rpc_object
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> _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_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;
+
+ 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
}
/**
- * 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 cap = _env.ep().manage(*this);
- cap.call();
- _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(env);
- task->run();
+ kernel = unmanaged_singleton(env);
+ kernel->run();
}
diff --git a/repos/libports/src/lib/libc/task.h b/repos/libports/src/lib/libc/task.h
new file mode 100644
index 0000000000..19bffd86b8
--- /dev/null
+++ b/repos/libports/src/lib/libc/task.h
@@ -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
+
+
+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_ */
diff --git a/repos/ports/run/noux_fork.run b/repos/ports/run/noux_fork.run
index 3f0667ae1f..fad5d4dd72 100644
--- a/repos/ports/run/noux_fork.run
+++ b/repos/ports/run/noux_fork.run
@@ -30,7 +30,7 @@ install_config {
-
+