文章目录
1.为什么会有中断底半部
设备的中断会打断内核中进程的调度和运行,所以在中断顶半部中不能够做延时,耗时,甚至休眠的动作,也就是说在中断顶半部中只能做简短不耗时的操作。但是有的时候又希望在中断到来的时候做尽可能多的事情。所以两者就产生矛盾,内核为了解决这一矛盾引入了中断底半部的机制。
Linux将中断分为顶半部和底半部
顶半部 | 底半部 |
---|---|
完成尽可能少的比较紧急的任务,它往往只是简单的读取寄存器中的中断状态并清除中断标志后就进行”登记中断“(也就是将底半部处理程序挂到设备的底半部执行队列中)的工作 | 中断处理的大部分工作都在底半部,它几乎做了中断处理程序的所有事情 |
响应速度快 | 处理相对不是非常紧急的事情 |
2.中断的划分
那么对于一个中断,如何划分上下两部分呢?哪些处理放在上半部,哪些处理放在下半部?
- 如果一个任务对时间十分敏感,将其放在上半部;
- 如果一个任务和硬件有关,将其放在上半部;
- 如果一个任务要保证不被其他中断打断,将其放在上半部;
- 其他所有任务,考虑放在下半部。
3.底半部的实现方法
3.1 软中断
软中断是内核工程师使用的,因为软中断的个数是有限制的(32 个),工作在中断上下文。里面可以做相对耗时的操作。休眠,schedule()等都不允许在这个底半部中使用。因此,不需要关注。
3.2 tasklet
tasklet 是基于软中断实现的,它是没有个数限制的(链表结构)。驱动工程师如果需要在中断上下文中使用底半部机制就使用 tasklet 即可。tasklet 是不能脱离中断执行。中断顶半部执行即将结束的时候启动底半部执行即可。
3.2.1 tasklet的API
1.分配对象
struct tasklet_struct
{
struct tasklet_struct *next; //构成链表的成员
unsigned long state; //触发的状态
atomic_t count; //触发次数
bool use_callback; // 真,新版本,假,旧版本
union {
void (*func)(unsigned long data); //旧版本
void (*callback)(struct tasklet_struct *t); //新版本
};
unsigned long data; //向底半部处理函数传参
};
struct tasklet_struct tasklet;
2.对象初始化
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data) //旧版本初始化
void tasklet_setup(struct tasklet_struct *t, void (*callback)(struct tasklet_struct *)) //新版本的初始化
3.调用执行
void tasklet_schedule(struct tasklet_struct *t)
3.2.2 tasklet实例
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
// myirq{
// compatilbe = "hqyj,irq";
// interrupt-parent = <&gpiof>;
// interrupts = <9 0>, <7 0>,<8 0>;
// key = <&gpiof 9 0>,<&gpiof 7 0>,<&gpiof 8 0>;
// };
struct device_node* node;
unsigned int irqno;
int gpiono;
struct tasklet_struct tasklet;
void irq_tasklet(struct tasklet_struct *ts)
{
int i=30;
while(--i){
printk("i = %d\n",i);
}
}
irqreturn_t key_irq(int irq, void* dev)
{
tasklet_schedule(&tasklet);
return IRQ_HANDLED;
}
static int __init myirq_init(void)
{
int ret;
//0.初始化tasklet
tasklet_setup(&tasklet,irq_tasklet);
// 1.获取到设备树的节点
node = of_find_compatible_node(NULL, NULL, "hqyj,irq");
if (node == NULL) {
printk("get node error\n");
return -ENODATA;
}
// 2.解析得到软中断号
irqno = irq_of_parse_and_map(node, 0);
if (irqno == 0) {
printk("get irq number error\n");
return -EAGAIN;
}
// 3.注册中断
ret = request_irq(irqno, key_irq, IRQF_TRIGGER_FALLING,
"key1", NULL);
if (ret) {
printk("reqest irq error\n");
return ret;
}
return 0;
}
static void __exit myirq_exit(void)
{
free_irq(irqno, NULL);
}
module_init(myirq_init);
module_exit(myirq_exit);
MODULE_LICENSE("GPL");
3.3 工作队列
在内核启动的时候会启动一个 events 的线程,这个线程默认处于休眠的状态。在这个线程内部维护的队列,如果队列中被放入一个 work,这个线程被唤醒之后就开始解析这个队列(回调work 中的底半部处理函数)。工作队列工作在进程上下文,可以脱离中断执行。也没有个数限制。
3.3.1工作队列的API
1.分配对象
struct work_struct {
atomic_long_t data; //传递的数据
struct list_head entry; //构成队列
work_func_t func; //底半部处理函数
//typedef void (*work_func_t)(struct work_struct *work);
};
struct work_struct work;
2.对象初始化
INIT_WORK(&work, 底半部处理函数);
3.调用执行
schedule_work(struct work_struct *work)
4.取消工作队列的执行
cancel_work_sync(&work);
3.3.2 工作队列实例
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
#include <linux/delay.h>
// myirq{
// compatilbe = "hqyj,irq";
// interrupt-parent = <&gpiof>;
// interrupts = <9 0>, <7 0>,<8 0>;
// key = <&gpiof 9 0>,<&gpiof 7 0>,<&gpiof 8 0>;
// };
struct device_node* node;
unsigned int irqno;
int gpiono;
struct work_struct work;
void irq_work(struct work_struct *work)
{
int i=30;
while(--i){
printk("i = %d\n",i);
mdelay(1000);
}
}
irqreturn_t key_irq(int irq, void* dev)
{
schedule_work(&work);
return IRQ_HANDLED;
}
static int __init myirq_init(void)
{
int ret;
//0.初始化工作队列
INIT_WORK(&work,irq_work);
// 1.获取到设备树的节点
node = of_find_compatible_node(NULL, NULL, "hqyj,irq");
if (node == NULL) {
printk("get node error\n");
return -ENODATA;
}
// 2.解析得到软中断号
irqno = irq_of_parse_and_map(node, 0);
if (irqno == 0) {
printk("get irq number error\n");
return -EAGAIN;
}
// 3.注册中断
ret = request_irq(irqno, key_irq, IRQF_TRIGGER_FALLING,
"key1", NULL);
if (ret) {
printk("reqest irq error\n");
return ret;
}
return 0;
}
static void __exit myirq_exit(void)
{
free_irq(irqno, NULL);
}
module_init(myirq_init);
module_exit(myirq_exit);
MODULE_LICENSE("GPL");
底半部 | 上下文 |
---|---|
软中断 | 中断 |
tasklet | 中断 |
工作队列 | 进程 |