跟着野火从零开始手搓FreeRTOS(4)临界段的概念及应用

本文详细解释了在FreeRTOS中如何使用CPS指令管理和控制中断,以保护临界段,包括关中断、开中断函数,以及进入和退出临界段的宏及其嵌套处理。特别强调了中断保护版本和非保护版本的区别。
摘要由CSDN通过智能技术生成

临界段

        临界段就是一段在执行的时候不能被中断的代码段。在FreeRTOS最常用的情况是对全局变量的操作。

        基本上只有和系统调度,外部中断能打断临界段。在FreeRTOS中,系统调度也就是 PendSV 中断,所以对临界段的保护还是在于对中断的开和关的控制。中断被关闭之后,临界段就无法响应中断,从而做到保护临界段。

        Cortex-M 内核专门设置了一条 CPS 指令来实现快速开关中断,PRIMASK 和 FAULTMAST 是 Cortex-M 内核 里面三个中断屏蔽寄存器中的两个,还有一个是 BASEPRI。用法如下。

CPSID I ; PRIMASK=1;    关中断
CPSIE I ; PRIMASK=0;    开中断
CPSID F ; FAULTMASK=1;  关异常
CPSIE F ; FAULTMASK=0;  开异常

         通过对这三个寄存器的操作,使内核能够实现中断的开关。

关中断

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

        下面是不带返回值的。

/* 不带返回值的关中断函数 */
#define portDISABLE_INTERRUPTS()				vPortRaiseBASEPRI()

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. */
		msr basepri, ulNewBASEPRI    //值为11,大于11的一律不能响应
		dsb
		isb
	}
}

        因为不带返回值,即返回值为空,因此不能嵌套。

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

/* 带返回值的关中断函数 */
#define portSET_INTERRUPT_MASK_FROM_ISR()		ulPortRaiseBASEPRI()

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. */
		mrs ulReturn, basepri    //把原先的basepri值保存在返回值中
		msr basepri, ulNewBASEPRI    //设置basepri的新值
		dsb
		isb
	}

	return ulReturn;
}

        这个函数返回了一个32位无符号的整型变量,这意味着这个函数可以有返回值,也可以进行嵌套。 

开中断

#define portENABLE_INTERRUPTS()					vPortSetBASEPRI( 0 )
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)	vPortSetBASEPRI(x)

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. */
		msr basepri, ulBASEPRI
	}
}

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

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

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

        此外,进入和退出临界段的宏在 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 )

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

进入临界段

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

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

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

    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
    {
    msr basepri, ulNewBASEPRI
    dsb
    isb
 }
}

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

vTaskStartScheduler()->xPortStartScheduler()->uxCriticalNesting = 0

        如果 uxCriticalNesting 等于 1,即一层嵌套,要确保当前没有中断活跃,即内核外设 SCB 中的中断和控制寄存器 SCB_ICSR 的低 8 位要等于 0。

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

/* 在 task.h 中定义 */

#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()

/* 在 portmacro.h 中定义 */

#define portSET_INTERRUPT_MASK_FROM_ISR()

ulPortRaiseBASEPRI() 7

/* 在 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;
}

退出临界段

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

 /* 在 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

        }

}
 /* ==========退出临界段,带中断保护版本,可以嵌套=============== */

/* 在 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

        }

}

临界段代码的应用

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

/* 在中断场合,临界段可以嵌套 */

 {

uint32_t ulReturn;

/* 进入临界段,临界段可以嵌套 */

ulReturn = taskENTER_CRITICAL_FROM_ISR();

/* 临界段代码 */

/* 退出临界段 */

taskEXIT_CRITICAL_FROM_ISR( ulReturn );

}

/* 在非中断场合,临界段不能嵌套 */

{

/* 进入临界段 */

taskENTER_CRITICAL();

/* 临界段代码 */

/* 退出临界段*/

taskEXIT_CRITICAL();

}

PS:最近这几篇感觉好水,这部分能理解就行,和源码移植没多大关系,重点在于理解RTOS的机制。 因此打算最近开始直接移植,基础这里看情况更新。

  • 16
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值