VxWorks/MIPS中断处理机制

http://blog.csdn.net/phunxm/article/details/8957955

在《中断处理》中,梳理了中断处理的一些通用框架和概念,下面我们来探讨一下VxWorks/MIPS具体平台下的中断处理机制。时钟中断作为最高优先级的中断,其处理不失一般性,可作为分析理解VxWorks中断处理机制的切入点。

所谓“硬件搭台,软件唱戏”,在展开具体软件实现之前,先厘清既有的硬件资源支持,是本人一贯秉承的思维习惯。本文以VxWorks中的tick起源问题为引子,沿着“MIPS中断资源->SoC硬件Timer配置->VxWorks时钟中断挂接->VxWorks中断调度->tick脉 动”的线索铺开,试图系统阐述VxWorks/MIPS下的中断挂接处理机制。

通用中断处理框架包括了进入ISR之前的很多进入路径(entry path),MIPS中断(异常)处理步骤大概如下:设置或屏蔽相关寄存器;进入异常入口点取指;现场保护;异常分类处理;查找中断向量表调用ISR。这些进入路径是跟硬件预设置和操作系统内存管理布局紧密相关,很多都是用汇编语言写就。限于时间、精力和资源,本文没有去深究这些底层路径的完整实现,而是站在开发者的角度,重点关注日常工作中需要动手操刀的接口部分及周边实现。

1 MIPS异常

在MIPS中,中断、陷进、系统调用和任何可以中断程序正常执行流的情况都称之为异常。典型的MIPS R4k及以后的处理器用同一个中断入口地址处理冷启动和热启动,因此系统重置通常被看作是一种异常。

(1)精确异常

《MIPS体系结构透视》中将MIPS的异常机制称为“精确异常”(precise exception)。何为精确异常呢?由于异常是在执行指令时同步发生,因此在造成异常的指令之前执行的指令,无疑均是有效的。然而,由于MIPS的高度流水线设计,在引发异常的指令执行时,后面一条指令已经完成了读取和译码的预备工作,万事俱备,只待ALU部件空闲即执行之。当异常产生时,这些预备工作便被废弃。CPU从异常中返回时,再重新做读取和译码的工作,因此可以保证,在异常发生时,异常指令之后所有的指令均不会被执行。这样,就不需要在MIPS的异常处理例程(Exception Handler)中为延迟槽(Delay Slot)指令而烦恼了。但MIPS精确异常的代价是非常高的,因为它限制了流水线的深度。这对FPU影响很大,因为浮点运算通常需要更多的流水线阶段。

(2)异常向量:异常处理开始的地方

所有的异常入口点,都位于MIPS内存映射不需要地址转换的区域——非缓存的kseg1和缓存的kseg0段。当C0_SR:BEV=1置位时,ROM/Flash中非缓存的异常入口是固定的:R_VEC=0xBFC00000,BEV_VEC=0xBFC00380。当C0_SR:BEV=0清零时,C0_EBase寄存器可以通过编程配置异常入口基地址(Exception entry point base addres),从而实现异常向量的整体移动。BASE默认为K0BASE=0x80000000,可通过C0_EBase寄存器改变。

VxWorks/MIPS中断处理机制_第1张图片

(3)异常处理步骤(Exception Handle Procedure)

产生异常时MIPS CPU所要做的工作:

<1>.设置EPC,指向重新启动的位置

<2>.设置C0_SR:EXL位迫使CPU进入内核模式(高特权级)并且禁用中断

<3>.设置C0_CAUSE寄存器,使得软件能看到异常原因。地址异常时,也要设置BadVAddr寄存器。存储管理系统异常还要设置一些MMU寄存器。

<4>.CPU从异常入口点取指执行。例如,启动后(C0_SR:BEV=0),所有其他异常的入口点(excNormalVec=0x80000180)。

MIPS异常处理例程都需要经过以下步骤:

<1>.保存现场:在异常处理例程入口,需要保护被中断的程序的现场,存储寄存器的状态,保证关键状态不被覆盖。一般用k0和k1这两个寄存器索引一块内存区域,用来存储其他的寄存器。这块内存区域一般被称作中断栈(Interrupt Stack),用于存储寄存器状态,并且支持复杂的C等高级语言编写的异常处理例程。

<2>.处理异常:根据C0_CAUSE:ExcCode确定发生了什么类型的异常,然后调用OS定义的不同的函数处理。假设此时再其他异常的入口点(excNormalVec=0x80000180),若确定了是中断(C0_CAUSE:ExcCode=0),则进一步调用中断处理函数excIntStub;否则调用excStub。

<3>.准备返回:恢复现场,修改C0_SR,设置成安全模式(内核态,禁止异常),也就是异常发生后的模式。

<4>.从异常返回:控制权交给异常victim指令,将内核特权级恢复为较低的特权级。MIPS CPU提供了原子指令“eret”做这个工作:既清除C0_SR:EXL位,也将控制权返回给存储在EPC中的地址。

2 MIPS中断

CPU核外部的事件,即从一些真正的“硬件连线”过来的输入信号,这些就是中断。中断用于使CPU的注意力转向某外部事件:OS的一个基本特征就是可以同时注意多个事件。中断是唯一独立于CPU正常指令流的异常条件

MIPS CPU的协处理器0(CP0)主要完成对CPU、缓存控制、异常/中断控制、存储管理单元控制和其他一些功能配置。MIPS CPU对中断的支持涉及CP0的两个重要的寄存器:状态寄存器(C0_SR)和原因寄存器(C0_CAUSE)。

(1)使能全局中断(IE:Interrupt Enable)

要想使能中断,则全局中断位C0_SR:IE必须置1,它是一个全局开关。

(2)中断使能屏蔽(IM:Interrupt Mask)

C0_SR[15~8]为中断屏蔽位,对应IM[7~0],这8个bit位决定了哪些中断源有请求时可以触发一个异常,实际上是对中断信号的使能开关。8个中断源中的6个(IM[7~2])可用于外部硬件设备中断,其他2个(IM[1~0])对应C0_CAUSE:IP[1~0],为软件中断屏蔽位。所谓中断源就是产生硬中断信号的PIC外接设备或者软中断。

(3)异常级别(EXL:Exception Level)

异常发生后,CPU立即设置C0_SR:EXL,进入异常模式。异常模式强制CPU进入内核特权级模式并屏蔽中断,而不会理会C0_SR其他位的值。EXL位在已设置的情况下,还没有真正准备好调用主内核的例程。在这种状态下,系统不能处理其他异常。保持EXL足够的时间保存现场,使软件决定CPU新的特权级别和中断屏蔽位应该如何设置。

(4)异常类型(ExcCode:Exception Code)

PIC每个输入引脚上的有效(IM位为1)输入每个周期都会被采样,如果被使能,则引起一个异常。异常处理程序检查到C0_CAUSE:ExcCode=0,则说明发生的异常是中断,此时将进入通用中断处理程序。

(5)中断挂起反馈(IP:Interrupt Pending)

C0_CAUSE[15~8]为中断挂起状态位,用于指示哪些设备发生了中断,具体来说就是识别PIC的哪个接入引脚对应的设备发来了中断信号。IP[7~2]随着CPU硬件输入引脚上的信号而变化,而IP[1~0]为软件中断位,可读可写并存储最后写入的值。当C0_SR:IM[7~0]某些位使能,且硬中断或软中断触发时,C0_CAUSE:IP[7~0]对应位将被置位,一般通过查询C0_CAUSE:IP[7~2]的pending位,确定哪个(些)设备发生了中断。

(6)中断处理步骤(Interrupt Handle Procedure)

中断是异常的一种,所以中断处理只是异常处理的一条分流。经过上一层异常处理例程处理后,进入excIntStub。其处理步骤如下:

<1>.将C0_CAUSE:IP与C0_SR:IM进行逻辑与运算,获得一个或多个活跃且使能的中断请求。

<2>.选择一个活跃且使能的中断来处理,优先处理最高优先级的中断。

<3>.存储C0_SR:IM中的中断屏蔽位,不过很可能在上一层异常处理例程(excNormalVec)中已经保存过。改变C0_SR:IM,以保证禁止当前中断以及所有优先级小于等于本中断的中断在处理期间产生。

<4>.对于嵌套异常,如果在上一层异常处理例程中没有保护现场,则此时需要保护现场。

<5>.修改CPU到合适的状态以适应中断处理程序的高层部分,这时通常允许一些嵌套的中断或异常。

设置全局中断使能C0_SR:IE位,以允许处理高优先级的中断。还需要改变CPU特权级域(C0_SR:KSU),使得CPU处于内核态,清除C0_SR:EXL以离开异常模式,并把这些改动反映到状态寄存器中。

<6>.调用设备驱动程序注册的中断处理例程ISR。

<7>.恢复现场,retore相关寄存器,继续被中断的任务。

3 VxWorks/MIPS异常向量初始化

(1)位于ROM非缓存(kseg1)中的异常向量初始化

《VxWorks BSP for AMD's AU1500(MIPS)》的V100R001CPE\romMipsInit.s定义了上电复位时(C0_SR:BEV=1)ROM中的异常入口点。

[cpp]  view plain copy print ?
  1. #define RVECENT(f,n) \  
  2.     b f; nop  
  3. #define XVECENT(f,bev) \  
  4.     b f; li k0,bev  
  5.   
  6. promEntry:  
  7. romInit:  
  8. _romInit:  
  9.     /* MIPS_VECTOR_TABLE */  
  10.     RVECENT(__romInit,0)        /* PROM entry point */  
  11.     RVECENT(romReboot,1)        /* software reboot */  
  12.     RVECENT(romReserved,2)  
  13.     ...  
  14.     RVECENT(romReserved,63)  
  15.     XVECENT(romExcHandle,0x200) /* bfc00200: R4000 tlbmiss vector */  
  16.     ...  
  17.     RVECENT(romReserved,95)  
  18.     XVECENT(romExcHandle,0x300) /* bfc00300: R4000 cache vector */  
  19.     ...  
  20.     RVECENT(romReserved,111)  
  21.     XVECENT(romExcHandle,0x380) /* bfc00380: R4000 general vector */  
  22.     ...  
  23.     /* We hope there are no more reserved vectors! 
  24.      * 128 * 8 == 1024 == 0x400 
  25.      * so this is address R_VEC+0x400 == 0xbfc00400 
  26.      */  

《VxWorks BSP for AMD's AU1500(MIPS)》的V100R001CPE\ romInit.s中定义了romExcHandle(),其中k0为exception type,例如k0=0x380表示BEV_VEC。

/* romExcHandle- rom based exception/interrupt handler */

可见,ROM的前1KB安排给了中断向量。

(2)位于RAM缓存(kseg0)中的异常向量初始化

usrInit()->excVecInit()中,将excTlbVec()、excNormVec()等异常处理代码分别拷贝到0x80000000、0x80000180地址。可参考《VxWorks Source Code》,其注释中说“Allvectors from vector 0 (address 0x0000) to 255 (address 0x07f8) are initialized.”, 从RAM的底端从0x80000000到0x800007f8(大约4KB)安排给了中断向量,所以我们通常将RAM_LOW_ADRS设置为0x80001000

操作系统可调用sysToMonitor()跳转到ROM中的romReboot实现热启动。热启动实现了软件重启,不用重新初始化SDRAM等设备;冷启动则一切从头再来。

4 VxWorks/MIPS中断初始化

关于MIPS启动,可参考《MIPS体系结构透视》5.9节<启动>。

在《VxWorks BSP for AMD's AU1500(MIPS)》的V100R001CPE\V100R001CPE.h中定义了初始SR配置和默认SR配置:

[cpp]  view plain copy print ?
  1. #if (_BYTE_ORDER == _BIG_ENDIAN)  
  2.     /* initial status register */  
  3.     # define INITIAL_SR (SR_CU0 | SR_BEV)   
  4.     /* default status register at task level */  
  5.     # define DEFAULT_SR (SR_CU0 | SR_IMASK0 | SR_IE)  
  6. #else  
  7. #endif  

在《VxWorks BSP for AMD's AU1500(MIPS)》的V100R001CPE\sysLib.c的sysHwInit()中初始化先禁掉全局中断使能(SR_IE)。

[cpp]  view plain copy print ?
  1. /* init status register but leave interrupts disabled */  
  2. intSRSet (DEFAULT_SR & ~SR_IE);  
  3. /* !should clear IE bit, or you can not enter usrRoot! */  
  4. taskSRInit (DEFAULT_SR & ~SR_IE);  

然后待硬件设备初始化就绪,在sysHwInit2()中使能全局中断和8个中断源。

[cpp]  view plain copy print ?
  1. taskSRInit(DEFAULT_SR);  
关于SR_CU0、SR_BEV和SR_IMASK0

在《vxworks 6.x 的全部头文件》的h/arch/mips/archMips.h中可以找到它们的定义。

SR_CU0表示Coprocessor 0 usable,对CU0置位会得到用户特权级别的程序。

SR_BEV为启动异常向量(bootexception vectors),当BEV==1时,CPU用ROM(kseg1)空间的异常入口。启动之初,INITIAL_SR中置位BEV;启动之后,DEFAULT_SR中将BEV置零。

SR_IMASK0表示mask level 0,C0_SR[15~8](IM[7~0])全部置1使能。启动之后,DEFAULT_SR中使能SR_IE,然后设置SR_IMASK0,使能8个中断源响应。

在讲述定时器中断的相关处理之前,先来熟悉一下频率和时间的概念。

5 主频=外频x倍频

CPU内部没有振荡器,而是依靠外部的晶振电路(ReferenceCrystal Oscillator Frequency Synthesizer)提供基准时钟信号,这个外部晶振提供给CPU的时钟频率称之为外频(externalclock)。可以将晶振时钟源看作是SoC系统的心脏,只有心脏跳动起来,SoC才有脉搏进而驱动数字逻辑掉路。

为了降低电磁干扰和降低板间布线要求,芯片外接的晶振频率通常很低(比如25MHz),可以通过钟控制逻辑的锁相环PLL(Phase Lock Loop)提高系统时钟,即我们通常所说的倍频技术。

CPU主频率就是CPU流水线频率(the CPU’s pipeline clock rate),即每秒能够处理的指令条数,它决定了处理器的运算速度。CPU的主频可由晶振提供的频率经过CPU内部的PLL倍频得到,一般有主频=外频×倍频。同样,前端总线(FSB)的频率也是这么得到的。

以下给出一种可能的SoC频率配置。假设Reference Crystal Oscillator Frequency=25MHz,降频系数Mdiv=1,则参考频率fref=25MHz。假设倍频系数为32,则VCO frequency为800MHz。假设Ndiv=2,则分频后的PLL frequency=400MHz。

《s3c2440的时钟详解》中给出了S3C2440A datasheet中一段关于FCLK/AHB/APB的描述:

--------------------------------------------------------------

The Clock control logic in S3C2440A can generate the required clock signals including FCLK for CPU, HCLK for the AHB bus peripherals, andPCLK for the APB bus peripherals.

FCLKCPU用的;HCLKAHB总线用的,该slave其服务的master包括SDRAM/USB/MAC/DMA;PCLKAPB总线用的,该slave服务的master包括SPI Flash/ROM/GPIO/UART/hardware timer/watchdog timer。

--------------------------------------------------------------

假设CPU的分频系数(division ratio)为1,FCLK=MPLL=400MHz;假设AHB的分频系数为2,则HCLK=FCLK/2=200MHz。

CPU上的计时器:高精度时间戳部件(Time Stamp Counter,TSC)

高精度时间戳主要用于剖析和监测代码,在某些应用场合,例如基于令牌桶的IP带宽控制中的时间片度量可能微秒级别的计时分辨率,此时需要高精度时钟戳支持。

Intel Pentium级以上的CPU都提供了时间戳计数器部件,用于记录自启动以来处理器消耗的时钟周期数。MIPS CPU提供了计数/比较寄存器(Count/Compare),这两个寄存器提供一个简单的连续运行的通用内部计数器,可以通过编程来导致中断。Count是一个不停计数的32位寄存器,计数的频率与CPU流水线的时钟周期相同,也可以是一半或是其他分频比例。

6 初始化硬件Timer

内核的许多工作都高度依赖于时间信息,例如中断底半部基于时间的任务延期处理。这种将在一定时间之后发生的事情就是典型的超时应用,而定时则主要是基于高分辨的应用。

上面梳理了频率和时间的概念,下面来看一下时间控制系统的底层硬件支持。典型的系统都会有提供定时功能的时钟芯片。例如IA-32和AMD64系统有一个PIT(Programmable Interrupt Timer,可编程中断计时器,由8235芯片实现)。单片机MCU和嵌入式SoC芯片往往会提供多个通用hardware timer和watchdog timer。VxWorks一般在BSP标准例程sysLib.c\sysHwInit()中配置硬件Timer寄存器参数。

假设CPU Clock Frequency为400MHz,Peripheral APB Bus Clock Frequency为200MHz,若将分频寄存器Division(有的芯片有这个Timer Control寄存器)配置为200,则Timer的频率为1MHz,计时周期为1us。接下来要配置计时寄存器(Timer Value),它将决定timer发起中断的频率。这里涉及到一个重要的参数——SYS_CLK_RATE,通过usrRoot()中的sysClkRateSet()设置,它表示每秒的时间片数量(ticksPerSecond)。默认=SYS_CLK_RATE=60,则timeout=1MHz/sysClkRateGet()=1/60MHz将配置到计时寄存器,每次timeout时将发起一次中断,每秒产生60次时钟中断。时钟中断的间隔为1/60s,这个时间间隔就是VxWorks下的时钟嘀嗒——tick。

在概念上,VxWorks中的SYS_CLK_RATE对应Linux中的HZ,VxWorks中的tick对应Linux中的jiffy(jiffies)。

7 挂接定时器中断

(1)为定时器中断分配IRQ向量号

《VxWorks BSP for AMD's AU1500(MIPS)》的V100R001CPE\au1500Int.c中定义了PRIO_TABLE intPrioTable[]:

[cpp]  view plain copy print ?
  1. #define IV_TIMER_VEC    70  
  2. #define IV_UART0_VEC    90  
  3.   
  4. typedef struct   
  5. {  
  6.     ULONG   intCause;       /* cause of interrupt   */  
  7.     ULONG   bsrTableOffset; /* index to BSR table   */  
  8.     ULONG   statusReg;      /* mask bit */  
  9.     ULONG   pad;            /* intNum or demux routine? */  
  10. } PRIO_TABLE;  
  11.   
  12. PRIO_TABLE intPrioTable[] =   
  13. {  
  14.     {CAUSE_SW1,(ULONG) IV_SWTRAP0_VEC,  0x0100, 0}, /* sw trap 0 */  
  15.     {CAUSE_SW2,(ULONG) IV_SWTRAP1_VEC,  0x0200, 0}, /* sw trap 1 */  
  16.     {CAUSE_IP3,(ULONG) sysCtrl0Req0IntDemux,0x0400, 1}, /* DeMultiplex */  
  17.     {CAUSE_IP4,(ULONG) sysCtrl0Req1IntDemux,0x0800, 1}, /* DeMultiplex */  
  18.     {CAUSE_IP5,(ULONG) sysCtrl1Req0IntDemux,0x1000, 1}, /* DeMultiplex */  
  19.     {CAUSE_IP6,(ULONG) sysCtrl1Req1IntDemux,0x2000, 1}, /* DeMultiplex */  
  20.     {CAUSE_IP7,(ULONG) IV_HW4_VEC,      0x4000, 0}, /* Available */  
  21.     {CAUSE_IP8,(ULONG) IV_TIMER_VEC,    0x8000, 0}  /* Timer share */  
  22. };  
PRIO_TABLE::intCause为cause ofinterrupt,对应C0_CAUSE:IP[7~0],PRIO_TABLE::statusReg为其掩码位。

intPrioTable[0~1]软中断,对应C0_CAUSE:IP[1~0]。intPrioTable[2~5]中的PRIO_TABLE::pad均为1,则说明为中断共享,PRIO_TABLE::bsrTableOffset存放的不是intNum,而是demux routine。所谓demux就是解复用。intPrioTable[6~7]中的PRIO_TABLE::pad为0,则说明为普通中断。intPrioTable[]是硬件相关的,外设中断源与C0_CAUSE:IP[7~2]的对应关系取决于硬件中断源与PIC输入引脚的连线顺序。

VxWorks一般在sysHwInit()中初始化IRR寄存器,将所有SoC硬件定义的中断源(Interrupt Source)映射到控制寄存器CAUSE_IP[7~2]这六位。intPrioTable[]体现了IRR建立的硬件中断源(device)与CPU中断使能/状态位(C0_CAUSE:IP[7~2])的映射关系,同时建立了CPU中断使能/状态位(C0_CAUSE:IP[7~2])与IRQ向量号(vector)之间的映射关系。

(2)挂接定时器中断到中断向量表

<1>中断向量表——excBsrTbl[]

intPrioTable[7]定义了Timer的中断映射,PRIO_TABLE::intCause=C0_CAUSE:IP[7]为中断源编号,PRIO_TABLE::bsrTableOffset=IV_TIMER_VEC为IRQ向量号(vector),它是BSR table的索引(index to BSR table)。那么什么是BSR table呢?这里的BSRtable就是通常所说的中断向量表。

在VxWorks 5.5的源代码excArchLib.c中可以看到excBsrTbl的定义:

[cpp]  view plain copy print ?
  1. VOIDFUNCPTR excBsrTbl[] =   
  2. {  
  3.     excIntHandle,  /* 0 - interrupt exception */  
  4.     excExcHandle,  /* 1 - tlb mod exception */  
  5.     ...  
  6.     excIntHandle, /* 70 - timer 0 interrupt */  
  7.     excIntHandle, /* 71 - timer 1 interrupt */  
  8.     ...  
  9.     excIntHandle, /* 90 - uart 0 interrupt */  
  10.     excIntHandle, /* 91 - uart 1 interrupt */  
  11.     ...  
  12.     excIntHandle, /* 255 */  
  13. };  
基本上,excBsrTbl[256]是一个函数指针数组,初始化元素只有excExcHandle、excIntHandle两种选项,它们是对没有安装处理程序的异常和中断的默认处理。此时针对timer0的默认处理是excIntHandle。除了前面预留的一些向量位置,可安装设备IRQ处理程序到其他向量表槽位。

<2>挂接定时器时钟中断

IV_TIMER_VE意即TimerInterrupt Vector,除了在intPrioTable中看到了它,在《VxWorks BSP for AMD's AU1500(MIPS)》的V100R001CPE\sysLib.c中可以看到sysHwInit2()中的intConnect()调用也涉及到了IV_TIMER_VE。

[cpp]  view plain copy print ?
  1. sysHwInit2()  
  2. {  
  3.       
  4.     /* connect sys clock and aux clock interrupts */  
  5.     (void) intConnect (INUM_TO_IVEC(IV_TIMER_VEC), sysClkInt, 0);  
  6.       
  7. }  
顾名思义,intConnect意即中断挂接,有点类似Linux中的request_irq。那么这个中断到底挂接到哪里去了,当发生时钟中断时,是如何调度到sysClkInt()的呢?

查看intArchLib.c找到了intConnect()原型:

[cpp]  view plain copy print ?
  1. /* 
  2. * intConnect - connect a C routine to a hardware interrupt 
  3. * 
  4. * This routine connects a specified C routine to a specified  
  5. * interrupt vector.  The address of <routine> is stored at <vector> 
  6. */  
  7. STATUS intConnect  
  8.     (  
  9.     VOIDFUNCPTR *vector,    /* interrupt vector to attach to */  
  10.     VOIDFUNCPTR routine,    /* routine to be called */  
  11.     int parameter       /* parameter to be passed to routine */  
  12.     )  
  13. {  
  14.     FUNCPTR intDrvRtn = intHandlerCreate ((FUNCPTR) routine, parameter);  
  15.       
  16.     if (intDrvRtn == NULL)  
  17.         return (ERROR);  
  18.       
  19.     /* make vector point to synthesized code */  
  20.     intVecSet ((FUNCPTR *) vector, (FUNCPTR) intDrvRtn);  
  21.       
  22.     return (OK);  
  23. }  
在intConnect()中,首先通过 intHandlerCreate()为用户提供的回调函数与指针分配(malloc)一小块内存。这块内存中存放的是5条指令(intConnectCode),用于保存中处理函数与参数的地址,以及一条跳转到该中断处理函数地址的指令。然后,调用 intVecSet()将这段内存的地址设置到excBsrTbl[vec],其地址为(int)excBsrTbl+vec*4,宏INUM_TO_IVEC实现了右移2。intVecSet()有点类似Linux中的set_except_vector()。

sysHwInit2()调用intConnect()后,excBsrTbl[IV_TIMER_VEC]=sysClkInt()。

<3>时钟中断处理例程

intConnect()指定timer0的ISR为sysClkInt(),其中调用sysClkRoutine。

《VxWorksBSP for AMD's AU1500(MIPS)》的V100R001CPE\bootConfig.c中定义了usrRoot(),其中调用sysClkConnect()指定了最终的时钟中断处理例程。

[cpp]  view plain copy print ?
  1. STATUS  sysClkRateSet (int ticksPerSecond);  
  2.     int ticksPerSecond  /* number of clock interrupts per second */  
  3.   
  4. usrRoot()  
  5. {  
  6.   
  7.     /* set up system timer */  
  8.     sysClkConnect ((FUNCPTR) usrClock, 0);  /* connect clock ISR */  
  9.     sysClkRateSet (SYS_CLK_RATE);       /* set system clock rate */  
  10.     sysClkEnable ();            /* start it */  
  11.   
  12. }  

sysClkConnect()指定sysClkRoutine为usrClock()。sysClkEnable()使能中断后,每隔1个tick,硬件timer发起一次中断,最终回调usrClock()响应时钟中断。

8 VxWorks中断调度

(1)中断响应与分发

usrInit()->excVecInit()中,将excTlbVec()、excNormVec()等异常处理代码分别拷贝到0x80000000、0x80000180地址。(这里的excVecInit()对应于Linux中的trap_init(),excNormVec对应Linux中的except_vec3_generic。)

中断到来时,在入口点excNormVec(0x80000180)中会根据CAUSE_ExcCode位判断是否为中断类型的异常(ExcCode位0),如果否则跳转到excStub;如果是则跳转到excIntStub()(对应于Linux中的handle_int()->irq_dispatch()->...->do_IRQ()),在该函数除了保存以及恢复中断现场之外,主要做了以下工作:

<1>. 通过CAUSE和SR判断产生中断的中断源;

<2>. 将CAUSE[IP0~7]的值作为sysHashOrder表(ffsMsbTbl[256])的下标,可以得到优先执行的中断源号码;

<3>. 将以上中断源号码作为intPrioTable[]表的下标,可以得到该中断源对应的异常向量偏移vec(相对于excBsrTbl);(这里的excBsrTbl对应Linux中的exception_handlers。)

<4>. 跳转到以上偏移地址处(excBsrTbl+vec*4)所保存的值的地址,可以得到5条运行时(通过intConnect())构建的指令;

<5>. 这5条指令中包含了intConnect()运行时所注册的中断处理函数及其地址,并跳转到该函数去执行;

VxWorks/MIPS中断处理机制_第2张图片

(2)中断处理流程

Linux中的struct irq_desc irq_desc[NR_IRQS]相当于intPrioTable[],这两个管理IRQ的全局数组的size为硬件支持的中断源数量,MIPS中为8,IA-32中为16。

Linux对于共享的IRQ设置irq_desc::irqaction::flags=IRQF_SHARED,然后通过irq_desc::irqaction::next指针将IRQ处理程序链化。当有对应IRQ中断发生时,将调用irq_desc[irq]::irqaction,检测到IRQF_SHARED,则依次next调用各共享中断设备的IRQ处理程序。在每个irqaction中通过GIMR&GISR判决该设备是否为实际的中断来源,如果是则进一步action处理该中断。

VxWorks所有设备都通过intConnect()指定一个IRQ(vector)并将ISR挂接到全局数组excBsrTbl[]中。中断向量表excBsrTbl[]包含了所有的异常和中断处理例程入口地址,IRQ(vector)用于索引。PRIO_TABLE::intCause/PRIO_TABLE::intMask和PRIO_TABLE:: bsrTableOffset建立了中断源与IRQ号(vector)的映射关系。当多个设备共享中断时,PRIO_TABLE::pad参数置1表示中断复用,参数二指定解复用例程,而非IRQ(Vector),需要解复用获得实际发生了中断的设备IRQ(vector)。

当有中断发生时,excIntStub()将通过C0_CAUSE:IP位查找intPrioTable[],通过PRIO_TABLE::pad判决是否为共享中断,如果是则首先调用解复用例程,通过GIMR&GISR判决是哪个设备发生了中断,返回相应IRQ(vector)。如果非共享中断,则无需解复用,直接通过IRQ(vector)索引excBsrTbl[]找到当初注册的ISR并调用。

9 tickAnnounce

当timer计时到期(1tick=1/60s),将会发起时钟中断。经中断路由和中断调度后,最终回调usrClock()函数。

《VxWorksBSP for AMD's AU1500(MIPS)》的V100R001CPE\bootConfig.c中定义了usrClock():

[cpp]  view plain copy print ?
  1. /*usrClock - user defined system clock interrupt routine*/  
  2. void usrClock (void)  
  3. {  
  4.     tickAnnounce ();    /* announce system tick to kernel */  
  5. }  

tickAnnounce代码参考《VxWorks Source Code》。

[cpp]  view plain copy print ?
  1. /* 
  2. * tickAnnounce - announce a clock tick to the kernel 
  3. * 
  4. * This routine informs the kernel of the passing of time.  It should be called  
  5. * from an interrupt service routine that is connected to the system clock.   
  6. */  
  7. void tickAnnounce (void);  

tickAnnounce-将调用windTickAnnounce,代码参考《VxWorks Source Code》。

[cpp]  view plain copy print ?
  1. /* 
  2. * windTickAnnounce - acknowledge the passing of time 
  3. * 
  4. * Process delay list.  Make tasks at the end of their delay, ready. 
  5. * Perform round robin scheduling if selected. 
  6. * Call any expired watchdog routines. 
  7. */  
  8. void windTickAnnounce (void);  

每隔1个tick,将announce aclock tick to the kernel,windTickAnnounce中将递增全局变量vxAbsTicks(类似Linux中的全局变量jiffies),这个全局变量记录了自最近一次系统启动成功初始化时钟中断以来的时钟嘀嗒数(absolute time since power on in ticks)。我们可以通过tickGet()获取返回它。时间嘀嗒是任务调度的时间片度量单位(the system time-slice),有了tick脉动以后,就可以开展基于时间片的任务调度以及定时延时等操作。

作为高性能的嵌入式硬实时操作系统,任务调度是VxWorks的核心模块。VxWorks的wind内核缺省调度机制为基于优先级的抢占式调度,在相同优先级的多个任务之间,则采用时间片轮转调度机制。关于VxWorks的硬实时特性和任务调度机制,可参考《什么是真正的实时操作系统》。


参考:

《MIPS体系结构透视》

第3章 协处理器0:MIPS处理器控制

第5章 异常、中断和初始化

14.1 中断的生命周期

《VxWorks内核、设备驱动与BSP开发详解》

10.3 深入:任务切换的深层原因

第12章 控制时间——系统计时

《VxWorks程序开发实践》

第4章 多任务环境

 

《电脑的脉搏—时钟频率》

《锁相环PLL》《锁相环(PLL)基本原理、设计与应用》

《s3c2440的时钟详解》《s3c2440定时器中断的应用》

《PLL,FCLK、HCLK、PCLK,AHB/APB(S3C2410)》

《how does frequency divider influenceclock jitter?》

 

《中断处理》中断屏蔽


《MIPS体系架构》Bring up VxWorks on MIPS Core


《VxWorks中断处理》

《VxWorks启动代码usrInit()函数分析》

《VxWorks的异常处理过程(以MIPS为例)》

《VxWorks实时操作系统中的中断处理机制分析》

《基于嵌入式操作系统VxWorks的多任务并发程序设计(5)——中断与任务》


《x86 Linux中断处理源码情景分析》 

《Linux内核开发之中断与时钟》


《MIPS Linux异常中断代码分析》

《简述MIPS中断(AG7240的中断初始化)

《AR9344(MIPS)中ethernet处理方式分析》

《AR9344(MIPS)中断处理流程》

你可能感兴趣的:(VxWorks/MIPS中断处理机制)