linux之中断子系统介绍(1)
1.中断介绍
中 断是指CPU在正常执行指令过程中,出现了突发且紧急的事件,CPU必须暂时停止执行当前的程序,跳转到处理突发事件的指令处执行,处理完毕后返回到原来的指令处继续执行。根据中断的来源,中断可分为内部中断和外部中断,内部中断来源于CPU内部,如软件中断指令、除法错误、溢出等,外部中断来自于CPU外部,由外设产生,如串口收到数据产生中断、定时器到期产生中断。根据中断入口跳转方法可分为向量中断和非向量中断,CPU通常为向量中断分配不通的中断号,中断到来后,就自动跳转到该中断号对应的地址处执行,非向量中断共享一个入口地址,进入该地址后,通过判断中断标志确定中断源。
2.Linux中断框架介绍
中断服务程序是在关中断的状态下执行,如果中断服务程序执行的时间过长,会使其他进程得不到调度执行,系统不能响应其他中断,导致系统的实时性和性能降低。为了解决这个问题,Linux将中断服务程序分为两部分,即顶半部分(Top Half)和底半部分(Bottom Half)。顶半部分是在关中断的状态下进行,此阶段不能响应中断,顶半部分用于完成尽量短且比较紧急的工作,如获取中断状态、登记中断、清除中断状态,登记的中断在底半部分处理。底半部分是在开中断的状态下进行,此阶段可以响应中断,底半部分用来处理顶半部分登记的中断,主要是一些复杂且耗时的工作,如拷贝数据、解析数据等工作。当然,中断处理是否一定要分为顶半部分和底半部分,需要根据实际的中断任务确定,对于一些简单的任务,可以直接在顶半部分完成。
Linux内核支持众多的处理器体系结构,中断子系统非常复杂,但根据其功能,可大致分为四部分:
(1)硬件层,例如CPU和中断控制器的连接,硬件中断号,中断触发方式等
(2)处理器的异常处理模式,例如CPU如何处理不同异常(中断也是一种异常),如何进入异常和如何退出异常
(3)中断控制器的管理,例如硬件中断号和软件中断号的映射,中断控制器的操作
(4)Linux内核通用中断处理层,此部分屏蔽了硬件之间的差异,提供了通用的中断处理的框架,向上提供中断处理的统一接口,如注册和注销中断等,向下兼容所有体系结构。
3.ARM处理器异常处理模式
ARMv7-A and ARMv7-R架构支持9种处理器运行模式,其中6种为特权模式,1种为非特权模式,剩下2种和虚拟化、安全相关。特权模式分别为FIQ(快速中断)、IRQ(中断)、Supervisor、Abort、Undefined、System,操作系统通常运行在Supervisor模式下。非特权模式为User mode,即用户模式,应用程序就在此模式下运行。特权模式中除了System,其他模式又称为异常模式,中断也属于异常的一种。ARM架构处理器运行模式可参考《Cortex-A Series Programmer’s Guide》第11节。
ARM处理器对中断的响应过程如下:
(1)CPU自动保存处理器状态,即将中断发生时的CPSR寄存器内容保存到SPSR_irq寄存器中
(2)CPU自动设置当前程序状态寄存器CPSR的低5位,使处理器进入特权模式中的IRQ模式(Linux不使用FIQ,只使用IRQ)
(3)硬件自动关闭IRQ中断
(4)将返回地址(PC)自动保存到LR_irq寄存器中
(5)CPU自动的将程序计数器PC设置成异常中断向量表中的地址,进入相应的异常处理程序中处理中断
(6)处理完中断后,恢复中断发生前的处理器状态,即将SPSR_irq寄存器中的数据复制到CPSR寄存器中
(7)设置程序计数器PC,使其指向中断发生前要执行的指令,即将LR_irq寄存器中的数据复制到PC中
异常处理程序的入口地址保存在中断向量表中,如下图所示。中断向量表正常情况下位于0x0-0x1C地址处,处于内存中的低地址。也有可能位于高地址处,即0xFFFF0000-0xFFFF001C处,Linux中断向量表通常位于内存的高地址处,因为0-3G为用户空间,3-4G为内核空间,中断向量表属于内核空间。我们关心的IRQ中断向量的地址为0x1C或者0xFFFF001C。需要注意的是上述地址都为虚拟地址。

arm处理器内部的寄存器数量相比x86架构的处理器要多一些,某些cpu模式还拥有专用的寄存器,arm处理器寄存器和处理器模式的对应关系如下图所示。所有模式共用PC和CPSR;除了FIQ模式,所有模式共用R0-R12寄存器,专用的寄存器有SP、LR及SPSR;FIQ模式下有专用的R8-R12寄存器。

4.Linux内核底层中断处理源代码分析
4.1.中断向量表
4.1.1.中断向量表的定义
Linux的中断向量表中存放的是一系列的跳转指令,用于跳转到异常处理程序的入口处。这里重点关注IRQ中断向量。vector_irq是通过宏定义的标签,由vector_stub定义而来。
[arch/arm/kernel/entry-armv.S].section .vectors, "ax", %progbits @ 说明中断向量表的段及属性,ax表示可分配、可执行.L__vectors_start: @ 中断向量表开始W(b) vector_rst @ 复位W(b) vector_und @ 未定义W(ldr) pc, .L__vectors_start + 0x1000 @ 特权指令(svc、swi)入口W(b) vector_pabt @ prefetch abortW(b) vector_dabt @ data abort W(b) vector_addrexcptn @ 地址异常W(b) vector_irq @ 中断W(b) vector_fiq @ 快速中断
4.1.2.中断向量表stubs段
stubs段起到分发中断的作用,使不同的中断跳转到不同的处理程序中去,如中断发生在usr模式,将会跳转到__irq_usr中处理,如中断发生在svc模式,将会跳转到__irq_svc中处理。.macro表示宏,vector_stub是宏的名称,name、mode和correction为参数。correction的默认值为4。将vector_stub irq, IRQ_MODE, 4宏定义展开,则name为irq,mode为0x00000012,correction为4。
[arch/arm/kernel/entry-armv.S].macro vector_stub, name, mode, correction=0 @ 宏定义开始.align 5vector_\name: @ \表示使用宏的参数,替换后得到vector_irq,可见中断向量表中的irq跳转指令是跳转到此处.if \correction @ correction为4,条件成立@ 将lr寄存器数据减4并保存到lr中,lr中保存的是中断返回的地址,这里减4是由于流水线的原因,发生中断的@ 时候pc还未更新,指向了中断发生前执行指令后面的第二条指令,即中断发生前执行指令加8个字节的位置。中@ 断返回后要执行中断发生前执行指令后面的第一条指令,则需要将lr减4。sub lr, lr, #\correction.endif@ 将r0和lr保存到irq模式的栈中,lr是中断返回的地址stmia sp, {r0, lr} @ save r0, lr@ 将spsr保存到lr中mrs lr, spsr@ 将spsr_irq保存到栈中,此时的spsr为发生中断时的cpsrstr lr, [sp, #8] @ save spsr@ 将cpsr保存到r0中mrs r0, cpsr@ 将r0最后一位设置为1,0x12为irq模式,0x13为svc模式eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)@ 将r0保存到spsr中,此时spsr中的低5位为0x13,表示svc模式,此时处理器还处于irq模式,还未切换到svc模式msr spsr_cxsf, r0@ 获取中断发生时cpsr寄存器的低4位,如果低4位为0表示中断发生时cpu处于usr模式(用户空间),@ 如低4位为3,则表示中断发生时cpu处于svc模式(内核空间)and lr, lr, #0x0f@ 获取标号1的地址,此地址为相对地址,是当前pc的偏移地址THUMB( adr r0, 1f )@ 将表号1的地址和cpsr低四位相加,得到跳转的相对偏移地址,中断发生在usr模式则跳转到__irq_usr,中断发生在svc模式,@ 则跳转到__irq_svcTHUMB( ldr lr, [r0, lr, lsl #2] ) @ lsl为左移指令,左移两位,保证地址4字节对齐mov r0, sp @ 栈指针保存在r0中@ 将偏移地址和pc相加存放到lr寄存器中ARM( ldr lr, [pc, lr, lsl #2] )@ 将lr中的地址复制到pc中,跳转到__irq_usr或__irq_svc处,同时将spsr的值复制到cpsr,将处理器模式切换到svc模式movs pc, lr @ branch to handler in SVC modeENDPROC(vector_\name).align 2@ handler addresses follow this label1:.endm @ 宏定义结束