[015] [ARM-Cortex-M3/4] SVC和PendSV异常实战

ARM
Contents
SVC异常.
SVC异常编程.
PendSV异常.
PendSV异常编程.

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位立即数, 获得请求执行的功能代号,其机器码编码格式为:

image-20220320224822637

SVC指令地址加载到r1,从r1-2位置加载一个字节到r2,意思是将指针向低地址方向偏移16bits(满减栈),指向SVC指令第0位,然后从此地址开始加载8bits,即提取出imm8。(指令地址中存储的为机器码/数组

串口打印结果为:SVCall_Handler: svc = 0x00000007,提取Svc请求功能代号成功。

反汇编代码中其机器码0~7位也为7:

image-20220320234721755

注意Uasge异常的栈帧中PC返回地址是当前发生异常的指令,而SVCPC返回地址则是当前指令的下一条指令,因此不需要修改异常栈帧中返回地址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中断中进行时间片轮转调度时,需要进行上下文切换:

image-20220320015229742

上图为两个任务轮转调度,但当产生systick异常时正在响应一个中断,则systick异常会抢占其中断服务例程,若在此时OS进行上下文切换,会导致中断请求被延时,这在实时系统是不允许出现的。对于M3/4,当存在活跃的异常时,设计默认不允许返回到线程模式非基级的线程模式例外),否则触发Usage fault。

image-20220320015949367

PendSV可完美解决此问题,PendSV异常会自动延迟上下文切换的请求,直到其它的ISR都完成了处理后才放行(编程为最低优先级的异常)。如果OS检测到某IRQ正在活动并且被SysTick抢占,它将悬起一个PendSV异常,以便缓期执行上下文切换:

image-20220320020225094

  1. 任务A呼叫SVC请求任务切换
  2. OS收到请求做好上下文切换准备,并悬起PendSV异常
  3. 当CPU退出SVC后,立即进入PendSV进行上下文切换
  4. 当PendSV执行完后,然后任务B,切换到线程模式
  5. 中断发生,ISR开始执行
  6. ISR执行过程中,发生systick异常,抢占了该ISR
  7. OS执行必要操作,然后悬起PendSV异常以做好上下文切换的准备
  8. 当systick退出后,被抢占的ISR继续执行
  9. ISR执行完毕,PendSV服务例程开始执行,并在其中进行上下文切换
  10. 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异常栈帧的返回地址时是触发异常指令后的第三条指令地址:

image-20220321024426408

但经过测试,这不是绝对的,可能PendSV异常响应没有那么及时,存在执行后面第1~3条指令才响应的情况

SCB->ICSR的后8位表示当前活动的异常号,即PendSV异常向量编号14,第11位置1表示没有活动异常或当前正在执行异常。

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柯西的彷徨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值