os: introduce and test timeout framework

Ref #2170
This commit is contained in:
Martin Stein 2016-11-07 13:59:37 +01:00 committed by Christian Helmuth
parent f97e0f3fa0
commit 791138ee63
9 changed files with 671 additions and 0 deletions

View File

@ -0,0 +1,62 @@
/*
* \brief Interface of a time source that can handle one timeout at a time
* \author Martin Stein
* \date 2016-11-04
*/
/*
* Copyright (C) 2016 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 _OS__TIME_SOURCE_H_
#define _OS__TIME_SOURCE_H_
namespace Genode { class Time_source; }
/**
* Interface of a time source that can handle one timeout at a time
*/
struct Genode::Time_source
{
/**
* Makes it clear which time unit an interfaces takes
*/
struct Microseconds
{
unsigned long value;
explicit Microseconds(unsigned long const value) : value(value) { }
};
/**
* Interface of a timeout callback
*/
struct Timeout_handler
{
virtual void handle_timeout(Microseconds curr_time) = 0;
};
/**
* Return the current time of the source
*/
virtual Microseconds curr_time() const = 0;
/**
* Return the maximum timeout duration that the source can handle
*/
virtual Microseconds max_timeout() const = 0;
/**
* Install a timeout, overrides the last timeout if any
*
* \param duration timeout duration
* \param handler timeout callback
*/
virtual void schedule_timeout(Microseconds duration,
Timeout_handler &handler) = 0;
};
#endif /* _OS__TIME_SOURCE_H_ */

View File

@ -0,0 +1,259 @@
/*
* \brief Multiplexing one time source amongst different timeouts
* \author Martin Stein
* \date 2016-11-04
*/
/*
* Copyright (C) 2016 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 _OS__TIMEOUT_H_
#define _OS__TIMEOUT_H_
/* Genode includes */
#include <util/noncopyable.h>
#include <os/time_source.h>
#include <os/alarm.h>
namespace Genode {
class Timeout_scheduler;
class Timeout;
class Alarm_timeout_scheduler;
template <typename> class Periodic_timeout;
template <typename> class One_shot_timeout;
}
/**
* Interface of a time-source multiplexer
*/
struct Genode::Timeout_scheduler
{
using Microseconds = Time_source::Microseconds;
/**
* Read out the now time of the scheduler
*/
virtual Microseconds curr_time() const = 0;
/**
* Add a one-shot timeout to the schedule
*
* \param timeout timeout callback object
* \param duration timeout trigger delay
*/
virtual void schedule_one_shot(Timeout &timeout, Microseconds duration) = 0;
/**
* Add a periodic timeout to the schedule
*
* \param timeout timeout callback object
* \param duration timeout trigger period
*/
virtual void schedule_periodic(Timeout &timeout, Microseconds duration) = 0;
/**
* Remove timeout from the scheduler
*
* \param timeout corresponding timeout callback object
*/
virtual void discard(Timeout &timeout) = 0;
};
/**
* Timeout callback that can be used for both one-shot and periodic timeouts
*
* This class should be used only if it is necessary to use one timeout
* callback for both periodic and one-shot timeouts. This is the case, for
* example, in a Timer-session server. If this is not the case, the classes
* Periodic_timeout and One_shot_timeout are the better choice.
*/
class Genode::Timeout : private Noncopyable
{
friend class Alarm_timeout_scheduler;
public:
/**
* Interface of a timeout handler
*/
struct Handler
{
using Microseconds = Time_source::Microseconds;
virtual void handle_timeout(Microseconds curr_time) = 0;
};
private:
using Microseconds = Time_source::Microseconds;
struct Alarm : Genode::Alarm
{
Timeout_scheduler &timeout_scheduler;
Handler *handler = nullptr;
bool periodic;
Alarm(Timeout_scheduler &timeout_scheduler)
: timeout_scheduler(timeout_scheduler) { }
/*******************
** Genode::Alarm **
*******************/
bool on_alarm(unsigned) override;
} _alarm;
public:
Timeout(Timeout_scheduler &timeout_scheduler)
: _alarm(timeout_scheduler) { }
~Timeout() { _alarm.timeout_scheduler.discard(*this); }
void schedule_periodic(Microseconds duration, Handler &handler);
void schedule_one_shot(Microseconds duration, Handler &handler);
};
/**
* Periodic timeout that is linked to a custom handler, starts when constructed
*/
template <typename HANDLER>
struct Genode::Periodic_timeout : private Noncopyable
{
public:
using Microseconds = Timeout_scheduler::Microseconds;
private:
typedef void (HANDLER::*Handler_method)(Microseconds);
Timeout _timeout;
struct Handler : Timeout::Handler
{
HANDLER &object;
Handler_method const method;
Handler(HANDLER &object, Handler_method method)
: object(object), method(method) { }
/**********************
** Timeout::Handler **
**********************/
void handle_timeout(Microseconds curr_time) override {
(object.*method)(curr_time); }
} _handler;
public:
Periodic_timeout(Timeout_scheduler &timeout_scheduler,
HANDLER &object,
Handler_method method,
Microseconds duration)
:
_timeout(timeout_scheduler), _handler(object, method)
{
_timeout.schedule_periodic(duration, _handler);
}
};
/**
* One-shot timeout that is linked to a custom handler, started manually
*/
template <typename HANDLER>
class Genode::One_shot_timeout : private Noncopyable
{
private:
using Microseconds = Timeout_scheduler::Microseconds;
typedef void (HANDLER::*Handler_method)(Microseconds);
Timeout _timeout;
struct Handler : Timeout::Handler
{
HANDLER &object;
Handler_method const method;
Handler(HANDLER &object, Handler_method method)
: object(object), method(method) { }
/**********************
** Timeout::Handler **
**********************/
void handle_timeout(Microseconds curr_time) override {
(object.*method)(curr_time); }
} _handler;
public:
One_shot_timeout(Timeout_scheduler &timeout_scheduler,
HANDLER &object,
Handler_method method)
: _timeout(timeout_scheduler), _handler(object, method) { }
void start(Microseconds duration) {
_timeout.schedule_one_shot(duration, _handler); }
};
/**
* Timeout-scheduler implementation using the Alarm framework
*/
class Genode::Alarm_timeout_scheduler : private Noncopyable,
public Timeout_scheduler,
public Time_source::Timeout_handler
{
private:
Time_source &_time_source;
Alarm_scheduler _alarm_scheduler;
/**********************************
** Time_source::Timeout_handler **
**********************************/
void handle_timeout(Microseconds curr_time) override;
public:
Alarm_timeout_scheduler(Time_source &time_source);
/***********************
** Timeout_scheduler **
***********************/
void schedule_one_shot(Timeout &timeout, Microseconds duration) override;
void schedule_periodic(Timeout &timeout, Microseconds duration) override;
Microseconds curr_time() const override {
return _time_source.curr_time(); }
void discard(Timeout &timeout) override {
_alarm_scheduler.discard(&timeout._alarm); }
};
#endif /* _OS__TIMEOUT_H_ */

107
repos/os/include/os/timer.h Normal file
View File

@ -0,0 +1,107 @@
/*
* \brief Multiplexes a timer session amongst different timeouts
* \author Martin Stein
* \date 2016-11-04
*/
/*
* Copyright (C) 2016 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 _TIMER_H_
#define _TIMER_H_
/* Genode includes */
#include <timer_session/timer_session.h>
#include <os/time_source.h>
#include <os/timeout.h>
namespace Genode { class Timer; }
/**
* Multiplexes a timer session amongst different timeouts
*/
class Genode::Timer : public Timeout_scheduler
{
private:
class Time_source : public Genode::Time_source
{
private:
enum { MIN_TIMEOUT_US = 100000 };
using Signal_handler =
Genode::Signal_handler<Time_source>;
::Timer::Session &_session;
Signal_handler _signal_handler;
Timeout_handler *_handler = nullptr;
void _handle_timeout()
{
if (_handler) {
_handler->handle_timeout(curr_time()); }
}
public:
Time_source(::Timer::Session &session, Entrypoint &ep)
:
_session(session),
_signal_handler(ep, *this, &Time_source::_handle_timeout)
{
_session.sigh(_signal_handler);
}
Microseconds curr_time() const {
return Microseconds(1000ULL * _session.elapsed_ms()); }
void schedule_timeout(Microseconds duration,
Timeout_handler &handler)
{
if (duration.value < MIN_TIMEOUT_US) {
duration.value = MIN_TIMEOUT_US; }
if (duration.value > max_timeout().value) {
duration.value = max_timeout().value; }
_handler = &handler;
_session.trigger_once(duration.value);
}
Microseconds max_timeout() const {
return Microseconds(~0UL); }
} _time_source;
Alarm_timeout_scheduler _timeout_scheduler { _time_source };
public:
Timer(::Timer::Session &session, Entrypoint &ep)
: _time_source(session, ep) { }
/***********************
** Timeout_scheduler **
***********************/
void schedule_periodic(Timeout &timeout, Microseconds duration) override {
_timeout_scheduler.schedule_periodic(timeout, duration); }
void schedule_one_shot(Timeout &timeout, Microseconds duration) override {
_timeout_scheduler.schedule_one_shot(timeout, duration); }
Microseconds curr_time() const override {
return _timeout_scheduler.curr_time(); }
void discard(Timeout &timeout) override {
_timeout_scheduler.discard(timeout); }
};
#endif /* _TIMER_H_ */

View File

@ -0,0 +1,5 @@
SRC_CC += timeout.cc
LIBS += alarm
vpath % $(REP_DIR)/src/lib/timeout

72
repos/os/run/timeout.run Normal file
View File

@ -0,0 +1,72 @@
#
# Build
#
build "core init drivers/timer test/timeout"
#
# Boot image
#
create_boot_directory
install_config {
<config>
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
</parent-provides>
<default-route>
<any-service><parent/><any-child/></any-service>
</default-route>
<start name="timer">
<resource name="RAM" quantum="10M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="test">
<binary name="test-timeout"/>
<resource name="RAM" quantum="10M"/>
</start>
</config>
}
build_boot_image "core init timer test-timeout"
#
# Execution
#
append qemu_args "-nographic -m 64"
#
# We check for each timeout that has a distance of at least 200ms to each
# other timeout:
#
# 0 ms
# 0 ms
# 700 ms -> check for 700 ms
# 1000 ms -> check for 1000 ms
# 1400 ms -> check for 700 ms
# 2000 ms
# 2100 ms
# 2800 ms -> check for 700 ms
# 3000 ms -> check for 1000 ms
# 3250 ms -> check for 3250 ms
# 3500 ms -> check for 700 ms
# 4000 ms -> check for 1000 ms
# 4200 ms -> check for 700 ms
# 4900 ms
# 5000 ms
# 5200 ms -> check for 5200 ms
# 5600 ms -> check for 700 ms
# 6000 ms -> check for 1000 ms
#
run_genode_until ".*700ms timeout.*\n.*1000ms timeout.*\n.*700ms timeout.*\n.*700ms timeout.*\n.*1000ms timeout.*\n.*3250ms timeout.*\n.*700ms timeout.*\n.*1000ms timeout.*\n.*700ms timeout.*\n.*5200ms timeout.*\n.*700ms timeout.*\n.*1000ms timeout.*\n" 20

View File

@ -0,0 +1,101 @@
/*
* \brief Multiplexing one time source amongst different timeout subjects
* \author Martin Stein
* \date 2016-11-04
*/
/*
* Copyright (C) 2016 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.
*/
/* Genode includes */
#include <os/timeout.h>
using namespace Genode;
/*************
** Timeout **
*************/
void Timeout::schedule_periodic(Microseconds duration, Handler &handler)
{
_alarm.handler = &handler;
_alarm.periodic = true;
_alarm.timeout_scheduler.schedule_periodic(*this, duration);
}
void Timeout::schedule_one_shot(Microseconds duration, Handler &handler)
{
_alarm.handler = &handler;
_alarm.periodic = false;
_alarm.timeout_scheduler.schedule_one_shot(*this, duration);
}
/********************
** Timeout::Alarm **
********************/
bool Timeout::Alarm::on_alarm(unsigned)
{
if (handler) {
handler->handle_timeout(timeout_scheduler.curr_time()); }
return periodic;
}
/*****************************
** Alarm_timeout_scheduler **
*****************************/
void Alarm_timeout_scheduler::handle_timeout(Microseconds curr_time)
{
_alarm_scheduler.handle(curr_time.value);
unsigned long sleep_time_us;
Alarm::Time deadline_us;
if (_alarm_scheduler.next_deadline(&deadline_us)) {
sleep_time_us = deadline_us - curr_time.value;
} else {
sleep_time_us = _time_source.max_timeout().value; }
if (sleep_time_us == 0) {
sleep_time_us = 1; }
_time_source.schedule_timeout(Microseconds(sleep_time_us), *this);
}
Alarm_timeout_scheduler::Alarm_timeout_scheduler(Time_source &time_source)
:
_time_source(time_source)
{
time_source.schedule_timeout(Microseconds(0), *this);
}
void Alarm_timeout_scheduler::schedule_one_shot(Timeout &timeout,
Microseconds duration)
{
_alarm_scheduler.schedule_absolute(&timeout._alarm,
_time_source.curr_time().value +
duration.value);
if (_alarm_scheduler.head_timeout(&timeout._alarm)) {
_time_source.schedule_timeout(Microseconds(0), *this); }
}
void Alarm_timeout_scheduler::schedule_periodic(Timeout &timeout,
Microseconds duration)
{
_alarm_scheduler.handle(_time_source.curr_time().value);
_alarm_scheduler.schedule(&timeout._alarm, duration.value);
if (_alarm_scheduler.head_timeout(&timeout._alarm)) {
_time_source.schedule_timeout(Microseconds(0), *this); }
}

View File

@ -0,0 +1,60 @@
/*
* \brief Test for timeout library
* \author Martin Stein
* \date 2016-11-24
*/
/*
* Copyright (C) 2016 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.
*/
/* Genode includes */
#include <base/component.h>
#include <timer_session/connection.h>
#include <os/timer.h>
using namespace Genode;
class Main
{
private:
using Microseconds = Genode::Timer::Microseconds;
void _handle(Microseconds now, Cstring name) {
log(now.value / 1000, " ms: ", name, " timeout triggered"); }
void _handle_pt1(Microseconds now) { _handle(now, "Periodic 700ms"); }
void _handle_pt2(Microseconds now) { _handle(now, "Periodic 1000ms"); }
void _handle_ot1(Microseconds now) { _handle(now, "One-shot 3250ms"); }
void _handle_ot2(Microseconds now) { _handle(now, "One-shot 5200ms"); }
Timer::Connection _timer_connection;
Genode::Timer _timer;
Periodic_timeout<Main> _pt1 { _timer, *this, &Main::_handle_pt1, Microseconds(700000) };
Periodic_timeout<Main> _pt2 { _timer, *this, &Main::_handle_pt2, Microseconds(1000000) };
One_shot_timeout<Main> _ot1 { _timer, *this, &Main::_handle_ot1 };
One_shot_timeout<Main> _ot2 { _timer, *this, &Main::_handle_ot2 };
public:
Main(Env &env) : _timer_connection(env),
_timer(_timer_connection, env.ep())
{
_ot1.start(Microseconds(3250000));
_ot2.start(Microseconds(5300000));
}
};
/***************
** Component **
***************/
size_t Component::stack_size() { return 4 * 1024 * sizeof(addr_t); }
void Component::construct(Env &env) { static Main main(env); }

View File

@ -0,0 +1,4 @@
TARGET = test-timeout
SRC_CC += main.cc
LIBS += base timeout
INC_DIR += $(PRG_DIR)

View File

@ -75,3 +75,4 @@ cpu_sampler_noux
usb_hid
smartcard
new_delete
timeout