Add pipe and dup2 syscalls to Noux

Fixes #133.
This commit is contained in:
Norman Feske 2012-03-19 22:52:26 +01:00
parent 48739422ac
commit afeb54ebed
11 changed files with 505 additions and 46 deletions

View File

@ -49,6 +49,8 @@ namespace Noux {
SYSCALL_FORK,
SYSCALL_GETPID,
SYSCALL_WAIT4,
SYSCALL_PIPE,
SYSCALL_DUP2,
SYSCALL_INVALID = -1
};
@ -72,6 +74,8 @@ namespace Noux {
NOUX_DECL_SYSCALL_NAME(FORK)
NOUX_DECL_SYSCALL_NAME(GETPID)
NOUX_DECL_SYSCALL_NAME(WAIT4)
NOUX_DECL_SYSCALL_NAME(PIPE)
NOUX_DECL_SYSCALL_NAME(DUP2)
case SYSCALL_INVALID: return 0;
}
return 0;

View File

@ -256,6 +256,9 @@ namespace Noux {
SYSIO_DECL(wait4, { int pid; bool nohang; },
{ int pid; int status; });
SYSIO_DECL(pipe, { }, { int fd[2]; });
SYSIO_DECL(dup2, { int fd; int to_fd; }, { });
};
};
};

View File

@ -498,10 +498,12 @@ namespace {
bool supports_chdir(const char *) { return true; }
bool supports_open(const char *, int) { return true; }
bool supports_stat(const char *) { return true; }
bool supports_pipe() { return true; }
Libc::File_descriptor *open(const char *, int);
ssize_t write(Libc::File_descriptor *, const void *, ::size_t);
int close(Libc::File_descriptor *);
int dup2(Libc::File_descriptor *, Libc::File_descriptor *);
int fstat(Libc::File_descriptor *, struct stat *);
int fstatfs(Libc::File_descriptor *, struct statfs *);
int fcntl(Libc::File_descriptor *, int, long);
@ -511,6 +513,7 @@ namespace {
ssize_t read(Libc::File_descriptor *, void *, ::size_t);
int stat(const char *, struct stat *);
int ioctl(Libc::File_descriptor *, int request, char *argp);
int pipe(Libc::File_descriptor *pipefd[2]);
};
@ -539,7 +542,7 @@ namespace {
}
Libc::Plugin_context *context = noux_context(sysio()->open_out.fd);
return Libc::file_descriptor_allocator()->alloc(this, context);
return Libc::file_descriptor_allocator()->alloc(this, context, sysio()->open_out.fd);
}
@ -600,9 +603,6 @@ namespace {
Genode::memcpy(buf, sysio()->read_out.chunk, sysio()->read_out.count);
// for (int i = 0; i < sysio()->read_out.count; i++)
// Genode::printf("read %d\n", ((char *)buf)[i]);
sum_read_count += sysio()->read_out.count;
if (sysio()->read_out.count < sysio()->read_in.count)
@ -626,6 +626,7 @@ namespace {
/* XXX set errno */
return -1;
}
Libc::file_descriptor_allocator()->free(fd);
return 0;
}
@ -696,6 +697,44 @@ namespace {
}
int Plugin::pipe(Libc::File_descriptor *pipefd[2])
{
/* perform syscall */
if (!noux()->syscall(Noux::Session::SYSCALL_PIPE)) {
PERR("pipe error");
/* XXX set errno */
return -1;
}
for (int i = 0; i < 2; i++) {
Libc::Plugin_context *context = noux_context(sysio()->pipe_out.fd[i]);
pipefd[i] = Libc::file_descriptor_allocator()->alloc(this, context, sysio()->pipe_out.fd[i]);
}
return 0;
}
int Plugin::dup2(Libc::File_descriptor *fd, Libc::File_descriptor *new_fd)
{
/*
* We use a one-to-one mapping of libc fds and Noux fds.
*/
new_fd->context = noux_context(new_fd->libc_fd);
sysio()->dup2_in.fd = noux_fd(fd->context);
sysio()->dup2_in.to_fd = noux_fd(new_fd->context);
/* perform syscall */
if (!noux()->syscall(Noux::Session::SYSCALL_DUP2)) {
PERR("dup2 error");
/* XXX set errno */
return -1;
}
return 0;
}
int Plugin::fstat(Libc::File_descriptor *fd, struct stat *buf)
{
sysio()->fstat_in.fd = noux_fd(fd->context);

View File

@ -90,6 +90,14 @@ namespace Noux {
/* trigger exit of main event loop */
init_process_exited();
} else {
/* destroy 'Noux::Child' */
destroy(Genode::env()->heap(), _child);
PINF("destroy %p", _child);
PINF("quota: avail=%zd, used=%zd",
Genode::env()->ram_session()->avail(),
Genode::env()->ram_session()->used());
}
}
};
@ -110,7 +118,6 @@ namespace Noux {
void dispatch()
{
PINF("execve cleanup dispatcher called");
destroy(env()->heap(), _child);
}
};
@ -240,6 +247,14 @@ namespace Noux {
child->add_io_channel(io_channel_by_fd(fd), fd);
}
void _block_for_io_channel(Shared_pointer<Io_channel> &io)
{
Wake_up_notifier notifier(&_blocker);
io->register_wake_up_notifier(&notifier);
_blocker.down();
io->unregister_wake_up_notifier(&notifier);
}
public:
/**
@ -316,6 +331,11 @@ namespace Noux {
_resources.cpu.start_main_thread(ip, sp);
}
void submit_exit_signal()
{
Signal_transmitter(_exit_context_cap).submit();
}
Ram_session_capability ram() const { return _resources.ram.cap(); }
Rm_session_capability rm() const { return _resources.rm.cap(); }
Dataspace_registry &ds_registry() { return _resources.ds_registry; }

View File

@ -28,6 +28,8 @@
namespace Noux {
using namespace Genode;
/**
* Input/output channel interface
*/
@ -39,18 +41,20 @@ namespace Noux {
* List of notifiers (i.e., processes) used by threads that block
* for an I/O-channel event
*/
Genode::List<Wake_up_notifier> _notifiers;
Genode::Lock _notifiers_lock;
List<Wake_up_notifier> _notifiers;
Lock _notifiers_lock;
public:
virtual bool write(Sysio *sysio) { return false; }
virtual bool read(Sysio *sysio) { return false; }
virtual bool fstat(Sysio *sysio) { return false; }
virtual bool fcntl(Sysio *sysio) { return false; }
virtual bool fchdir(Sysio *sysio, Pwd *pwd) { return false; }
virtual bool dirent(Sysio *sysio) { return false; }
virtual bool ioctl(Sysio *sysio) { return false; }
virtual ~Io_channel() { }
virtual bool write(Sysio *sysio, size_t &count) { return false; }
virtual bool read(Sysio *sysio) { return false; }
virtual bool fstat(Sysio *sysio) { return false; }
virtual bool fcntl(Sysio *sysio) { return false; }
virtual bool fchdir(Sysio *sysio, Pwd *pwd) { return false; }
virtual bool dirent(Sysio *sysio) { return false; }
virtual bool ioctl(Sysio *sysio) { return false; }
/**
* Return true if an unblocking condition of the channel is satisfied
@ -62,8 +66,6 @@ namespace Noux {
virtual bool check_unblock(bool rd, bool wr, bool ex) const {
return false; }
virtual ~Io_channel() { };
/**
* Register blocker for getting waked up on an I/O channel event
*
@ -72,7 +74,7 @@ namespace Noux {
*/
void register_wake_up_notifier(Wake_up_notifier *notifier)
{
Genode::Lock::Guard guard(_notifiers_lock);
Lock::Guard guard(_notifiers_lock);
_notifiers.insert(notifier);
}
@ -86,13 +88,13 @@ namespace Noux {
*/
void unregister_wake_up_notifier(Wake_up_notifier *notifier)
{
Genode::Lock::Guard guard(_notifiers_lock);
Lock::Guard guard(_notifiers_lock);
_notifiers.remove(notifier);
}
/**
* Tell all registered notifiers about an occurred I/O channel
* Tell all registered notifiers about an occurred I/O event
*
* This function is called by I/O channel implementations that
* respond to external signals, e.g., the availability of new
@ -100,7 +102,7 @@ namespace Noux {
*/
void invoke_all_notifiers()
{
Genode::Lock::Guard guard(_notifiers_lock);
Lock::Guard guard(_notifiers_lock);
for (Wake_up_notifier *n = _notifiers.first(); n; n = n->next())
n->wake_up();

View File

@ -29,9 +29,9 @@
* ;- run 'cat' (read syscall)
* ;- execve
* ;- fork
* - pipe
* ;- pipe
* ;- read init binary from vfs
* - import env into child (execve and fork)
* ;- import env into child (execve and fork)
* ;- shell
* - debug 'find'
* - stacked file system infrastructure
@ -48,6 +48,7 @@
#include <vfs_io_channel.h>
#include <terminal_io_channel.h>
#include <dummy_input_io_channel.h>
#include <pipe_io_channel.h>
#include <root_file_system.h>
#include <tar_file_system.h>
@ -86,14 +87,36 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
sizeof(_sysio->getcwd_out.path));
return true;
case SYSCALL_WRITE:
{
size_t const count_in = _sysio->write_in.count;
return _lookup_channel(_sysio->write_in.fd)->write(_sysio);
for (size_t count = 0; count != count_in; ) {
Shared_pointer<Io_channel> io = _lookup_channel(_sysio->write_in.fd);
if (!io->check_unblock(false, true, false))
_block_for_io_channel(io);
/*
* 'io->write' is expected to update 'write_out.count'
*/
if (io->write(_sysio, count) == false)
return false;
}
return true;
}
case SYSCALL_READ:
{
Shared_pointer<Io_channel> io = _lookup_channel(_sysio->read_in.fd);
return _lookup_channel(_sysio->read_in.fd)->read(_sysio);
while (!io->check_unblock(true, false, false))
_block_for_io_channel(io);
io->read(_sysio);
return true;
}
case SYSCALL_STAT:
case SYSCALL_LSTAT: /* XXX implement difference between 'lstat' and 'stat' */
@ -113,7 +136,7 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
{
Absolute_path absolute_path(_sysio->open_in.path, _env.pwd());
PWRN("open pwd=%s path=%s", _env.pwd(), _sysio->open_in.path);
PINF("open pwd=%s path=%s", _env.pwd(), _sysio->open_in.path);
/* remember mode only for debug output */
int const mode = _sysio->open_in.mode;
@ -123,7 +146,7 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
return false;
Shared_pointer<Io_channel> channel(new Vfs_io_channel(absolute_path.base(), _vfs, vfs_handle),
Genode::env()->heap());
Genode::env()->heap());
_sysio->open_out.fd = add_io_channel(channel);
@ -134,8 +157,6 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
case SYSCALL_CLOSE:
{
int const fd = _sysio->close_in.fd;
PINF("close fd %d", fd);
remove_io_channel(_sysio->close_in.fd);
return true;
}
@ -208,7 +229,6 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
_assign_io_channels_to(child);
/* signal main thread to remove ourself */
PINF("submit signal to _execve_cleanup_context_cap");
Genode::Signal_transmitter(_execve_cleanup_context_cap).submit();
/* start executing the new process */
@ -373,12 +393,8 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
_sysio->wait4_out.status = exited->exit_status();
Family_member::remove(exited);
/* destroy 'Noux::Child' */
destroy(Genode::env()->heap(), exited);
PINF("quota: avail=%zd, used=%zd",
Genode::env()->ram_session()->avail(),
Genode::env()->ram_session()->used());
PINF("submit exit signal for PID %d", exited->pid());
static_cast<Child *>(exited)->submit_exit_signal();
} else {
_sysio->wait4_out.pid = 0;
@ -387,6 +403,28 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
return true;
}
case SYSCALL_PIPE:
{
Shared_pointer<Pipe> pipe(new Pipe, Genode::env()->heap());
Shared_pointer<Io_channel> pipe_sink(new Pipe_sink_io_channel(pipe, *_sig_rec),
Genode::env()->heap());
Shared_pointer<Io_channel> pipe_source(new Pipe_source_io_channel(pipe, *_sig_rec),
Genode::env()->heap());
_sysio->pipe_out.fd[0] = add_io_channel(pipe_source);
_sysio->pipe_out.fd[1] = add_io_channel(pipe_sink);
return true;
}
case SYSCALL_DUP2:
{
add_io_channel(io_channel_by_fd(_sysio->dup2_in.fd),
_sysio->dup2_in.to_fd);
return true;
}
case SYSCALL_INVALID: break;
}
}
@ -588,9 +626,8 @@ int main(int argc, char **argv)
Signal_dispatcher *dispatcher =
static_cast<Signal_dispatcher *>(signal.context());
if (dispatcher)
for (int i = 0; i < signal.num(); i++)
dispatcher->dispatch();
for (int i = 0; i < signal.num(); i++)
dispatcher->dispatch();
}
PINF("-- exiting noux ---");

View File

@ -0,0 +1,339 @@
/*
* \brief I/O channels for pipe input/output
* \author Norman Feske
* \date 2012-03-19
*/
/*
* Copyright (C) 2012 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 _NOUX__PIPE_IO_CHANNEL_H_
#define _NOUX__PIPE_IO_CHANNEL_H_
/* Noux includes */
#include <io_channel.h>
namespace Noux {
using namespace Genode;
class Pipe : public Reference_counter
{
private:
Lock mutable _lock;
enum { BUFFER_SIZE = 4096 };
char _buffer[BUFFER_SIZE];
unsigned _read_offset;
unsigned _write_offset;
Signal_context_capability _read_ready_sigh;
Signal_context_capability _write_ready_sigh;
bool _writer_is_gone;
/**
* Return space available in the buffer for writing, in bytes
*/
size_t _avail_buffer_space() const
{
if (_read_offset < _write_offset)
return (BUFFER_SIZE - _write_offset) + _read_offset - 1;
if (_read_offset > _write_offset)
return _read_offset - _write_offset - 1;
/* _read_offset == _write_offset */
return BUFFER_SIZE - 1;
}
bool _any_space_avail_for_writing() const
{
return _avail_buffer_space() > 0;;
}
void _wake_up_reader()
{
if (_read_ready_sigh.valid())
Signal_transmitter(_read_ready_sigh).submit();
}
void _wake_up_writer()
{
if (_write_ready_sigh.valid())
Signal_transmitter(_write_ready_sigh).submit();
}
public:
Pipe()
: _read_offset(0), _write_offset(0), _writer_is_gone(false) { }
~Pipe()
{
Lock::Guard guard(_lock);
}
void writer_close()
{
Lock::Guard guard(_lock);
_writer_is_gone = true;
_write_ready_sigh = Signal_context_capability();
_wake_up_reader();
}
void reader_close()
{
Lock::Guard guard(_lock);
_read_ready_sigh = Signal_context_capability();
}
bool writer_is_gone() const
{
Lock::Guard guard(_lock);
return _writer_is_gone;
}
bool any_space_avail_for_writing() const
{
Lock::Guard guard(_lock);
return _any_space_avail_for_writing();
}
bool data_avail_for_reading() const
{
Lock::Guard guard(_lock);
return _read_offset != _write_offset;
}
size_t read(char *dst, size_t dst_len)
{
Lock::Guard guard(_lock);
if (_read_offset < _write_offset) {
size_t len = min(dst_len, _write_offset - _read_offset);
memcpy(dst, &_buffer[_read_offset], len);
_read_offset += len;
_wake_up_writer();
return len;
}
if (_read_offset > _write_offset) {
size_t const upper_len = min(dst_len, BUFFER_SIZE - _read_offset);
memcpy(dst, &_buffer[_read_offset], upper_len);
size_t const lower_len = min(dst_len - upper_len, _write_offset);
memcpy(dst + upper_len, &_buffer[0], lower_len);
_read_offset = lower_len;
_wake_up_writer();
return upper_len + lower_len;
}
/* _read_offset == _write_offset */
return 0;
}
/**
* Write to pipe buffer
*
* \return number of written bytes (may be less than 'len')
*/
size_t write(char *src, size_t len)
{
Lock::Guard guard(_lock);
/* trim write request to the available buffer space */
size_t const trimmed_len = min(len, _avail_buffer_space());
/*
* Remember pipe state prior writing to see whether a reader
* must be unblocked after writing.
*/
bool const pipe_was_empty = (_read_offset == _write_offset);
/* write data up to the upper boundary of the pipe buffer */
size_t const upper_len = min(BUFFER_SIZE - _write_offset, trimmed_len);
memcpy(&_buffer[_write_offset], src, upper_len);
_write_offset += upper_len;
/*
* Determine number of remaining bytes beyond the buffer boundary.
* The buffer wraps. So this data will end up in the lower part
* of the pipe buffer.
*/
size_t const lower_len = trimmed_len - upper_len;
if (lower_len > 0) {
/* pipe buffer wrap-around, write remaining data to the lower part */
memcpy(&_buffer[0], src + upper_len, lower_len);
_write_offset = lower_len;
}
/*
* Wake up reader who may block for incoming data.
*/
if (pipe_was_empty || !_any_space_avail_for_writing())
_wake_up_reader();
/* return number of written bytes */
return trimmed_len;
}
void register_write_ready_sigh(Signal_context_capability sigh)
{
Lock::Guard guard(_lock);
_write_ready_sigh = sigh;
}
void register_read_ready_sigh(Signal_context_capability sigh)
{
Lock::Guard guard(_lock);
_read_ready_sigh = sigh;
}
};
class Pipe_sink_io_channel : public Io_channel, public Signal_dispatcher
{
private:
Shared_pointer<Pipe> _pipe;
Signal_receiver &_sig_rec;
public:
Pipe_sink_io_channel(Shared_pointer<Pipe> pipe,
Signal_receiver &sig_rec)
: _pipe(pipe), _sig_rec(sig_rec)
{
pipe->register_write_ready_sigh(_sig_rec.manage(this));
}
~Pipe_sink_io_channel()
{
_sig_rec.dissolve(this);
_pipe->writer_close();
}
bool check_unblock(bool rd, bool wr, bool ex) const
{
return wr && _pipe->any_space_avail_for_writing();
}
bool write(Sysio *sysio, size_t &count)
{
/*
* If the write operation is larger than the space available in
* the pipe buffer, the write function is successively called
* for different portions of original write request. The
* current read pointer of the request is tracked via the
* 'count' in/out argument. If completed, 'count' equals
* 'write_in.count'.
*/
/* dimension the pipe write operation to the not yet written data */
size_t curr_count = _pipe->write(sysio->write_in.chunk + count,
sysio->write_in.count - count);
count += curr_count;
return true;
}
bool fstat(Sysio *sysio)
{
sysio->fstat_out.st.mode = Sysio::STAT_MODE_CHARDEV;
return true;
}
/*********************************
** Signal_dispatcher interface **
*********************************/
/**
* Called by Noux main loop on the occurrence of new STDIN input
*/
void dispatch()
{
Io_channel::invoke_all_notifiers();
}
};
class Pipe_source_io_channel : public Io_channel, public Signal_dispatcher
{
private:
Shared_pointer<Pipe> _pipe;
Signal_receiver &_sig_rec;
public:
Pipe_source_io_channel(Shared_pointer<Pipe> pipe, Signal_receiver &sig_rec)
: _pipe(pipe), _sig_rec(sig_rec)
{
_pipe->register_read_ready_sigh(sig_rec.manage(this));
}
~Pipe_source_io_channel()
{
_sig_rec.dissolve(this);
_pipe->reader_close();
}
bool check_unblock(bool rd, bool wr, bool ex) const
{
/* unblock if the writer has already closed its pipe end */
if (_pipe->writer_is_gone())
return true;
return (rd && _pipe->data_avail_for_reading());
}
bool read(Sysio *sysio)
{
size_t const max_count =
min(sysio->read_in.count,
sizeof(sysio->read_out.chunk));
sysio->read_out.count =
_pipe->read(sysio->read_out.chunk, max_count);
return true;
}
bool fstat(Sysio *sysio)
{
sysio->fstat_out.st.mode = Sysio::STAT_MODE_CHARDEV;
return true;
}
/*********************************
** Signal_dispatcher interface **
*********************************/
/**
* Called by Noux main loop on the occurrence of new STDIN input
*/
void dispatch()
{
Io_channel::invoke_all_notifiers();
}
};
}
#endif /* _NOUX__PIPE_IO_CHANNEL_H_ */

View File

@ -37,9 +37,7 @@ namespace Noux {
return old_value;
}
operator T () {
PDBG("value=%d", (int)value);
return value; }
operator T () { return value; }
};
}

View File

@ -27,13 +27,14 @@ namespace Noux {
struct Terminal_io_channel : Io_channel, Signal_dispatcher
{
Terminal::Session &terminal;
Terminal::Session &terminal;
Genode::Signal_receiver &sig_rec;
enum Type { STDIN, STDOUT, STDERR } type;
Terminal_io_channel(Terminal::Session &terminal, Type type,
Genode::Signal_receiver &sig_rec)
: terminal(terminal), type(type)
: terminal(terminal), sig_rec(sig_rec), type(type)
{
/*
* Enable wake up STDIN channel on the presence of new input
@ -51,9 +52,12 @@ namespace Noux {
}
}
bool write(Sysio *sysio)
~Terminal_io_channel() { sig_rec.dissolve(this); }
bool write(Sysio *sysio, size_t &count)
{
terminal.write(sysio->write_in.chunk, sysio->write_in.count);
count = sysio->write_in.count;
return true;
}
@ -86,6 +90,9 @@ namespace Noux {
bool check_unblock(bool rd, bool wr, bool ex) const
{
/* never block for writing */
if (wr) return true;
/*
* Unblock I/O channel if the terminal has new user input. Channels
* otther than STDIN will never unblock.

View File

@ -96,6 +96,15 @@ namespace Noux {
sysio->dirent_in.index -= 2;
return _fh->ds()->dirent(sysio, _path.base());
}
bool check_unblock(bool rd, bool wr, bool ex) const
{
/*
* XXX For now, we use the TAR fs only, which never blocks.
* However, real file systems may block.
*/
return true;
}
};
}

View File

@ -24,7 +24,8 @@ namespace Noux {
{
Genode::Semaphore *semaphore;
Wake_up_notifier() : semaphore(0) { }
Wake_up_notifier(Genode::Semaphore *semaphore = 0)
: semaphore(semaphore) { }
void wake_up()
{