中断是cpu在执行过程中,出现了某些突发情况,cpu必须暂停当前的任务,去处理紧急的事件,处理结束后继续处理刚才暂停的任务。
内部中断:来源于cpu内部,例如软件中断指令、溢出、除法错误等
外部中断:来源于cpu外部,外设提出请求
可屏蔽中断:中断被屏蔽后不再相应
不可屏蔽中断:NMI
向量中断:cpu通常为不同的中断分配不同的中断入口地址,当检测到某个中断来临,自动跳转到该中断对应的地址执行
非向量中断:多个中断共享一个入口地址,中断响应后,要通过软件判断具体是哪个中断,软件中断较多。
irq_handler()
{
int int_src = read_int_status();
switch (int_src){
case DEV_A:
dev_a_handler();
break;
case DEV_B:
DEV_b_handler();
break;
default:
break;
}
}
ARM多核处理中最常用的GIC(generic interrupt controller),支持3种类型中断:
1. SGI(Software generic interrupt):软件产生的中断,可以用于多核之间的通信,一个cpu可以通过写GIC的寄存器给另外一个cpu产生中断。
2. PPI(Private peripheral interrupt):某个CPU私有外设的中断,这类外设的中断只能发给绑定的cpu
3. SPI(Shared peripheral interrupt):共享外设中断,这类外设的中断可以绑定到任意的cpu
extern int irq_set_aff inity (unsigned int irq, const struct cpumask *m);
例如irq_set_aff inity(irq, cpumask_of(i)); 把中断绑定到cpui上,由这个cpu处理
设备的中断会打断内核进程的正常调度,要求中断处理函数要尽可能的短小,但是实际上中断处理函数可能需要很长时间,linux将中断处理函数分为上下部分。
1.中断上半部(Top Half) :处理比较紧急短小的事情,比如读取寄存器等。 清除中断标志以后就“登记中断”工作,意味着将中断下半部处理程序挂到下半部执行队列中。不可被中断打断
2.中断下半部(Bottom Half) :处理比较耗时的事情。可以被中断打断。
调度
——-中断——->中断上半部——->中断下半部
cat /proc/interrupt 查看中断信息
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
irq :中断号
handler:中断处理函数
typedef irqreturn_t (*irq_handler_t)(int, void *);
flags:中断处理属性
触发方式:
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010
处理方式:
#define IRQF_SHARED 0x00000080 //表示多个设备共享中断
#define IRQF_PROBE_SHARED 0x00000100
#define __IRQF_TIMER 0x00000200
#define IRQF_PERCPU 0x00000400
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000
#define IRQF_ONESHOT 0x00002000
#define IRQF_NO_SUSPEND 0x00004000
#define IRQF_FORCE_RESUME 0x00008000
#define IRQF_NO_THREAD 0x00010000
#define IRQF_EARLY_RESUME 0x00020000
#define IRQF_COND_SUSPEND 0x00040000
name:中断名称,在/proc/interrupt 中看到的名称
dev:是要传递给中断服务程序的私有数据,一般设置为中断设备的结构体或者指针NULL
return:
0 表示成功
-EINVAL 表示中断号无效或者处理函数指针为NULL
-EBUSY 表示中断已经被占用且不能共享
devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
//devm_开头的api表示系统管理的资源,系统自动回收,不需要使用free_irq,就像java中的内存回收机制一样
void free_irq(unsigned int irq, void *dev_id)
屏蔽一个中断源
void disable_irq(int irq) 立刻返回
void disable_irq_nosync(int irq) 等待中断函数执行结束返回
void enable_irq(int irq)
屏蔽本cpu内所有中断
#define local_irq_save(flags) ... //禁止中断前,将目前cpu的中断状态保存在flags中 flags为unsigned log
void local_irq_disable(void) //直接禁止中断而不保存
对应恢复如下
#define local_irq_restore(flags) ...
void local_irq_enable(void)
实现机制:tasklet 工作队列 软中断 线程化irq
执行上下文:软中断
使用: 定义-关联-使用
1. void my_tasklet_func(unsigned long) //定义一个处理函数
2. DECLARE_TASKLET(my_tasklet, my_tasklet_func, data) //定义一个tasklet结构体,与my_tasklet_func(data)函数关联
3.tasklet_schedule(&my_tasklet);
执行上下文:进程上下文,可睡眠、调度
使用: 定义-关联-使用
1.struct work_struct my_wq; //定义一个工作队列
2.void my_wq_func(struct work_struct *work) //定义一个处理函数
3.INIT_WORK(&my_wq, my_wq_func) //绑定
4.schedule_work(&my_wq) //使用工作队列
执行上下文:软中断
使用:
softirq_atcion //结构体表示一个软中断
open_softirq() //注册软中断对应的处理函数
local_bh_disable()
local_bh_enable()
//内核中用于禁止和使能软中断以及tasklet底半步机制的函数
int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id) //中断上下文
int devm_request_threaded_irq(struct device *dev, unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id) //进程上下文
//上半部函数 handler
//下半部函数 thread_fn
使用方法:当上半部返回 IRQ_WAKE_THREAD 时,内核调度会执行thread_fn函数
request_threaded_irq和devm_request_threaded_irq支持标记IRQF_ONESHOT
内核会帮助我们在中断上下文时屏蔽该中断号,在执行thread_fn的时候重新使能该中断号,对于在上半部无法清除中断的情况,IRQF_ONESHOT有特别的用处
linux支持多个设备共享一个中断线的情况。
共享中断的设备在申请中断时要加上 IRQF_SHARED标志
1.1 该中断未被申请
1.2 该中断已经被申请,但是被申请的都是以IRQF_SHARED为标志的
中断到来时,会编译执行共享此中断的所有中断处理函数,直到某个函数返回IRQ_HANDLED.
在中断处理函数顶半部中,应根据硬件寄存器中的信息比对传入的dev_id参数,迅速判断是否为本设备的中断,如果不是,应该迅速返回IRQ_NONE.
接下来三篇文章讲一次介绍4种使用方法和示例代码