中断下半部
在 Linux 内核中,中断下半部(也称为中断下半场)是指在中断服务程序(Top Half)执行完毕后,在上下文之外延迟执行的一些操作。中断下半部通常用于处理那些不适合在中断上下文中立即执行的任务,以保持中断服务程序的快速响应。
中断下半部可以通过以下几种机制来实现:
1、Tasklet,一种内核机制,用于在延迟上下文中执行轻量级的任务。Tasklet 通常在中断服务程序的上下文中调度,并在延迟上下文中执行。是在禁止所有中断的情况下进行的,因此它们可以安全地访问共享的数据结构和资源。
2、工作队列(Workqueue),工作队列是一种内核机制,用于在延迟上下文中异步执行较为耗时的任务。工作队列可以在系统的后台执行,并且可以并发执行多个任务。是由内核线程来完成的,因此可以执行任意复杂度的操作,并且不会阻塞其他内核活动。
3、软中断(SoftIRQ),软中断是一种内核机制,用于在延迟上下文中执行一些较为复杂或耗时的任务。软中断是在内核中断上下文之外的一种执行机制。
代码实现:
1、Tasklet
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data)
tasklet_init
是 Linux 内核中用于初始化 Tasklet 结构体的函数。
t
是要初始化的 Tasklet 结构体指针。func
是 Tasklet 的处理函数,即中断下半部的处理逻辑。data
是传递给处理函数的参数。
static inline void tasklet_schedule(struct tasklet_struct *t)
tasklet_schedule
是 Linux 内核中用于调度 Tasklet 来执行任务的函数,在中断处理触发后合适的位置执行;
t
是要调度执行的 Tasklet 结构体指针。
void tasklet_kill(struct tasklet_struct *t);
tasklet_kill
是 Linux 内核中用于终止 Tasklet 的函数。
举个例子:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#define GPIO_BUTTON_PIN 40
static unsigned int irq_num;
struct tasklet_struct key_drv_tasklet;
static void key_drv_tasklet_handler(unsigned long data) {
printk("Tasklet executed in delayed context\n");
}
// 中断处理函数
static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
int value = gpio_get_value(GPIO_BUTTON_PIN);
if (!value) {
printk("====> key press\n");
} else {
printk("====> key up\n");
}
tasklet_schedule(&key_drv_tasklet);
return IRQ_HANDLED;
}
static int __init key_drv_init(void) {
int ret;
// 获取中断号
// 请求 GPIO 引脚
ret = gpio_request(GPIO_BUTTON_PIN, "button_gpio");
if (ret) {
printk(KERN_ERR "Failed to request GPIO pin\n");
return ret;
}
// 设置 GPIO 引脚方向为输入
ret = gpio_direction_input(GPIO_BUTTON_PIN);
if (ret) {
printk(KERN_ERR "Failed to set GPIO pin direction\n");
gpio_free(GPIO_BUTTON_PIN);
return ret;
}
// 将 GPIO 映射到 IRQ
irq_num = gpio_to_irq(GPIO_BUTTON_PIN);
if (irq_num < 0) {
printk(KERN_ERR "Failed to map GPIO to IRQ\n");
gpio_free(GPIO_BUTTON_PIN);
return irq_num;
}
tasklet_init(&key_drv_tasklet, key_drv_tasklet_handler, 0);
// 请求中断
ret = request_irq(irq_num, key_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "key_irq", NULL);
if (ret) {
printk(KERN_ERR "Failed to register IRQ handler\n");
gpio_free(GPIO_BUTTON_PIN);
return ret;
}
return 0;
}
static void __exit key_drv_exit(void) {
free_irq(irq_num, NULL);
tasklet_kill(&key_drv_tasklet);
gpio_free(GPIO_BUTTON_PIN);
}
module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dengcaixiang");
MODULE_DESCRIPTION("Simple key driver");
执行结果:
2、工作队列
INIT_WORK(work, func)
INIT_WORK
是 Linux 内核中用于初始化工作队列中的工作项的宏
work
是要初始化的struct work_struct
结构体指针。func
是要与工作项关联的处理函数的函数指针。
static inline bool schedule_work(struct work_struct *work)
schedule_work
用于将一个工作项(struct work_struct
)添加到工作队列中以延迟执行。
work
:指向要添加到工作队列中的工作项的指针,类型为struct work_struct *
。该工作项必须是预先初始化的,并且在调用schedule_work()
函数之后,内核将负责安排该工作项的执行。
举个例子:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#define GPIO_BUTTON_PIN 40
static unsigned int irq_num;
static struct work_struct key_drv_workqueue;
static void key_drv_work_handler(struct work_struct *work) {
printk("Workqueue executed in process context\n");
}
// 中断处理函数
static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
int value = gpio_get_value(GPIO_BUTTON_PIN);
if (!value) {
printk("====> key press\n");
} else {
printk("====> key up\n");
}
schedule_work(&key_drv_workqueue);
return IRQ_HANDLED;
}
static int __init key_drv_init(void) {
int ret;
// 获取中断号
// 请求 GPIO 引脚
ret = gpio_request(GPIO_BUTTON_PIN, "button_gpio");
if (ret) {
printk(KERN_ERR "Failed to request GPIO pin\n");
return ret;
}
// 设置 GPIO 引脚方向为输入
ret = gpio_direction_input(GPIO_BUTTON_PIN);
if (ret) {
printk(KERN_ERR "Failed to set GPIO pin direction\n");
gpio_free(GPIO_BUTTON_PIN);
return ret;
}
// 将 GPIO 映射到 IRQ
irq_num = gpio_to_irq(GPIO_BUTTON_PIN);
if (irq_num < 0) {
printk(KERN_ERR "Failed to map GPIO to IRQ\n");
gpio_free(GPIO_BUTTON_PIN);
return irq_num;
}
INIT_WORK(&key_drv_workqueue, key_drv_work_handler);
// 请求中断
ret = request_irq(irq_num, key_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "key_irq", NULL);
if (ret) {
printk(KERN_ERR "Failed to register IRQ handler\n");
gpio_free(GPIO_BUTTON_PIN);
return ret;
}
return 0;
}
static void __exit key_drv_exit(void) {
free_irq(irq_num, NULL);
gpio_free(GPIO_BUTTON_PIN);
}
module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dengcaixiang");
MODULE_DESCRIPTION("Simple key driver");
执行结果: