uc/os软件中断与硬件中断处理流程分析
===================================================================================
=====================软件中断===================================================================================
SWI(software interrupt)软件中断,由用户定义的中断指令.可以用于用户模式下的程序调用特权操作指令.在实时操作系统中可以通过该机制实现系统调用.
一个 SWI 所做的一切就是把模式改变成超级用户并设置 PC 来执行在地址 &08 处的下一个指令!
编程异常通常叫做软中断.软中断是通讯进程之间用来模拟硬中断的一种信号通讯方式。中断源发中断请求或软中断信号后,CPU或接收进程在适当的时机自动进行中断处理或完成软中断信号对应的功能.
软中断是软件实现的中断,也就是程序运行时其他程序对它的中断;而硬中断是硬件实现的中断,是程序运行时设备对它的中断。
1.软中断发生的时间是由程序控制的,而硬中断发生的时间是随机的
2.软中断是由程序调用发生的,而硬中断是由外设引发的
3.硬件中断处理程序要确保它能快速地完成它的任务,这样程序执行时才不会等待较长时间
2. 软件中断使用的始末
2.1 SWI指令
SWI{cond} immed_24
其中,immed_24位24位立即数
2.2 ADS编译器中软中断的规定
ADS编译器规定,用户可使用关键字__swi作为前缀来声明一个利用软中断的调用,其格式如下:
__swi(功能号) 返回值类型名称(参数列表);
其中关键字__swi后面的括号中的字段叫做软中断的功能编号。系统是根据这个编号在软中断服务程序户中来确定应执行的程序段的。用户可以在用户程序中像调用一个普通函数那样实现软中断的调用。只是该“函数”没有普通函数那样明显的函数实现体。至于软件中断具体的函数实体后面会讲到。
利用软中断服务程序可以规避由于ARM处在不同工作模式时所造成的访问限制(如资源的访问限制)。
2.3 在Uc/OS-II中软中断来实现的函数的声明
函数注册软中断号
__swi(0x00) void OS_TASK_SW(void); /* 任务级任务切换函数 */
__swi(0x01) void __OSStartHighRdy(void); /* 运行优先级最高的任务 */
__swi(0x02) void OS_ENTER_CRITICAL(void); /* 关中断 */
__swi(0x03) void OS_EXIT_CRITICAL(void); /* 开中断 */
__swi(0x40) void *GetOSFunctionAddr(int Index); /* 获取系统服务函数入口 */
__swi(0x41) void *GetUsrFunctionAddr(int Index); /* 获取自定义服务函数入口 */
__swi(0x42) void OSISRBegin(void); /* 中断开始处理 */
__swi(0x43) int OSISRNeedSwap(void); /* 判断中断是否需要切换 */
__swi(0x80) void ChangeToSYSMode(void); /* 任务切换到系统模式 */
__swi(0x81) void ChangeToUSRMode(void); /* 任务切换到用户模式 */
__swi(0x82) void TaskIsARM(INT8U prio); /* 任务代码是ARM代码 */
__swi(0x83) void TaskIsTHUMB(INT8U prio); /* 任务代码是THUMB */
这些函数不是通常意义上的函数,只是可以让用户在应用程序中使用这些“函数名”去引发一个携带功能号的软中断SWI,即ADS编译器在编译这些函数时,把他们编译成SWI指令的二进制代码。
注:软中断功能号为0x00、0x01的函数使用汇编语言写的,而其他的函数是用c语言写的。具体代码见下面。
注:遵照ATPCS函数调用标准,一个SWI调用允许带1~4个字型参数和1~4个字型返回值,触发SWI调用时四个参数依次保存在R0~R3中,返回值也存于R0~R3内。所以在软中断服务程序中应函数所需要的参数按顺序保存到R0~R3中。
2.4 软中断具体实现始末
以两个具体的函数调用来说明软中断具体的实现。一个是任务级切换函数void OS_TASK_SW(void),一个是任务模式切换函数void ChangeToSYSMode(void).
2.4.1. 从启动代码中开始
硬件平台为ARM7内核。当有软中断发生(即调用2.2中某一个函数时)时,系统首先自动调转到0x0008处执行。
1、第一级中断向量
AREA Init,CODE,READONLY
ENTRY
b ResetHandler ;for debug
b HandlerUndef ;handlerUndef
b HandlerSWI ;SWI interrupt handler
b HandlerPabort ;handlerPAbort
b HandlerDabort ;handlerDAbort
b . ;handlerReserved
b HandlerIRQ
b HandlerFIQ
2、宏定义(2440init.s)
//HANDLER为宏定义名,当调用该宏定义时候,都进入到该函数,相当于函数名为HandlerLanble,参数为HandleLable
//该标号前面有$,即表示有调用该宏的时候,替换HANDLER的前后。
MACRO
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel ;标号,如果前面有“b HandlerIRQ”且宏展开有“HandlerIRQ HANDLER HandleIRQ”
;该处立即表示HandlerIRQ,立即跳转到此处
sub sp,sp,#4 ;减少sp(用于存放转跳地址)
stmfd sp!,{r0} ;把工作寄存器压入栈
ldr r0,=$HandleLabel ;将HandleXXX的址址放入r0
ldr r0,[r0] ;把HandleXXX所指向的内容(也就是中断程序的入口)放入r0
;如果前面有“b HandlerIRQ”且宏展开有“HandlerIRQ HANDLER HandleIRQ”,立即跳转到HandleIRQ处(为具体该中断处理函数)。
str r0,[sp,#4] ;把中断服务程序(ISR)压入栈
ldmfd sp!,{r0,pc} ;用出栈的方式恢复r0的原值和为pc设定新值(也就完成了到ISR的转跳)
MEND
3、宏展开
继续找HandlerSWI。
HandlerSWI HANDLER HandleSWI ;//当有“b HandlerSWI”,立即跳转到HandleSWI
4、中断入口函数地址(opthion.h)
#define _RAM_STARTADDRESS 0x30000000
#define _ISR_STARTADDRESS 0x33ffff00
5、中断处理中不同的函数入口地址(OS_CPU.S)
// Exception vector
#define pISR_RESET (*(unsigned *)(_ISR_STARTADDRESS+0x0))
#define pISR_UNDEF (*(unsigned *)(_ISR_STARTADDRESS+0x4))
#define pISR_SWI (*(unsigned *)(_ISR_STARTADDRESS+0x8))
#define pISR_PABORT (*(unsigned *)(_ISR_STARTADDRESS+0xc))
#define pISR_DABORT (*(unsigned *)(_ISR_STARTADDRESS+0x10))
#define pISR_RESERVED (*(unsigned *)(_ISR_STARTADDRESS+0x14))
#define pISR_IRQ (*(unsigned *)(_ISR_STARTADDRESS+0x18))
#define pISR_FIQ (*(unsigned *)(_ISR_STARTADDRESS+0x1c))
// Interrupt vector
#define pISR_EINT0 (*(unsigned *)(_ISR_STARTADDRESS+0x20))
#define pISR_EINT1 (*(unsigned *)(_ISR_STARTADDRESS+0x24))
#define pISR_EINT2 (*(unsigned *)(_ISR_STARTADDRESS+0x28))
#define pISR_EINT3 (*(unsigned *)(_ISR_STARTADDRESS+0x2c))
#define pISR_EINT4_7 (*(unsigned *)(_ISR_STARTADDRESS+0x30))
#define pISR_EINT8_23 (*(unsigned *)(_ISR_STARTADDRESS+0x34))
#define pISR_CAM (*(unsigned *)(_ISR_STARTADDRESS+0x38)) // Added for 2440.
#define pISR_BAT_FLT (*(unsigned *)(_ISR_STARTADDRESS+0x3c))
#define pISR_TICK (*(unsigned *)(_ISR_STARTADDRESS+0x40))
#define pISR_WDT_AC97 (*(unsigned *)(_ISR_STARTADDRESS+0x44)) //Changed to pISR_WDT_AC97 for 2440A
#define pISR_TIMER0 (*(unsigned *)(_ISR_STARTADDRESS+0x48))
#define pISR_TIMER1 (*(unsigned *)(_ISR_STARTADDRESS+0x4c))
#define pISR_TIMER2 (*(unsigned *)(_ISR_STARTADDRESS+0x50))
#define pISR_TIMER3 (*(unsigned *)(_ISR_STARTADDRESS+0x54))
#define pISR_TIMER4 (*(unsigned *)(_ISR_STARTADDRESS+0x58))
#define pISR_UART2 (*(unsigned *)(_ISR_STARTADDRESS+0x5c))
#define pISR_LCD (*(unsigned *)(_ISR_STARTADDRESS+0x60))
#define pISR_DMA0 (*(unsigned *)(_ISR_STARTADDRESS+0x64))
#define pISR_DMA1 (*(unsigned *)(_ISR_STARTADDRESS+0x68))
#define pISR_DMA2 (*(unsigned *)(_ISR_STARTADDRESS+0x6c))
#define pISR_DMA3 (*(unsigned *)(_ISR_STARTADDRESS+0x70))
#define pISR_SDI (*(unsigned *)(_ISR_STARTADDRESS+0x74))
#define pISR_SPI0 (*(unsigned *)(_ISR_STARTADDRESS+0x78))
#define pISR_UART1 (*(unsigned *)(_ISR_STARTADDRESS+0x7c))
#define pISR_NFCON (*(unsigned *)(_ISR_STARTADDRESS+0x80)) // Added for 2440.
#define pISR_USBD (*(unsigned *)(_ISR_STARTADDRESS+0x84))
#define pISR_USBH (*(unsigned *)(_ISR_STARTADDRESS+0x88))
#define pISR_IIC (*(unsigned *)(_ISR_STARTADDRESS+0x8c))
#define pISR_UART0 (*(unsigned *)(_ISR_STARTADDRESS+0x90))
#define pISR_SPI1 (*(unsigned *)(_ISR_STARTADDRESS+0x94))
#define pISR_RTC (*(unsigned *)(_ISR_STARTADDRESS+0x98))
#define pISR_ADC (*(unsigned *)(_ISR_STARTADDRESS+0x9c))
6、内存第二级中断向量(2440INIT.S)
再找HandleSWI。
AREA RamData, DATA, READWRITE ; 在内存中分配一数据段
^ _ISR_STARTADDRESS ; _ISR_STARTADDRESS=0x33FF_FF00 ,"^"相当于MAP,定义一个结构化内存表的首地址
HandleReset # 4 ;"#"相当于FIELD,结构化内存表的数据域,4个字节,“#”仅仅是定义结构,并不分配内存单元
HandleUndef # 4
HandleSWI # 4
HandlePabort # 4
HandleDabort # 4
HandleReserved # 4
HandleIRQ # 4
HandleFIQ # 4
;Don''t use the label 'IntVectorTable',
;The value of IntVectorTable is different with the address you think it may be.
;IntVectorTable
;@0x33FF_FF20
HandleEINT0 # 4
HandleEINT1 # 4
HandleEINT2 # 4
HandleEINT3 # 4
HandleEINT4_7 # 4
HandleEINT8_23 # 4
HandleCAM # 4 ; Added for 2440.
HandleBATFLT # 4
HandleTICK # 4
HandleWDT # 4
HandleTIMER0 # 4
HandleTIMER1 # 4
HandleTIMER2 # 4
HandleTIMER3 # 4
HandleTIMER4 # 4
HandleUART2 # 4
;@0x33FF_FF60
HandleLCD # 4
HandleDMA0 # 4
HandleDMA1 # 4
HandleDMA2 # 4
HandleDMA3 # 4
HandleMMC # 4
HandleSPI0 # 4
HandleUART1 # 4
HandleNFCON # 4 ; Added for 2440.
HandleUSBD # 4
HandleUSBH # 4
HandleIIC # 4
HandleUART0 # 4
HandleSPI1 # 4
HandleRTC # 4
HandleADC # 4
;@0x33FF_FFA0
END
现在我们知道软中断的服务程序跑到了内存中去了,我们可以编写相应的代码来实现不同功能号下的不同功能。
7、在文件OS_CPU_A.S中编写软中断服务程序
HandleSWI
LDR sp, StackSvc;
STMFD sp!, {r0-r3, r12, lr};// STMDB,保护现场
MOV r1, sp ;// 若SWI调用带参,将R1指向第二个参数
;// 遵照ATPCS标准,第一个参数存于R0中
MRS r3, spsr
TST r3, #0x20 ;//检查时ARM还是THUMB指令
STMFD sp!, {r0} ;// spsr入栈
LDRNEH r0, [lr,# -2] ;// THUMB指令时执行,获取SWI指令码
BICNE r0, r0, #0xFF00 ;// 获取SWI number
;// r0 now contains SWI number
;// r1 now contains pointer to stacked registers
LDREQ r0, r0, #0xFF000000;//ro中存放着软中断的功能号
CMP r0, #1;
LDRLO pc, =OSIntCtxSw ;//LDRLO=LDRCC r0=0时跳转到汇编语言处理
LDREQ pc, =__OSStartHighRdy ;//r0=1时跳转到汇编语言处理
BL SWI_Exception ;// r0>1时跳转,调用C编写的SWI处理函数
LDMFD sp!, {r0-r3, r12, pc}^ ;// 恢复现场
我们可以看出当调用2.2中某一个函数并且函数具有两个参数时,函数的第一个参数存放在r0中,第二个参数存放在r1中。
8、在文件OS_CPU_C.C中编写软中断服务程序
为了使uC/OS-II的一些底层系统函数在调用时与处理器工作模式无关,所以在移植时采用软中断来实现.除了功能号01、02的软中断函数是用汇编语言,其他的软中断函数都是用C语言编写的.
void SWI_Exception( int SWI_Num,//软中断功能号
int *Regs //为指向堆栈中保存寄存器的值的指针
)
{
OS_TCB *ptch;
Switch(SWI_Num)// 具体代码间参考文献[1]P214
{
case 0x02://宏OS_ENTER_CRITICAL()的代码
case 0x03://宏OS_EXIT_CRITICAL()的代码
case 0x80://宏ChangeToSYSMode ()的代码
case 0x81://宏ChangeToUSRMode ()的代码
case 0x82://宏TaskIsARM ()的代码
case 0x83://宏TaskIsTHUMB ()的代码
default:break;
}
}
===================================================================================
=============================================硬件中断
===================================================================================
1、初始化HandleIRQ地址,系统复位定义
; Setup IRQ handler,//因为IRQ的不同处理就是通过这个程序段实现的,具体的看IsrIRQ程序
ldr r0,=HandleIRQ ;This routine is needed
;ldr r1,=IsrIRQ ;if there isn''t 'subs pc,lr,#4' at 0x18, 0x1c
ldr r1, =OS_CPU_IRQ_ISR ;modify by txf, for ucos ,//HandleIRQ的(中断处理函数)指向具体中断处理函数
str r1,[r0]
2、有硬件中断后执行(2440init.s)
b HandlerIRQ
3、根据宏展开(2440init.s)
HandlerIRQ HANDLER HandleIRQ
执行宏定义中,跳转到HandleIRQ,而HandleIRQ指向了OS_CPU_IRQ_ISR汇编函数
4、调用OS_CPU_IRQ_ISR(os_cpu_a.s)
OS_CPU_IRQ_ISR
;0.//保存IRQ模式堆栈地址和部分寄存器值
STMFD SP!, {R1-R3} ; We will use R1-R3 as temporary registers
;----------------------------------------------------------------------------
; R1--SP
; R2--PC
; R3--SPSR
;------------------------------------------------------------------------
MOV R1, SP
ADD SP, SP, #12 ;Adjust IRQ stack pointer
SUB R2, LR, #4 ;Adjust PC for return address to task
MRS R3, SPSR ; Copy SPSR (Task CPSR)
;1.// 保存中断前任务的寄存器(需要切换到中断前任务的运行模式SVC模式),压栈后,task栈存储: CPSR,R0->R12,LR,PC;
MSR CPSR_cxsf, #SVCMODE|NOINT ;Change to SVC mode
; SAVE TASK''S CONTEXT ONTO OLD TASK''S STACK
STMFD SP!, {R2} ; Push task''s PC
STMFD SP!, {R4-R12, LR} ; Push task''s LR,R12-R4
LDMFD R1!, {R4-R6} ; Load Task''s R1-R3 from IRQ stack
STMFD SP!, {R4-R6} ; Push Task''s R1-R3 to SVC stack
STMFD SP!, {R0} ; Push Task''s R0 to SVC stack
STMFD SP!, {R3} ; Push task''s CPSR
LDR R0,=OSIntNesting ;OSIntNesting++
LDRB R1,[R0]
ADD R1,R1,#1
STRB R1,[R0]
CMP R1,#1 ;if(OSIntNesting==1){ ;//第一次中断
BNE %F1
;2.//保存中断前任务的SP到OSTCBCur中;
LDR R4,=OSTCBCur ;OSTCBHighRdy->OSTCBStkPtr=SP;
LDR R5,[R4]
STR SP,[R5] ;}
;3.//切换到中断模式
1
MSR CPSR_c,#IRQMODE|NOINT ;Change to IRQ mode to use IRQ stack to handle interrupt
LDR R0, =INTOFFSET ;//得到中断偏移量值
LDR R0, [R0]
;4.//处理中断函数
LDR R1, IRQIsrVect ;//中断0向量的入口地址
MOV LR, PC ;//保存PC值 ; Save LR befor jump to the C function we need return back
LDR PC, [R1, R0, LSL #2] ; Call OS_CPU_IRQ_ISR_handler(); 跳转到具体的中断入口地址(看下面5、6)
;5.//退出中断,切换到SVC模式
MSR CPSR_c,#SVCMODE|NOINT ;Change to SVC mode
BL OSIntExit ;Call OSIntExit;//如果退出了所有中断,则调用任务切换
;6.//恢复堆栈,并中断处理完返回
LDMFD SP!,{R4} ;POP the task''s CPSR
MSR SPSR_cxsf,R4
LDMFD SP!,{R0-R12,LR,PC}^ ;POP new Task''s context
5、分配一个内存,指向第一个中断入口地址(os_cpu_a.s)
IRQIsrVect DCD HandleEINT0 ;//分配一个内存单元,并等于HandleEINT0.即由IRQIsrVect
6、函数关联入口地址(中断初始化)
pISR_TIMER0= (uint32) OSTickISR;
pISR_TIEMR0在软件中断中已见定义指针,这里初始化具体指向函数
7、处理中断
OSTickISR
MOV R5,LR
MOV R1, #1
MOV R1, R1, LSL #10 ; Timer0 Source Pending Reg.
LDR R0, =SRCPND
LDR R2, [R0]
ORR R1, R1,R2
STR R1, [R0]
LDR R0, =INTPND
LDR R1, [R0]
STR R1, [R0]
;----------------------------------------------------------------------------------
; OSTimeTick();
;----------------------------------------------------------------------------------
BL OSTimeTick ;//调用具体中断的数据处理
MOV PC, R5 ; Return