第8章 临界段的保护-总结

整理 野火 《FreeRTOS 内核实现与应用开发实战指南》—基于野火 STM32 全系列(M3/4/7)开发板

第8章 临界段的保护

8.1 什么是临界段

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

8.2 Cortex-M 内核快速开关中断指令

  为了快速地开关中断, Cortex-M 内核专门设置了一条 CPS 指令,有 4 种用法,具体见代码清单 8-1。
代码清单 8-1 CPS 指令用法
在这里插入图片描述
  代码清单 8-1PRIMASKFAULTMASTCortex-M内核里面三个中断屏蔽寄存器中的两个,还有一个是 BASEPRI,有关这三个寄存器的详细用法见表格8-1。

名字功能描述
PRIMASK这是个只有单一比特的寄存器。 在它被置 1 后,就关掉所有可屏蔽的异常,只剩下 NMI硬 FAULT可以响应。它的缺省值是 0表示没有关中断
FAULTMASK这是个只有 1 个位的寄存器。当它置 1 时,只有 NMI 才能响应,所有其它的异常,甚至是硬 FAULT,也通通闭嘴。它的缺省值也是 0表示没有关异常
BASEPRI这个寄存器最多有 9 位(由表达优先级的位数决定)。它定义了被屏蔽优先级的阈值。当它被设成某个值后,所有优先级号大于等于此值的中断都被关(优先级号越大,优先级越低)。但若被设成 0,则不关闭任何中断, 0 也是缺省值。

  在 FreeRTOS 中,对中断的开和关是通过操作 BASEPRI 寄存器来实现的,即大于等于 BASEPRI 的值的中断会被屏蔽,小于 BASEPRI 的值的中断则不会被屏蔽,不受FreeRTOS 管理。

8.3 关中断

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

8.3.1 不带返回值的关中断函数

/**
 * @brief 提升BASEPRI寄存器的值以进入临界区
 * 
 * 此函数通过将BASEPRI寄存器设置为configMAX_SYSCALL_INTERRUPT_PRIORITY来屏蔽所有优先级低于或等于该值的中断,从而进入临界区。 
 * 不带返回值的关中断函数,不能嵌套,不能在中断里面使用
 * 
 * @param void 无参数
 * @return void 无返回值
 */
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
    // 定义一个变量ulNewBASEPRI,并将其初始化为configMAX_SYSCALL_INTERRUPT_PRIORITY
    uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

    // 使用内联汇编代码来设置BASEPRI寄存器的值
    __asm
    {
        /* 将BASEPRI寄存器设置为ulNewBASEPRI的值,以屏蔽所有优先级低于或等于该值的中断 */
        msr basepri, ulNewBASEPRI
        /* 数据同步屏障,确保之前的内存访问操作完成 */
        dsb
        /* 指令同步屏障,确保之前的指令执行完成 */
        isb
    }
}

8.3.2 带返回值的关中断函数

/**
 * @brief 提升BASEPRI寄存器的值以进入临界区
 * 
 * 此函数通过将BASEPRI寄存器设置为configMAX_SYSCALL_INTERRUPT_PRIORITY来屏蔽所有优先级低于或等于该值的中断,从而进入临界区。
 * 带返回值的关中断函数,可以嵌套,可以在中断里面使用
 * 
 * @param void 无参数
 * @return uint32_t 返回之前的BASEPRI寄存器值
 */
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
    // 定义一个变量ulReturn,用于存储之前的BASEPRI寄存器值
    uint32_t ulReturn;
    // 定义一个变量ulNewBASEPRI,并将其初始化为configMAX_SYSCALL_INTERRUPT_PRIORITY
    uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

    // 使用内联汇编代码来设置BASEPRI寄存器的值
    __asm
    {
        /* 读取当前的BASEPRI寄存器值并存储到ulReturn中 */
        mrs ulReturn, basepri
        /* 将BASEPRI寄存器设置为ulNewBASEPRI的值,以屏蔽所有优先级低于或等于该值的中断 */
        msr basepri, ulNewBASEPRI
        /* 数据同步屏障,确保之前的内存访问操作完成 */
        dsb
        /* 指令同步屏障,确保之前的指令执行完成 */
        isb
    }

    // 返回之前的BASEPRI寄存器值
    return ulReturn;
}

8.4 开中断

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

/* 不带中断保护的开中断函数 */
#define portENABLE_INTERRUPTS()              vPortSetBASEPRI( 0 )

/* 带中断保护的开中断函数 */
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)

/**
 * @brief 设置BASEPRI寄存器的值
 * 
 * 此函数通过将BASEPRI寄存器设置为指定的值来控制中断的屏蔽。
 * 
 * @param ulBASEPRI 要设置的BASEPRI寄存器的值
 * @return void 无返回值
 */
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
    // 使用内联汇编代码来设置BASEPRI寄存器的值
    __asm
    {
        /* Barrier instructions are not used as this function is only used to
        lower the BASEPRI value. */
        msr basepri, ulBASEPRI
    }
}

8.5 进入/退出临界段的宏

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

8.5.1 进入临界段

  进入临界段,不带中断保护版本且不能嵌套的代码实现具体见代码。

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

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

/* 在 port.c 中定义 */
void vPortEnterCritical( void )
{
	/* 禁用中断 */
	portDISABLE_INTERRUPTS();
	/* 增加临界区嵌套计数器 */
	uxCriticalNesting++;

	/* 如果是第一次进入临界区,检查当前是否有中断正在执行 */
	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
	{
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}
}
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     // 从 BASEPRI 寄存器读取当前的值到 ulReturn 变量
		msr basepri, ulNewBASEPRI // 将新的 BASEPRI 值(configMAX_SYSCALL_INTERRUPT_PRIORITY)写入 BASEPRI 寄存器
		dsb                       // 数据同步屏障(Data Synchronization Barrier),确保内存操作的顺序性
		isb                       // 指令同步屏障(Instruction Synchronization Barrier),确保指令的顺序性
	}

	return ulReturn;              // 返回 BASEPRI 寄存器的原始值
}

8.5.2 退出临界段

  退出临界段,不带中断保护版本且不能嵌套的代码实现具体见代码。

1. 不带中断保护的版本,不能嵌套
/* ==========退出临界段,不带中断保护版本,不能嵌套=============== */
/* 在 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
	}
}
2. 带中断保护的版本,可以嵌套
/* ==========退出临界段,带中断保护版本,可以嵌套=============== */
/* 在 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
	}
}

8.6 临界段代码的应用

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

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

	/* 临界段代码 */

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

/* 在非中断场合,临界段不能嵌套 */
{
	/* 进入临界段 */
	taskENTER_CRITICAL();

	/* 临界段代码 */

	/* 退出临界段*/
	taskEXIT_CRITICAL();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值