mirror of
https://github.com/genodelabs/genode.git
synced 2025-05-06 10:38:31 +00:00
libc: use monitor for select
This patch simplifies the control flow within the libc kernel by facilitating the monitor mechanism for the implementation of 'select'. Issue #3874
This commit is contained in:
parent
52a6cf1412
commit
e9a3f0f095
@ -72,7 +72,7 @@ namespace Libc {
|
|||||||
/**
|
/**
|
||||||
* Select support
|
* Select support
|
||||||
*/
|
*/
|
||||||
void init_select(Suspend &, Resume &, Select &, Signal &);
|
void init_select(Select &, Signal &, Monitor &);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Support for querying available RAM quota in sysctl functions
|
* Support for querying available RAM quota in sysctl functions
|
||||||
|
@ -209,7 +209,7 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
|
|||||||
bool _dispatch_pending_io_signals = false;
|
bool _dispatch_pending_io_signals = false;
|
||||||
|
|
||||||
/* io_progress_handler marker */
|
/* io_progress_handler marker */
|
||||||
bool _io_ready { false };
|
bool _io_progressed { false };
|
||||||
|
|
||||||
Thread &_myself { *Thread::myself() };
|
Thread &_myself { *Thread::myself() };
|
||||||
|
|
||||||
@ -285,11 +285,15 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
|
|||||||
Reconstructible<Io_signal_handler<Kernel>> _execute_monitors {
|
Reconstructible<Io_signal_handler<Kernel>> _execute_monitors {
|
||||||
_env.ep(), *this, &Kernel::_monitors_handler };
|
_env.ep(), *this, &Kernel::_monitors_handler };
|
||||||
|
|
||||||
bool _execute_monitors_pending { false };
|
Monitor::Pool::State _execute_monitors_pending = Monitor::Pool::State::ALL_COMPLETE;
|
||||||
|
|
||||||
|
Constructible<Main_job> _main_monitor_job { };
|
||||||
|
|
||||||
void _monitors_handler()
|
void _monitors_handler()
|
||||||
{
|
{
|
||||||
/* used to leave I/O-signal dispatcher only - handled afterwards */
|
/* mark monitors for execution when running in kernel only */
|
||||||
|
_execute_monitors_pending = Monitor::Pool::State::JOBS_PENDING;
|
||||||
|
_io_progressed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Constructible<Clone_connection> _clone_connection { };
|
Constructible<Clone_connection> _clone_connection { };
|
||||||
@ -440,29 +444,86 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
|
|||||||
/* the 'kernel_routine' may install another kernel routine */
|
/* the 'kernel_routine' may install another kernel routine */
|
||||||
_kernel_routine = nullptr;
|
_kernel_routine = nullptr;
|
||||||
routine.execute_in_kernel();
|
routine.execute_in_kernel();
|
||||||
if (!_kernel_routine)
|
|
||||||
|
if (!_kernel_routine) {
|
||||||
_switch_to_user();
|
_switch_to_user();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_execute_monitors_pending) {
|
if (_kernel_routine) {
|
||||||
|
|
||||||
_execute_monitors_pending = false;
|
|
||||||
_monitors.execute_monitors();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (_dispatch_pending_io_signals) {
|
|
||||||
/* dispatch pending signals but don't block */
|
|
||||||
while (_env.ep().dispatch_pending_io_signal()) ;
|
|
||||||
} else {
|
|
||||||
/* block for signals */
|
|
||||||
_env.ep().wait_and_dispatch_one_io_signal();
|
_env.ep().wait_and_dispatch_one_io_signal();
|
||||||
handle_io_progress();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_kernel_routine && _resume_main_once && !_setjmp(_kernel_context))
|
/*
|
||||||
|
* Dispatch all pending I/O signals at once and execute
|
||||||
|
* monitors that may now become able to complete.
|
||||||
|
*/
|
||||||
|
auto dispatch_all_pending_io_signals = [&] ()
|
||||||
|
{
|
||||||
|
while (_env.ep().dispatch_pending_io_signal());
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatch_all_pending_io_signals();
|
||||||
|
|
||||||
|
if (_io_progressed)
|
||||||
|
Kernel::resume_all();
|
||||||
|
|
||||||
|
_io_progressed = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Execute monitors on kernel entry regardless of any I/O
|
||||||
|
* because the monitor function may be unrelated to I/O.
|
||||||
|
*/
|
||||||
|
if (_execute_monitors_pending == Monitor::Pool::State::JOBS_PENDING)
|
||||||
|
_execute_monitors_pending = _monitors.execute_monitors();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process I/O signals without returning to the application
|
||||||
|
* as long as the main thread depends on I/O.
|
||||||
|
*/
|
||||||
|
|
||||||
|
auto main_blocked_in_monitor = [&] ()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* In general, 'resume_all()' only flags the main state but
|
||||||
|
* does not alter the main monitor job. For exmaple in case
|
||||||
|
* of a sleep timeout, main is resumed by 'resume_main()'
|
||||||
|
* in 'Main_blockade::wakeup()' but did not yet return from
|
||||||
|
* 'suspend()'. The expired state in the main job is set
|
||||||
|
* only after 'suspend()' returned.
|
||||||
|
*/
|
||||||
|
if (_resume_main_once)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _main_monitor_job.constructed()
|
||||||
|
&& !_main_monitor_job->completed()
|
||||||
|
&& !_main_monitor_job->expired();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto main_suspended_for_io = [&] {
|
||||||
|
return _resume_main_once == false; };
|
||||||
|
|
||||||
|
while (main_blocked_in_monitor() || main_suspended_for_io()) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Block for one I/O signal and process all pending ones
|
||||||
|
* before executing the monitor functions. This avoids
|
||||||
|
* superflous executions of the monitor functions when
|
||||||
|
* receiving bursts of I/O signals.
|
||||||
|
*/
|
||||||
|
_env.ep().wait_and_dispatch_one_io_signal();
|
||||||
|
|
||||||
|
dispatch_all_pending_io_signals();
|
||||||
|
|
||||||
|
handle_io_progress();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return to the application
|
||||||
|
*/
|
||||||
|
if (!_kernel_routine && _resume_main_once && !_setjmp(_kernel_context)) {
|
||||||
_switch_to_user();
|
_switch_to_user();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,11 +569,17 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
|
|||||||
Monitor::Result _monitor(Mutex &mutex, Function &fn, uint64_t timeout_ms) override
|
Monitor::Result _monitor(Mutex &mutex, Function &fn, uint64_t timeout_ms) override
|
||||||
{
|
{
|
||||||
if (_main_context()) {
|
if (_main_context()) {
|
||||||
Main_job job { fn, timeout_ms };
|
|
||||||
|
|
||||||
_monitors.monitor(mutex, job);
|
_main_monitor_job.construct(fn, timeout_ms);
|
||||||
return job.completed() ? Monitor::Result::COMPLETE
|
|
||||||
: Monitor::Result::TIMEOUT;
|
_monitors.monitor(mutex, *_main_monitor_job);
|
||||||
|
|
||||||
|
Monitor::Result const job_result = _main_monitor_job->completed()
|
||||||
|
? Monitor::Result::COMPLETE
|
||||||
|
: Monitor::Result::TIMEOUT;
|
||||||
|
_main_monitor_job.destruct();
|
||||||
|
|
||||||
|
return job_result;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Pthread_job job { fn, _timer_accessor, timeout_ms };
|
Pthread_job job { fn, _timer_accessor, timeout_ms };
|
||||||
@ -523,13 +590,12 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _charge_monitors() override
|
void _trigger_monitor_examination() override
|
||||||
{
|
{
|
||||||
if (!_execute_monitors_pending) {
|
if (_main_context())
|
||||||
_execute_monitors_pending = true;
|
_monitors_handler();
|
||||||
if (!_main_context())
|
else
|
||||||
Signal_transmitter(*_execute_monitors).submit();
|
Signal_transmitter(*_execute_monitors).submit();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -600,7 +666,7 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
|
|||||||
/**
|
/**
|
||||||
* Cwd interface
|
* Cwd interface
|
||||||
*/
|
*/
|
||||||
Absolute_path &cwd() { return _cwd; }
|
Absolute_path &cwd() override { return _cwd; }
|
||||||
|
|
||||||
|
|
||||||
/*********************************
|
/*********************************
|
||||||
@ -625,11 +691,8 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
|
|||||||
** Vfs::Io_response_handler interface **
|
** Vfs::Io_response_handler interface **
|
||||||
****************************************/
|
****************************************/
|
||||||
|
|
||||||
void read_ready_response() override {
|
void read_ready_response() override { _io_progressed = true; }
|
||||||
_io_ready = true; }
|
void io_progress_response() override { _io_progressed = true; }
|
||||||
|
|
||||||
void io_progress_response() override {
|
|
||||||
_io_ready = true; }
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************
|
/**********************************************
|
||||||
|
@ -60,7 +60,7 @@ class Libc::Monitor : Interface
|
|||||||
protected:
|
protected:
|
||||||
|
|
||||||
virtual Result _monitor(Mutex &, Function &, uint64_t) = 0;
|
virtual Result _monitor(Mutex &, Function &, uint64_t) = 0;
|
||||||
virtual void _charge_monitors() = 0;
|
virtual void _trigger_monitor_examination() = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ class Libc::Monitor : Interface
|
|||||||
* Block until monitored execution completed or timeout expires
|
* Block until monitored execution completed or timeout expires
|
||||||
*
|
*
|
||||||
* The mutex must be locked when calling the monitor. It is released
|
* The mutex must be locked when calling the monitor. It is released
|
||||||
* during wait for completion and re-aquired before the function
|
* during wait for completion and re-acquired before the function
|
||||||
* returns. This behavior is comparable to condition variables.
|
* returns. This behavior is comparable to condition variables.
|
||||||
*
|
*
|
||||||
* Returns true if execution completed, false on timeout.
|
* Returns true if execution completed, false on timeout.
|
||||||
@ -83,14 +83,13 @@ class Libc::Monitor : Interface
|
|||||||
_Function(FN const &fn) : fn(fn) { }
|
_Function(FN const &fn) : fn(fn) { }
|
||||||
} function { fn };
|
} function { fn };
|
||||||
|
|
||||||
_charge_monitors();
|
|
||||||
return _monitor(mutex, function, timeout_ms);
|
return _monitor(mutex, function, timeout_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Charge monitor to execute the monitored function
|
* Trigger examination of monitored functions
|
||||||
*/
|
*/
|
||||||
void charge_monitors() { _charge_monitors(); }
|
void trigger_monitor_examination() { _trigger_monitor_examination(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -136,21 +135,35 @@ struct Libc::Monitor::Pool
|
|||||||
|
|
||||||
mutex.release();
|
mutex.release();
|
||||||
|
|
||||||
_monitor.charge_monitors();
|
_monitor.trigger_monitor_examination();
|
||||||
|
|
||||||
job.wait_for_completion();
|
job.wait_for_completion();
|
||||||
|
|
||||||
mutex.acquire();
|
mutex.acquire();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class State { JOBS_PENDING, ALL_COMPLETE };
|
||||||
|
|
||||||
/* called by the monitor context itself */
|
/* called by the monitor context itself */
|
||||||
void execute_monitors()
|
State execute_monitors()
|
||||||
{
|
{
|
||||||
|
State result = State::ALL_COMPLETE;
|
||||||
|
|
||||||
_jobs.for_each([&] (Job &job) {
|
_jobs.for_each([&] (Job &job) {
|
||||||
if (!job.completed() && !job.expired() && job.execute()) {
|
|
||||||
job.complete();
|
if (!job.completed() && !job.expired()) {
|
||||||
|
|
||||||
|
bool const completed = job.execute();
|
||||||
|
|
||||||
|
if (completed)
|
||||||
|
job.complete();
|
||||||
|
|
||||||
|
if (!completed)
|
||||||
|
result = State::JOBS_PENDING;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -391,28 +391,13 @@ extern void (*libc_select_notify_from_kernel)();
|
|||||||
|
|
||||||
void Libc::Kernel::handle_io_progress()
|
void Libc::Kernel::handle_io_progress()
|
||||||
{
|
{
|
||||||
if (_execute_monitors_pending) {
|
if (_io_progressed) {
|
||||||
_execute_monitors_pending = false;
|
_io_progressed = false;
|
||||||
_monitors.execute_monitors();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: make VFS I/O completion checks during
|
|
||||||
* kernel time to avoid flapping between stacks
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (_io_ready) {
|
|
||||||
_io_ready = false;
|
|
||||||
|
|
||||||
/* some contexts may have been deblocked from select() */
|
|
||||||
select_notify_from_kernel();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* resume all as any VFS context may have
|
|
||||||
* been deblocked from blocking I/O
|
|
||||||
*/
|
|
||||||
Kernel::resume_all();
|
Kernel::resume_all();
|
||||||
_monitors.execute_monitors();
|
|
||||||
|
if (_execute_monitors_pending == Monitor::Pool::State::JOBS_PENDING)
|
||||||
|
_execute_monitors_pending = _monitors.execute_monitors();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,7 +475,7 @@ Libc::Kernel::Kernel(Genode::Env &env, Genode::Allocator &heap)
|
|||||||
init_vfs_plugin(*this, _env.rm());
|
init_vfs_plugin(*this, _env.rm());
|
||||||
init_file_operations(*this);
|
init_file_operations(*this);
|
||||||
init_time(*this, *this);
|
init_time(*this, *this);
|
||||||
init_select(*this, *this, *this, _signal);
|
init_select(*this, _signal, *this);
|
||||||
init_socket_fs(*this);
|
init_socket_fs(*this);
|
||||||
init_passwd(_passwd_config());
|
init_passwd(_passwd_config());
|
||||||
init_signal(_signal);
|
init_signal(_signal);
|
||||||
|
@ -22,7 +22,6 @@
|
|||||||
#include <internal/errno.h>
|
#include <internal/errno.h>
|
||||||
#include <internal/file.h>
|
#include <internal/file.h>
|
||||||
#include <internal/init.h>
|
#include <internal/init.h>
|
||||||
#include <internal/suspend.h>
|
|
||||||
|
|
||||||
using namespace Libc;
|
using namespace Libc;
|
||||||
|
|
||||||
|
@ -38,10 +38,9 @@
|
|||||||
#include <internal/kernel.h>
|
#include <internal/kernel.h>
|
||||||
#include <internal/init.h>
|
#include <internal/init.h>
|
||||||
#include <internal/signal.h>
|
#include <internal/signal.h>
|
||||||
#include <internal/suspend.h>
|
|
||||||
#include <internal/resume.h>
|
|
||||||
#include <internal/select.h>
|
#include <internal/select.h>
|
||||||
#include <internal/errno.h>
|
#include <internal/errno.h>
|
||||||
|
#include <internal/monitor.h>
|
||||||
|
|
||||||
namespace Libc {
|
namespace Libc {
|
||||||
struct Select_cb;
|
struct Select_cb;
|
||||||
@ -51,19 +50,16 @@ namespace Libc {
|
|||||||
using namespace Libc;
|
using namespace Libc;
|
||||||
|
|
||||||
|
|
||||||
static Suspend *_suspend_ptr;
|
|
||||||
static Resume *_resume_ptr;
|
|
||||||
static Select *_select_ptr;
|
static Select *_select_ptr;
|
||||||
static Libc::Signal *_signal_ptr;
|
static Libc::Signal *_signal_ptr;
|
||||||
|
static Monitor *_monitor_ptr;
|
||||||
|
|
||||||
|
|
||||||
void Libc::init_select(Suspend &suspend, Resume &resume, Select &select,
|
void Libc::init_select(Select &select, Signal &signal, Monitor &monitor)
|
||||||
Signal &signal)
|
|
||||||
{
|
{
|
||||||
_suspend_ptr = &suspend;
|
|
||||||
_resume_ptr = &resume;
|
|
||||||
_select_ptr = &select;
|
_select_ptr = &select;
|
||||||
_signal_ptr = &signal;
|
_signal_ptr = &signal;
|
||||||
|
_monitor_ptr = &monitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -210,7 +206,6 @@ static int selscan(int nfds,
|
|||||||
/* this function gets called by plugin backends when file descripors become ready */
|
/* this function gets called by plugin backends when file descripors become ready */
|
||||||
void Libc::select_notify_from_kernel()
|
void Libc::select_notify_from_kernel()
|
||||||
{
|
{
|
||||||
bool resume_all = false;
|
|
||||||
fd_set tmp_readfds, tmp_writefds, tmp_exceptfds;
|
fd_set tmp_readfds, tmp_writefds, tmp_exceptfds;
|
||||||
|
|
||||||
/* check for each waiting select() function if one of its fds is ready now
|
/* check for each waiting select() function if one of its fds is ready now
|
||||||
@ -224,18 +219,8 @@ void Libc::select_notify_from_kernel()
|
|||||||
scb.readfds = tmp_readfds;
|
scb.readfds = tmp_readfds;
|
||||||
scb.writefds = tmp_writefds;
|
scb.writefds = tmp_writefds;
|
||||||
scb.exceptfds = tmp_exceptfds;
|
scb.exceptfds = tmp_exceptfds;
|
||||||
|
|
||||||
resume_all = true;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resume_all) {
|
|
||||||
struct Missing_call_of_init_select : Exception { };
|
|
||||||
if (!_resume_ptr)
|
|
||||||
throw Missing_call_of_init_select();
|
|
||||||
|
|
||||||
_resume_ptr->resume_all();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -288,35 +273,14 @@ int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* suspend as we don't have any immediate events */
|
using Genode::uint64_t;
|
||||||
|
|
||||||
struct Timeout
|
|
||||||
{
|
|
||||||
timeval const *_tv;
|
|
||||||
bool const valid { _tv != nullptr };
|
|
||||||
Genode::uint64_t duration {
|
|
||||||
valid ? (Genode::uint64_t)_tv->tv_sec*1000 + _tv->tv_usec/1000 : 0UL };
|
|
||||||
|
|
||||||
bool expired() const { return valid && duration == 0; };
|
|
||||||
|
|
||||||
Timeout(timeval *tv) : _tv(tv) { }
|
|
||||||
} timeout { tv };
|
|
||||||
|
|
||||||
struct Check : Suspend_functor
|
|
||||||
{
|
|
||||||
struct Timeout *timeout;
|
|
||||||
Select_cb *select_cb;
|
|
||||||
|
|
||||||
Check(Timeout *timeout, Select_cb * select_cb)
|
|
||||||
: timeout(timeout), select_cb(select_cb) { }
|
|
||||||
|
|
||||||
bool suspend() override {
|
|
||||||
return !timeout->expired() && select_cb->nready == 0; }
|
|
||||||
} check ( &timeout, &*select_cb );
|
|
||||||
|
|
||||||
|
uint64_t const timeout_ms = (tv != nullptr)
|
||||||
|
? (uint64_t)tv->tv_sec*1000 + tv->tv_usec/1000
|
||||||
|
: 0UL;
|
||||||
{
|
{
|
||||||
struct Missing_call_of_init_select : Exception { };
|
struct Missing_call_of_init_select : Exception { };
|
||||||
if (!_suspend_ptr || !_signal_ptr)
|
if (!_monitor_ptr || !_signal_ptr)
|
||||||
throw Missing_call_of_init_select();
|
throw Missing_call_of_init_select();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,25 +288,31 @@ int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
|||||||
|
|
||||||
auto signal_occurred_during_select = [&] ()
|
auto signal_occurred_during_select = [&] ()
|
||||||
{
|
{
|
||||||
return _signal_ptr->count() != orig_signal_count;
|
return (_signal_ptr->count() != orig_signal_count);
|
||||||
};
|
};
|
||||||
|
|
||||||
for (;;) {
|
auto monitor_fn = [&] ()
|
||||||
if (timeout.expired())
|
{
|
||||||
break;
|
select_notify_from_kernel();
|
||||||
|
|
||||||
if (select_cb->nready != 0)
|
if (select_cb->nready != 0)
|
||||||
break;
|
return Monitor::Function_result::COMPLETE;
|
||||||
|
|
||||||
if (signal_occurred_during_select())
|
if (signal_occurred_during_select())
|
||||||
break;
|
return Monitor::Function_result::COMPLETE;
|
||||||
|
|
||||||
timeout.duration = _suspend_ptr->suspend(check, timeout.duration);
|
return Monitor::Function_result::INCOMPLETE;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
Mutex mutex { };
|
||||||
|
Mutex::Guard guard(mutex);
|
||||||
|
|
||||||
|
Monitor::Result const monitor_result =
|
||||||
|
_monitor_ptr->monitor(mutex, monitor_fn, timeout_ms);
|
||||||
|
|
||||||
select_cb_list().remove(&(*select_cb));
|
select_cb_list().remove(&(*select_cb));
|
||||||
|
|
||||||
if (timeout.expired())
|
if (monitor_result == Monitor::Result::TIMEOUT)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (signal_occurred_during_select())
|
if (signal_occurred_during_select())
|
||||||
@ -357,6 +327,7 @@ int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
|||||||
return select_cb->nready;
|
return select_cb->nready;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
extern "C" __attribute__((alias("select")))
|
extern "C" __attribute__((alias("select")))
|
||||||
int __sys_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
int __sys_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
||||||
struct timeval *tv);
|
struct timeval *tv);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user