2014/09/22

FreeRTOS task management

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不夠了
底下是sample code:

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
.....

因為有足夠的時間, 所以雖然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輪流執行



沒有留言:

張貼留言