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 { - +