« Previous Lecture | Next Lecture »
If you are following our free linux kernel development course, this lecture is where theory
turns into a running driver. Building a working linux kernel timer module is the single best
way to understand how the kernel measures time, schedules deferred work, and safely tears that work down again
on module removal. In this lecture we design, write, and test a complete, original timer-driven kernel module —
not a toy snippet — and we update every API call to match the current kernel 6.x timer subsystem, including the
newer timer_shutdown_sync() cleanup path that replaced the old deletion helpers.
What You Will Learn
- How a linux kernel timer module is structured, end to end
- How to arm a timer with
timer_setup()andadd_timer() - How to safely re-arm a repeating timer using
mod_timer() - How to retrieve your driver’s private context from inside a timer callback
- Why cleanup on kernel 6.2+ needs
timer_shutdown_sync(), not the older deletion calls - Common bugs, race conditions, and performance/security pitfalls with kernel timers
Prerequisites
- Comfort building and loading a basic Loadable Kernel Module (LKM) with
insmod/rmmod - A Linux VM or machine running kernel 6.2 or later (check with
uname -r) - Kernel headers installed for your running kernel
- Basic C pointer and struct knowledge
What Is a Linux Kernel Timer Module, Really?
At its core, a kernel timer is a request you hand to the kernel: “run this function once a certain number of jiffies have passed.” A linux kernel timer module wraps that request inside a loadable driver so you can arm it, watch it fire, and clean it up safely. Unlike a userspace timer, the callback here runs in softirq (atomic) context — no sleeping, no blocking allocations, no copying to or from user space.
Why Build Your Own Timer Module Instead of Just Reading About It?
Reading about timer_setup() and mod_timer() only gets you so far. Real-world drivers —
watchdogs, polling-based sensor drivers, connection keep-alives, LED blink patterns, debounce logic — all lean on
exactly this pattern. Writing your own linux kernel timer module from scratch is what makes the
API click.
Architecture and Workflow of Our Timer Module
Here is the lifecycle we are about to implement, shown as an inline diagram rather than a text-based drawing:
Notice the loop between “callback fires” and “mod_timer() reload” — this is what makes the timer repeat without
calling add_timer() again. And notice that unload always goes through a synchronous shutdown call
before the module is actually removed; skipping that step is one of the most common causes of kernel crashes on
unload.
Complete Original Linux Kernel Timer Module Example
Below is an original driver written for this lecture — a “heartbeat” timer module that logs a heartbeat message a fixed number of times and then stops, demonstrating arm, repeat, and safe shutdown in one place.
// heartbeat_timer.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#define HEARTBEAT_INTERVAL_MS 500
#define HEARTBEAT_MAX_BEATS 5
struct heartbeat_dev {
struct timer_list beat_timer;
unsigned int beats_left;
};
static struct heartbeat_dev hb;
static void heartbeat_fire(struct timer_list *t)
{
struct heartbeat_dev *dev = from_timer(dev, t, beat_timer);
pr_info("heartbeat_timer: beat! (%u remaining)\n", dev->beats_left);
if (dev->beats_left > 0) {
dev->beats_left--;
mod_timer(&dev->beat_timer,
jiffies + msecs_to_jiffies(HEARTBEAT_INTERVAL_MS));
} else {
pr_info("heartbeat_timer: sequence complete\n");
}
}
static int __init heartbeat_init(void)
{
hb.beats_left = HEARTBEAT_MAX_BEATS;
timer_setup(&hb.beat_timer, heartbeat_fire, 0);
hb.beat_timer.expires = jiffies + msecs_to_jiffies(HEARTBEAT_INTERVAL_MS);
add_timer(&hb.beat_timer);
pr_info("heartbeat_timer: armed, interval=%ums\n", HEARTBEAT_INTERVAL_MS);
return 0;
}
static void __exit heartbeat_exit(void)
{
/* kernel 6.2+: replaces the old del_timer_sync() */
timer_shutdown_sync(&hb.beat_timer);
pr_info("heartbeat_timer: unloaded cleanly\n");
}
module_init(heartbeat_init);
module_exit(heartbeat_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Original heartbeat linux kernel timer module example");
Step-by-Step Explanation
- Struct design: the
timer_listlives inside our ownheartbeat_devstruct, so the callback can recover our private data viafrom_timer(). - Arming:
timer_setup()binds the callback;add_timer()starts the countdown onceexpiresis set. - Repeating: instead of calling
add_timer()again, the callback callsmod_timer(), which both reschedules an active timer and can arm an inactive one. - Shutdown:
timer_shutdown_sync()guarantees the timer is fully stopped and no callback is mid-flight before the module’s memory is freed on unload.
Building and Testing the Module
make
sudo insmod heartbeat_timer.ko
dmesg | tail
sudo rmmod heartbeat_timer
dmesg | tail
You should see five heartbeat lines roughly 500ms apart, followed by a completion message, and a clean unload log on removal.
Kernel 6.x Timer Cleanup: What Changed
| API | Older Kernels | Kernel 6.2+ |
|---|---|---|
| Synchronous stop on unload | del_timer_sync() | timer_shutdown_sync() |
| Non-blocking stop | del_timer() | Deprecated; prefer timer_shutdown_sync() in exit paths |
| Old wrappers removed | — | del_timer()/del_timer_sync() wrappers removed starting kernel 6.15 |
Real-World Use Cases for Kernel Timers
- Hardware watchdog “kick” or timeout detection
- Polling a sensor register at a fixed interval when interrupts aren’t available
- Debouncing a GPIO input line
- LED blink patterns and status indicators
- Connection or link keep-alive checks in network drivers
Common Mistakes and Troubleshooting
- Sleeping in the callback: the callback runs in softirq context — never call
msleep(),kmalloc(GFP_KERNEL), or copy_to/from_user here. - Forgetting shutdown before free: freeing the struct holding your
timer_listbefore stopping the timer is a classic use-after-free. - Using stale jiffies math: always recompute
jiffies + msecs_to_jiffies(...)at the time you arm or re-arm, don’t cache an old expiry value. - Ignoring the return value of
mod_timer(): a 0 means it armed an inactive timer; a 1 means it modified one already running — useful for debugging double-arm bugs.
Best Practices for a Linux Kernel Timer Module
- Keep callback logic minimal — hand heavier work off to a workqueue or kernel thread.
- Always pair every
add_timer()with a matching shutdown call in the exit path. - Use
TIMER_DEFERRABLEwhen timing precision doesn’t matter but power savings do. - Log both arm and fire events during development; remove noisy logs before production.
Performance Considerations
Kernel timers are cheap, but very short, high-frequency timers (sub-millisecond) can add measurable softirq
overhead across many CPUs. If you need sub-millisecond precision, look at hrtimer instead of the
classic timer wheel used here.
Security Considerations
Never let a timer callback act on user-controlled pointers directly, and always validate that the private
context recovered via from_timer() hasn’t already been torn down — this matters most in drivers
that can be opened, closed, and removed concurrently by multiple userspace processes.
Summary / Key Takeaways
- A linux kernel timer module follows a simple arm → fire → optionally re-arm → shutdown lifecycle.
timer_setup()+add_timer()start it;mod_timer()repeats it.- On kernel 6.2+, use
timer_shutdown_sync()for safe teardown. - Callbacks run in atomic context — no sleeping, no blocking work.
Conclusion
You’ve now built, run, and understood a complete linux kernel timer module updated for modern
kernel 6.x APIs. This pattern — private context struct, timer_setup(), self-rearming via
mod_timer(), and safe shutdown via timer_shutdown_sync() — is the same one you’ll reuse
across watchdogs, polling drivers, and countless other real drivers. Continue with the next lecture in our free
linux kernel development course to see how kernel threads compare to timers for deferred work.
Frequently Asked Questions
1. What is a linux kernel timer module used for?
It lets a driver schedule a function to run after a set delay, without blocking, for tasks like polling, watchdogs, or timeouts.
2. Can a kernel timer callback sleep?
No. The callback runs in softirq/atomic context, so blocking calls and GFP_KERNEL allocations are not allowed.
3. What replaced del_timer_sync() in newer kernels?
timer_shutdown_sync() is the recommended replacement starting kernel 6.2, and the old wrappers were removed in kernel 6.15.
4. What’s the difference between add_timer() and mod_timer()?
add_timer() arms a freshly initialized, inactive timer. mod_timer() can both re-arm an active timer and start an inactive one, returning 0 or 1 accordingly.
5. When should I use hrtimer instead of a classic kernel timer?
When you need sub-millisecond or high-precision timing; classic timers are tied to the jiffies tick and are coarser.
6. Is this free linux kernel development course beginner friendly?
Yes — this lecture assumes only basic LKM experience and builds every concept from first principles with original code.
Continue the Free Linux Kernel Development Course
More hands-on lectures on kernel timers, threads, and workqueues are coming — part of EmbeddedPathashala’s free embedded systems course.

2 Comments