1 SVC异常
-
PendSV和SVC一般用于OS,SVC用于产生系统函数的调用请求,从而导致内核的异常处理函数被调用,进而去使用内核的服务。
-
使用
SVC #VAL
指令即可触发SVC异常,进入Handler模式——特权级,其中#VAL
立即数一般作为系统调用代号。 -
在SVC服务例程执行后,上次执行的SVC指令地址在异常栈帧中保存,从SVC指令的机器码提取出立即数,即获得请求执行的功能代号。
-
若用户程序使用了PSP,SVC服务例程需先执行
MRS Rn PSP
来获取应用程序栈指针。
注意:
- SVC服务例程中嵌套使用SVC指令,由于相同优先级的异常不能抢占自身,产生一个
Usage fault
。(实测使能了Usage 会进入hard fault) - SVC异常必须在执行SVC指令后立即得到响应(保证能够立即硬件入栈r0-r3等寄存器,这是因为SVC中断需要传递参数到中断模式,所以必须立即响应,保证寄存器中存储的参数不被破坏,另一个原因是用户态程序,即线程模式的程序有的时候必须得到SVC中断响应函数的返回值才能继续执行,否则强行继续执行,将会出错),若因优先级不比当前正处理的高,或是其它原因使之无法立即响应,将上访成
hard fault
,如在NMI服务例程中使用SVC指令。
1.1 SVC异常编程
1.1.1 汇编中使用SVC
- 定义向量表
__Vectors DCD 0 ; Top of Stack
DCD Reset_Handler ; Reset Handler
[...]
DCD SVCall_Handler ; SVCall Handler 编号11
- 插入SVC指令
svc #7
LDR R0, =my_main
- SVC异常服务例程
USART1 DCD 0x40013800
SVC_MSG DCB "SVCall_Handler: svc = ",0 ; 字符串末尾加\0
SVCall_Handler PROC
IMPORT put_s_hex
; svc #3 ; 进入HardFault_Handler
tst lr, #(1<<2) ; lr & (1<<2) check psp or msp
mrseq r0, msp ; z = 1
mrsne r0, psp ; z = 0
ldr r1, [r0,#24] ; r1 = pc
ldrb r2, [r1,#-2] ; 提取SVC指令中的8位立即数, 获得请求执行的功能代号
ldr r0, USART1
ldr r1, =SVC_MSG
b put_s_hex ; 不能用bl, 因为lr = exc_return
ENDP
使用tst
指令测试当前exception stack frame使用的MSP/PSP,然后从sp+24位置取出svc指令处的PC值,最后提取SVC指令中的8位立即数, 获得请求执行的功能代号,其机器码编码格式为:
SVC指令地址加载到r1,从r1-2位置加载一个字节到r2,意思是将指针向低地址方向偏移16bits(满减栈),指向SVC指令第0位,然后从此地址开始加载8bits,即提取出imm8。(指令地址中存储的为机器码/数组)
串口打印结果为:SVCall_Handler: svc = 0x00000007
,提取Svc请求功能代号成功。
反汇编代码中其机器码0~7位也为7:
注意:Uasge
异常的栈帧中PC返回地址是当前发生异常的指令,而SVC
PC返回地址则是当前指令的下一条指令,因此不需要修改异常栈帧中返回地址PC值,异常也能正常返回。(但是不知道为什么读异常栈帧中的PC时,依然能读取到SVC指令的机器码)
1.1.2 C中使用SVC
需要先在汇编中用r0传递sp指针:
SVCall_Handler PROC
tst lr, #4
mrseq r0, msp
mrsne r0, psp
b SVCall_Handler_C
ENDP
使用C写成的SVC服务例程:
// r0, r1, r2, r3, r12, lr, pc, xpsr
uint32_t SVCall_Handler_C(uint32_t *svc_stack_frame)
{
//! 提取svc指令机器码0~7位, 即其立即数 (C数组还能这么玩~)
uint32_t svc_num = ((char*)svc_stack_frame[6])[-2];
uint32_t retVal = 123;
put_s_hex(usart1, "\r\npc\t= ", svc_stack_frame[6]);
put_s_hex(usart1, "\r\nsvc_num\t= ", svc_num);
svc_stack_frame[0] = 123; // 函数返回值
return 0;
}
打印结果:
pc = 0x0800007A
svc_num = 0x00000007
(char*)svc_stack_frame[6]
将SVC指令地址强制为char*指针,向前偏移2字节,读取指令的立即数。
注意:函数的返回值可不是0!(这里return 0
是防止编译器警告),此函数是异常服务例程,异常返回时的出栈行为由硬件自动控制,把返回值写到异常栈帧中的r0位置,借助自动返回机制返回ESR自己的值(ATPCS中规定r0做函数返回值,当然长度太长也会用上r1~r3)
2 PendSV异常
-
PendSV异常触发后可以像普通中断一样被悬起,OS可以利用其直到其它重要的任务完成后才执行动作
-
可以通过
SCB->ICSR[27:28]
来悬起(触发)与解悬PendSV异常 -
PendSV异常完美应用于OS上下文切换场合。(需要编程为最低优先级的异常)
当系统发起任务调度或在systick中断中进行时间片轮转调度时,需要进行上下文切换:
上图为两个任务轮转调度,但当产生systick异常时正在响应一个中断,则systick异常会抢占其中断服务例程,若在此时OS进行上下文切换,会导致中断请求被延时,这在实时系统是不允许出现的。对于M3/4,当存在活跃的异常时,设计默认不允许返回到线程模式(非基级的线程模式
例外),否则触发Usage fault。
PendSV可完美解决此问题,PendSV异常会自动延迟上下文切换的请求,直到其它的ISR都完成了处理后才放行(编程为最低优先级的异常)。如果OS检测到某IRQ正在活动并且被SysTick抢占,它将悬起一个PendSV异常,以便缓期执行上下文切换:
- 任务A呼叫SVC请求任务切换
- OS收到请求做好上下文切换准备,并悬起PendSV异常
- 当CPU退出SVC后,立即进入PendSV进行上下文切换
- 当PendSV执行完后,然后任务B,切换到线程模式
- 中断发生,ISR开始执行
- ISR执行过程中,发生systick异常,抢占了该ISR
- OS执行必要操作,然后悬起PendSV异常以做好上下文切换的准备
- 当systick退出后,被抢占的ISR继续执行
- ISR执行完毕,PendSV服务例程开始执行,并在其中进行上下文切换
- PendSV执行完毕,回到任务A,切换到线程模式
2.1 PendSV异常编程
- 异常初始化函数
SCB_ICSR EQU 0xE000ED04 ; interrupt control state register
PendSV_SET EQU 0x10000000 ; value to trigger PendSV exception(BIT28)
SCB_SHPR2 EQU 0xE000ED20 ; set pendsv priority
PendSV_PRI EQU 0x00FF0000 ; PendSV priority value (lowest)
PendSV_Init PROC
ldr r0, =SCB_SHPR2
ldr r1, =PendSV_PRI
str r1, [r0]
bx lr
ENDP
将PendSV异常优先级设为最低,即操作SCB->SHPR2
寄存器
- 触发异常函数
PendSV_Trigger PROC
ldr r0, =SCB_ICSR
ldr r1, =PendSV_SET
str r1, [r0]
mov r0,r0 ; 仅占位, 测试异常返回地址
mov r0,r0 ; 仅占位, 测试异常返回地址
bx lr
ENDP
设置SCB->ICSR
寄存器中的PENDSV悬起位。
- 在复位向量中调用初始化并触发PendSV
Reset_Handler PROC
[...]
bl PendSV_Init ; 优先级设为最低
bl PendSV_Trigger ; 触发异常
LDR R0, =my_main ; ldr pc, =my_main
BX R0
ENDP
- PendSV异常处理函数
PendSV_Handler PROC
tst lr, #4
mrseq r0, msp
mrsne r0, psp
b PendSV_Handler_C
ENDP
void PendSV_Handler_C(uint32_t *pendsv_stack_frame){
put_s_hex(usart1, "\r\npc\t= ", pendsv_stack_frame[6]);
put_s_hex(usart1, "\r\nICSR\t= ", SCB->ICSR);
}
串口打印结果:
pc = 0x080000D6
ICSR = 0x0000080E
PendSV
异常栈帧的返回地址时是触发异常指令后的第三条指令地址:
但经过测试,这不是绝对的,可能PendSV异常响应没有那么及时,存在执行后面第1~3条指令才响应的情况。
SCB->ICSR
的后8位表示当前活动的异常号,即PendSV异常向量编号14,第11位置1表示没有活动异常或当前正在执行异常。
END