Noux: add timeout handling to select()

Previously there was not actual timeout handling. If a select() call
set an timeout it would be set to zero instead and was always handled
as blocking i/o. While this works fine for file descriptors which
will be triggerd externally (for example vim through terminal i/o) it
does not work at all for socket descriptors and network operations in
general.

So this commit introduces proper timeout handling and changes the
behaviour of SYSCALL_SELECT so that it now returns more than just
one descriptor at a time.

noux/minimal and noux/net now depend on thread and alarm libraries.
This commit is contained in:
Josef Söntgen 2012-08-20 16:13:21 +02:00 committed by Norman Feske
parent d4d229bdc7
commit ae524e4beb
5 changed files with 200 additions and 42 deletions

View File

@ -271,17 +271,31 @@ extern "C" int select(int nfds, fd_set *readfds, fd_set *writefds,
int *dst = in_fds.array;
size_t dst_len = Noux::Sysio::Select_fds::MAX_FDS;
in_fds.num_rd = marshal_fds(readfds, nfds, dst, dst_len);
/**
* These variables are used in max_fds_exceeded() calculation, so
* they need to be proper initialized.
*/
in_fds.num_rd = 0;
in_fds.num_wr = 0;
in_fds.num_ex = 0;
dst += in_fds.num_rd;
dst_len -= in_fds.num_rd;
if (readfds != NULL) {
in_fds.num_rd = marshal_fds(readfds, nfds, dst, dst_len);
in_fds.num_wr = marshal_fds(writefds, nfds, dst, dst_len);
dst += in_fds.num_rd;
dst_len -= in_fds.num_rd;
}
dst += in_fds.num_wr;
dst_len -= in_fds.num_wr;
if (writefds != NULL) {
in_fds.num_wr = marshal_fds(writefds, nfds, dst, dst_len);
in_fds.num_ex = marshal_fds(exceptfds, nfds, dst, dst_len);
dst += in_fds.num_wr;
dst_len -= in_fds.num_wr;
}
if (exceptfds != NULL) {
in_fds.num_ex = marshal_fds(exceptfds, nfds, dst, dst_len);
}
if (in_fds.max_fds_exceeded()) {
errno = ENOMEM;
@ -294,14 +308,6 @@ extern "C" int select(int nfds, fd_set *readfds, fd_set *writefds,
if (timeout) {
sysio()->select_in.timeout.sec = timeout->tv_sec;
sysio()->select_in.timeout.usec = timeout->tv_usec;
if (!sysio()->select_in.timeout.zero()) {
// PDBG("timeout=%d,%d -> replaced by zero timeout",
// (int)timeout->tv_sec, (int)timeout->tv_usec);
sysio()->select_in.timeout.sec = 0;
sysio()->select_in.timeout.usec = 0;
}
} else {
sysio()->select_in.timeout.set_infinite();
}
@ -323,18 +329,29 @@ extern "C" int select(int nfds, fd_set *readfds, fd_set *writefds,
Noux::Sysio::Select_fds &out_fds = sysio()->select_out.fds;
int *src = out_fds.array;
int total_fds = 0;
unmarshal_fds(src, out_fds.num_rd, readfds);
src += out_fds.num_rd;
if (readfds != NULL) {
unmarshal_fds(src, out_fds.num_rd, readfds);
src += out_fds.num_rd;
total_fds += out_fds.num_rd;
}
unmarshal_fds(src, out_fds.num_wr, writefds);
src += out_fds.num_wr;
if (writefds != NULL) {
unmarshal_fds(src, out_fds.num_wr, writefds);
src += out_fds.num_wr;
total_fds += out_fds.num_wr;
}
unmarshal_fds(src, out_fds.num_ex, exceptfds);
if (exceptfds != NULL) {
unmarshal_fds(src, out_fds.num_ex, exceptfds);
/* exceptfds are currently ignored */
}
return out_fds.total_fds();
return total_fds;
}
#include <setjmp.h>
#include <base/platform_env.h>

View File

@ -60,6 +60,12 @@ namespace Noux {
*/
Pid_allocator *pid_allocator();
/**
* Return singleton instance of timeout scheduler
*/
class Timeout_scheduler;
Timeout_scheduler *timeout_scheduler();
class Child;

View File

@ -14,6 +14,8 @@
/* Genode includes */
#include <cap_session/connection.h>
#include <os/config.h>
#include <os/alarm.h>
#include <timer_session/connection.h>
/* Noux includes */
#include <child.h>
@ -41,6 +43,75 @@ extern void (*close_socket)(int);
extern void init_network();
/**
* Timeout thread for SYSCALL_SELECT
*/
namespace Noux {
using namespace Genode;
class Timeout_scheduler : Thread<4096>, public Alarm_scheduler
{
private:
Timer::Connection _timer;
Alarm::Time _curr_time;
enum { TIMER_GRANULARITY_MSEC = 10 };
void entry()
{
for (;;) {
_timer.msleep(TIMER_GRANULARITY_MSEC);
Alarm_scheduler::handle(_curr_time);
_curr_time += TIMER_GRANULARITY_MSEC;
}
}
public:
Timeout_scheduler() : _curr_time(0) { start(); }
Alarm::Time curr_time() const { return _curr_time; }
};
struct Timeout_state
{
bool timed_out;
Timeout_state() : timed_out(false) { }
};
class Timeout_alarm : public Alarm
{
private:
Timeout_state *_state;
Semaphore *_blocker;
Timeout_scheduler *_scheduler;
public:
Timeout_alarm(Timeout_state *st, Semaphore *blocker, Timeout_scheduler *scheduler, Time timeout)
:
_state(st),
_blocker(blocker),
_scheduler(scheduler)
{
_scheduler->schedule_absolute(this, _scheduler->curr_time() + timeout);
_state->timed_out = false;
}
void discard() { _scheduler->discard(this); }
protected:
bool on_alarm()
{
_state->timed_out = true;
_blocker->up();
return false;
}
};
};
/*****************************
** Noux syscall dispatcher **
*****************************/
@ -231,6 +302,14 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
case SYSCALL_SELECT:
{
Sysio::Select_fds &in_fds = _sysio->select_in.fds;
size_t in_fds_total = in_fds.total_fds();
int _rd_array[in_fds_total];
int _wr_array[in_fds_total];
long timeout_sec = _sysio->select_in.timeout.sec;
long timeout_usec = _sysio->select_in.timeout.usec;
bool timeout_reached = false;
/*
* Block for one action of the watched file descriptors
@ -242,7 +321,12 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
* unblock condition. Return if one I/O channel satisfies
* the condition.
*/
for (Genode::size_t i = 0; i < in_fds.total_fds(); i++) {
size_t unblock_rd = 0;
size_t unblock_wr = 0;
size_t unblock_ex = 0;
/* process read fds */
for (Genode::size_t i = 0; i < in_fds_total; i++) {
int fd = in_fds.array[i];
if (!fd_in_use(fd)) continue;
@ -250,29 +334,50 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
Shared_pointer<Io_channel> io = io_channel_by_fd(fd);
if (io->check_unblock(in_fds.watch_for_rd(i),
in_fds.watch_for_wr(i),
in_fds.watch_for_ex(i))) {
in_fds.watch_for_wr(i),
in_fds.watch_for_ex(i))) {
/*
* Return single file descriptor that triggered the
* unblocking. For now, only a single file
* descriptor is returned on each call of select.
*/
Sysio::Select_fds &out_fds = _sysio->select_out.fds;
if (io->check_unblock(true, false, false)) {
_rd_array[unblock_rd++] = fd;
// io->clear_unblock(true, false, false);
}
if (io->check_unblock(false, true, false)) {
_wr_array[unblock_wr++] = fd;
// io->clear_unblock(false, true, false);
}
out_fds.array[0] = fd;
out_fds.num_rd = io->check_unblock(true, false, false);
out_fds.num_wr = io->check_unblock(false, true, false);
out_fds.num_ex = io->check_unblock(false, false, true);
if (io->check_unblock(false, false, true)) {
unblock_ex++;
// io->clear_unblock(false, false, true);
}
return true;
}
}
if (unblock_rd || unblock_wr || unblock_ex) {
for (size_t i = 0; i < unblock_rd; i++) {
_sysio->select_out.fds.array[i] = _rd_array[i];
_sysio->select_out.fds.num_rd = unblock_rd;
}
for (size_t i = 0; i < unblock_wr; i++) {
_sysio->select_out.fds.array[i] = _wr_array[i];
_sysio->select_out.fds.num_wr = unblock_wr;
}
_sysio->select_out.fds.num_ex = unblock_ex;
return true;
}
/*
* Return if I/O channel triggered, but timeout exceeded
*/
if (_sysio->select_in.timeout.zero()) {
if (_sysio->select_in.timeout.zero() || timeout_reached) {
/*
if (timeout_reached) PINF("timeout_reached");
else PINF("timeout.zero()");
*/
_sysio->select_out.fds.num_rd = 0;
_sysio->select_out.fds.num_wr = 0;
_sysio->select_out.fds.num_ex = 0;
@ -292,9 +397,10 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
* conditions such as the destruction of the child.
* ...to be done.
*/
Wake_up_notifier notifiers[in_fds.total_fds()];
for (Genode::size_t i = 0; i < in_fds.total_fds(); i++) {
Wake_up_notifier notifiers[in_fds_total];
for (Genode::size_t i = 0; i < in_fds_total; i++) {
int fd = in_fds.array[i];
if (!fd_in_use(fd)) continue;
@ -306,18 +412,42 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
/*
* Block at barrier except when reaching the timeout
*/
_blocker.down();
if (!_sysio->select_in.timeout.infinite()) {
unsigned long to_msec = (timeout_sec * 1000) + (timeout_usec / 1000);
Timeout_state ts;
Timeout_alarm ta(&ts, &_blocker, Noux::timeout_scheduler(), to_msec);
/* block until timeout is reached or we were unblocked */
_blocker.down();
if (ts.timed_out) {
timeout_reached = 1;
}
else {
/*
* We woke up before reaching the timeout,
* so we discard the alarm
*/
ta.discard();
}
}
else {
/* let's block infinitely */
_blocker.down();
}
/*
* Unregister barrier at watched I/O channels
*/
for (Genode::size_t i = 0; i < in_fds.total_fds(); i++) {
for (Genode::size_t i = 0; i < in_fds_total; i++) {
int fd = in_fds.array[i];
if (!fd_in_use(fd)) continue;
Shared_pointer<Io_channel> io = io_channel_by_fd(fd);
io->unregister_wake_up_notifier(&notifiers[i]);
}
}
return true;
@ -567,6 +697,11 @@ Noux::Pid_allocator *Noux::pid_allocator()
return &inst;
}
Noux::Timeout_scheduler *Noux::timeout_scheduler()
{
static Noux::Timeout_scheduler inst;
return &inst;
}
void *operator new (Genode::size_t size) {
return Genode::env()->heap()->alloc(size); }

View File

@ -1,5 +1,5 @@
TARGET = noux
LIBS = cxx env server process signal
LIBS = cxx env server process signal thread alarm
SRC_CC = main.cc dummy_net.cc
INC_DIR += $(PRG_DIR)
INC_DIR += $(PRG_DIR)/../

View File

@ -1,5 +1,5 @@
TARGET = noux_net
LIBS = cxx env server process signal lwip
LIBS = cxx env server process signal lwip thread alarm
LIBS += libc libc_lwip