This Linux kernel timers tutorial is the second lecture in EmbeddedPathashala’s free Linux kernel development course. In the previous lecture we saw how to insert short delays with udelay() and msleep(). But what happens when you need something to run repeatedly, every second, in the background, without your driver code sitting there and blocking? That is exactly the job of a kernel timer, and this Linux kernel timers tutorial walks through the modern, kernel 6.x-compatible API from scratch.
What You Will Learn
- What a kernel timer is and why it is different from a delay
- The modern
timer_setup()/struct timer_listAPI used in kernel 6.x - How to arm, rearm, and safely tear down a kernel timer
- Why timer callbacks run in softirq context and what that means for your code
- A complete, original one-second periodic timer example
Prerequisites
Read the previous lecture on Linux kernel delay functions first, and make sure you’re comfortable building and loading a basic kernel module. This Linux kernel timers tutorial assumes both.
What a Kernel Timer Actually Is
A kernel timer lets you say “run this function once, after N jiffies have passed” without blocking the caller. Internally, the kernel maintains the timer in a per-CPU wheel data structure and fires your callback from softirq context once the deadline is reached. Because it never blocks the thread that armed it, this is the standard building block for any periodic or one-shot background activity in a driver — watchdog checks, polling a status register, debouncing a switch, or timing out a stalled operation.
The Modern Kernel Timer API
Older kernel code you may find online (including in books written before kernel 4.15) uses setup_timer() with a raw unsigned long data field. That interface was removed years ago. Kernel 6.x only supports the timer_setup() style, where your callback receives a pointer to the struct timer_list itself, and you use the container_of-based from_timer() helper to reach your own driver structure.
| Function | Purpose |
|---|---|
timer_setup(timer, fn, flags) | Initialize a timer and bind its callback |
mod_timer(timer, expires) | Arm or rearm the timer for a given jiffies value |
del_timer_sync(timer) | Stop the timer and block until any running callback finishes |
timer_pending(timer) | Check whether the timer is currently armed |
A Complete One-Second Periodic Timer Example
Here is an original, self-contained example that logs a heartbeat message once every second using a kernel timer:
#include <linux/module.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
struct ep_timer_ctx {
struct timer_list timer;
unsigned int ticks;
};
static struct ep_timer_ctx ep_ctx;
static void ep_timer_callback(struct timer_list *t)
{
struct ep_timer_ctx *ctx = from_timer(ctx, t, timer);
ctx->ticks++;
pr_info("ep_timer: heartbeat #%u\n", ctx->ticks);
/* Rearm for another second to make it periodic */
mod_timer(&ctx->timer, jiffies + msecs_to_jiffies(1000));
}
static int __init ep_timer_init(void)
{
timer_setup(&ep_ctx.timer, ep_timer_callback, 0);
mod_timer(&ep_ctx.timer, jiffies + msecs_to_jiffies(1000));
pr_info("ep_timer: module loaded\n");
return 0;
}
static void __exit ep_timer_exit(void)
{
del_timer_sync(&ep_ctx.timer);
pr_info("ep_timer: module unloaded\n");
}
module_init(ep_timer_init);
module_exit(ep_timer_exit);
MODULE_LICENSE("GPL");
Notice the pattern: the callback rearms itself with mod_timer() to become periodic. There is no separate “periodic timer” API in the kernel — every recurring timer is really a one-shot timer that keeps rearming itself.
Timer Callback Context Matters
Kernel timer callbacks run in softirq context, which is a form of atomic context. That means inside a timer callback you must never sleep, never call blocking allocation with GFP_KERNEL, and never call the blocking delay functions covered in the previous lecture of this Linux kernel timers tutorial series. If your callback needs to do something that can sleep, schedule a workqueue item from inside it instead — which is exactly the topic of a later lecture.
Common Mistakes
- Forgetting to call
del_timer_sync()in the module’s exit path, which can leave a stale timer pointing at freed memory. - Calling
del_timer()instead ofdel_timer_sync()during unload, which does not wait for an in-flight callback to finish, risking a race. - Sleeping or allocating with
GFP_KERNELinside a timer callback. - Assuming timer expiry is exact — jiffies-based timers have granularity tied to
HZand are not hard real-time.
Best Practices
- Always pair
timer_setup()with a guaranteeddel_timer_sync()on every exit path, including error paths. - Keep timer callbacks short; hand off real work to a workqueue if it might sleep or take a while.
- Use
msecs_to_jiffies()rather than hand-computing jiffies fromHZ, so your code stays portable across kernel configurations.
Performance Considerations
Kernel timers are lightweight, but arming thousands of independent timers across many driver instances adds real overhead to the timer wheel. If you need many similar periodic tasks, consider whether a single shared timer or a workqueue with delayed work would scale better.
Key Takeaways
- Kernel timers schedule a callback to run later without blocking the caller.
- Kernel 6.x uses
timer_setup()andfrom_timer(), not the olddata-based API. - Timer callbacks run in atomic (softirq) context — no sleeping allowed.
- Always tear down timers with
del_timer_sync()before your module exits.
Conclusion
This Linux kernel timers tutorial covered everything you need to safely schedule background work with jiffies-based timers on a modern kernel. Next in this free Linux kernel development course, we move on to kernel threads — for when you need a full background loop rather than a short periodic callback.
Frequently Asked Questions
timer_setup() replaced setup_timer(); the callback now receives a struct timer_list * pointer instead of an unsigned long data value.
Call mod_timer() again from inside the callback itself to rearm it for the next interval — there is no built-in “repeating timer” type.
No, timer callbacks run in softirq (atomic) context, so sleeping or GFP_KERNEL allocation is not allowed. Offload such work to a workqueue.
del_timer_sync() blocks until any currently running callback finishes before returning, making it safe to use during module unload; plain del_timer() does not wait.
Their resolution is tied to the kernel’s jiffies/HZ configuration, so they are suitable for general-purpose background scheduling but not for hard real-time deadlines.
Yes, it is part of EmbeddedPathashala’s free Linux kernel development course, alongside a free Linux device drivers course and a free embedded systems course.
Next up: Linux Kernel Threads — running background loops with kthread.
Next Lecture →
2 Comments