一.引入linux内核中断目的
引入linux内核中断之前,内核访问设备要不断轮询访问;
引入linux内核中断便于内核对设备的访问,当设备事件发生后主动通知内核,内核再去访问设备;
二.linux内核中断实现过程框图
根据软中断号回调当前中断的 中断函数 过程:
中断注册进内核之后,中断信息会保存在一个struct irq_desc对象中,内核中存在一个struct irq_desc类型的数组,软中断号就是数组的下标,数组中每一个成员都是保存了一个注册进内核的设备中断信息,类型为struct irqaction,struct irqaction对象里有中断处理函数的函数指针,指向自定义中断处理函数;
三.添加按键的设备树节点
在stm32mp157a-fsmp1a.dts文件的根节点内部添加如下内容:
myirq
{
compatible="myirq"; interrupt-parent=<&gpiof>; interrupts=<9 0>,<7 0>,<8 0>;
};
四.中断底半部
1.引入目的(解决问题)
当一个中断被触发以后,会关闭抢占,一个单核CPU处理当前中断任务时,当前CPU无法处理其他任务,所有的CPU都会关闭当前中断线。在这种情况下,如果一个中断中有延时、耗时甚至休眠操作,最终会导致整个系统功能的延迟。所以一般在中断处理过程中不允许有延时、耗时甚至休眠的操作。但是有的时候又必须在中断的处理程序中进行一些耗时任务。
这样就会产生一个冲突:中断不允许有耗时但是有时候需要耗时的冲突;
2.解决上面冲突,引入中断底半部
将一个中断处理得分过程分为了中断顶半部和中断底半部,中断顶半部就是通过 request_irq注册的中断处理函数,在顶半部中主要进行一些重要的、不耗时的任务;中断底半部则是区进行一些耗时,不紧急的任务。在执行中断底半部时,会将执行中断顶半部时关闭的中断线启用以及抢占开启,这样进程以及其他的中断就可以正常的工作了。
3.中断底半部的实现机制
实现机制有softirq(软中断)、tasklet以及工作队列;
1)软中断机制
当顶半部即将执行结束时开启软中断,在软中断处理函数中取处理当前中断里的耗时任务。软中断存在数量限制(32个),一般留给内核开发者使用。
2)tasklet机制
- 基于软中断工作原理进行的;
- tasklet没有使用数量的限制;
- 中断底半部可以进行耗时任务,但不可以进行休眠操作;
- 工作于中断上下文,不用于进程上下文;
3)工作队列机制
内核中存在工作队列对应的内核线程,这个线程从内核启动就存在,处于休眠态。当有任务需要执行时,只需要将任务提交到工作队列中,然后唤醒休眠的内核线程,由内核线程去处理对应的任务即可。工作队列既可以用于中断,也可以用于进程。
4)tasklet机制驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
struct device_node *dnode;
unsigned int irqno[3];
int i;
struct tasklet_struct tasklet; //分配tasklet对象
//定义底半部处理函数
void key_callback(struct tasklet_struct *t)
{
int i;
//耗时事件
for(i=0; i<100; i++)
{
printk("i=%d\n",i);
}
}
// 定义中断处理函数
irqreturn_t key_handler(int irq, void *dev)
{
int witch = (int)dev;
switch (witch)
{
case 0:
printk("KEY1_INTERRUPT\n");
break;
case 1:
printk("KEY2_INTERRUPT\n");
break;
case 2:
printk("KEY3_INTERRUPT\n");
break;
}
//开启底半部
tasklet_schedule(&tasklet);
return IRQ_HANDLED;
}
static int __init mycdev_init(void)
{
// 1解析按键的设备树节点
dnode = of_find_compatible_node(NULL, NULL, "myirq");
if (dnode == NULL)
{
printk("解析设备树节点失败\n");
return -ENXIO;
}
printk("解析设备树节点成功\n");
// 2解析按键的软中断号
for (i = 0; i < 3; i++)
{
irqno[i] = irq_of_parse_and_map(dnode, i);
if (!irqno[i])
{
printk("解析按键%d软中断号失败\n", i);
return -ENXIO;
}
printk("解析按键%d软中断号成功%d\n", i, irqno[i]);
// 3注册按键1中断
int ret = request_irq(irqno[i], key_handler, IRQF_TRIGGER_FALLING, "key_int", (void *)i);
if (ret < 0)
{
printk("注册按键%d中断失败\n", i);
return ret;
}
}
printk("注册按键中断成功\n");
//初始化底半部
tasklet_setup(&tasklet,key_callback);
return 0;
}
static void __exit mycdev_exit(void)
{
// 注销中断
int i;
for (i = 0; i < 3; i++)
{
free_irq(irqno[i], (void *)i);
}
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
现象:
5)工作队列机制驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
struct device_node *dnode;
unsigned int irqno[3];
int i;
struct work_struct work; //分配工作队列对象
//定义底半部处理函数
void key_work(struct work_struct *t)
{
int i;
//耗时事件
for(i=0; i<100; i++)
{
printk("i=%d\n",i);
}
}
// 定义中断处理函数
irqreturn_t key_handler(int irq, void *dev)
{
int witch = (int)dev;
switch (witch)
{
case 0:
printk("KEY1_INTERRUPT\n");
break;
case 1:
printk("KEY2_INTERRUPT\n");
break;
case 2:
printk("KEY3_INTERRUPT\n");
break;
}
//开启底半部
schedule_work(&work);
return IRQ_HANDLED;
}
static int __init mycdev_init(void)
{
// 1解析按键的设备树节点
dnode = of_find_compatible_node(NULL, NULL, "myirq");
if (dnode == NULL)
{
printk("解析设备树节点失败\n");
return -ENXIO;
}
printk("解析设备树节点成功\n");
// 2解析按键的软中断号
for (i = 0; i < 3; i++)
{
irqno[i] = irq_of_parse_and_map(dnode, i);
if (!irqno[i])
{
printk("解析按键%d软中断号失败\n", i);
return -ENXIO;
}
printk("解析按键%d软中断号成功%d\n", i, irqno[i]);
// 3注册按键1中断
int ret = request_irq(irqno[i], key_handler, IRQF_TRIGGER_FALLING, "key_int", (void *)i);
if (ret < 0)
{
printk("注册按键%d中断失败\n", i);
return ret;
}
}
printk("注册按键中断成功\n");
//初始化队列项
INIT_WORK(&work,key_work);
return 0;
}
static void __exit mycdev_exit(void)
{
// 注销中断
int i;
for (i = 0; i < 3; i++)
{
free_irq(irqno[i], (void *)i);
}
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
现象: