genode/os/include/os/timed_semaphore.h
Stefan Kalkowski 4254cb04e1 Reduce IPC with timer service in timed semaphore
Instead of using msleep to sleep periodically, and then increase jiffies
counter in the alarm scheduler implementation of the timed semaphore
use the 'trigger_periodic' call introduced by the change of the timer session
interface into an asynchronous one. Thereby, we can reduce the necessary IPC
communication with the timer service effectively.

Ref #35
2013-08-30 13:38:21 +02:00

244 lines
5.7 KiB
C++

/*
* \brief Semaphore implementation with timeout facility
* \author Stefan Kalkowski
* \date 2010-03-05
*
* This semaphore implementation allows to block on a semaphore for a
* given time instead of blocking indefinetely.
*
* For the timeout functionality the alarm framework is used.
*/
/*
* Copyright (C) 2010-2013 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 _INCLUDE__OS__TIMED_SEMAPHORE_H_
#define _INCLUDE__OS__TIMED_SEMAPHORE_H_
#include <base/thread.h>
#include <base/semaphore.h>
#include <timer_session/connection.h>
#include <os/alarm.h>
namespace Genode {
/**
* Alarm thread, which counts jiffies and triggers timeout events.
*/
class Timeout_thread : public Thread<4096>,
public Alarm_scheduler
{
private:
enum { JIFFIES_STEP_MS = 10 };
Timer::Connection _timer; /* timer session */
Signal_context _context;
Signal_receiver _receiver;
Genode::Alarm::Time _time; /* current time */
void entry(void);
public:
Timeout_thread() : Thread<4096>("alarm-timer"), _time(0)
{
_timer.sigh(_receiver.manage(&_context));
_timer.trigger_periodic(JIFFIES_STEP_MS*1000);
start();
}
Genode::Alarm::Time time(void) { return _time; }
/*
* Returns the singleton timeout-thread used for all timeouts.
*/
static Timeout_thread *alarm_timer();
};
class Timeout_exception : public Exception { };
class Nonblocking_exception : public Exception { };
/**
* Semaphore with timeout on down operation.
*/
class Timed_semaphore : public Semaphore
{
private:
typedef Fifo_semaphore_queue::Element Element;
/**
* Aborts blocking on the semaphore, raised when a timeout occured.
*
* \param element the waiting-queue element associated with a timeout.
* \return true if a thread was aborted/woken up
*/
bool _abort(Element *element)
{
Lock::Guard lock_guard(Semaphore::_meta_lock);
/* potentially, the queue is empty */
if (++Semaphore::_cnt <= 0) {
/*
* Iterate through the queue and find the thread,
* with the corresponding timeout.
*/
Element *first = Semaphore::_queue.dequeue();
Element *e = first;
while (true) {
/*
* Wakeup the thread.
*/
if (element == e) {
e->wake_up();
return true;
}
/*
* Noninvolved threads are enqueued again.
*/
Semaphore::_queue.enqueue(e);
e = Semaphore::_queue.dequeue();
/*
* Maybe, the alarm was triggered just after the corresponding
* thread was already dequeued, that's why we have to track
* whether we processed the whole queue.
*/
if (e == first)
break;
}
}
/* The right element was not found, so decrease counter again */
--Semaphore::_cnt;
return false;
}
/**
* Represents a timeout associated with the blocking-
* operation on a semaphore.
*/
class Timeout : public Alarm
{
private:
Timed_semaphore *_sem; /* Semaphore we block on */
Element *_element; /* Queue element timeout belongs to */
bool _triggered; /* Timeout expired */
Time _start;
public:
Timeout(Time duration, Timed_semaphore *s, Element *e)
: _sem(s), _element(e), _triggered(false)
{
Timeout_thread *tt = Timeout_thread::alarm_timer();
_start = tt->time();
tt->schedule_absolute(this, _start + duration);
}
void discard(void) { Timeout_thread::alarm_timer()->discard(this); }
bool triggered(void) { return _triggered; }
Time start() { return _start; }
protected:
bool on_alarm()
{
/* Abort blocking operation */
_triggered = _sem->_abort(_element);
return false;
}
};
public:
/**
* Constructor
*
* \param n initial counter value of the semphore
*/
Timed_semaphore(int n = 0) : Semaphore(n) { }
/**
* Decrements semaphore and blocks when it's already zero.
*
* \param t after t milliseconds of blocking a Timeout_exception is thrown.
* if t is zero do not block, instead raise an
* Nonblocking_exception.
* \return milliseconds the caller was blocked
*/
Alarm::Time down(Alarm::Time t)
{
Semaphore::_meta_lock.lock();
if (--Semaphore::_cnt < 0) {
/* If t==0 we shall not block */
if (t == 0) {
++_cnt;
Semaphore::_meta_lock.unlock();
throw Genode::Nonblocking_exception();
}
/*
* Create semaphore queue element representing the thread
* in the wait queue.
*/
Element queue_element;
Semaphore::_queue.enqueue(&queue_element);
Semaphore::_meta_lock.unlock();
/* Create the timeout */
Timeout to(t, this, &queue_element);
/*
* The thread is going to block on a local lock now,
* waiting for getting waked from another thread
* calling 'up()'
* */
queue_element.block();
/* Deactivate timeout */
to.discard();
/*
* When we were only woken up, because of a timeout,
* throw an exception.
*/
if (to.triggered())
throw Genode::Timeout_exception();
/* return blocking time */
return Timeout_thread::alarm_timer()->time() - to.start();
} else {
Semaphore::_meta_lock.unlock();
}
return 0;
}
/********************************
** Base class implementations **
********************************/
void down() { Semaphore::down(); }
void up() { Semaphore::up(); }
};
}
#endif /* _INCLUDE__OS__TIMED_SEMAPHORE_H_ */