• 基于STM32结合CubeMX学习Free-RT-OS的源码之两类中断解析


    目录

    认识

     常见的中断应用场景

    进入与退出临界区(开中断与关中断)

    Free RTOS的systick中断 和 PendSV中断。

    Cortex-M3/4的工作模式以及双堆栈指针MSP和PSP(CPU与OS的相辅相成与互相成就)

    为什么要引入这两种工作状态?

    为什么区分出MSP与PSP?


    认识

            Free RT OS提供了OS级别的API(比如信号量,队列,互斥量等等),有些以FromISR为后缀的API能够在中断中使用,那么他们到底是适用于所有的中断?还是仅仅只是在一些中断中可以适用?

            我们知道在裸机开发中,中断是可以抢占的,这种抢占也就是中断嵌套。Free RT OS里也遵循这个规则。Free-RT-OS里的中断可以人为的去设置其优先级。( Free-RT-OS里有任务的优先级,优先级设置地越大则优先级越高),而Free RT OS里的中断与裸机一样,设置的数字越小则优先级越高,以前我习惯性地把裸机开发的那些中断(比如硬件定时器,串口,DMA,系统滴答定时器产生的中断等这些中断)称为硬件中断。裸机开发中不一定需要滴答定时器去计时。而RTOS则一定需要一个定时器去计时 ,构成系统的心跳。在Free RTOS中最低优先级的中断也可以打断最高优先级的任务。

    而Free RT OS把这些"硬件中断"又分为两类。 以一个临界值  

     划分为更高优先级的中断,和在它之下的中断。 如下图

     大于这个临界值(越大优先级越低)则可以适用Free RTOS的API(也就是下图中的B类中断)

    小于这个临界值(越小优先级越高) 则不允许适用于Free  RTOS的API,也就是下图中的A类中断。

    一旦在A类中断(更高优先级的中断)中使用Free RTOS的API则导致死循环。)

     在以ISR为后缀的API中通常带有校验机制(判断此时中断的优先级是否有效。) 

    为什么Free RTOS禁止更高优先级的中断中使用Free RTOS的API?

    中断从设计之出是为了解决及时性情况而创造出来的,作为一个开源免费的操作系统,它不希望承担任何责任,比如汽车的临时刹车中断,Free RTOS说明请用户在更高优先级的中断中使用自己的代码,而不是它的api。

     而B类中断,则可以使用Free RTOS的API。  而B类中断包括systick的中断,PendSV中断,定时器溢出中断,串口中断等等。

     常见的中断应用场景

    进入与退出临界区(开中断与关中断)

    taskENTER_CRITICAL() 进入临界区。

    taskEXIT_CRITICAL();  退出临界区。

    他们对应宏

     

    这里的开关中断只是禁止CPU去响应中断,因为中断标记位并没有清除,待开启中断后会立刻响应之前的中断并把标记位清零。

    Free RTOS的systick中断 和 PendSV中断。

    准确的说是systick中断做任务调度。PendSV进行任务上下文的切换。

    Free RTOS默认以systick作为系统时基

    1. void xPortSysTickHandler( void )
    2. {
    3. uint32_t ulPreviousMask;
    4. ulPreviousMask = portSET_INTERRUPT_MASK_FROM_ISR();
    5. {
    6. /* Increment the RTOS tick. */
    7. if( xTaskIncrementTick() != pdFALSE )
    8. {
    9. /* Pend a context switch. */
    10. *(portNVIC_INT_CTRL) = portNVIC_PENDSVSET;
    11. }
    12. }
    13. portCLEAR_INTERRUPT_MASK_FROM_ISR( ulPreviousMask );
    14. }

    最终调用    xTaskIncrementTick()

    在解除阻塞链表的阻塞任务时,通过xNextTaskUnblockTime  (当前阻塞链表最有可能第一个解除阻塞的时间)来减少判断次数。

    在当前时间

    否则执行:

    判断是否有任务在阻塞链表中阻塞,若有则取出阻塞链表的第一个任务也是第一个会解决阻塞的任务(阻塞链表按阻塞时间升序排列),若是则判断当前是否能解除阻塞,若不能则从阻塞链表中更新xNextTaskUnblockTime。 若能解除阻塞,能加入到它所在优先级的就绪链表中。  

    在判断是否需要唤醒阻塞任务后,判断当前任务优先级的就绪链表中是否有与当前优先级一样的任务,若是,则在配置为可抢占和时间片轮转成立的同时执行相同时间片轮转算法。

    PendSV (切换任务,且有中断触发则延期执行)

    PendSV(可悬起的系统调用),它是一种CPU系统级别的异常,它可以像普通外设中断一样被悬起,而不会像SVC服务那样,因为没有及时响应处理,而触发Fault。

    为什么需要PendSV缓期执行?

    引用

    SVC(系统服务调用)和PendSV(可悬起系统调用)_宁静以致墨的博客-CSDN博客_svc和pendsv

    根据 权威指南。PendSV是为系统设备而设的“可悬挂请求”(pendable request)。

    上下文切换 不能在中断中进行,会导致中断延期。为了解决这个问题,使用 PendSV。PendSV可以挂起,也就是等到别的 ISR结束后缓期执行。
    为了实现缓期执行PendSV,PendSV一定要被设置为最低优先级的异常。
    =

    众所周知,在C/C++值可直接嵌入汇编代码。

     PendSV(功能:将当前任务(寄存器的值)入任务栈,把新任务的任务栈的值取出装载进寄存器)

    见代码: 

    1. __asm void xPortPendSVHandler( void )
    2. {
    3. extern vTaskSwitchContext
    4. extern pxCurrentTCB
    5. PRESERVE8
    6. mrs r0, psp
    7. ldr r3, =pxCurrentTCB /* Get the location of the current TCB. */
    8. ldr r2, [r3]
    9. subs r0, #32 /* Make space for the remaining low registers. */
    10. str r0, [r2] /* Save the new top of stack. */
    11. stmia r0!, {r4-r7} /* Store the low registers that are not saved automatically. */
    12. mov r4, r8 /* Store the high registers. */
    13. mov r5, r9
    14. mov r6, r10
    15. mov r7, r11
    16. stmia r0!, {r4-r7}
    17. push {r3, r14}
    18. cpsid i
    19. bl vTaskSwitchContext
    20. cpsie i
    21. pop {r2, r3} /* lr goes in r3. r2 now holds tcb pointer. */
    22. ldr r1, [r2]
    23. ldr r0, [r1] /* The first item in pxCurrentTCB is the task top of stack. */
    24. adds r0, #16 /* Move to the high registers. */
    25. ldmia r0!, {r4-r7} /* Pop the high registers. */
    26. mov r8, r4
    27. mov r9, r5
    28. mov r10, r6
    29. mov r11, r7
    30. msr psp, r0 /* Remember the new top of stack for the task. */
    31. subs r0, #32 /* Go back for the low registers that are not automatically restored. */
    32. ldmia r0!, {r4-r7} /* Pop low registers. */
    33. bx r3
    34. ALIGN
    35. }

    Cortex-M3/4的工作模式以及双堆栈指针MSP和PSP(CPU与OS的相辅相成与互相成就)

    我们听说过SP。在平时的裸机开发中SP就是MSP,而PSP则是为了OS而产生的。

    SP:Stack Pointer,堆栈指针,也是通用寄存器,用于入栈和出栈操作。在任何情况下,SP只会指向MSP与PSP其中一种。所以SP属于逻辑指针。

    来着Cortex-M3权威指南 

    它们都在寄存器R13,于是干脆用SP去引用R13,

    PSP与MSP对应CPU的两种特权分级(对应CPU的两种工作模式)

    用户态 (目态)  只能使用PSP指针 (不给程序员操作OS的机会)

    系统态(管理态,管态) PSP与MSP都能使用。当发生异常或中断时一定使用MSP,裸机程序开发则一直使用MSP。

    为什么要引入这两种工作状态?

    引入这两个工作状态的原因是:为了避免用户程序错误地使用特权指令,保护操作系统不被用户程序破坏。具体规定为,当CPU处于用户态时,不允许执行特权指令;当CPU处于系统态时,可执行包括特权指令在内的一切机器指令。

    为什么区分出MSP与PSP?

    为了安全,裸机开发则完全对寄存器开发,属于CPU的内核工作模式。而OS的多线程(任务)则有其自己的任务栈,这样CPU的两种工作模式都使用不同的堆栈,它们的堆栈分离更加安全。

    PSP的堆栈用用户在创建任务(线程)时划分。而MSP的堆栈则在启动文件中划分。

    关于PSP与MSP的切换

    若CPU工作在用户态,则使用的是PSP堆栈指针,SP此时为PSP。中断或异常发生后,中断或异常必定打断用户任务(最低优先级的中断也会打断最高优先级的任务),CPU切换为系统态,PSP管理用户任务程序进栈(由寄存器进栈), 此时PSP切换为MSP,管理中断或异常处理程序的出栈。(由栈取出到寄存器)

    区分SP此时为PSP还是MSP,取决于CPU的工作状态(系统态还是用户态)。总的规律:中断异常处理程序必定使用MSP.用户任务程序只能使用PSP。

  • 相关阅读:
    【ES的优势和原理及分布式开发的好处与坏处】
    自动化测试 —— Pytest fixture及conftest详解!
    点云从入门到精通技术详解100篇-基于点云和图像的智能交通路侧感知(续)
    HttpServletRequest详解
    Current request is not a multipart request 状态码:511 异常
    注册gitlab-runner
    2022年0702 第八课 JAVA中的数据结构重中的集合
    Mysql类的封装
    CentOS系统/root根目录扩容(扩展逻辑卷)
    Redis(6)五大数据类型——List(列表)
  • 原文地址:https://blog.csdn.net/PHILICS7/article/details/127892987