写过按键裸机程序的同学都知道按键的读取一般都采用中断的方式,如果采用轮询的方式真的是太浪费CPU资源了。下面将介绍Linux中的中断处理程序。
1、裸机中断处理流程回顾
对所有芯片而言,中断都有一个通用的入口,进入后会保存保存当前的环境把它压栈,然后查询中断向量表,根据中断源去调用对应的中断处理程序。
因此总结出以下3点:
1、中断统一入口
2、事先注册中断处理程序
3、根据中断源编号查询中断向量表,调用中断处理程序
2、Linux中断处理流程分析
在Linux内核代码里也有统一的入口,这个函数叫做__irq_svc函数,这个函数可以在内核代码的entry-armv.S文件中找到。
这个函数保存现场之后会调用一个irq_handler的宏,
/*
* Interrupt handling. Preserves r7, r8, r9
*/
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r5, =handle_arch_irq
mov r0, sp
ldr r5, [r5]
adr lr, BSYM(9997f)
teq r5, #0
movne pc, r5
#endif
arch_irq_handler_default
irq_handle这个宏又由arch_irq_default这个宏完成,找出这个宏,它在entry-macro-multi.S中实现。
/*
* Interrupt handling. Preserves r7, r8, r9
*/
.macro arch_irq_handler_default
get_irqnr_preamble r5, lr
1: get_irqnr_and_base r0, r6, r5, lr
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, BSYM(1b)
bne asm_do_IRQ
arch_irq_default函数完成中断向量表的查询
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
mov \base, #S3C24XX_VA_IRQ
@@ try the interrupt offset register, since it is there
ldr \irqstat, [ \base, #INTPND ]
teq \irqstat, #0
beq 1002f
ldr \irqnr, [ \base, #INTOFFSET ]
mov \tmp, #1
tst \irqstat, \tmp, lsl \irqnr
bne 1001f
@@ the number specified is not a valid irq, so try
@@ and work it out for ourselves
mov \irqnr, #0 @@ start here
@@ work out which irq (if any) we got
总结如下:
1、进入同一的入口
2、拿到产生中断源的编号
3、根据中断号找到irq_desc结构,这个结构中有一个action选项,这个选项中填写的就是用户注册的中断服务器函数。
3、Linux中断处理程序设计
由上面的分析可以知道,对应驱动程序的中断函数需要完成:
1、中断服务函数的编写
2、在内核中注册中断服务函数
3、驱动不用时注销中断服务函数
3.1中断注册
request_irq函数用于注册中断。
int request_irq(unsigned int irq,
void (*handler)(int, void*, structpt_regs *),
unsigned long flags,
const char *devname,
void *dev_id)
返回值:
返回0表示成功,或者返回一个错误码
参数:
unsigned int irq 中断号。
void (*handler)(int,void *) 中断处理函数。
unsigned long flags 与中断管理有关的各种选项。
const char * devname 设备名
void *dev_id 共享中断时使用。
void (*handler)(int,void *) 中断处理函数。
unsigned long flags 与中断管理有关的各种选项。
const char * devname 设备名
void *dev_id 共享中断时使用。
在flags参数中,可以选择一些与中断管理有关的选项,如:
• IRQF_DISABLED(SA_INTERRUPT) 如果设置该位,表示是一个“快速”中断处理程序;如果没有设置这位,那么是一个“慢速”中断处理程序。
• IRQF_SHARED(SA_SHIRQ) 该位表明该中断号是多个设备共享的。比如串口和网卡都可以使用这个中断号,它们挂在一个链表下,Linux检测到这个中断号时会把每个中断服务函数都调用一遍,因此在中断服务函数中需要判断自己是否产生了中断,如果没有产生需要退出。
• IRQF_DISABLED(SA_INTERRUPT) 如果设置该位,表示是一个“快速”中断处理程序;如果没有设置这位,那么是一个“慢速”中断处理程序。
• IRQF_SHARED(SA_SHIRQ) 该位表明该中断号是多个设备共享的。比如串口和网卡都可以使用这个中断号,它们挂在一个链表下,Linux检测到这个中断号时会把每个中断服务函数都调用一遍,因此在中断服务函数中需要判断自己是否产生了中断,如果没有产生需要退出。
快/慢速中断的主要区别在于:快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。换句话说,也就是“开启中断”标志位(处理器IF)在运行快速中断处理程序时是关闭的,因此在服务该中断时,不会被其他类型的中断打断;而调用慢速中断处理时,其它类型的中断仍可以得到服务。
3.2中断处理程序
中断处理程序的特别之处是在中断上下文中运行的,它的行为受到某些限制:
1.不能使用可能引起阻塞的函数 如果阻塞了所有的中断都不能得到及时的处理
2.不能使用可能引起调度的函数
1.不能使用可能引起阻塞的函数 如果阻塞了所有的中断都不能得到及时的处理
2.不能使用可能引起调度的函数
中断处理程序编写流程:
1、检查设备是否产生中断
2、清楚中断标志位
3、完成相应的硬件操作
3.3注销中断
当设备不再需要使用中断时(通常在驱动卸载时), 应当把它们注销, 使用函数:
void free_irq(unsigned int irq, void *dev_id)
void free_irq(unsigned int irq, void *dev_id)
这里有2个参数,中断号和dev_id,由于共享中断中不能直接注销中断号,而是注销dev_id。
3.4代码编写
在上面的框架中实现中断处理程序。
1、中断服务函数的编写
2、在内核中注册中断服务函数
3、驱动不用时注销中断服务函数
如果有函数不知道怎么使用可以参考内核源代码。整体的框架如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
/*按键中断服务函数*/
irqreturn_t key_interupter(int irq, void *dev_id)
{
//检测设备是否产生中断
//清除中断标志位
//打印信息
}
/*设备文件打开操作*/
int key_open(struct inode *node, struct file *filp)
{
return 0;
}
/*初始化文件操作函数*/
struct file_operations key_fops
{
.open = key_open;
};
/*初始化设备文件*/
struct miscdevice key_miscdev{
.minor = 200;
.name = "key"
.fops = &key_fops
};
/*模块初始化函数*/
static int key_init(void)
{
/*注册混杂设备*/
misc_register(&key_miscdev);
/*注册中断函数*/
request_irq(irqno?, key_interupter, IRQF_SHARED, "key", 0);
return 0;
}
static void key_exit(void)
{
/*注销设备*/
misc_deregister(&key_miscdev);
/*注销中断服务程序*/
free_irq(irqno, 0);
}
MODULE_LICENSE("GPL");
module_init(key_init);
module_exit(key_exit);