Author: bonefish Date: 2011-06-07 21:24:17 +0200 (Tue, 07 Jun 2011) New Revision: 42020 Changeset: https://dev.haiku-os.org/changeset/42020 Modified: haiku/branches/developer/bonefish/signals/headers/private/kernel/UserTimer.h haiku/branches/developer/bonefish/signals/src/system/kernel/UserTimer.cpp Log: * SystemTimeUserTimer, RealTimeUserTimer: Moved the kernel timer scheduling to new method ScheduleKernelTimer(). * Changed all UserTimer implementations to not use periodic kernel timers anymore. Instead one-shot kernel timers are used and rescheduled after the event was fired. This is necessary to maintain a precise overrun count. * Introduced a minimum interval by which the kernel timer start time is advanced for periodic timers. For user timers with a shorter interval we adjust the overrun count respectively. Modified: haiku/branches/developer/bonefish/signals/headers/private/kernel/UserTimer.h =================================================================== --- haiku/branches/developer/bonefish/signals/headers/private/kernel/UserTimer.h 2011-06-07 18:17:08 UTC (rev 42019) +++ haiku/branches/developer/bonefish/signals/headers/private/kernel/UserTimer.h 2011-06-07 19:24:17 UTC (rev 42020) @@ -51,6 +51,8 @@ static int32 HandleTimerHook(struct timer* timer); virtual void HandleTimer(); + inline void UpdatePeriodicStartTime(bigtime_t& startTime, + bigtime_t interval); inline void CheckPeriodicOverrun(bigtime_t now, bigtime_t& startTime, bigtime_t interval); @@ -70,6 +72,12 @@ virtual void GetInfo(bigtime_t& _remainingTime, bigtime_t& _interval, uint32& _overrunCount); + +protected: + virtual void HandleTimer(); + + void ScheduleKernelTimer(bigtime_t now, + bool checkPeriodicOverrun); }; Modified: haiku/branches/developer/bonefish/signals/src/system/kernel/UserTimer.cpp =================================================================== --- haiku/branches/developer/bonefish/signals/src/system/kernel/UserTimer.cpp 2011-06-07 18:17:08 UTC (rev 42019) +++ haiku/branches/developer/bonefish/signals/src/system/kernel/UserTimer.cpp 2011-06-07 19:24:17 UTC (rev 42020) @@ -17,6 +17,13 @@ #include <util/AutoLock.h> +// Minimum interval length in microseconds for a periodic timer. This is not a +// restriction on the user timer interval length itself, but the minimum time +// span by which we advance the start time for kernel timers. A shorted user +// timer interval will result in the overrun count to be increased every time +// the kernel timer is rescheduled. +static const bigtime_t kMinPeriodicTimerInterval = 100; + static RealTimeUserTimerList sAbsoluteRealTimeTimers; static spinlock sAbsoluteRealTimeTimersLock = B_SPINLOCK_INITIALIZER; @@ -198,12 +205,35 @@ fOverrunCount = 0; } - // If it's not a periodic timer, it isn't scheduled anymore. - if (fTimer.period == 0) - fScheduled = false; + // Since we don't use periodic kernel timers, it isn't scheduled anymore. + // If the timer is periodic, the derived class' version will schedule it + // again. + fScheduled = false; } +/*! Updates the start time for a periodic timer after it expired, enforcing + sanity limits and updating \c fOverrunCount, if necessary. + + \param startTime The start time of the timer. Will be adjusted. + \param interval The timer interval. Must be <tt> > 0 <\tt>. +*/ +void +UserTimer::UpdatePeriodicStartTime(bigtime_t& startTime, bigtime_t interval) +{ + if (interval < kMinPeriodicTimerInterval) { + bigtime_t skip = (kMinPeriodicTimerInterval + interval - 1) / interval; + startTime += skip * interval; + + if (skip + fOverrunCount > MAX_USER_TIMER_OVERRUN_COUNT) + fOverrunCount = MAX_USER_TIMER_OVERRUN_COUNT; + else + fOverrunCount += skip; + } else + startTime += interval; +} + + /*! Checks whether the given time start time lies too much in the past and, if so, adjusts it and updates \c fOverrunCount. @@ -249,6 +279,8 @@ _oldRemainingTime = fTimer.schedule_time - now; _oldInterval = fTimer.period; + + fScheduled = false; } else { _oldRemainingTime = B_INFINITE_TIMEOUT; _oldInterval = 0; @@ -258,27 +290,13 @@ fTimer.schedule_time = nextTime; fTimer.period = interval; - if (nextTime != B_INFINITE_TIMEOUT) { - if ((flags & B_RELATIVE_TIMEOUT) != 0) - fTimer.schedule_time += now; + if (nextTime == B_INFINITE_TIMEOUT) + return; - // If periodic, check whether the start time is too far in the past. - if (fTimer.period > 0) - CheckPeriodicOverrun(now, fTimer.schedule_time, fTimer.period); + if ((flags & B_RELATIVE_TIMEOUT) != 0) + fTimer.schedule_time += now; - uint32 timerFlags = (interval > 0 - ? B_PERIODIC_TIMER : B_ONE_SHOT_ABSOLUTE_TIMER) - | B_TIMER_USE_TIMER_STRUCT_TIMES | B_TIMER_ACQUIRE_SCHEDULER_LOCK; - // We use B_TIMER_ACQUIRE_SCHEDULER_LOCK to avoid race conditions - // between setting/canceling the timer and the event handler. - - add_timer(&fTimer, &HandleTimerHook, interval, timerFlags); - - fScheduled = true; - } else { - // mark the timer canceled - fScheduled = false; - } + ScheduleKernelTimer(now, fTimer.period > 0); } @@ -300,6 +318,44 @@ } +void +SystemTimeUserTimer::HandleTimer() +{ + UserTimer::HandleTimer(); + + // if periodic, reschedule the kernel timer + if (fTimer.period > 0) { + UpdatePeriodicStartTime(fTimer.schedule_time, fTimer.period); + ScheduleKernelTimer(system_time(), true); + } +} + + +/*! Schedules the kernel timer. + + \param now The current system time to be used. + \param checkPeriodicOverrun If \c true, calls CheckPeriodicOverrun() first, + i.e. the start time will be adjusted to not lie too much in the past. +*/ +void +SystemTimeUserTimer::ScheduleKernelTimer(bigtime_t now, + bool checkPeriodicOverrun) +{ + // If periodic, check whether the start time is too far in the past. + if (checkPeriodicOverrun) + CheckPeriodicOverrun(now, fTimer.schedule_time, fTimer.period); + + uint32 timerFlags = B_ONE_SHOT_ABSOLUTE_TIMER + | B_TIMER_USE_TIMER_STRUCT_TIMES | B_TIMER_ACQUIRE_SCHEDULER_LOCK; + // We use B_TIMER_ACQUIRE_SCHEDULER_LOCK to avoid race conditions + // between setting/canceling the timer and the event handler. + + add_timer(&fTimer, &HandleTimerHook, fTimer.schedule_time, timerFlags); + + fScheduled = true; +} + + // #pragma mark - RealTimeUserTimer @@ -353,15 +409,7 @@ } else fTimer.schedule_time += now; - uint32 timerFlags = (interval > 0 - ? B_PERIODIC_TIMER : B_ONE_SHOT_ABSOLUTE_TIMER) - | B_TIMER_USE_TIMER_STRUCT_TIMES | B_TIMER_ACQUIRE_SCHEDULER_LOCK; - // We use B_TIMER_ACQUIRE_SCHEDULER_LOCK to avoid race conditions - // between setting/canceling the timer and the event handler. - - add_timer(&fTimer, &HandleTimerHook, interval, timerFlags); - - fScheduled = true; + ScheduleKernelTimer(now, false); } @@ -386,26 +434,14 @@ fTimer.schedule_time += oldRealTimeOffset - fRealTimeOffset; - // If periodic, check whether we've moved too far into the past. - if (fTimer.period > 0) { - CheckPeriodicOverrun(system_time(), fTimer.schedule_time, - fTimer.period); - } - - uint32 timerFlags = (fTimer.period > 0 - ? B_PERIODIC_TIMER : B_ONE_SHOT_ABSOLUTE_TIMER) - | B_TIMER_USE_TIMER_STRUCT_TIMES | B_TIMER_ACQUIRE_SCHEDULER_LOCK; - // We use B_TIMER_ACQUIRE_SCHEDULER_LOCK to avoid race conditions - // between setting/canceling the timer and the event handler. - - add_timer(&fTimer, &HandleTimerHook, fTimer.period, timerFlags); + ScheduleKernelTimer(system_time(), fTimer.period > 0); } void RealTimeUserTimer::HandleTimer() { - UserTimer::HandleTimer(); + SystemTimeUserTimer::HandleTimer(); // remove from global list, if no longer scheduled if (!fScheduled && fAbsolute) { @@ -597,7 +633,7 @@ fTeam->ReleaseReference(); fTeam = NULL; } else { - fNextTime += fInterval; + UpdatePeriodicStartTime(fNextTime, fInterval); _Update(false); } } @@ -658,9 +694,6 @@ fTeamID(teamID), fTeam(NULL) { - fTimer.period = 0; - // initialize, since UserTimer::HandleTimer() reads it -- doesn't matter - // though } @@ -785,6 +818,7 @@ // (CheckPeriodicOverrun() only makes it > now - fInterval). CheckPeriodicOverrun(now, fNextTime, fInterval); fNextTime += fInterval; + fScheduled = true; } @@ -930,8 +964,7 @@ fTimer.schedule_time = system_time() + fNextTime - now; fTimer.period = fInterval; - uint32 flags = (fInterval > 0 - ? B_PERIODIC_TIMER : B_ONE_SHOT_ABSOLUTE_TIMER) + uint32 flags = B_ONE_SHOT_ABSOLUTE_TIMER | B_TIMER_USE_TIMER_STRUCT_TIMES | B_TIMER_ACQUIRE_SCHEDULER_LOCK; // We use B_TIMER_ACQUIRE_SCHEDULER_LOCK to avoid race conditions // between setting/canceling the timer and the event handler. @@ -998,11 +1031,17 @@ { UserTimer::HandleTimer(); - // If the timer is not periodic, it is no longer active. - if (fInterval == 0 && fThread != NULL) { - fThread->UserTimerDeactivated(this); - fThread->ReleaseReference(); - fThread = NULL; + if (fThread != NULL) { + // If the timer is periodic, reschedule the kernel timer. Otherwise it + // is no longer active. + if (fInterval > 0) { + UpdatePeriodicStartTime(fNextTime, fInterval); + Start(); + } else { + fThread->UserTimerDeactivated(this); + fThread->ReleaseReference(); + fThread = NULL; + } } }