# QEMU-FreeRTOS & Scheduler Tracing ###### tags: `learning` ## Outline :::info 1. Basic steps for environment set-up - QEMU installation - Build up RTOS on QEMU emulator - Tracing Original RTOS scheduler - Implement FCFS algo 2. Advanced implementations on scheduler - Multi-level Feedback Queue - Schedulability Analysis - Checking Deadline Missed ::: ## Basic Steps for Environment Set-upupup ### QEMU installation - 使用`Ubuntu 20.04`, 首先更新到最新版本 `$ sudo apt update; sudo apt upgrade` - 安裝開發工具&QEMU emulator ```bash $ sudo apt install git vim make gcc gcc-arm-none-eabi gdb gdb-multiarch make net-tools qemu-system universal-ctags -y ``` - 可以再為gdb裝個插件`gef` `$ wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef.sh | sh ` ### Build RTOS - 從github上面把完整的FreeRTOS下載下來 `$ git clone https://github.com/FreeRTOS/FreeRTOS.git` - 因為有些source code是以submodule的方式存在 因此要再進到`FreeRTOS`的目錄把source code拉下來 `$ cd FreeRTOS; git submodule update --init --recursive` - 接著進到`FreeRTOS/Demo` 裡面有不同對應不同架構的code跟`Makefile` 這邊選擇模擬`CORTEX_LM3S811_GCC` - 進到`FreeRTOS/Demo/CORTEX_LM3S811_GCC`目錄底下 因為我們之後要做source code的debug 所以compile時,gcc的指令應該要加上`-g`才能在debug時產生symbol table 因此需要修改`Makefile`中的`CFLAGS`,加上`-g`參數 ![](https://i.imgur.com/vrRiHrO.png =350x) - 接著就可以`make`進行編譯 編譯成功之後,在會在`gcc`目錄下產生兩個檔案 1. `RTOSDemo.axf` : 這個檔案是給 gdb 看的 2. `RTOSDemo.bin` : 這個檔案是要丟進去`QEMU`執行 - 然後就可以用下面指令用`QEMU`把FreeRTOS的kernel跑起來 ```c $ qemu-system-arm -machine lm3s811evb //選擇要模擬的機器 -kernel gcc/RTOSDemo.bin //指定kernel image -s //shorthand for -gdb tcp::1234 -S //freeze CPU at startup (use 'c' to start execution) -nographic //disable graphical output and redirect serial I/Os to console ``` - 打開另一個terminal 進到`FreeRTOS/Demo/CORTEX_LM3S811_GCC`目錄 此時可以用以下指令連上`QEMU`的gdb server `$ gdb-multiarch -ex="target remote localhost:1234" gcc/RTOSDemo.axf` 會斷在`startup.c`中 ![](https://i.imgur.com/x6PsJNp.png) ### Tracing Original RTOS scheduler - FreeRTOS使用<font color='red'>`Prioritized Preemptive Scheduling with Time Slicing`</font> - `#define configUSE_PREEMPTION 1` - 定義在`FreeRTOSConfig.h`內 - 代表優先權高的可以馬上插隊 - `#define configUSE_TIME_SLICING 1` - 定義在`FreeRTOS.h`內 - 相同優先權的task可以共享processing time - `#define configMAX_PRIORITIES ( 5 )` - 定義在`FreeRTOSConfig.h`內 - 代表優先權分成5個等級(0-4) - 一個task為執行的基本單位,可以用`xTaskCreate`函數來建立task ```c= BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, const char * const pcName, const configSTACK_DEPTH_TYPE usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask ) ``` 1. `pxTaskCode`: 每個task都由一個函數組成,並將function name當成參數傳入`xTaskCreate`,一個典型的task function如下 ```c= void ATaskFunc(void *pvParameters) { int i = 0; while(1) { //do_sth } /* 如果該task需要離開loop結束(ex loop中執行錯誤要跳出來) * 需要用vTaskDelete來刪除自己而非使用return或自然結束 */ vTaskDelete(NULL); //NULL是指自己 } ``` 2. `pcName`: 這名稱是用來debug時識別用的,task不會用到 3. `usStackDepth`: 每個task會有自己的stack,這個值用來告訴kernel應該要分配多少stack空間(<font color='red'>多少個word</font>)給這個task 4. `pvParameters`: 要傳給這個task的argumemt vector 5. `uxPriority`: 定義這個task的優先權值(0~`configMAX_PRIORITIES`-1) 6. `pxCreatedTask`: 代表這個task的handle,相當於fd的概念 - `xTaskCreate`實作如下 ```c= BaseType_t xTaskCreate(...) { TCB_t * pxNewTCB; BaseType_t xReturn; #if ( portSTACK_GROWTH > 0 ) { /* Allocate space for the TCB. Where the memory comes from depends on * the implementation of the port malloc function and whether or not static * allocation is being used. */ pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); if( pxNewTCB != NULL ) { /* Allocate space for the stack used by the task being created. * The base of the stack memory stored in the TCB so the task can * be deleted later if required. */ pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ if( pxNewTCB->pxStack == NULL ) { /* Could not allocate the stack. Delete the allocated TCB. */ vPortFree( pxNewTCB ); pxNewTCB = NULL; } } } ... if( pxNewTCB != NULL ) { #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */ { /* Tasks can be created statically or dynamically, so note this * task was created dynamically in case it is later deleted. */ pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB; } #endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */ prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL ); prvAddNewTaskToReadyList( pxNewTCB ); xReturn = pdPASS; } else { xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; } return xReturn; } ``` - 前面先是為這個task建立了一個`pxNewTCB`,TCB大概結構如下 ```c //task.c typedef struct tskTaskControlBlock { volatile StackType_t * pxTopOfStack //stack ptr ... ListItem_t xStateListItem; //相同state的task會用這邊串起來 ListItem_t xEventListItem; UBaseType_t uxPriority; //task的優先值(永遠不會變) StackType_t * pxStack; //指向stack base char pcTaskName[...]; ... }tskTCB; typedef tskTCB TCB_t; ``` - 然後第40行呼叫了`prvInitialiseNewTask`進行TCB的初始化,最後第41行呼叫`prvAddNewTaskToReadyList`把目前task丟到Ready list中等待排程 - `prvAddNewTaskToReadyList` ```c= static void prvAddNewTaskToReadyList(TCB_t * pxNewTCB) { taskENTER_CRITICAL(); { uxCurrentNumberOfTask++; if (pxCurrentTCB == NULL) //如果目前沒有其他ready或正在run的task { pxCurrentTCB = pxNewTCB; ... } else { if (xSchedulerRunning == pdFalse) //如果scheduler還未啟動 { if (pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority) //目前這個task的優先值最高 { pxCurrentTCB = pxNewTCB; } ... } } uxTaskNumber++; ... prvAddTaskToReadyList( pxNewTCB ); } taskEXIT_CRITICAL(); } ``` - 由此可以知道,要把某個task放回ready list時 相關的function(其他例如`xTaskResume`.`xTaskResumeAll`等等) 應該都會先去檢查他的`tcb->uxPriority`是否比`pxCurrentTCB->uxPriority`大(上面第16行的code) 如果較大表是優先權更高,可以插隊(檢查`configUSE_PREEMITION`的值) 如果沒有則在呼叫`prvAddTaskToReadyList`(第25行) ```c= //task.c #define prvAddTaskToReadyList( pxTCB ) \ traceMOVED_TASK_TO_READY_STATE( pxTCB ); \ taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \ vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \ tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB ) ``` - FreeRTOS使用`ready list`去管理準備好要執行的task,而`ready list`的資料儲存方式如下圖 ![](https://i.imgur.com/LY94UD9.png =400x) - OS進行context switch(`vTaskSwitchContext`)時會去選出下一個要執行的task ```c= //vTaskSwitchContext from task.c if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE ) { /* The scheduler is currently suspended - do not allow a context * switch. */ xYieldPending = pdTRUE; } else { xYieldPending = pdFALSE; traceTASK_SWITCHED_OUT(); //switch out current task ... taskSELECT_HIGHEST_PRIORITY_TASK(); //選出下一個要執行的task traceTASK_SWITCHED_IN(); //switch in 要執行的task ... } } ``` - `taskSELECT_HIGHEST_PRIORITY_TASK` ```c= #define taskSELECT_HIGHEST_PRIORITY_TASK() \ { \ UBaseType_t uxTopPriority = uxTopReadyPriority; \ \ /* Find the highest priority queue that contains ready tasks. */ \ while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \ { \ configASSERT( uxTopPriority ); \ --uxTopPriority; \ } \ \ /* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of \ * the same priority get an equal share of the processor time. */ \ listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \ uxTopReadyPriority = uxTopPriority; \ } /* taskSELECT_HIGHEST_PRIORITY_TASK */ ``` - 建立好task之後,`main`還需要呼叫`vTaskStartScheduler`來啟動scheduler 1. 第6行先建立了一個`idle task`,這個task會在沒有其他task時執行(priority為0) 2. 第23行如果有config timer,通過`xTimerCreateTimerTask`建立timer 3. 如果scheduler建立成功,(第39行)就把`xSchedulerRunning`設為true 4. 第41行,呼叫`xPortStartScheduler` - 裡面會呼叫`vPortStartFirstTask`來執行第一個task ```c= void vTaskStartScheduler( void ) { BaseType_t xReturn; ... xIdleTaskHandle = xTaskCreateStatic( prvIdleTask, onfigIDLE_TASK_NAME, ulIdleTaskStackSize, ( void * ) NULL, /*lint !e961. The cast is not redundant for all compilers. */ portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */ pxIdleTaskStackBuffer, pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */ if( xIdleTaskHandle != NULL ) { xReturn = pdPASS; } else { xReturn = pdFAIL; } #if ( configUSE_TIMERS == 1 ) { if( xReturn == pdPASS ) { xReturn = xTimerCreateTimerTask(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_TIMERS */ ... if( xReturn == pdPASS ) { ... xSchedulerRunning = pdTRUE; ... if( xPortStartScheduler() != pdFALSE ) } } ... ``` - ### Implement FCFS algo - 還要再把`FreeRTOSConfig.h`中的`#define configUSE_PREEMPTION 1`改成`0` - 最後準備撰寫`main.c` 建立3個task(`A`,`B`,`C`),每個task分別print出`This is the A/B/C function` 並且將priority亂調(`A=4`, `B=1`, `C=3`) ```c= void vTaskAfunc(void *pvParameters) { while(1) { printf('This is the A function\n'); break; } vTaskDelete(NULL); } void vTaskBfunc(void *pvParameters) { while(1) { printf('This is the B function\n'); break; } vTaskDelete(NULL); } void vTaskCfunc(void *pvParameters) { while(1) { printf('This is the C function\n'); break; } vTaskDelete(NULL); } int main( void ) { ... xTaskHandle a, b, c; xTaskCreate(vTaskAfunc, "A", configMINIMAL_STACK_SIZE, NULL, 4, a); xTaskCreate(vTaskBfunc, "B", configMINIMAL_STACK_SIZE, NULL, 1, b); xTaskCreate(vTaskCfunc, "C", configMINIMAL_STACK_SIZE, NULL, 3, c); /* Start the scheduler. */ vTaskStartScheduler(); /* Will only get here if there was insufficient heap to start the scheduler. */ return 0; } ``` - 照原本的scheduler,執行順序應該是(`A->C->B`) FCFS則應該是(`A->B->C`)