mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-10 21:01:49 +00:00
libc: use task switch in select()
This commit is contained in:
parent
59c0796820
commit
0f6800b20f
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
61
repos/libports/src/lib/libc/task.h
Normal file
61
repos/libports/src/lib/libc/task.h
Normal 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_ */
|
@ -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/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user