diff --git a/repos/base/lib/symbols/ld b/repos/base/lib/symbols/ld index 54081ae2a..95d72e439 100644 --- a/repos/base/lib/symbols/ld +++ b/repos/base/lib/symbols/ld @@ -201,6 +201,9 @@ _ZN6Genode23Alarm_timeout_scheduler18_schedule_periodicERNS_7TimeoutENS_12Micros _ZN6Genode23Alarm_timeout_scheduler7_enableEv T _ZN6Genode23Alarm_timeout_schedulerC1ERNS_11Time_sourceENS_12MicrosecondsE T _ZN6Genode23Alarm_timeout_schedulerC2ERNS_11Time_sourceENS_12MicrosecondsE T +_ZN6Genode23Alarm_timeout_schedulerD0Ev T +_ZN6Genode23Alarm_timeout_schedulerD1Ev T +_ZN6Genode23Alarm_timeout_schedulerD2Ev T _ZN6Genode25env_stack_area_region_mapE B 8 _ZN6Genode28env_stack_area_ram_allocatorE B 8 _ZN6Genode3Log3logEv T @@ -317,6 +320,9 @@ _ZN6Genode7Console7vprintfEPKcPv T _ZN6Genode7Console7vprintfEPKcSt9__va_list T _ZN6Genode7Timeout17schedule_one_shotENS_12MicrosecondsERNS0_7HandlerE T _ZN6Genode7Timeout17schedule_periodicENS_12MicrosecondsERNS0_7HandlerE T +_ZN6Genode7Timeout5AlarmD0Ev T +_ZN6Genode7Timeout5AlarmD1Ev T +_ZN6Genode7Timeout5AlarmD2Ev T _ZN6Genode7Timeout7discardEv T _ZN6Genode7cap_mapEv T _ZN6Genode7vprintfEPKcP13__va_list_tag T @@ -396,7 +402,7 @@ _ZTIN6Genode5ChildE D 72 _ZTIN6Genode6OutputE D 24 _ZTIN6Genode6ThreadE D 16 _ZTIN6Genode7ConsoleE D 16 -_ZTIN6Genode7Timeout5AlarmE D 24 +_ZTIN6Genode7Timeout5AlarmE D 16 _ZTIPDd D 32 _ZTIPDe D 32 _ZTIPDf D 32 @@ -555,7 +561,7 @@ _ZTVN6Genode5ChildE D 408 _ZTVN6Genode6OutputE D 48 _ZTVN6Genode6ThreadE D 48 _ZTVN6Genode7ConsoleE D 48 -_ZTVN6Genode7Timeout5AlarmE D 40 +_ZTVN6Genode7Timeout5AlarmE D 32 _ZTVSt10bad_typeid D 40 _ZTVSt13bad_exception D 40 _ZTVSt16bad_array_length D 40 @@ -578,6 +584,8 @@ _ZThn296_N5Timer10Connection8_discardERN6Genode7TimeoutE T _ZThn296_N5Timer10Connection9curr_timeEv T _ZThn4_N6Genode23Alarm_timeout_scheduler14handle_timeoutENS_8DurationE T _ZThn8_N6Genode23Alarm_timeout_scheduler14handle_timeoutENS_8DurationE T +_ZThn8_N6Genode23Alarm_timeout_schedulerD0Ev T +_ZThn8_N6Genode23Alarm_timeout_schedulerD1Ev T _ZdlPv W _ZdlPvPN6Genode11DeallocatorE T _ZdlPvPN6Genode9AllocatorE W diff --git a/repos/os/include/timer/timeout.h b/repos/os/include/timer/timeout.h index ebc7152e2..98ebdd2dc 100644 --- a/repos/os/include/timer/timeout.h +++ b/repos/os/include/timer/timeout.h @@ -21,7 +21,7 @@ /* Genode includes */ #include -#include +#include #include #include @@ -152,13 +152,44 @@ class Genode::Timeout : private Noncopyable private: - class Alarm : public Genode::Alarm + class Alarm { + friend class Alarm_timeout_scheduler; + private: - /* - * Noncopyable - */ + typedef unsigned long Time; + + struct Raw + { + Time deadline; + bool deadline_period; + Time period; + + bool is_pending_at(unsigned long time, bool time_period) const; + }; + + Lock _dispatch_lock { }; + Raw _raw { }; + int _active { 0 }; + Alarm *_next { nullptr }; + Alarm_timeout_scheduler *_scheduler { nullptr }; + + void _alarm_assign(Time period, + Time deadline, + bool deadline_period, + Alarm_timeout_scheduler *scheduler) + { + _raw.period = period; + _raw.deadline_period = deadline_period; + _raw.deadline = deadline; + _scheduler = scheduler; + } + + void _alarm_reset() { _alarm_assign(0, 0, false, 0), _active = 0, _next = 0; } + + bool _on_alarm(unsigned); + Alarm(Alarm const &); Alarm &operator = (Alarm const &); @@ -169,14 +200,9 @@ class Genode::Timeout : private Noncopyable bool periodic = false; Alarm(Timeout_scheduler &timeout_scheduler) - : timeout_scheduler(timeout_scheduler) { } + : timeout_scheduler(timeout_scheduler) { _alarm_reset(); } - - /******************* - ** Genode::Alarm ** - *******************/ - - bool on_alarm(unsigned) override; + virtual ~Alarm(); } _alarm; @@ -206,11 +232,26 @@ class Genode::Alarm_timeout_scheduler : private Noncopyable, { friend class Timer::Connection; friend class Timer::Root_component; + friend class Timeout::Alarm; private: + using Alarm = Timeout::Alarm; + Time_source &_time_source; - Alarm_scheduler _alarm_scheduler; + Lock _lock { }; + Alarm *_head { nullptr }; + Alarm::Time _now { 0UL }; + bool _now_period { false }; + Alarm::Raw _min_handle_period { }; + + void _alarm_unsynchronized_enqueue(Alarm *alarm); + + void _alarm_unsynchronized_dequeue(Alarm *alarm); + + Alarm *_alarm_get_pending_alarm(); + + void _alarm_setup_alarm(Alarm &alarm, Alarm::Time period, Alarm::Time deadline); void _enable(); @@ -230,13 +271,30 @@ class Genode::Alarm_timeout_scheduler : private Noncopyable, void _schedule_periodic(Timeout &timeout, Microseconds duration) override; void _discard(Timeout &timeout) override { - _alarm_scheduler.discard(&timeout._alarm); } + _alarm_discard(&timeout._alarm); } + + void _alarm_discard(Alarm *alarm); + + void _alarm_schedule_absolute(Alarm *alarm, Alarm::Time timeout); + + void _alarm_schedule(Alarm *alarm, Alarm::Time period); + + void _alarm_handle(Alarm::Time now); + + bool _alarm_next_deadline(Alarm::Time *deadline); + + bool _alarm_head_timeout(const Alarm * alarm) { return _head == alarm; } + + Alarm_timeout_scheduler(Alarm_timeout_scheduler const &); + Alarm_timeout_scheduler &operator = (Alarm_timeout_scheduler const &); public: Alarm_timeout_scheduler(Time_source &time_source, Microseconds min_handle_period = Microseconds(1)); + ~Alarm_timeout_scheduler(); + /*********************** ** Timeout_scheduler ** diff --git a/repos/os/src/lib/timeout/timeout.cc b/repos/os/src/lib/timeout/timeout.cc index 9fda814fc..572f78048 100644 --- a/repos/os/src/lib/timeout/timeout.cc +++ b/repos/os/src/lib/timeout/timeout.cc @@ -48,7 +48,7 @@ void Timeout::discard() ** Timeout::Alarm ** ********************/ -bool Timeout::Alarm::on_alarm(unsigned) +bool Timeout::Alarm::_on_alarm(unsigned) { if (handler) { Handler *current = handler; @@ -61,6 +61,22 @@ bool Timeout::Alarm::on_alarm(unsigned) } +Timeout::Alarm::~Alarm() +{ + if (_scheduler) + _scheduler->_alarm_discard(this); +} + + +bool Timeout::Alarm::Raw::is_pending_at(unsigned long time, bool time_period) const +{ + return (time_period == deadline_period && + time >= deadline) || + (time_period != deadline_period && + time < deadline); +} + + /***************************** ** Alarm_timeout_scheduler ** *****************************/ @@ -69,12 +85,12 @@ void Alarm_timeout_scheduler::handle_timeout(Duration duration) { unsigned long const curr_time_us = duration.trunc_to_plain_us().value; - _alarm_scheduler.handle(curr_time_us); + _alarm_handle(curr_time_us); /* sleep time is either until the next deadline or the maximum timout */ unsigned long sleep_time_us; Alarm::Time deadline_us; - if (_alarm_scheduler.next_deadline(&deadline_us)) { + if (_alarm_next_deadline(&deadline_us)) { sleep_time_us = deadline_us - curr_time_us; } else { sleep_time_us = _time_source.max_timeout().value; } @@ -92,8 +108,25 @@ void Alarm_timeout_scheduler::handle_timeout(Duration duration) Alarm_timeout_scheduler::Alarm_timeout_scheduler(Time_source &time_source, Microseconds min_handle_period) : - _time_source(time_source), _alarm_scheduler(min_handle_period.value) -{ } + _time_source(time_source) +{ + Alarm::Time const deadline = _now + min_handle_period.value; + _min_handle_period.period = min_handle_period.value; + _min_handle_period.deadline = deadline; + _min_handle_period.deadline_period = _now > deadline ? + !_now_period : _now_period; +} + + +Alarm_timeout_scheduler::~Alarm_timeout_scheduler() +{ + Lock::Guard lock_guard(_lock); + while (_head) { + Alarm *next = _head->_next; + _head->_alarm_reset(); + _head = next; + } +} void Alarm_timeout_scheduler::_enable() @@ -109,11 +142,11 @@ void Alarm_timeout_scheduler::_schedule_one_shot(Timeout &timeout, _time_source.curr_time().trunc_to_plain_us().value; /* ensure that the schedulers time is up-to-date before adding a timeout */ - _alarm_scheduler.handle(curr_time_us); - _alarm_scheduler.schedule_absolute(&timeout._alarm, + _alarm_handle(curr_time_us); + _alarm_schedule_absolute(&timeout._alarm, curr_time_us + duration.value); - if (_alarm_scheduler.head_timeout(&timeout._alarm)) { + if (_alarm_head_timeout(&timeout._alarm)) { _time_source.schedule_timeout(Microseconds(0), *this); } } @@ -122,9 +155,251 @@ void Alarm_timeout_scheduler::_schedule_periodic(Timeout &timeout, Microseconds duration) { /* ensure that the schedulers time is up-to-date before adding a timeout */ - _alarm_scheduler.handle(_time_source.curr_time().trunc_to_plain_us().value); - _alarm_scheduler.schedule(&timeout._alarm, duration.value); + _alarm_handle(_time_source.curr_time().trunc_to_plain_us().value); + _alarm_schedule(&timeout._alarm, duration.value); - if (_alarm_scheduler.head_timeout(&timeout._alarm)) { + if (_alarm_head_timeout(&timeout._alarm)) { _time_source.schedule_timeout(Microseconds(0), *this); } } + + +void Alarm_timeout_scheduler::_alarm_unsynchronized_enqueue(Alarm *alarm) +{ + if (alarm->_active) { + error("trying to insert the same alarm twice!"); + return; + } + + alarm->_active++; + + /* if alarmlist is empty add first element */ + if (!_head) { + alarm->_next = 0; + _head = alarm; + return; + } + + /* if deadline is smaller than any other deadline, put it on the head */ + if (alarm->_raw.is_pending_at(_head->_raw.deadline, _head->_raw.deadline_period)) { + alarm->_next = _head; + _head = alarm; + return; + } + + /* find list element with a higher deadline */ + Alarm *curr = _head; + while (curr->_next && + curr->_next->_raw.is_pending_at(alarm->_raw.deadline, alarm->_raw.deadline_period)) + { + curr = curr->_next; + } + + /* if end of list is reached, append new element */ + if (curr->_next == 0) { + curr->_next = alarm; + return; + } + + /* insert element in middle of list */ + alarm->_next = curr->_next; + curr->_next = alarm; +} + + +void Alarm_timeout_scheduler::_alarm_unsynchronized_dequeue(Alarm *alarm) +{ + if (!_head) return; + + if (_head == alarm) { + _head = alarm->_next; + alarm->_alarm_reset(); + return; + } + + /* find predecessor in alarm queue */ + Alarm *curr; + for (curr = _head; curr && (curr->_next != alarm); curr = curr->_next); + + /* alarm is not enqueued */ + if (!curr) return; + + /* remove alarm from alarm queue */ + curr->_next = alarm->_next; + alarm->_alarm_reset(); +} + + +Timeout::Alarm *Alarm_timeout_scheduler::_alarm_get_pending_alarm() +{ + Lock::Guard lock_guard(_lock); + + if (!_head || !_head->_raw.is_pending_at(_now, _now_period)) { + return nullptr; } + + /* remove alarm from head of the list */ + Alarm *pending_alarm = _head; + _head = _head->_next; + + /* + * Acquire dispatch lock to defer destruction until the call of '_on_alarm' + * is finished + */ + pending_alarm->_dispatch_lock.lock(); + + /* reset alarm object */ + pending_alarm->_next = nullptr; + pending_alarm->_active--; + + return pending_alarm; +} + + +void Alarm_timeout_scheduler::_alarm_handle(Alarm::Time curr_time) +{ + /* + * Raise the time counter and if it wraps, update also in which + * period of the time counter we are. + */ + if (_now > curr_time) { + _now_period = !_now_period; + } + _now = curr_time; + + if (!_min_handle_period.is_pending_at(_now, _now_period)) { + return; + } + Alarm::Time const deadline = _now + _min_handle_period.period; + _min_handle_period.deadline = deadline; + _min_handle_period.deadline_period = _now > deadline ? + !_now_period : _now_period; + + Alarm *curr; + while ((curr = _alarm_get_pending_alarm())) { + + unsigned long triggered = 1; + + if (curr->_raw.period) { + Alarm::Time deadline = curr->_raw.deadline; + + /* schedule next event */ + if (deadline == 0) + deadline = curr_time; + + triggered += (curr_time - deadline) / curr->_raw.period; + } + + /* do not reschedule if alarm function returns 0 */ + bool reschedule = curr->_on_alarm(triggered); + + if (reschedule) { + + /* + * At this point, the alarm deadline normally is somewhere near + * the current time but If the alarm had no deadline by now, + * initialize it with the current time. + */ + if (curr->_raw.deadline == 0) { + curr->_raw.deadline = _now; + curr->_raw.deadline_period = _now_period; + } + /* + * Raise the deadline value by one period of the alarm and + * if the deadline value wraps thereby, update also in which + * period it is located. + */ + Alarm::Time const deadline = curr->_raw.deadline + + triggered * curr->_raw.period; + if (curr->_raw.deadline > deadline) { + curr->_raw.deadline_period = !curr->_raw.deadline_period; + } + curr->_raw.deadline = deadline; + + /* synchronize enqueue operation */ + Lock::Guard lock_guard(_lock); + _alarm_unsynchronized_enqueue(curr); + } + + /* release alarm, resume concurrent destructor operation */ + curr->_dispatch_lock.unlock(); + } +} + + +void Alarm_timeout_scheduler::_alarm_setup_alarm(Alarm &alarm, Alarm::Time period, Alarm::Time deadline) +{ + /* + * If the alarm is already present in the queue, re-consider its queue + * position because its deadline might have changed. I.e., if an alarm is + * rescheduled with a new timeout before the original timeout triggered. + */ + if (alarm._active) + _alarm_unsynchronized_dequeue(&alarm); + + alarm._alarm_assign(period, deadline, _now > deadline ? !_now_period : _now_period, this); + + _alarm_unsynchronized_enqueue(&alarm); +} + + +void Alarm_timeout_scheduler::_alarm_schedule_absolute(Alarm *alarm, Alarm::Time timeout) +{ + Lock::Guard alarm_list_lock_guard(_lock); + + _alarm_setup_alarm(*alarm, 0, timeout); +} + + +void Alarm_timeout_scheduler::_alarm_schedule(Alarm *alarm, Alarm::Time period) +{ + Lock::Guard alarm_list_lock_guard(_lock); + + /* + * Refuse to schedule a periodic timeout of 0 because it would trigger + * infinitely in the 'handle' function. To account for the case where the + * alarm object was already scheduled, we make sure to remove it from the + * queue. + */ + if (period == 0) { + _alarm_unsynchronized_dequeue(alarm); + return; + } + + /* first deadline is overdue */ + _alarm_setup_alarm(*alarm, period, _now); +} + + +void Alarm_timeout_scheduler::_alarm_discard(Alarm *alarm) +{ + /* + * Make sure that nobody is inside the '_alarm_get_pending_alarm' when + * grabbing the '_dispatch_lock'. This is important when this function + * is called from the 'Alarm' destructor. Without the '_dispatch_lock', + * we could take the lock and proceed with destruction just before + * '_alarm_get_pending_alarm' tries to grab the lock. When the destructor is + * finished, '_alarm_get_pending_alarm' would proceed with operating on a + * dangling pointer. + */ + Lock::Guard alarm_list_lock_guard(_lock); + + if (alarm) { + Lock::Guard alarm_lock_guard(alarm->_dispatch_lock); + _alarm_unsynchronized_dequeue(alarm); + } +} + + +bool Alarm_timeout_scheduler::_alarm_next_deadline(Alarm::Time *deadline) +{ + Lock::Guard alarm_list_lock_guard(_lock); + + if (!_head) return false; + + if (deadline) + *deadline = _head->_raw.deadline; + + if (*deadline < _min_handle_period.deadline) { + *deadline = _min_handle_period.deadline; + } + return true; +}