1、什么是临界段
//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           //进入临界区
    //创建TASK1任务
    xTaskCreate((TaskFunction_t )task1_task,
                (const char*    )"task1_task",
                (uint16_t       )TASK1_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK1_TASK_PRIO,
                (TaskHandle_t*  )&Task1Task_Handler);
    //创建TASK2任务
    xTaskCreate((TaskFunction_t )task2_task,
                (const char*    )"task2_task",
                (uint16_t       )TASK2_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK2_TASK_PRIO,
                (TaskHandle_t*  )&Task2Task_Handler);
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}

大家有没有注意在开始任务中创建了两个任务,在开头和结尾分别加上了进入临界区taskENTER_CRITICAL(); 和退出临界区taskEXIT_CRITICAL();的代码。

临界段用一句话概括就是一段在执行的时候不能被中断的代码段

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

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

2、关中断

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

不带返回值的关中断函数

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

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

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

【FreeRTOS】FreeRTOS学习笔记(6)— 中断+临界区的保护_临界区

【FreeRTOS】FreeRTOS学习笔记(6)— 中断+临界区的保护_寄存器_02
【FreeRTOS】FreeRTOS学习笔记(6)— 中断+临界区的保护_#define_03

带返回值的关中断函数

【FreeRTOS】FreeRTOS学习笔记(6)— 中断+临界区的保护_临界区_04【FreeRTOS】FreeRTOS学习笔记(6)— 中断+临界区的保护_寄存器_05
1、带返回值的关中断函数,可以嵌套,可以在中断里面使用。带返回值的意思是:在往 BASEPRI写入新的值的时候,先将BASEPRI的值保存起来,在更新完BASEPRI的值的时候,将之前保存好的BASEPRI的值返回,返回的值作为形参传入开中断函数。

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

3、保存BASEPRI的值,记录当前哪些中断被关闭

4、返回原来BASEPRI的值

3、开中断

FreeRTOS开中断的函数在portmacro.h中定义
【FreeRTOS】FreeRTOS学习笔记(6)— 中断+临界区的保护_临界区_06

4、进入临界段

【FreeRTOS】FreeRTOS学习笔记(6)— 中断+临界区的保护_临界区_07

4.1、不带版本保护,不能嵌套

/* ==========进入临界段,不带中断保护版本,不能嵌套=============== */
/* 在 task.h 中定义 */
#define taskENTER_CRITICAL()               portENTER_CRITICAL()

/* 在 portmacro.h 中定义 */
#define portENTER_CRITICAL()                      vPortEnterCritical()

/* 在 port.c 中定义 */
void vPortEnterCritical( void )
{
    portDISABLE_INTERRUPTS();
    uxCriticalNesting++;

    /* 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 )
    {
        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* */
    }
}

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
	{
		mrs ulReturn, basepri
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}	
	return ulReturn;
 }
5、退出临界段

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

【FreeRTOS】FreeRTOS学习笔记(6)— 中断+临界区的保护_#define_08

/* ==========退出临界段,不带中断保护版本,不能嵌套=============== */
/* 在 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
	{
		msr basepri, ulBASEPRI
	}
}

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

【FreeRTOS】FreeRTOS学习笔记(6)— 中断+临界区的保护_临界区_09

/* ==========退出临界段,带中断保护版本,可以嵌套=============== */
/* 在 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
	{
		msr basepri, ulBASEPRI
	}
}

试验应用

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           //进入临界区
    //创建TASK1任务
    xTaskCreate((TaskFunction_t )task1_task,
                (const char*    )"task1_task",
                (uint16_t       )TASK1_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK1_TASK_PRIO,
                (TaskHandle_t*  )&Task1Task_Handler);
    //创建TASK2任务
    xTaskCreate((TaskFunction_t )task2_task,
                (const char*    )"task2_task",
                (uint16_t       )TASK2_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK2_TASK_PRIO,
                (TaskHandle_t*  )&Task2Task_Handler);
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}