ID:技術讓夢想更偉大
作者:李肖遙
1. 任務和協程(Co-routines)
應用程式可以使用任務也可以使用協程,或者兩者混合使用,但是任務和協程使用不同的API函數,是以在任務和協程之間不能使用同一個隊列或信号量傳遞資料。
通常情況下,協程僅用在資源非常少的微處理器中,特别是RAM非常稀缺的情況下。
目前協程很少被使用到,是以對于協程FreeRTOS作者既沒有把它删除也沒有進一步開發。
是以本系列文章以後不會對協程過多描述,包括其API函數。
1.1任務的特性
「簡而言之:」
使用RTOS的實時應用程式可認為是一系列獨立任務的集合。每個任務在自己的環境中運作,不依賴于系統中的其它任務或者RTOS排程器。
在任何時刻,隻有一個任務得到運作,RTOS排程器決定運作哪個任務。排程器會不斷的啟動、停止每一個任務,宏觀看上去就像整個應用程式都在執行。
作為任務,不需要對排程器的活動有所了解,在任務切入切出時儲存上下文環境(寄存器值、堆棧内容)是排程器主要的職責。
為了實作這點,每個任務都需要有自己的堆棧。當任務切出時,它的執行環境會被儲存在該任務的堆棧中,這樣當再次運作時,就能從堆棧中正确的恢複上次的運作環境。
1.2任務概要
- 簡單
- 沒有使用限制
- 支援完全搶占
- 支援優先級
- 每個任務都有自己的堆棧,消耗RAM較多
- 如果使用搶占,必須小心的考慮可重入問題
2. 任務狀态
「一個任務可為下面中的一個:」
- 「運作」:如果一個任務正在執行,那麼說這個任務處于運作狀态。此時它占用處理器。
- 「就緒」:就緒的任務已經具備執行的能力(不同于阻塞和挂起),但是因為有一個同優先級或者更高優先級的任務處于運作狀态而還沒有真正執行。
- 「阻塞」:如果任務目前正在等待某個時序或外部中斷,我們就說這個任務處于阻塞狀态。比如一個任務調用
後會阻塞到延時周期到為止。任務也可能阻塞在隊列或信号量的事件上。進入阻塞狀态的任務通常有一個“逾時”周期,當事件逾時後解除阻塞。vTaskDelay()
- 「挂起」:處于挂起狀态的任務同樣對排程器無效。僅當明确的分别調用
和vTaskSuspend()
API函數後,任務才會進入或退出挂起狀态。不可以指定逾時周期事件(不可以通過設定逾時事件而退出挂起狀态)xTaskResume()
3.任務優先級
每個任務都要被指定一個優先級,從
0~configMAX_PRIORITIES
,
configMAX_PRIORITIES
定義在
FreeRTOSConfig.h
中。
如果某架構硬體支援CLZ(或類似)指令(計算前導零的數目,Cortex-M3是支援該指令的,從ARMv6T2才支援這個指令),并且打算在移植層使用這個特性來優化任務排程機制,需要有一些步驟。
首先将FreeRTOSConfig.h中
configUSE_PORT_OPTIMISED_TASK_SELECTION
設定為1,并且最大優先級數目
configMAX_PRIORITIES
不能大于32。
除此之外,
configMAX_PRIORITIES
可以設定為任意值,但是考慮到
configMAX_PRIORITIES
設定越大,RAM消耗也越大,一般設定為滿足使用的最小值。
低優先級數值代表低優先級。空閑任務(idle task)的優先級為0(tskIDLE_PRIORITY)。
FreeRTOS排程器確定處于最高優先級的就緒或運作态任務擷取處理器,換句話說,處于運作狀态的任務,隻有其中的最高優先級任務才會運作。
任何數量的任務可以共享同一個優先級。如果宏
configUSE_TIME_SLICING
未定義或者宏
configUSE_TIME_SLICING
定義為1,處于就緒态的多個相同優先級任務将會以時間片切換的方式共享處理器。
4.實作一個任務
「一個任務具有以下結構:」
void vATaskFunction( void *pvParameters )
{
for( ;; )
{
/*-- 應用程式代碼放在這裡. --*/
}
/* 任務不可以從這個函數傳回或退出。在較新的FreeRTOS移植包中,如果
試圖從一個任務中傳回,将會調用configASSERT()(如果定義的話)。
如果一個任務确實要退出函數,那麼這個任務應調用vTaskDelete(NULL)
函數,以便處理一些清理工作。*/
vTaskDelete( NULL );
}
複制
任務函數傳回為void,參數隻有一個void類型指針。所有的任務函數都應該是這樣。void類型指針可以向任務傳遞任意類型資訊。
任務函數決不應該傳回,是以通常任務函數都是一個死循環。
任務由
xTaskCreate()
函數建立,由
vTaskDelete()
函數删除。
5.空閑任務和空閑任務鈎子(idle task和Idle Task hook)
5.1空閑任務
空閑任務是啟動RTOS排程器時由核心自動建立的任務,這樣可以確定至少有一個任務在運作。
空閑任務具有最低任務優先級,這樣如果有其它更高優先級的任務進入就緒态就可以立刻讓出CPU。
删除任務後,空閑任務用來釋放RTOS配置設定給被删除任務的記憶體。是以,在應用中使用
vTaskDelete()
函數後確定空閑任務能獲得處理器時間就很重要了。
除此之外,空閑任務沒有其它有效功能,是以可以被合理的剝奪處理器時間,并且它的優先級也是最低的。
應用程式任務共享空閑任務優先級(
tskIDLE_PRIORITY
)也是可能的。這種情況如何配置可以參考
configIDLE_SHOULE_YIELD
配置參數類擷取更多資訊。
5.2空閑任務鈎子
空閑任務鈎子是一個函數,每一個空閑任務周期被調用一次。如果你想将任務程式功能運作在空閑優先級上,可以有兩種選擇:
- 在一個空閑任務鈎子中實作這個功能:因為FreeRTOS必須至少有一個任務處于就緒或運作狀态,是以鈎子函數不可以調用可能引起空閑任務阻塞的API函數(比如vTaskDelay()或者帶有逾時事件的隊列或信号量函數)
- 建立一個具有空閑優先級的任務去實作這個功能:這是個更靈活的解決方案,但是會帶來更多RAM開銷。
「建立一個空閑鈎子步驟如下」:
- 在
頭檔案中設定FreeRTOSConfig.h
為1;configUSE_IDLE_HOOK
- 定義一個函數,名字和參數原型如下所示:
void vApplicationIdleHook( void );
複制
通常,使用這個空閑鈎子函數設定CPU進入低功耗模式。
6.任務建立
任務建立和删除API函數位于檔案task.c中,需要包含task.h頭檔案。
6.1 函數描述
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode,
const char * const pcName,
unsigned short usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * pvCreatedTask
);
複制
建立新的任務并加入任務就緒清單。
如果使用FreeRTOS-MPU(在官方下載下傳包中,為Cortex-M3核心寫了兩個移植方案,一個是普通的FreeRTOS移植層,還有一個是FreeRTOS-MPU移植層。後者包含完整的記憶體保護),那麼推薦使用函數
xTaskCreateRestricted()
來代替
xTaskCreate()
。
在使用FreeRTOS-MPU的情況下,使用
xTaskCreate()
函數可以建立運作在特權模式或使用者模式(見下面對函數參數uxPriority的描述)的任務。
當運作在特權模式下,任務可以通路整個記憶體映射;當處于使用者模式下,任務僅能通路自己的堆棧。
無論在何種模式下,MPU都不會自動捕獲堆棧溢出,是以标準的FreeRTOS堆棧溢出檢測機制仍然會被用到。
xTaskCreateRestricted()
函數具有更大的靈活性。
6.2參數描述
- 「pvTaskCode」:指針,指向任務函數的入口。任務永遠不會傳回(位于死循環内)。該參數類型
定義在檔案projdefs.h中,定義為:TaskFunction_t
。typedefvoid (*TaskFunction_t)( void * )
- 「pcName」:任務描述。主要用于調試。字元串的最大長度由宏
指定,該宏位于FreeRTOSConfig.h檔案中。configMAX_TASK_NAME_LEN
- 「usStackDepth」:指定任務堆棧大小,能夠支援的堆棧變量數量,而不是位元組數。比如,在16位寬度的堆棧下,
定義為100,則實際使用200位元組堆棧存儲空間。堆棧的寬度乘以深度必須不超過size_t類型所能表示的最大值。比如,size_t為16位,則可以表示的最大值是65535。usStackDepth
- 「pvParameters」:指針,當任務建立時,作為一個參數傳遞給任務。
- uxPriority:任務的優先級。具有MPU支援的系統,可以通過置位優先級參數的
位,随意的在特權(系統)模式下建立任務。比如,建立一個優先級為2的特權任務,參數portPRIVILEGE_BIT
可以設定為( 2 | portPRIVILEGE_BIT )。uxPriority
- 「pvCreatedTask」:用于回傳一個句柄(ID),建立任務後可以使用這個句柄引用任務。
6.3傳回值
如果任務成功建立并加入就緒清單函數傳回
pdPASS
,否則函數傳回錯誤碼,具體參見
projdefs.h
。
6.4用法舉例
/* 建立任務. */
void vTaskCode( void * pvParameters )
{
for( ;; )
{
/* 任務代碼放在這裡 */
}
}
/* 建立任務函數 */
void vOtherFunction( void )
{
static unsigned char ucParameterToPass;
xTaskHandlexHandle;
/* 建立任務,存儲句柄。注:傳遞的參數ucParameterToPass必須和任務具有相同的生存周期,
是以這裡定義為靜态變量。如果它隻是一個自動變量,可能不會有太長的生存周期,因為
中斷和高優先級任務可能會用到它。 */
xTaskCreate( vTaskCode, "NAME", STACK_SIZE,&ucParameterToPass, tskIDLE_PRIORITY, &xHandle );
/* 使用句柄删除任務. */
if( xHandle !=NULL )
{
vTaskDelete( xHandle );
}
}
複制
7.任務删除
7.1 任務描述
voidvTaskDelete( TaskHandle_t xTask );
複制
從RTOS核心管理器中删除一個任務。任務删除後将會從就緒、阻塞、暫停和事件清單中移除。在檔案
FreeRTOSConfig.h
中,必須定義宏
INCLUDE_vTaskDelete
為1,本函數才有效。
「注意:」
被删除的任務,其在任務建立時由核心配置設定的存儲空間,會由空閑任務釋放。
如果有應用程式調用
xTaskDelete()
,必須保證空閑任務擷取一定的微控制器處理時間。
任務代碼自己配置設定的記憶體是不會自動釋放的,是以删除任務前,應該将這些記憶體釋放。
7.2參數描述
- 「xTask」:被删除任務的句柄。為
表示删除目前任務。NULL