Linux按键驱动程序设计(2)-Linux中断处理程序

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);

猜你喜欢

转载自blog.csdn.net/qq_22847457/article/details/91381844