新闻中心

EEPW首页>嵌入式系统>设计应用> 异常中断处理程序

异常中断处理程序

作者: 时间:2016-11-21 来源:网络 收藏
6.2.1异常中断处理程序的方法种类及其介绍
1.SWI 序的实现
在 SWI 指令中包括一个 24 位的立即数,该立即数指示了用户请求的特定的 SWI 功能。在
SWI 序要读取该 24 位的立即数,这涉及到 SWI异常模式下对寄存器 LR的读
取,并且要从存储器读取该 SWI 指令。这样需要使用汇编程序来实现。通常 SWI异常中断处
理程序分为两级:第 1 级 SWI 序为汇编,用于确定 SWI 指令中的 24 位的立
即数;第 2级 SWI 异常中断处理程序具体实现 SWI 的各个功能,它可以是汇编程序,也可以
时 C 程序,下面我们分别介绍这两级。
● SWI 异常中断调用
① 在特权模式下调用 SWI
执行 SWI 指令后,系统将会把 CPSR 寄存器的内容保存到寄存器 SPSR_SVC 中,
将返回地址保存到寄存器 LR_svc 中。这样如果执行 SWI 指令时,系统已经处于
特权模式下,这时寄存器 SPSR_svc 和寄存器 LR_svc 中的内容就会被破坏。因
此如果在特权模式下调用 SWI 功能,比如在一个 SWI 异常中断处理程序中执行
SWI指令,就必须将原始的寄存器SPSR_svc和寄存器LR_svc值保存在数据栈中。
程序6.1说明在SWI中断处理程序中如何保存寄存器SPSR_svc和寄存器LR_svc
值。
程序 6.1 在SWI 中断处理程序中保存寄存器 SPSR_svc 和寄存器 LR_svc 值
;保存寄存器,包括寄存器 lr_svc
STMFD sp!(r0-r3,r12,lr)
;保存 SPSR_svc
MOV r1,sp
MRS r0,spsr
STMFD sp!,(r0)
;读取SWI指令
LDR r0,(lr,#4)
;计算其中的 24 位立即数,并将其放入寄存器 R0 中
BIC r0,r0.#0xff000000
;调用C_SWI_Handler 完成相应的 SWI 功能
BL C_SWI_Handler
;恢复SPSR_svc 的值
LDMFD sp!,(r0)
MSB spsr_cf,r0
;恢复其他寄存器,包括寄存器 LR_svc
LDMFD sp!,(r0-r3,r12,pc)^
② 从应用程序中调用 SWI
这里分两种情况考虑从应用程序中调用特定的 SWI 功能:一种考虑使用汇编指
令调用特定的 SWI 功能;一种考虑从 C 语言程序中调用特定的 SWI 功能。
使用 汇编指令调用特定的 SWI 功能比较简单,将需要的参数按照 ATPCS 的要求
放在相应的寄存器中,然后在指令 SWI 中指定相应 24 位立即数即可。下面的例
子中,SWI 中断处理程序需要的参数放在寄存器 R0 中,这里该参数为 100,然
后调用功能号为 0x0 的SWI 功能调用。
MOV R0,#100
SWI 0x0
从 C 语言程序中调用特定的 SWI 功能比较复杂,因为这时需要将一个 C 程序的
子程序调用映射到一个 SWI 异常中断处理程序。这些被映射的 C 语言子程序使
用编译器伪操作__SWI 来声明。如果该子程序需要的参数和返回的结果只使用寄
存器 R0~R3,则该 SWI 可以被编译成 inline 的,不需要使用子程序调用过程。
否则必须告诉编译器通过结构数据类型来返回参数,这时需要使用编译器伪操
作_value_in_reg 声明该C 语言子程序。
下面通过一个完整的例子来说明如何从 C 程序中调用特定的 SWI 功能,该例子
是 ARM 公司的 ADS1.2 中所带的。该例子提供的 4 个SWI 功能调用,功能号分别
为 0x0,0x1,0x2,0x3。其中 SWI 0x0及 SWI 0x1 使用两个整形的输入参数,并返
回一个结果值,SWI 0x2使用 4 个输入参数,并返回一个结果值;SWI 0x3 使用
4 个输入参数,并返回 4个结果值。
整个 SWI 异常中断处理程序分为两级结构。第 1 级的 SWI 异常中断处理程序是
汇编程序 SWI_HANDLER,它读取 SWI 指令中的 24 位立即数,然后调用第 2 个级
SWI 异常中断处理程序 C_SWI_HANDLER来实现具体的 SWI 功能。第 2 级SWI异常
中谷底你处理程序 C_SWI_HANDLER 为 C 语言程序。其中实现了功能号分别为
0x0,0x1,0x2,0x3的 SWI功能调用。
主程序中的子程序 multiply_two()对应着 SWI 0x0;add_two()对应着
0x1;add_multiply_two()对应着 0x2;many_operations()对应着 SWI 0x3。
Many_operations()返回 4 个结果,使用编译器伪操作_valuc_in_reg 声明。4
个子程序都使用编译器伪操作__SWI来声明。主程序使用 lnstall_Handler()来
安装该 SWI异常中断处理程序,lnstall_Handler()在前面已经有详细的介绍。
整个代码如程序 6.2 所示。
程序 6.2 从C 程序中调用特定的 SWI功能
__swi(0) int multiply_two (int,int);
__swi(1) int add_two (int,int);
__swi(2) int add_multiply_two (int ,int ,int ,int );
Struct four_results
{
int a;
int b;
int c;
int d;
};
__swi(3) __value_in_regs struct four_results
Many operations (int ,int,int,int);
#include
#include “swi.h”
Unsigned *swi_vec=(unsigned *)0x08;
Unsigned install_Handler(unsigned routine, unsigned *vector)
{
Unsigned vec,old_vec;
Vec=(routine –(unsigned)vector-8)>>2;
If (vec & 0xff000000)
{
print(“Handler greter than 32Mbytes from vector”);
vec=0xea000000 |vec;
Old_vec=*vector;
*vector=vec;
Return(old_vec);
}
Int result1,result2;
Struct fcur_results result3;
Install_Handler ((unsigned) SWI_Handler,swi_vec);
Printf(“result1=mutiply_two (2,4)=%dn”,
Result1=multiply_two(2,4));
Printf(“result2=mutiply_two (3,6)=%dn”,
Result2=multiply_two(3,6));
Printf(“add_two(result1,result2 ) =%dn, add_two(result1,result2));
Printf(‘add_multiply_two(2,4,3,6)=&dn”,add_multiply_two(2,4,3,6));
Res_3=many_operations(12,4,3,1);
Printf(“res_3.a=&dn”,res_3.a);
Printf(“res_3.b=&dn”,res_3.b);
Printf(“res_3.c=&dn”,res_3.c);
Printf(“res_3.d=&dn”,res_3.d);
Return 0;
}
;第1 级SWI 异常中断处理程序 SWI_Handler
;SWI_Handler 在前面已有详细介绍
AREA SWI_Area, CODE,PEADONLY
EXPORT SWI_Handler
IMPORT C_SWI_Handler
T_bit EQU 0x2c
SWI_Handler
STMED sp!(r0-r3,r12,lr)
MOV r1,sp
MRS r0,spsr
STMFD sp!,(r0)
TST r0,#T_bit
LDRNEH r0(lr,#-2)
BTCNE r0,r0,#0xff00
LDREQ r0,(lr,#-4)
BICEQ r0,r0,#0xff000000
BL C_SWI_Handler
LDMFD sp!,(r0)
MSR spsr cf,r0
LDMFD sp!,(r0-r3,r12,pc)^
END
Void C_SWI_Handler (int swi_num,int *regs)
{
Switch (swi_num)
{
//对应于SWI 0x0
case 0:
regs[0]=regs[0]*regs[1];
break;
//对应于SWI 0x1
case 1:
regs[0]=regs[0]+ regs[1];
break;
//对应于SWI 0x2
case 2:
regs[0]=(regs[0]*regs[1]) +(regs[2]*regs[3]);
break;
//对应于SWI 0x3
case 3:
{int w,x,y,z;
w=regs[0];
x=regs[1];
y=regs[2];
z=regs[3];
regs[0]=w+x+y+z;
regs[1]=w-x-y-z;
regs[2]=w*x*y*z;
regs[3]=(w+x)*(y-z);
}
Break;
}
}
③ 从应用程序中动态调用 SWI
在有些情况下,直到运行时才能够确定需要调用的 SWI 功能号。这时,有两种
方法处理这种情况。
第 1 种方法是在运行时得到 SWI 功能号,然后构造出相应的 SWI 指令的编码,
把这个指令的编码保存在一个存储器单元中,执行该指令即可。
第 2 种方法使用一个通用的 SWI 异常中断处理程序,将运行时需要调用的 SWI
功能号作为参数传递给该通用的 SWI 异常中断处理程序,通用的 SWI 异常中断
处理程序根据参数值调用相应的 SWI 处理程序完成需要的操作。
在汇编程序中很容易实现第 2 中方法。在执行 SWI 指令之前先将需要调用的 SWI
功能号放在一个寄存器。在通用的 SWI 异常中断处理程序读取该寄存器值,决
定需要执行的操作,但有些 SWI 处理程序需要 SWI 指令的 24位立即数,因而上
述两种方法常常组合使用。
在操作系统中通常使用一个 SWI 功能号和一个寄存器来提供很多的 SWI 功能调
用。这样可以将其他的 SWI 功能号留给用户使用。在 DOS 系统中,DOS 提供功能
调用是 INT 21H ,这时通过指定寄存器 AX 的值,可以实现很多不同的功能调用。
ARM 体系中semihost 的实现也是一个例子。ARM 程序使用 SWI 0x123456 来实现
semilhost 功能调用;Thunb 程序使用SWI 0xAB 来实现 semihost 功能调用。在
下面的例子中,将子程序 WRITEC (unsigned op,char *c)映射到 semihost
功能调用,具体 semihost SWI 的子功能号通过参数 op 传递。
程序 6.3 从应用程序中动态调用 SWI 功能
#ifdef_thumb
#else
#define semiSWI 0x123456
#endif
__swi (semiSWI) void semibosting (unsigned op,char *c);
Void write_a_character (int ch)
{
char tempth=ch;
writec (&tempch);
}
2.FIQ 和IRQ 异常中断处理程序
ARM 提供的FIQ和 IRQ 异常中断用于外部设备向 CPU 请求中断服务。这两个异常中断的引脚
都是低电平有效的。当前程序状态寄存器 CPSR 的 1 控制位可以屏蔽这两个异常中断请求;
当程序状态寄存器 CPSR由 1 控制位位 0 时,CPU正常响应 FIQ 和IRQ 异常中断请求。
FIQ 异常中断为快速异常中断,它比 IRQ 异常中断优先级高,这主要表现在下面的两个方
面:
● 当 FIQ 和IRQ 异常中断同时产生时,CPU 先处理 FIQ 异常中断。
● 在 FIQ 异常中断处理程序中 IRQ 异常中断被禁止。
由于 FIQ 异常中断通常用于系统对于响应时间要求比较苛刻的任务,ARM 体系在设计上有
一些特别的安排,以尽量减少 FIQ 异常中断响应时间。FIQ 异常中断的中断向量为 0x1c,位
于中断向量表的最后。这样 FIQ 异常中断处理程序可以直接放在地址 0x1c 开始的存储单元,
这种安排省掉了中断向量表的跳转指令,从而也就节省了中断响应时间。当系统中存在
cache 时,可以把 FIQ 异常中断向量以及处理程序一起锁在 cache 中,从而大大地提高了
FIQ 异常中断响应时间。除此之外,与其他的异常模式相比,FIQ 异常中断还有额外的 5 个
物理寄存器,这样在进入 FIQ 处理程序时可以保存这 5 个寄存器,从而也提高了 FIQ 异常中
断的执行速度。
在有些 IRQ/FIQ 异常中断处理程序中,允许新的 IRQ/FIQ 异常中断,这时将需要一些特
别的操作保证“老的”异常中断的寄存器不会“新的”异常中断破坏,这种 IRQ/FIQ 异常中
断处理程序称为可重入的异常中断处理程序。否则称为不可重入的异常中断处理程序。
① 不可重入的 IRQ/FIQ 异常中断处理程序
对于 C 语言不可重入的 IRQ/FIQ 异常中断处理程序可以使用关键词_irq来说明。关键词
_irq 可以实现下面的操作:
● 保存 APCS 规定的被破坏的寄存器。
● 保存其他中断处理程序中用到的寄存器。
● 同时将(LD-4)赋予程序计数器 pc 实现中断处理程序的返回,并且恢复 CPSR 寄
存器的内容。
当 IRQ/FIQ异常中断处理程序调用了子程序时,关键词_irq可以使 IRQ/FIQ 异常中断
处理程序返回时从其数据栈中读取 LR_irq 值,并通过 SUBS PC,LR,#4 实现返回。程序
6.4 说明的关键词_irq 的作用,其中列出了 C 语言程序及其对应的汇编程序,两个 C
语言程序中,第 1 个使用关键词_irq 声明,第2个没有使用关键词_irq声明。
程序 6.4 关键词_irq 的作用
;第1 个程序使用关键词_irq 声明
_irq void IRQHandler (void)
{
Volatile unsigned int *base=(unsigned int *)0x8000000;
If (*base 1)
{
//调用相应的 C 语言处理程序
C_int_Handler ();
}
//清除中断标志
*(base=1)=0;
}
;第1 个C 语言程序对应的汇编程序
IRQHandler PROC
STMFD sp!,(r0-r4,r12,lr)
MOV r4,#0x8000000
LDR r0,(r4,#0)
SUB sp,sp,#4
CMP r0,#1
BLEQ Q_int_handler
MOV r0,#0
STR r0,(r4,#4)
ADD sp,sp,#4
LDMFD sp!,(r0-r4,r12,lr)
SUBS pc,lr,#4
ENDP
BXPORT IRQHandler
//第2 个程序没有使用关键词_irq 声明
_irq void IRQHandler (void)
{
Volatile unsigned int *base=(unsigned int *) 0x80000;
If(*base 1)
{
//调用相应的 C 语言处理程序
C_int_handler();
}
//清除中断标志
*(base+1)=0;
}
;第1 个C 语言程序对应的汇编程序
IRQHandler PROC
STMFD sp!(r4,lr)
MOV r4,#0x8000000
LDR r0,(r4,#0)
CMP r0,#1
SLEQ C_int_handler
MOV r0,#0
STR r0,(r4,#4)
LDMFD sp!(r4,pc)
ENDP
② 可重入的 IRQ/FIQ 异常中断处理程序
如果在可重入的 IRQ/FIQ 异常中断处理程序中调用了子程序,子程序的返回地址被保存
到寄存器的 LR_irq 中,这时如果发生了 IRQ/FIQ异常中断,这个 LR_irq寄存器的值将
被破坏,那么被调用的子程序将不能正确的返回。因此,对于可重入的 IRQ/FIQ异常中
断处理程序一些需要特别的操作。下面列出了在可重入的 IRQ/FIQ 异常中断处理程序中
需要的操作。这时,第1 级中断处理程序不能使用 C 语言,因为其中一些操作不能通过
C 语言实现:
● 将返回地址保存到 IRQ的数据栈中。
● 保存工作寄存器和 SPSR_irq。
● 清除中断标志位。
● 将处理器切换到系统模式,重新使能中断(IRQ/FIQ)。
● 保存用户模式的 LR 寄存器和被调用者的不保存的寄存器。
● 调用 C 语言的 IRQ/FIQ异常中断处理程序。
● 当 C 语言的 IRQ/FIQ 异常中断程序返回后,恢复用户模式的寄存器,并禁止中断
(IRQ/FIQ)。
● 切换到 IRQ模式,禁止中断。
● 恢复工作组寄存器和寄存器 LR_irq。
● 从 IRQ 异常中断处理程序中返回。
下面程序 6.5 演示了这些操作的过程。
程序6.5 可重入的 IRQ/FIQ 异常中断处理程序
AREA INTERRUPT ,CODE,PEADONJY
;引入C语言的 IRQ中断处理程序 C_irq_handler
IMPORT C_irq_handler
IRQ
;保存返回 IRQ 处理程序地址
SUB lr,lr,#4
STMFD sp! ,(lr)
保存 SPSR_irq,及其他工作寄存器
MRS r14,SPSR
STMFD sp!,lr12,r14
;
;在这里添加指令,清除中断标志位
;添加指令重新使能中断
;
;切换到系统模式,并使能中断
MSR CPSR_C,#0x1f
;保存用户模式的 LR_usr 及被调用者不保存寄存器
STMFD sp!,(r0-r3,lr)
;跳转到C 语言的中断处理程序
BL C_irq_handler
;恢复用户模式的寄存器
LDMFD sp!,(r0-r3,lr)
;切换到IRQ 模式,禁止 IRQ 中断,FIQ 中断允许
MSR CPSP_c,#0x92
;恢复工作寄存器和 SPSR_irq
LDMFD sp!,(pc)^
END
6.2.2 复位中的异常中断处理程序
复位异常中断处理程序在系统加电或复位时执行,它将进行一些初始化的工作,具体内
容与复位系统相关,然后程序控制权交给应用程序,因而复位异常中断处理程序不需要返回。
下面时通常在复位异常中断处理程序进行的一些处理:
● 设置异常中断向量表。
● 初始化数据栈和寄存器。
● 初始化存储系统,如系统中的 MMU等。
● 初始化一些关键的 I/O设备。
● 使用中断。
● 将处理器切换到会话模式。
● 初始化 C 语言环境变量,条状到应用程序执行。
6.2.3 C 语言程序中的异常中断处理程序
在程序运行过程中,也可以在 C 语言程序中安装异常中断处理程序。这时需要把相应的跳
转指令或者数据读取指令的编码写到中断向量表的响应位置。下面分别讨论这两种情况下安
装异常中断处理程序的方法。
1.中断向量表中使用跳转指令的情况
当中断向量表中使用跳转指令时,在 C 程序中安装异常中断处理程序的操作如下:
(1) 读取中断处理程序的地址。
(2 ) 从上一步得到的地址中减去该异常中断对应的中断向量的地址。
(3) 从上一步得到的地址中减去 8,以允许指令的预取。
(4 ) 将上一步得到的地址右移 2 位,得到以字(32位)为单位的偏移量。
(5) 确保上一步得到的地址高 8 位为0,因为跳转指令只允许 24位的偏移量。
(6) 将上一步得到的地址与数据 0xea000000 作逻辑或,从而得到将要写到中断向
量表的跳转指令的编码。
以上具体操作通过程序 6.6 实现下面的 C 程序。其中参数 routine 是中断处理程序
的地址,vector 为中断向量的地址。
程序 6.6 使用跳转指令的中断向量表
Unsigned install_handler (unsigned routine ,unsigned *vector)
{ unsigned vec, oldvec;
vec=((routine-(unsigned)vector-0x8)>>2);
If (vec & 0xff000000)
{
Printf(“Installation of handler fai led” )
exit (2);
}
vec=0xea000000|vec;
oldvec=^vector;
*vector=vec;
return (oldvec);
}
2. 中断向量表中使用数据读取指令的情况
当中断向量表中使用数据读取指令时,在 C 程序中安装异常中断处理程序的操作序列如下
所示:
(1) 读取中断处理程序的地址。
(2) 从上一步得到的地址中减去该异常中断对应的中断向量的地址。
(3) 从上一步得到的地址中减去 8,允许指令预取。
(4) 将上一步得到的地址与数据 0xe59f000 作逻辑或,从而得到将要写到中断向量
表中的数据读取指令的编码。
(5) 将中断处理程序的地址放到相应的存储单元。
程序 6.7中的 C 程序实现了上面的操作序列。其中参数 location 是一个存储单元,其中
保存了中断处理程序的地址;vector 为中断向量的地址。
程序6.7 数据读取指令的中断向量表
Unsigned Install_Handler (unsigned location ,unsigned *vector)
{ unsigned vec ,oldvec;
Vec=((unsigned)location-(unsigned)vector-0x8) | (0xe59ff0000 oldvec -
*vector;
*vector=vec;
Return (oldvec);
}
下面的语句调用上面的代码,在 C 程序中安装中断处理程序。
Unsigned *irqvec=(unsigned *)0x18;
Install_Handler ((unsigned)IRQHandler,irqvec);
6.3 其它种类的异常中断
1.数据访问中止异常中断处理程序
如果系统不包含 MMU,数据访问中止异常中断处理程序只是简单地报告错误,然后退出。如
果系统中包含 MMU,数据访问中止异常中断处理程序要处理该数据访问中止。当发生数据访
问中止异常中断的指令时。LR_abt 寄存器已经被更新,它指向引起数据访问中止异常中断
的指令后面第 2 条指令。此时要返回到引起数据访问中止异常中断的指令。即(LR_abt)处。
下面3 种情况可能引起数据访问中止异常中断。
① LDR/STR 指令
对于 ARM 数据访问中止异常中断发生时,LR_abt 寄存器已经被更新,它指向引起数据访问
中止异常中断的指令后面第 2 条指令,此时要返回到引起数据访问中止异常中断的指令。
对于 ARM9、ARM10、strongARM 处理器,数据访问中止异常中断发生后,处理器将程序计数
器设置称引起数据访问中止异常中断的指令的地址,不需要用户来完成这种程序计数器的设
置操作。
② SWAP 指令
SWAP 指令执行时,未更新及存取 LR_abt.
③ LDM/STM 指令
对于 ARM6及 ARM7 处理器,如果写回机制使能的话,基址寄存器将被更新。对于 ARM9、ARM10
及 strongARM 处理器,如果写回机制使能的话,数据访问中止异常中断发生时,处理器将恢
复基址寄存器的值。
3.指令预取中止异常中断处理程序
如果系统不包含 MMC,指令预取中止异常中断处理程序只是简单地报告错误,然后退出。
如果系统中包含 MMU,则发生错误的指令触发虚拟地址失效,在该失效处理程序中重新
读取该指令。指令预取中止异常中断是有错的指令执行时被触发是,这时 LR_abt 寄存
器还没有被更新,它指向该指令的下面一条指令。因为该有问题的指令要被重新读取,
因而应该返回到该有问题的指令,即返回到(LR_abt-4)处。
4.未定义指令异常中断
当 CPU 不认识当前指令时,它将该指令发送到协处理器。如果所以的协处理器都不认识
该指令,这时将产生未定义指令异常中断。在未定义指令异常中断进行响应的处理。可
以看出这种机制可以用来通过软件仿真系统中一些部件的功能。比如,如果系统中不包
含浮点运算部件,CPU 遇到浮点运算指令时。将发生未定义指令异常中断,在该未定义
指令异常中断的处理程序中可以通过其他指令序列仿真该浮点运算指令。
这种仿真的处理过程类似有 SWI异常中断的功能调用。在 SWI 异常中断的功能调用中
通过读取 SWI 指令中的 24 位的立即数。判断具体请求的 SWI 功能。这种仿真机制的操
作过程如下:
① 将仿真程序设置成未定义指令异常中断的中断处理程序(链接到未定义指令异常中
断的中断处理程序链中),并保存原来的中断处理程序。这是通过修改中断向量表
中未定义指令异常中断对应的中断向量来实现的。
② 读取该未定义的位[27:24],判断该未定义指令是否是一个协处理器指令。当位
[27:24]为 0b1110 或 0b110x 时,该未定义指令时一个协处理器指令。接着读取该
未定义的指令的位[11:8],如果位[11:8]指定通过仿真程序实现该未定义指令,则
相应的调用仿真程序实现该指令的功能,后来返回到用户程序。
③ 如果不仿真该未定义指令,程序跳转到原来的未定义指令异常中断的中断处理程序
执行。
Thumb 指令集中不包含协处理器指令,因而不需要这种仿真机制。



关键词:异常中断处理程

评论


技术专区

关闭