• 系统异常SVC与PendSV指令


    参考文献:
    1、《[野火®]uCOS-III 内核实现与应用开发实战指南—基于STM32 》;
    2、CM3 权威指南CnR2(电子版)/Cortex-M3 权威指南 Joseph Yiu 著 宋岩 译 ★★;【两个指令含义的具体说明】
    3、https://www.cnblogs.com/dream397/p/15898026.html ;
    4、有关CM3内核外设寄存器定义可参考官方文档:STM32F10xxx Cortex-M3 programming manual ★★★;
    5、正点原子STM32F1 UCOS开发手册_V2.0;
    6、AAPCS:Procedure Call Standard for the ARM Architecture/ Procedure Call Standard for the ARM 64-bit Architecture /ARM架构的程序调用标准; https://github.com/ARM-software/abi-aa/releases ;https://developer.arm.com;
    1、起因
    分析该函数处理CPU内部寄存器时
    在这里插入图片描述

    /* 任务堆栈初始化函数 */
    CPU_STK *OSTaskStkInit (OS_TASK_PTR  p_task,
                            void         *p_arg,
                            CPU_STK      *p_stk_base,
                            CPU_STK_SIZE stk_size)
    {
    		CPU_STK  *p_stk;
    	
    		p_stk = &p_stk_base[stk_size];
    																/* 系统异常时自动保存的寄存器  */
    		*--p_stk = (CPU_STK)0x01000000u;                        /* xPSR的bit24位必须置1                                     */
    		*--p_stk = (CPU_STK)p_task;                             /* R15(PC) 任务的入口地址                                       */
    		*--p_stk = (CPU_STK)0x14141414u;                        /* R14 (LR)                                               */
    		*--p_stk = (CPU_STK)0x12121212u;                        /* R12                                                    */
    		*--p_stk = (CPU_STK)0x03030303u;                        /* R3                                                     */
    		*--p_stk = (CPU_STK)0x02020202u;                        /* R2                                                     */
    		*--p_stk = (CPU_STK)0x01010101u;                        /* R1                                                     */
    		*--p_stk = (CPU_STK)p_arg;                              /* R0 : 任务形参存放到了这                                        */
    																/* 系统异常时手动保存的寄存器                            */
    		*--p_stk = (CPU_STK)0x11111111u;                        /* R11                                                    */
    		*--p_stk = (CPU_STK)0x10101010u;                        /* R10                                                    */
    		*--p_stk = (CPU_STK)0x09090909u;                        /* R9                                                     */
    		*--p_stk = (CPU_STK)0x08080808u;                        /* R8                                                     */
    		*--p_stk = (CPU_STK)0x07070707u;                        /* R7                                                     */
    		*--p_stk = (CPU_STK)0x06060606u;                        /* R6                                                     */
    		*--p_stk = (CPU_STK)0x05050505u;                        /* R5                                                     */
    		*--p_stk = (CPU_STK)0x04040404u;                        /* R4                                                     */
    		return (p_stk);
    }
    
    void OSTaskCreate (OS_TCB *p_tcb, 
                       OS_TASK_PTR   p_task, 
                       void          *p_arg,
                       uint32       *p_stk_base,
                       uint32        stk_size,
                       OS_ERR        *p_err)
    {
    	    uint32	 	*p_sp;	
    		p_sp = OSTaskStkInit (p_task,
    													p_arg,
    													p_stk_base,
    													stk_size);
    		p_tcb->StkPtr = p_sp;    //将用户定义的该任务的任务堆栈栈顶指针赋值给任务自己的任务控制块
    		p_tcb->StkSize = stk_size;
    }
    

    ①、最开始的配置的device是基于ARMCM3 处理器,该处理器的栈是由高地址向低地址生长的(先下增长),所以 p_stk = &p_stk_base[stk_size] 最后一个元素的下一个地址开始的; 当然也可以写成 p_stk = &p_stk_base[stk_size-1];
    ②、*–p_stk = (CPU_STK)0x01000000u; // xPSR的bit24位必须置1;关于该寄存器的bit24位
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    T(bit24):控制指令执行状态,表明本指令是 ARM 指令(置0)还是 Thumb 指令(置1);
    ③、 赋值时的各个寄存器的顺序? 初始化的顺序固定,首先是异常发生时自动保存的 8 个寄存器,即 xPSR、R15、R14、R12、R3、R2、R1 和 R0。其中 xPSR 寄存器的位 24 必须是 1,R15(PC) 指针必须存的是任务的入口地址,R0 必须是任务形参 ;除了R15、R13、xPSR和 R0以外,其它寄存器为了调试方便,填入与寄存器号相对应的 16 进制数;【★★★暂时没深究初始化为什么是这个固定顺序】
    ④、对于程序状态寄存器xPSR而言不属于通用寄存器,所以ARM专门为其设立了这2条访问指令,该指令也可用于中断屏蔽寄存器;
    在这里插入图片描述
    在这里插入图片描述

    ⑤、除了程序状态寄存器,还有中断屏蔽寄存器PRIMASK / FAULTMASK / BASEPRI这三个,具体含义不在此赘述;修改程序状态指令:CPS(CPSIE/CPSID enable/disable 使能/除能) 直接修改xPSR寄存器的bit位、仅在特权模式下,才允许访问这三个寄存器
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    ⑥、Processor mode and privilege levels for software execution
    控制器寄存器CONTROL
    https://zhuanlan.zhihu.com/p/274534292
    特权级/非特权级(用户级)(特权等级)、handler模式/线程模式 (两种处理器模式);
    CM3内核支持两种处理器模式,引入两种模式是用于区别普通应用程序和异常服务例程(含中断服务例程),说白了就是handler模式下干异常服务例程的事,线程模式下干正常程序的事;引入两种特权等级是提供一种对于访问CPU内部特殊寄存器等寄存器的保护机制,普通的用户程序不能意外的,恶意的执行涉及到要害的操作,复位后CONTROL寄存器默认值是处于特权级+线程模式,而CONTROL寄存器在特权级下可以RW,所以CONTROL[0]很容易置1进入非特权级的线程模式,用户级下的代码不能再试图修改 CONTROL[0]来回到特权级。它必须通过一个异常 handler,由那个异常 handler 来修改 CONTROL[0],才能在返回到线程模式后拿到特权级;复位后,处理器CPU默认会进入线程模式+特权级;CONTROL[0]只有在特权级下才能访问。用户级的程序如想进入特权级,通常都是使用一条“系统服务呼叫指令(SVCsupervisor call)”来触发“SVC 异常”,该异常的服务例程可以选择修改CONTROL[0];
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    (一)、Thread mode:线程模式用于执行应用程序软件,处理器退出复位后进入线程模式。
    Handler mode:处理程序模式用于处理异常例程,处理器完成异常处理后返回线程模式。
    非特权级别Unprivileged:
    •对MSR和MRS指令的访问权限有限,无法访问使用CPS(CPSID/CPSIE)指令
    •无法访问系统定时器、NVIC或系统控制块寄存器
    •可能限制了对内存或外围设备的访问。
    非特权软件在非特权级别执行。
    特权级别privileged:
    软件可以使用所有指令,并可以访问所有资源。
    特权软件在特权级别执行。
    (二)、MSP/PSP
    在这里插入图片描述
    默认情况下(参考CONTROL控制器寄存器bit位),线程模式使用MSP。要将线程模式中使用的堆栈指针切换到PSP,请使用MSR指令将当前活动堆栈指针bit位设置为1;在 Cortex‐M3 的 handler 模式中,CONTROL[1]总是 0。在线程模式中则可以为 0 或 1(当然仅在线程模式+特权级别下该bit位才能写);
    (三)CM3堆栈操作
    CM3(Cortex-M3)采用的是增长方向为向下的内核;所以任务堆栈栈顶地址为:&MyTaskStk[TaskSize-1];
    在这里插入图片描述
    对于MSP/PSP双堆栈的使用切换可参考顶部文献,在此不赘述;
    (四)AAPCS下的堆栈帧Stack frame 固定初始化顺序
    在符号该规则下的应用程序中,响应异常时的堆栈操作是要进行双字对齐的(8字节对齐);所以一批是8个寄存器;
    对于cortex-M3、M4内核来说,R0-R3、R12、LR、PC、xPSR是由硬件帮你保存。剩余的寄存器就需要自己保存了;
    在这里插入图片描述
    (五)SVC调用系统服务函数指令(软中断)
    ①、操作系统不让用户程序直接访问硬件;操作系统提供一些系统服务函数;用户程序通过SVC指令产生SVC异常,来调用系统服务函数,间接访问硬件;
    【tips】直接对外设硬件操作的是底层驱动程序,对于应用程序来讲,底层驱动程序已经是操作系统的一部分了;
    ②、CONTROL[0]只有在特权级下才能访问。用户级的程序如想进入特权级,通常都是使用一条“系统服务呼叫指令(SVCsupervisor call)”来触发“SVC 异常”,该异常的服务例程可以选择修改CONTROL[0];通过异常使处理器进入handler模式(ISR下都是特权模式);
    ③、SVC异常时必须立即得到响应的,若其优先级不比当前正在处理的异常的优先级高,或是其它原因使之无法立即响应,将上访升级为硬fault;用户使用SVC指令都是希望被立即得到响应;不能在SVC的ISR中使用SVC指令,因为同优先级的异常不能抢占自身,会产生用法fault;同理,在NMI的ISR里也不能使用SVC指令,否则触发硬fault;
    ④、在μCOS中并未使用SVC这个功能
    ⑤、
    (六)PendSV指令(软中断)
    1、PendSV可以像普通的中断一样被悬起,而不会像SVC那样不被立即执行就会升级上访到硬fault,
    操作系统可利用它能缓期执行(先悬挂),直到其它重要的(优先级高)任务完成后再执行动作,一般设置PendSV异常优先级为最低;
    2、悬起PendSV的,是通过向ICSR (中断控制及状态寄存器) 的bit28(PENDSVSET) 置1;
    3、PendSV典型应用于上下文切换(在不同任务之间切换),一般放在定时器的ISR里执行,
    而且常常选用systick定时器(该异常需要使能systick寄存器bit位才能进ISR执行),而且上下文切换的前提是不能有中断嵌套
    (还有未正常退出的中断响应,必须得先执行完所有的中断响应才能进行任务切换,才能满足所谓的实时性);
    3.1、同时一旦进入上下文切换,就进入临界段(关中断区间,期间无法响应中断);
    4、① 不使用PendSV指令时:
    任务切换函数放在systick的ISR里执行,但systick的异常响应(★★★系统定时器的优先级设置为最高,作为任务的时基),
    任务通过系统定时器定时(该任务被安排执行多长时间,时间到了进systick的异常中断响应ISR,进行任务切换)
    这段话的时序场景是:系统先响应了其他低优先级的中断,随后有任务执行时间到了要进行任务切换(★★★要注意不是在进行任务切换中响应其他中断是);
    systick异常会打断低优先级的ISR执行,导致其他中断不能及时响应执行,不够实时;
    如果systick抢占了其他中断响应,那么也不能进行上下文切换,内核会报用法fault错误;
    当一直有其他中断,并且中断频率和systick异常的频率接近时,上下文切换(任务切换)就会被拖延,
    说白了就是系统轮到该进行任务切换时却受到其它中断响应的掣肘;甚至不能够进行任务切换,那这肯定违背了一个实时性多任务的操作系统原则;
    ② 使用PendSV指令时:
    PendSV指令的设计就是避免上面时序场景下的尴尬,而且把PendSV指令放到systick异常的ISR里执行,
    而把上下文切换逻辑放到PendSV异常ISR里执行(产生PendSV异常);
    如果os判断到某中断ISR正常执行然后被systick抢占,它将悬起一个PendSV异常,以便缓期执行上下文切换(任务切换);
    PendSV异常优先级设置为最低,以避让其他中断的响应;
    5、在ARM的cm3内核中,当中断响应后正在执行其ISR时(此时处理器处于handler模式),是不允许rtos从handler模式切换到线程模式的,
    强行切换会报用法fault错误;
    【配图参考】正点原子STM32F1 UCOS开发手册_V2.0 第二章 SVC异常

    持续更新中。。。

  • 相关阅读:
    微机原理:逻辑运算指令、移位指令
    移动应用测试快速指南
    6-8 最宽层次结点数 分数 10
    Apache Calcite入门
    修改Ubuntu的镜像源为中科大镜像源
    蓝牙资讯|苹果iOS 18增加对AirPods Pro 2自适应音频的更多控制
    Linux网络技术学习(四)—— 用户空间与内核的接口
    分库分表的介绍
    Flink Window&Time 原理
    仿牛客论坛项目
  • 原文地址:https://blog.csdn.net/SUR0608/article/details/127071790