搜索
您的当前位置:首页正文

FreeRTOS学习记录(五):临界段的保护

来源:筏尚旅游网

2022-04-24

依据:[野火]《FreeRTOS内核实现与应用开发实战指南》



每天激励自己学习!!

不要忘了心中的梦想和对生活的热望~!

一、临界段

临界段用一句话概括就是一段在执行的时候不能被中断的代码段。在 FreeRTOS 里面, 这个临界段最常出现的就是对全局变量的操作,全局变量就好像是一个枪把子,谁都可以 对他开枪,但是我开枪的时候,你就不能开枪,否则就不知道是谁命中了靶子。可能有人会说我可以在子弹上面做个标记,我说你能不能不要瞎扯淡。

那么什么情况下临界段会被打断?一个是系统调度,还有一个就是外部中断。在 FreeRTOS,系统调度,最终也是产生 PendSV 中断,在 PendSV Handler 里面实现任务的切换,所以还是可以归结为中断。既然这样,FreeRTOS 对临界段的保护最终还是回到对中断的开和关的控制

二、Cortex-M关中断指令

为了快速地开关中断, Cortex-M 内核专门设置了一条 CPS 指令,有 4 种用法

//CPS 指令用法
CPSID I ;PRIMASK=1 ;//关中断
CPSIE I ;PRIMASK=0 ;//开中断
CPSID F ;FAULTMASK=1 ;//关异常
CPSIE F ;FAULTMASK=0 ;//开异常

PRIMASK FAULTMAST 是 Cortex-M内核 里面三个中断屏蔽寄存 器中的两个,还有一个是 BASEPRI

Cortex-M 内核中断屏蔽寄存器组描述

在 FreeRTOS 中,对中断的开和关是通过操作BASEPRI 寄存器来实现的,即大于等于 BASEPRI 的值的中断会被屏蔽,小于 BASEPRI 的值的中断则不会被屏蔽,不受 FreeRTOS 管理。用户可以设置 BASEPRI 的值来选择性的给一些非常紧急的中断留一条后路。

三、关中断

FreeRTOS 关中断的函数在 portmacro.h 中定义,分不带返回值带返回值两种

//portmacro.h

#define portDISABLE_INTERRUPTS()                  vPortRaiseBASEPRI()
#define portSET_INTERRUPT_MASK_FROM_ISR()         ulPortRaiseBASEPRI()


/*-----------------------------------------------------------*/
//不带返回值的关中断函数,不能嵌套,不能在中断里面使用            //(1)
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
    uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;    //(2)

    __asm
    {
            /* Set BASEPRI to the max syscall priority to effect a critical
             * section. */
/* *INDENT-OFF* */
        msr basepri, ulNewBASEPRI       //(3)
        dsb
        isb
/* *INDENT-ON* */
    }
}
/*-----------------------------------------------------------*/
/*-----------------------------------------------------------*/
//带返回值的关中断函数,可以嵌套,可以在中断里面使用              //(4)
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
    uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;    //(5)

    __asm
    {
            /* Set BASEPRI to the max syscall priority to effect a critical
             * section. */
/* *INDENT-OFF* */
        mrs ulReturn, basepri    //(6)
        msr basepri, ulNewBASEPRI        //(7)
        dsb
        isb
/* *INDENT-ON* */
    }

    return ulReturn;        //(8)
}
/*-----------------------------------------------------------*/

(1)不带返回值的关中断函数,不能嵌套,不能在中断里面使用。不带返回值的意思是:在往 BASEPRI 写入新的值的时候,不用先将 BASEPRI 的值保存起来, 即不用管当前的中断状态是怎么样的,既然不用管当前的中断状态,也就意味着这样的函数不能在中断里面调用。

(2)configMAX_SYSCALL_INTERRUPT_PRIORITY 是 一 个在 FreeRTOSConfig.h 中定义的宏,即要写入到 BASEPRI 寄存器的值。该宏默认定义为 191,高四位有效,即等于 0xb0,或者是 11,即优先级大于等于 11 的中断都会被屏蔽,11 以内的中断则不受 FreeRTOS 管理。

(3)将 configMAX_SYSCALL_INTERRUPT_PRIORITY 的值写入 BASEPRI 寄存器,实现关中断(准确来说是关部分中断)。

(4)带返回值的关中断函数,可以嵌套,可以在中断里面使用。带返回值的意思是:在往 BASEPRI 写入新的值的时候,先将 BASEPRI 的值保存起来,在更新完BASEPRI 的值的时候,将之前保存好的 BASEPRI 的值返回,返回的值作为形参传入开中断函数。

(5)configMAX_SYSCALL_INTERRUPT_PRIORITY 是 一 个 在 FreeRTOSConfig.h 中定义的宏,即要写入到 BASEPRI 寄存器的值。该宏默认定义为 191, 高四位有效,即等于 0xb0,或者是 11,即优先级大于等于 11 的中断都会被屏蔽,11 以内 的中断则不受 FreeRTOS 管理。

(6)保存 BASEPRI 的值,记录当前哪些中断被关闭。

(7)更新 BASEPRI 的值。

(8)返回原来 BASEPRI 的值。

四、开中断

FreeRTOS 开中断的函数在 portmacro.h 中定义

//portmacro.h

#define portENABLE_INTERRUPTS()                   vPortSetBASEPRI( 0 )    //(2)

#define portCLEAR_INTERRUPT_MASK_FROM_ISR( x )    vPortSetBASEPRI( x )    //(3)
/*-----------------------------------------------------------*/

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )        //(1)
{
    __asm
    {
        /* Barrier instructions are not used as this function is only used to
         * lower the BASEPRI value. */
/* *INDENT-OFF* */
        msr basepri, ulBASEPRI
/* *INDENT-ON* */
    }
}
/*-----------------------------------------------------------*/

(1)开中断函数,具体是将传进来的形参更新到 BASEPRI 寄存器。根 据传进来形参的不同,分为中断保护版本非中断保护版本

(2)不带中断保护的开中断函数,直接将 BASEPRI 的值设置为 0,与portDISABLE_INTERRUPTS()(不带返回值的关中断函数)成对使用。

(3)带中断保护的开中断函数,将上一次关中断时保存的 BASEPRI 的 值作为形参 ,与 portSET_INTERRUPT_MASK_FROM_ISR()(带返回值的关中断函数)成对使用。

五、进入/退出临界段的宏

进入和退出临界段的宏在 task.h 中定义

//task.h
#define taskENTER_CRITICAL()               portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR()      portSET_INTERRUPT_MASK_FROM_ISR()

#define taskEXIT_CRITICAL()                portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x )    portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

进入和退出临界段的宏分中断保护版本非中断版本,但最终都是通过开/关中断来实现。有关开/关中断的底层代码我们已经讲解,那么接下来的退出和进入临界段的代码配套 注释来理解即可。

1、进入临界段

不带中断保护版本,不能嵌套

/* ==========进入临界段,不带中断保护版本,不能嵌套============== */

/*-----------------------------------------------------------*/
//task.h
#define taskENTER_CRITICAL()               portENTER_CRITICAL()
//portmacro.h
#define portENTER_CRITICAL()                      vPortEnterCritical()
//port.c
void vPortEnterCritical( void )
{
    portDISABLE_INTERRUPTS();
    uxCriticalNesting++;     //(1)

    /* This is not the interrupt safe version of the enter critical function so
     * assert() if it is being called from an interrupt context.  Only API
     * functions that end in "FromISR" can be used in an interrupt.  Only assert if
     * the critical nesting count is 1 to protect against recursive calls if the
     * assert function also uses a critical section. */
    if( uxCriticalNesting == 1 )    //(2)
    {
        configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
    }
}
/*-----------------------------------------------------------*/

/*-----------------------------------------------------------*/
//portmacro.h
#define portDISABLE_INTERRUPTS()                  vPortRaiseBASEPRI()
//portmacro.h
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
    uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

    __asm
    {
        /* Set BASEPRI to the max syscall priority to effect a critical
         * section. */
/* *INDENT-OFF* */
        msr basepri, ulNewBASEPRI
        dsb
        isb
/* *INDENT-ON* */
    }
}
/*-----------------------------------------------------------*/

(1)uxCriticalNesting 是在 port.c 中定义的静态变量,表示临界段嵌套计数器 ,默认初始化为 0xaaaaaaaa ,在调度器启动时会被重新初始化为 0 : vTaskStartScheduler()->xPortStartScheduler()->uxCriticalNesting = 0

(2)如果 uxCriticalNesting 等于1,即一层嵌套,要确保当前没有中断活跃,即内核外设 SCB 中的中断和控制寄存器 SCB_ICSR 的低 8 位要等于 0。有关SCB_ICSR 的具体描述可参考“STM32F10xxx Cortex-M3 programming manual-4.4.2 小节”。

带中断版本,可以嵌套

/* ==========进入临界段,带中断保护版本,可以嵌套=============== */
//task.h
#define taskENTER_CRITICAL_FROM_ISR()      portSET_INTERRUPT_MASK_FROM_ISR()
//portmacro.h
#define portSET_INTERRUPT_MASK_FROM_ISR()         ulPortRaiseBASEPRI()
//portmacro.h
/*-----------------------------------------------------------*/
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
    uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

    __asm
    {
        /* Set BASEPRI to the max syscall priority to effect a critical
         * section. */
/* *INDENT-OFF* */
        mrs ulReturn, basepri
        msr basepri, ulNewBASEPRI
        dsb
        isb
/* *INDENT-ON* */
    }

    return ulReturn;
}
/*-----------------------------------------------------------*/

2、退出临界段

不带中断保护的版本,不能嵌套

/* ==========退出临界段,不带中断保护版本,不能嵌套=============== */
//task.h
#define taskEXIT_CRITICAL()                portEXIT_CRITICAL()
//portmacro.h
#define portEXIT_CRITICAL()                       vPortExitCritical()
//port.c
/*-----------------------------------------------------------*/
void vPortExitCritical( void )
{
    configASSERT( uxCriticalNesting );
    uxCriticalNesting--;

    if( uxCriticalNesting == 0 )
    {
        portENABLE_INTERRUPTS();
    }
}
/*-----------------------------------------------------------*/

//portmacro.h
#define portENABLE_INTERRUPTS()                   vPortSetBASEPRI( 0 )
//portmacro.h
/*-----------------------------------------------------------*/
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
   __asm
   {
        /* Barrier instructions are not used as this function is only used to
         * lower the BASEPRI value. */
/* *INDENT-OFF* */
        msr basepri, ulBASEPRI
/* *INDENT-ON* */
    }
}
/*-----------------------------------------------------------*/

带中断保护的版本,可以嵌套

/* ==========退出临界段,带中断保护版本,可以嵌套=============== */
//task.h
#define taskEXIT_CRITICAL_FROM_ISR( x )    portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
//portmacro.h
#define portCLEAR_INTERRUPT_MASK_FROM_ISR( x )    vPortSetBASEPRI( x )
//portmacro.h
/*-----------------------------------------------------------*/
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
    __asm
    {
        /* Barrier instructions are not used as this function is only used to
         * lower the BASEPRI value. */
/* *INDENT-OFF* */
        msr basepri, ulBASEPRI
/* *INDENT-ON* */
    }
}
/*-----------------------------------------------------------*/

六、临界段代码的应用

在 FreeRTOS 中,对临界段的保护出现在两种场合,一种是在中断场合一种是在非中断场合

//临界段代码应用
/* 在中断场合,临界段可以嵌套 */
{
    uint32_t ulReturn;
    /* 进入临界段,临界段可以嵌套 */
    ulReturn = taskENTER_CRITICAL_FROM_ISR();

    /* 临界段代码 */

    /* 退出临界段 */
    taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}

/* 在非中断场合,临界段不能嵌套 */
{
    /* 进入临界段 */
    taskENTER_CRITICAL();
 
    /* 临界段代码 */
 
    /* 退出临界段*/
    taskEXIT_CRITICAL();
}

说明:不就用了这四个函数嘛

/**
 * task. h
 *
 * Macro to mark the start of a critical code region.  Preemptive context
 * switches cannot occur when in a critical region.
 *
 * NOTE: This may alter the stack (depending on the portable implementation)
 * so must be used with care!
 *
 * \defgroup taskENTER_CRITICAL taskENTER_CRITICAL
 * \ingroup SchedulerControl
 */
#define taskENTER_CRITICAL()               portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR()      portSET_INTERRUPT_MASK_FROM_ISR()

/**
 * task. h
 *
 * Macro to mark the end of a critical code region.  Preemptive context
 * switches cannot occur when in a critical region.
 *
 * NOTE: This may alter the stack (depending on the portable implementation)
 * so must be used with care!
 *
 * \defgroup taskEXIT_CRITICAL taskEXIT_CRITICAL
 * \ingroup SchedulerControl
 */
#define taskEXIT_CRITICAL()                portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x )    portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

比较简单啦~~好好理解就好啦~~也没多少内容啦~~~~~~~~~~~~

 

因篇幅问题不能全部显示,请点此查看更多更全内容

Top