FreeRTOS 笔记之⑤:临界区及应用

目录

1. 临界区概述

2. Cortex-M内核快速关中断指令

3. 关中断

4. 开中断

5. 进入/退出临界段的宏

6. 临界段代码的应用


1. 临界区概述

临界区指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个线程访问的特性。当有线程进入临界区段时,其他线程或是进程必须等待(例如:bounded waiting 等待法),有一些同步的机制必须在临界区段的进入点与离开点实现,以确保这些共用资源是被互斥获得使用,例如:semaphore。只能被单一线程访问的设备,例如:打印机

 

进程进入临界区的调度原则是:

1、如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入。

2、任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待。

3、进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。

4、如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象。

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

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

2. Cortex-M内核快速关中断指令

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

PRIMASK和FAULTMAST是Cortex-M内核 里面三个中断屏蔽寄存器中的两个,还有一个是BASEPRI,有关这三个寄存器的详细用法见表格

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

3. 关中断

FreeRTOS关中断的函数在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
		dsb
		isb
	}
}
  • 不带返回值的关中断函数,不能嵌套,不能在中断里面使用。不带返回值的意思是:在往BASEPRI写入新的值的时候,不用先将BASEPRI的值保存起来,即不用管当前的中断状态是怎么样的,既然不用管当前的中断状态,也就意味着这样的函数不能在中断里面调用

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

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

/* 带返回值的关中断函数,可以嵌套,可以在中断里面使用 */
#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
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}

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

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

4. 开中断

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

/* 不带中断保护的开中断函数 */
#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()成对使用。

5. 进入/退出临界段的宏

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

进入和退出临界段的宏分中断保护版本和非中断版本,但最终都是通过开/关中断来实现

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

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 );
	}
}

void vPortExitCritical( void )
{
	//configASSERT( uxCriticalNesting );
	uxCriticalNesting--;
    
	if( uxCriticalNesting == 0 )
	{
		portENABLE_INTERRUPTS();
	}
}

6. 临界段代码的应用

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

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

    /* 临界段代码 */

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

 

发布了86 篇原创文章 · 获赞 12 · 访问量 1万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章