【FreeRTOS學習06】深度解剖中斷與任務之間同步的具體使用場景

嵌入式系統中中斷是必不可少的一部分;

【FreeRTOS實戰彙總】小白博主的RTOS學習實戰快速進階之路(持續更新)

1 前言

本文會在中斷基礎上對FreeRTOS的中斷管理做一個介紹,讀者需要掌握中斷的概念,本文暫不會對此進行深入介紹;再操作系統中,中斷隨處可見,從Windows的中斷,Linux的中斷,以及RTOS的中斷,其處理過程都是相同的;無論是軟件中斷還是硬件中斷,在接收到中斷源發出的中斷請求之後,就會觸發中斷,CPU尋找中斷向量表,然後跳轉到中斷服務函數,具體如下所示;
在這裏插入圖片描述

什麼是中斷源,通常就PC來說,中斷源可以是以下幾種:

  • I/O設備:鼠標,鍵盤等等;
  • 定時器中斷;
  • 內部故障產生的中斷;
  • CPU主動中斷,比如調試程序,單步執行的情況;

那麼,如何在中斷服務函數和RTOS的任務之間,安全地完成數據的交互呢?下面會進一步介紹。

2 中斷特點

中斷髮生的時候,會打斷正常執行的函數,這時候就會進行現場保護,即將當前各個寄存器的值壓到入棧,執行玩中斷之後恢復現場,即出棧,重新恢復各個寄存器的值,系統還原到中斷之前的狀態;具體如下圖所示;
在這裏插入圖片描述
FreeRTOS中,中斷需要注意幾點:

  • 事件的檢測,除了中斷方式,還可以通過輪詢方式,需要更加具體的情況進行選擇;
  • 何時使用中斷;中斷服務函數(ISR)要處理的數據量有多大,通常我們希望中斷的切換越快越好,也就是說,ISR儘量採用耗時較少的處理方式;
  • 事件如何通知到任務(和中斷服務函數區別開,非ISR函數),如何設計程序的架構可以完成良好的異步處理過程;
  • 事件通知的時候需要注意使用FreeRTOS提供的中斷安全API,通常這些函數接口的後綴爲xxxxxISR(),例如xQueueSendFrom

3 延遲中斷處理

上面提到過,中斷服務函數應該儘量斷,因此這裏才用中斷的前部中斷的後部來處理;

  • 前部:負責處理不耗時的操作,比如任務的同步,發送信號量去通知任務;
  • 後部:負責處理耗時的操作,這時候,中斷已經恢復現場,實際執行可以視爲軟中斷,即在一個Task任務中執行;

上述的方式也可以稱之爲延遲中斷處理,具體的思路是:對應的中斷創建一個相應的handler task,使用二值信號量去同步,在某個特殊的中斷髮生時,發送信號量,讓任務解除阻塞,相當於讓任務與中斷同步。這樣就可以讓中斷事件處理量大的工作在同步任務中完成,中斷服務例程(ISR)中只是快速處理少部份工作,幾點羅列一下;

  1. 中斷處理可以說是被推遲(deferred)到一個處理(handler)任務中;
  2. 如果某個中斷處理要求特別緊急,其延遲處理任務的優先級可以設爲最高,以保證延遲處理任務隨時都搶佔系統中的其它任務。
  3. 延遲處理任務就成爲其對應的 ISR退出後第一個執行的任務,在時間上緊接着 ISR 執行,相當於所有的處理都在 ISR 中完成一樣。1

具體如下所示;
在這裏插入圖片描述

3.1 信號量的使用

關於信號量(Semaphore):通俗的解釋,信號量是一個數,二值信號量,互斥信號量,只能表示01,假設一個信號量X,兩個任務A,B;

  • 任務A獲取了信號量,則該信號量X被設爲0B任務都處於堵塞狀態,等待A任務釋放信號量;
  • A任務釋放了信號量,則該信號量X被設爲1B任務獲取了信號量,則進入運行狀態; 2
    FreeRTOS中對於信號量操作給出了以下相應的API
  • vSemaphoreCreateBinary:創建二值信號量;
  • xSemaphoreTake:獲取信號量;
  • xSemaphoreGiveFromISR:發送信號量;
    這是一組宏定義,具體的實現在頭文件semphr.h中,下面進一步介紹;

通常信號量的同步操作如下圖所示;

在這裏插入圖片描述

3.2 vSemaphoreCreateBinary

FreeRTOS 中各種信號量的句柄都存儲在 xSemaphoreHandle類型的變量中,在使用信號量之前,必須先通過vSemaphoreCreateBinary創建信號量,其具體函數原型如下;

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	#define vSemaphoreCreateBinary( xSemaphore )																							\
		{																																	\
			( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE );	\
			if( ( xSemaphore ) != NULL )																									\
			{																																\
				( void ) xSemaphoreGive( ( xSemaphore ) );																					\
			}																																\
		}
#endif

可以看到這個接口是通過調用xQueueGenericCreate函數創建了大小爲1semSEMAPHORE_QUEUE_ITEM_LENGTH的信號量,並且創建成功之後,通過xSemaphoreGive將信號量設置爲1,此時信號量有效;

3.3 xSemaphoreTake

“帶走(Taking)”一個信號量意爲”獲取(Obtain)”或”接收(Receive)”信號量。只有當信號量有效的時候纔可以被獲取。在經典信號量術中,xSemaphoreTake()等同於一次P()操作。函數原型如下所示;

#define xSemaphoreTake( xSemaphore, xBlockTime )		\
		xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )

3.4 xSemaphoreGiveFromISR

xSemaphoreGiveFromISR()xSemaphoreGive()的特殊形式, 專門用於中斷服務函數中,其函數原型如下;

#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )	\
		xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )

4 計數信號量

上面講的二值信號量最多隻能鎖存一個事件,通俗的講只能進行一對一的觸發,也就是說在中斷頻率相對較低的情況下,使用二值信號量是比較完美的,但是在中斷頻率較高的情況下,會出現這種情況:

  • 產生了中斷,任務A獲取信號量,並開始運行;
  • 新的中斷產生,任務A還沒有運行結束(任務A佔有了信號量),此時信號量無效,因此這個中斷信號沒有處理就丟失了;
    這裏通過使用計數信號量可以解決這種情況,通過使用xSemaphoreCreateCounting函數,函數原型如下所示;
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) \
	xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
#endif

創建一個計數信號量;

xCountingSemaphore = xSemaphoreCreateCounting( 10, 0 );

具體中斷和任務通過計數信號量的同步過程可以參考下圖;

在這裏插入圖片描述

5 總結

本文對FreeRTOS中如何對中斷服務函數和任務進行同步做了簡單介紹,分析了通過二值信號量和計數信號量這兩種場景的應用和注意事項,另外作者能力有限,難免存在錯誤和紕漏,請不吝賜教。


  1. Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide ↩︎

  2. FREERTOS 實時內核實用指南,Zou Changjun ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章