一、linux内核中断
1.目的:
用于对设备不用进行轮询访问,而是当设备事件发生后主动通知内核,内核再去访问设备。
2.linux内核中断实现过程框图
3.中断子系统API
1.解析中断相关的设备树节点
struct device_node *of_find_compatible_node( struct device_node *from, const char *type, const char *compat)
2.解析设备中断的软中断号
#include<linux/of_irq.h>
unsigned int irq_of_parse_and_map(struct device_node *node, int index)
3.将中断注册进内核
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
4.注销中断
void *free_irq(unsigned int irq, void *dev_id)
二、中断底半部
1.概念
将一个中断处理得分过程分为了中断顶半部和中断底半部,中断顶半部就是通过 request_irq注册的中断处理函数,在顶半部中主要进行一些重要的、不耗时的任务;中断底半部则是区进行一些耗时,不紧急的任务。在执行中断底半部时,会将执行中断顶半部时关闭的中断线启用以及抢占开启,这样进程以及其他的中断就可以正常的工作了。
2.实现机制
softirq(软中断)、tasklet以及工作队列
3.API
1)分配一个tasklet对象
struct tasklet_struct tasklet;
2) 初始化taklet对象
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data)
功能:当底半部处理函数是func类型时用此函数初始化对象
void tasklet_setup(struct tasklet_struct *t, void (*callback)(struct tasklet_struct *))
功能:当底半部处理函数是callback类型时用此函数初始化对象
3)开启底半部
void tasklet_schedule(struct tasklet_struct *t)
三、工作队列
1.概述
工作队列用于底半部原理:内核中存在工作队列对应的内核线程,这个线程从内核启动就存在,处于休眠态。当有任务需要执行时,只需要将任务提交到工作队列中,然后唤醒休眠的内核线程,由内核线程去处理对应的任务即可。工作队列既可以用于中断,也可以用于进程。
2.API
1)分配工作队列项
struct work_struct work;
2)初始化队列项
INIT_WORK(&work,底半部函数指针);
3)开启底半部
bool schedule_work(struct work_struct *work)
注册三个按键中断实例
mykey_irq.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
/* myirq{
compatible="hqyj,myirq";
interrupt-parent=<&gpiof>;
interrupts=<9 0>,<7 0>,<8 0>;
};*/
unsigned int irqno[3];
struct device_node *dnode;
//定义中断处理函数
irqreturn_t key_handler(int irq, void *dev)
{
int which=(int)dev;
switch(which)
{
case 0:
printk("KEY1_INTERRUPT\n");
break;
case 1:
printk("KEY2_INTERRUPT\n");
break;
case 2:
printk("KEY3_INTERRUPT\n");
break;
}
return IRQ_HANDLED;
}
static int __init mycdev_init(void)
{
//解析按键的设备树节点
dnode=of_find_compatible_node(NULL,NULL,"hqyj,myirq");
if(dnode==NULL)
{
printk("解析设备树节点失败\n");
return -ENXIO;
}
printk("解析设备树节点成功\n");
//解析按键的软中断号
int i;
for(i=0;i<3;i++)
{
irqno[i]=irq_of_parse_and_map(dnode,i);
if(!irqno[i])
{
printk("解析按键1软中断号失败\n");
return -ENXIO;
}
printk("解析按键软中断号成功%d\n",irqno[i]);
//注册 按键中断
int ret=request_irq(irqno[i],key_handler,IRQF_TRIGGER_FALLING,"key_int",(void *)i);
if(ret<0)
{
printk("注册按键中断失败\n");
return ret;
}
}
printk("注册按键中断成功\n");
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");
测试现象:
中断底半部实例
mykey_tasklet.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
/* myirq{
compatible="hqyj,myirq";
interrupt-parent=<&gpiof>;
interrupts=<9 0>,<7 0>,<8 0>;
};*/
unsigned int irqno[3];
struct device_node *dnode;
struct tasklet_struct 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 which=(int)dev;
switch(which)
{
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)
{
//解析按键的设备树节点
dnode=of_find_compatible_node(NULL,NULL,"hqyj,myirq");
if(dnode==NULL)
{
printk("解析设备树节点失败\n");
return -ENXIO;
}
printk("解析设备树节点成功\n");
//解析按键的软中断号
int i;
for(i=0;i<3;i++)
{
irqno[i]=irq_of_parse_and_map(dnode,i);
if(!irqno[i])
{
printk("解析按键1软中断号失败\n");
return -ENXIO;
}
printk("解析按键软中断号成功%d\n",irqno[i]);
//注册 按键中断
int ret=request_irq(irqno[i],key_handler,IRQF_TRIGGER_FALLING,"key_int",(void *)i);
if(ret<0)
{
printk("注册按键中断失败\n");
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");