linux中断处理

姓名:郑煜烁  学号:19029100010   学院:电子工程学院

转自:https://blog.csdn.net/u012142460/article/details/79272207

【嵌牛导读】介绍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文件可以获得中断信息。

来源 CSDN

二 、中断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开发板上,实现按键中断

原理图如下

来源 CSDN

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

来源 CSDN

它对应的是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");

————————————————

版权声明:本文为CSDN博主「念念有余」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/u012142460/article/details/79272207

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容