1、简介:
中断的概念:
指CPU在执行过程中,出现某些突发事件急待处理,CPU暂停执行当前程序,转去处理突发事件,处理完后CPU又返回原程序被中断的位置继续执行。
中断的分类:
内部中断和外部中断:
内部中断:中断源来自CPU内部(软件中断指令、溢出、触发错误等);
外部中断:中断源来自CPU外部,由外设提出请求。
屏蔽中断和不可屏蔽中断:
可屏蔽中断:可以通过屏蔽字被屏蔽,屏蔽后,该中断不再得到响应;
不可屏蔽中断:不能被屏蔽。
向量中断和非向量中断:
向量中断:CPU通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行;
非向量中断:多个中断共享一个入口地址。进入该入口地址后再通过软件判断中断标志来识别具体哪个是中断。
也就是说向量中断由软件提供中断服务程序入口地址,非向量中断由软件提供中断入口地址。
小知识:Linux中查看/proc/interrupts文件可以获得系统中断的统计信息。如下图所示:
第一列是中断号 第二列是向CPU产生该中断的次数
2、Linux中断编程:
2.1、框架:
Linux将中断分为:顶半部(top half)和底半部(bottom half)
顶半部: 完成尽可能少的比较紧急的功能,它往往只是简单的读取寄存器中的中断状态并清除中断标志后就进行“登记中断”(也就是将底半部处理程序挂在到设备的底半部执行队列中)的工作
特点:响应速度快底半部: 中断处理的大部分工作都在底半部,它几乎做了中断处理程序的所有事情。
特点:处理相对来说不是非常紧急的事件
内核维护了一个中断信号线的注册表,该注册表类似于I/O端口的注册表。驱动模块在使用中断前,要先请求一个中断通道(或者中断请求IRQ),然后在使用后释放该通道。在头文件<linux/interrupt.h>中申明了申请和释放IRQ的接口。
2.1.1.申请和释放中断
申请中断:
int request_irq(unsigned int irq,irq_handler_t handler,unsigned long irqflags,const char *devname,void *dev_id)
参数介绍:
irq 是要申请的硬件中断号
handler 是向系统登记的中断处理程序(顶半部),是一个回调函数,中断发生时,系统调用它,将dev_id参数传递给它。
irqflags: 是中断处理的属性,可以指定中断的触发方式和处理方式:
触发方式: IRQF_TRIGGER_RISING、IRQF_TRIGGER_FALLING、IRQF_TRIGGER_HIGH、IRQF_TRIGGER_LOW
处理方式: IRQF_DISABLE表明中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断;IRQF_SHARED 表示多个设备共享中断
dev_id 在中断共享时会用到,一般设置为NULL
返回值:为0表示成功,返回-EINVAL表示中断号无效,返回-EBUSY表示中断已经被占用,且不能共享
顶半部的handler的类型irq_handler_t定义为
typedef irqreturn_t (*irq_handler_t)(int,void*);
typedef int irqreturn_t;
2.1.2.释放IRQ
有请求当然就有释放了
void free_irq(unsigned int irq,void *dev_id);
参数定义与request_irq类似
2.1.3.使能和屏蔽中断
void disable_irq(int irq);//等待目前中断处理完成(最好别在顶板部使用,你懂得)
void disable_irq_nosync(int irq);//立即返回
void enable_irq(int irq);//
2.1.4.屏蔽本CPU内所有中断:
#define local_irq_save(flags)...//禁止中断并保存状态
void local_irq_disable(void);//禁止中断,不保存状态
2.2、顶半部和底半部的实现机制
底半部机制:
简介:底半部机制主要有tasklet、工作队列、软中断
2.2.1.底半部实现方法之一tasklet
(1)我们需要定义tasklet机器处理器并将两者关联
例如:
void my_tasklet_func(unsigned long);/*定义一个处理函数*/
DECLARE_TASKLET(my_tasklet,my_tasklet_func,data);
/*上述代码定义了名为my_tasklet的tasklet并将其与my_tasklet_func()函数绑定,传入的参数为data*/
(2)调度
tasklet_schedule(&my_tasklet);
//使用此函数就能在适当的时候进行调度运行
tasklet使用模板
/*定义tasklet和底半部函数并关联*/
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);
//将xxx_tasklet与xxx_do_tasklet绑定,传入参数0
/*中断处理底半部*/
void xxx_do_tasklet(unsigned long)
{
...
}
/*中断处理顶半部*/
irqreturn_t xxx_interrupt(int irq,void *dev_id)
{
...
tasklet_schedule(&xxx_tasklet);//调度底半部
...
}
/*设备驱动模块加载函数*/
int __init xxx_init(void)
{
...
/*申请中断*/
/**/
result = request_irq(xxx_irq,xxx_interrupt,IRQF_DISABLED,"xxx",NULL);
...
return IRQ_HANDLED;
}
/*设备驱动模块卸载函数*/
void __exit xxx_exit(void)
{
...
/*释放中断*/
free_irq(xxx_irq,xxx_interrupt);
...
}
2.2.2.底半部实现方法之二---工作队列
使用方法和tasklet类似
相关操作:
struct work_struct my_wq; /*定义一个工作队列*/
void my_wq_func(unsigned long);/*定义一个处理函数*/
通过INIT_WORK()可以初始化这个工作队列并将工作队列与处理函数绑定
INIT_WORK(&my_wq,(void (*)(void *))my_wq_func,NULL);/*初始化工作队列并将其与处理函数绑定*/
schedule_work(&my_wq);/*调度工作队列执行*/
/*工作队列使用模板*/
/*定义工作队列和关联函数*/
struct work_struct xxx_wq(unsigned long);
void xxx_do_work(unsigned long);
/*中断处理底半部*/
void xxx_do_work(unsigned long)
{
...
}
/*中断处理顶半部*/
irqreturn_t xxx_interrupt(int irq,void *dev_id)
{
...
schedule_work(&my_wq);//调度底半部
...
return IRQ_HANDLED;
}
/*设备驱动模块加载函数*/
int xxx_init(void)
{
...
/*申请中断*/
result = request_irq(xxx_wq,xxx_interrupt,IRQF_DISABLED,"xxx",NULL);
...
/*初始化工作队列*/
INIT_WORK(&my_wq,(void (*)(void *))xxx_do_work,NULL);
}
/*设备驱动模块卸载函数*/
void xxx_exit(void)
{
...
/*释放中断*/
free_irq(xxx_irq,xxx_interrupt);
...
}
2.3、中断共享
中断共享是指多个设备共享一根中断线的情况
中断共享的使用方法:
(1).在申请中断时,使用IRQF_SHARED标识
(2).在中断到来时,会遍历共享此中断的所有中断处理程序,直到某一个函数返回
IRQ_HANDLED,在中断处理程序顶半部中,应迅速根据硬件寄存器中的信息参照dev_id参数
判断是否为本设备的中断,若不是立即返回IR1_NONE
/*共享中断编程模板*/
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
...
int status = read_int_status();/*获知中断源*/
if(!is_myint(dev_id,status))/*判断是否为本设备中断*/
return IRQ_NONE;/*不是本设备中断,立即返回*/
/*是本设备中断,进行处理*/
...
return IRQ_HANDLED;/*返回IRQ_HANDLER表明中断已经被处理*/
}
/*设备模块加载函数*/
int xxx_init(void)
{
...
/*申请共享中断*/
result = request_irq(sh_irq,xxx_interrupt,IRQF_SHARE,"xxx",xxx_dev);
...
}
/*设备驱动模块卸载函数*/
void xxx_exit()
{
...
/*释放中断*/
free_irq(xxx_irq,xxx_interrupt);
...
}