freertos用task來表示一個進程。
task擁有自己的stack,這是進程最明顯的標誌。
task被表示爲4種狀態。run, ready, suspend, block。
run態是當前佔據CPU的task的狀態。
ready態是當前不需要等待資源到位,只需要排隊獲得CPU的task的狀態。
block態是當前需要等待事件到位的task的狀態,具有超時時間,不會一直block。
suspend態是當前需要等待時間到位的task的狀態,但是不具有超時時間,會一直suspend。
來看看任務控制塊。
typedef struct tskTaskControlBlock{
char pcTaskName[configMAX_TASK_NAME_SIZE];
volatile StackType_t * pxTopOfStack;
ListItem_t xStateListItem;
ListItem_t xEventListItem;
UBaseType_t uxPriority;
StackType_t * pxStack;
...
}tskTCB;
typedef tskTCB TCB_t;
每個TASK,要指定一個任務函數,這是創建任務時的入口函數。
void vTaskFunction(void* pvParameters);
taskfunction有一些約束,
返回類型必須是void,
函數過程必須包含一個無限循環,一般不允許退出這個無限循環,如果要跳出這個無限循環,則最後一定要用vTaskDelete(NULL)來刪除這個TASK。
freertos中有一個特殊的任務,IDLETASK,它是在startscheduler時被系統自動創建的。
IDLETASK使用最低的優先級0.
我們知道,任務可以刪除其他任務,但是如果有任務刪除自身,那麼IDLETASK就要負責釋放這個被刪除的任務的資源。
freertos提供了一系列TASK API。
BaseType_t
xTaskCreate(
TaskFunction_t pxTaskFunction,
const char* const pcName,
const uint16_t usStackDepth,
void* const pvParamters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask
);
void vTaskDelete(TaskHandle_t xTaskToDelete);
void vTaskSuspend(TaskHandle_t xTaskToSuspend);
void vTaskResume(TaskHandle_t xTaskToResume);
void vTaskResumeFromISR(TaskHandle_t xTaskToResume);
當用戶程序中調用TASK API時,OS會利用調度器對進程進行管理。
在main中,使用
vTaskStartScheduler(),
就啓動了調度器。
調度器中,會首先創建一個IDLETASK。
然後會創建一個TIMERTASK。
最後,會啓動SYSTICKTIMER,由SYSTICK中斷來進行事件驅動。
在每個SYSTICK中斷髮生時,OS會進行任務調度。
除了SYSTICK中斷中會發生任務切換,還可以執行一個SYSCALL,觸發SWI,來進行任務切換。
有些API會觸發任務切換。例如taskYIELD()。
有些API會調用taskYIELD,所以這些API都會引起任務切換。
如果需要使用TIMESLICING調度。那麼
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
這兩個宏必須設置爲1.
除此之外,OS提供了其他一些API。
UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask);
UBaseType_t uxTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority);
TaskHandle_t xTaskGetCurrentTaskHandle(void);
TaskHandle_t xTaskGetHandle(const char* pcNameToQuery);
TaskHandle_t xTaskGetIdleTaskHandle(void);
char* pcTaskGetName(TaskHandle_t xTaskToQuery);
TickType_t xTaskGetTickCount(void);
TickType_t xTaskGetTickCountFromISR(void);
freertos中,經常需要在一個任務中使用延時,對任務延時。
延時函數執行時,由於需要等待時間事件的到來,所以會將任務設置爲block態,這就意味着,將發生任務切換。
當延時時間到後,任務才解除block,重新進入ready態。
OS提供了DELAY API。
void vTaskDelay(const TickType_t xTicksToDelay);
void vTaskDelayUntil(TickType_t* const pxPreviousWakeTime, const TickType_t xTimeIncrement);
如果延時時間爲0,則相當於直接使用了taskYIELD進行任務切換。
如果延時時間爲portMAX_DELAY,則直接將任務設置爲suspend,而不是block。
taskdelayuntil用於週期性喚醒的任務。它使用的是絕對時間。如果是對週期性要求比較高的任務,則需要使用這個函數。
典型代碼如下:
void vTaskA(void* pvParameters)
{
static TickType_t PreviousWakeTime;
const TickType_t TimeIncrement = pdMS_TO_TICKS(100);
for(;;){
vTaskDelayUntil(&previousWakeTime, TimeIncrement);
...//when wake up ,do your action
}
}
當TASKA被喚醒後,進入ready態,如果獲得了CPU,則進入run態,在進入Run態時,會從之前的斷點返回,即vTaskDelayUntil。
從taskdelayuntil返回後,進開始執行主體代碼,當主體代碼執行完時,又會回到循環開頭,再次被阻塞到時間事件上。
freertos的啓動流程,
int main(void){
BSP_init();
rtos_init();
start_task_create(start_task_function);
start_scheduler();
while(1);
}
void start_task_function(void* arg)
{
task_create(task1);
task_create(task2);
...
taskdelete)(start_task);
}
void task1(void* arg)
{
while(1)
{
...
}
}
void task2(void* arg)
{
while(1)
{
...
}
}