mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-18 13:26:27 +00:00
parent
45cee6e951
commit
cdc45e15f1
@ -672,6 +672,7 @@ set default_test_pkgs {
|
||||
test-init_loop
|
||||
test-ldso
|
||||
test-libc
|
||||
test-libc_alarm
|
||||
test-libc_connect_lwip
|
||||
test-libc_connect_lxip
|
||||
test-libc_connect_vfs_server_lwip
|
||||
@ -681,7 +682,7 @@ set default_test_pkgs {
|
||||
test-libc_fifo_pipe
|
||||
test-libc_fork
|
||||
test-libc_getenv
|
||||
test-libc_alarm
|
||||
test-libc_kqueue
|
||||
test-libc_pipe
|
||||
test-libc_vfs
|
||||
test-libc_vfs_audit
|
||||
|
@ -19,7 +19,7 @@ SRC_CC = atexit.cc dummies.cc rlimit.cc sysctl.cc \
|
||||
vfs_plugin.cc dynamic_linker.cc signal.cc \
|
||||
socket_operations.cc socket_fs_plugin.cc syscall.cc \
|
||||
getpwent.cc getrandom.cc fork.cc execve.cc kernel.cc component.cc \
|
||||
genode.cc spinlock.cc
|
||||
genode.cc spinlock.cc kqueue.cc
|
||||
|
||||
#
|
||||
# Pthreads
|
||||
|
@ -5,6 +5,7 @@
|
||||
#
|
||||
# libc
|
||||
#
|
||||
_Exit T
|
||||
___mb_cur_max D 50
|
||||
___runetype T
|
||||
___tolower T
|
||||
@ -39,7 +40,6 @@ __stdoutp D 8
|
||||
__swbuf T
|
||||
__test_sse T
|
||||
__xuname T
|
||||
_Exit T
|
||||
_exit T
|
||||
_getlong T
|
||||
_getshort T
|
||||
@ -412,8 +412,10 @@ iswupper T
|
||||
iswxdigit T
|
||||
isxdigit T
|
||||
jrand48 T
|
||||
kevent T
|
||||
kill W
|
||||
killpg T
|
||||
kqueue T
|
||||
ksem_init T
|
||||
l64a T
|
||||
l64a_r T
|
||||
@ -500,8 +502,8 @@ posix_fadvise T
|
||||
posix_madvise T
|
||||
posix_memalign T
|
||||
posix_spawn T
|
||||
posix_spawn_file_actions_addclose T
|
||||
posix_spawn_file_actions_addchdir_np T
|
||||
posix_spawn_file_actions_addclose T
|
||||
posix_spawn_file_actions_adddup2 T
|
||||
posix_spawn_file_actions_addopen T
|
||||
posix_spawn_file_actions_destroy T
|
||||
|
1
repos/libports/recipes/pkg/test-libc_kqueue/README
Normal file
1
repos/libports/recipes/pkg/test-libc_kqueue/README
Normal file
@ -0,0 +1 @@
|
||||
Libc kqueue() test.
|
5
repos/libports/recipes/pkg/test-libc_kqueue/archives
Normal file
5
repos/libports/recipes/pkg/test-libc_kqueue/archives
Normal file
@ -0,0 +1,5 @@
|
||||
_/src/init
|
||||
_/src/libc
|
||||
_/src/posix
|
||||
_/src/test-libc_kqueue
|
||||
_/src/vfs
|
1
repos/libports/recipes/pkg/test-libc_kqueue/hash
Normal file
1
repos/libports/recipes/pkg/test-libc_kqueue/hash
Normal file
@ -0,0 +1 @@
|
||||
2024-09-06 84548c3bc95921f81f27f1561b2d79475e09558d
|
24
repos/libports/recipes/pkg/test-libc_kqueue/runtime
Normal file
24
repos/libports/recipes/pkg/test-libc_kqueue/runtime
Normal file
@ -0,0 +1,24 @@
|
||||
<runtime ram="3M" caps="200" binary="test-libc_kqueue">
|
||||
|
||||
<requires> <timer/> </requires>
|
||||
|
||||
<fail after_seconds="8"/>
|
||||
<succeed>--- test succeeded ---</succeed>
|
||||
|
||||
<content>
|
||||
<rom label="ld.lib.so"/>
|
||||
<rom label="libc.lib.so"/>
|
||||
<rom label="libm.lib.so"/>
|
||||
<rom label="posix.lib.so"/>
|
||||
<rom label="test-libc_kqueue"/>
|
||||
<rom label="vfs.lib.so"/>
|
||||
</content>
|
||||
|
||||
<config>
|
||||
<vfs> <dir name="dev"> <log/> <inline name="rtc">2019-08-20 15:01</inline> </dir> </vfs>
|
||||
<libc stdout="/dev/log" stderr="/dev/log" rtc="/dev/rtc"/>
|
||||
<arg value="test-libc_kqueue"/>
|
||||
<arg value="/dev/log"/>
|
||||
</config>
|
||||
|
||||
</runtime>
|
2
repos/libports/recipes/src/test-libc_kqueue/content.mk
Normal file
2
repos/libports/recipes/src/test-libc_kqueue/content.mk
Normal file
@ -0,0 +1,2 @@
|
||||
SRC_DIR := src/test/libc_kqueue
|
||||
include $(GENODE_DIR)/repos/base/recipes/src/content.inc
|
1
repos/libports/recipes/src/test-libc_kqueue/hash
Normal file
1
repos/libports/recipes/src/test-libc_kqueue/hash
Normal file
@ -0,0 +1 @@
|
||||
2024-09-06 f378e168a5fc152eab217d9d61fc636867d4ca39
|
2
repos/libports/recipes/src/test-libc_kqueue/used_apis
Normal file
2
repos/libports/recipes/src/test-libc_kqueue/used_apis
Normal file
@ -0,0 +1,2 @@
|
||||
libc
|
||||
posix
|
@ -156,6 +156,11 @@ namespace Libc {
|
||||
* Atexit handling
|
||||
*/
|
||||
void init_atexit(Atexit &);
|
||||
|
||||
/**
|
||||
* Kqueue support
|
||||
*/
|
||||
void init_kqueue(Genode::Allocator &, Monitor &);
|
||||
}
|
||||
|
||||
#endif /* _LIBC__INTERNAL__INIT_H_ */
|
||||
|
40
repos/libports/src/lib/libc/internal/kqueue.h
Normal file
40
repos/libports/src/lib/libc/internal/kqueue.h
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* \brief kqueue plugin interface
|
||||
* \author Benjamin Lamowski
|
||||
* \date 2024-08-07
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2024 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _LIBC__INTERNAL__KQUEUE_H_
|
||||
#define _LIBC__INTERNAL__KQUEUE_H_
|
||||
|
||||
/* Libc includes */
|
||||
#include <sys/event.h>
|
||||
|
||||
#include <base/allocator.h>
|
||||
#include <internal/plugin.h>
|
||||
|
||||
namespace Libc { class Kqueue_plugin; }
|
||||
|
||||
|
||||
class Libc::Kqueue_plugin : public Libc::Plugin
|
||||
{
|
||||
private:
|
||||
|
||||
Genode::Allocator & _alloc;
|
||||
|
||||
public:
|
||||
|
||||
Kqueue_plugin(Genode::Allocator & alloc) : _alloc(alloc) { }
|
||||
|
||||
int create_kqueue();
|
||||
int close(File_descriptor *) override;
|
||||
};
|
||||
|
||||
#endif /* _LIBC__INTERNAL__KQUEUE_H_ */
|
@ -514,6 +514,7 @@ Libc::Kernel::Kernel(Genode::Env &env, Genode::Allocator &heap)
|
||||
init_socket_fs(*this, *this);
|
||||
init_passwd(_passwd_config());
|
||||
init_signal(_signal);
|
||||
init_kqueue(_heap, *this);
|
||||
|
||||
_init_file_descriptors();
|
||||
|
||||
|
563
repos/libports/src/lib/libc/kqueue.cc
Normal file
563
repos/libports/src/lib/libc/kqueue.cc
Normal file
@ -0,0 +1,563 @@
|
||||
/*
|
||||
* \brief kqueue/kevent implementation
|
||||
* \author Benjamin Lamowski
|
||||
* \date 2024-06-12
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2024 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
/* Libc includes */
|
||||
#include <sys/event.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* internal includes */
|
||||
#include <internal/fd_alloc.h>
|
||||
#include <internal/file.h>
|
||||
#include <internal/kernel.h>
|
||||
#include <internal/monitor.h>
|
||||
#include <internal/kqueue.h>
|
||||
#include <sys/poll.h>
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/mutex.h>
|
||||
#include <util/avl_tree.h>
|
||||
#include <util/register.h>
|
||||
|
||||
using namespace Libc;
|
||||
|
||||
namespace Libc {
|
||||
class Kqueue;
|
||||
|
||||
bool read_ready_from_kernel(File_descriptor *);
|
||||
void notify_read_ready_from_kernel(File_descriptor *);
|
||||
bool write_ready_from_kernel(File_descriptor *);
|
||||
}
|
||||
|
||||
namespace { using Fn = Libc::Monitor::Function_result; }
|
||||
|
||||
|
||||
static Monitor *_monitor_ptr;
|
||||
static Libc::Kqueue_plugin *_kqueue_plugin_ptr;
|
||||
|
||||
|
||||
static Libc::Monitor & monitor()
|
||||
{
|
||||
struct Missing_call_of_init_kqueue_support : Genode::Exception { };
|
||||
if (!_monitor_ptr)
|
||||
throw Missing_call_of_init_kqueue_support();
|
||||
return *_monitor_ptr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* kqueue(2): "A kevent is identified by the (ident, filter) pair;
|
||||
* there may only be one unique kevent per kqueue.
|
||||
*/
|
||||
bool operator==(struct kevent a, struct kevent b)
|
||||
{
|
||||
return ((a.ident == b.ident) && (a.filter == b.filter));
|
||||
}
|
||||
|
||||
|
||||
bool operator>(struct kevent a, struct kevent b)
|
||||
{
|
||||
if (a.ident > b.ident)
|
||||
return true;
|
||||
if ((a.ident == b.ident) && (a.filter > b.filter))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static consteval int pos(int flag)
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(flag) * 8; i++)
|
||||
if ((1 << i) & flag)
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Kqueue backend implementation
|
||||
*/
|
||||
|
||||
struct Libc::Kqueue
|
||||
{
|
||||
const unsigned short flags_whitelist =
|
||||
EV_ADD |
|
||||
EV_DELETE |
|
||||
EV_CLEAR |
|
||||
EV_ONESHOT |
|
||||
EV_ENABLE |
|
||||
EV_DISABLE;
|
||||
|
||||
const int filter_whitelist =
|
||||
EVFILT_READ |
|
||||
EVFILT_WRITE;
|
||||
|
||||
struct Kqueue_flags : Genode::Register<32> {
|
||||
struct Add : Bitfield< pos(EV_ADD), 1> { };
|
||||
struct Delete : Bitfield< pos(EV_DELETE), 1> { };
|
||||
struct Enable : Bitfield< pos(EV_ENABLE), 1> { };
|
||||
struct Disable : Bitfield<pos(EV_DISABLE), 1> { };
|
||||
struct Clear : Bitfield< pos(EV_CLEAR), 1> { };
|
||||
struct Oneshot : Bitfield<pos(EV_ONESHOT), 1> { };
|
||||
};
|
||||
|
||||
struct Kqueue_elements;
|
||||
|
||||
struct Kqueue_element : kevent, public Avl_node<Kqueue_element>
|
||||
{
|
||||
Kqueue_element *find_by_kevent(struct kevent const & k)
|
||||
{
|
||||
if (*this == k) return this;
|
||||
Kqueue_element *ele = this->child(k > *this);
|
||||
return ele ? ele->find_by_kevent(k) : nullptr;
|
||||
}
|
||||
|
||||
bool higher(Kqueue_element *e)
|
||||
{
|
||||
return *e > *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Run fn arcoss the element's subtree until fn returns false.
|
||||
*/
|
||||
bool with_all_elements(auto const &fn)
|
||||
{
|
||||
if (!fn(*this))
|
||||
return false;
|
||||
|
||||
if (Kqueue_element * l = child(Avl_node<Kqueue_element>::LEFT))
|
||||
if (!l->with_all_elements(fn))
|
||||
return false;
|
||||
|
||||
if (Kqueue_element * r = child(Avl_node<Kqueue_element>::RIGHT))
|
||||
if (!r->with_all_elements(fn))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct Kqueue_elements : Avl_tree<Kqueue_element>
|
||||
{
|
||||
bool with_any_element(auto const &fn)
|
||||
{
|
||||
Kqueue_element *curr_ptr = first();
|
||||
if (!curr_ptr)
|
||||
return false;
|
||||
|
||||
fn(*curr_ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
auto with_element(struct kevent const & k, FN const &match_fn, auto const &no_match_fn)
|
||||
-> typename Trait::Functor<decltype(&FN::operator())>::Return_type
|
||||
{
|
||||
Kqueue_element *ele = (this->first()) ?
|
||||
this->first()->find_by_kevent(k) :
|
||||
nullptr;
|
||||
if (ele)
|
||||
return match_fn(*ele);
|
||||
else
|
||||
return no_match_fn();
|
||||
}
|
||||
|
||||
/*
|
||||
* Run fn arcoss the tree until fn returns false.
|
||||
*/
|
||||
void with_all_elements(auto const &fn) const
|
||||
{
|
||||
if (first()) first()->with_all_elements(fn);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Genode::Allocator &_alloc;
|
||||
Mutex _requests_mutex;
|
||||
Kqueue_elements _requests;
|
||||
|
||||
/*
|
||||
* Collect invalid elements for deletion.
|
||||
* This needs to be done out of band because otherwise the reshuffling of the AVL tree
|
||||
* might lead to missed valid events.
|
||||
*/
|
||||
struct Kqueue_element_container : Fifo<Kqueue_element_container>::Element
|
||||
{
|
||||
Kqueue_element const & ele;
|
||||
Kqueue_element_container(Kqueue_element const & e) : ele(e)
|
||||
{ }
|
||||
};
|
||||
|
||||
Fifo<Kqueue_element_container> _delete_queue;
|
||||
|
||||
void _queue_for_deletion(Kqueue_element &ele)
|
||||
{
|
||||
Kqueue_element_container &c = *new (_alloc) Kqueue_element_container(ele);
|
||||
_delete_queue.enqueue(c);
|
||||
}
|
||||
|
||||
/*
|
||||
* This may be called only when we can safely reshuffle the AVL tree
|
||||
*/
|
||||
void _delete_elements()
|
||||
{
|
||||
auto cancel_fn = [&](Kqueue_element_container& c) {
|
||||
/* At this point we are sure that we can reshuffle the AVL tree. */
|
||||
Kqueue_element * non_const_ptr = &(const_cast<Kqueue_element &>(c.ele));
|
||||
_requests.remove(non_const_ptr);
|
||||
destroy(_alloc, non_const_ptr);
|
||||
destroy(_alloc, &c);
|
||||
};
|
||||
|
||||
{
|
||||
Mutex::Guard guard(_requests_mutex);
|
||||
_delete_queue.dequeue_all(cancel_fn);
|
||||
}
|
||||
}
|
||||
|
||||
int _add_event(struct kevent const& k)
|
||||
{
|
||||
if (k.filter & ~filter_whitelist) {
|
||||
warning("kqueue: filter not implemented: ", k.filter);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
Kqueue_element *ele = new (_alloc) Kqueue_element(k);
|
||||
{
|
||||
Mutex::Guard guard(_requests_mutex);
|
||||
_requests.insert(ele);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _delete_event(struct kevent const& k)
|
||||
{
|
||||
Mutex::Guard guard(_requests_mutex);
|
||||
|
||||
auto match_fn = [&](Kqueue_element & ele) {
|
||||
/* Since we know we won't match another element, we can safely remove the element here. */
|
||||
_requests.remove(&ele);
|
||||
destroy(_alloc, &ele);
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
auto no_match_fn = [&]() {
|
||||
error("kqueue: did not find kevent to delete: ident: ", k.ident, " filter: ", k.filter);
|
||||
return EINVAL;
|
||||
};
|
||||
|
||||
return _requests.with_element(k, match_fn, no_match_fn);
|
||||
}
|
||||
|
||||
int _enable_event(struct kevent const& k)
|
||||
{
|
||||
auto match_fn = [&](Kqueue_element & ele) {
|
||||
Kqueue_flags::Disable::clear((Kqueue_flags::access_t &)ele.flags);
|
||||
Kqueue_flags::Enable::set((Kqueue_flags::access_t &)ele.flags);
|
||||
return 0;
|
||||
};
|
||||
|
||||
auto no_match_fn = [&]() {
|
||||
error("kqueue: did not find kevent to enable: ident: ", k.ident, " filter: ", k.filter);
|
||||
return EINVAL;
|
||||
};
|
||||
|
||||
return _requests.with_element(k, match_fn, no_match_fn);
|
||||
}
|
||||
|
||||
int _disable_event(struct kevent const& k)
|
||||
{
|
||||
auto match_fn = [&](Kqueue_element & ele) {
|
||||
Kqueue_flags::Enable::clear((Kqueue_flags::access_t &)ele.flags);
|
||||
Kqueue_flags::Disable::set((Kqueue_flags::access_t &)ele.flags);
|
||||
return 0;
|
||||
};
|
||||
|
||||
auto no_match_fn = [&]() {
|
||||
error("kqueue: did not find kevent to disable: ident: ", k.ident, " filter: ", k.filter);
|
||||
return EINVAL;
|
||||
};
|
||||
|
||||
return _requests.with_element(k, match_fn, no_match_fn);
|
||||
}
|
||||
|
||||
Kqueue(Genode::Allocator & alloc) : _alloc(alloc)
|
||||
{ }
|
||||
|
||||
~Kqueue()
|
||||
{
|
||||
auto destroy_fn = [&] (Kqueue_element &e) {
|
||||
_requests.remove(&e);
|
||||
destroy(_alloc, &e); };
|
||||
while (_requests.with_any_element(destroy_fn));
|
||||
}
|
||||
|
||||
int process_events(const struct kevent * changelist, int nchanges,
|
||||
struct kevent * eventlist, int nevents)
|
||||
{
|
||||
int num_errors { 0 };
|
||||
|
||||
for (int i = 0; i < nchanges; i++) {
|
||||
const int flags = changelist[i].flags;
|
||||
int err { 0 };
|
||||
|
||||
if (flags & ~flags_whitelist) {
|
||||
error("kqueue: unsupported flags detected: ", flags & ~flags_whitelist);
|
||||
return Errno(EINVAL);
|
||||
}
|
||||
|
||||
if (Kqueue_flags::Add::get(flags))
|
||||
err = _add_event(changelist[i]);
|
||||
else if (Kqueue_flags::Delete::get(flags))
|
||||
err = _delete_event(changelist[i]);
|
||||
else if (Kqueue_flags::Enable::get(flags))
|
||||
err = _enable_event(changelist[i]);
|
||||
else if (Kqueue_flags::Disable::get(flags))
|
||||
err = _disable_event(changelist[i]);
|
||||
/* We ignore setting EV_CLEAR for now. */
|
||||
|
||||
if (err) {
|
||||
if (num_errors < nevents) {
|
||||
eventlist[num_errors] = changelist[i];
|
||||
eventlist[num_errors].flags = EV_ERROR;
|
||||
eventlist[num_errors].data = err;
|
||||
num_errors++;
|
||||
} else {
|
||||
return Errno(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return num_errors;
|
||||
}
|
||||
|
||||
int collect_completed_events(struct kevent * eventlist, int nevents, const struct timespec *timeout)
|
||||
{
|
||||
if (nevents == 0)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* event collection mode depending ont 'timeout'
|
||||
*
|
||||
* - timeout pointer == nullptr ... block infinitely for events
|
||||
* - timeout value == 0 ... poll for events and return
|
||||
* immediately
|
||||
* - timeout value != 0 ... block for events but don't return
|
||||
* later than timeout
|
||||
*/
|
||||
enum class Mode { INFINITE, POLL, TIMEOUT };
|
||||
|
||||
Mode mode = Mode::INFINITE;
|
||||
uint64_t timeout_ms = 0;
|
||||
|
||||
if (timeout) {
|
||||
timeout_ms = timeout->tv_sec * 1000 + timeout->tv_nsec / 1000000;
|
||||
mode = (timeout_ms == 0) ? Mode::POLL : Mode::TIMEOUT;
|
||||
}
|
||||
|
||||
int num_events { 0 };
|
||||
|
||||
auto monitor_fn = [&] ()
|
||||
{
|
||||
/*
|
||||
* kqueue(2):
|
||||
* "The filter is also run when the user attempts to retrieve the kevent
|
||||
* from the kqueue. If the filter indicates that the condition that triggered
|
||||
* the event no longer holds, the kevent is removed from the kqueue and is
|
||||
* not returned."
|
||||
*
|
||||
* Since we need to check the condition on retrieval anyway, we *only* check
|
||||
* the condition on retrieval and not asynchronously.
|
||||
*/
|
||||
auto check_fn = [&](Kqueue_element &ele) {
|
||||
File_descriptor *fd = libc_fd_to_fd(ele.ident, "kevent_collect");
|
||||
|
||||
/*
|
||||
* kqueue(2): "Calling close() on a file descriptor will remove any
|
||||
* kevents that reference the descriptor."
|
||||
*
|
||||
* Instead of removing the kqueue entry from close(), we collect
|
||||
* invalid entries for deletion here.
|
||||
*/
|
||||
if (!fd || !fd->plugin || !fd->context) {
|
||||
_queue_for_deletion(ele);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* If an event is disabled, ignore it.
|
||||
*/
|
||||
if (Kqueue_flags::Disable::get(ele.flags))
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Right now we do not support tracking newly available read data via
|
||||
* the clear flag, as that would entail tracking the availability of new
|
||||
* data across file system implementations. For the case that a kqueue
|
||||
* client sets EV_CLEAR and does not read the available data after receiving
|
||||
* a kevent, this will lead to extraneous kevents for the already existing data.
|
||||
*/
|
||||
switch (ele.filter) {
|
||||
case EVFILT_READ:
|
||||
if (Libc::read_ready_from_kernel(fd)) {
|
||||
eventlist[num_events] = ele;
|
||||
eventlist[num_events].flags = 0;
|
||||
num_events++;
|
||||
} else {
|
||||
Libc::notify_read_ready_from_kernel(fd);
|
||||
}
|
||||
break;
|
||||
case EVFILT_WRITE:
|
||||
if (Libc::write_ready_from_kernel(fd)) {
|
||||
eventlist[num_events] = ele;
|
||||
eventlist[num_events].flags = 0;
|
||||
num_events++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false && "Element with unknown filter inserted");
|
||||
}
|
||||
|
||||
/* Delete oneshot event */
|
||||
if (Kqueue_flags::Oneshot::get(ele.flags))
|
||||
_queue_for_deletion(ele);
|
||||
|
||||
return num_events < nevents;
|
||||
};
|
||||
|
||||
{
|
||||
Mutex::Guard guard(_requests_mutex);
|
||||
_requests.with_all_elements(check_fn);
|
||||
|
||||
}
|
||||
|
||||
_delete_elements();
|
||||
|
||||
if (mode != Mode::POLL && num_events == 0)
|
||||
return Monitor::Function_result::INCOMPLETE;
|
||||
|
||||
return Monitor::Function_result::COMPLETE;
|
||||
};
|
||||
|
||||
Monitor::Result const monitor_result =
|
||||
monitor().monitor(monitor_fn, timeout_ms);
|
||||
|
||||
if (monitor_result == Monitor::Result::TIMEOUT)
|
||||
return 0;
|
||||
|
||||
return num_events;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void Libc::init_kqueue(Genode::Allocator &alloc, Monitor &monitor)
|
||||
{
|
||||
_kqueue_plugin_ptr = new (alloc) Kqueue_plugin(alloc);
|
||||
_monitor_ptr = &monitor;
|
||||
}
|
||||
|
||||
|
||||
static Kqueue_plugin *kqueue_plugin()
|
||||
{
|
||||
if (!_kqueue_plugin_ptr) {
|
||||
error("libc kqueue not initialized - aborting");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return _kqueue_plugin_ptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int Libc::Kqueue_plugin::create_kqueue()
|
||||
{
|
||||
Kqueue *kq = new (_alloc) Kqueue(_alloc);
|
||||
|
||||
Plugin_context *context = reinterpret_cast<Libc::Plugin_context *>(kq);
|
||||
File_descriptor *fd =
|
||||
file_descriptor_allocator()->alloc(this, context, Libc::ANY_FD);
|
||||
|
||||
return fd->libc_fd;
|
||||
}
|
||||
|
||||
|
||||
int Libc::Kqueue_plugin::close(File_descriptor *fd)
|
||||
{
|
||||
if (fd->plugin != this)
|
||||
return -1;
|
||||
|
||||
if (fd->context)
|
||||
_alloc.free(fd->context, sizeof(Kqueue));
|
||||
|
||||
|
||||
file_descriptor_allocator()->free(fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
extern "C" int
|
||||
kevent(int libc_fd, const struct kevent *changelist, int nchanges,
|
||||
struct kevent *eventlist, int nevents, const struct timespec *timeout)
|
||||
{
|
||||
File_descriptor *fd = libc_fd_to_fd(libc_fd, "kevent");
|
||||
|
||||
if (fd->plugin != kqueue_plugin()) {
|
||||
error("File descriptor not reqistered to kqueue plugin");
|
||||
return Errno(EBADF);
|
||||
}
|
||||
|
||||
Kqueue *kq = reinterpret_cast<Libc::Kqueue *>(fd->context);
|
||||
assert(kq && "Kqueue not set in kqueue file descriptor");
|
||||
|
||||
if (nchanges < 0 || nevents < 0)
|
||||
return Errno(EINVAL);
|
||||
|
||||
int err { 0 };
|
||||
|
||||
if (changelist && nchanges) {
|
||||
int num_errors = kq->process_events(changelist, nchanges, eventlist, nevents);
|
||||
|
||||
/*
|
||||
* kqueue(2):
|
||||
* If an error occurs while processing an element of the
|
||||
* changelist and there is enough roomin the eventlist, then the
|
||||
* event will be placed in the eventlist with EV_ERROR set in
|
||||
* flags and the system error in data. Otherwise, -1 will be
|
||||
* returned, and errno will be set to indicate the error
|
||||
* condition.
|
||||
*/
|
||||
if (num_errors < 0)
|
||||
return -1;
|
||||
|
||||
/* reduce space available in the eventlist */
|
||||
if (num_errors) {
|
||||
nevents -= num_errors;
|
||||
eventlist = &eventlist[num_errors];
|
||||
}
|
||||
}
|
||||
|
||||
if (eventlist && nevents)
|
||||
err = kq->collect_completed_events(eventlist, nevents, timeout);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
extern "C"
|
||||
int kqueue(void)
|
||||
{
|
||||
return kqueue_plugin()->create_kqueue();
|
||||
}
|
@ -220,6 +220,13 @@ namespace Libc {
|
||||
return handle->fs().read_ready(*handle);
|
||||
}
|
||||
|
||||
void notify_read_ready_from_kernel(File_descriptor *fd)
|
||||
{
|
||||
Vfs::Vfs_handle *handle = vfs_handle(fd);
|
||||
if (handle)
|
||||
handle->fs().notify_read_ready(handle);
|
||||
}
|
||||
|
||||
bool write_ready_from_kernel(File_descriptor *fd)
|
||||
{
|
||||
Vfs::Vfs_handle const *handle = vfs_handle(fd);
|
||||
|
505
repos/libports/src/test/libc_kqueue/kqueue.c
Normal file
505
repos/libports/src/test/libc_kqueue/kqueue.c
Normal file
@ -0,0 +1,505 @@
|
||||
/*
|
||||
* \brief kqueue test
|
||||
* \author Benjamin Lamowski
|
||||
* \date 2024-08-20
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2024 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Note that our kqueue implementation does support EV_CLEAR
|
||||
* for EVFILT_READ and EVFILT_WRITE,
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/event.h>
|
||||
|
||||
/*
|
||||
* Information about the test.
|
||||
*/
|
||||
struct Test_info {
|
||||
char const *name; /* Name of the test */
|
||||
char const *path; /* Path of the file to open */
|
||||
int open_flags; /* Flags for opening the file */
|
||||
int filter; /* kqueue filter */
|
||||
int flags; /* kqueue flags */
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Structure to pass around open file descriptors.
|
||||
*/
|
||||
struct Fildes {
|
||||
int fd;
|
||||
int kq;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Set up a kqueue, open the file and insert the initial kevent.
|
||||
*/
|
||||
int bringup(struct Test_info *test, struct Fildes *fildes)
|
||||
{
|
||||
int ret = 0;
|
||||
struct kevent event;
|
||||
|
||||
fildes->kq = kqueue();
|
||||
if (fildes->kq == -1) {
|
||||
printf("%s: Failed to create kqueue: %s\n", test->name,
|
||||
strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
fildes->fd = open(test->path, test->open_flags);
|
||||
|
||||
if (fildes->fd == -1) {
|
||||
close(fildes->kq);
|
||||
printf("%s: Failed to open file %s: %s\n", test->name,
|
||||
test->path, strerror(errno));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
EV_SET(&event, fildes->fd, test->filter, test->flags, 0, 0, NULL);
|
||||
|
||||
ret = kevent(fildes->kq, &event, 1, NULL, 0, NULL);
|
||||
if (ret) {
|
||||
close(fildes->fd);
|
||||
close(fildes->kq);
|
||||
printf("%s: Failed to register event: %s\n",
|
||||
test->name, strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Get a result from the kqueue and close the file descriptors.
|
||||
*/
|
||||
int get_result(struct Test_info *test, struct Fildes *fildes)
|
||||
{
|
||||
int ret = 0;
|
||||
struct kevent result;
|
||||
ret = kevent(fildes->kq, NULL, 0, &result, 1, NULL);
|
||||
if (ret == -1) {
|
||||
close(fildes->fd);
|
||||
close(fildes->kq);
|
||||
printf("%s: Failed to retrieve result: %s\n", test->name,
|
||||
strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
|
||||
close(fildes->fd);
|
||||
close(fildes->kq);
|
||||
|
||||
if (ret > 0) {
|
||||
if (result.flags & EV_ERROR) {
|
||||
printf("%s: ", test->name);
|
||||
printf("%s: Event indcated failure: %s\n", test->name,
|
||||
strerror(result.data));
|
||||
return -1;
|
||||
} else {
|
||||
printf("%s: Test successful.\n", test->name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Test getting a kevent if a file can be written to.
|
||||
*/
|
||||
int test_write()
|
||||
{
|
||||
int ret = 0;
|
||||
struct Test_info test = {
|
||||
"Write test",
|
||||
"/dev/log",
|
||||
O_WRONLY,
|
||||
EVFILT_WRITE,
|
||||
EV_ADD
|
||||
};
|
||||
struct Fildes fildes;
|
||||
ret = bringup(&test, &fildes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return get_result(&test, &fildes);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Test getting a kevent if a file can be read.
|
||||
*/
|
||||
int test_read()
|
||||
{
|
||||
int ret = 0;
|
||||
struct Test_info test = {
|
||||
"Read test",
|
||||
"/dev/rtc",
|
||||
O_RDONLY,
|
||||
EVFILT_READ,
|
||||
EV_ADD
|
||||
};
|
||||
struct Fildes fildes;
|
||||
ret = bringup(&test, &fildes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return get_result(&test, &fildes);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Test deleting a queued kevent.
|
||||
*/
|
||||
int test_delete()
|
||||
{
|
||||
int ret = 0;
|
||||
struct kevent event;
|
||||
struct Test_info test = {
|
||||
"Cancel test",
|
||||
"/dev/rtc",
|
||||
O_RDONLY,
|
||||
EVFILT_READ,
|
||||
EV_ADD
|
||||
};
|
||||
struct Fildes fildes;
|
||||
const struct timespec timeout = {.tv_sec = 0, .tv_nsec = 1000000};
|
||||
ret = bringup(&test, &fildes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Delete the event */
|
||||
EV_SET(&event, fildes.fd, test.filter, EV_DELETE, 0, 0, NULL);
|
||||
ret = kevent(fildes.kq, &event, 1, NULL, 0, NULL);
|
||||
if (ret) {
|
||||
close(fildes.fd);
|
||||
close(fildes.kq);
|
||||
printf("%s: Failed to delete event: %s\n", test.name,
|
||||
strerror(errno));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = kevent(fildes.kq, NULL, 0, &event, 1, &timeout);
|
||||
if (ret == -1) {
|
||||
close(fildes.fd);
|
||||
close(fildes.kq);
|
||||
printf("%s: Failed to retrieve result: %s\n", test.name,
|
||||
strerror(errno));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Since we can always read /dev/rtc, wetting the results will only
|
||||
* timeout if the event has been deleted sucessfuly.
|
||||
*/
|
||||
if (ret == 0) {
|
||||
printf("%s: Test successful.\n", test.name);
|
||||
return 0;
|
||||
} else {
|
||||
printf("%s: Event was not deleted: %s\n", test.name,
|
||||
strerror(errno));
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Test repeating a non-oneshot event.
|
||||
*/
|
||||
int test_repeat()
|
||||
{
|
||||
int ret = 0;
|
||||
struct Test_info test = {
|
||||
"Repeat test",
|
||||
"/dev/rtc",
|
||||
O_RDONLY,
|
||||
EVFILT_READ,
|
||||
EV_ADD
|
||||
};
|
||||
struct Fildes fildes;
|
||||
struct kevent event1, event2;
|
||||
|
||||
ret = bringup(&test, &fildes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = kevent(fildes.kq, NULL, 0, &event1, 1, NULL);
|
||||
if (ret == -1) {
|
||||
close(fildes.fd);
|
||||
close(fildes.kq);
|
||||
printf("%s: Failed to retrieve result 1: %s\n", test.name,
|
||||
strerror(errno));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = kevent(fildes.kq, NULL, 0, &event2, 1, NULL);
|
||||
|
||||
close(fildes.fd);
|
||||
close(fildes.kq);
|
||||
|
||||
switch (ret) {
|
||||
case 1:
|
||||
printf("%s: Test successful.\n", test.name);
|
||||
return 0;
|
||||
case -1:
|
||||
printf("%s: Failed to retrieve result 2: %s\n",
|
||||
test.name, strerror(errno));
|
||||
break;
|
||||
default:
|
||||
printf("%s: Non-oneshot event was not repeated: %s\n",
|
||||
test.name, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Test queuing a oneshot event.
|
||||
*/
|
||||
int test_oneshot()
|
||||
{
|
||||
int ret = 0;
|
||||
struct Test_info test = {
|
||||
"Oneshot test",
|
||||
"/dev/rtc",
|
||||
O_RDONLY,
|
||||
EVFILT_READ,
|
||||
EV_ADD | EV_ONESHOT
|
||||
};
|
||||
struct Fildes fildes;
|
||||
const struct timespec timeout = {.tv_sec = 0, .tv_nsec = 1000000};
|
||||
struct kevent event1, event2;
|
||||
|
||||
ret = bringup(&test, &fildes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = kevent(fildes.kq, NULL, 0, &event1, 1, NULL);
|
||||
if (ret == -1) {
|
||||
close(fildes.fd);
|
||||
close(fildes.kq);
|
||||
printf("%s: Failed to retrieve result 1: %s\n", test.name,
|
||||
strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = kevent(fildes.kq, NULL, 0, &event2, 1, &timeout);
|
||||
if (ret == -1) {
|
||||
close(fildes.fd);
|
||||
close(fildes.kq);
|
||||
printf("%s: Failed to retrieve result 2: %s\n", test.name,
|
||||
strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
printf("%s: Test successful.\n", test.name);
|
||||
return 0;
|
||||
} else {
|
||||
printf("%s: Oneshot event was repeated: %s\n", test.name,
|
||||
strerror(errno));
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Test disabling a queued event.
|
||||
*/
|
||||
int test_disable()
|
||||
{
|
||||
int ret = 0;
|
||||
struct Test_info test = {
|
||||
"Disable test",
|
||||
"/dev/rtc",
|
||||
O_RDONLY,
|
||||
EVFILT_READ,
|
||||
EV_ADD
|
||||
};
|
||||
struct Fildes fildes;
|
||||
const struct timespec timeout = {.tv_sec = 0, .tv_nsec = 1000000};
|
||||
struct kevent event;
|
||||
|
||||
ret = bringup(&test, &fildes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Disable the event */
|
||||
EV_SET(&event, fildes.fd, test.filter, EV_DISABLE, 0, 0, NULL);
|
||||
ret = kevent(fildes.kq, &event, 1, NULL, 0, NULL);
|
||||
if (ret) {
|
||||
close(fildes.fd);
|
||||
close(fildes.kq);
|
||||
printf("%s: Failed to disable event: %s\n", test.name,
|
||||
strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = kevent(fildes.kq, NULL, 0, &event, 1, &timeout);
|
||||
|
||||
close(fildes.fd);
|
||||
close(fildes.kq);
|
||||
|
||||
switch (ret) {
|
||||
case 0:
|
||||
printf("%s: Test successful.\n", test.name);
|
||||
return 0;
|
||||
case -1:
|
||||
printf("%s: Failed to retrieve result: %s\n", test.name,
|
||||
strerror(errno));
|
||||
return -1;
|
||||
default:
|
||||
printf("%s: Event was not disabled: %s\n", test.name,
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Test (re-)enabling a disabled event.
|
||||
*/
|
||||
int test_enable()
|
||||
{
|
||||
int ret = 0;
|
||||
struct Test_info test = {
|
||||
"Enable test",
|
||||
"/dev/rtc",
|
||||
O_RDONLY,
|
||||
EVFILT_READ,
|
||||
EV_ADD
|
||||
};
|
||||
struct Fildes fildes;
|
||||
const struct timespec timeout = {.tv_sec = 0, .tv_nsec = 1000000};
|
||||
struct kevent event;
|
||||
|
||||
ret = bringup(&test, &fildes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Disable the event */
|
||||
EV_SET(&event, fildes.fd, test.filter, EV_DISABLE, 0, 0, NULL);
|
||||
ret = kevent(fildes.kq, &event, 1, NULL, 0, NULL);
|
||||
if (ret) {
|
||||
close(fildes.fd);
|
||||
close(fildes.kq);
|
||||
printf("%s: Failed to disable event: %s\n", test.name,
|
||||
strerror(errno));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = kevent(fildes.kq, NULL, 0, &event, 1, &timeout);
|
||||
if (ret == -1) {
|
||||
close(fildes.fd);
|
||||
close(fildes.kq);
|
||||
printf("%s: Failed to retrieve result 2: %s\n", test.name,
|
||||
strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
printf("%s: Event was not disabled: %s\n", test.name,
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* (Re-)Enable the event */
|
||||
EV_SET(&event, fildes.fd, test.filter, EV_ENABLE, 0, 0, NULL);
|
||||
ret = kevent(fildes.kq, &event, 1, NULL, 0, NULL);
|
||||
if (ret) {
|
||||
close(fildes.fd);
|
||||
close(fildes.kq);
|
||||
printf("%s: Failed to enable event: %s\n", test.name,
|
||||
strerror(errno));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return get_result(&test, &fildes);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Test queuing a disabled event.
|
||||
*/
|
||||
int test_queue_disabled()
|
||||
{
|
||||
int ret = 0;
|
||||
struct Test_info test = {
|
||||
"Queue disabled test",
|
||||
"/dev/rtc",
|
||||
O_RDONLY,
|
||||
EVFILT_READ,
|
||||
EV_ADD | EV_DISABLE
|
||||
};
|
||||
struct Fildes fildes;
|
||||
const struct timespec timeout = {.tv_sec = 0, .tv_nsec = 1000000};
|
||||
struct kevent event;
|
||||
|
||||
ret = bringup(&test, &fildes);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = kevent(fildes.kq, NULL, 0, &event, 1, &timeout);
|
||||
|
||||
close(fildes.fd);
|
||||
close(fildes.kq);
|
||||
|
||||
switch (ret) {
|
||||
case 0:
|
||||
printf("%s: Test successful.\n", test.name);
|
||||
return 0;
|
||||
case -1:
|
||||
printf("%s: Failed to retrieve result: %s\n", test.name,
|
||||
strerror(errno));
|
||||
return -1;
|
||||
default:
|
||||
printf("%s: Event was not disabled: %s\n", test.name,
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int retval = 0;
|
||||
|
||||
retval += test_write();
|
||||
retval += test_read();
|
||||
retval += test_delete();
|
||||
retval += test_repeat();
|
||||
retval += test_oneshot();
|
||||
retval += test_disable();
|
||||
retval += test_enable();
|
||||
retval += test_queue_disabled();
|
||||
|
||||
if (!retval)
|
||||
printf("--- test succeeded ---\n");
|
||||
|
||||
return retval;
|
||||
}
|
5
repos/libports/src/test/libc_kqueue/target.mk
Normal file
5
repos/libports/src/test/libc_kqueue/target.mk
Normal file
@ -0,0 +1,5 @@
|
||||
TARGET = test-libc_kqueue
|
||||
SRC_C = kqueue.c
|
||||
LIBS = posix
|
||||
|
||||
CC_CXX_WARN_STRICT =
|
Loading…
Reference in New Issue
Block a user