CPU在工作的过程中,经常需要与外设进行交互,交互的方式包括”轮询方式”,”中断方式”。
- 轮询方式:CPU不断地查询设备的状态。该方式实现比较简单,但CPU利用率很低,不适合多任务的系统。
- 中断方式:CPU在告知硬件开始一项工作后,就去做别的事去了,当硬件完成了该项工作后,向CPU发送一个信号,告知CPU它已经完成了这项工作。
写过按键裸机程序的同学都知道按键的读取一般都采用中断的方式,如果采用轮询的方式真的是太浪费CPU资源了。下面将介绍Linux中的中断处理程序。
1.裸机中断处理流程回顾
- 对所有芯片而言,中断都有一个通用的入口,进入后会保存保存当前的环境把它压栈,然后查询中断向量表,根据中断源去调用对应的中断处理程序。
- 因此总结出以下3点:
- 1、中断统一入口
- 2、事先注册中断处理程序
- 3、根据中断源编号查询中断向量表,调用中断处理程序
2.Linux中断处理流程分析
- 在Linux内核代码里也有统一的入口,这个函数叫做__irq_svc函数,这个函数可以在内核代码的entry-armv.S文件中找到。这个函数保存现场之后会调用一个irq_handler的宏
__irq_svc:
svc_entry
#ifdef CONFIG_PREEMPT
get_thread_info tsk
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
add r7, r8, #1 @ increment it
str r7, [tsk, #TI_PREEMPT]
#endif
irq_handler
- irq_handler的这个宏主要获取中断源,然后跳转到asm_do_IRQ
/*
* Interrupt handling. Preserves r7, r8, r9
*/
.macro irq_handler
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
- 总结如下:
- 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 共享中断时使用。
- 在flags参数中,可以选择一些与中断管理有关的选项,如:
- IRQF_DISABLED(SA_INTERRUPT)
- 如果设置该位,表示是一个“快速”中断处理程序;如果没有设置这位,那么是一个“慢速”中断处理程序。
- 快/慢速中断的主要区别在于:快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。换句话说,也就是“开启中断”标志位(处理器IF)在运行快速中断处理程序时是关闭的,因此在服务该中断时,不会被其他类型的中断打断;而调用慢速中断处理时,其它类型的中断仍可以得到服务。
- IRQF_SHARED(SA_SHIRQ):该位表明该中断号是多个设备共享的。
3.2 中断处理程序
- 中断处理程序的特别之处是在中断上下文中运行的,它的行为受到某些限制:
- 1.不能使用可能引起阻塞的函数 如果阻塞了所有的中断都不能得到及时的处理
- 2.不能使用可能引起调度的函数
- 中断处理程序编写流程:
- 1、检查设备是否产生中断
- 2、清楚中断标志位
- 3、完成相应的硬件操作
3.3 注销中断
- 当设备不再需要使用中断时(通常在驱动卸载时), 应当把它们注销, 使用函数:
- void free_irq(unsigned int irq, void *dev_id)
- 这里有2个参数,中断号和dev_id,由于共享中断中不能直接注销中断号,而是注销dev_id。
3.4代码编写
- 在上面的框架中实现中断处理程序。
- 1、中断服务函数的编写
- 2、在内核中注册中断服务函数
- 3、驱动不用时注销中断服务函数
- 如果有函数不知道怎么使用可以参考内核源代码。整体的框架如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
// 中断处理函数
irqreturn_t key_int(int irq, void *dev_id)
{
// 1.检测是否发生了按键中断
// 2.清除已经发生的按键中断
// 3.打印按键值
}
int key_open(struct inode *node, struct file *filp)
{
return 0;
}
const struct file_operations key_fops =
{
.open = key_open,
};
// 初始化miscdevice
struct miscdevice key_miscdev =
{
.minor = 200,
.name = "mykey",
.fops = &key_fops,
};
static int key_init()
{
// 注册miscdevice
misc_register(&key_miscdev);
// 注册中断处理程序
request_irq(irqno, key_int, IRQF_SHARED, "mykey", 0);
return 0;
}
static void key_exit()
{
// 注销miscdevice
misc_deregister(&key_miscdev);
// 注销中断处理程序
free_irq(irqno, 0);
}
MODULE_LICENSE("GPL");
module_init(key_init);
module_exit(key_exit);