Master Linux Kernel Interrupts and Timers: A Hands‑On Guide
This article explains Linux kernel interrupt handling, top‑half and bottom‑half mechanisms, IRQ request/release, shared IRQs, disabling/enabling IRQs, tasklets, workqueues, softirqs, the timer API, and provides a complete character‑device driver example with a user‑space test program.
Overview
Linux kernel interrupt and timer programming are essential topics for low‑level driver development. The article introduces the concepts of interrupts, their classification, and how the kernel processes them in two halves, followed by a detailed description of the timer subsystem.
Interrupt Handling
An interrupt pauses the current CPU execution to handle a sudden event. Linux splits interrupt handling into a top half (quick registration) and a bottom half (deferred processing). The bottom half can be implemented using tasklets , workqueues , or softirqs .
Requesting and Releasing IRQs
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id); irqis the interrupt number, handler is the function to register, and irqflags control the trigger mode. The IRQF_SHARED flag allows multiple devices to share the same line.
void free_irq(unsigned int irq, void *dev_id);Enabling and Disabling IRQs
void disable_irq(int irq); void disable_irq_nosync(int irq); // returns immediately void enable_irq(int irq);Local CPU‑only variants use local_irq_save(flags), local_irq_disable(), local_irq_restore(flags), and local_irq_enable().
Bottom‑Half Mechanisms
Tasklet example:
void my_tasklet_func(unsigned long data);</code>
<code>DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);</code>
<code>tasklet_schedule(&my_tasklet);Workqueue example:
struct work_struct my_wq;</code>
<code>void my_wq_func(unsigned long data);</code>
<code>INIT_WORK(&my_wq, my_wq_func);</code>
<code>schedule_work(&my_wq);Softirq example (registration and triggering):
void open_softirq(unsigned int nr, void (*action)(struct softirq_action *));</code>
<code>raise_softirq(unsigned int nr);Shared Interrupts
When multiple devices share an IRQ line, each must request the IRQ with IRQF_SHARED. The handler should check the hardware registers and the dev_id argument to determine whether the interrupt belongs to its device.
Timer Subsystem
The kernel timer is represented by struct timer_list. A timer expires after a specified number of jiffies and invokes a callback in soft‑irq context.
struct timer_list {
struct list_head entry;
unsigned long expires; // expiration time
void (*function)(unsigned long);
unsigned long data; // argument passed to function
struct timer_base_s *base;
};Typical timer workflow:
Declare a struct timer_list my_timer; Initialize with init_timer(&my_timer); Set my_timer.function = my_handler; and my_timer.expires = jiffies + delay; Register with add_timer(&my_timer); Modify with mod_timer(&my_timer, new_expires); Delete with del_timer(&my_timer); Delay functions:
void ndelay(unsigned long nsecs); // nanoseconds</code>
<code>void udelay(unsigned long usecs); // microseconds</code>
<code>void mdelay(unsigned long msecs); // milliseconds (busy‑wait)</code>
<code>void msleep(unsigned int millisecs); // sleep, can be interrupted</code>
<code>unsigned long msleep_interruptible(unsigned int millisecs);</code>
<code>void ssleep(unsigned int seconds);Complete Example: second_drv.c
The driver creates a character device /dev/second. When the device is opened, a kernel timer is started that fires every second, increments a counter, and prints the current jiffies. The timer is removed when the device is released.
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev>
#include <linux/timer.h>
#include <linux/atomic.h>
#define SECOND_MAJOR 248
static int second_major = SECOND_MAJOR;
struct second_dev {
struct cdev cdev;
atomic_t counter;
struct timer_list s_timer;
};
static struct second_dev *second_devp;
static void second_timer_handle(unsigned long arg)
{
mod_timer(&second_devp->s_timer, jiffies + HZ);
atomic_inc(&second_devp->counter);
printk(KERN_NOTICE "current jiffies is %ld
", jiffies);
}
static int second_open(struct inode *inode, struct file *filp)
{
init_timer(&second_devp->s_timer);
second_devp->s_timer.function = second_timer_handle;
second_devp->s_timer.expires = jiffies + HZ;
add_timer(&second_devp->s_timer);
atomic_set(&second_devp->counter, 0);
return 0;
}
static int second_release(struct inode *inode, struct file *filp)
{
del_timer(&second_devp->s_timer);
return 0;
}
static ssize_t second_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
int counter = atomic_read(&second_devp->counter);
if (put_user(counter, (int __user *)buf))
return -EFAULT;
return sizeof(unsigned int);
}
static const struct file_operations second_fops = {
.owner = THIS_MODULE,
.open = second_open,
.release = second_release,
.read = second_read,
};
static int __init second_init(void)
{
dev_t devno = MKDEV(second_major, 0);
int ret;
if (second_major)
ret = register_chrdev_region(devno, 1, "second");
else {
ret = alloc_chrdev_region(&devno, 0, 1, "second");
second_major = MAJOR(devno);
}
if (ret < 0) return ret;
second_devp = kmalloc(sizeof(*second_devp), GFP_KERNEL);
if (!second_devp) { ret = -ENOMEM; goto fail; }
memset(second_devp, 0, sizeof(*second_devp));
cdev_init(&second_devp->cdev, &second_fops);
second_devp->cdev.owner = THIS_MODULE;
ret = cdev_add(&second_devp->cdev, devno, 1);
if (ret) printk(KERN_NOTICE "Error %d adding CDEV", ret);
return 0;
fail:
unregister_chrdev_region(devno, 1);
return ret;
}
static void __exit second_exit(void)
{
cdev_del(&second_devp->cdev);
kfree(second_devp);
unregister_chrdev_region(MKDEV(second_major, 0), 1);
}
MODULE_AUTHOR("Ljia-----Ljia");
MODULE_LICENSE("Dual BSD/GPL");
module_param(second_major, int, S_IRUGO);
module_init(second_init);
module_exit(second_exit);A user‑space program second_test opens /dev/second and repeatedly reads the counter, printing the number of seconds elapsed since the device was opened.
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int fd = open("/dev/second", O_RDONLY);
if (fd == -1) { perror("open"); return 1; }
unsigned int counter, old = 0;
while (1) {
read(fd, &counter, sizeof(counter));
if (counter != old) {
printf("seconds after open /dev/second: %u
", counter);
old = counter;
}
}
return 0;
}Running the driver and the test program shows the kernel timer printing jiffies values and the user program displaying the elapsed seconds.
Conclusion
Linux interrupt handling consists of a fast top‑half and a deferred bottom‑half (tasklet, workqueue, softirq). For non‑precise delays, sleeping functions such as msleep are recommended. Understanding and experimenting with the provided examples is essential for mastering kernel timing and interrupt mechanisms.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
