From 2633ff8661c8b710f6f9369c1075910a2c77332c Mon Sep 17 00:00:00 2001 From: Martin Stein <martin.stein@genode-labs.com> Date: Tue, 12 Sep 2017 13:15:01 +0200 Subject: [PATCH] alarm: fix information loss due to int-cast When we have two time values of an unsigned integer type and we create the difference and want to know wether it is positive or negative within the same value we loose at least one half of the value range for casting to signed integers. This was the case in the alarm scheduler when checking wether an alarm already triggered. Even worse, we casted from 'unsigned long' to 'signed int' which caused further loss on at least x86_64. Thus, big timeouts like ~0UL falsely triggered directly. Now, we use an extra boolean value to remember in which period of the time counter we are and to which period of the time counter the deadline of an alarm belongs. This boolean switches its value each time the time counter wraps. This way, we can avoid any casting by checking wether the current time is of the same period as the deadline of the alarm that we inspect. If so, the alarm is pending if "current time >= alarm deadline", otherwise it is pending if "current time < alarm deadline". Ref #2490 --- repos/os/include/os/alarm.h | 18 +++++++++-- repos/os/src/lib/alarm/alarm.cc | 57 ++++++++++++++++++++++++++------- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/repos/os/include/os/alarm.h b/repos/os/include/os/alarm.h index 43c2bcd836..d009c93bf9 100644 --- a/repos/os/include/os/alarm.h +++ b/repos/os/include/os/alarm.h @@ -34,16 +34,27 @@ class Genode::Alarm Lock _dispatch_lock; /* taken during handle method */ Time _deadline; /* next deadline */ + bool _deadline_period; Time _period; /* duration between alarms */ int _active; /* set to one when active */ Alarm *_next; /* next alarm in alarm list */ Alarm_scheduler *_scheduler; /* currently assigned scheduler */ - void _assign(Time period, Time deadline, Alarm_scheduler *scheduler) { - _period = period, _deadline = deadline, _scheduler = scheduler; } + void _assign(Time period, + Time deadline, + bool deadline_period, + Alarm_scheduler *scheduler) + { + _period = period; + _deadline_period = deadline_period; + _deadline = deadline; + _scheduler = scheduler; + } void _reset() { - _assign(0, 0, 0), _active = 0, _next = 0; } + _assign(0, 0, false, 0), _active = 0, _next = 0; } + + bool _is_pending_at(unsigned long time, bool time_period) const; protected: @@ -71,6 +82,7 @@ class Genode::Alarm_scheduler Lock _lock; /* protect alarm list */ Alarm *_head; /* head of alarm list */ Alarm::Time _now; /* recent time (updated by handle method) */ + bool _now_period { false }; /** * Enqueue alarm into alarm queue diff --git a/repos/os/src/lib/alarm/alarm.cc b/repos/os/src/lib/alarm/alarm.cc index e48a87d073..c50cc440c6 100644 --- a/repos/os/src/lib/alarm/alarm.cc +++ b/repos/os/src/lib/alarm/alarm.cc @@ -34,7 +34,7 @@ void Alarm_scheduler::_unsynchronized_enqueue(Alarm *alarm) } /* if deadline is smaller than any other deadline, put it on the head */ - if ((int)alarm->_deadline - (int)_now < (int)_head->_deadline - (int)_now) { + if (alarm->_is_pending_at(_head->_deadline, _head->_deadline_period)) { alarm->_next = _head; _head = alarm; return; @@ -43,8 +43,10 @@ void Alarm_scheduler::_unsynchronized_enqueue(Alarm *alarm) /* find list element with a higher deadline */ Alarm *curr = _head; while (curr->_next && - (int)curr->_next->_deadline - (int)_now < (int)alarm->_deadline - (int)_now) + curr->_next->_is_pending_at(alarm->_deadline, alarm->_deadline_period)) + { curr = curr->_next; + } /* if end of list is reached, append new element */ if (curr->_next == 0) { @@ -81,12 +83,21 @@ void Alarm_scheduler::_unsynchronized_dequeue(Alarm *alarm) } +bool Alarm::_is_pending_at(unsigned long time, bool time_period) const +{ + return (time_period == _deadline_period && + time >= _deadline) || + (time_period != _deadline_period && + time < _deadline); +} + + Alarm *Alarm_scheduler::_get_pending_alarm() { Lock::Guard lock_guard(_lock); - if (!_head || ((int)_head->_deadline - (int)_now >= 0)) - return 0; + if (!_head || !_head->_is_pending_at(_now, _now_period)) { + return nullptr; } /* remove alarm from head of the list */ Alarm *pending_alarm = _head; @@ -99,7 +110,7 @@ Alarm *Alarm_scheduler::_get_pending_alarm() pending_alarm->_dispatch_lock.lock(); /* reset alarm object */ - pending_alarm->_next = 0; + pending_alarm->_next = nullptr; pending_alarm->_active--; return pending_alarm; @@ -108,9 +119,16 @@ Alarm *Alarm_scheduler::_get_pending_alarm() void Alarm_scheduler::handle(Alarm::Time curr_time) { - Alarm *curr; + /* + * 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; + Alarm *curr; while ((curr = _get_pending_alarm())) { unsigned long triggered = 1; @@ -130,11 +148,26 @@ void Alarm_scheduler::handle(Alarm::Time curr_time) if (reschedule) { - /* schedule next event */ - if (curr->_deadline == 0) - curr->_deadline = _now; - - curr->_deadline += triggered * curr->_period; + /* + * 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->_deadline == 0) { + curr->_deadline = _now; + curr->_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->_deadline + + triggered * curr->_period; + if (curr->_deadline > deadline) { + curr->_deadline_period = !curr->_deadline_period; + } + curr->_deadline = deadline; /* synchronize enqueue operation */ Lock::Guard lock_guard(_lock); @@ -157,7 +190,7 @@ void Alarm_scheduler::_setup_alarm(Alarm &alarm, Alarm::Time period, Alarm::Time if (alarm._active) _unsynchronized_dequeue(&alarm); - alarm._assign(period, deadline, this); + alarm._assign(period, deadline, _now > deadline, this); _unsynchronized_enqueue(&alarm); }