* Copyright 2014, Paweł Dziepak, pdziepak@quarnos.org.
* Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include <UserTimer.h>
#include <algorithm>
#include <AutoDeleter.h>
#include <debug.h>
#include <kernel.h>
#include <real_time_clock.h>
#include <syscall_clock_info.h>
#include <team.h>
#include <thread_types.h>
#include <UserEvent.h>
#include <util/AutoLock.h>
#define CPUCLOCK_TEAM 0x00000000
#define CPUCLOCK_THREAD 0x80000000
#define CPUCLOCK_SPECIAL 0xc0000000
#define CPUCLOCK_ID_MASK (~(CPUCLOCK_SPECIAL))
static const bigtime_t kMinPeriodicTimerInterval = 100;
static RealTimeUserTimerList sAbsoluteRealTimeTimers;
static spinlock sAbsoluteRealTimeTimersLock = B_SPINLOCK_INITIALIZER;
static seqlock sUserTimerLock = B_SEQLOCK_INITIALIZER;
namespace {
struct TimerLocker {
Team* team;
Thread* thread;
TimerLocker()
:
team(NULL),
thread(NULL)
{
}
~TimerLocker()
{
Unlock();
}
void Lock(Team* team, Thread* thread)
{
this->team = team;
team->Lock();
this->thread = thread;
if (thread != NULL) {
thread->AcquireReference();
thread->Lock();
}
}
status_t LockAndGetTimer(thread_id threadID, int32 timerID,
UserTimer*& _timer)
{
team = thread_get_current_thread()->team;
team->Lock();
if (threadID >= 0) {
thread = Thread::GetAndLock(threadID);
if (thread == NULL)
return B_BAD_THREAD_ID;
if (thread->team != team)
return B_NOT_ALLOWED;
}
UserTimer* timer = thread != NULL
? thread->UserTimerFor(timerID) : team->UserTimerFor(timerID);
if (timer == NULL)
return B_BAD_VALUE;
_timer = timer;
return B_OK;
}
void Unlock()
{
if (thread != NULL) {
thread->UnlockAndReleaseReference();
thread = NULL;
}
if (team != NULL) {
team->Unlock();
team = NULL;
}
}
};
}
UserTimer::UserTimer()
:
fID(-1),
fEvent(NULL),
fNextTime(0),
fInterval(0),
fOverrunCount(0),
fScheduled(false),
fSkip(0)
{
fTimer.user_data = this;
}
UserTimer::~UserTimer()
{
if (fEvent != NULL)
fEvent->ReleaseReference();
}
bigtime_t& _oldRemainingTime, bigtime_t& _oldInterval)
Cancels the timer, if it is already scheduled, and optionally schedules it
with new parameters.
\param nextTime The time at which the timer should go off the next time. If
\c B_INFINITE_TIMEOUT, the timer will not be scheduled. Whether the
value is interpreted as absolute or relative time, depends on \c flags.
\param interval If <tt> >0 </tt>, the timer will be scheduled to fire
periodically every \a interval microseconds. Otherwise it will fire
only once at \a nextTime. If \a nextTime is \c B_INFINITE_TIMEOUT, it
will fire never in either case.
\param flags Bitwise OR of flags. Currently \c B_ABSOLUTE_TIMEOUT and
\c B_RELATIVE_TIMEOUT are supported, indicating whether \a nextTime is
an absolute or relative time.
\param _oldRemainingTime Return variable that will be set to the
microseconds remaining to the time for which the timer was scheduled
next before the call. If it wasn't scheduled, the variable is set to
\c B_INFINITE_TIMEOUT.
\param _oldInterval Return variable that will be set to the interval in
microseconds the timer was to be scheduled periodically. If the timer
wasn't periodic, the variable is set to \c 0.
*/
*/
void
UserTimer::Cancel()
{
bigtime_t oldNextTime;
bigtime_t oldInterval;
return Schedule(B_INFINITE_TIMEOUT, 0, 0, oldNextTime, oldInterval);
}
uint32& _overrunCount)
Return information on the current timer.
\param _remainingTime Return variable that will be set to the microseconds
remaining to the time for which the timer was scheduled next before the
call. If it wasn't scheduled, the variable is set to
\c B_INFINITE_TIMEOUT.
\param _interval Return variable that will be set to the interval in
microseconds the timer is to be scheduled periodically. If the timer
isn't periodic, the variable is set to \c 0.
\param _overrunCount Return variable that will be set to the number of times
the timer went off, but its event couldn't be delivered, since it's
previous delivery hasn't been handled yet.
*/
int32
UserTimer::HandleTimerHook(struct timer* timer)
{
UserTimer* userTimer = reinterpret_cast<UserTimer*>(timer->user_data);
InterruptsLocker _;
bool locked = false;
while (!locked && atomic_get(&userTimer->fSkip) == 0) {
locked = try_acquire_write_seqlock(&sUserTimerLock);
if (!locked)
cpu_pause();
}
if (locked) {
userTimer->HandleTimer();
release_write_seqlock(&sUserTimerLock);
}
return B_HANDLED_INTERRUPT;
}
void
UserTimer::HandleTimer()
{
if (fEvent != NULL) {
status_t error = fEvent->Fire();
if (error == B_BUSY) {
if (fOverrunCount < MAX_USER_TIMER_OVERRUN_COUNT)
fOverrunCount++;
}
}
fScheduled = false;
}
sanity limits and updating \c fOverrunCount, if necessary.
The caller must not hold \c sUserTimerLock.
*/
void
UserTimer::UpdatePeriodicStartTime()
{
if (fInterval < kMinPeriodicTimerInterval) {
bigtime_t skip = (kMinPeriodicTimerInterval + fInterval - 1) / fInterval;
fNextTime += skip * fInterval;
skip--;
if (skip + fOverrunCount > MAX_USER_TIMER_OVERRUN_COUNT)
fOverrunCount = MAX_USER_TIMER_OVERRUN_COUNT;
else
fOverrunCount += skip;
} else
fNextTime += fInterval;
}
adjusts it and updates \c fOverrunCount.
The caller must not hold \c sUserTimerLock.
\param now The current time.
*/
void
UserTimer::CheckPeriodicOverrun(bigtime_t now)
{
if (fNextTime + fInterval > now)
return;
bigtime_t skip = (now - fNextTime) / fInterval;
fNextTime += skip * fInterval;
if (skip + fOverrunCount > MAX_USER_TIMER_OVERRUN_COUNT)
fOverrunCount = MAX_USER_TIMER_OVERRUN_COUNT;
else
fOverrunCount += skip;
}
void
UserTimer::CancelTimer()
{
ASSERT(fScheduled);
atomic_set(&fSkip, 1);
cancel_timer(&fTimer);
atomic_set(&fSkip, 0);
}
void
SystemTimeUserTimer::Schedule(bigtime_t nextTime, bigtime_t interval,
uint32 flags, bigtime_t& _oldRemainingTime, bigtime_t& _oldInterval)
{
InterruptsWriteSequentialLocker locker(sUserTimerLock);
bigtime_t now = system_time();
if (fScheduled) {
CancelTimer();
_oldRemainingTime = fNextTime - now;
_oldInterval = fInterval;
fScheduled = false;
} else {
_oldRemainingTime = B_INFINITE_TIMEOUT;
_oldInterval = 0;
}
fNextTime = nextTime;
fInterval = interval;
fOverrunCount = 0;
if (nextTime == B_INFINITE_TIMEOUT)
return;
if ((flags & B_RELATIVE_TIMEOUT) != 0)
fNextTime += now;
ScheduleKernelTimer(now, fInterval > 0);
}
void
SystemTimeUserTimer::GetInfo(bigtime_t& _remainingTime, bigtime_t& _interval,
uint32& _overrunCount)
{
uint32 count;
do {
count = acquire_read_seqlock(&sUserTimerLock);
if (fScheduled) {
_remainingTime = fNextTime - system_time();
_interval = fInterval;
} else {
_remainingTime = B_INFINITE_TIMEOUT;
_interval = 0;
}
_overrunCount = fOverrunCount;
} while (!release_read_seqlock(&sUserTimerLock, count));
}
void
SystemTimeUserTimer::HandleTimer()
{
UserTimer::HandleTimer();
if (fInterval > 0) {
UpdatePeriodicStartTime();
ScheduleKernelTimer(system_time(), true);
}
}
The caller must hold \c sUserTimerLock.
\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 (checkPeriodicOverrun)
CheckPeriodicOverrun(now);
uint32 timerFlags = B_ONE_SHOT_ABSOLUTE_TIMER
| B_TIMER_USE_TIMER_STRUCT_TIMES;
fTimer.schedule_time = std::max(fNextTime, (bigtime_t)0);
fTimer.period = 0;
add_timer(&fTimer, &HandleTimerHook, fTimer.schedule_time, timerFlags);
fScheduled = true;
}
void
RealTimeUserTimer::Schedule(bigtime_t nextTime, bigtime_t interval,
uint32 flags, bigtime_t& _oldRemainingTime, bigtime_t& _oldInterval)
{
InterruptsWriteSequentialLocker locker(sUserTimerLock);
bigtime_t now = system_time();
if (fScheduled) {
CancelTimer();
_oldRemainingTime = fNextTime - now;
_oldInterval = fInterval;
if (fAbsolute) {
SpinLocker globalListLocker(sAbsoluteRealTimeTimersLock);
sAbsoluteRealTimeTimers.Remove(this);
}
fScheduled = false;
} else {
_oldRemainingTime = B_INFINITE_TIMEOUT;
_oldInterval = 0;
}
fNextTime = nextTime;
fInterval = interval;
fOverrunCount = 0;
if (nextTime == B_INFINITE_TIMEOUT)
return;
fAbsolute = (flags & B_RELATIVE_TIMEOUT) == 0;
if (fAbsolute) {
fRealTimeOffset = rtc_boot_time();
fNextTime -= fRealTimeOffset;
if (fInterval > 0)
CheckPeriodicOverrun(now);
SpinLocker globalListLocker(sAbsoluteRealTimeTimersLock);
sAbsoluteRealTimeTimers.Insert(this);
} else
fNextTime += now;
ScheduleKernelTimer(now, false);
}
The caller must hold \c sUserTimerLock. Optionally the caller may also
hold \c sAbsoluteRealTimeTimersLock.
*/
void
RealTimeUserTimer::TimeWarped()
{
ASSERT(fScheduled && fAbsolute);
bigtime_t oldRealTimeOffset = fRealTimeOffset;
fRealTimeOffset = rtc_boot_time();
if (fRealTimeOffset == oldRealTimeOffset)
return;
CancelTimer();
fNextTime += oldRealTimeOffset - fRealTimeOffset;
ScheduleKernelTimer(system_time(), fInterval > 0);
}
void
RealTimeUserTimer::HandleTimer()
{
SystemTimeUserTimer::HandleTimer();
if (!fScheduled && fAbsolute) {
SpinLocker globalListLocker(sAbsoluteRealTimeTimersLock);
sAbsoluteRealTimeTimers.Remove(this);
}
}
TeamTimeUserTimer::TeamTimeUserTimer(team_id teamID)
:
fTeamID(teamID),
fTeam(NULL)
{
}
TeamTimeUserTimer::~TeamTimeUserTimer()
{
ASSERT(fTeam == NULL);
}
void
TeamTimeUserTimer::Schedule(bigtime_t nextTime, bigtime_t interval,
uint32 flags, bigtime_t& _oldRemainingTime, bigtime_t& _oldInterval)
{
InterruptsWriteSequentialLocker locker(sUserTimerLock);
SpinLocker timeLocker(fTeam != NULL ? &fTeam->time_lock : NULL);
bool nowValid = fTeam != NULL;
bigtime_t now = nowValid ? fTeam->CPUTime(false) : 0;
if (fTeam != NULL) {
if (fScheduled) {
CancelTimer();
fScheduled = false;
}
_oldRemainingTime = fNextTime - now;
_oldInterval = fInterval;
fTeam->UserTimerDeactivated(this);
fTeam->ReleaseReference();
fTeam = NULL;
} else {
_oldRemainingTime = B_INFINITE_TIMEOUT;
_oldInterval = 0;
}
fNextTime = nextTime;
fInterval = interval;
fOverrunCount = 0;
if (fNextTime == B_INFINITE_TIMEOUT)
return;
Team* newTeam = Team::Get(fTeamID);
if (newTeam == NULL) {
fTeam = NULL;
return;
} else if (fTeam == NULL)
timeLocker.SetTo(newTeam->time_lock, false);
fTeam = newTeam;
fAbsolute = (flags & B_RELATIVE_TIMEOUT) == 0;
if (!fAbsolute) {
if (!nowValid)
now = fTeam->CPUTime(false);
fNextTime += now;
}
fTeam->UserTimerActivated(this);
Update(NULL);
}
void
TeamTimeUserTimer::GetInfo(bigtime_t& _remainingTime, bigtime_t& _interval,
uint32& _overrunCount)
{
uint32 count;
do {
count = acquire_read_seqlock(&sUserTimerLock);
if (fTeam != NULL) {
InterruptsSpinLocker timeLocker(fTeam->time_lock);
_remainingTime = fNextTime - fTeam->CPUTime(false);
_interval = fInterval;
} else {
_remainingTime = B_INFINITE_TIMEOUT;
_interval = 0;
}
_overrunCount = fOverrunCount;
} while (!release_read_seqlock(&sUserTimerLock, count));
}
The caller must hold \c time_lock and \c sUserTimerLock.
*/
void
TeamTimeUserTimer::Deactivate()
{
if (fTeam == NULL)
return;
if (fScheduled) {
CancelTimer();
fScheduled = false;
}
fTeam->UserTimerDeactivated(this);
fTeam->ReleaseReference();
fTeam = NULL;
}
Called whenever threads of the team whose CPU time is referred to by the
timer are scheduled or unscheduled (or leave the team), or when the timer
was just set. Schedules a kernel timer for the remaining time, respectively
cancels it.
The caller must hold \c time_lock and \c sUserTimerLock.
\param unscheduledThread If not \c NULL, this is the thread that is
currently running and which is in the process of being unscheduled.
*/
void
TeamTimeUserTimer::Update(Thread* unscheduledThread, Thread* lockedThread)
{
if (fTeam == NULL)
return;
fRunningThreads = 0;
int32 cpuCount = smp_get_num_cpus();
for (int32 i = 0; i < cpuCount; i++) {
Thread* thread = gCPU[i].running_thread;
if (thread != unscheduledThread && thread->team == fTeam)
fRunningThreads++;
}
_Update(unscheduledThread != NULL, lockedThread);
}
set.
The caller must hold \c time_lock and \c sUserTimerLock.
\param changedBy The value by which the clock has changed.
*/
void
TeamTimeUserTimer::TimeWarped(bigtime_t changedBy)
{
if (fTeam == NULL || changedBy == 0)
return;
if (!fAbsolute)
fNextTime += changedBy;
_Update(false);
}
void
TeamTimeUserTimer::HandleTimer()
{
UserTimer::HandleTimer();
if (fTeam != NULL) {
if (fInterval == 0) {
fTeam->UserTimerDeactivated(this);
fTeam->ReleaseReference();
fTeam = NULL;
} else {
UpdatePeriodicStartTime();
_Update(false);
}
}
}
\c fRunningThreads must be up-to-date.
The caller must hold \c time_lock and \c sUserTimerLock.
\param unscheduling \c true, when the current thread is in the process of
being unscheduled.
*/
void
TeamTimeUserTimer::_Update(bool unscheduling, Thread* lockedThread)
{
if (fScheduled)
CancelTimer();
if (fRunningThreads == 0) {
fScheduled = false;
return;
}
bigtime_t now = fTeam->CPUTime(unscheduling, lockedThread);
if (fInterval > 0)
CheckPeriodicOverrun(now);
if (fNextTime > now) {
fTimer.schedule_time = system_time()
+ (fNextTime - now + fRunningThreads - 1) / fRunningThreads;
if (fTimer.schedule_time < 0)
fTimer.schedule_time = B_INFINITE_TIMEOUT;
} else
fTimer.schedule_time = 0;
fTimer.period = 0;
add_timer(&fTimer, &HandleTimerHook, fTimer.schedule_time,
B_ONE_SHOT_ABSOLUTE_TIMER | B_TIMER_USE_TIMER_STRUCT_TIMES);
fScheduled = true;
}
TeamUserTimeUserTimer::TeamUserTimeUserTimer(team_id teamID)
:
fTeamID(teamID),
fTeam(NULL)
{
}
TeamUserTimeUserTimer::~TeamUserTimeUserTimer()
{
ASSERT(fTeam == NULL);
}
void
TeamUserTimeUserTimer::Schedule(bigtime_t nextTime, bigtime_t interval,
uint32 flags, bigtime_t& _oldRemainingTime, bigtime_t& _oldInterval)
{
InterruptsWriteSequentialLocker locker(sUserTimerLock);
SpinLocker timeLocker(fTeam != NULL ? &fTeam->time_lock : NULL);
bool nowValid = fTeam != NULL;
bigtime_t now = nowValid ? fTeam->UserCPUTime() : 0;
if (fTeam != NULL) {
_oldRemainingTime = fNextTime - now;
_oldInterval = fInterval;
fTeam->UserTimerDeactivated(this);
fTeam->ReleaseReference();
fTeam = NULL;
} else {
_oldRemainingTime = B_INFINITE_TIMEOUT;
_oldInterval = 0;
}
fNextTime = nextTime;
fInterval = interval;
fOverrunCount = 0;
if (fNextTime == B_INFINITE_TIMEOUT)
return;
Team* newTeam = Team::Get(fTeamID);
if (newTeam == NULL) {
fTeam = NULL;
return;
} else if (fTeam == NULL)
timeLocker.SetTo(newTeam->time_lock, false);
fTeam = newTeam;
if ((flags & B_RELATIVE_TIMEOUT) != 0) {
if (!nowValid)
now = fTeam->CPUTime(false);
fNextTime += now;
}
fTeam->UserTimerActivated(this);
Check();
}
void
TeamUserTimeUserTimer::GetInfo(bigtime_t& _remainingTime, bigtime_t& _interval,
uint32& _overrunCount)
{
uint32 count;
do {
count = acquire_read_seqlock(&sUserTimerLock);
if (fTeam != NULL) {
InterruptsSpinLocker timeLocker(fTeam->time_lock);
_remainingTime = fNextTime - fTeam->UserCPUTime();
_interval = fInterval;
} else {
_remainingTime = B_INFINITE_TIMEOUT;
_interval = 0;
}
_overrunCount = fOverrunCount;
} while (!release_read_seqlock(&sUserTimerLock, count));
}
The caller must hold \c time_lock and \c sUserTimerLock.
*/
void
TeamUserTimeUserTimer::Deactivate()
{
if (fTeam == NULL)
return;
fTeam->UserTimerDeactivated(this);
fTeam->ReleaseReference();
fTeam = NULL;
}
The caller must hold \c time_lock and \c sUserTimerLock.
*/
void
TeamUserTimeUserTimer::Check()
{
if (fTeam == NULL)
return;
bigtime_t now = fTeam->UserCPUTime();
if (now < fNextTime)
return;
HandleTimer();
if (fInterval == 0) {
fTeam->UserTimerDeactivated(this);
fTeam->ReleaseReference();
fTeam = NULL;
return;
}
CheckPeriodicOverrun(now);
fNextTime += fInterval;
fScheduled = true;
}
ThreadTimeUserTimer::ThreadTimeUserTimer(thread_id threadID)
:
fThreadID(threadID),
fThread(NULL)
{
}
ThreadTimeUserTimer::~ThreadTimeUserTimer()
{
ASSERT(fThread == NULL);
}
void
ThreadTimeUserTimer::Schedule(bigtime_t nextTime, bigtime_t interval,
uint32 flags, bigtime_t& _oldRemainingTime, bigtime_t& _oldInterval)
{
InterruptsWriteSequentialLocker locker(sUserTimerLock);
SpinLocker timeLocker(fThread != NULL ? &fThread->time_lock : NULL);
bool nowValid = fThread != NULL;
bigtime_t now = nowValid ? fThread->CPUTime(false) : 0;
if (fThread != NULL) {
if (fScheduled) {
CancelTimer();
fScheduled = false;
}
_oldRemainingTime = fNextTime - now;
_oldInterval = fInterval;
fThread->UserTimerDeactivated(this);
fThread->ReleaseReference();
fThread = NULL;
} else {
_oldRemainingTime = B_INFINITE_TIMEOUT;
_oldInterval = 0;
}
fNextTime = nextTime;
fInterval = interval;
fOverrunCount = 0;
if (fNextTime == B_INFINITE_TIMEOUT)
return;
Thread* newThread = Thread::Get(fThreadID);
if (newThread == NULL) {
fThread = NULL;
return;
} else if (fThread == NULL)
timeLocker.SetTo(newThread->time_lock, false);
fThread = newThread;
fAbsolute = (flags & B_RELATIVE_TIMEOUT) == 0;
if (!fAbsolute) {
if (!nowValid)
now = fThread->CPUTime(false);
fNextTime += now;
}
fThread->UserTimerActivated(this);
if (fThread->cpu != NULL)
Start();
}
void
ThreadTimeUserTimer::GetInfo(bigtime_t& _remainingTime, bigtime_t& _interval,
uint32& _overrunCount)
{
uint32 count;
do {
count = acquire_read_seqlock(&sUserTimerLock);
if (fThread != NULL) {
SpinLocker timeLocker(fThread->time_lock);
_remainingTime = fNextTime - fThread->CPUTime(false);
_interval = fInterval;
} else {
_remainingTime = B_INFINITE_TIMEOUT;
_interval = 0;
}
_overrunCount = fOverrunCount;
} while (!release_read_seqlock(&sUserTimerLock, count));
}
The caller must hold \c time_lock and \c sUserTimerLock.
*/
void
ThreadTimeUserTimer::Deactivate()
{
if (fThread == NULL)
return;
if (fScheduled) {
CancelTimer();
fScheduled = false;
}
fThread->UserTimerDeactivated(this);
fThread->ReleaseReference();
fThread = NULL;
}
Called when the thread whose CPU time is referred to by the timer is
scheduled, or, when the timer was just set and the thread is already
running. Schedules a kernel timer for the remaining time.
The caller must hold \c time_lock and \c sUserTimerLock.
*/
void
ThreadTimeUserTimer::Start()
{
if (fThread == NULL)
return;
ASSERT(!fScheduled);
bigtime_t now = fThread->CPUTime(false);
if (fInterval > 0)
CheckPeriodicOverrun(now);
if (fNextTime > now) {
fTimer.schedule_time = system_time() + fNextTime - now;
if (fTimer.schedule_time < 0)
fTimer.schedule_time = B_INFINITE_TIMEOUT;
} else
fTimer.schedule_time = 0;
fTimer.period = 0;
uint32 flags = B_ONE_SHOT_ABSOLUTE_TIMER | B_TIMER_USE_TIMER_STRUCT_TIMES;
add_timer(&fTimer, &HandleTimerHook, fTimer.schedule_time, flags);
fScheduled = true;
}
Called when the thread whose CPU time is referred to by the timer is
unscheduled, or, when the timer is canceled.
The caller must hold \c sUserTimerLock.
*/
void
ThreadTimeUserTimer::Stop()
{
if (fThread == NULL)
return;
ASSERT(fScheduled);
CancelTimer();
fScheduled = false;
}
set.
The caller must hold \c time_lock and \c sUserTimerLock.
\param changedBy The value by which the clock has changed.
*/
void
ThreadTimeUserTimer::TimeWarped(bigtime_t changedBy)
{
if (fThread == NULL || changedBy == 0)
return;
if (!fAbsolute)
fNextTime += changedBy;
if (fScheduled) {
Stop();
Start();
}
}
void
ThreadTimeUserTimer::HandleTimer()
{
UserTimer::HandleTimer();
if (fThread != NULL) {
if (fInterval > 0) {
UpdatePeriodicStartTime();
Start();
} else {
fThread->UserTimerDeactivated(this);
fThread->ReleaseReference();
fThread = NULL;
}
}
}
UserTimerList::UserTimerList()
{
}
UserTimerList::~UserTimerList()
{
ASSERT(fTimers.IsEmpty());
}
\param id The timer's ID
\return The user timer with the given ID or \c NULL, if there is no such
timer.
*/
UserTimer*
UserTimerList::TimerFor(int32 id) const
{
for (TimerList::ConstIterator it = fTimers.GetIterator();
UserTimer* timer = it.Next();) {
if (timer->ID() == id)
return timer;
}
return NULL;
}
\param timer The timer to be added.
*/
void
UserTimerList::AddTimer(UserTimer* timer)
{
int32 id = timer->ID();
if (id < 0) {
id = USER_TIMER_FIRST_USER_DEFINED_ID;
UserTimer* insertAfter = NULL;
for (TimerList::Iterator it = fTimers.GetIterator();
UserTimer* other = it.Next();) {
if (other->ID() > id)
break;
if (other->ID() == id)
id++;
insertAfter = other;
}
timer->SetID(id);
fTimers.InsertAfter(insertAfter, timer);
} else {
UserTimer* insertAfter = NULL;
for (TimerList::Iterator it = fTimers.GetIterator();
UserTimer* other = it.Next();) {
if (other->ID() > id)
break;
if (other->ID() == id) {
panic("UserTimerList::AddTimer(): timer with ID %" B_PRId32
" already exists!", id);
}
insertAfter = other;
}
fTimers.InsertAfter(insertAfter, timer);
}
}
\param userDefinedOnly If \c true, only the user-defined timers are deleted,
otherwise all timers are deleted.
\return The number of user-defined timers that were removed and deleted.
*/
int32
UserTimerList::DeleteTimers(bool userDefinedOnly)
{
int32 userDefinedCount = 0;
for (TimerList::Iterator it = fTimers.GetIterator();
UserTimer* timer = it.Next();) {
if (timer->ID() < USER_TIMER_FIRST_USER_DEFINED_ID) {
if (userDefinedOnly)
continue;
} else
userDefinedCount++;
it.Remove();
timer->Cancel();
delete timer;
}
return userDefinedCount;
}
static int32
create_timer(clockid_t clockID, int32 timerID, Team* team, Thread* thread,
uint32 flags, const struct sigevent& event,
ThreadCreationAttributes* threadAttributes, bool isDefaultEvent)
{
UserTimer* timer;
switch (clockID) {
case CLOCK_MONOTONIC:
timer = new(std::nothrow) SystemTimeUserTimer;
break;
case CLOCK_REALTIME:
timer = new(std::nothrow) RealTimeUserTimer;
break;
case CLOCK_THREAD_CPUTIME_ID:
timer = new(std::nothrow) ThreadTimeUserTimer(
thread_get_current_thread()->id);
break;
case CLOCK_PROCESS_CPUTIME_ID:
if (team == NULL)
return B_BAD_VALUE;
timer = new(std::nothrow) TeamTimeUserTimer(team->id);
break;
case CLOCK_PROCESS_USER_CPUTIME_ID:
if (team == NULL)
return B_BAD_VALUE;
timer = new(std::nothrow) TeamUserTimeUserTimer(team->id);
break;
default:
{
if (clockID <= 0)
return B_BAD_VALUE;
if (clockID == team_get_kernel_team_id())
return B_NOT_ALLOWED;
Team* timedTeam = Team::GetAndLock(clockID);
if (timedTeam == NULL)
return B_BAD_VALUE;
uid_t uid = geteuid();
uid_t teamUID = timedTeam->effective_uid;
timedTeam->UnlockAndReleaseReference();
if (uid != 0 && uid != teamUID)
return B_NOT_ALLOWED;
timer = new(std::nothrow) TeamTimeUserTimer(clockID);
break;
}
}
if (timer == NULL)
return B_NO_MEMORY;
ObjectDeleter<UserTimer> timerDeleter(timer);
if (timerID >= 0)
timer->SetID(timerID);
SignalEvent* signalEvent = NULL;
switch (event.sigev_notify) {
case SIGEV_NONE:
break;
case SIGEV_SIGNAL:
{
if (event.sigev_signo <= 0 || event.sigev_signo > MAX_SIGNAL_NUMBER)
return B_BAD_VALUE;
if (thread != NULL && (flags & USER_TIMER_SIGNAL_THREAD) != 0) {
signalEvent = ThreadSignalEvent::Create(thread,
event.sigev_signo, SI_TIMER, 0, team->id);
} else {
signalEvent = TeamSignalEvent::Create(team, event.sigev_signo,
SI_TIMER, 0);
}
if (signalEvent == NULL)
return B_NO_MEMORY;
timer->SetEvent(signalEvent);
break;
}
case SIGEV_THREAD:
{
if (threadAttributes == NULL)
return B_BAD_VALUE;
CreateThreadEvent* event
= CreateThreadEvent::Create(*threadAttributes);
if (event == NULL)
return B_NO_MEMORY;
timer->SetEvent(event);
break;
}
default:
return B_BAD_VALUE;
}
TimerLocker timerLocker;
timerLocker.Lock(team, thread);
status_t error = thread != NULL
? thread->AddUserTimer(timer) : team->AddUserTimer(timer);
if (error != B_OK)
return error;
if (signalEvent != NULL) {
union sigval signalValue = event.sigev_value;
if (isDefaultEvent)
signalValue.sival_int = timer->ID();
signalEvent->SetUserValue(signalValue);
}
return timerDeleter.Detach()->ID();
}
The caller must hold \c time_lock.
\param thread The thread whose CPU time clock has been set.
\param changedBy The value by which the CPU time clock has changed
(new = old + changedBy).
*/
static void
thread_clock_changed(Thread* thread, bigtime_t changedBy)
{
for (ThreadTimeUserTimerList::ConstIterator it
= thread->CPUTimeUserTimerIterator();
ThreadTimeUserTimer* timer = it.Next();) {
timer->TimeWarped(changedBy);
}
}
The caller must hold \c time_lock.
\param team The team whose CPU time clock has been set.
\param changedBy The value by which the CPU time clock has changed
(new = old + changedBy).
*/
static void
team_clock_changed(Team* team, bigtime_t changedBy)
{
for (TeamTimeUserTimerList::ConstIterator it
= team->CPUTimeUserTimerIterator();
TeamTimeUserTimer* timer = it.Next();) {
timer->TimeWarped(changedBy);
}
}
The thread may not have been added to its team yet, hence the team must be
passed
\param team The thread's (future) team.
\param thread The thread whose pre-defined timers shall be created.
\return \c B_OK, when everything when fine, another error code otherwise.
*/
status_t
user_timer_create_thread_timers(Team* team, Thread* thread)
{
struct sigevent event = {0};
event.sigev_notify = SIGEV_SIGNAL;
event.sigev_signo = SIGALRM;
int32 timerID = create_timer(CLOCK_MONOTONIC, USER_TIMER_REAL_TIME_ID,
team, thread, USER_TIMER_SIGNAL_THREAD, event, NULL, true);
if (timerID < 0)
return timerID;
return B_OK;
}
\param team The team whose pre-defined timers shall be created.
\return \c B_OK, when everything when fine, another error code otherwise.
*/
status_t
user_timer_create_team_timers(Team* team)
{
struct sigevent event = {0};
event.sigev_notify = SIGEV_SIGNAL;
event.sigev_signo = SIGALRM;
int32 timerID = create_timer(CLOCK_MONOTONIC, USER_TIMER_REAL_TIME_ID,
team, NULL, 0, event, NULL, true);
if (timerID < 0)
return timerID;
event.sigev_notify = SIGEV_SIGNAL;
event.sigev_signo = SIGPROF;
timerID = create_timer(CLOCK_PROCESS_CPUTIME_ID,
USER_TIMER_TEAM_TOTAL_TIME_ID, team, NULL, 0, event, NULL, true);
if (timerID < 0)
return timerID;
event.sigev_notify = SIGEV_SIGNAL;
event.sigev_signo = SIGVTALRM;
timerID = create_timer(CLOCK_PROCESS_USER_CPUTIME_ID,
USER_TIMER_TEAM_USER_TIME_ID, team, NULL, 0, event, NULL, true);
if (timerID < 0)
return timerID;
return B_OK;
}
status_t
user_timer_get_clock(clockid_t clockID, bigtime_t& _time)
{
switch (clockID) {
case CLOCK_MONOTONIC:
_time = system_time();
return B_OK;
case CLOCK_REALTIME:
_time = real_time_clock_usecs();
return B_OK;
case CLOCK_THREAD_CPUTIME_ID:
{
Thread* thread = thread_get_current_thread();
InterruptsSpinLocker timeLocker(thread->time_lock);
_time = thread->CPUTime(false);
return B_OK;
}
case CLOCK_PROCESS_USER_CPUTIME_ID:
{
Team* team = thread_get_current_thread()->team;
InterruptsSpinLocker timeLocker(team->time_lock);
_time = team->UserCPUTime();
return B_OK;
}
case CLOCK_PROCESS_CPUTIME_ID:
default:
{
team_id teamID = 0;
if (clockID == CLOCK_PROCESS_CPUTIME_ID) {
teamID = B_CURRENT_TEAM;
} else if ((clockID & CPUCLOCK_SPECIAL) == CPUCLOCK_THREAD) {
thread_id threadID = clockID & CPUCLOCK_ID_MASK;
Thread* thread = Thread::Get(threadID);
if (thread == NULL)
return B_BAD_VALUE;
BReference<Thread> threadReference(thread, true);
if (thread->team == team_get_kernel_team())
return B_NOT_ALLOWED;
InterruptsSpinLocker timeLocker(thread->time_lock);
_time = thread->CPUTime(false);
return B_OK;
} else if ((clockID & CPUCLOCK_SPECIAL) == CPUCLOCK_TEAM) {
teamID = clockID & CPUCLOCK_ID_MASK;
if (teamID == team_get_kernel_team_id())
return B_NOT_ALLOWED;
} else {
return B_BAD_VALUE;
}
Team* team = Team::Get(teamID);
if (team == NULL)
return B_BAD_VALUE;
BReference<Team> teamReference(team, true);
InterruptsSpinLocker timeLocker(team->time_lock);
_time = team->CPUTime(false);
return B_OK;
}
}
}
void
user_timer_real_time_clock_changed()
{
InterruptsWriteSequentialLocker locker(sUserTimerLock);
SpinLocker globalListLocker(sAbsoluteRealTimeTimersLock);
for (RealTimeUserTimerList::Iterator it
= sAbsoluteRealTimeTimers.GetIterator();
RealTimeUserTimer* timer = it.Next();) {
timer->TimeWarped();
}
}
void
user_timer_stop_cpu_timers(Thread* thread, Thread* nextThread)
{
for (ThreadTimeUserTimerList::ConstIterator it
= thread->CPUTimeUserTimerIterator();
ThreadTimeUserTimer* timer = it.Next();) {
timer->Stop();
}
if (nextThread == NULL || nextThread->team != thread->team) {
for (TeamTimeUserTimerList::ConstIterator it
= thread->team->CPUTimeUserTimerIterator();
TeamTimeUserTimer* timer = it.Next();) {
timer->Update(thread, thread);
}
}
}
void
user_timer_continue_cpu_timers(Thread* thread, Thread* previousThread)
{
if (previousThread == NULL || previousThread->team != thread->team) {
for (TeamTimeUserTimerList::ConstIterator it
= thread->team->CPUTimeUserTimerIterator();
TeamTimeUserTimer* timer = it.Next();) {
timer->Update(NULL, thread);
}
}
for (ThreadTimeUserTimerList::ConstIterator it
= thread->CPUTimeUserTimerIterator();
ThreadTimeUserTimer* timer = it.Next();) {
timer->Start();
}
}
void
user_timer_check_team_user_timers(Team* team)
{
for (TeamUserTimeUserTimerList::ConstIterator it
= team->UserTimeUserTimerIterator();
TeamUserTimeUserTimer* timer = it.Next();) {
timer->Check();
}
}
status_t
_user_get_clock(clockid_t clockID, bigtime_t* userTime)
{
bigtime_t time;
status_t error = user_timer_get_clock(clockID, time);
if (error != B_OK)
return error;
if (userTime == NULL || !IS_USER_ADDRESS(userTime)
|| user_memcpy(userTime, &time, sizeof(time)) != B_OK) {
return B_BAD_ADDRESS;
}
return B_OK;
}
status_t
_user_set_clock(clockid_t clockID, bigtime_t time)
{
switch (clockID) {
case CLOCK_MONOTONIC:
return B_BAD_VALUE;
case CLOCK_REALTIME:
if (geteuid() != 0)
return B_NOT_ALLOWED;
set_real_time_clock_usecs(time);
return B_OK;
case CLOCK_THREAD_CPUTIME_ID:
{
Thread* thread = thread_get_current_thread();
InterruptsSpinLocker timeLocker(thread->time_lock);
bigtime_t diff = time - thread->CPUTime(false);
thread->cpu_clock_offset += diff;
thread_clock_changed(thread, diff);
return B_OK;
}
case CLOCK_PROCESS_USER_CPUTIME_ID:
return B_BAD_VALUE;
case CLOCK_PROCESS_CPUTIME_ID:
default:
{
team_id teamID;
if (clockID == CLOCK_PROCESS_CPUTIME_ID) {
teamID = B_CURRENT_TEAM;
} else if ((clockID & CPUCLOCK_THREAD) != 0) {
thread_id threadID = clockID & CPUCLOCK_ID_MASK;
if (threadID < 0)
return B_BAD_VALUE;
Thread* thread = Thread::Get(threadID);
if (thread == NULL)
return B_BAD_VALUE;
BReference<Thread> threadReference(thread, true);
if (thread->team == team_get_kernel_team())
return B_NOT_ALLOWED;
InterruptsSpinLocker timeLocker(thread->time_lock);
bigtime_t diff = time - thread->CPUTime(false);
thread->cpu_clock_offset += diff;
thread_clock_changed(thread, diff);
return B_OK;
} else {
teamID = clockID & CPUCLOCK_ID_MASK;
if (teamID < 0)
return B_BAD_VALUE;
if (teamID == team_get_kernel_team_id())
return B_NOT_ALLOWED;
}
Team* team = Team::Get(teamID);
if (team == NULL)
return B_BAD_VALUE;
BReference<Team> teamReference(team, true);
InterruptsSpinLocker timeLocker(team->time_lock);
bigtime_t diff = time - team->CPUTime(false);
team->cpu_clock_offset += diff;
team_clock_changed(team, diff);
return B_OK;
}
}
return B_OK;
}
status_t
_user_get_cpuclockid(thread_id id, int32 which, clockid_t* userclockID)
{
clockid_t clockID;
if (which != TEAM_ID && which != THREAD_ID)
return B_BAD_VALUE;
if (which == TEAM_ID) {
Team* team = Team::Get(id);
if (team == NULL)
return B_BAD_VALUE;
clockID = id | CPUCLOCK_TEAM;
} else if (which == THREAD_ID) {
Thread* thread = Thread::Get(id);
if (thread == NULL)
return B_BAD_VALUE;
clockID = id | CPUCLOCK_THREAD;
}
if (userclockID != NULL
&& (!IS_USER_ADDRESS(userclockID)
|| user_memcpy(userclockID, &clockID, sizeof(clockID)) != B_OK)) {
return B_BAD_ADDRESS;
}
return B_OK;
}
int32
_user_create_timer(clockid_t clockID, thread_id threadID, uint32 flags,
const struct sigevent* userEvent,
const thread_creation_attributes* userThreadAttributes)
{
struct sigevent event = {0};
if (userEvent != NULL) {
if (!IS_USER_ADDRESS(userEvent)
|| user_memcpy(&event, userEvent, sizeof(event)) != B_OK) {
return B_BAD_ADDRESS;
}
} else {
event.sigev_notify = SIGEV_SIGNAL;
event.sigev_signo = SIGALRM;
}
char nameBuffer[B_OS_NAME_LENGTH];
ThreadCreationAttributes threadAttributes;
if (event.sigev_notify == SIGEV_THREAD) {
status_t error = threadAttributes.InitFromUserAttributes(
userThreadAttributes, nameBuffer);
if (error != B_OK)
return error;
}
Team* team = thread_get_current_thread()->team;
Thread* thread = NULL;
if (threadID >= 0) {
thread = Thread::GetAndLock(threadID);
if (thread == NULL)
return B_BAD_THREAD_ID;
thread->Unlock();
}
BReference<Thread> threadReference(thread, true);
return create_timer(clockID, -1, team, thread, flags, event,
userThreadAttributes != NULL ? &threadAttributes : NULL,
userEvent == NULL);
}
status_t
_user_delete_timer(int32 timerID, thread_id threadID)
{
if (timerID < USER_TIMER_FIRST_USER_DEFINED_ID)
return B_BAD_VALUE;
TimerLocker timerLocker;
UserTimer* timer;
status_t error = timerLocker.LockAndGetTimer(threadID, timerID, timer);
if (error != B_OK)
return error;
timer->Cancel();
if (threadID >= 0)
timerLocker.thread->RemoveUserTimer(timer);
else
timerLocker.team->RemoveUserTimer(timer);
delete timer;
return B_OK;
}
status_t
_user_get_timer(int32 timerID, thread_id threadID,
struct user_timer_info* userInfo)
{
TimerLocker timerLocker;
UserTimer* timer;
status_t error = timerLocker.LockAndGetTimer(threadID, timerID, timer);
if (error != B_OK)
return error;
user_timer_info info;
timer->GetInfo(info.remaining_time, info.interval, info.overrun_count);
if (info.remaining_time <= 0)
info.remaining_time = 1;
timerLocker.Unlock();
if (userInfo != NULL
&& (!IS_USER_ADDRESS(userInfo)
|| user_memcpy(userInfo, &info, sizeof(info)) != B_OK)) {
return B_BAD_ADDRESS;
}
return B_OK;
}
status_t
_user_set_timer(int32 timerID, thread_id threadID, bigtime_t startTime,
bigtime_t interval, uint32 flags, struct user_timer_info* userOldInfo)
{
if (startTime < 0 || interval < 0)
return B_BAD_VALUE;
TimerLocker timerLocker;
UserTimer* timer;
status_t error = timerLocker.LockAndGetTimer(threadID, timerID, timer);
if (error != B_OK)
return error;
user_timer_info oldInfo;
timer->Schedule(startTime, interval, flags, oldInfo.remaining_time,
oldInfo.interval);
if (oldInfo.remaining_time <= 0)
oldInfo.remaining_time = 1;
timerLocker.Unlock();
if (userOldInfo != NULL
&& (!IS_USER_ADDRESS(userOldInfo)
|| user_memcpy(userOldInfo, &oldInfo, sizeof(oldInfo)) != B_OK)) {
return B_BAD_ADDRESS;
}
return B_OK;
}