啥叫中断?
就是指cpu在执行过程中,出现了某些突发事件时CPU必须暂停执行当前的程序,转去处理突发事件,处理完毕后CPU有返回原程序被中断的位置并继续执行。
中断的分法不懂,分类就不同,向什么内外部中断,可/不可屏蔽中断…等等乱七八糟一大堆,我这里要说明的一点是按照中断入口跳转方法的不同,可分为向量中断和非向量中断。采用向量中断的CPU通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行。不同的中断号有不同的中断地址(即入口)。而非向量中断的多个中断共享一个入口地址。进入后根据软件判断中断标志来识别具体是哪个中断。也就是说,向量中断是由硬件提供中断服务程序入口地址,非向量中断由软件提供中断服务程序入口地址。
Linux中断处理架构
设备的中断会打断内核中进程的正常调度和运行,会影响系统的性能。为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点,Linux将中断处理程序分解成两个半部:顶半部和底半部。其中顶半部尽可能完成尽可能少的比较紧急的功能。而底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断。
在linux设备驱动中,提供了一系列函数来帮助设备实现中断的相关操作:
1)设备申请中断
int request_irq(unsigned int irq, //irq是要申请的中断号
void (*handler)(int irq, void *dev_id, struct pt_regs * *regs),//回调函数,中断发生时,系统会调用该函数,
unsigned long irqflags,
const char *devname,
void *dev_id);
其中irqflags是中断处理的属性,若设置为SA_INTERRUPT,则表示中断处理程序是快速处理程序,它被调用时屏蔽所有中断。若设置为SA_SHIRQ,则表示多个设备共享中断,dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL.
该函数返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回EBUSY表示中断已经被占用且不能共享。
2)释放中断
free_irq(unsigned int irq, void *dev_id);
3)使能和屏蔽中断
void disable_irq(int irq); //这个会立即返回
void disable_irq_nosync(int irq);//等待目前的中断处理完成再返回。
void enable_irq(int irq);
上述三个函数作用于可编程中断处理器,因此对系统内所有的CPU都生效。
void local_irq_save(unsigned long flags);//会将目前的中断状态保留在flags中
void local_irq_disable(void);//直接中断
这两个将屏蔽本CPU内的所有中断。对应的上边两个中断的方法如下
void local_irq_restore(unsigned long flags);
void local_irq_enable(void);
Linux系统中断的底半部机制实现
这主要有tasklet,工作队列,软中断:
1)tasklet:使用比较简单,如下:
void my_tasklet_function(unsigned long); //定义一个处理函数
DECLARE_TASKLET(my_tasklet, my_tasklet_function, data); //定义了一个名叫my_tasklet的tasklet并将其与处理函数绑定,而传入参数为data
在需要调度tasklet的时候引用一个tasklet_schedule()函数就能使系统在适当的时候进行调度运行:tasklet_schedule(&my_tasklet);
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_irq);来在系统在适当的时候需要调度时使用运行。
3)软中断:使用软件方式模拟硬件中断的概念,实现宏观上的异步执行效果,tasklet也是基于软中断实现的。
在Linux内核中,用softirq_action结构体表征一个软中断,这个结构体中包含软中断处理函数指针和传递给函数的参数,使用open_softirq()可以注册软中断对应的处理函数,而raise_softirq()函数可以触发一个中断。
软中断和tasklet仍然运行与中断上下文,而工作队列则运行于进程上下文。因此,软中断和tasklet的处理函数不能休眠,但工作队列是可以的。
local_bh_disable()和local_bh_enable()是内核用于禁止和使能软中断和tasklet底半部机制的函数。
tasklet使用模版:
void xxx_do_tasklet(unsigned long); DECLARE_TASKLET(XXX_tasklet, xxx_do_tasklet, 0); void xxx_do_tasklet(unsigned long) //中断处理底半部 { ..... } irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs) //中断处理顶半部 { ... tasklet_schedule(&xxx_tasklet); } int __init xxx_init(void) //设备驱动模块加载函数 { .. result= request_irq(xxx_irq, xxx_interrupt, SA_INTERRUPT, "XXX",NULL); //申请中断 ... } void __exit xxx_exit(void) //设备驱动卸载模块 { .. free_irq(xxx_irq, xxx_interrupt); //释放中断 .. }
工作队列模版:
struct work_struct xxx_wq; void xxx_do_work(unsigned long); void xxx_do_work(unsigned long) //中断处理底半部 { ..... } irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs) //中断处理顶半部 { ... schedule_work(&xxx_wq); } int xxx_init(void) //设备驱动模块加载函数 { .. result= request_irq(xxx_irq, xxx_interrupt, SA_INTERRUPT, "XXX",NULL); //申请中断 ... INIT_WORK(&xxx_wq, (void (*)(void *))xxx_do_work, NULL); ... } void __exit xxx_exit(void) //设备驱动卸载模块 { .. free_irq(xxx_irq, xxx_interrupt); //释放中断 .. }
软中断 一般驱动编写者不宜使用....
中断共享模板
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; } int __init xxx_init(void) //设备驱动模块加载函数 { .. result= request_irq(xxx_irq, xxx_interrupt, SA_SHIRQ, "XXX",xxx_dev); //申请共享中断 ... } void __exit xxx_exit(void) //设备驱动卸载模块 { .. free_irq(xxx_irq, xxx_interrupt); //释放中断 .. }
注意异步通信机制的信号也是类似我们的中断的,
硬中断, 软中断, 信号的区别:
硬中断 : 外部设备对cpu的中断
软中断: 中断底半部的一种处理机制 (对于ARM的软中断为swi, 主要指的是由软件指令引发的中断和我们这里说的是两个完全不同的概念)
信号: 由内核或者其他进程对某个进程的中断
本文章参考来源:
1. linux设备驱动开发详解
2. http://www.360doc.com/content/11/1104/23/1317564_161775357.shtml