一、任務管理算法
任務或者說進程是一個操做系統的基本概念,該書並無去說明什麼是任務,而是從應用的角度去介紹怎麼在FreeRTOS中去建立一個任務並管理它。express
1.1 任務函數安全
FreeRTOS中的任務是以一個函數的形式存在的,具備統一的函數原型,以下:架構
void TaskFunction(void *pvParameters);函數
其必須返回void且帶有一個void指針參數,任務函數體內一般有一個死循環,決不能有一條return語句,也不能執行到函數尾部,若是某個任務再也不須要,能夠顯式的將其刪除。測試
1.2任務狀態操作系統
當MCU只有一個核,應用程序又包含多個任務,那麼只有一個任務正在執行,其餘任務都處於等待狀態,以下:.net
這是最簡單的模型,那麼CPU選擇哪個任務運行呢?在早期CPU很是昂貴和稀有,許多用戶排隊等待CPU資源,那個時候的任務調度算法側重「公平共享」處理器時間,全部的任務地位平等,調度器給每一個任務一個固定的時間得到CPU資源,時間一到運行的任務就必須將CPU讓出來給其餘任務,這種調度算法成爲時間片輪轉調度。可是在實際的應用場景中,不一樣的任務緊急程度不一樣,比如你正在洗衣服手機響了,那麼你應該放下手中的衣服並去接聽電話,當電話說完了再去接着洗衣服。因而時間片輪轉調度發展出一些變種,好比將優先級相同的任務放在同一個隊列,而且優先級越高的隊列其時間片也越短。3d
隨着處理器功能愈來愈強大,價格愈來愈便宜,單個用戶能夠獨佔一個處理器,可使用戶同時運行多個應用程序,好比用戶能夠一邊聽音樂一邊看網頁。隨着系統中的任務愈來愈多,應用場景也愈來愈複雜,原來只有運行態和等待態的模型已經不能知足要求。例如:一個已得到除CPU以外的全部資源(如內存空間等)的任務A,和一個已經在CPU中運行,可是由於等待某些事件(好比等待用戶輸入)而被中斷運行的任務B,這兩個任務此時都在內存中且沒有在CPU上運行,那麼這兩個任務是否均可以歸爲等待態呢?顯然不行,試想一下,A和B都在等待態,且B的優先級更高,那麼調度器就會將CPU交給B,運行B時發現它在等待事件,將B移出CPU如此往復,直到B等待的事件發生,在這個過程當中CPU實際沒有作有效工做,浪費了CPU資源。如此只能將A和B置爲不一樣的狀態,FreeRTOS就將A的狀態定義爲就緒態,而B的狀態成爲阻塞態。就緒態就是已經準備好,只要CPU一空閒立馬就能夠運行的狀態,而阻塞態是等待某一事件,只有該事件發生才能繼續運行的狀態,一些操做系統是將已等到事件發生的阻塞態任務轉爲就緒態,以下:指針
就緒態和運行態之間之因此是雙向箭頭,由於一些支持搶佔式的操做系統中,當就緒態中有了一個優先級更高的任務時,會搶佔正在運行的低優先級的任務,並將低優先級的任務置爲就緒態或者由於運行的任務已運行超過容許的時間而被移出運行態進入就緒態。
隨着任務進一步增多,而內存由於成本等緣由,空間增加跟不上任務的增加,另外CPU的速度遠高於IO等設備的速度,內存中可能會出現許多處於阻塞態的任務。這個時候就有必要將一些阻塞態任務從內存中移到磁盤,將內存讓給處於運行態和就緒態的任務,以下圖:
掛起態之因此可以轉化爲就緒態,是由於在任務掛起時,若是它等待的事件發生了,那當調度器將其移入內存時,它的狀態就變爲就緒態了。下圖是FreeRTOS中的完整任務狀態機模型。
爲了不反覆下載到板子等繁瑣操做帶來的麻煩,我使用了FreeRTOS在Windows下的模擬器,在PC機上作實驗,模擬器下載地址是
https://sourceforge.net/projects/freertos/
壓縮包裏面有Visual Studio環境的工程文件。
1.3 實驗一:建立任務
//每隔1秒,打印一次字符串
static void MyFirstTask( void *pvParameters )
{
while(1)
{
printf("\r\nThis is MyFirstTask!\r\n");
vTaskDelay(1000);//延時1000ms
}
}
void Test1_CreateTask( void )
{
//建立一個任務
xTaskCreate( MyFirstTask, //任務函數
"MyFirstTask", //任務名
configMINIMAL_STACK_SIZE, //任務棧深度
NULL, //傳入任務函數的參數
5, //任務優先級
NULL ); //任務句柄
vTaskStartScheduler();
for( ;; ); //任務調度器啓動失敗會進入這裏
}
FreeRTOS中任務實例是一個永不返回的函數,函數原型固定爲:
void ATaskFunction( void *pvParameters );
建立任務使用FreeRTOS 的API 函數xTaskCreate():
portBASE_TYPE xTaskCreate( pdTASK_CODE pvTaskCode,
const signed portCHAR * const pcName,
unsigned portSHORT usStackDepth,
void *pvParameters,
unsigned portBASE_TYPE uxPriority,
xTaskHandle *pxCreatedTask );
pvTaskCode :任務只是永不退出的C 函數,實現常一般是一個死循環。參數
pvTaskCode 只一個指向任務的實現函數的指針(效果上僅僅是函數名)。
pcName: 具備描述性的任務名。這個參數不會被FreeRTOS 使用。其只是單純地用於輔助調試。識別一個具備可讀性的名字老是比經過句柄來識別容易得多。
應用程序能夠經過定義常量 config_MAX_TASK_NAME_LEN 來定義任務名的最大長度——包括’\0’結束符。若是傳入的字符串長度超過了這個最大值,字符串將會自動被截斷。
usStackDepth: 當任務建立時,內核會分爲每一個任務分配屬於任務本身的惟一狀態。usStackDepth 值用於告訴內核爲它分配多大的棧空間。這個值指定的是棧空間能夠保存多少個字(word),而不是多少個字節(byte)。好比說,若是是32 位寬的棧空間,傳入的usStackDepth值爲100,則將會分配400 字節的棧空間(100 * 4bytes)。棧深度乘以棧寬度的結果千萬不能超過一個size_t 類型變量所能表達的最大值。應用程序經過定義常量 configMINIMAL_STACK_SIZE 來決定空閒
任務任用的棧空間大小。在FreeRTOS 爲微控制器架構提供的Demo 應用程序中,賦予此常量的值是對全部任務的最小建議值。若是你的任務會使用大量棧空間,那麼你應當賦予一個更大的值。沒有任何簡單的方法能夠決定一個任務到底須要多大的棧空間。計算出來雖然是可能的,但大多數用戶會先簡單地賦予一個自認爲合理的值,而後利用FreeRTOS 提供的特性來確證分配的空間既不欠缺也不浪費。
pvParameters: 任務函數接受一個指向void 的指針(void*)。pvParameters 的值便是傳遞到任務中的值。這篇文檔中的一些範例程序將會示範這個參數能夠如何使用。
uxPriority: 指定任務執行的優先級。優先級的取值範圍能夠從最低優先級0 到
最高優先級(configMAX_PRIORITIES – 1)。configMAX_PRIORITIES 是一個由用戶定義的常量。優先級號並無上限(除了受限於採用的數據類型和系統的有效內存空間),但最好使用實際須要的最小數值以免內存浪費。若是uxPriority 的值超過了(configMAX_PRIORITIES – 1),將會致使實際賦給任務的優先級被自動封頂到最大合法值。
pxCreatedTask pxCreatedTask: 用於傳出任務的句柄。這個句柄將在API 調用中對該建立出來的任務進行引用,好比改變任務優先級,或者刪除任務。若是應用程序中不會用到這個任務的句柄,則pxCreatedTask 能夠被設爲NULL。
返回值: 有兩個可能的返回值:
1. pdTRUE
代表任務建立成功。
2. errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
因爲內存堆空間不足,FreeRTOS 沒法分配足夠的空間來保存任務結構數據和任務棧,所以沒法建立任務。
該實驗的運行效果以下:
實驗一:建立任務
在任務中調用的vTaskDelay須要在FreeRTOSConfig.h文件中將「INCLUDE_vTaskDelay」宏開關打開。調用vTaskDelay的任務會被阻塞,並進行一次任務切換。
那麼在任務MyFirstTask被阻塞的這1000ms時間裏,CPU在幹啥?CPU是一直處於運行狀態的,FreeRTOS在啓動任務調度器vTaskStartScheduler()時,會自動建立一個空閒任務:
//若是須要空閒任務的句柄,就會進入該分支,並將空閒任務句柄存到xIdleTaskHandle中
#if ( INCLUDE_xTaskGetIdleTaskHandle == 1 )
{
xReturn = xTaskCreate( prvIdleTask, ( signed char * ) "IDLE", tskIDLE_STACK_SIZE, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), &xIdleTaskHandle );
}
#else
{
xReturn = xTaskCreate( prvIdleTask, ( signed char * ) "IDLE", tskIDLE_STACK_SIZE, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), NULL );
}
#endif
空閒任務的優先級爲默認爲0,是最低優先級,執行的是循環指令(若是有任務被刪除,空閒任務要負責回收資源),爲的就是當全部任務都被阻塞時,CPU「有事可作」。
固然也能夠在FreeRTOSConfig.h文件中將「configUSE_IDLE_HOOK」宏開關打開,而後在空閒任務鉤子函數vApplicationIdleHook()中搞一些「小事情」:
l 執行低優先級,後臺或須要不停處理的功能代碼。
l 測試系統處理裕量(空閒任務只會在全部其它任務都不運行時纔有機會執行,因此測量出空閒任務佔用的處理時間就能夠清楚的知道系統有多少富餘的處理時間)。
l 將處理器配置到低功耗模式——提供一種自動省電方法,使得在沒有任何應用功能須要處理的時候,系統自動進入省電模式。
由於空閒任務隨時均可能被搶斷,因此空閒任務鉤子函數不能完成較複雜的功能並且永遠不能被阻塞或掛起,以防沒有任務處於就緒態使得CPU「無事可作」。
1.4實驗二:建立兩個任務
static char strTask1[] = "\r\nThis is Task1!\r\n";
static char strTask2[] = "\r\nThis is Task2!\r\n";
//打印字符串
static void PrintString( void *pvParameters )
{
//將傳入的任務參數強制轉爲char *型
char *strPrintf = (char *)pvParameters;
int i ,j;
while(1)
{
printf("\r\n%s\r\n",strPrintf);
for(i=0;i<10000;i++)
for(j=0;j<10000;j++);
}
}
void Test2_CreateTwoTask( void )
{
//建立一個任務
xTaskCreate( PrintString, //任務函數
"PrintString1", //任務名
configMINIMAL_STACK_SIZE, //任務棧深度
strTask1, //傳入任務函數的參數
5, //任務優先級
NULL ); //任務句柄
//建立一個任務
xTaskCreate( PrintString, //任務函數
"PrintString2", //任務名
configMINIMAL_STACK_SIZE, //任務棧深度
strTask2, //傳入任務函數的參數
5, //任務優先級
NULL ); //任務句柄
vTaskStartScheduler();
for( ;; ); //任務調度器啓動失敗會進入這裏
}
這裏建立的兩個任務幾乎徹底同樣,只是打印的字符串不同,所以能夠共用一個任務函數來建立兩個任務實例,只是傳入任務函數的參數不一樣,兩個任務實例在調度器的控制下獨立運行。
運行效果以下:
實驗二:建立兩個任務
能夠看出同優先級的任務,即便自身沒有阻塞或掛起,依然會被交替執行,這叫「時間片輪轉」,每一個任務執行一個「時間片」後退出運行態,而後調度器選擇另外一個同優先級的任務。時間片的長度能夠經過在FreeRTOSConfig.h文件中配置宏「configTICK_RATE_HZ」,好比將其配置爲100(Hz),那麼時間片的長度就是10ms,時間片的長度要合適,不能太長也不能過短。
若是將上述源碼中任務1的優先級從5提升到6,能夠看到運行效果以下:
調度器每次選擇就緒列表中優先級最高的任務,因此任務2得不到被執行的機會,稱之爲「餓死」。爲了不多任務系統中低優先級的任務被餓死,高優先級的任務要主動將本身阻塞或者掛起,好比調用前文提到的vTaskDelay()。同時須要注意的是,FreeRTOS是「可搶佔式調度」,意思是若是有優先級更高的任務被建立出來,調度器會進行一次任務切換,運行更高優先級的任務,即便當前任務的「時間片」沒有用完。
1.5實驗三:修改任務的優先級
FreeRTOS經常使用的管理任務優先級的有這樣幾個API:
UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask )
功能:得到某個任務當前的優先級;
參數:
xTask:須要獲取優先級的任務的句柄,當值爲NULL時表示獲取當前任務的優先級;
UBaseType_t uxTaskPriorityGetFromISR( TaskHandle_t xTask )
功能:與uxTaskPriorityGet完成的功能是同樣的,惟一的區別是uxTaskPriorityGetFromISR是中斷安全的API。必須說明的是,只有以」FromISR」或」FROM_ISR」結束的API 函數或宏才能夠在中斷服務例程中。
參數:
xTask:須要獲取優先級的任務的句柄,當值爲NULL時表示獲取當前任務的優先級;
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority )
功能:設置任務的優先級
參數:
xTask:須要設置優先級的任務的句柄,當值爲NULL時表示獲取當前任務的優先級;
uxNewPriority:新優先級
實驗源碼以下:
TaskHandle_t taskHandle_Task2;//任務2的句柄
//任務1函數
static void Task1( void *pvParameters )
{
int i,j;
while(1)
{ //將任務2的優先級設置成與本任務優先級一致
vTaskPrioritySet(taskHandle_Task2,uxTaskPriorityGet(NULL));
printf("\r\nTask2 is Running!\r\n");
for(i=0;i<1000;i++)
for(j=0;j<1000;j++);
}
}
//任務2 函數
static void Task2( void *pvParameters )
{
int i,j;
while(1)
{
//下降本任務優先級
vTaskPrioritySet(NULL,uxTaskPriorityGet(NULL)-1);
printf("\r\nTask2 Cannot Run!\r\n");
for(i=0;i<1000;i++)
for(j=0;j<1000;j++);
}
}
void Test3_CreateTwoTask( void )
{
//建立任務1
xTaskCreate( Task1, //任務函數
"Task1", //任務名
configMINIMAL_STACK_SIZE, //任務棧深度
NULL, //傳入任務函數的參數
5, //任務優先級
NULL ); //任務句柄
//建立任務2
xTaskCreate( Task2, //任務函數
"Task2", //任務名
configMINIMAL_STACK_SIZE, //任務棧深度
NULL, //傳入任務函數的參數
5, //任務優先級
&taskHandle_Task2 ); //任務句柄
vTaskStartScheduler();
for( ;; ); //任務調度器啓動失敗會進入這裏
}
運行效果:
實驗三:修改任務的優先級
1.6實驗四:刪除任務
用到的API是void vTaskDelete( TaskHandle_t xTaskToDelete ),
功能:刪除指定的任務
參數:
xTaskToDelete:要刪除的任務句柄
實驗源碼以下:
TaskHandle_t taskHandle_Task2;//任務2的句柄
//任務1函數
static void Task1( void *pvParameters )
{
vTaskDelete(taskHandle_Task2);//刪除任務2
while(1)
{
printf("\r\nTHis is Task1!\r\n");
vTaskDelay(1000);
}
}
//任務2函數
static void Task2( void *pvParameters )
{
while(1)
{
printf("\r\nTHis is Task2!\r\n");
vTaskDelay(1000);
}
}
void Test4_CreateTwoTask( void )
{
//建立任務1
xTaskCreate( Task1, //任務函數
"Task1", //任務名
configMINIMAL_STACK_SIZE, //任務棧深度
NULL, //傳入任務函數的參數
4, //任務優先級
NULL ); //任務句柄
//建立任務2
xTaskCreate( Task2, //任務函數
"Task2", //任務名
configMINIMAL_STACK_SIZE, //任務棧深度
NULL, //傳入任務函數的參數
5, //任務優先級
&taskHandle_Task2 ); //任務句柄
vTaskStartScheduler();
for( ;; ); //任務調度器啓動失敗會進入這裏
}
運行效果以下:
任務2的優先級高於任務1,可是在任務2自我阻塞時,任務1開始運行並刪除了任務2。須要主要的是,任務1刪除任務2並自我阻塞後,空閒任務開始運行,空閒任務會回收任務2佔用的資源。