libc: reimplement synchronization primitives

The new implementation relieves the main entrypoint from monitor jobs
for contended lock primitives and is based on custom applicant data
structures, per-lock resp. per-semaphore applicant lists, and a
libc-internal blockade with timeouts based on libc kernel primitives.
This commit is contained in:
Christian Helmuth 2020-02-19 10:26:04 +01:00
parent f3ec246b67
commit e52802162c
11 changed files with 485 additions and 312 deletions

View File

@ -64,7 +64,7 @@ class Libc::Env_implementation : public Libc::Env, public Config_accessor
public: public:
Env_implementation(Genode::Env &env, Allocator &alloc) Env_implementation(Genode::Env &env, Genode::Allocator &alloc)
: _env(env), _vfs_env(_env, alloc, _vfs_config()) { } : _env(env), _vfs_env(_env, alloc, _vfs_config()) { }

View File

@ -38,6 +38,7 @@ namespace Libc {
struct Watch; struct Watch;
struct Signal; struct Signal;
struct File_descriptor_allocator; struct File_descriptor_allocator;
struct Timer_accessor;
/** /**
* Support for shared libraries * Support for shared libraries
@ -107,8 +108,8 @@ namespace Libc {
/** /**
* Pthread/semaphore support * Pthread/semaphore support
*/ */
void init_pthread_support(Monitor &, Suspend &, Resume &); void init_pthread_support(Suspend &, Resume &, Timer_accessor &);
void init_semaphore_support(Monitor &); void init_semaphore_support(Timer_accessor &);
struct Config_accessor : Interface struct Config_accessor : Interface
{ {

View File

@ -40,8 +40,52 @@
#include <internal/watch.h> #include <internal/watch.h>
#include <internal/signal.h> #include <internal/signal.h>
#include <internal/monitor.h> #include <internal/monitor.h>
#include <internal/pthread.h>
namespace Libc { class Kernel; } namespace Libc {
class Kernel;
class Main_blockade;
class Main_job;
}
class Libc::Main_blockade : public Blockade
{
private:
uint64_t _timeout_ms;
bool const _timeout_valid { _timeout_ms != 0 };
struct Check : Suspend_functor
{
bool const &woken_up;
Check(bool const &woken_up) : woken_up(woken_up) { }
bool suspend() override { return !woken_up; }
};
public:
Main_blockade(uint64_t timeout_ms) : _timeout_ms(timeout_ms) { }
void block() override;
void wakeup() override;
};
class Libc::Main_job : public Monitor::Job
{
private:
Main_blockade _blockade;
public:
Main_job(Monitor::Function &fn, uint64_t timeout_ms)
: Job(fn, _blockade), _blockade(timeout_ms)
{ }
};
/** /**
@ -79,7 +123,7 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
* *
* Not mirrored to forked processes. Preserved across 'execve' calls. * Not mirrored to forked processes. Preserved across 'execve' calls.
*/ */
Allocator &_heap; Genode::Allocator &_heap;
/** /**
* Name of the current binary's ROM module * Name of the current binary's ROM module
@ -344,7 +388,7 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
public: public:
Kernel(Genode::Env &env, Allocator &heap); Kernel(Genode::Env &env, Genode::Allocator &heap);
~Kernel() { error(__PRETTY_FUNCTION__, " should not be executed!"); } ~Kernel() { error(__PRETTY_FUNCTION__, " should not be executed!"); }
@ -484,53 +528,13 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
bool _monitor(Genode::Lock &mutex, Function &fn, uint64_t timeout_ms) override bool _monitor(Genode::Lock &mutex, Function &fn, uint64_t timeout_ms) override
{ {
if (_main_context()) { if (_main_context()) {
Main_job job { fn, timeout_ms };
struct Job : Monitor::Job
{
Kernel &_kernel;
uint64_t _timeout_ms;
bool _timeout_valid { _timeout_ms != 0 };
struct Check : Suspend_functor
{
bool const &completed;
Check(bool const &completed) : completed(completed) { }
bool suspend() override
{
return !completed;
}
} check { _completed };
Job(Monitor::Function &fn, Kernel &kernel,
Timer_accessor &timer_accessor, uint64_t timeout_ms)
:
Monitor::Job(fn, timer_accessor, 0 /* timeout handled by suspend */),
_kernel(kernel), _timeout_ms(timeout_ms)
{ }
void wait_for_completion() override
{
do {
_timeout_ms = _kernel._suspend_main(check, _timeout_ms);
_expired = _timeout_valid && !_timeout_ms;
} while (!completed() && !expired());
}
void complete() override
{
_completed = true;
_kernel._resume_main();
}
} job { fn, *this, _timer_accessor, timeout_ms };
_monitors.monitor(mutex, job); _monitors.monitor(mutex, job);
return job.completed(); return job.completed();
} else { } else {
Monitor::Job job { fn, _timer_accessor, timeout_ms }; Pthread_job job { fn, _timer_accessor, timeout_ms };
_monitors.monitor(mutex, job); _monitors.monitor(mutex, job);
return job.completed(); return job.completed();
@ -609,6 +613,14 @@ struct Libc::Kernel final : Vfs::Io_response_handler,
*/ */
bool main_context() const { return _main_context(); } bool main_context() const { return _main_context(); }
void resume_main()
{
if (_main_context())
_resume_main();
else
Signal_transmitter(*_resume_main_handler).submit();
}
/** /**
* Execute application code while already executing in run() * Execute application code while already executing in run()
*/ */

View File

@ -19,9 +19,28 @@
#include <base/registry.h> #include <base/registry.h>
/* libc-internal includes */ /* libc-internal includes */
#include <internal/timer.h>
#include <internal/types.h> #include <internal/types.h>
namespace Libc { class Blockade; };
class Libc::Blockade
{
protected:
bool _woken_up { false };
bool _expired { false };
public:
bool woken_up() const { return _woken_up; }
bool expired() const { return _expired; }
virtual void block() = 0;
virtual void wakeup() = 0;
};
namespace Libc { class Monitor; }; namespace Libc { class Monitor; };
@ -32,10 +51,10 @@ class Libc::Monitor : Interface
struct Job; struct Job;
struct Pool; struct Pool;
protected:
struct Function : Interface { virtual bool execute() = 0; }; struct Function : Interface { virtual bool execute() = 0; };
protected:
virtual bool _monitor(Genode::Lock &, Function &, uint64_t) = 0; virtual bool _monitor(Genode::Lock &, Function &, uint64_t) = 0;
virtual void _charge_monitors() = 0; virtual void _charge_monitors() = 0;
@ -70,52 +89,27 @@ class Libc::Monitor : Interface
}; };
struct Libc::Monitor::Job : Timeout_handler struct Libc::Monitor::Job
{ {
private: private:
Monitor::Function &_fn; Monitor::Function &_fn;
Blockade &_blockade;
protected:
bool _completed { false };
bool _expired { false };
Lock _blockade { Lock::LOCKED };
Constructible<Timeout> _timeout;
public: public:
Job(Monitor::Function &fn, Job(Monitor::Function &fn, Blockade &blockade)
Timer_accessor &timer_accessor, uint64_t timeout_ms) : _fn(fn), _blockade(blockade) { }
: _fn(fn)
{
if (timeout_ms) {
_timeout.construct(timer_accessor, *this);
_timeout->start(timeout_ms);
}
}
bool completed() const { return _completed; } virtual ~Job() { }
bool expired() const { return _expired; }
bool execute() { return _fn.execute(); }
virtual void wait_for_completion() { _blockade.lock(); } bool execute() { return _fn.execute(); }
virtual void complete() bool completed() const { return _blockade.woken_up(); }
{ bool expired() const { return _blockade.expired(); }
_completed = true;
_blockade.unlock();
}
/** void wait_for_completion() { _blockade.block(); }
* Timeout_handler interface void complete() { _blockade.wakeup(); }
*/
void handle_timeout() override
{
_expired = true;
_blockade.unlock();
}
}; };

View File

@ -26,11 +26,15 @@
/* libc-internal includes */ /* libc-internal includes */
#include <internal/types.h> #include <internal/types.h>
#include <internal/monitor.h>
#include <internal/timer.h>
namespace Libc { namespace Libc {
struct Pthread; struct Pthread;
struct Pthread_registry; struct Pthread_registry;
struct Pthread_blockade;
struct Pthread_job;
} }
@ -293,4 +297,57 @@ struct pthread : Libc::Pthread
}; };
class Libc::Pthread_blockade : public Blockade, public Timeout_handler
{
private:
Lock _blockade { Lock::LOCKED };
Constructible<Timeout> _timeout;
public:
Pthread_blockade(Timer_accessor &timer_accessor, uint64_t timeout_ms)
{
if (timeout_ms) {
_timeout.construct(timer_accessor, *this);
_timeout->start(timeout_ms);
}
}
void block() override { _blockade.lock(); }
void wakeup() override
{
_woken_up = true;
_blockade.unlock();
}
/**
* Timeout_handler interface
*/
void handle_timeout() override
{
_expired = true;
_blockade.unlock();
}
};
struct Libc::Pthread_job : Monitor::Job
{
private:
Pthread_blockade _blockade;
public:
Pthread_job(Monitor::Function &fn,
Timer_accessor &timer_accessor, uint64_t timeout_ms)
:
Job(fn, _blockade),
_blockade(timer_accessor, timeout_ms)
{ }
};
#endif /* _LIBC__INTERNAL__PTHREAD_H_ */ #endif /* _LIBC__INTERNAL__PTHREAD_H_ */

View File

@ -19,6 +19,27 @@
Libc::Kernel * Libc::Kernel::_kernel_ptr; Libc::Kernel * Libc::Kernel::_kernel_ptr;
/**
* Blockade for main context
*/
inline void Libc::Main_blockade::block()
{
Check check { _woken_up };
do {
_timeout_ms = Kernel::kernel().suspend(check, _timeout_ms);
_expired = _timeout_valid && !_timeout_ms;
} while (!woken_up() && !expired());
}
inline void Libc::Main_blockade::wakeup()
{
_woken_up = true;
Kernel::kernel().resume_main();
}
/** /**
* Main context execution was suspended (on fork) * Main context execution was suspended (on fork)
* *
@ -376,8 +397,8 @@ Libc::Kernel::Kernel(Genode::Env &env, Genode::Allocator &heap)
{ {
atexit(close_file_descriptors_on_exit); atexit(close_file_descriptors_on_exit);
init_semaphore_support(*this); init_semaphore_support(_timer_accessor);
init_pthread_support(*this, *this, *this); init_pthread_support(*this, *this, _timer_accessor);
_env.ep().register_io_progress_handler(*this); _env.ep().register_io_progress_handler(*this);

View File

@ -3,7 +3,6 @@
* \author Christian Prochaska * \author Christian Prochaska
* \author Christian Helmuth * \author Christian Helmuth
* \date 2012-03-12 * \date 2012-03-12
*
*/ */
/* /*
@ -27,28 +26,31 @@
#include <stdlib.h> /* malloc, free */ #include <stdlib.h> /* malloc, free */
/* libc-internal includes */ /* libc-internal includes */
#include <internal/pthread.h>
#include <internal/init.h> #include <internal/init.h>
#include <internal/suspend.h> #include <internal/kernel.h>
#include <internal/pthread.h>
#include <internal/resume.h> #include <internal/resume.h>
#include <internal/monitor.h> #include <internal/suspend.h>
#include <internal/time.h> #include <internal/time.h>
#include <internal/timer.h>
using namespace Libc; using namespace Libc;
static Thread *_main_thread_ptr; static Thread *_main_thread_ptr;
static Resume *_resume_ptr; static Resume *_resume_ptr;
static Suspend *_suspend_ptr; static Suspend *_suspend_ptr;
static Monitor *_monitor_ptr; static Timer_accessor *_timer_accessor_ptr;
void Libc::init_pthread_support(Monitor &monitor, Suspend &suspend, Resume &resume) void Libc::init_pthread_support(Suspend &suspend, Resume &resume,
Timer_accessor &timer_accessor)
{ {
_main_thread_ptr = Thread::myself(); _main_thread_ptr = Thread::myself();
_monitor_ptr = &monitor; _suspend_ptr = &suspend;
_suspend_ptr = &suspend; _resume_ptr = &resume;
_resume_ptr = &resume; _timer_accessor_ptr = &timer_accessor;
} }
@ -186,61 +188,129 @@ struct pthread_mutex_attr { pthread_mutextype type; };
* This class is named 'struct pthread_mutex' because the 'pthread_mutex_t' * This class is named 'struct pthread_mutex' because the 'pthread_mutex_t'
* type is defined as 'struct pthread_mutex *' in '_pthreadtypes.h' * type is defined as 'struct pthread_mutex *' in '_pthreadtypes.h'
*/ */
struct pthread_mutex class pthread_mutex : Genode::Noncopyable
{ {
pthread_t _owner { nullptr }; private:
unsigned _applicants { 0 };
Lock _data_mutex;
Lock _monitor_mutex;
struct Missing_call_of_init_pthread_support : Exception { }; struct Applicant : Genode::Noncopyable
struct Applicant
{ {
pthread_mutex &m; pthread_t const thread;
Applicant(pthread_mutex &m) : m(m) Applicant *next { nullptr };
{
Lock::Guard lock_guard(m._data_mutex);
++m._applicants;
}
~Applicant() Libc::Blockade &blockade;
{
Lock::Guard lock_guard(m._data_mutex); Applicant(pthread_t thread, Libc::Blockade &blockade)
--m._applicants; : thread(thread), blockade(blockade)
} { }
}; };
Monitor & _monitor() Applicant *_applicants { nullptr };
{
if (!_monitor_ptr)
throw Missing_call_of_init_pthread_support();
return *_monitor_ptr;
}
pthread_mutex() { } protected:
virtual ~pthread_mutex() { } pthread_t _owner { nullptr };
Lock _data_mutex;
/* /* _data_mutex must be hold when calling the following methods */
* The behavior of the following function follows the "robust mutex"
* described IEEE Std 1003.1 POSIX.1-2017 void _append_applicant(Applicant *applicant)
* https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_lock.html {
*/ Applicant **tail = &_applicants;
virtual int lock() = 0;
virtual int timedlock(timespec const &) = 0; for (; *tail; tail = &(*tail)->next) ;
virtual int trylock() = 0;
virtual int unlock() = 0; *tail = applicant;
}
void _remove_applicant(Applicant *applicant)
{
Applicant **a = &_applicants;
for (; *a && *a != applicant; a = &(*a)->next) ;
*a = applicant->next;
}
void _next_applicant_to_owner()
{
if (Applicant *next = _applicants) {
_remove_applicant(next);
_owner = next->thread;
next->blockade.wakeup();
} else {
_owner = nullptr;
}
}
bool _applicant_for_mutex(pthread_t thread, Libc::Blockade &blockade)
{
Applicant applicant { thread, blockade };
_append_applicant(&applicant);
_data_mutex.unlock();
blockade.block();
_data_mutex.lock();
if (blockade.woken_up()) {
return true;
} else {
_remove_applicant(&applicant);
return false;
}
}
struct Missing_call_of_init_pthread_support : Exception { };
Timer_accessor & _timer_accessor()
{
if (!_timer_accessor_ptr)
throw Missing_call_of_init_pthread_support();
return *_timer_accessor_ptr;
}
/**
* Enqueue current context as applicant for mutex
*
* Return true if mutex was aquired, false on timeout expiration.
*/
bool _apply_for_mutex(pthread_t thread, Libc::uint64_t timeout_ms)
{
if (Libc::Kernel::kernel().main_context()) {
Main_blockade blockade { timeout_ms };
return _applicant_for_mutex(thread, blockade);
} else {
Pthread_blockade blockade { _timer_accessor(), timeout_ms };
return _applicant_for_mutex(thread, blockade);
}
}
public:
pthread_mutex() { }
virtual ~pthread_mutex() { }
/*
* The behavior of the following function follows the "robust mutex"
* described IEEE Std 1003.1 POSIX.1-2017
* https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_lock.html
*/
virtual int lock() = 0;
virtual int timedlock(timespec const &) = 0;
virtual int trylock() = 0;
virtual int unlock() = 0;
}; };
struct Libc::Pthread_mutex_normal : pthread_mutex struct Libc::Pthread_mutex_normal : pthread_mutex
{ {
/* unsynchronized try */
int _try_lock(pthread_t thread) int _try_lock(pthread_t thread)
{ {
Lock::Guard lock_guard(_data_mutex);
if (!_owner) { if (!_owner) {
_owner = thread; _owner = thread;
return 0; return 0;
@ -251,30 +321,25 @@ struct Libc::Pthread_mutex_normal : pthread_mutex
int lock() override final int lock() override final
{ {
Lock::Guard monitor_guard(_monitor_mutex);
pthread_t const myself = pthread_self(); pthread_t const myself = pthread_self();
Lock::Guard lock_guard(_data_mutex);
/* fast path without lock contention */ /* fast path without lock contention */
if (_try_lock(myself) == 0) if (_try_lock(myself) == 0)
return 0; return 0;
{ _apply_for_mutex(myself, 0);
Applicant guard { *this };
_monitor().monitor(_monitor_mutex,
[&] { return _try_lock(myself) == 0; });
}
return 0; return 0;
} }
int timedlock(timespec const &abs_timeout) override final int timedlock(timespec const &abs_timeout) override final
{ {
Lock::Guard monitor_guard(_monitor_mutex);
pthread_t const myself = pthread_self(); pthread_t const myself = pthread_self();
Lock::Guard lock_guard(_data_mutex);
/* fast path without lock contention - does not check abstimeout according to spec */ /* fast path without lock contention - does not check abstimeout according to spec */
if (_try_lock(myself) == 0) if (_try_lock(myself) == 0)
return 0; return 0;
@ -286,37 +351,29 @@ struct Libc::Pthread_mutex_normal : pthread_mutex
if (!timeout_ms) if (!timeout_ms)
return ETIMEDOUT; return ETIMEDOUT;
{ if (_apply_for_mutex(myself, timeout_ms))
Applicant guard { *this }; return 0;
else
auto fn = [&] { return _try_lock(myself) == 0; }; return ETIMEDOUT;
if (_monitor().monitor(_monitor_mutex, fn, timeout_ms))
return 0;
else
return ETIMEDOUT;
}
return 0;
} }
int trylock() override final int trylock() override final
{ {
return _try_lock(pthread_self()); pthread_t const myself = pthread_self();
Lock::Guard lock_guard(_data_mutex);
return _try_lock(myself);
} }
int unlock() override final int unlock() override final
{ {
Lock::Guard monitor_guard(_monitor_mutex);
Lock::Guard lock_guard(_data_mutex); Lock::Guard lock_guard(_data_mutex);
if (_owner != pthread_self()) if (_owner != pthread_self())
return EPERM; return EPERM;
_owner = nullptr; _next_applicant_to_owner();
if (_applicants)
_monitor().charge_monitors();
return 0; return 0;
} }
@ -325,73 +382,56 @@ struct Libc::Pthread_mutex_normal : pthread_mutex
struct Libc::Pthread_mutex_errorcheck : pthread_mutex struct Libc::Pthread_mutex_errorcheck : pthread_mutex
{ {
enum Try_lock_result { SUCCESS, BUSY, DEADLOCK }; /* unsynchronized try */
int _try_lock(pthread_t thread)
Try_lock_result _try_lock(pthread_t thread)
{ {
Lock::Guard lock_guard(_data_mutex);
if (!_owner) { if (!_owner) {
_owner = thread; _owner = thread;
return SUCCESS; return 0;
} }
return _owner == thread ? DEADLOCK : BUSY; return _owner == thread ? EDEADLK : EBUSY;
} }
int lock() override final int lock() override final
{ {
Lock::Guard monitor_guard(_monitor_mutex);
pthread_t const myself = pthread_self(); pthread_t const myself = pthread_self();
/* fast path without lock contention */ Lock::Guard lock_guard(_data_mutex);
switch (_try_lock(myself)) {
case SUCCESS: return 0;
case DEADLOCK: return EDEADLK;
case BUSY: [[fallthrough]];
}
{ /* fast path without lock contention (or deadlock) */
Applicant guard { *this }; int const result = _try_lock(myself);
if (!result || result == EDEADLK)
return result;
_monitor().monitor(_monitor_mutex, [&] { _apply_for_mutex(myself, 0);
/* DEADLOCK already handled above - just check for SUCCESS */
return _try_lock(myself) == SUCCESS;
});
}
return 0; return 0;
} }
int timedlock(timespec const &) override final int timedlock(timespec const &) override final
{ {
/* XXX not implemented yet */
return ENOSYS; return ENOSYS;
} }
int trylock() override final int trylock() override final
{ {
switch (_try_lock(pthread_self())) { pthread_t const myself = pthread_self();
case SUCCESS: return 0;
case DEADLOCK: return EDEADLK;
case BUSY: return EBUSY;
}
return EBUSY; Lock::Guard lock_guard(_data_mutex);
return _try_lock(myself);
} }
int unlock() override final int unlock() override final
{ {
Lock::Guard monitor_guard(_monitor_mutex);
Lock::Guard lock_guard(_data_mutex); Lock::Guard lock_guard(_data_mutex);
if (_owner != pthread_self()) if (_owner != pthread_self())
return EPERM; return EPERM;
_owner = nullptr; _next_applicant_to_owner();
if (_applicants)
_monitor().charge_monitors();
return 0; return 0;
} }
@ -402,13 +442,11 @@ struct Libc::Pthread_mutex_recursive : pthread_mutex
{ {
unsigned _nesting_level { 0 }; unsigned _nesting_level { 0 };
/* unsynchronized try */
int _try_lock(pthread_t thread) int _try_lock(pthread_t thread)
{ {
Lock::Guard lock_guard(_data_mutex);
if (!_owner) { if (!_owner) {
_owner = thread; _owner = thread;
_nesting_level = 1;
return 0; return 0;
} else if (_owner == thread) { } else if (_owner == thread) {
++_nesting_level; ++_nesting_level;
@ -420,20 +458,15 @@ struct Libc::Pthread_mutex_recursive : pthread_mutex
int lock() override final int lock() override final
{ {
Lock::Guard monitor_guard(_monitor_mutex);
pthread_t const myself = pthread_self(); pthread_t const myself = pthread_self();
Lock::Guard lock_guard(_data_mutex);
/* fast path without lock contention */ /* fast path without lock contention */
if (_try_lock(myself) == 0) if (_try_lock(myself) == 0)
return 0; return 0;
{ _apply_for_mutex(myself, 0);
Applicant guard { *this };
_monitor().monitor(_monitor_mutex,
[&] { return _try_lock(myself) == 0; });
}
return 0; return 0;
} }
@ -445,23 +478,24 @@ struct Libc::Pthread_mutex_recursive : pthread_mutex
int trylock() override final int trylock() override final
{ {
return _try_lock(pthread_self()); pthread_t const myself = pthread_self();
Lock::Guard lock_guard(_data_mutex);
return _try_lock(myself);
} }
int unlock() override final int unlock() override final
{ {
Lock::Guard monitor_guard(_monitor_mutex);
Lock::Guard lock_guard(_data_mutex); Lock::Guard lock_guard(_data_mutex);
if (_owner != pthread_self()) if (_owner != pthread_self())
return EPERM; return EPERM;
--_nesting_level; if (_nesting_level == 0)
if (_nesting_level == 0) { _next_applicant_to_owner();
_owner = nullptr; else
if (_applicants) --_nesting_level;
_monitor().charge_monitors();
}
return 0; return 0;
} }

View File

@ -24,142 +24,192 @@
#include <time.h> #include <time.h>
/* libc-internal includes */ /* libc-internal includes */
#include <internal/monitor.h>
#include <internal/errno.h> #include <internal/errno.h>
#include <internal/types.h>
#include <internal/time.h>
#include <internal/init.h> #include <internal/init.h>
#include <internal/kernel.h>
#include <internal/monitor.h>
#include <internal/time.h>
#include <internal/types.h>
using namespace Libc; using namespace Libc;
static Monitor *_monitor_ptr; static Timer_accessor *_timer_accessor_ptr;
void Libc::init_semaphore_support(Monitor &monitor) void Libc::init_semaphore_support(Timer_accessor &timer_accessor)
{ {
_monitor_ptr = &monitor; _timer_accessor_ptr = &timer_accessor;
} }
extern "C" { /*
* This class is named 'struct sem' because the 'sem_t' type is
* defined as 'struct sem*' in 'semaphore.h'
*/
struct sem : Genode::Noncopyable
{
private:
/* struct Applicant : Genode::Noncopyable
* This class is named 'struct sem' because the 'sem_t' type is
* defined as 'struct sem*' in 'semaphore.h'
*/
struct sem
{
int _count;
unsigned _applicants { 0 };
Lock _data_mutex;
Lock _monitor_mutex;
struct Missing_call_of_init_pthread_support : Exception { };
struct Applicant
{ {
sem &s; Applicant *next { nullptr };
Applicant(sem &s) : s(s) Libc::Blockade &blockade;
{
Lock::Guard lock_guard(s._data_mutex);
++s._applicants;
}
~Applicant() Applicant(Libc::Blockade &blockade) : blockade(blockade) { }
{
Lock::Guard lock_guard(s._data_mutex);
--s._applicants;
}
}; };
Monitor & _monitor() Applicant *_applicants { nullptr };
int _count;
Lock _data_mutex;
/* _data_mutex must be hold when calling the following methods */
void _append_applicant(Applicant *applicant)
{ {
if (!_monitor_ptr) Applicant **tail = &_applicants;
throw Missing_call_of_init_pthread_support();
return *_monitor_ptr; for (; *tail; tail = &(*tail)->next) ;
*tail = applicant;
} }
sem(int value) : _count(value) { } void _remove_applicant(Applicant *applicant)
{
int trydown() Applicant **a = &_applicants;
for (; *a && *a != applicant; a = &(*a)->next) ;
*a = applicant->next;
}
void _count_up()
{
if (Applicant *next = _applicants) {
_remove_applicant(next);
next->blockade.wakeup();
} else {
++_count;
}
}
bool _applicant_for_semaphore(Libc::Blockade &blockade)
{
Applicant applicant { blockade };
_append_applicant(&applicant);
_data_mutex.unlock();
blockade.block();
_data_mutex.lock();
if (blockade.woken_up()) {
return true;
} else {
_remove_applicant(&applicant);
return false;
}
}
struct Missing_call_of_init_semaphore_support : Exception { };
Timer_accessor & _timer_accessor()
{
if (!_timer_accessor_ptr)
throw Missing_call_of_init_semaphore_support();
return *_timer_accessor_ptr;
}
/**
* Enqueue current context as applicant for semaphore
*
* Return true if down was successful, false on timeout expiration.
*/
bool _apply_for_semaphore(Libc::uint64_t timeout_ms)
{
if (Libc::Kernel::kernel().main_context()) {
Main_blockade blockade { timeout_ms };
return _applicant_for_semaphore(blockade);
} else {
Pthread_blockade blockade { _timer_accessor(), timeout_ms };
return _applicant_for_semaphore(blockade);
}
}
/* unsynchronized try */
int _try_down()
{ {
Lock::Guard lock_guard(_data_mutex);
if (_count > 0) { if (_count > 0) {
_count--; --_count;
return 0; return 0;
} }
return EBUSY; return EBUSY;
} }
public:
sem(int value) : _count(value) { }
int count() const { return _count; }
int trydown()
{
Lock::Guard lock_guard(_data_mutex);
return _try_down();
}
int down() int down()
{ {
Lock::Guard monitor_guard(_monitor_mutex); Lock::Guard lock_guard(_data_mutex);
/* fast path without contention */ /* fast path */
if (trydown() == 0) if (_try_down() == 0)
return 0; return 0;
{ _apply_for_semaphore(0);
Applicant guard { *this };
auto fn = [&] { return trydown() == 0; };
(void)_monitor().monitor(_monitor_mutex, fn);
}
return 0; return 0;
} }
int down_timed(timespec const &abs_timeout) int down_timed(timespec const &abs_timeout)
{ {
Lock::Guard monitor_guard(_monitor_mutex); Lock::Guard lock_guard(_data_mutex);
/* fast path without wait - does not check abstimeout according to spec */ /* fast path */
if (trydown() == 0) if (_try_down() == 0)
return 0; return 0;
timespec abs_now; timespec abs_now;
clock_gettime(CLOCK_REALTIME, &abs_now); clock_gettime(CLOCK_REALTIME, &abs_now);
uint64_t const timeout_ms = calculate_relative_timeout_ms(abs_now, abs_timeout); Libc::uint64_t const timeout_ms =
calculate_relative_timeout_ms(abs_now, abs_timeout);
if (!timeout_ms) if (!timeout_ms)
return ETIMEDOUT; return ETIMEDOUT;
{ if (_apply_for_semaphore(timeout_ms))
Applicant guard { *this }; return 0;
else
auto fn = [&] { return trydown() == 0; }; return ETIMEDOUT;
if (_monitor().monitor(_monitor_mutex, fn, timeout_ms))
return 0;
else
return ETIMEDOUT;
}
} }
int up() int up()
{ {
Lock::Guard monitor_guard(_monitor_mutex);
Lock::Guard lock_guard(_data_mutex); Lock::Guard lock_guard(_data_mutex);
_count++; _count_up();
if (_applicants)
_monitor().charge_monitors();
return 0; return 0;
} }
};
int count()
{
return _count;
}
};
extern "C" {
int sem_close(sem_t *) int sem_close(sem_t *)
{ {

View File

@ -325,7 +325,7 @@ static void *thread_mutex_func(void *arg)
/* unlock normal mutex */ /* unlock normal mutex */
if (pthread_mutex_unlock(&test_mutex_data->normal_mutex) != 0) { if (pthread_mutex_unlock(&test_mutex_data->normal_mutex) != 0) {
printf("Error: could not lock normal mutex\n"); printf("Error: could not unlock normal mutex\n");
exit(-1); exit(-1);
} }
@ -568,15 +568,15 @@ struct Test_mutex_stress
Test_mutex_stress() Test_mutex_stress()
{ {
printf("main thread: start %s stress test\n", mutex.type_string()); printf("main thread: start %s stress test\n", mutex.type_string());
pthread_mutex_lock(mutex.mutex());
for (Thread &t : threads) t.start(); for (Thread &t : threads) t.start();
pthread_mutex_unlock(mutex.mutex());
for (Thread &t : threads) t.join(); for (Thread &t : threads) t.join();
printf("main thread: finished %s stress test\n", mutex.type_string()); printf("main thread: finished %s stress test\n", mutex.type_string());
} }
}; };
extern "C" void wait_for_continue();
static void test_mutex_stress() static void test_mutex_stress()
{ {
printf("main thread: stressing mutexes\n"); printf("main thread: stressing mutexes\n");
@ -689,7 +689,7 @@ struct Test_cond
void signaller() void signaller()
{ {
printf("signaller: started\n"); Genode::log("signaller: started");
unsigned num_events = 0; unsigned num_events = 0;
bool test_done = false; bool test_done = false;
@ -708,7 +708,7 @@ struct Test_cond
pthread_cond_signal(_cond.cond()); pthread_cond_signal(_cond.cond());
break; break;
case State::SHUTDOWN: case State::SHUTDOWN:
printf("signaller: shutting down\n"); Genode::log("signaller: shutting down");
_shared_state = State::END; _shared_state = State::END;
++num_events; ++num_events;
pthread_cond_broadcast(_cond.cond()); pthread_cond_broadcast(_cond.cond());
@ -723,7 +723,7 @@ struct Test_cond
usleep(1000); usleep(1000);
} }
printf("signaller: finished after %u state changes\n", num_events); Genode::log("signaller: finished after ", num_events, " state changes");
} }
static void *waiter_fn(void *arg) static void *waiter_fn(void *arg)
@ -736,7 +736,7 @@ struct Test_cond
{ {
char const * const note = main_thread ? "(main thread)" : ""; char const * const note = main_thread ? "(main thread)" : "";
printf("waiter%s: started\n", note); Genode::log("waiter", note, ": started");
unsigned pings = 0, pongs = 0; unsigned pings = 0, pongs = 0;
unsigned long iterations = 0; unsigned long iterations = 0;
@ -747,7 +747,7 @@ struct Test_cond
auto handle_state = [&] { auto handle_state = [&] {
unsigned const num_events = pings + pongs; unsigned const num_events = pings + pongs;
if (num_events == 2000) { if (num_events == 2000) {
printf("waiter%s: request shutdown\n", note); Genode::log("waiter", note, ": request shutdown");
_shared_state = State::SHUTDOWN; _shared_state = State::SHUTDOWN;
} else if (num_events % 2 == 0) { } else if (num_events % 2 == 0) {
pthread_cond_wait(_cond.cond(), _mutex.mutex()); pthread_cond_wait(_cond.cond(), _mutex.mutex());
@ -777,8 +777,8 @@ struct Test_cond
++iterations; ++iterations;
} }
printf("waiter%s: finished (pings=%u, pongs=%u, iterations=%lu)\n", Genode::log("waiter", note, ": finished (pings=", pings, ", pongs=",
note, pings, pongs, iterations); pongs, ", iterations=", iterations, ")");
} }
Test_cond() Test_cond()

View File

@ -37,12 +37,14 @@ MIRROR_FROM_LIBPORTS := lib/mk/libc_pipe.mk \
src/lib/libc_pipe \ src/lib/libc_pipe \
lib/mk/libc-mem.mk \ lib/mk/libc-mem.mk \
lib/mk/libc-common.inc \ lib/mk/libc-common.inc \
src/lib/libc/libc_mem_alloc.cc \
src/lib/libc/internal/mem_alloc.h \
src/lib/libc/internal/init.h \ src/lib/libc/internal/init.h \
src/lib/libc/internal/thread_create.h \ src/lib/libc/internal/mem_alloc.h \
src/lib/libc/internal/monitor.h \
src/lib/libc/internal/pthread.h \ src/lib/libc/internal/pthread.h \
src/lib/libc/internal/thread_create.h \
src/lib/libc/internal/timer.h \
src/lib/libc/internal/types.h \ src/lib/libc/internal/types.h \
src/lib/libc/libc_mem_alloc.cc \
include/libc-plugin \ include/libc-plugin \
lib/import/import-qemu-usb_include.mk \ lib/import/import-qemu-usb_include.mk \
lib/mk/qemu-usb_include.mk \ lib/mk/qemu-usb_include.mk \

View File

@ -38,12 +38,14 @@ MIRROR_FROM_LIBPORTS := lib/mk/libc_pipe.mk \
src/lib/libc_pipe \ src/lib/libc_pipe \
lib/mk/libc-mem.mk \ lib/mk/libc-mem.mk \
lib/mk/libc-common.inc \ lib/mk/libc-common.inc \
src/lib/libc/libc_mem_alloc.cc \
src/lib/libc/internal/mem_alloc.h \
src/lib/libc/internal/init.h \ src/lib/libc/internal/init.h \
src/lib/libc/internal/thread_create.h \ src/lib/libc/internal/mem_alloc.h \
src/lib/libc/internal/monitor.h \
src/lib/libc/internal/pthread.h \ src/lib/libc/internal/pthread.h \
src/lib/libc/internal/thread_create.h \
src/lib/libc/internal/timer.h \
src/lib/libc/internal/types.h \ src/lib/libc/internal/types.h \
src/lib/libc/libc_mem_alloc.cc \
include/libc-plugin \ include/libc-plugin \
lib/import/import-qemu-usb_include.mk \ lib/import/import-qemu-usb_include.mk \
lib/mk/qemu-usb_include.mk \ lib/mk/qemu-usb_include.mk \