linux中断处理

一、中断介绍

所谓中断是指CPU在执行程序的过程中,出现了某些突发事件需要紧急处理,CPU必须暂时停止当前的工作,转去执行处理突发事件,处理完毕又返回原程序被中断的位置继续执行。

在ARM多核处理器中最常用的中断控制器是GIC,支持三类中断

1、SGI:Software Generated Interrupt,软件产生的中断,用于多核的核间通信

2、PPI:Private Peripheral Interrupt,某个CPU私有外设的中断,这类外设的中断只能发给绑定的那个CPU

3、SPI:Shared Peripheral Interrupt 共享外设中断,这类外设的中断可以路由到任何一个CPU

在proc/interrupts文件可以获得中断信息。


二 、中断API

看一下常用的中断相关的API函数

1、申请中断

static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
         const char *name, void *dev)

功能:申请中断

参数:irq:中断号,这个中断号不是硬件手册上的中断号,而是linux的中断号。

         handler:中断处理函数

         flags:标志位             #define IRQF_TRIGGER_RISING0x00000001  上升沿触发
                                          #define IRQF_TRIGGER_FALLING0x00000002   下降沿触发
                                          #define IRQF_TRIGGER_HIGH0x00000004           高电平触发
                       #define IRQF_TRIGGER_LOW 0x00000008           低电平触发
     IRQF_SHARED                                                  共享标志,表示该中断可以被多个设备共享

      name:中断的名称

      dev:要传递给中断服务程序的私有数据,一般设置为这个设备的结构体或者为NULL

返回值:成功返回0,返回-EINVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经占用且不能共享。

2、释放中断

void free_irq(unsigned int irq, void *dev_id)

参数与申请中断中的参数一样。

三、中断上下部机制

         中断会打断内核进程中的正常调度,系统对更高吞吐率的追求势必要求中断服务程序尽量短小精悍。但实际上,很多中断程序需要处理很多事务,可能进行大量的耗时操作。
      Linux为了解决这个问题,找出一个平衡点,将中断程序分成了两个部分:顶半部和底半部。顶半部处理紧急事务,底半部处理耗时操作。顶半部和底半部最大不同在于,底半部是可以被其他中断打断的,这样就不会耽误其他中断的进行了。
      顶半部处理紧急事务,一些中断必须的事务在此处理,比如读中断状态,清中断标志等等,这些都是必要但很简单的工作。在底半部主要处理一些不太紧要的工作,必要数据的处理等等。
      尽管上述机制能够改善系统的响应能力,但不能僵化的把所有中断驱动分成两个半部,如果本身处理情况就比较简单,则是完全可以在顶半部来全部完成的。
 我们使用tasklet和工作队列来完成顶半部底半部机制

1、tasklet


taskelet实际上是linux中软中断的一种,Linux提供了一系列宏和函数来完成tasklet
DECLARE_TASKLET(name, func, data)
功能:定义一个tasklet对象
参数:name:名称
          func:底半部函数
          data:传递给底半部函数的参数

DECLARE_TASKLET_DISABLED(name, func, data)
功能和上面类似,但还需要调用task_enable使能一下。

static inline void tasklet_schedule(struct tasklet_struct *tasklet)
功能:将指定的tasklet对象添加的加入到tasklet列表中,要执行底半部函数需要先执行这个函数,一般在顶半部函数执行。

2、工作队列

工作队列的方法也可以实现顶半部底半部机制
struct work_struct {
	atomic_long_t data;       //传递给工作函数的参数
	struct list_head entry;
	work_func_t func;        //工作函数,可以理解就是底半部函数。
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};
工作队列对象结构体。使用工作队列需要首先定义一个工作队列对象
  INIT_WORK(_work, _func)
功能:将工作队列与底半部函数绑定。
参数: _work,定义的工作队列
          _func,定义的底半部函数,可以简单的理解成将底半部函数赋值给定义的工作队列对象中func变量。

int schedule_work(struct work_struct *work)
功能:将工作队列节点加入到工作队列链表中,和tasklet中的tasklet_schedule函数功能类似,一般在顶半部函数中完成。
参数:work,工作队列对象

3、taskelet与工作队列的异同

        两者有什么区别呢?taskelet是在中断上下文来完成的,而中断中是不能进行进程调度的,所以在tasklet的顶半部和底半部都能进行进程的调度。工作队列是处于进程上下文中的,所以底半部是工作队列时,是可以进程进程调度的
         在使用时,步骤基本一致,绑定工作队列/tasklet和底半部函数,然后在顶半部中执行schedule函数,执行底半部函数。

实例,在迅为4412开发板上,实现按键中断

原理图如下


我们只拿一个来做例子,UART_RING,看他对应的GPIO口


它对应的是GPIOX1_1

得到中断号有一个API  gpio_to_irq(gpio),三星定义了GPIOX1_1的gpio值为EXYNOS4_GPX1(1),这样就可以得到中断号了。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <mach/gpio-exynos4.h>
#define TASKLET;
int irq1;
#ifdef WORK_QUEUE
struct work_struct  key_wq;   //定义一个工作队列对象
#endif

void key_tasklet_func(unsigned long data);
#ifdef TASKLET
DECLARE_TASKLET(key_tasklet,key_tasklet_func,0); //绑定tasklet对象和函数
#endif
void key_tasklet_func(unsigned long data)
{
	printk(KERN_INFO"key_tasklet_func enter...\n");
}

irqreturn_t key_handler(int irq, void *date)
{

	printk(KERN_INFO"key1 enter...\n");
	
#ifdef TASKLET
	tasklet_schedule(&key_tasklet);	   //执行tasklet底半部
#endif

#ifdef WORK_QUEUE
	schedule_work(&key_wq);            //执行工作队列底半部
#endif
	return IRQ_HANDLED;
}

static int __init demo_key_init(void)
{
	int ret = 0;

	irq1 = gpio_to_irq(EXYNOS4_GPX1(1));
	ret = request_irq(irq1,key_handler,IRQF_TRIGGER_FALLING,"KEY222",NULL);
	if(ret < 0){
		printk(KERN_INFO"request_irq fail...%s,%d,ret:%d\n",__func__,__LINE__,ret);
		return -EINVAL;
	printk(KERN_INFO"%s,%d\n",__func__,__LINE__);
#ifdef WORK_QUEUE
	INIT_WORK(&key_wq,key_tasklet_func);  //初始化一个工作队列
#endif

	return 0;
}

static void  __exit demo_key_exit(void)
{
	free_irq(irq1,NULL);
	printk(KERN_INFO"%s,%d\n",__func__,__LINE__);
}

module_init(demo_key_init);
module_exit(demo_key_exit);
MODULE_LICENSE("GPL");














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值