genode/repos/libports/src/lib/libc/select.cc
Christian Helmuth eab477370f libc: select support for components
Libc components cannot use regular calls to select() as this may suspend
their execution. In this case incoming RPCs will be deferred until
select() returns and the component returns to the entrypoint dispatch
loop. The Libc::Signal_handler solves this problem with a its select()
that either returns the currently ready file descriptors immediately or
calls the registered handler function during libc resume.
2017-02-28 12:59:17 +01:00

405 lines
9.8 KiB
C++

/*
* \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-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 <util/reconstructible.h>
/* Libc includes */
#include <libc-plugin/plugin_registry.h>
#include <libc-plugin/plugin.h>
#include <libc/select.h>
#include <stdlib.h>
#include <sys/select.h>
#include <signal.h>
#include "task.h"
namespace Libc {
struct Select_cb;
struct Select_cb_list;
}
void (*libc_select_notify)() __attribute__((weak));
/** Description for a task waiting in select */
struct Libc::Select_cb
{
Select_cb *next = nullptr;
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)
{ }
};
struct Libc::Select_cb_list
{
Genode::Lock _mutex;
Select_cb *_first = nullptr;
struct Guard : Genode::Lock::Guard
{
Select_cb_list *l;
Guard(Select_cb_list &list) : Genode::Lock::Guard(list._mutex), l(&list) { }
};
void unsynchronized_insert(Select_cb *scb)
{
scb->next = _first;
_first = scb;
}
void insert(Select_cb *scb)
{
Guard guard(*this);
unsynchronized_insert(scb);
}
void remove(Select_cb *scb)
{
Guard guard(*this);
/* address of pointer to next allows to change the head */
for (Select_cb **next = &_first; *next; next = &(*next)->next) {
if (*next == scb) {
*next = scb->next;
break;
}
}
}
template <typename FUNC>
void for_each(FUNC const &func)
{
Guard guard(*this);
for (Select_cb *scb = _first; scb; scb = scb->next)
func(*scb);
}
};
/** The global list of tasks waiting for select */
static Libc::Select_cb_list select_cb_list;
/**
* 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 };
/* temporary fd sets that are passed to the plugins */
int plugin_nready;
fd_set plugin_readfds;
fd_set plugin_writefds;
fd_set plugin_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 (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;
plugin_writefds = *in_writefds;
plugin_exceptfds = *in_exceptfds;
plugin_nready = plugin->select(nfds, &plugin_readfds, &plugin_writefds, &plugin_exceptfds, &tv_0);
if (plugin_nready > 0) {
for (int libc_fd = 0; libc_fd < nfds; libc_fd++) {
if (out_readfds && FD_ISSET(libc_fd, &plugin_readfds)) {
FD_SET(libc_fd, out_readfds);
}
if (out_writefds && FD_ISSET(libc_fd, &plugin_writefds)) {
FD_SET(libc_fd, out_writefds);
}
if (out_exceptfds && FD_ISSET(libc_fd, &plugin_exceptfds)) {
FD_SET(libc_fd, out_exceptfds);
}
}
nready += plugin_nready;
} else if (plugin_nready < 0) {
Genode::error("plugin->select() returned error value ", plugin_nready);
}
}
}
return nready;
}
/* this function gets called by plugin backends when file descripors become ready */
static void select_notify()
{
bool resume_all = false;
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 all up */
select_cb_list.for_each([&] (Libc::Select_cb &scb) {
scb.nready = selscan(scb.nfds,
&scb.readfds, &scb.writefds, &scb.exceptfds,
&tmp_readfds, &tmp_writefds, &tmp_exceptfds);
if (scb.nready > 0) {
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 *tv)
{
fd_set in_readfds, in_writefds, in_exceptfds;
Genode::Constructible<Libc::Select_cb> select_cb;
/* initialize the select notification function pointer */
if (!libc_select_notify)
libc_select_notify = select_notify;
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);
{
/*
* We use the guard directly to atomically check is any descripor is
* ready, and insert into select-callback list otherwise.
*/
Libc::Select_cb_list::Guard guard(select_cb_list);
int const nready = selscan(nfds,
&in_readfds, &in_writefds, &in_exceptfds,
readfds, writefds, exceptfds);
/* return if any descripor is ready */
if (nready)
return nready;
/* return on zero-timeout */
if (tv && (tv->tv_sec) == 0 && (tv->tv_usec == 0))
return 0;
/* suspend as we don't have any immediate events */
select_cb.construct(nfds, in_readfds, in_writefds, in_exceptfds);
select_cb_list.unsynchronized_insert(&(*select_cb));
}
struct Timeout
{
timeval const *_tv;
bool const valid { _tv != nullptr };
unsigned long duration { valid ? _tv->tv_sec*1000 + _tv->tv_usec/1000 : 0UL };
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);
select_cb_list.remove(&(*select_cb));
if (timeout.expired())
return 0;
/* not timed out -> results have been stored in select_cb by select_notify() */
if (readfds) *readfds = select_cb->readfds;
if (writefds) *writefds = select_cb->writefds;
if (exceptfds) *exceptfds = select_cb->exceptfds;
return select_cb->nready;
}
extern "C" int
__attribute__((weak))
select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout)
{
return _select(nfds, readfds, writefds, exceptfds, timeout);
}
extern "C" int
__attribute__((weak))
_pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask)
{
struct timeval tv;
sigset_t origmask;
int nready;
if (timeout) {
tv.tv_usec = timeout->tv_nsec / 1000;
tv.tv_sec = timeout->tv_sec;
}
if (sigmask)
sigprocmask(SIG_SETMASK, sigmask, &origmask);
nready = select(nfds, readfds, writefds, exceptfds, &tv);
if (sigmask)
sigprocmask(SIG_SETMASK, &origmask, NULL);
return nready;
}
extern "C" int
__attribute__((weak))
pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask)
{
return _pselect(nfds, readfds, writefds, exceptfds, timeout, sigmask);
}
/****************************************
** Select handler for libc components **
****************************************/
int Libc::Select_handler_base::select(int nfds, fd_set &readfds,
fd_set &writefds, fd_set &exceptfds)
{
fd_set in_readfds, in_writefds, in_exceptfds;
/* initialize the select notification function pointer */
if (!libc_select_notify)
libc_select_notify = select_notify;
in_readfds = readfds;
in_writefds = writefds;
in_exceptfds = exceptfds;
/* remove potentially enqueued callback from list */
if (_select_cb->constructed())
select_cb_list.remove(&(**_select_cb));
{
/*
* We use the guard directly to atomically check is any descripor is
* ready, and insert into select-callback list otherwise.
*/
Libc::Select_cb_list::Guard guard(select_cb_list);
int const nready = selscan(nfds,
&in_readfds, &in_writefds, &in_exceptfds,
&readfds, &writefds, &exceptfds);
/* return if any descripor is ready */
if (nready)
return nready;
/* suspend as we don't have any immediate events */
_select_cb->construct(nfds, readfds, writefds, exceptfds);
select_cb_list.unsynchronized_insert(&(**_select_cb));
}
Libc::schedule_select(this);
return 0;
}
void Libc::Select_handler_base::dispatch_select()
{
Select_handler_cb &select_cb = *_select_cb;
if (select_cb->nready == 0) return;
select_cb_list.remove(&(*select_cb));
Libc::schedule_select(nullptr);
select_ready(select_cb->nready, select_cb->readfds,
select_cb->writefds, select_cb->exceptfds);
}
Libc::Select_handler_base::Select_handler_base()
:
_select_cb((Select_handler_cb*)malloc(sizeof(*_select_cb)))
{ }
Libc::Select_handler_base::~Select_handler_base()
{ }