文章目錄
- 系列教程總目錄
- 概述
- 3.1 基本概念
-
3.2 任務(wù)創(chuàng)建與刪除
- 3.2.1 什么是任務(wù)
- 3.2.2 創(chuàng)建任務(wù)
- 3.2.3 示例1: 創(chuàng)建任務(wù)
- 3.2.4 示例2: 使用任務(wù)參數(shù)
- 3.2.5 任務(wù)的刪除
- 3.2.6 示例3: 刪除任務(wù)
-
3.3 任務(wù)優(yōu)先級和Tick
- 3.3.1 任務(wù)優(yōu)先級
- 3.3.2 Tick
- 3.3.3 示例4: 優(yōu)先級實(shí)驗(yàn)
- 3.3.4 示例5: 修改優(yōu)先級
-
3.4 任務(wù)狀態(tài)
- 3.4.1 阻塞狀態(tài)(Blocked)
- 3.4.2 暫停狀態(tài)(Suspended)
- 3.4.3 就緒狀態(tài)(Ready)
- 3.4.4 完整的狀態(tài)轉(zhuǎn)換圖
-
3.5 Delay函數(shù)
- 3.5.1 兩個(gè)Delay函數(shù)
- 3.5.2 示例6: Delay
-
3.6 空閑任務(wù)及其鉤子函數(shù)
- 3.6.1 介紹
- 3.6.2 使用鉤子函數(shù)的前提
-
3.7 調(diào)度算法
- 3.7.1 重要概念
- 3.7.2 配置調(diào)度算法
- 3.7.3 示例7: 調(diào)度
- 3.7.4 對比效果: 搶占與否
- 3.7.5 對比效果: 時(shí)間片輪轉(zhuǎn)與否
- 3.7.6 對比效果: 空閑任務(wù)讓步
?
需要獲取更好閱讀體驗(yàn)的同學(xué),請?jiān)L問我專門設(shè)立的站點(diǎn)查看,地址:http://rtos.100ask.net/
系列教程總目錄
本教程連載中,篇章會(huì)比較多,為方便同學(xué)們閱讀,點(diǎn)擊這里可以查看文章的 目錄列表,目錄列表頁面地址:https://blog.csdn.net/thisway_diy/article/details/121399484
概述
在本章中,會(huì)涉及如下內(nèi)容:
- FreeRTOS如何給每個(gè)任務(wù)分配CPU時(shí)間
- 如何選擇某個(gè)任務(wù)來運(yùn)行
- 任務(wù)優(yōu)先級如何起作用
- 任務(wù)有哪些狀態(tài)
- 如何實(shí)現(xiàn)任務(wù)
- 如何使用任務(wù)參數(shù)
- 怎么修改任務(wù)優(yōu)先級
- 怎么刪除任務(wù)
- 怎么實(shí)現(xiàn)周期性的任務(wù)
- 如何使用空閑任務(wù)
- ?
3.1 基本概念
對于整個(gè)單片機(jī)程序,我們稱之為application,應(yīng)用程序。
使用FreeRTOS時(shí),我們可以在application中創(chuàng)建多個(gè)任務(wù)(task),有些文檔把任務(wù)也稱為線程(thread)。
?以日常生活為例,比如這個(gè)母親要同時(shí)做兩件事:
- 喂飯:這是一個(gè)任務(wù)
- 回信息:這是另一個(gè)任務(wù)
這可以引入很多概念:
-
任務(wù)狀態(tài)(State):
- 當(dāng)前正在喂飯,它是running狀態(tài);另一個(gè)"回信息"的任務(wù)就是"not running"狀態(tài)
-
"not running"狀態(tài)還可以細(xì)分:
- ready:就緒,隨時(shí)可以運(yùn)行
- blocked:阻塞,卡住了,母親在等待同事回信息
- suspended:掛起,同事廢話太多,不管他了
-
優(yōu)先級(Priority)
- 我工作生活兼顧:喂飯、回信息優(yōu)先級一樣,輪流做
- 我忙里偷閑:還有空閑任務(wù),休息一下
- 廚房著火了,什么都別說了,先滅火:優(yōu)先級更高
-
棧(Stack)
- 喂小孩時(shí),我要記得上一口喂了米飯,這口要喂青菜了
- 回信息時(shí),我要記得剛才聊的是啥
- 做不同的任務(wù),這些細(xì)節(jié)不一樣
- 對于人來說,當(dāng)然是記在腦子里
- 對于程序,是記在棧里
- 每個(gè)任務(wù)有自己的棧
-
事件驅(qū)動(dòng)
- 孩子吃飯?zhí)合刃菹⒁粫?huì),等他咽下去了、等他提醒我了,再喂下一口
-
協(xié)助式調(diào)度(Co-operative Scheduling)
-
你在給同事回信息
- 同事說:好了,你先去給小孩喂一口飯吧,你才能離開
- 同事不放你走,即使孩子哭了你也不能走
-
你好不容易可以給孩子喂飯了
- 孩子說:好了,媽媽你去處理一下工作吧,你才能離開
- 孩子不放你走,即使同事連發(fā)信息你也不能走
-
你在給同事回信息
這涉及很多概念,后續(xù)章節(jié)詳細(xì)分析。
3.2 任務(wù)創(chuàng)建與刪除
3.2.1 什么是任務(wù)
在FreeRTOS中,任務(wù)就是一個(gè)函數(shù),原型如下:
void ATaskFunction( void *pvParameters );
要注意的是:
- 這個(gè)函數(shù)不能返回
- 同一個(gè)函數(shù),可以用來創(chuàng)建多個(gè)任務(wù);換句話說,多個(gè)任務(wù)可以運(yùn)行同一個(gè)函數(shù)
-
函數(shù)內(nèi)部,盡量使用局部變量:
- 每個(gè)任務(wù)都有自己的棧
-
每個(gè)任務(wù)運(yùn)行這個(gè)函數(shù)時(shí)
- 任務(wù)A的局部變量放在任務(wù)A的棧里、任務(wù)B的局部變量放在任務(wù)B的棧里
- 不同任務(wù)的局部變量,有自己的副本
-
函數(shù)使用全局變量、靜態(tài)變量的話
- 只有一個(gè)副本:多個(gè)任務(wù)使用的是同一個(gè)副本
- 要防止沖突(后續(xù)會(huì)講)
下面是一個(gè)示例:
void ATaskFunction( void *pvParameters )
{
/* 對于不同的任務(wù),局部變量放在任務(wù)的棧里,有各自的副本 */
int32_t lVariableExample = 0;
/* 任務(wù)函數(shù)通常實(shí)現(xiàn)為一個(gè)無限循環(huán) */
for( ;; )
{
/* 任務(wù)的代碼 */
}
/* 如果程序從循環(huán)中退出,一定要使用vTaskDelete刪除自己
* NULL表示刪除的是自己
*/
vTaskDelete( NULL );
/* 程序不會(huì)執(zhí)行到這里, 如果執(zhí)行到這里就出錯(cuò)了 */
}
3.2.2 創(chuàng)建任務(wù)
創(chuàng)建任務(wù)時(shí)使用的函數(shù)如下:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函數(shù)指針, 任務(wù)函數(shù)
const char * const pcName, // 任務(wù)的名字
const configSTACK_DEPTH_TYPE usStackDepth, // 棧大小,單位為word,10表示40字節(jié)
void * const pvParameters, // 調(diào)用任務(wù)函數(shù)時(shí)傳入的參數(shù)
UBaseType_t uxPriority, // 優(yōu)先級
TaskHandle_t * const pxCreatedTask ); // 任務(wù)句柄, 以后使用它來操作這個(gè)任務(wù)
參數(shù)說明:
參數(shù) | 描述 |
---|---|
pvTaskCode |
函數(shù)指針,可以簡單地認(rèn)為任務(wù)就是一個(gè)C函數(shù)。 它稍微特殊一點(diǎn):永遠(yuǎn)不退出,或者退出時(shí)要調(diào)用"vTaskDelete(NULL)" |
pcName |
任務(wù)的名字,F(xiàn)reeRTOS內(nèi)部不使用它,僅僅起調(diào)試作用。 長度為:configMAX_TASK_NAME_LEN |
usStackDepth |
每個(gè)任務(wù)都有自己的棧,這里指定棧大小。 單位是word,比如傳入100,表示棧大小為100 word,也就是400字節(jié)。 最大值為uint16_t的最大值。 怎么確定棧的大小,并不容易,很多時(shí)候是估計(jì)。 精確的辦法是看反匯編碼。 |
pvParameters | 調(diào)用pvTaskCode函數(shù)指針時(shí)用到:pvTaskCode(pvParameters) |
uxPriority |
優(yōu)先級范圍:0~(configMAX_PRIORITIES – 1) 數(shù)值越小優(yōu)先級越低, 如果傳入過大的值,xTaskCreate會(huì)把它調(diào)整為(configMAX_PRIORITIES – 1) |
pxCreatedTask |
用來保存xTaskCreate的輸出結(jié)果:task handle。 以后如果想操作這個(gè)任務(wù),比如修改它的優(yōu)先級,就需要這個(gè)handle。 如果不想使用該handle,可以傳入NULL。 |
返回值 |
成功:pdPASS; 失敗:errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(失敗原因只有內(nèi)存不足) 注意:文檔里都說失敗時(shí)返回值是pdFAIL,這不對。 pdFAIL是0,errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY是-1。 |
3.2.3 示例1: 創(chuàng)建任務(wù)
代碼為:FreeRTOS_01_create_task
使用2個(gè)函數(shù)分別創(chuàng)建2個(gè)任務(wù)。
任務(wù)1的代碼:
void vTask1( void *pvParameters )
{
const char *pcTaskName = "T1 run\r\n";
volatile uint32_t ul; /* volatile用來避免被優(yōu)化掉 */
/* 任務(wù)函數(shù)的主體一般都是無限循環(huán) */
for( ;; )
{
/* 打印任務(wù)1的信息 */
printf( pcTaskName );
/* 延遲一會(huì)(比較簡單粗暴) */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
}
}
}
任務(wù)2的代碼:
void vTask2( void *pvParameters )
{
const char *pcTaskName = "T2 run\r\n";
volatile uint32_t ul; /* volatile用來避免被優(yōu)化掉 */
/* 任務(wù)函數(shù)的主體一般都是無限循環(huán) */
for( ;; )
{
/* 打印任務(wù)1的信息 */
printf( pcTaskName );
/* 延遲一會(huì)(比較簡單粗暴) */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
}
}
}
main函數(shù):
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
/* 啟動(dòng)調(diào)度器 */
vTaskStartScheduler();
/* 如果程序運(yùn)行到了這里就表示出錯(cuò)了, 一般是內(nèi)存不足 */
return 0;
}
運(yùn)行結(jié)果如下:
?注意:
- task 2先運(yùn)行!
- 要分析xTaskCreate的代碼才能知道原因:更高優(yōu)先級的、或者后面創(chuàng)建的任務(wù)先運(yùn)行。
任務(wù)運(yùn)行圖:
- 在t1:Task2進(jìn)入運(yùn)行態(tài),一直運(yùn)行直到t2
- 在t2:Task1進(jìn)入運(yùn)行態(tài),一直運(yùn)行直到t3;在t3,Task2重新進(jìn)入運(yùn)行態(tài)
3.2.4 示例2: 使用任務(wù)參數(shù)
代碼為:FreeRTOS_02_create_task_use_params
我們說過,多個(gè)任務(wù)可以使用同一個(gè)函數(shù),怎么體現(xiàn)它們的差別?
- 棧不同
- 創(chuàng)建任務(wù)時(shí)可以傳入不同的參數(shù)
我們創(chuàng)建2個(gè)任務(wù),使用同一個(gè)函數(shù),代碼如下:
void vTaskFunction( void *pvParameters )
{
const char *pcTaskText = pvParameters;
volatile uint32_t ul; /* volatile用來避免被優(yōu)化掉 */
/* 任務(wù)函數(shù)的主體一般都是無限循環(huán) */
for( ;; )
{
/* 打印任務(wù)的信息 */
printf(pcTaskText);
/* 延遲一會(huì)(比較簡單粗暴) */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
}
}
}
上述代碼中的pcTaskText
來自參數(shù)pvParameters
,pvParameters
來自哪里?創(chuàng)建任務(wù)時(shí)傳入的。
代碼如下:
- 使用xTaskCreate創(chuàng)建2個(gè)任務(wù)時(shí),第4個(gè)參數(shù)就是pvParameters
- 不同的任務(wù),pvParameters不一樣
static const char *pcTextForTask1 = "T1 run\r\n";
static const char *pcTextForTask2 = "T2 run\r\n";
int main( void )
{
prvSetupHardware();
xTaskCreate(vTaskFunction, "Task 1", 1000, (void *)pcTextForTask1, 1, NULL);
xTaskCreate(vTaskFunction, "Task 2", 1000, (void *)pcTextForTask2, 1, NULL);
/* 啟動(dòng)調(diào)度器 */
vTaskStartScheduler();
/* 如果程序運(yùn)行到了這里就表示出錯(cuò)了, 一般是內(nèi)存不足 */
return 0;
}
3.2.5 任務(wù)的刪除
刪除任務(wù)時(shí)使用的函數(shù)如下:
void vTaskDelete( TaskHandle_t xTaskToDelete );
參數(shù)說明:
參數(shù) | 描述 |
---|---|
pvTaskCode |
任務(wù)句柄,使用xTaskCreate創(chuàng)建任務(wù)時(shí)可以得到一個(gè)句柄。 也可傳入NULL,這表示刪除自己。 |
怎么刪除任務(wù)?舉個(gè)不好的例子:
-
自殺:
vTaskDelete(NULL)
-
被殺:別的任務(wù)執(zhí)行
vTaskDelete(pvTaskCode)
,pvTaskCode是自己的句柄 -
殺人:執(zhí)行
vTaskDelete(pvTaskCode)
,pvTaskCode是別的任務(wù)的句柄
3.2.6 示例3: 刪除任務(wù)
代碼為:FreeRTOS_03_delete_task
本節(jié)代碼會(huì)涉及優(yōu)先級的知識,可以只看vTaskDelete的用法,忽略優(yōu)先級的講解。
我們要做這些事情:
- 創(chuàng)建任務(wù)1:任務(wù)1的大循環(huán)里,創(chuàng)建任務(wù)2,然后休眠一段時(shí)間
- 任務(wù)2:打印一句話,然后就刪除自己
任務(wù)1的代碼如下:
void vTask1( void *pvParameters )
{
const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL );
BaseType_t ret;
/* 任務(wù)函數(shù)的主體一般都是無限循環(huán) */
for( ;; )
{
/* 打印任務(wù)的信息 */
printf("Task1 is running\r\n");
ret = xTaskCreate( vTask2, "Task 2", 1000, NULL, 2, &xTask2Handle );
if (ret != pdPASS)
printf("Create Task2 Failed\r\n");
// 如果不休眠的話, Idle任務(wù)無法得到執(zhí)行
// Idel任務(wù)會(huì)清理任務(wù)2使用的內(nèi)存
// 如果不休眠則Idle任務(wù)無法執(zhí)行, 最后內(nèi)存耗盡
vTaskDelay( xDelay100ms );
}
任務(wù)2的代碼如下:
void vTask2( void *pvParameters )
{
/* 打印任務(wù)的信息 */
printf("Task2 is running and about to delete itself\r\n");
// 可以直接傳入?yún)?shù)NULL, 這里只是為了演示函數(shù)用法
vTaskDelete(xTask2Handle);
}
main函數(shù)代碼如下:
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
/* 啟動(dòng)調(diào)度器 */
vTaskStartScheduler();
/* 如果程序運(yùn)行到了這里就表示出錯(cuò)了, 一般是內(nèi)存不足 */
return 0;
}
運(yùn)行結(jié)果如下:
任務(wù)運(yùn)行圖:
- main函數(shù)中創(chuàng)建任務(wù)1,優(yōu)先級為1。任務(wù)1運(yùn)行時(shí),它創(chuàng)建任務(wù)2,任務(wù)2的優(yōu)先級是2。
- 任務(wù)2的優(yōu)先級最高,它馬上執(zhí)行。
- 任務(wù)2打印一句話后,就刪除了自己。
-
任務(wù)2被刪除后,任務(wù)1的優(yōu)先級最高,輪到任務(wù)1繼續(xù)運(yùn)行,它調(diào)用
vTaskDelay()
進(jìn)入Block狀態(tài) - 任務(wù)1 Block期間,輪到Idle任務(wù)執(zhí)行:它釋放任務(wù)2的內(nèi)存(TCB、棧)
- 時(shí)間到后,任務(wù)1變?yōu)樽罡邇?yōu)先級的任務(wù)繼續(xù)執(zhí)行。
- 如此循環(huán)。
在任務(wù)1的函數(shù)中,如果不調(diào)用vTaskDelay,則Idle任務(wù)用于沒有機(jī)會(huì)執(zhí)行,它就無法釋放創(chuàng)建任務(wù)2是分配的內(nèi)存。
而任務(wù)1在不斷地創(chuàng)建任務(wù),不斷地消耗內(nèi)存,最終內(nèi)存耗盡再也無法創(chuàng)建新的任務(wù)。
現(xiàn)象如下:
任務(wù)1的代碼中,需要注意的是:xTaskCreate的返回值。
- 很多手冊里說它失敗時(shí)返回值是pdFAIL,這個(gè)宏是0
- 其實(shí)失敗時(shí)返回值是errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY,這個(gè)宏是-1
- 為了避免混淆,我們使用返回值跟pdPASS來比較,這個(gè)宏是1
3.3 任務(wù)優(yōu)先級和Tick
3.3.1 任務(wù)優(yōu)先級
在上個(gè)示例中我們體驗(yàn)過優(yōu)先級的使用:高優(yōu)先級的任務(wù)先運(yùn)行。
優(yōu)先級的取值范圍是:0~(configMAX_PRIORITIES – 1),數(shù)值越大優(yōu)先級越高。
FreeRTOS的調(diào)度器可以使用2種方法來快速找出優(yōu)先級最高的、可以運(yùn)行的任務(wù)。使用不同的方法時(shí),configMAX_PRIORITIES 的取值有所不同。
-
通用方法
使用C函數(shù)實(shí)現(xiàn),對所有的架構(gòu)都是同樣的代碼。對configMAX_PRIORITIES的取值沒有限制。但是configMAX_PRIORITIES的取值還是盡量小,因?yàn)槿≈翟酱笤嚼速M(fèi)內(nèi)存,也浪費(fèi)時(shí)間。
configUSE_PORT_OPTIMISED_TASK_SELECTION被定義為0、或者未定義時(shí),使用此方法。 -
架構(gòu)相關(guān)的優(yōu)化的方法
架構(gòu)相關(guān)的匯編指令,可以從一個(gè)32位的數(shù)里快速地找出為1的最高位。使用這些指令,可以快速找出優(yōu)先級最高的、可以運(yùn)行的任務(wù)。
使用這種方法時(shí),configMAX_PRIORITIES的取值不能超過32。
configUSE_PORT_OPTIMISED_TASK_SELECTION被定義為1時(shí),使用此方法。
在學(xué)習(xí)調(diào)度方法之前,你只要初略地知道:
- FreeRTOS會(huì)確保最高優(yōu)先級的、可運(yùn)行的任務(wù),馬上就能執(zhí)行
- 對于相同優(yōu)先級的、可運(yùn)行的任務(wù),輪流執(zhí)行
這無需記憶,就像我們舉的例子:
- 廚房著火了,當(dāng)然優(yōu)先滅火
- 喂飯、回復(fù)信息同樣重要,輪流做
3.3.2 Tick
對于同優(yōu)先級的任務(wù),它們“輪流”執(zhí)行。怎么輪流?你執(zhí)行一會(huì),我執(zhí)行一會(huì)。
"一會(huì)"怎么定義?
人有心跳,心跳間隔基本恒定。
FreeRTOS中也有心跳,它使用定時(shí)器產(chǎn)生固定間隔的中斷。這叫Tick、滴答,比如每10ms發(fā)生一次時(shí)鐘中斷。
如下圖:
- 假設(shè)t1、t2、t3發(fā)生時(shí)鐘中斷
- 兩次中斷之間的時(shí)間被稱為時(shí)間片(time slice、tick period)
- 時(shí)間片的長度由configTICK_RATE_HZ 決定,假設(shè)configTICK_RATE_HZ為100,那么時(shí)間片長度就是10ms
相同優(yōu)先級的任務(wù)怎么切換呢?請看下圖:
- 任務(wù)2從t1執(zhí)行到t2
-
在t2發(fā)生tick中斷,進(jìn)入tick中斷處理函數(shù):
- 選擇下一個(gè)要運(yùn)行的任務(wù)
- 執(zhí)行完中斷處理函數(shù)后,切換到新的任務(wù):任務(wù)1
- 任務(wù)1從t2執(zhí)行到t3
- 從下圖中可以看出,任務(wù)運(yùn)行的時(shí)間并不是嚴(yán)格從t1,t2,t3哪里開始
有了Tick的概念后,我們就可以使用Tick來衡量時(shí)間了,比如:
vTaskDelay(2); // 等待2個(gè)Tick,假設(shè)configTICK_RATE_HZ=100, Tick周期時(shí)10ms, 等待20ms
// 還可以使用pdMS_TO_TICKS宏把ms轉(zhuǎn)換為tick
vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms
注意,基于Tick實(shí)現(xiàn)的延時(shí)并不精確,比如vTaskDelay(2)
的本意是延遲2個(gè)Tick周期,有可能經(jīng)過1個(gè)Tick多一點(diǎn)就返回了。
如下圖:
使用vTaskDelay函數(shù)時(shí),建議以ms為單位,使用pdMS_TO_TICKS把時(shí)間轉(zhuǎn)換為Tick。
這樣的代碼就與configTICK_RATE_HZ無關(guān),即使配置項(xiàng)configTICK_RATE_HZ改變了,我們也不用去修改代碼。
3.3.3 示例4: 優(yōu)先級實(shí)驗(yàn)
代碼為:FreeRTOS_04_task_priority
本程序會(huì)創(chuàng)建3個(gè)任務(wù):
- 任務(wù)1、任務(wù)2:優(yōu)先級相同,都是1
- 任務(wù)3:優(yōu)先級最高,是2
任務(wù)1、2代碼如下:
void vTask1( void *pvParameters )
{
/* 任務(wù)函數(shù)的主體一般都是無限循環(huán) */
for( ;; )
{
/* 打印任務(wù)的信息 */
printf("T1\r\n");
}
}
void vTask2( void *pvParameters )
{
/* 任務(wù)函數(shù)的主體一般都是無限循環(huán) */
for( ;; )
{
/* 打印任務(wù)的信息 */
printf("T2\r\n");
}
}
任務(wù)3代碼如下:
void vTask3( void *pvParameters )
{
const TickType_t xDelay3000ms = pdMS_TO_TICKS( 3000UL );
/* 任務(wù)函數(shù)的主體一般都是無限循環(huán) */
for( ;; )
{
/* 打印任務(wù)的信息 */
printf("T3\r\n");
// 如果不休眠的話, 其他任務(wù)無法得到執(zhí)行
vTaskDelay( xDelay3000ms );
}
}
main函數(shù)代碼如下:
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);
/* 啟動(dòng)調(diào)度器 */
vTaskStartScheduler();
/* 如果程序運(yùn)行到了這里就表示出錯(cuò)了, 一般是內(nèi)存不足 */
return 0;
}
運(yùn)行情況如下圖所示:
- 任務(wù)3優(yōu)先執(zhí)行,直到它調(diào)用vTaskDelay主動(dòng)放棄運(yùn)行
- 任務(wù)1、任務(wù)2:輪流執(zhí)行
調(diào)度情況如下圖所示:
3.3.4 示例5: 修改優(yōu)先級
本節(jié)代碼為:FreeRTOS_05_change_priority
。
使用uxTaskPriorityGet來獲得任務(wù)的優(yōu)先級:
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );
使用參數(shù)xTask來指定任務(wù),設(shè)置為NULL表示獲取自己的優(yōu)先級。
使用vTaskPrioritySet 來設(shè)置任務(wù)的優(yōu)先級:
void vTaskPrioritySet( TaskHandle_t xTask,
UBaseType_t uxNewPriority );
使用參數(shù)xTask來指定任務(wù),設(shè)置為NULL表示設(shè)置自己的優(yōu)先級;
參數(shù)uxNewPriority表示新的優(yōu)先級,取值范圍是0~(configMAX_PRIORITIES – 1)。
main函數(shù)的代碼如下,它創(chuàng)建了2個(gè)任務(wù):任務(wù)1的優(yōu)先級更高,它先執(zhí)行:
int main( void )
{
prvSetupHardware();
/* Task1的優(yōu)先級更高, Task1先執(zhí)行 */
xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, &xTask2Handle );
/* 啟動(dòng)調(diào)度器 */
vTaskStartScheduler();
/* 如果程序運(yùn)行到了這里就表示出錯(cuò)了, 一般是內(nèi)存不足 */
return 0;
}
任務(wù)1的代碼如下:
void vTask1( void *pvParameters )
{
UBaseType_t uxPriority;
/* Task1,Task2都不會(huì)進(jìn)入阻塞或者暫停狀態(tài)
* 根據(jù)優(yōu)先級決定誰能運(yùn)行
*/
/* 得到Task1自己的優(yōu)先級 */
uxPriority = uxTaskPriorityGet( NULL );
for( ;; )
{
printf( "Task 1 is running\r\n" );
printf("About to raise the Task 2 priority\r\n" );
/* 提升Task2的優(yōu)先級高于Task1
* Task2會(huì)即刻執(zhí)行
*/
vTaskPrioritySet( xTask2Handle, ( uxPriority + 1 ) );
/* 如果Task1能運(yùn)行到這里,表示它的優(yōu)先級比Task2高
* 那就表示Task2肯定把自己的優(yōu)先級降低了
*/
}
}
任務(wù)2的代碼如下:
void vTask2( void *pvParameters )
{
UBaseType_t uxPriority;
/* Task1,Task2都不會(huì)進(jìn)入阻塞或者暫停狀態(tài)
* 根據(jù)優(yōu)先級決定誰能運(yùn)行
*/
/* 得到Task2自己的優(yōu)先級 */
uxPriority = uxTaskPriorityGet( NULL );
for( ;; )
{
/* 能運(yùn)行到這里表示Task2的優(yōu)先級高于Task1
* Task1提高了Task2的優(yōu)先級
*/
printf( "Task 2 is running\r\n" );
printf( "About to lower the Task 2 priority\r\n" );
/* 降低Task2自己的優(yōu)先級,讓它小于Task1
* Task1得以運(yùn)行
*/
vTaskPrioritySet( NULL, ( uxPriority - 2 ) );
}
}
調(diào)度情況如下圖所示:
- 1:一開始Task1優(yōu)先級最高,它先執(zhí)行。它提升了Task2的優(yōu)先級。
- 2:Task2的優(yōu)先級最高,它執(zhí)行。它把自己的優(yōu)先級降低了。
- 3:Task1的優(yōu)先級最高,再次執(zhí)行。它提升了Task2的優(yōu)先級。
- 如此循環(huán)。
- 注意:Task1的優(yōu)先級一直是2,Task2的優(yōu)先級是3或1,都大于0。所以Idel任務(wù)沒有機(jī)會(huì)執(zhí)行。
3.4 任務(wù)狀態(tài)
以前我們很簡單地把任務(wù)的狀態(tài)分為2中:運(yùn)行(Runing)、非運(yùn)行(Not Running)。
對于非運(yùn)行的狀態(tài),還可以繼續(xù)細(xì)分,比如前面的FreeRTOS_04_task_priority
中:
- Task3執(zhí)行vTaskDelay后:處于非運(yùn)行狀態(tài),要過3秒種才能再次運(yùn)行
- Task3運(yùn)行期間,Task1、Task2也處于非運(yùn)行狀態(tài),但是它們隨時(shí)可以運(yùn)行
-
這兩種"非運(yùn)行"狀態(tài)就不一樣,可以細(xì)分為:
- 阻塞狀態(tài)(Blocked)
- 暫停狀態(tài)(Suspended)
- 就緒狀態(tài)(Ready)
3.4.1 阻塞狀態(tài)(Blocked)
在日常生活的例子中,母親在電腦前跟同事溝通時(shí),如果同事一直沒回復(fù),那么母親的工作就被卡住了、被堵住了、處于阻塞狀態(tài)(Blocked)。重點(diǎn)在于:母親在等待。
在FreeRTOS_04_task_priority
實(shí)驗(yàn)中,如果把任務(wù)3中的vTaskDelay調(diào)用注釋掉,那么任務(wù)1、任務(wù)2根本沒有執(zhí)行的機(jī)會(huì),任務(wù)1、任務(wù)2被"餓死"了(starve)。
在實(shí)際產(chǎn)品中,我們不會(huì)讓一個(gè)任務(wù)一直運(yùn)行,而是使用"事件驅(qū)動(dòng)"的方法讓它運(yùn)行:
- 任務(wù)要等待某個(gè)事件,事件發(fā)生后它才能運(yùn)行
- 在等待事件過程中,它不消耗CPU資源
- 在等待事件的過程中,這個(gè)任務(wù)就處于阻塞狀態(tài)(Blocked)
在阻塞狀態(tài)的任務(wù),它可以等待兩種類型的事件:
-
時(shí)間相關(guān)的事件
- 可以等待一段時(shí)間:我等2分鐘
- 也可以一直等待,直到某個(gè)絕對時(shí)間:我等到下午3點(diǎn)
-
同步事件:這事件由別的任務(wù),或者是中斷程序產(chǎn)生
- 例子1:任務(wù)A等待任務(wù)B給它發(fā)送數(shù)據(jù)
- 例子2:任務(wù)A等待用戶按下按鍵
-
同步事件的來源有很多(這些概念在后面會(huì)細(xì)講):
- 隊(duì)列(queue)
- 二進(jìn)制信號量(binary semaphores)
- 計(jì)數(shù)信號量(counting semaphores)
- 互斥量(mutexes)
- 遞歸互斥量、遞歸鎖(recursive mutexes)
- 事件組(event groups)
- 任務(wù)通知(task notifications)
在等待一個(gè)同步事件時(shí),可以加上超時(shí)時(shí)間。比如等待隊(duì)里數(shù)據(jù),超時(shí)時(shí)間設(shè)為10ms:
- 10ms之內(nèi)有數(shù)據(jù)到來:成功返回
- 10ms到了,還是沒有數(shù)據(jù):超時(shí)返回
3.4.2 暫停狀態(tài)(Suspended)
在日常生活的例子中,母親正在電腦前跟同事溝通,母親可以暫停:
- 好煩啊,我暫停一會(huì)
- 領(lǐng)導(dǎo)說:你暫停一下
FreeRTOS中的任務(wù)也可以進(jìn)入暫停狀態(tài),唯一的方法是通過vTaskSuspend函數(shù)。函數(shù)原型如下:
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
參數(shù)xTaskToSuspend表示要暫停的任務(wù),如果為NULL,表示暫停自己。
要退出暫停狀態(tài),只能由別人來操作:
- 別的任務(wù)調(diào)用:vTaskResume
- 中斷程序調(diào)用:xTaskResumeFromISR
實(shí)際開發(fā)中,暫停狀態(tài)用得不多。
3.4.3 就緒狀態(tài)(Ready)
這個(gè)任務(wù)完全準(zhǔn)備好了,隨時(shí)可以運(yùn)行:只是還輪不到它。這時(shí),它就處于就緒態(tài)(Ready)。
3.4.4 完整的狀態(tài)轉(zhuǎn)換圖
3.5 Delay函數(shù)
3.5.1 兩個(gè)Delay函數(shù)
有兩個(gè)Delay函數(shù):
- vTaskDelay:至少等待指定個(gè)數(shù)的Tick Interrupt才能變?yōu)榫途w狀態(tài)
- vTaskDelayUntil:等待到指定的絕對時(shí)刻,才能變?yōu)榫途w態(tài)。
這2個(gè)函數(shù)原型如下:
void vTaskDelay( const TickType_t xTicksToDelay ); /* xTicksToDelay: 等待多少給Tick */
/* pxPreviousWakeTime: 上一次被喚醒的時(shí)間
* xTimeIncrement: 要阻塞到(pxPreviousWakeTime + xTimeIncrement)
* 單位都是Tick Count
*/
BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement );
下面畫圖說明:
- 使用vTaskDelay(n)時(shí),進(jìn)入、退出vTaskDelay的時(shí)間間隔至少是n個(gè)Tick中斷
-
使用xTaskDelayUntil(&Pre, n)時(shí),前后兩次退出xTaskDelayUntil的時(shí)間至少是n個(gè)Tick中斷
- 退出xTaskDelayUntil時(shí)任務(wù)就進(jìn)入的就緒狀態(tài),一般都能得到執(zhí)行機(jī)會(huì)
- 所以可以使用xTaskDelayUntil來讓任務(wù)周期性地運(yùn)行
3.5.2 示例6: Delay
本節(jié)代碼為:FreeRTOS_06_taskdelay
。
本程序會(huì)創(chuàng)建2個(gè)任務(wù):
-
Task1:
- 高優(yōu)先級
-
設(shè)置變量flag為1,然后調(diào)用
vTaskDelay(xDelay50ms);
或vTaskDelayUntil(&xLastWakeTime, xDelay50ms);
-
Task2:
- 低優(yōu)先級
- 設(shè)置變量flag為0
main函數(shù)代碼如下:
int main( void )
{
prvSetupHardware();
/* Task1的優(yōu)先級更高, Task1先執(zhí)行 */
xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
/* 啟動(dòng)調(diào)度器 */
vTaskStartScheduler();
/* 如果程序運(yùn)行到了這里就表示出錯(cuò)了, 一般是內(nèi)存不足 */
return 0;
}
Task1的代碼中使用條件開關(guān)來選擇Delay函數(shù),把#if 1
改為#if 0
就可以使用vTaskDelayUntil
,代碼如下:
void vTask1( void *pvParameters )
{
const TickType_t xDelay50ms = pdMS_TO_TICKS( 50UL );
TickType_t xLastWakeTime;
int i;
/* 獲得當(dāng)前的Tick Count */
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
flag = 1;
/* 故意加入多個(gè)循環(huán),讓程序運(yùn)行時(shí)間長一點(diǎn) */
for (i = 0; i <5; i++)
printf( "Task 1 is running\r\n" );
#if 1
vTaskDelay(xDelay50ms);
#else
vTaskDelayUntil(&xLastWakeTime, xDelay50ms);
#endif
}
}
Task2的代碼如下:
void vTask2( void *pvParameters )
{
for( ;; )
{
flag = 0;
printf( "Task 2 is running\r\n" );
}
}
使用Keil的邏輯分析觀察flag變量的bit波形,如下:
- flag為1時(shí)表示Task1在運(yùn)行,flag為0時(shí)表示Task2在運(yùn)行,也就是Task1處于阻塞狀態(tài)
- vTaskDelay:指定的是阻塞的時(shí)間
- vTaskDelayUntil:指定的是任務(wù)執(zhí)行的間隔、周期
3.6 空閑任務(wù)及其鉤子函數(shù)
3.6.1 介紹
在FreeRTOS_03_delete_task
的實(shí)驗(yàn)里,我們體驗(yàn)過空閑任務(wù)(Idle任務(wù))的作用:釋放被刪除的任務(wù)的內(nèi)存。
除了上述目的之外,為什么必須要有空閑任務(wù)?一個(gè)良好的程序,它的任務(wù)都是事件驅(qū)動(dòng)的:平時(shí)大部分時(shí)間處于阻塞狀態(tài)。有可能我們自己創(chuàng)建的所有任務(wù)都無法執(zhí)行,但是調(diào)度器必須能找到一個(gè)可以運(yùn)行的任務(wù):所以,我們要提供空閑任務(wù)。在使用vTaskStartScheduler()
函數(shù)來創(chuàng)建、啟動(dòng)調(diào)度器時(shí),這個(gè)函數(shù)內(nèi)部會(huì)創(chuàng)建空閑任務(wù):
- 空閑任務(wù)優(yōu)先級為0:它不能阻礙用戶任務(wù)運(yùn)行
- 空閑任務(wù)要么處于就緒態(tài),要么處于運(yùn)行態(tài),永遠(yuǎn)不會(huì)阻塞
空閑任務(wù)的優(yōu)先級為0,這意味著一旦某個(gè)用戶的任務(wù)變?yōu)榫途w態(tài),那么空閑任務(wù)馬上被切換出去,讓這個(gè)用戶任務(wù)運(yùn)行。在這種情況下,我們說用戶任務(wù)"搶占"(pre-empt)了空閑任務(wù),這是由調(diào)度器實(shí)現(xiàn)的。
要注意的是:如果使用vTaskDelete()
來刪除任務(wù),那么你就要確保空閑任務(wù)有機(jī)會(huì)執(zhí)行,否則就無法釋放被刪除任務(wù)的內(nèi)存。
我們可以添加一個(gè)空閑任務(wù)的鉤子函數(shù)(Idle Task Hook Functions),空閑任務(wù)的循環(huán)每執(zhí)行一次,就會(huì)調(diào)用一次鉤子函數(shù)。鉤子函數(shù)的作用有這些:
- 執(zhí)行一些低優(yōu)先級的、后臺的、需要連續(xù)執(zhí)行的函數(shù)
- 測量系統(tǒng)的空閑時(shí)間:空閑任務(wù)能被執(zhí)行就意味著所有的高優(yōu)先級任務(wù)都停止了,所以測量空閑任務(wù)占據(jù)的時(shí)間,就可以算出處理器占用率。
- 讓系統(tǒng)進(jìn)入省電模式:空閑任務(wù)能被執(zhí)行就意味著沒有重要的事情要做,當(dāng)然可以進(jìn)入省電模式了。
空閑任務(wù)的鉤子函數(shù)的限制:
- 不能導(dǎo)致空閑任務(wù)進(jìn)入阻塞狀態(tài)、暫停狀態(tài)
-
如果你會(huì)使用
vTaskDelete()
來刪除任務(wù),那么鉤子函數(shù)要非常高效地執(zhí)行。如果空閑任務(wù)移植卡在鉤子函數(shù)里的話,它就無法釋放內(nèi)存。
3.6.2 使用鉤子函數(shù)的前提
在FreeRTOS\Source\tasks.c
中,可以看到如下代碼,所以前提就是:
- 把這個(gè)宏定義為1:configUSE_IDLE_HOOK
-
實(shí)現(xiàn)
vApplicationIdleHook
函數(shù)
3.7 調(diào)度算法
3.7.1 重要概念
這些知識在前面都提到過了,這里總結(jié)一下。
正在運(yùn)行的任務(wù),被稱為"正在使用處理器",它處于運(yùn)行狀態(tài)。在單處理系統(tǒng)中,任何時(shí)間里只能有一個(gè)任務(wù)處于運(yùn)行狀態(tài)。
非運(yùn)行狀態(tài)的任務(wù),它處于這3中狀態(tài)之一:阻塞(Blocked)、暫停(Suspended)、就緒(Ready)。就緒態(tài)的任務(wù),可以被調(diào)度器挑選出來切換為運(yùn)行狀態(tài),調(diào)度器永遠(yuǎn)都是挑選最高優(yōu)先級的就緒態(tài)任務(wù)并讓它進(jìn)入運(yùn)行狀態(tài)。
阻塞狀態(tài)的任務(wù),它在等待"事件",當(dāng)事件發(fā)生時(shí)任務(wù)就會(huì)進(jìn)入就緒狀態(tài)。事件分為兩類:時(shí)間相關(guān)的事件、同步事件。所謂時(shí)間相關(guān)的事件,就是設(shè)置超時(shí)時(shí)間:在指定時(shí)間內(nèi)阻塞,時(shí)間到了就進(jìn)入就緒狀態(tài)。使用時(shí)間相關(guān)的事件,可以實(shí)現(xiàn)周期性的功能、可以實(shí)現(xiàn)超時(shí)功能。同步事件就是:某個(gè)任務(wù)在等待某些信息,別的任務(wù)或者中斷服務(wù)程序會(huì)給它發(fā)送信息。怎么"發(fā)送信息"?方法很多,有:任務(wù)通知(task notification)、隊(duì)列(queue)、事件組(event group)、信號量(semaphoe)、互斥量(mutex)等。這些方法用來發(fā)送同步信息,比如表示某個(gè)外設(shè)得到了數(shù)據(jù)。
3.7.2 配置調(diào)度算法
所謂調(diào)度算法,就是怎么確定哪個(gè)就緒態(tài)的任務(wù)可以切換為運(yùn)行狀態(tài)。
通過配置文件FreeRTOSConfig.h的兩個(gè)配置項(xiàng)來配置調(diào)度算法:configUSE_PREEMPTION、configUSE_TIME_SLICING。
還有第三個(gè)配置項(xiàng):configUSE_TICKLESS_IDLE,它是一個(gè)高級選項(xiàng),用于關(guān)閉Tick中斷來實(shí)現(xiàn)省電,后續(xù)單獨(dú)講解。現(xiàn)在我們假設(shè)configUSE_TICKLESS_IDLE被設(shè)為0,先不使用這個(gè)功能。
調(diào)度算法的行為主要體現(xiàn)在兩方面:高優(yōu)先級的任務(wù)先運(yùn)行、同優(yōu)先級的就緒態(tài)任務(wù)如何被選中。調(diào)度算法要確保同優(yōu)先級的就緒態(tài)任務(wù),能"輪流"運(yùn)行,策略是"輪轉(zhuǎn)調(diào)度"(Round Robin Scheduling)。輪轉(zhuǎn)調(diào)度并不保證任務(wù)的運(yùn)行時(shí)間是公平分配的,我們還可以細(xì)化時(shí)間的分配方法。
從3個(gè)角度統(tǒng)一理解多種調(diào)度算法:
可否搶占?高優(yōu)先級的任務(wù)能否優(yōu)先執(zhí)行(配置項(xiàng): configUSE_PREEMPTION)
- 可以:被稱作"可搶占調(diào)度"(Pre-emptive),高優(yōu)先級的就緒任務(wù)馬上執(zhí)行,下面再細(xì)化。
-
不可以:不能搶就只能協(xié)商了,被稱作"合作調(diào)度模式"(Co-operative Scheduling)
- 當(dāng)前任務(wù)執(zhí)行時(shí),更高優(yōu)先級的任務(wù)就緒了也不能馬上運(yùn)行,只能等待當(dāng)前任務(wù)主動(dòng)讓出CPU資源。
- 其他同優(yōu)先級的任務(wù)也只能等待:更高優(yōu)先級的任務(wù)都不能搶占,平級的更應(yīng)該老實(shí)點(diǎn)
可搶占的前提下,同優(yōu)先級的任務(wù)是否輪流執(zhí)行(配置項(xiàng):configUSE_TIME_SLICING)
- 輪流執(zhí)行:被稱為"時(shí)間片輪轉(zhuǎn)"(Time Slicing),同優(yōu)先級的任務(wù)輪流執(zhí)行,你執(zhí)行一個(gè)時(shí)間片、我再執(zhí)行一個(gè)時(shí)間片
- 不輪流執(zhí)行:英文為"without Time Slicing",當(dāng)前任務(wù)會(huì)一直執(zhí)行,直到主動(dòng)放棄、或者被高優(yōu)先級任務(wù)搶占
在"可搶占"+"時(shí)間片輪轉(zhuǎn)"的前提下,進(jìn)一步細(xì)化:空閑任務(wù)是否讓步于用戶任務(wù)(配置項(xiàng):configIDLE_SHOULD_YIELD)
- 空閑任務(wù)低人一等,每執(zhí)行一次循環(huán),就看看是否主動(dòng)讓位給用戶任務(wù)
- 空閑任務(wù)跟用戶任務(wù)一樣,大家輪流執(zhí)行,沒有誰更特殊
列表如下:
配置項(xiàng) | A | B | C | D | E |
---|---|---|---|---|---|
configUSE_PREEMPTION | 1 | 1 | 1 | 1 | 0 |
configUSE_TIME_SLICING | 1 | 1 | 0 | 0 | x |
configIDLE_SHOULD_YIELD | 1 | 0 | 1 | 0 | x |
說明 | 常用 | 很少用 | 很少用 | 很少用 | 幾乎不用 |
注:
- A:可搶占+時(shí)間片輪轉(zhuǎn)+空閑任務(wù)讓步
- B:可搶占+時(shí)間片輪轉(zhuǎn)+空閑任務(wù)不讓步
- C:可搶占+非時(shí)間片輪轉(zhuǎn)+空閑任務(wù)讓步
- D:可搶占+非時(shí)間片輪轉(zhuǎn)+空閑任務(wù)不讓步
- E:合作調(diào)度
3.7.3 示例7: 調(diào)度
本節(jié)代碼為:FreeRTOS_07_scheduler
。后續(xù)的實(shí)驗(yàn)都是基于這個(gè)程序,通過修改配置項(xiàng)來觀察效果。
代碼里創(chuàng)建了3個(gè)任務(wù):Task1、Task2的優(yōu)先級都是0,跟空閑任務(wù)一樣,Task3優(yōu)先級最高為2。程序里定義了4個(gè)全局變量,當(dāng)某個(gè)的任務(wù)執(zhí)行時(shí),對應(yīng)的變量就被設(shè)為1,可以通過Keil的邏輯分析儀查看任務(wù)切換情況:
static volatile int flagIdleTaskrun = 0; // 空閑任務(wù)運(yùn)行時(shí)flagIdleTaskrun=1
static volatile int flagTask1run = 0; // 任務(wù)1運(yùn)行時(shí)flagTask1run=1
static volatile int flagTask2run = 0; // 任務(wù)2運(yùn)行時(shí)flagTask2run=1
static volatile int flagTask3run = 0; // 任務(wù)3運(yùn)行時(shí)flagTask3run=1
main函數(shù)代碼如下:
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 0, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);
/* 啟動(dòng)調(diào)度器 */
vTaskStartScheduler();
/* 如果程序運(yùn)行到了這里就表示出錯(cuò)了, 一般是內(nèi)存不足 */
return 0;
}
任務(wù)1、任務(wù)2代碼如下,它們是"連續(xù)任務(wù)"(continuous task):
void vTask1( void *pvParameters )
{
/* 任務(wù)函數(shù)的主體一般都是無限循環(huán) */
for( ;; )
{
flagIdleTaskrun = 0;
flagTask1run = 1;
flagTask2run = 0;
flagTask3run = 0;
/* 打印任務(wù)的信息 */
printf("T1\r\n");
}
}
void vTask2( void *pvParameters )
{
/* 任務(wù)函數(shù)的主體一般都是無限循環(huán) */
for( ;; )
{
flagIdleTaskrun = 0;
flagTask1run = 0;
flagTask2run = 1;
flagTask3run = 0;
/* 打印任務(wù)的信息 */
printf("T2\r\n");
}
}
任務(wù)3代碼如下,它會(huì)調(diào)用vTaskDelay
,這樣別的任務(wù)才可以運(yùn)行:
void vTask3( void *pvParameters )
{
const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );
/* 任務(wù)函數(shù)的主體一般都是無限循環(huán) */
for( ;; )
{
flagIdleTaskrun = 0;
flagTask1run = 0;
flagTask2run = 0;
flagTask3run = 1;
/* 打印任務(wù)的信息 */
printf("T3\r\n");
// 如果不休眠的話, 其他任務(wù)無法得到執(zhí)行
vTaskDelay( xDelay5ms );
}
}
提供了一個(gè)空閑任務(wù)的鉤子函數(shù):
void vApplicationIdleHook(void)
{
flagIdleTaskrun = 1;
flagTask1run = 0;
flagTask2run = 0;
flagTask3run = 0;
/* 故意加入打印讓flagIdleTaskrun變?yōu)?的時(shí)間維持長一點(diǎn) */
printf("Id\r\n");
}
3.7.4 對比效果: 搶占與否
在FreeRTOSConfig.h
中,定義這樣的宏,對比邏輯分析儀的效果:
// 實(shí)驗(yàn)1:搶占
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configIDLE_SHOULD_YIELD 1
// 實(shí)驗(yàn)2:不搶占
#define configUSE_PREEMPTION 0
#define configUSE_TIME_SLICING 1
#define configIDLE_SHOULD_YIELD 1
從下面的對比圖可以知道:
- 搶占時(shí):高優(yōu)先級任務(wù)就緒時(shí),就可以馬上執(zhí)行
-
不搶占時(shí):優(yōu)先級失去意義了,既然不能搶占就只能協(xié)商了,圖中任務(wù)1一直在運(yùn)行(一點(diǎn)都沒有協(xié)商精神),其他任務(wù)都無法執(zhí)行。即使任務(wù)3的
vTaskDelay
已經(jīng)超時(shí)、即使它的優(yōu)先級更高,都沒辦法執(zhí)行。
3.7.5 對比效果: 時(shí)間片輪轉(zhuǎn)與否
在FreeRTOSConfig.h
中,定義這樣的宏,對比邏輯分析儀的效果:
// 實(shí)驗(yàn)1:時(shí)間片輪轉(zhuǎn)
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configIDLE_SHOULD_YIELD 1
// 實(shí)驗(yàn)2:時(shí)間片不輪轉(zhuǎn)
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 0
#define configIDLE_SHOULD_YIELD 1
從下面的對比圖可以知道:
- 時(shí)間片輪轉(zhuǎn):在Tick中斷中會(huì)引起任務(wù)切換
- 時(shí)間片不輪轉(zhuǎn):高優(yōu)先級任務(wù)就緒時(shí)會(huì)引起任務(wù)切換,高優(yōu)先級任務(wù)不再運(yùn)行時(shí)也會(huì)引起任務(wù)切換。可以看到任務(wù)3就緒后可以馬上執(zhí)行,它運(yùn)行完畢后導(dǎo)致任務(wù)切換。其他時(shí)間沒有任務(wù)切換,可以看到任務(wù)1、任務(wù)2都運(yùn)行了很長時(shí)間。
3.7.6 對比效果: 空閑任務(wù)讓步
在FreeRTOSConfig.h
中,定義這樣的宏,對比邏輯分析儀的效果:
// 實(shí)驗(yàn)1:空閑任務(wù)讓步
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configIDLE_SHOULD_YIELD 1
// 實(shí)驗(yàn)2:空閑任務(wù)不讓步
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configIDLE_SHOULD_YIELD 0
從下面的對比圖可以知道:
- 讓步時(shí):在空閑任務(wù)的每個(gè)循環(huán)中,會(huì)主動(dòng)讓出處理器,從圖中可以看到flagIdelTaskrun的波形很小
- 不讓步時(shí):空閑任務(wù)跟任務(wù)1、任務(wù)2同等待遇,它們的波形寬度是差不多的
審核編輯:符乾江
評論
查看更多