1. Task function
在 FreeRTOS 裡, 每條thread都稱作 task, 實作成C function, 回傳值是void, 並且parameter為void pointer, 像這樣
void ATaskFunction( void *pvParameters );
每個task通常會一直執行不會停 (所以應該有個loop在裡面), FreeRTOS 不准有"return", 也不准執行到最後一行
如果我們不需要某個task, 我們應該刪除這個task
底下是 task function可能的實作
void ATaskFunction( void *pvParameters ) { /* 如同一般的function來宣告變數 */ int iVariableExample = 0; /* 實作一個 infinite loop */ for( ;; ) { /* task實際上要做的事 */ } /* 如果task在loop中break出來, 那麼task必需在到function end之前刪除, 比如說用 vTaskDelete( NULL ), 裡面的NULL表示是刪目前的task */ vTaskDelete( NULL ); }
task function 可以拿來建多個task, 每個task都是獨立的instance, 有自己的stack
2. Task state
我們先簡單的把task分成Running / Not Running
當 task 從 Not Running 變成 Running, 稱作 task in
相反地稱作 task out
3. Create Task
建task用到 xTaskCreate() API, 它的prototype如下
portBASE_TYPE xTaskCreate( pdTASK_CODE pvTaskCode, const signed portCHAR * const pcName, unsigned portSHORT usStackDepth, void *pvParameters, unsigned portBASE_TYPE uxPriority, xTaskHandle *pxCreatedTask );( task.h )
底下是參數的說明:
- pvTaskCode : 放task function的function pointer
- pcName : task的名字, 在FreeRTOS不會用到它, 這只是用於debug用途, 名字長度限制為configMAX_TASK_NAME_LEN
- usStackDepth : 每個 task 被放進 stack 的時候, usStackDepth表示它需要的最大stack space, usStackDepth以byte為單位, 比如說, stack size = 32 bit, usStackDepth = 100, 則會用到100*4=400 bytes stack space, 如果task只是設計成idle task, 那麼可以用這個值 configMINIMAL_STACK_SIZE
- pvParameters : task function的參數, 型態為 (void *)
- uxPriority : task 的優先權, 0最低, (configMAX_PRIORITIES – 1)最高
- pxCreatedTask : 這個task的reference, 用於動態調整這個task的priority, 或是刪掉這個task
可能的回傳值 :
- pdTRUE : 表示成功
- errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY : 表示RAM不夠了
void vTask1( void *pvParameters ) { const char *pcTaskName = "Task 1 is running\r\n"; volatile unsigned long ul; /* As per most tasks, this task is implemented in an infinite loop. */ for( ;; ) { /* Print out the name of this task. */ vPrintString( pcTaskName ); /* Delay for a period. */ for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ ) { /* busy delay */ } } } void vTask2( void *pvParameters ) { const char *pcTaskName = "Task 2 is running\r\n"; volatile unsigned long ul; /* As per most tasks, this task is implemented in an infinite loop. */ for( ;; ) { /* Print out the name of this task. */ vPrintString( pcTaskName ); /* Delay for a period. */ for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ ) { /* busy delay */ } } } int main( void ) { /* 建tasks1, 通常需要檢查回傳值, 這裡先略過 */ xTaskCreate( vTask1, /* task function pointer, 通常用function name就可以 */ "Task 1", /* Task name */ 1000, /* Stack depth - 一般的例子不會用到這麼多 */ NULL, /* 不帶parameter */ 1, /* priority */ NULL ); /* 不需要task handle */ /* task2跟task1是差不多的 */ xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL ); /* 啟動scheduler, 讓tasks開始執行 */ vTaskStartScheduler(); /* 正常來說不會執行到這邊, 因為scheduler會執行tasks, 執行到這邊可能是 heap memory 不夠 */ for( ;; ); }
實際執行結果會看到task1 & task2兩個不停地task in/out
前個例子是把兩個task在main裡面create, 我們也可以把task放在task裡面create
void vTask1( void *pvParameters ) { const char *pcTaskName = "Task 1 is running\r\n"; volatile unsigned long ul; /* 執行到這邊表示Scheduler已經開始執行task1 */ xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL ); for( ;; ) { /* Print out the name of this task. */ vPrintString( pcTaskName ); /* Delay for a period. */ for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ ) { /* busy delay */ } } }
因為task1 & task2很像, 所以改寫成一個就好
void vTaskFunction( void *pvParameters ) { char *pcTaskName; volatile unsigned long ul; /* 把要印出來的字串經由parameter帶進來, 再轉成 (char *) */ pcTaskName = ( char * ) pvParameters; for( ;; ) { /* 把perameter帶進來的字串印出來 */ vPrintString( pcTaskName ); /* Delay for a period. */ for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ ) { /* busy delay*/ } } } /* 要帶給task的字串 */ static const char *pcTextForTask1 = “Task 1 is running\r\n”; static const char *pcTextForTask2 = “Task 2 is running\t\n”; int main( void ) { /* Create task 1 */ xTaskCreate( vTaskFunction, /* function pointer */ "Task 1", /* task name */ 1000, /* stack depth */ (void*)pcTextForTask1, /* 參數, 把要印的字串轉成(void *) */ 1, /* priority */ NULL ); /* task handle */ /* Create task2, 參數不一樣 */ xTaskCreate( vTaskFunction, "Task 2", 1000, (void*)pcTextForTask2, 1, NULL ); /* Start scheduler */ vTaskStartScheduler(); for( ;; ); }
4. Task priority
priority可以動態的改, 最高不會超過configMAX_PRIORITIES (defined in FreeRTOSConfig.h)
我們可以改configMAX_PRIORITIES, 但改的愈高也就耗掉更多的RAM, 所以通常會保留default值
為了選出下一個要執行的task, scheduler應該要在task執行的片段最後面執行
而 tick interrupt 就是讓scheduler達到這個目的
time slice的常度定義在 configTICK_RATE_HZ (FreeRTOSConfig.h)
舉例來說, 如果 configTICK_RATE_HZ =100Hz, 那麼time slice = 10ms
要注意的是, FreeRTOS的時間是以tick當單位, 就連 portTICK_RATE_MS 也是以tick當單位
每次選task, 都會選priority最高的task, 如果有一樣的priority則會輪流
如果我們把剛剛的例子改一下priority的部份
int main( void ) { /* task1 的 priority 是 1 */ xTaskCreate( vTaskFunction, "Task 1", 1000, (void*)pcTextForTask1, 1, NULL ); /* task2 的 priority 是 2 */ xTaskCreate( vTaskFunction, "Task 2", 1000, (void*)pcTextForTask2, 2, NULL ); vTaskStartScheduler(); return 0; }
執行結果會看到:
Task 2 is running
Task 2 is running
Task 2 is running
Task 2 is running
Task 2 is running
.....
結果Task 1因為priority比Task 2, 所以每次選都選不到它造成starvation
5. Not running state
為了避免starvation造成某些task永遠跑不到, 可以使用event driven
這意味著在選擇task的時候, scheduler沒有選priority最高的task
5.1 Blocked State
當task在等某個event的時候, 稱它在 Blocked State
task等的event分兩種 :
- Temporal event : 在等delay或是確切的時間
- Synchronization event : 這個 event 由其它 task 或 interrupt 產生
task 也可以同時等這兩種, 比如說, 在10s內等待data
5.2 Suspended State
當 task 在 suspended state 的時候, 就不會被scheduler選上
進suspend : vTaskSuspend()
離開suspend : vTaskResume() or xTaskResumeFromISR()
但大多時候不太會用到suspend
5.3 Ready State
當 task為 Not running, 並且不是Blocked/Suspended State, 就稱作 Ready State
5.4 State Transition Diagram
5.4 vTaskDelay()
之前的例子, 用的是busy delay, 所以Task 2實際上並沒有suspend
我們可以用 vTaskDelay() 來改寫這個 delay 讓 Task 2 進 suspend
void vTaskFunction( void *pvParameters ) { char *pcTaskName; pcTaskName = ( char * ) pvParameters; for( ;; ) { vPrintString( pcTaskName ); /* 等250 ms, 參數是ticks, 可以利用portTICK_RATE_MS把milliseconds轉成ticks */ vTaskDelay( 250 / portTICK_RATE_MS ); } }
實際結果是:
Task 2 is running
Task 1 is running
Task 2 is running
Task 1 is running
Task 2 is running
Task 1 is running
Task 1 is running
.....
因為有足夠的時間, 所以雖然Task2的priority比較高, 但它執行完就suspend, 讓Task 1在Task 2 suspend的期間成為唯一的task可以執行, 然後suspend
5.5 vTaskDelayUntil()
vTaskDelayUntil() 跟vTaskDelay()很像, 差別是vTaskDelayUntil()決定多少個tick之後要被離開blocked state, 適合用在需要定時執行的task
這是它的prototype
void vTaskDelayUntil( portTickType * pxPreviousWakeTime, portTickType xTimeIncrement );
其中它的參數:
- pxPreviousWakeTime : 這個pointer會更新上一次離開block state的時間
- xTimeIncrement : 週期, 單位為ticks, 一樣可以用 portTICK_RATE_MS 轉換成ms
void vTaskFunction( void *pvParameters ) { char *pcTaskName; portTickType xLastWakeTime; pcTaskName = ( char * ) pvParameters; /* xLastWakeTime 需要初始化成目前的tick count */ xLastWakeTime = xTaskGetTickCount(); for( ;; ) { vPrintString( pcTaskName ); /* 自 xLastWakeTime 的時間等 250ms 離開 block state */ vTaskDelayUntil( &xLastWakeTime, ( 250 / portTICK_RATE_MS ) ); } }
6. Idle task
Processor通常都需要執行些什麼, 如果task都進suspend, 那裡它會執行idle task
idle task 是在 scheduler 一開始的時候產生, 擁有priority 0, 做的事情就跟busy loop差不多
6.1 Idle Task Hook Function
我們也可以加 callback function 在 idle task 裡
通常用於:
- 執行低優先權的工作, 或只需要背景處理的工作
- 計算剩下的processing capacity
- 進low power mode
6.2 Limitation of Idle Task
不可以block / suspend
如果 callback function 呼叫 vTaskDelete() 來刪除某個task, 那麼會馬上跳回這個task, 讓這個task能夠釋放已用的資源
6.3 Usage of callback of Idle Task
callback function的prototype如下
void vApplicationIdleHook( void );
使用的時候要注意 USE_IDLE_HOOK 的config要設成1 (FreeRTOSConfig.h)
底下是sample code:
/* 儲存 ilde task 裡的 loop 次數 */ unsigned long ulIdleCycleCount = 0UL; /* Idle hook function只能叫做 vApplicationIdleHook() */ void vApplicationIdleHook( void ) { ulIdleCycleCount++; } void vTaskFunction( void *pvParameters ) { char *pcTaskName; pcTaskName = ( char * ) pvParameters; for( ;; ) { /* 印出 task name 以及 ulIdleCycleCount */ vPrintStringAndNumber( pcTaskName, ulIdleCycleCount ); /* Delay 250 ms*/ vTaskDelay( 250 / portTICK_RATE_MS ); } }
執行結果像這樣:
Task 2 is running
uIdleCycleCOunt = 0
Task 1 is running
uIdleCycleCOunt = 0
Task 2 is running
uIdleCycleCOunt = 3869504
Task 1 is running
uIdleCycleCOunt = 3869504
Task 2 is running
uIdleCycleCOunt = 8564623
Task 1 is running
uIdleCycleCOunt = 8564623
......
7. Changing task priority
更改priority :
void vTaskPrioritySet( xTaskHandle pxTask, unsigned portBASE_TYPE uxNewPriority );
pxTask 是 task handle, uxNewPriority 是新的 priority
取得priority :
unsigned portBASE_TYPE uxTaskPriorityGet( xTaskHandle pxTask );
底下是 sample code
/* 拿來存task2 handle */ xTaskHandle xTask2Handle; void vTask1( void *pvParameters ) { unsigned portBASE_TYPE uxPriority; uxPriority = uxTaskPriorityGet( NULL ); for( ;; ) { vPrintString( "Task1 is running\r\n" ); /* 把 Task 2 的 priority 設成 Task 1 多 1 */ vTaskPrioritySet( xTask2Handle, ( uxPriority + 1 ) ); } } void vTask2( void *pvParameters ) { unsigned portBASE_TYPE uxPriority; uxPriority = uxTaskPriorityGet( NULL ); for( ;; ) { vPrintString( "Task2 is running\r\n" ); /* 把 Task 2 的 priority 降 2 */ vTaskPrioritySet( NULL, ( uxPriority - 2 ) ); } } int main( void ) { xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL ); xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, &xTask2Handle ); vTaskStartScheduler(); for( ;; ); }
因為Task 2的priority在一開始比Task 1少1,
所以這個sample裡, Scheduler會讓Task 1執行 (並把Task 2的priority設成比Task 1大1)
在下一輪, Scheduler發現Task 2的priority比Task 1大, 就執行Task 2 (並降priority比Task 1小1)
執行結果就是Task 1/2輪流地執行
8. Deleting Task
用 vTaskDelete() API 來刪掉task, 被刪的task就再也不會進到 Running state
而 idle task 負責釋放被刪掉的task的資源, 注意不可大量用 vTaskDelete() 造成 idle task starve
並且要注意只有 kernel 分配給 task 的資源會自動釋放, 但如果是 task 主動要的資源則要在刪除前釋放
底下是 prototype
void vTaskDelete( xTaskHandle pxTaskToDelete );
底下是sample code
/* 存 Task 2 handle */ xTaskHandle xTask2Handle; void vTask2( void *pvParameters ) { vPrintString( "Task2 is running and about to delete itself\r\n" ); vTaskDelete( xTask2Handle ); } void vTask1( void *pvParameters ) { const portTickType xDelay100ms = 100 / portTICK_RATE_MS; for( ;; ) { vPrintString( "Task1 is running\r\n" ); xTaskCreate( vTask2, "Task 2", 1000, NULL, 2, &xTask2Handle ); vTaskDelay( xDelay100ms ); } } int main( void ) { xTaskCreate( vTask1, "Task 1", 1000, NULL, 1, NULL ); vTaskStartScheduler(); for( ;; ); }
Task 1會create Task 2, Task 2在執行完後就會自刪, 所以執行結果就是Task 1/2輪流執行
沒有留言:
張貼留言