Timers are used to setup one-shot or repeating callbacks in units of milliseconds.
These callbacks happen from interrupt context, so the available APIs are limited to things like signaling an event or unblocking a thread on a wait queue.
It is always safe to reprogram or cancel a timer from its own callback.
Timekeeping: lk_time_t This unsigned type is used by kernel functions involving timers, timeouts, etc, and is in units of milliseconds. The maximum value (defined as INFINITE_TIME) is used to specify a timeout that will never expire. The value 0 passed as a timeout parameter always means ``return immediately if you would have to wait.’’
ERR_TIMED_OUT is the status returned by a function taking a timeout value, if the timeout expires before the requested action can be accomplished.
Safe Actions From IRQ handlers and Timer Callbacks: (Timer callbacks happen from IRQ context)
A number of these functions take a ‘reschedule’ parameter, which must be false when called from IRQ context. If your interrupt handler takes action which will wake a thread and you want to ensure it wakes now (provided priority is high enough) rather than when the next quantum expires, return INT_RESCHEDULE instead of INT_NO_RESCHEDULE. This will cause the kernel to invoke the scheduler before returning to thread context.
The follow actions are IRQ-safe:
Signal an event with event_signal().
Reprogram a timer with timer_set_oneshot(), timer_set_periodic(), or timer_cancel().
Timer reprogramming is safe even from within that timer’s callback.
Using spinlocks.
Wake threads on wait queues with wake_queue_wake_one() or wake_queue_wake_all(), provided you hold the thread lock while doing so (useful for building new synchronization primitives).
/* called at interrupt time to process any pending timers */ static enum handler_return timer_tick(void *arg, lk_time_t now) { timer_t *timer; enum handler_return ret = INT_NO_RESCHEDULE;
DEBUG_ASSERT(arch_ints_disabled());
THREAD_STATS_INC(timer_ints); // KEVLOG_TIMER_TICK(); // enable only if necessary
uint cpu = arch_curr_cpu_num();
LTRACEF("cpu %u now %u, sp %p\n", cpu, now, __GET_FRAME());
spin_lock(&timer_lock);
for (;;) { /* see if there's an event to process */ timer = list_peek_head_type(&timers[cpu].timer_queue, timer_t, node); if (likely(timer == 0)) break; LTRACEF("next item on timer queue %p at %u now %u (%p, arg %p)\n", timer, timer->scheduled_time, now, timer->callback, timer->arg); if (likely(TIME_LT(now, timer->scheduled_time))) break;
/* process it */ LTRACEF("timer %p\n", timer); DEBUG_ASSERT(timer && timer->magic == TIMER_MAGIC); list_delete(&timer->node);
/* we pulled it off the list, release the list lock to handle it */ spin_unlock(&timer_lock);
/* it may have been requeued or periodic, grab the lock so we can safely inspect it */ spin_lock(&timer_lock);
/* if it was a periodic timer and it hasn't been requeued * by the callback put it back in the list */ if (periodic && !list_in_list(&timer->node) && timer->periodic_time > 0) { LTRACEF("periodic timer, period %u\n", timer->periodic_time); timer->scheduled_time = now + timer->periodic_time; insert_timer_in_queue(cpu, timer); } }
#if PLATFORM_HAS_DYNAMIC_TIMER /* reset the timer to the next event */ timer = list_peek_head_type(&timers[cpu].timer_queue, timer_t, node); if (timer) { /* has to be the case or it would have fired already */ DEBUG_ASSERT(TIME_GT(timer->scheduled_time, now));
lk_time_t delay = timer->scheduled_time - now;
LTRACEF("setting new timer for %u msecs for event %p\n", (uint)delay, timer); platform_set_oneshot_timer(timer_tick, NULL, delay); }
/* we're done manipulating the timer queue */ spin_unlock(&timer_lock); #else /* release the timer lock before calling the tick handler */ spin_unlock(&timer_lock);
/* let the scheduler have a shot to do quantum expiration, etc */ /* in case of dynamic timer, the scheduler will set up a periodic timer */ if (thread_timer_tick() == INT_RESCHEDULE) ret = INT_RESCHEDULE; #endif
return ret; }
timer_tick 是个死循环,会从变量timer_queue 中是否有到期的时间:
1 2
/* see if there's an event to process */ timer = list_peek_head_type(&timers[cpu].timer_queue, timer_t, node);
如果timer为空或者还未到期,则跳出循环:
1 2 3 4 5
if (likely(timer == 0)) break; LTRACEF("next item on timer queue %p at %u now %u (%p, arg %p)\n", timer, timer->scheduled_time, now, timer->callback, timer->arg); if (likely(TIME_LT(now, timer->scheduled_time))) break;
如果有的话,就从timer_queue 中删除:
1
list_delete(&timer->node);
并调用这个timer到期时间需要执行的callback
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* we pulled it off the list, release the list lock to handle it */ spin_unlock(&timer_lock);
/* it may have been requeued or periodic, grab the lock so we can safely inspect it */ spin_lock(&timer_lock);
如果这个timer是periodic类型的额,那么再次插入到timer_queue中:
1 2 3 4 5 6 7 8
/* if it was a periodic timer and it hasn't been requeued - by the callback put it back in the list */ if (periodic && !list_in_list(&timer->node) && timer->periodic_time > 0) { LTRACEF("periodic timer, period %u\n", timer->periodic_time); timer->scheduled_time = now + timer->periodic_time; insert_timer_in_queue(cpu, timer); }