今天学了如果编写软中断,把今天学的ADS的SWI的例子的工作流程总结一下。
这里面一般有四个文件:main.c ahandle.s chandle.s swi.h。main.c是程序的入口。Ahanle.s是软中断的处理程序,但它只是处理程序的一部分,是软件中断处理程序的入口,主要的处理部分在chandle.s中,Ahanle.s文件调chanle.s文件, chanle.s文件中有处理程序的具体实现细节。而swi.h文件是中断处理程序在c语言中的声明。
首先让我们来看看swi.h文件,具体内容如下:
__swi(0) int multiply_two(int, int);
__swi(1) int add_two(int, int);
__swi(2) int add_multiply_two(int, int, int, int);
__swi(3) __value_in_regs struct four_results
many_operations(int, int, int, int);
struct four_results
...{
int a;
int b;
int c;
int d;
};
最上面的__swi(0)…,__swi(1)…, __swi(2)…, __swi(3)…,是指你要调用多少号的中断,括号里面的数字被放到指令中,所以你想判断要调用几号软件中断服务程序,你必须用像:
LDREQ r0, [lr, #-4]
BICEQ r0, r0, #0xFF000000 ; ...extract comment field
这样的指令来读把号码读出来。可能有人要问,为什么是lr要减4呢?因为在执行中断指令 swi * 时,编译器自动的把lr指向中断指令的下一条指令的地址。而我们要得到的中断号却在swi * 这条指令中,所以我们要把这条指令用 lr-4 的方式把它取出来。然后用把前8位的指令码去掉,得出中断号。__swi(0)等后面的 int multiply_two(int, int) 等,这函数的名是自己定的,是为了在c语言中能够调用汇编编写的中断函数。最后看这个结构体,这个结构体没有什么特别的意思,主是为了测试程序用的。
接下来看看main.c文件,具体内容如下:
#include <stdio.h>
#include "swi.h"
unsigned *swi_vec = (unsigned *)0x08;
extern void SWI_Handler(void);
unsigned Install_Handler( unsigned routine, unsigned *vector )
...{
unsigned vec, old_vec;
vec = (routine - (unsigned)vector - 8) >> 2;
if (vec & 0xff000000)
...{
printf("Handler greater than 32MBytes from vector");
}
vec = 0xea000000 | vec; /**//* OR in 'branch always' code */
old_vec = *vector;
*vector = vec;
return (old_vec);
}
int main( void )
...{
int result1, result2;
struct four_results res_3;
Install_Handler( (unsigned) SWI_Handler, swi_vec );
printf("result1 = multiply_two( 2, 4 ) = %d ", result1 = multiply_two( 2, 4 ));
printf("result2 = multiply_two( 3, 6 ) = %d ", result2 = multiply_two( 3, 6 ));
printf("add_two( result1, result2 ) = %d ", add_two( result1, result2 ));
printf("add_multiply_two( 2, 4, 3, 6 ) = %d ", add_multiply_two( 2, 4, 3, 6 ));
res_3 = many_operations( 12, 4, 3, 1 );
printf("res_3.a = %d ", res_3.a );
printf("res_3.b = %d ", res_3.b );
printf("res_3.c = %d ", res_3.c );
printf("res_3.d = %d ", res_3.d );
return 0;
}
首先看到是的两个文件包含,第一个就不说了,第二个是为了能够使用定义的结构体和能够调用中断程序。接下来两句:
unsigned *swi_vec = (unsigned *)0x08;
extern void SWI_Handler(void);
第一句是定义一个指针,这个指针指向0x08地址的单元,是软中断的中断向量号。第二句是声明一个汇编的外部函数。
接下是Install_Handler这个方法,这个方法主要作用是把能跳转到用汇编编写的软件中断处理程序的指令存到arm规定的中断向量表中,下面我们具体分析一下这个函数。首先看看这句:
vec = (routine - (unsigned)vector - 8) >> 2;
这句让我搞了半天也没明白,后来是问的老师。在arm体系结构参考手册中关于BL:
1. Sign-extending the 24-bit signed (two's complement) immediate to 32 bits.
2. Shifting the result left two bits.
3. Adding this to the contents of the PC, which contains the address of the branch instruction plus?.
它是把BL中24有符号数扩展成32位,然后左移两位,再这个数加8加到pc中。所以,我们得先把这个数减8再右移两位,好让arm的自动操作能算出正确的地址。但是我没明白arm为什么先左移再加8。
if (vec & 0xff000000)
{
printf("Handler greater than 32MBytes from vector");
}
接下来看看bl要跳转的地址,手册里规定bl跳转不能超过+-32M的地址空间。
vec = 0xea000000 | vec; /* OR in 'branch always' code */
这句中,0xea000000是bl的二进制编码。用0xea000000和要跳转的相对地址相或操作,就是一条要跳转到中断处理程序相对地址的指令(bl addr)。
然后用 *vector = vec; 把这条指令保存到中断向量表中,也就是把指令保存到0x08这个地址所指向的空间。
old_vec = *vector;
return (old_vec);
这两句是为了保存0x08这个中断向量中的内容。
下面说说ahandle.s这个文件中的内容:
AREA SWI_Area, CODE, READONLY
EXPORT SWI_Handler
IMPORT C_SWI_Handler
T_bit EQU 0x20
SWI_Handler
STMFD sp!, ...{r0-r3, r12, lr} ; Store registers
MOV r1, sp ; Set pointer to parameters
MRS r0, spsr ; Get spsr
STMFD sp!, ...{r0} ; Store spsr onto stack
TST r0, #T_bit ; Occurred in Thumb state?
LDRNEH r0, [lr,#-2] ; Yes: Load halfword and...
BICNE r0, r0, #0xFF00 ; ...extract comment field
LDREQ r0, [lr,#-4] ; No: Load word and...
BICEQ r0, r0, #0xFF000000 ; ...extract comment field
; r0 now contains SWI number
; r1 now contains pointer to stacked registers
BL C_SWI_Handler ; Call main part of handler
LDMFD sp!, ...{r0} ; Get spsr from stack
MSR spsr_cf, r0 ; Restore spsr
LDMFD sp!, ...{r0-r3, r12, pc}^ ; Restore registers and return
END
这段程序是中断处理程序的一部分,由它调用中断处理程序的主要处理部分,主要处理部分是用c语言写的chandle.c。下面分析一下这段程序。从下面几条语句开始:
STMFD sp!, {r0-r3, r12, lr} ; Store registers
MOV r1, sp ; Set pointer to parameters
MRS r0, spsr ; Get spsr
STMFD sp!, {r0} ; Store spsr onto stack
第一条语句是把c语言中函数调用时的参数保存到堆栈中,在c语言的中断处理程序中读出这些参数。保存lr是因为一会要调用别的函数,所以防止lr丢失。为什么要保存r12这个我有点不太明白,因为这段程序运行根本就没用到它。第二条是把堆栈的地址保存到r1中,因为一会要把它传到c语言的中断处理函数中。
MRS r0, spsr ; Get spsr
STMFD sp!, {r0}
这两句是把spsr保存到堆栈中,但我看这段程序运行也没改动这它,真不知道是为什么?
TST r0, #T_bit ; Occurred in Thumb state?
LDRNEH r0, [lr,#-2] ; Yes: Load halfword and...
BICNE r0, r0, #0xFF00 ; ...extract comment field
LDREQ r0, [lr,#-4] ; No: Load word and...
BICEQ r0, r0, #0xFF000000 ; ...extract comment field
这一小段的代码主要作用是判断现在arm是thumb状态还是arm状态,根据不同的状态取半字还是字,然后得到用户要调用几号软中断,把这个号放到r0 中。这里为什么会是lr-4呢,因为当main.c中执行中执行h文件中声明的方法时,就是调用了swi * 指令,当执行 swi * 这条指令时,编译器同时把这条指令的下一条指令的地址放到lr中,我们要得到swi * 这条指令,并得到 * (就是用户要调多少号软中断的号)是多少时,就得用lr-4了。
BL C_SWI_Handler ; Call main part of handler
LDMFD sp!, {r0} ; Get spsr from stack
MSR spsr_cf, r0 ; Restore spsr
LDMFD sp!, {r0-r3, r12, pc}^ ; Restore registers and return
一切准备工作都做完后,就调用c语言编写的具体的处理函数。函数返回后,恢复保存的寄存器。
下面讲一讲最后的一个文件chandle.h文件。
它的作用主要就是根据ahandle.c程序中SWI_Handler方法传过来的参数,做相应的处理。具体内容如下:
void C_SWI_Handler( int swi_num, int *regs )
...{
switch( swi_num )
...{
case 0:
regs[0] = regs[0] * regs[1];
break;
case 1:
regs[0] = regs[0] + regs[1];
break;
case 2:
regs[0] = (regs[0] * regs[1]) + (regs[2] * regs[3]);
break;
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 * 指令中的 * 。第二个参数传过来的是一个指针,是指向堆栈的指针。因为在 ahandle.s中,把main.c中调用函数时的实参都放到了堆栈中。
所有的文件都说明完了,最后说一下程序的流程。
程序以main.c的main函数开时,首先调用main.c中的Install_Handler函数,把跳转到中断向量处理程序(中断处理程序就是 ahandle.s中的SWI_Handler函数)的指令放到中断向量表中。当有中软中断出现时,arm就会调用跳转到中断向量处理程序(SWI_Handler),中断向量处理程序就会调用具体的处理函数chandle.c中的C_SWI_Handler函数。
以上就是对ADS自带的swi的例子的分析,有不足之处,请大家指出。
关键词:
具体
分析
中断
文件
程序
处理
调用
指令
地