一、中断简介
1.1 简介
Linux 的中断机制将中断处理程序分成了顶半部和底半部, 顶半部用来处理比较紧急但是又不是很耗时的事情,如设置标志位, 底半部用来处理耗时的事情。 需要注意的是中断处理程序并不是总是分成两个部分, 如果中断处理程序中要执行的代码很小, 则直接在顶半部完成即可, 就不需要激活底半部。
中断框架:
处理器中断引脚数量有限, 给每个外设预留一个引脚是不现实的。 为了解决这个问题, 现代设备的中断信号线并不直接连接至处理器, 而是先连接至中断控制器, 再由中断控制器统一连接至处理器的中断引脚。如果硬件系统非常复杂, 外设很多, 硬件系统可能有多个中断控制器, 中断控制器可以级联。

1.2 中断上文函数

1.3 代码
cpp
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#define GPIO_PIN 42
/**************中断处理函数*****************/
static irqreturn_t gpio_irq_func(int irq, void *dev_id)
{
printk("enter gpio_irq\n");
return IRQ_HANDLED; //表示中断服务函数已成功处理该中断, 中断控制器无需进一步处理
}
static int __init irq_test_init(void)
{
int irq_num,ret;
/******获取中断号*********/
irq_num=gpio_to_irq(GPIO_PIN);//此处用的是GPIO1_B2,pin42
if(irq_num<0)
{
printk("gpio_to_irq failed\n");
return -1;
}
/************申请中断函数*****************/
ret=request_irq(irq_num,gpio_irq_func,IRQF_TRIGGER_RISING, "my_irq_test",NULL);//上升沿触发
if(ret<0)
{
gpio_free(GPIO_PIN);//释放GPIO引脚
printk("request_irq failed\n");
return -2;
}
return 0;
}
static void __exit irq_test_exit(void)
{
int irq_num=gpio_to_irq(GPIO_PIN);
free_irq(irq_num, NULL);//释放中断
printk("byebye\n");
}
module_init(irq_test_init);
module_exit(irq_test_exit);
MODULE_LICENSE("GPL");
1.4 中断处理函数分析

二、tasklet 下文中断
2.1 简介
在 Linux 内核中, tasklet 是一种特殊的软中断机制, 被广泛用于处理中断下文相关的任务。tasklet 绑定的函数在同一时间只能在一个 CPU 上运行, 因此不会出现并发冲突。 tasklet 绑定的函数中不能调用可能导致休眠的函数, 否则可能引起内核异常。
2.2 相关函数

1.3 代码
cpp
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#define GPIO_PIN 42
struct tasklet_struct my_tasklet_struct;//定义一个结构体
/**************中断处理函数(中断上文)*****************/
static irqreturn_t gpio_irq_func(int irq, void *dev_id)
{
printk("进入中断上文\n");
tasklet_schedule(&my_tasklet_struct);//调度中断下文
return IRQ_HANDLED; //表示中断服务函数已成功处理该中断, 中断控制器无需进一步处理
}
/*******************中断处理函数(中断下文)*********************************/
void tasklet_func(unsigned long data)
{
printk("进入中断下文 data=%ld\n",data);
}
static int __init irq_test_init(void)
{
int irq_num,ret;
/******获取中断号*********/
irq_num=gpio_to_irq(GPIO_PIN);//此处用的是GPIO1_B2,pin42
if(irq_num<0)
{
printk("gpio_to_irq failed\n");
return -1;
}
/************申请中断函数*****************/
ret=request_irq(irq_num,gpio_irq_func,IRQF_TRIGGER_RISING, "my_irq_test",NULL);//上升沿触发
if(ret<0)
{
gpio_free(GPIO_PIN);//释放GPIO引脚
printk("request_irq failed\n");
return -2;
}
/****************初始化tasklet函数*********************/
tasklet_init(&my_tasklet_struct,tasklet_func,1);
return 0;
}
static void __exit irq_test_exit(void)
{
int irq_num=gpio_to_irq(GPIO_PIN);
free_irq(irq_num, NULL);//释放中断
tasklet_disable(&my_tasklet_struct);//禁能tasklet
tasklet_kill(&my_tasklet_struct); // 销毁 tasklet
printk("byebye\n");
}
module_init(irq_test_init);
module_exit(irq_test_exit);
MODULE_LICENSE("GPL");
三、软中断
3.1 简介
软中断也可以实现中断处理函数的底半部, 实际上, tasklet 也是基于软中断来实现的
软中断使用枚举类型来标识中断不同类型和优先级, 软中断的序号越小, 优先级越高。 在驱动代码中, 可以使用inux_sdk/kernel/include/linux/interrupt.h中定义了软中断类型, 也可以自己添加软中断。
cpp
//linux_sdk/kernel/include/linux/interrupt.h
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the
numbering. Sigh! */
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
TEST_SOFTIRQ, //添加的自定义软中断
NR_SOFTIRQS
}
尽管添加一个自定义的软中断非常简单, 但是 Linux 内核的开发者并不希望我们这样去做,如果要用软中断, 建议使用 tasklet
3.2 相关函数

四、共享队列
4.1 简介
工作队列也是实现中断处理函数底半部的机制, 在工作队列中是可以休眠的。 一个工作队列就好像常用的消息队列一样, 里面存着很多工作项等待着被处理
共享队列是由内核管理的全局工作队列, 用于处理内核中一些系统级任务。 共享工作队列是内核中一个默认工作队列, 可以由多个驱动程序共享使用
4.2 相关函数

4.3 代码
cpp
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#define GPIO_PIN 42
struct work_struct my_queue_struct;//定义work_struct结构体
/**************中断处理函数(中断上文)*****************/
static irqreturn_t gpio_irq_func(int irq, void *dev_id)
{
printk("进入中断上文\n");
schedule_work(&my_queue_struct);//调度中断下文
return IRQ_HANDLED; //表示中断服务函数已成功处理该中断, 中断控制器无需进一步处理
}
/*******************中断处理函数(中断下文)*********************************/
void queue_func(struct work_struct *work)
{
msleep(1000);
printk("queue进入中断下文\n" );
}
static int __init irq_test_init(void)
{
int irq_num,ret;
/******获取中断号*********/
irq_num=gpio_to_irq(GPIO_PIN);//此处用的是GPIO1_B2,pin42
if(irq_num<0)
{
printk("gpio_to_irq failed\n");
return -1;
}
/************申请中断函数*****************/
ret=request_irq(irq_num,gpio_irq_func,IRQF_TRIGGER_RISING, "my_irq_test",NULL);//上升沿触发
if(ret<0)
{
gpio_free(GPIO_PIN);//释放GPIO引脚
printk("request_irq failed\n");
return -2;
}
/****************初始化共享工作队列函数*********************/
INIT_WORK(&my_queue_struct,queue_func);
return 0;
}
static void __exit irq_test_exit(void)
{
int irq_num=gpio_to_irq(GPIO_PIN);
free_irq(irq_num, NULL);//释放中断
printk("byebye\n");
}
module_init(irq_test_init);
module_exit(irq_test_exit);
MODULE_LICENSE("GPL");
4.4 结果

中断上文比中断下文多触发,是因为=在中断上文调度工作项处理函数之后, 内核没有来得及去执行工作项处理函数, 没有执行相当于无效操作
五、自定义工作队列
5.1 简介
自定义工作队列故名思意需要自己创建工作队列, 然后把工作项放在自己创建的工作队列上。 而共享工作队列是所有人共用一个工作队列
5.2 相关函数

5.3 代码
cpp
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#define GPIO_PIN 42
struct work_struct my_queue_work;//定义work_struct结构体用于描述工作项
struct workqueue_struct *my_queue; //定义一个workqueue_struct结构体指针用于描述工作队列
/**************中断处理函数(中断上文)*****************/
static irqreturn_t gpio_irq_func(int irq, void *dev_id)
{
printk("进入中断上文\n");
queue_work(my_queue,&my_queue_work);//提交工作队列项到工作队列,调度中断下文
return IRQ_HANDLED; //表示中断服务函数已成功处理该中断, 中断控制器无需进一步处理
}
/*******************中断处理函数(中断下文)*********************************/
void queue_func(struct work_struct *work)
{
msleep(1000);
printk("queue进入中断下文\n" );
}
static int __init irq_test_init(void)
{
int irq_num,ret;
/******获取中断号*********/
irq_num=gpio_to_irq(GPIO_PIN);//此处用的是GPIO1_B2,pin42
if(irq_num<0)
{
printk("gpio_to_irq failed\n");
return -1;
}
/************申请中断函数*****************/
ret=request_irq(irq_num,gpio_irq_func,IRQF_TRIGGER_RISING, "my_irq_test",NULL);//上升沿触发
if(ret<0)
{
gpio_free(GPIO_PIN);//释放GPIO引脚
printk("request_irq failed\n");
return -2;
}
/***************创建并初始化自定义工作队列*********************/
my_queue=create_workqueue("my_test_queue");
if(my_queue==NULL)
{
printk("create_workqueue failed\n");
return -3;
}
INIT_WORK(&my_queue_work,queue_func);//初始化工作队列
return 0;
}
static void __exit irq_test_exit(void)
{
int irq_num=gpio_to_irq(GPIO_PIN);
free_irq(irq_num, NULL);//释放中断
cancel_work_sync(&my_queue_work);//取消工作项
flush_workqueue(my_queue);//刷新工作队列
destroy_workqueue(my_queue);//销毁工作队列
printk("byebye\n");
}
module_init(irq_test_init);
module_exit(irq_test_exit);
MODULE_LICENSE("GPL");
六 、延迟函数

代码
cpp
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#define GPIO_PIN 42
struct delayed_work my_delay_work;//定义delayed_work结构体
struct workqueue_struct *my_queue; //定义一个workqueue_struct结构体指针用于描述工作队列
/**************中断处理函数(中断上文)*****************/
static irqreturn_t gpio_irq_func(int irq, void *dev_id)
{
printk("进入中断上文\n");
queue_delayed_work(my_queue,&my_delay_work,1*HZ);//调度,延迟1s
return IRQ_HANDLED; //表示中断服务函数已成功处理该中断, 中断控制器无需进一步处理
}
/*******************中断处理函数(中断下文)*********************************/
void queue_func(struct work_struct *work)
{
msleep(1000);
printk("queue进入中断下文\n" );
}
static int __init irq_test_init(void)
{
int irq_num,ret;
/******获取中断号*********/
irq_num=gpio_to_irq(GPIO_PIN);//此处用的是GPIO1_B2,pin42
if(irq_num<0)
{
printk("gpio_to_irq failed\n");
return -1;
}
/************申请中断函数*****************/
ret=request_irq(irq_num,gpio_irq_func,IRQF_TRIGGER_RISING, "my_irq_test",NULL);//上升沿触发
if(ret<0)
{
gpio_free(GPIO_PIN);//释放GPIO引脚
printk("request_irq failed\n");
return -2;
}
/***************创建自定义工作队列,初始化延迟函数*********************/
my_queue=create_workqueue("my_test_queue");
if(my_queue==NULL)
{
printk("create_workqueue failed\n");
return -3;
}
INIT_DELAYED_WORK(&my_delay_work, queue_func);//初始化延迟工作函数
return 0;
}
static void __exit irq_test_exit(void)
{
int irq_num=gpio_to_irq(GPIO_PIN);
free_irq(irq_num, NULL);//释放中断
cancel_delayed_work_sync(&my_delay_work);//取消工作项
flush_workqueue(my_queue);//刷新工作队列
destroy_workqueue(my_queue);//销毁工作队列
printk("byebye\n");
}
module_init(irq_test_init);
module_exit(irq_test_exit);
MODULE_LICENSE("GPL");
七、工作队列传参
cpp
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/types.h>
#define GPIO_PIN 42
struct work_data
{
struct delayed_work my_delay_work;//定义delayed_work结构体
int a;
int b;
};
struct work_data my_data;
struct workqueue_struct *my_queue; //定义一个workqueue_struct结构体指针用于描述工作队列
/**************中断处理函数(中断上文)*****************/
static irqreturn_t gpio_irq_func(int irq, void *dev_id)
{
printk("进入中断上文\n");
queue_delayed_work(my_queue,&(my_data.my_delay_work),1*HZ);//调度,延迟1s
return IRQ_HANDLED; //表示中断服务函数已成功处理该中断, 中断控制器无需进一步处理
}
/*******************中断处理函数(中断下文)*********************************/
void queue_func(struct work_struct *work)
{
struct work_data *pata;
pata=container_of(work, struct work_data, my_delay_work.work);
printk("queue进入中断下文\n" );
printk("a=%d,b=%d\n",pata->a,pata->b);
}
static int __init irq_test_init(void)
{
int irq_num,ret;
/******获取中断号*********/
irq_num=gpio_to_irq(GPIO_PIN);//此处用的是GPIO1_B2,pin42
if(irq_num<0)
{
printk("gpio_to_irq failed\n");
return -1;
}
/************申请中断函数*****************/
ret=request_irq(irq_num,gpio_irq_func,IRQF_TRIGGER_RISING, "my_irq_test",NULL);//上升沿触发
if(ret<0)
{
gpio_free(GPIO_PIN);//释放GPIO引脚
printk("request_irq failed\n");
return -2;
}
/***************创建自定义工作队列,初始化延迟函数*********************/
my_queue=create_workqueue("my_test_queue");
if(my_queue==NULL)
{
printk("create_workqueue failed\n");
return -3;
}
INIT_DELAYED_WORK(&(my_data.my_delay_work), queue_func);//初始化延迟工作函数
my_data.a = 10;
my_data.b = 20;
return 0;
}
static void __exit irq_test_exit(void)
{
int irq_num=gpio_to_irq(GPIO_PIN);
free_irq(irq_num, NULL);//释放中断
cancel_delayed_work_sync(&(my_data.my_delay_work));//取消工作项
flush_workqueue(my_queue);//刷新工作队列
destroy_workqueue(my_queue);//销毁工作队列
printk("byebye\n");
}
module_init(irq_test_init);
module_exit(irq_test_exit);
MODULE_LICENSE("GPL");
八、并发管理工作队列
CMWQ 全称是 concurrency Managed Workqueue, 意为并发管理工作队列。 并发管理工作队列是多线程或分布式系统中用于协调任务执行的一种机制, 核心是在并发(同时处理多个任务) 场景下, 对"待处理任务队列" 进行有序管理, 确保任务被合理调度、 资源高效利用

代码:
cpp
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#define GPIO_PIN 42
struct work_struct my_queue_work;//定义work_struct结构体用于描述工作项
struct workqueue_struct *my_queue; //定义一个workqueue_struct结构体指针用于描述工作队列
/**************中断处理函数(中断上文)*****************/
static irqreturn_t gpio_irq_func(int irq, void *dev_id)
{
printk("进入中断上文\n");
queue_work(my_queue,&my_queue_work);//提交工作队列项到工作队列,调度中断下文
return IRQ_HANDLED; //表示中断服务函数已成功处理该中断, 中断控制器无需进一步处理
}
/*******************中断处理函数(中断下文)*********************************/
void queue_func(struct work_struct *work)
{
msleep(1000);
printk("queue进入中断下文\n" );
}
static int __init irq_test_init(void)
{
int irq_num,ret;
/******获取中断号*********/
irq_num=gpio_to_irq(GPIO_PIN);//此处用的是GPIO1_B2,pin42
if(irq_num<0)
{
printk("gpio_to_irq failed\n");
return -1;
}
/************申请中断函数*****************/
ret=request_irq(irq_num,gpio_irq_func,IRQF_TRIGGER_RISING, "my_irq_test",NULL);//上升沿触发
if(ret<0)
{
gpio_free(GPIO_PIN);//释放GPIO引脚
printk("request_irq failed\n");
return -2;
}
/***************创建并初始化自定义工作队列*********************/
my_queue=alloc_workqueue("my_test_queue",WQ_UNBOUND,0);
if(my_queue==NULL)
{
printk("create_workqueue failed\n");
return -3;
}
INIT_WORK(&my_queue_work,queue_func);//初始化工作队列
return 0;
}
static void __exit irq_test_exit(void)
{
int irq_num=gpio_to_irq(GPIO_PIN);
free_irq(irq_num, NULL);//释放中断
cancel_work_sync(&my_queue_work);//取消工作项
flush_workqueue(my_queue);//刷新工作队列
destroy_workqueue(my_queue);//销毁工作队列
printk("byebye\n");
}
module_init(irq_test_init);
module_exit(irq_test_exit);
MODULE_LICENSE("GPL");
九、中断线程化
中断线程化是 Linux 内核中优化中断处理的一种机制。 传统的中断处理分成了顶半部和底半部, 顶半部用来处理比较紧急但是又不是很耗时的事情, 如设置标志位, 底半部用来处理耗时的事情。
在中断线程化机制中, 中断处理程序不再执行具体任务, 而是只负责唤醒一个内核线程来完成主要的中断处理逻辑。 这样可以显著缩短中断响应时间, 使被中断的实时进程能够更快地恢复运行。 因此中断线程化也被认为是一种实现底半部的方式。

代码:
cpp
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#define GPIO_PIN 42
/**************中断处理函数(中断上文)*****************/
static irqreturn_t gpio_irq_func(int irq, void *dev_id)
{
printk("进入中断上文\n");
return IRQ_WAKE_THREAD;//中断服务函数已处理该中断,并且请求唤醒一个内核线程来继续执行进一步的处理; //表示中断服务函数已成功处理该中断, 中断控制器无需进一步处理
}
/*******************中断处理函数(中断下文)*********************************/
irqreturn_t thread_next_func(int irq, void *dev_id)
{
msleep(1000);
printk("queue进入中断下文\n" );
return IRQ_HANDLED; //表示中断服务函数已成功处理该中断, 中断控制器无需进一步处理
}
static int __init irq_test_init(void)
{
int irq_num,ret;
/******获取中断号*********/
irq_num=gpio_to_irq(GPIO_PIN);//此处用的是GPIO1_B2,pin42
if(irq_num<0)
{
printk("gpio_to_irq failed\n");
return -1;
}
/************申请中断函数*****************/
ret=request_threaded_irq(irq_num,gpio_irq_func,thread_next_func, IRQF_TRIGGER_RISING, "my_irq_test",NULL);
if(ret<0)
{
gpio_free(GPIO_PIN);//释放GPIO引脚
printk("request_irq failed\n");
return -2;
}
return 0;
}
static void __exit irq_test_exit(void)
{
int irq_num=gpio_to_irq(GPIO_PIN);
free_irq(irq_num, NULL);//释放中断
printk("byebye\n");
}
module_init(irq_test_init);
module_exit(irq_test_exit);
MODULE_LICENSE("GPL");
十、总结
在资源下载pdf版本
