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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章