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:

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:

  1. Architecture-dependent top-half โ€” acknowledges the interrupt, saves the wall clock to the RTC, then calls the architecture-independent layer.
  2. Architecture-independent tick_periodic() โ€” increments jiffies64, updates process/system statistics, runs expired dynamic timers, and calls scheduler_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


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.

udelay and ndelay should 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

Key Takeaways

Practice

  1. On a 32-bit kernel compiled with HZ=1000, approximately how long before the jiffies counter overflows?
  2. Which macro correctly checks whether jiffies value a represents a time after jiffies value b, even across a wraparound boundary?
  3. Your kernel module registers a dynamic timer. In module_exit, which function should you call to remove the timer safely on an SMP system?
  4. In what execution context does a Linux dynamic timer callback run?
  5. A driver needs to wait exactly 5 microseconds for a hardware register to stabilize after a write. Which delay mechanism is most appropriate?
  6. You want to schedule a timer to fire 500 ms from now. Which expression correctly sets the expires field via mod_timer?
  7. Inside a timer callback, how do you retrieve a pointer to the struct my_data that embeds the struct timer_list field named timer? Write the one-line C expression.
  8. Explain why calling del_timer_sync() from a hard interrupt handler can cause a deadlock, and under what condition it is safe to call from interrupt context.
  9. Describe the two-part structure of Linux timer interrupt processing. What does each part do, and why is the split useful?
  10. A process context function shares a data structure with a dynamic timer callback. Which locking primitive should the process-context side use to protect that shared data?