一、理论基础

1、概述。

中断是指CPU在运行程序的过程中发生了需要处理的特定突发事件(事件、中断、异常),CPU现在需要暂停程序执行,处理突发事件。处理完成后,CPU返回程序中断的位置,继续支持程序的过程就是中断。

2、中断的分类

根据中断的来源,可以将中断分为内部中断和外部中断。

(1)内部中断:中断源来自CPU内部,例如软件中断、计数溢出、操作系统从用户太切换到内核态需要借助CPU内部的软件中断。

(2)外部中断:中断源来自CPU外部,由外设产生中断请求,例如外部中断。

根据中断是否可以屏蔽分为可屏蔽中断与不屏蔽中断( NMI)。

(1)可屏蔽中断:可以通过屏蔽字被屏蔽,屏蔽后,该中断不再得到响应.

(2)不屏蔽中断:不能被屏蔽的中断。

根据中断入口跳转方法的不同,分为向量中断和非向量中断。

(1)向量中断:采用向量中断的 CPU 通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就自动跳转到与该中断号对应的地址执行。不同中断号的中断有不同的入口地址。S5PV210的中断就是采用的向量中断。

(2)非向量中断:非向量中断的多个中断共享一个入口地址,进入该入口地址后再通过软件判断中断标志来识别具体是哪个中断。

(3)也就是说,向量中断由硬件提供中断服务程序入口地址,非向量中断由软件提供中断服务程序入口地址。

3、S5PV210外部中断

在这里以GPIO口作为中断源产生外部中断进行说明。为了更好更具体的进行说明,先来分析笔者板卡的按键相关部分原理图,以此为目标进行分析。

(1)原理图分析

如上图所示,笔者板卡所使用到了GPH0_0~GPH0_5,GPH2_6,GPH2_7这8个GPIO口作为按键的接口。现在对这些GPIO口的工作模式进行配置。

如上图所示,可以将这些GPIO口配置为外部中断模式,并且各占用一个外部中断通道。

(2)使能中断

S5PV210存在MASK中断控制器,用来管理各个通道的外部中断。如下图:

(3)中断的触发方式

S5PV210存在专门的外部中断控制器EXT_INT_x_CON,用来设置外部中断的触发方式。

(4)中断状态标志位

S5PV210存在专门的中断挂起寄存器PEND,外部中断时,每个外部中断对于的位被硬件置1。

以上的说明均是GPIO口这部分作为中断时的配置,实际上S5PV210还存在专业的中断向量控制器。

4、S5PV210向量中断控制器

(1)快速中断FIQ与普通中断IRQ

对于ARM提醒架构的SOC而言,其存在7中基本的工作模式,而FIQ和IRQ为其中之二的中断异常模式。对应的可以将中断配置为快速中断FIQ模式,也可以配置为IRQ模式。关于FIQ和IRQ工作模式的切换和使能关注需要状态寄存器CPSR。如下图:

而向量中断控制器与FIQ和IRQ的关系如下:

(2)向量中断控制器

在S5PV210的Datasheet的向量中断控制器章节可以看到这样一句话:“The interrupt controller in S5PV210 is composed of four Vectored Interrupt Controller (VIC), ARM PrimeCell PL192 and four TrustZone Interrupt Controller (TZIC), SP890.”。就是说S5PV210存在4个向量控制器分别是VIC0~VIC3,和4个TIZC控制器。在这里我们重点关注向量中断控制器VIC。

VIC0~VIC3这4个控制器中,每一个向量中断控制器都控制着32个中断号,分别是0~31,32~63,64~95,96~127。在这里拿出VIC0来分析,如下图:

如上图所示为S5PV210的向量中断控制器0(VIC0),其中可以看到笔者板卡所使用的外部中断EXT_INT[0]~EXT_INT[5],均具有独立的中断号,分别为0~5;EXT_INT[22]和EXT_INT[23]共用中断号16,

(3)向量中断控制器VIC0-中断使能

(4)中断服务地址

对于VIC0而言,当使用到中断中断号为0的中断时,对应的寄存器是VIC0VECTADDR0;使用中断号为16的中断时,对应的寄存器是VIC0VECTADDR16。其寄存器中存放中断服务函数的地址。

(5)有效的中断服务函数地址

假设,当中断号为0的中断到来时,硬件会将VIC0VECTADDR0寄存器中存放的地址放到VIC0ADDRESS寄存器中。

5、Linux内核中中断的申请注册

在Linux内核中,使用接口request_irq来为外设申请注册中断,其原型如下:

  • irq:中断号

  • handler:其原型为typedef irqreturn_t (*irq_handler_t)(int, void *);这个回调函数作为中断处理程序。

  • flags:与中断管理相关的标志选项,用来设置中断的触发方式和处理方式

触发方式:分别表示没有触发、上升沿触发、下降沿触发、高电平触发、低电平触发中断等。

处理方式:当设置为IRQF_DISABLED,表明中断处理程序是快速处理程序,快速处理程序在被调用时屏蔽所有中断,慢速处理程序则不会屏蔽其他中断。若设置为IRQF_SHARED,则表示多个设备共享中断,这时候就需要使用到了dev_id,dev_id将会传递给中断服务程序,表示某一个设备产生了中断。在其他非共享中断时,通常dev_id被设置为NULL。

·name:设备的名称

·dev:共享中断时使用,表示设备的ID,即为dev_id,共享中断号。

·返回值:返回0时表示成功,失败时返回一个错误码。返回-EINVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。

6、中断号的获取

在笔者板卡中,从Datasheet上可知,所占用的硬件中断号分别为0~5(EXT_INT[0]~EXT_INT[5]各自独立占有一个中断号)和16(EXT_INT[22]和EXT_INT[23]共用中断号16)。当在程序中需要使用接口request_irq申请注册每一个中断时,都需要中断号作为标识来判断所申请的是哪一个通道的中断。

那么这里就存在一个问题,Datasheet上说明的硬件的中断号和在Linux内核中request_irq接口所使用的中断号是一致的么??

真实的答案是否定的,在Datasheet手册上说明的中断号,在Linux内核中的定义作为一个偏移量标志着。如图在两个irqs.h文件中的定义:linux/arch/arm/mach-s5pv210/include/mac和linux/arch/arm/plat-s5p/include/pla

如上图分析可知,在Datasheet上向量中断控制器VIC0标识的中断号,在使用request_irq接口申请中断时,其作为基本中断号32进行偏移,得到的值才是使用在Linux内核中的中断号,举例:EXT_INT[1]在Linux内核中的中断号为:1+S5P_IRQ_OFFSET,即为1+32=33。

那么这里的S5P_IRQ_OFFSET是什么呢?并且值为32。实际上S5P_IRQ_OFFSET是作为中断的一个偏移量来进行使用的,在Linux内核中,前32个中断号(0~31)作为软中断使用,外部中断的中断号从S5P_IRQ_OFFSET这个偏移量开始进行偏移计算得到对应的中断号。

在这里有一点特殊的地方是,在Datasheet的VIC0中断号列表中可以看到,EXT_INT[16]~EXT_INT[31]折16个外部中断共用一个中断号16,那么按照上面的计算,它在Linux内核中的中断号应该为16+S5P_IRQ_OFFSET = 48,那么实际上是这样的吗??比如现在同时使用两路中断EXT_INT[22]和EXT_INT[23],CPU和在Linux系统中是如何区分这两个中断的呢?

在linux/arch/arm/mach-s5pv210/include/mac文件中有这样的宏定义:

如上图可知,接口gpio_to_irq最后返回的值实际上是表示这一路GPIO口所代表的是这个GPIO的中断编号。所以中断号相同的各个外部中断最后通过GPIO口所代表的中断编号来辨别到底是哪一路中断被触发了。所以在申请的时候也是以此为区别。

7、中断的释放

在Linux内核中,使用接口free_irq来释放已经申请的中断,其原型为:

当不在需要使用已经申请的中断或者驱动卸载时,直接调用这个接口即可释放已经申请的中断。其参数:

  • irq:中断号。

  • dev_id:表示设备ID。

8、Linux内核的中断处理机制

设备产生的中断会打断内核中进程的正常调度和运行,转而去执行中断程序,通常为了使得系统有更高的吞吐率,会要求中断服务程序要尽可能的短小精悍。但是往往在实际的操作中,中断到来后,要完成的工作往往会很多,更可能需要进行大量的耗时处理中断事件。

为了解决“中断服务程序执行时间尽可能短”和“实际的中断处理需要完成大量的工作”的矛盾问题,Linux内核中将中断服务程序分为两个部分,分别为中断上半部(top half)和中断下半部(bottom half)。如下图为其处理机制:

(1)中断上半部(top half)

完成处理尽可能少的并且紧急的功能或者事件,它往往只是简单的读取寄存器中的中断状态并清除中断标志之后就进行“登记中断”的工作。“登记中断”实际上就意味着将中断下半部的处理程序挂载到该设备的中断下半部程序执行队列中。

通过这种方式,在进行中断响应时,中断上半部作为“真正的”中断服务程序只需要处理简单并且很少的事件,其他耗时的操作通过在中断下半部完成,而中断下半部可以将其加入到工作队列中,通过调度来执行中断所需要处理的事件。

处理中断上半部服务程序时,CPU工作在FIQ或者IRQ模式。

(2)中断下半部(bottom)

将发生中断后,所需要处理的大部分工作都放在中断的下半部服务程序中进行处理,他可以完成处理大量的工作和耗时的操作(中断下半部基本上要完成所有的中断服务程序所要处理的工作),并且因为中断下半部的服务程序处理时,CPU工作在正常的工作模式(并没有工作在FIQ或者IRQ模式,实际上中断下半部的服务程序就是普通的程序),所以它可以被新的中断打断。

(3)中断上半部服务程序通常被设置为不可中断(不可嵌套中断)的程序。而中断下半部服务程序用来处理不是非常紧急的、比较耗时的工作,中断下半部服务程序不在硬件中断服务程序中执行。中断上半部服务程序就是硬件中断服务程序。

(4)Linux内核中使用中断上半部和中断上半部配合使用的方式来改善操作系统的响应能力,但是我们不能简单/僵化的认为Linux设备驱动的中断处理一定要分为中断上半部和中断下半部两个部分。实际上如果在调试设备时,产生的中断后所要处理的工作本身很少,那么完全可以不使用中断下半部,将所有的工作都在中断上半部完成。

9、中断下半部的实现机制

在Linux内核中,中断下半部的实现机制有:tasklet、工作队列和软中断。在这里详细讲解工作队列,后面再详细的分析tasklet和软中断(WSI)。

工作队列(work queue):

工作队列(work queue)是Linux内核中将工作推后执行的一种机制,他的实现方式是把要推后处理的工作交给一个内核线程去执行,因此工作队列的优势在于它允许重新调度甚至是睡眠。

(1)Linux Kernel中工作队列的描述

在Linux Kernel中使用结构体struct work_struct来描述一个工作队列,2.6.20之后的Linux内核版本的结构原型如下:

(2)Linux Kernel工作队列的申请

在Linux Kernel中使用宏接口INIT_WORK(_work, _func)申请注册一个工作队列,其原型如下:

  • _work:由struct work_struct表示的工作队列结构。

  • _func:为中断下半部处理函数,类型为void (*work_func_t)(struct work_struct *work);。

(3)Linux Kernel工作队列的调度

在Linux内核中,使用接口schedule_work作为调度工作队列执行的函数。其原型为:

二、 示例驱动代码

以按键驱动为例,实现使用中断方式响应按键操作。使用工作队列实现中断下半部。

1、驱动的装载

如上图所示,通过request_irq接口申请外部中断,其中EXT_INT[0]~EXT_INT[5]占用单独的中断号;EXT_INT[22]和EXT_INT[23]通过gpio_to_irq(S5PV210_GPH2(6)和gpio_to_irq(S5PV210_GPH2(7)获取GPH2_6和GPH2_7所对应的中断编号进行申请中断。为了简单的实现功能,使用同一个中断服务程序Key_hw_interrupt实现中断上半部。

通过INIT_WORK(interrupt_work, interrupt_work_handle);申请工作队列,其中interrupt_work为表示工作队列的结构;interrupt_work_handle为中断下半部执行函数。其实现原型如下:

如上图为中断下半部服务函数的实现,简单的通过判断GPH0_0~GPH0_5、GPH2_6和GPH2_7管脚的电平来判断按键是否按下。

2、中断上半部服务程序的实现

如上图所示,通过使用schedule_work函数接口实现对工作队列interrupt_work的调度。

3、驱动的卸载

需要释放混杂设备、注册工作队列时申请的存储空间和释放所申请的中断资源。

三、实验现象

1、将驱动进行编译,得到in驱动文件。将其拷贝到根文件系统中,使用命令insmod in将其加载到内核中。

按下按键,现象如下:

1.《linux如何注册一个中断 linux如何注册服务?》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。

2.《linux如何注册一个中断 linux如何注册服务?》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。

3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/keji/3213811.html