Timer and Time Management
Why This Matters
Nearly every kernel subsystem depends on time. The CFS scheduler needs to track CPU usage per process, device drivers must wait for hardware to become ready, and the kernel must maintain an accurate wall-clock for user-space programs. Understanding how the kernel represents time โ and how you can schedule or delay code from a kernel module โ is essential for writing correct, efficient kernel software.
The Kernel's Notion of Time
The kernel uses a periodic hardware interrupt (the timer interrupt or tick) as its basic heartbeat. On each tick the kernel:
- Increments the global time counter
- Updates system uptime and wall-clock time
- Re-balances run queues and records statistics
- Fires any expired dynamic timers
Tick Rate and HZ
The tick rate is configured at compile time via CONFIG_HZ (exposed as the HZ macro in <asm-generic/param.h>). Common values are 100, 250, or 1000 Hz depending on the architecture and use case.
HZ value |
Tick period | Overflow of 32-bit jiffies |
|---|---|---|
| 100 | 10 ms | ~497 days |
| 1000 | 1 ms | ~50 days |
Trade-off: A higher HZ gives finer timer resolution and more accurate process preemption, but generates more timer interrupts and therefore more overhead (though on modern hardware the overhead is small).
Tickless Kernels (NO_HZ)
The kernel can be compiled with NO_HZ options to avoid firing timer interrupts when there is nothing scheduled. This reduces overhead and allows CPUs to spend more time in low-power idle states โ important for laptops and VMs.
jiffies: The Kernel's Clock Counter
jiffies is an unsigned long global that holds the number of timer ticks since boot.
// Convert between jiffies and real time
unsigned long j = seconds * HZ; // seconds โ jiffies
unsigned long s = jiffies / HZ; // jiffies โ seconds
On 64-bit kernels jiffies is 64 bits and overflow is not a practical concern. On 32-bit kernels it wraps around (e.g., in ~50 days at HZ=1000). The kernel provides time-comparison macros that handle wraparound correctly:
time_after(a, b) // true if a is after b (handles wraparound)
time_before(a, b)
time_after_eq(a, b)
time_before_eq(a, b)
Always use these macros when comparing jiffies values โ never use raw > or <.
User-Space Clock Ticks
User space uses USER_HZ (always 100 on x86) regardless of the kernel's HZ. The kernel provides conversion helpers:
clock_t jiffies_to_clock_t(unsigned long x);
clock_t jiffies_64_to_clock_t(u64 x);
Hardware Clocks
| Clock | Purpose | Notes |
|---|---|---|
| RTC (Real-Time Clock) | Persistent wall-clock time | Battery-backed; keeps time when powered off |
| System timer (Local APIC / PIT) | Generates periodic or one-shot interrupts | APIC timer is the primary timer since kernel 2.6.17 |
| TSC (Time Stamp Counter) | High-resolution cycle counter | Read with rdtsc/rdtscp; invariant to frequency scaling on modern x86 |
Timer Interrupt Processing
When the timer interrupt fires, two layers of code run:
- Architecture-dependent top-half โ acknowledges the interrupt, saves the wall clock to the RTC, then calls the architecture-independent layer.
- Architecture-independent
tick_periodic()โ incrementsjiffies64, updates process/system statistics, runs expired dynamic timers, and callsscheduler_tick().
Dynamic (Kernel) Timers
A dynamic timer lets you schedule a callback to run at a future jiffies value. Timers run in softirq context (not process context).
API
#include <linux/timer.h>
struct timer_list my_timer;
// Initialize: associate timer with callback
timer_setup(&my_timer, my_callback, 0);
// Schedule (or reschedule): fire at absolute jiffies value
mod_timer(&my_timer, jiffies + msecs_to_jiffies(2000)); // 2 s from now
// Cancel โ may race on SMP if handler is already running
del_timer(&my_timer); // returns 1 if timer was active
// Cancel safely โ waits for any running handler to finish
del_timer_sync(&my_timer); // use this in module exit
Inside the callback, retrieve the parent struct with from_timer() (analogous to container_of()):
static void my_callback(struct timer_list *t)
{
struct my_data *d = from_timer(d, t, timer_field);
// use d ...
}
Self-Rescheduling Timer Pattern
void my_callback(struct timer_list *t) {
struct my_data *d = from_timer(d, t, timer);
d->counter++;
// reschedule for 1 second later
mod_timer(&d->timer, jiffies + msecs_to_jiffies(1000));
}
Race Conditions and Best Practices
- Always use
del_timer_sync()inmodule_exitโdel_timer()may return before the handler finishes on another CPU, leaving a dangling reference. - Never modify
timer->expiresdirectly โ always callmod_timer(). - Protect shared data with appropriate locks; the callback runs in softirq context, so use
spin_lock_bh()/spin_unlock_bh()from process context when accessing shared state. del_timer_sync()must not be called from interrupt context unless the timer was declaredTIMER_IRQSAFEโ doing so risks deadlock if the interrupt fires while the handler holds a lock.
Delaying Execution Without Timers
Sometimes a driver needs to wait for hardware without sleeping until the next timer tick. Three techniques exist:
1. Busy Looping
Spin until enough jiffies have elapsed. Wastes CPU; only appropriate for very short, bounded waits.
unsigned long timeout = jiffies + HZ; // 1 second
while (time_before(jiffies, timeout))
cpu_relax();
2. Sub-Tick Delays: mdelay / udelay / ndelay
For delays shorter than one tick (e.g., 1 ยตs for a register write):
udelay(10); // busy-loop for ~10 ยตs
mdelay(5); // busy-loop for ~5 ms
These are calibrated at boot using BogoMIPS (bogomips visible in /proc/cpuinfo) โ a measure of how many no-op loop iterations fit in a jiffy.
udelayandndelayshould not be used for delays longer than ~1 ms due to integer overflow in the busy-loop count.
3. schedule_timeout()
Put the current task to sleep for at least n ticks:
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(HZ / 2); // sleep ~500 ms
- Must set task state to
TASK_INTERRUPTIBLE(can be woken by signal) orTASK_UNINTERRUPTIBLEbefore calling. - Must be called from process context without holding any spinlock.
- Can be combined with wait queues: call
schedule_timeout()instead ofschedule()to add a deadline to a wait.
Key Takeaways
- The kernel's sense of time is driven by the periodic timer interrupt; its resolution is
1/HZseconds. jiffiescounts ticks since boot; always usetime_after()/time_before()to compare, never raw arithmetic.- Dynamic timers (
timer_setup/mod_timer/del_timer_sync) schedule callbacks in softirq context;del_timer_sync()is mandatory in module exit. from_timer()retrieves the enclosing struct inside a timer callback.- For sub-tick busy waits use
udelay()/mdelay(); for sleeping waits in process context useschedule_timeout(). - Tickless (
NO_HZ) kernels skip timer interrupts when idle, saving power and reducing overhead.