引入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>;
};
当一个中断被触发以后,会关闭抢占,一个单核CPU处理当前中断任务时,当前CPU无法处理其他任务,所有的CPU都会关闭当前中断线。在这种情况下,如果一个中断中有延时、耗时甚至休眠操作,最终会导致整个系统功能的延迟。所以一般在中断处理过程中不允许有延时、耗时甚至休眠的操作。但是有的时候又必须在中断的处理程序中进行一些耗时任务。
这样就会产生一个冲突:中断不允许有耗时但是有时候需要耗时的冲突;
将一个中断处理得分过程分为了中断顶半部和中断底半部,中断顶半部就是通过 request_irq注册的中断处理函数,在顶半部中主要进行一些重要的、不耗时的任务;中断底半部则是区进行一些耗时,不紧急的任务。在执行中断底半部时,会将执行中断顶半部时关闭的中断线启用以及抢占开启,这样进程以及其他的中断就可以正常的工作了。
实现机制有softirq(软中断)、tasklet以及工作队列;
当顶半部即将执行结束时开启软中断,在软中断处理函数中取处理当前中断里的耗时任务。软中断存在数量限制(32个),一般留给内核开发者使用。
内核中存在工作队列对应的内核线程,这个线程从内核启动就存在,处于休眠态。当有任务需要执行时,只需要将任务提交到工作队列中,然后唤醒休眠的内核线程,由内核线程去处理对应的任务即可。工作队列既可以用于中断,也可以用于进程。
- #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");
现象:
- #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");
现象: