FreeRTOS原理剖析:中断管理和临界区

1. 中断的基本概念

1.1 异常类型

Cortex-M处理器中异常编号为1~15为系统异常,编号为16及以上为外部中断异常,可由片上外设或者外设中断源产生。其中复位、NMI、HardFault异常的优先级固定不变,其它异常可编程。

系统异常表:
FreeRTOS原理剖析:中断管理和临界区_第1张图片

中断列表:
FreeRTOS原理剖析:中断管理和临界区_第2张图片

1.2 中断优先级分组

每个中断都有一个8位的优先级寄存器,用来配置中断的优先级。每个优先级占8位,4个相临的优先级寄存器组成1个32位的寄存器。如下:
FreeRTOS原理剖析:中断管理和临界区_第3张图片
其中:
FreeRTOS原理剖析:中断管理和临界区_第4张图片
Cortex-M处理器中有三个固定的优先级和256个可编程的优先级。Cortex-M处理器把8位分成高低两段:抢占优先级(分组优先级)和亚优先级(子优先级),通过设置NVIC中寄存器AIRCR的[10:8]中3位,可配置抢占优先级和亚优先级,有:

FreeRTOS原理剖析:中断管理和临界区_第5张图片
在STM32F4系列单片机中,只使用了优先级的寄存器的Bit4 ~ Bit7,Bit0 ~ Bit3没有实现,当往Bit0 ~ Bit3写入时则会忽略,读时会返回0。

对于STM32F4系列单片机,在misc.h中,有:

#define NVIC_PriorityGroup_0         ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
                                                            4 bits for subpriority */
#define NVIC_PriorityGroup_1         ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
                                                            3 bits for subpriority */
#define NVIC_PriorityGroup_2         ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
                                                            2 bits for subpriority */
#define NVIC_PriorityGroup_3         ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
                                                            1 bits for subpriority */
#define NVIC_PriorityGroup_4         ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
                                                            0 bits for subpriority */

对于FreeRTOS中,没有处理亚优先级的情况,则需要设置高4位全部为抢占优先级位,即需要设置为:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
1.3 用于异常或中断屏蔽的特殊寄存器

包括PRIMASK、FAULTMASK、BASEPRI

1.3.1 PRIMASK

PRIMASK用于禁止除NMI和HardFault外的所有异常。

使用汇编编程中CPS(修改处理器状态)指令修改PRIMASK寄存器的数值。

CPSIE I	;清除PRIMASK(使能中断)
CPSID I	;设置PRIMASK(禁止中断)

也可以使用MRS和MSR指令设置:

MOVS R0, #0
MSR PRIMASK,	R0	;将0写入PRIMASK使能中断

以及:

MOVS R0, #1
MSR PRIMASK,	R0	;将1写入PRIMASK禁止所有中断
1.3.2 FAULTMASK

FAULTMASK比PRIMASK更狠,将HardFault处理也屏蔽,即当FAULTMASK置位时,只有NMI异常才能执行。

使用汇编编程中CPS(修改处理器状态)指令修改FAULTMASK寄存器的状态。

CPSIE F	;清除FAULTMASK,使能中断
CPSID F	;设置FAULTMASK,禁止中断

也可以使用MRS和MSR指令设置:

MOVS R0, #0
MSR FAULTMASK,	R0	;将0写入FAULTMASK使能中断

以及:

MOVS R0, #1
MSR FAULTMASK, R0	;将1写入FAULTMASK禁止所有中断
1.3.3 BASEPRI

BASEPRI用于禁止优先级低于某特定等级的中断。如:要屏蔽优先级不高于0x60的中断,即

MOVS R0, #0X60
MSR BASEPRI, R0

若取消对BASEPRI对中断的屏蔽,则使用

MOVS R0, #0x0
MSR BASEPRI, R0

2. 临界段代码

临界段代码也称为临界区,用于保护那些必须完整执行,不能被打断的代码段。在FreeRTOS中,进入临界段时会关闭中断,当退出临界段时又重新打开中断。

临界段相关的函数有:

taskENTER_CRITICAL()			/* 进入临界段 */
taskEXIT_CRITICAL()				/* 退出临界段 */

taskENTER_CRITICAL_FROM_ISR()	/* 进入临界段 */
taskEXIT_CRITICAL_FROM_ISR()	/* 退出临界段 */

前两个是用于任务级的临界段代码保护,后面两个函数是用于中断级的临界段代码保护,可称为任务锁和中断锁。

2.1 任务代码临界区处理

为了防止当前任务的执行被其它高优先级的任务打断而提供的锁机制就是任务锁。

任务锁的开启和关闭的函数有如下宏定义:

#define portENTER_CRITICAL()	vPortEnterCritical()
#define portEXIT_CRITICAL()		vPortExitCritical()

跟踪宏定义有:

void vPortEnterCritical( void )
{
	/* 设置寄存器BASEPRI */
	portDISABLE_INTERRUPTS();

	/* 临界区嵌套加1 */
	uxCriticalNesting++;

	if( uxCriticalNesting == 1 )
	{
		configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
	}
}

void vPortExitCritical( void )
{
	configASSERT( uxCriticalNesting );
	
	/* 临界区嵌套减1 */
	uxCriticalNesting--;
	
	if( uxCriticalNesting == 0 )
	{
		/* 设置寄存器BASEPRI */
		portENABLE_INTERRUPTS();
	}
}

变量 uxCriticalNesting用于临界段的嵌套计数,假如开关中断临界区中嵌套了一个含有开关中断的临界区代码,当嵌套的临界区退出时中断就打开了,使用变量uxCriticalNesting可以解决这个问题。

最终调用函数portDISABLE_INTERRUPTS()和portENABLE_INTERRUPTS(),有:

#define portDISABLE_INTERRUPTS()	vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS()		vPortSetBASEPRI( 0 )

即,最终操作寄存器BASEPRI,如下:

static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
	/* 系统可管理的最高中断优先级,优先级高于ulNewBASEPRI不受FreeRTOS管理 */
	uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}
}

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
	__asm
	{
		/* ulBASEPRI为0,即取消BASEPRI屏蔽 */
		msr basepri, ulBASEPRI
	}
}
2.2 中断服务程序临界区处理

中断锁开启和关闭的函数有如下宏定义:

#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

其中:

#define portSET_INTERRUPT_MASK_FROM_ISR()		ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)	vPortSetBASEPRI(x)

有:

static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
	/* 系统可管理的最高中断优先级,优先级高于ulNewBASEPRI不受FreeRTOS管理 */
	uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		/* 读出basepri中的值 */
		mrs ulReturn, basepri
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}
	/* 返回basepri中的值,退出临界区保护时会使用此值 */
	return ulReturn;
}

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
	__asm
	{
		msr basepri, ulBASEPRI
	}
}

说明:

  • 通过保存和恢复寄存器 basepri 的数值就可以实现嵌套使用。
  • 当程序进入中断回调时,中断不能进行阻塞,FROM_ISR结尾的类似函数都应该在中断中调用。
2.3 挂起调度器来创建临界区

通过挂起调度器来创建临界区,称其为调度锁。处于调度锁开和调度锁关之间的代码在执行期间是不会被高优先级的任务抢占的,即任务调度被禁止,但调度锁只是禁止了任务调度,并没有关闭任何中断,中断还是正常执行的。而对于上面两种情况是进行了开关中断操作。

调度锁的开启和关闭操作函数:(详细分析参考FreeRTOS原理剖析:任务挂起和恢复)

vTaskSuspendAll()	/* 用于实现 FreeRTOS 调度锁开启 */
xTaskResumeAll() 	/* 调度锁关闭 */

参考资料:

【1】: 正点原子:《STM32F407 FreeRTOS开发手册V1.1》
【2】: 野火:《FreeRTOS 内核实现与应用开发实战指南》
【3】: 《Cortex M3权威指南(中文)》

你可能感兴趣的:(FreeRTOS原理剖析)