Linux_内核中断编程

1.中断基础

1.1.什么是中断

中断是指在CPU正常运行期间,由于内部事件或外部事件引起的CPU暂时停止正在运行的程序,转去该内部或外部事件的服务程序中去,服务程序执行完毕后再返回断点处继续运行被暂时中断的程序。
在中断发生时被调用的用来处理中断的函数称为中断处理函数。
中断的处理流程如下图所示:

在这里插入图片描述

1.2.中断的意义

外设的处理速度远远慢于CPU的处理速度,如果采用轮询方式(CPU一直等待)处理外设,会降低CPU的利用率。如果采用中断方式处理外设,会大大提高CPU的利用率。
以cpu读取串口信息为例:
为了防止串口数据的丢失,不采用中断,采用轮询,CPU只能不停读取串口的信息;
如果采用中断,cpu当发现串口数据不可读时,cpu可以做其他事情,一旦串口数据准备就绪,串口会给CPU发送一个中断信号,告诉cpu,数据准备就绪,请来处理,cpu处理完毕以后,再接着执行原先被打断的任务!

1.3.中断控制器

外设的中断信号并不是直接输送给CPU,而是先给中断控制器,经过中断控制器的判断以后,再决定是否给CPU发送中断信号。然后CPU一旦接收到了外设的中断信号之后,CPU开始进行中断处理。

中断控制器具有以下几个作用:
1.能够屏蔽或者使能某个中断信号;
2.能够设置中断优先级;
3.能够设置外设中断信号的有效触发方式;
4.如果是多核,能够指定中断信号给哪个CPU发送。
Cotex-M系列处理器使用NVIC中断控制器;
Cotex-A/R系列处理器使用GIC中断控制器。

2.内核中断编程

Linux内核中有专门中断子系统,其实现原理非常复杂,但是驱动开发者不需要知道其实现的具体细节,只需要知道如何应用该子系统提供的API函数来编写中断相关驱动代码即可。

2.1.申请中断

在Linux内核中要想使用某个中断是需要申请的,在驱动程序中通过request_irq函数申请中断。

头文件:	#include <linux/interrupt.h>
函数原型:int request_irq(unsigned int	irq, 
						irq_handler_t 	handler, 
	       				unsigned long 	flags, 
	          			const char 		*name, 
                   		void 			*dev)
函数功能:向内核申请硬件中断资源,注册中断处理函数到内核
函数参数:
	irq		要申请中断的中断号
	handler	中断处理函数,当中断发生后就会执行此中断处理函数
	flags	中断标志
	name	中断名字,申请中断成功后可在/proc/interrupts文件中看到对应的中断名字
	dev		给中断处理函数传递参数,可以为NULL
函数返回值:成功返回0,失败返回负数(-EINVAL表示中断号无效,-EBUSY表示中断已经被占用)

2.2.中断号

硬件中断对应的软件编号,又称中断号。该中断号一般由芯片厂家在内核源码中定义。不同的芯片中断数量不一样,对应的外设也不相同,所以中断号定义一般是存放在具体芯片相关的目录中。一般定义一般在:arch\构架\mach-芯片型号\include\mach\irqs.h头文件中。
在这里插入图片描述

对于外部中断,一般不直接使用上面的EXYNOS4_IRQ_EINTX 宏,都是使用内核提供的通用API函数:gpio_to_irq来获取中断号,该函数可以通过 IO 口管脚编号转换成它对应中断编号。IO口管脚编号也是由芯片厂商在内核源码arch/构架/mach-芯片型号/include/mach/gpio.h文件中定义好的。

2.3.中断处理函数

中断处理函数的格式为:irqreturn_t (*irq_handler_t)(int, void *);
 第一个参数表示中断号,第二个参数是一个指向void的指针。
 在中断处理函数被调用时,这两个参数实际传递的就是request_irq 时的第1个参数(irq)和最后一个参数(dev)。 	
 中断处理函数的返回值是irqreturn_t类型,
 irqreturn_t在include/linux/irqreturn.h文件中定义如下:
/*
 * enum irqreturn
 * @IRQ_NONE		    interrupt was not from this device
 * @IRQ_HANDLED		interrupt was handled by this device
 * @IRQ_WAKE_THREAD	handler requests to wake the handler thread
 */
enum irqreturn {
	IRQ_NONE		= (0 << 0),		//表示中断没有被处理,比较少用,只会在共享中断中出现。
	IRQ_HANDLED		= (1 << 0),		//表示中断被正确处理了,常用。
	IRQ_WAKE_THREAD	= (1 << 1),		//表示去唤醒中断处理者的线程,基本不用。
};
typedef enum irqreturn irqreturn_t;
	可以看出irqreturn_t是个枚举类型,所以中断处理函数一共有三种返回值。

注:
内核要求中断处理函数的执行的速度要快,不能做休眠操作以避免中断长时间占用CPU资源,导致系统的并发和响应能力受到影响。在Linux内核中,中断不隶属于任何进程,不参与进程的调度。

2.4.中断标志

中断标志用来描述中断的特征。其值由内核源码定义,可以在文件include/linux/interrupt.h里面查看所有的中断标志。这里介绍几个常用的中断标志:

标志						描述
IRQF_DISABLED			指明该中断是独占中断,只能注册一次
IRQF_SHARED				指明该中断是共享中断,可以注册多次
IRQF_ONESHOT			单次中断,中断执行一次就结束
IRQF_TRIGGER_NONE		无触发,用于内部事件
IRQF_TRIGGER_RISING		上升沿触发
IRQF_TRIGGER_FALLING	下降沿触发
IRQF_TRIGGER_HIGH		高电平触发
IRQF_TRIGGER_LOW		低电平触发
以上这些标志可以通过“|”来实现多种组合。当然IRQF_DISABLED与IRQF_SHARED是不能相组合的。

2.5.共享中断与独占中断

独占中断是指一个中断源单独占据一个中断线,该中断独占一个中断号,该中断号对应一个中断处理函数。
共享中断是指多个中断源共享一根中断线的情况,它们使用同一个中断号,该中断号对应N个中断处理函数。
对于独占中断,同一个中断号只能被request_irq注册一次。
对于共享中断,可以多次调request_irq注册同一个中断号。
中断处理函数模型差异:
使用同一个中断号注册的所有中断处理函数会链接成一个链表,这样当共享中断中的一个外设产生中断时,就要遍历其对应的中断处理程序链表,直到某一个中断服务函数返回时返回IRQ_HANDLED。

如果是共享中断,其中断处理函数模型如下:
irqreturn_t  isr_name(int irq,void *dev)
{
	if(不是本外设产生中断信号)	//一般通过外设的状态寄存器判断
		return IRQ_NONE;
	……                         //中断处理
	return IRQ_HANDLED;
}

注意:
当中断产生时判断哪一个外设产生的中断,是通过外设的状态寄存器来判断,而不是通过中断处理函数中的dev参数。

如果是独占中断,其中断处理函数模型如下:
irqreturn_t  isr_name(int irq,void *dev)
{
	if(外设状态寄存器标志位置1 或 IO口电平满足要求){
		……
	}
	return IRQ_HANDLED;
}

dev参数使用差异:
如果flags指定为独占中断(IRQF_DISABLED),request_irq函数中的dev参数可以指定为NULL。
如果 flag指定为共享中断(IRQF_SHARED),request_irq函数中的dev参数则不能为NULL,并且保证惟一性。
很多人认为指定为共享中断时,dev 用来标识发生中断时具体是哪一个中断。但实际上,这个dev并不具备标识是哪一个中断的能力。这个参数真正的用途是用来注销共享中断中的具体哪一个中断。
当然,不同的中断号可以注册相同的中断处理函数,用dev参数来区分不同的中断处理函数,这一点和在创建N个线程时,可以运行同样的线程函数,通过线程函数参数来进行区分是一个道理。

2.6.释放中断

中断使用完成以后需要进行释放,在驱动程序中通过free_irq函数释放相应的中断。

头文件:	#include <linux/interrupt.h>
函数原型:void free_irq(unsigned int irq, void *dev);
函数功能:释放中断资源,删除中断处理函数
函数参数:irq	要释放中断的中断号
		dev		给中断处理函数传递的参数,切记一定要和注册时传递的参数一样
函数返回值:无

2.7.中断使能与禁止

void enable_irq(unsigned int irq);
void disable_irq(unsigned int irq);
enable_irq和disable_irq用于使能和禁止指定的中断,irq就是要禁止的中断号。
使用request_irq函数申请中断时,便已激活中断,不需要手动使能中断。

要注意的是,disable_irq函数要等到当前正在执行的中断处理函数执行完才返回。因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。在这种情况下,可以使用另外一个中断禁止函数:

void disable_irq_nosync(unsigned int irq);
disable_irq_nosync函数调用以后立即返回,不会等待当前中断处理程序执行完毕。

有时候我们需要关闭当前处理器的整个中断系统,可以使用下面两个函数:

local_irq_enable()
local_irq_disable()

2.7.内核中断编程步骤

1)看硬件原理图,确定使用的中断;
2)确定功能编写中断服务函数;
3)在适当的地方(驱动加载函数)调用 request_irq注册中断;
4)在相反的地方(驱动卸载函数)调用 free_irq 注销中断;
补充:如果在注册过程没有完成整个工作流程,也需要注销已经注册的中断。

3.内核中断编程示例

3.1.按键中断信息结构体

typedef struct{
	int gpio_num;
	char *irq_name;
	int key_num;
}KeyIRQ_Info;

KeyIRQ_Info keysData[4]= {
	{EXYNOS4_GPX3(2),"key1",1},
	{EXYNOS4_GPX3(3),"key2",2},
	{EXYNOS4_GPX3(4),"key3",3},
	{EXYNOS4_GPX3(5),"key4",4},
};

3.2.中断处理函数

irqreturn_t keysIRQ_Handler(int irq, void *dev)
{
	//printk("*****%s*****\n",__FUNCTION__);
	int keynum = *(int *)dev;
	if( *keydat & 1<<(keynum+1) ){	//按键松开
		printk("key%d is up\n",keynum);
	}else{							//按键按下
		printk("key%d is press\n",keynum);
	}
	return IRQ_HANDLED;
}

3.3.驱动加载函数

static int __init keyIRQ_init(void)
{
	int i,ret;
	unsigned int flag = IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING;
	
    printk("*****%s*****\n",__FUNCTION__);	

	/* 地址映射 */
	keycon = ioremap(0x11000C60, 4);
	keydat = ioremap(0x11000C64, 4);

	/* 配置key对应IO口 */
	*keycon &= ~((0xf<<2*4)|(0xf<<3*4)|(0xf<<4*4)|(0xf<<5*4));
	
	/* 申请4个按键中断 */
	for(i=0;i<4;i++){
		key_irq = gpio_to_irq(keysData[i].gpio_num);
		ret = request_irq(key_irq, keysIRQ_Handler,flag,keysData[i].irq_name,&keysData[i].key_num);
		if(ret < 0){
			break;
		}		
	}
	/* 申请中断发生失败,则要把之前申请过的全部释放掉 */
	if(ret < 0){
		for(i=i-1;i>=0;i--){
			key_irq = gpio_to_irq(keysData[i].gpio_num);
			free_irq(key_irq, &keysData[i].key_num);
		}
		return ret;
	}
    return 0;
}

3.4.驱动卸载函数

static void __exit keyIRQ_exit(void)
{
	int i;
    printk("*****%s*****\n",__FUNCTION__);
	/* 释放4个按键中断 */
	for(i=0;i<4;i++){
		key_irq = gpio_to_irq(keysData[i].gpio_num);
		free_irq(key_irq, &keysData[i].key_num);
	}

	/* 解除映射 */
	iounmap(keycon);
	iounmap(keydat);
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值