This Linux kernel timers tutorial is part of EmbeddedPathashala’s free Linux kernel development course, and it walks you through everything a device driver author needs to know about setting up, arming, and safely tearing down a kernel timer. Kernel timers let a driver schedule work to happen later without blocking the current task, and understanding them properly is one of the core skills expected in any free Linux device drivers course. By the end of this lecture you will be able to write a working timer-based kernel module and understand exactly why the modern kernel API looks the way it does.
What You Will Learn
- What a kernel timer is and why drivers need one
- Why a timer callback runs in softirq (atomic) context, and what that means for your code
- How to initialize a timer with
timer_setup()and thestruct timer_listfields - The difference between
add_timer()andmod_timer() - How to safely cancel a timer with the modern
timer_delete_sync()andtimer_shutdown_sync()APIs introduced in kernel 6.2, and why the olddel_timer_sync()name was removed in kernel 6.15 - A complete, original, ready-to-build kernel module example
- Common mistakes, best practices, and troubleshooting tips
Prerequisites
- Comfort building and loading a basic “Hello World” kernel module
- Basic understanding of interrupt context vs process context
- A Linux machine (kernel 6.2 or later recommended) with kernel headers installed
- Familiarity with
dmesg,insmod, andrmmod
What Is a Kernel Timer?
A kernel timer is a mechanism that lets driver code say “run this function after N milliseconds have passed” without blocking the caller. This is different from a sleep function such as msleep(), which pauses the calling task. A timer schedules a callback to run later while the rest of the kernel keeps running normally. This makes timers ideal for jobs such as watchdog checks, periodic polling, debounce logic, and retransmission deadlines in network drivers.
Where Does the Timer Callback Actually Run?
This is the single most important thing to understand in this Linux kernel timers tutorial. When a hardware timer tick fires, the CPU first runs a very short interrupt handler. Once that handler finishes, the kernel schedules a special deferred routine called TIMER_SOFTIRQ to process every timer that has expired. Your callback function runs inside this softirq, which means it executes in atomic context.
Because atomic context cannot sleep or block, a timer callback must never call functions like msleep(), take a mutex, or perform any operation that might block. If your work genuinely needs to sleep, hand it off to a kernel thread or a workqueue from inside the callback instead of doing the blocking work directly.
The timer_list Structure
Every kernel timer is represented by a struct timer_list. On modern kernels, the fields that matter to driver authors are simple:
| Field | Purpose |
|---|---|
expires | The jiffies value at which the timer should fire |
function | Pointer to your callback, called as void callback(struct timer_list *t) |
flags | Timer behavior flags (CPU affinity plus special flags below) |
Setting Up a Timer: timer_setup()
To initialize a timer you call timer_setup(timer, callback, flags). This macro replaced the older two-step init_timer() plus manual field assignment pattern; since kernel 4.15, the callback receives a pointer to the timer_list itself, so you typically embed the timer inside your own driver structure and use container_of() to get back to it.
| Flag | What It Does |
|---|---|
0 | Default behavior, no special handling |
TIMER_DEFERRABLE | Does not wake an idle CPU just to run this timer; it waits for the CPU to wake up on its own |
TIMER_PINNED | Keeps the timer bound to the CPU it was started on, instead of letting it migrate |
TIMER_IRQSAFE | Allows the timer to be safely cancelled from hard interrupt context |
Arming a Timer: add_timer() vs mod_timer()
Once initialized, a timer does nothing until you arm it.
| Function | When To Use It |
|---|---|
add_timer(t) | Starts a timer that is not currently pending. Calling it on an already-armed timer is a bug. |
mod_timer(t, expires) | Safely (re)schedules a timer whether it is currently pending or not. This is also how you build a repeating (interval) timer, by calling it again from inside the callback. |
Kernel timers are one-shot by default. If you want periodic behavior, your callback must call mod_timer() again before it returns.
The Complete Timer Lifecycle
Complete Original Code Example: A Heartbeat Timer Module
Below is a self-contained, original kernel module for kernel 6.x that arms a repeating timer and logs a heartbeat message every two seconds. It demonstrates timer_setup(), an interval timer built with mod_timer(), and a clean shutdown using timer_delete_sync().
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#define HEARTBEAT_INTERVAL_MS 2000
struct heartbeat_dev {
struct timer_list beat_timer;
unsigned long tick_count;
};
static struct heartbeat_dev hb_dev;
/* Runs in TIMER_SOFTIRQ (atomic) context - keep it short! */
static void heartbeat_callback(struct timer_list *t)
{
struct heartbeat_dev *dev = from_timer(dev, t, beat_timer);
dev->tick_count++;
pr_info("heartbeat: tick #%lu\n", dev->tick_count);
/* Re-arm for the next beat -> makes this a periodic timer */
mod_timer(&dev->beat_timer,
jiffies + msecs_to_jiffies(HEARTBEAT_INTERVAL_MS));
}
static int __init heartbeat_init(void)
{
hb_dev.tick_count = 0;
timer_setup(&hb_dev.beat_timer, heartbeat_callback, 0);
mod_timer(&hb_dev.beat_timer,
jiffies + msecs_to_jiffies(HEARTBEAT_INTERVAL_MS));
pr_info("heartbeat: module loaded, timer armed\n");
return 0;
}
static void __exit heartbeat_exit(void)
{
/* Blocks until any in-flight callback finishes, then removes it */
timer_delete_sync(&hb_dev.beat_timer);
pr_info("heartbeat: module unloaded after %lu ticks\n",
hb_dev.tick_count);
}
module_init(heartbeat_init);
module_exit(heartbeat_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Original heartbeat timer example - EmbeddedPathashala");
Build and load it like any other module:
$ make
$ sudo insmod heartbeat.ko
$ dmesg -w | grep heartbeat
$ sudo rmmod heartbeat
Cancelling a Timer the Modern Way (Kernel 6.x Update)
Older Linux kernel timers tutorials teach del_timer() and del_timer_sync(). These still worked as thin wrappers for a while, but the kernel timer subsystem was renamed starting with kernel 6.2, and the guidance below reflects the current, up-to-date API that every free Linux kernel development course should now be teaching.
| Old Name (pre-6.2) | Current Name (6.2+) | Behavior |
|---|---|---|
del_timer() | timer_delete() | Removes a pending timer; does not wait for a currently running callback |
del_timer_sync() | timer_delete_sync() | Removes the timer and blocks until any running callback has finished |
| (new) | timer_shutdown_sync() | Same as timer_delete_sync(), but also permanently disables the timer so it can never be re-armed by mistake — the safest choice during module or device removal |
Important: the old del_timer() / del_timer_sync() compatibility wrappers were fully removed starting with kernel 6.15. Code written for older kernel versions that still calls these names will fail to compile on 6.15 and later, so new modules should be written against timer_delete(), timer_delete_sync(), and timer_shutdown_sync() directly.
Real-World Use Cases
- Watchdog timers that detect a hung device and trigger recovery
- Network retransmission timers that resend a packet if no acknowledgment arrives in time
- Debounce timers that filter noisy input signals such as buttons or GPIO lines
- Deadline monitors that flag an operation as failed if it does not complete within a required time window
- Power management timers that put a device to sleep after a period of inactivity
Common Mistakes and Troubleshooting
| Mistake | Why It’s a Problem |
|---|---|
| Calling a sleeping/blocking function inside the callback | The callback runs in atomic context; blocking there can hang the system |
Freeing driver memory right after timer_delete() | timer_delete() does not wait for an in-flight callback, so the callback can still touch freed memory; use the _sync variant instead |
Calling add_timer() on an already-armed timer | add_timer() expects the timer to be inactive; use mod_timer() for reschedules |
Holding a lock that the callback also needs while calling a _sync delete function | Can deadlock, since the sync delete waits for the callback to finish, and the callback is waiting on the lock |
Best Practices
- Keep timer callbacks short; hand heavier work to a workqueue or kernel thread
- Always tear down timers with a
_syncvariant during driver removal - Prefer
timer_shutdown_sync()when the timer’s data structure is about to be freed, since it also prevents accidental re-arming - Use
msecs_to_jiffies()rather than raw jiffies arithmetic for portability across HZ settings
Performance Considerations
Kernel timers use a hierarchical timer wheel designed for O(1) insertion and cancellation, which makes them cheap even when thousands of timers are active. However, very short, very frequent timers can still wake an otherwise idle CPU, increasing power use. Use TIMER_DEFERRABLE for background timers where exact timing does not matter, so the CPU can stay idle longer.
Security Considerations
The most common security-relevant bug with kernel timers is a use-after-free: freeing memory that a timer callback might still touch. Always call a synchronous delete function before releasing any memory the callback references, and never assume a callback has stopped just because you removed the timer with the non-sync variant.
Summary / Key Takeaways
- Kernel timers schedule work to run later without blocking the caller
- Callbacks run in atomic softirq context and must never sleep
- Initialize with
timer_setup(), arm withadd_timer()ormod_timer() - Always remove timers with
timer_delete_sync()ortimer_shutdown_sync()on kernel 6.2 and later - The pre-6.2 names
del_timer()/del_timer_sync()stop working on kernel 6.15 and later
Conclusion
Kernel timers are one of the simplest but most misused primitives in Linux driver development. Getting the setup, arming, and especially the teardown sequence right protects your driver from race conditions and use-after-free bugs. With the updated kernel 6.x API covered in this Linux kernel timers tutorial, you now have everything needed to write safe, modern timer-based drivers as part of this free Linux kernel development course.
Frequently Asked Questions
1. What is the difference between a kernel timer and msleep()?
msleep() blocks the calling task until the delay passes. A kernel timer schedules a callback to run later while the caller continues immediately, without blocking.
2. Can a timer callback sleep or call mutex_lock()?
No. The callback runs in softirq (atomic) context, so it must never call a function that can block or sleep.
3. Is add_timer() the same as mod_timer()?
No. add_timer() only works on a timer that is not currently pending, while mod_timer() works whether the timer is pending or not, and is also how you build repeating timers.
4. Why did del_timer_sync() get renamed?
The kernel timer functions were renamed starting in kernel 6.2 to use a consistent timer_ prefix, giving timer_delete() and timer_delete_sync(). The old names stopped working starting with kernel 6.15.
5. What does timer_shutdown_sync() do differently from timer_delete_sync()?
It performs the same synchronous removal, but also permanently marks the timer as dead so any later attempt to re-arm it is silently ignored, which is safer right before freeing memory.
6. Are kernel timers periodic by default?
No. A kernel timer fires exactly once by default. To build a periodic timer, call mod_timer() again from inside the callback.
7. What does TIMER_DEFERRABLE actually save?
It prevents the timer from waking an idle CPU just to service it, which reduces unnecessary CPU wakeups and saves power on low-priority, non-time-critical timers.
8. Why did my module fail to build on a newer kernel with “implicit declaration of del_timer_sync”?
Kernel 6.15 removed the compatibility wrappers for the old names. Replace del_timer_sync() with timer_delete_sync() in your source.
Continue Learning
This lecture is part of EmbeddedPathashala’s free Linux kernel development course.
Explore More Lectures
2 Comments