目錄
- 前言
- 一、FreeRTOS移植的注意事項
- 二、任務運行狀態的獲取
- 三、任務運行狀態的原始碼優化
前言
FreeRTOS因其簡潔小巧、功能齊全而深受嵌入式領域歡迎,如下圖所示,除Linux外,FreeRTOS為全球嵌入式領域市場份額最多的實時作業系統,與之對標的uCOS、RTX、ThreadX等都被遠遠的甩在其后,加之近年又被亞馬遜收購,FreeRTOS的應用應該會進一步擴大,網上已有很多各實時作業系統的對比,我也使用過幾種作業系統包括FreeRTOS、uCOS等,但FreeRTOS給我的最深的印象就是移植方便,能夠針對不同芯片進行快速移植,并且該有的功能也都有了,

本文包含以下內容(本文不適用于初學者,默認讀者已有了良好的工程搭建基礎,以及良好的嵌入式代碼閱讀、撰寫能力):
1 對FreeRTOS移植程序中的重點做簡要說明;
2 講解開發階段需要用到的【任務運行狀態監視】該如何配置;
3 針對實際軟體穩定性對FreeRTOS的原始碼稍作修改,使得任務運行狀態可根據自己的需求隨意擴展,文中主要是在原始碼中增加了【任務切換次數】這個屬性,在任務發生切換時進行自加,并能通過FreeRTOS自帶的任務狀態獲取函式獲取,
一、FreeRTOS移植的注意事項
由于移植教程網上已有很多,而且基于KEIL、IAR的工程Demo也不在少數,故本文不對FreeRTOS的移植程序作詳細講解,只講大概步驟和主要的注意事項,
1.從【官網】下載原始碼后,將source目錄下的c檔案加入到工程中,將include目錄拷貝到工程目錄下,然后在source/portable目錄下找到適合自己MCU內核的介面檔案,并加入到工程中,
2.在Demo檔案夾中找到適合自己MCU的檔案夾,并將FreeRTOSConfig.h檔案拷貝到工程目錄下;
3.作業系統轉起來需要在滴答定時器中斷中定時運行調度函式、并依賴SVC例外處理函式獲取當前任務控制塊、依賴Pending中斷來進行背景關系切換,以上幾個中斷、例外時執行的函式都在source/portable目錄下的檔案port.c或portasm.s中給出,一般為匯編函式,FreeRTOS中分別如下命名:
xPortSysTickHandler
vPortSVCHandler
xPortPendSVHandler
而在啟動檔案的中斷向量表中,以上三個中斷、例外按如下命名:
SysTick_Handler
SVC_Handler
PendSV_Handler
因此,當發生以上中斷、例外時,為了能順利呼叫FreeRTOS已經寫好的調度、切換函式,需要在FreeRTOSConfig.h中作如下宏定義:
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
這樣一來,FreeRTOS原始碼中的vPortSVCHandler、xPortPendSVHandler函式就被替換成了中斷向量表中對應的中斷服務函式,從而在中斷發生時能進入FreeRTOS寫好的調度、任務切換函式,
到這里,有人會問,不還有個xPortSysTickHandler嗎,怎么沒有替換,原因是現在的嵌入式系統,無論裸機還是跑作業系統,一般都使能滴答定時器中斷,并在中斷中對一個變數進行自加操作,從而使這個變數成為整個系統的時間戳,因此,系統一初始化我就配置了周期為1ms的滴答定時器中斷,并在中斷函式中進行了時間戳的自加,這樣一來,只需要把xPortSysTickHandler函式放到滴答定時器的中斷處理函式中即可,如下所示:
volatile static uint32_t uwTick = 0;
void uwTickInc(void)
{
uwTick++;
}
uint32_t sys_getTick(void)
{
return uwTick;
}
void SysTick_Handler(void)
{
uwTickInc();
xPortSysTickHandler();
}
如此一來,擁有了滴答定時器驅動的調度函式、用于背景關系切換的Pending中斷函式,FreeRTOS就可以跑起來了,具體的任務創建、系統開啟等就不再贅述了,
二、任務運行狀態的獲取
開發階段需要在長期運行、測驗中發現并解決問題,因此獲取每個任務的運行狀態是很有必要的,好在FreeRTOS中也提供了這個功能,一般說來,系統的監視最好放在等級最高的任務中,這樣才能保證能監視所有任務,FreeRTOS中開啟任務運行狀態獲取主要分以下幾步:
1.在FreeRTOSConfig.h中添加宏定義:
#define configUSE_TRACE_FACILITY 1
#define configGENERATE_RUN_TIME_STATS 1
這樣一來,在空閑任務中就會統計各個任務運行的總耗時以及系統運行的總時長,既然說到時間統計了,那么定時器必不可少,在下一步中配置,
2.需要配置FreeRTOS給出的兩個宏定義:
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
#define portGET_RUN_TIME_COUNTER_VALUE()
其中第一個是運行時間的定時器配置函式,第二個是定時器的時間戳獲取函式,由于我們已經配置了1ms的滴答定時器,并創建了一個永遠自加的時間戳變數,因此,這里的第一個宏定義可以直接為空,第二個宏定義配置為上文中的sys_getTick( )即可:
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
#define portGET_RUN_TIME_COUNTER_VALUE() sys_getTick( )
3.在監視任務中呼叫uxTaskGetSystemState進行各個任務狀態的獲取:
void watchTask(void *arg)
{
TaskStatus_t task_state[N];
uint32_t total_time;
while(1)
{
vTaskDelay(pdMS_TO_TICKS(arg));
uxTaskGetSystemState(task_state,N,&total_time);
}
其中,函式的傳入引數void *arg為該任務的空轉時間,用于釋放cpu占用權供其他低優先級任務使用;N為你創建的任務總數(包括一個空閑任務),呼叫uxTaskGetSystemState后,就將所有任務的狀態拉取到task_state陣列中了,并將系統運行的總時長賦值給total_time,也就是滴答定時器中的時間戳,其中,TaskStatus_t 結構體元素如下所示,能夠展示任務句柄、名稱、序號、當前狀態等等:
/* Used with the uxTaskGetSystemState() function to return the state of each task
* in the system. */
typedef struct xTASK_STATUS
{
TaskHandle_t xHandle; /* The handle of the task to which the rest of the information in the structure relates. */
const char * pcTaskName; /* A pointer to the task's name. This value will be invalid if the task was deleted since the structure was populated! */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
UBaseType_t xTaskNumber; /* A number unique to the task. */
eTaskState eCurrentState; /* The state in which the task existed when the structure was populated. */
UBaseType_t uxCurrentPriority; /* The priority at which the task was running (may be inherited) when the structure was populated. */
UBaseType_t uxBasePriority; /* The priority to which the task will return if the task's current priority has been inherited to avoid unbounded priority inversion when obtaining a mutex. Only valid if configUSE_MUTEXES is defined as 1 in FreeRTOSConfig.h. */
uint32_t ulRunTimeCounter; /* The total run time allocated to the task so far, as defined by the run time stats clock. See https://www.FreeRTOS.org/rtos-run-time-stats.html. Only valid when configGENERATE_RUN_TIME_STATS is defined as 1 in FreeRTOSConfig.h. */
StackType_t * pxStackBase; /* Points to the lowest address of the task's stack area. */
configSTACK_DEPTH_TYPE usStackHighWaterMark; /* The minimum amount of stack space that has remained for the task since the task was created. The closer this value is to zero the closer the task has come to overflowing its stack. */
} TaskStatus_t;
但有一點不好的是,uCOS中的TCB控制塊還有【任務切換次數】這個元素,而FreeRTOS的TCB控制塊中竟然沒有,而這個元素往往揭示了系統切換有無例外發生,在開發階段是一個重要資訊,有人會說,不是有個ulRunTimeCounter元素,能體現該任務的運行總用時嗎,這似乎可以替代切換次數吧?其實不然,因為任務運行時間的統計依賴于#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()定義的定時器的最小步長,通過對通過對ulRunTimeCounter統計原始碼的閱讀,發現如果定時器步長時間太長,如10ms,那么很有可能任務已經轉完一圈了,仍不能被統計到ulRunTimeCounter中,因為任務被切換時,由任務執行到任務釋放CPU所占用的時間如果還不足10ms,就不能夠被累加到ulRunTimeCounter,ulRunTimeCounter的統計原始碼部分如下所示:
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
#else
ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
#endif
/* Add the amount of time the task has been running to the
* accumulated time so far. The time the task started running was
* stored in ulTaskSwitchedInTime. Note that there is no overflow
* protection here so count values are only valid until the timer
* overflows. The guard against negative values is to protect
* against suspect run time stat counter implementations - which
* are provided by the application, not the kernel. */
if( ulTotalRunTime > ulTaskSwitchedInTime )
{
pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
ulTaskSwitchedInTime = ulTotalRunTime;
}
#endif /* configGENERATE_RUN_TIME_STATS */
三、任務運行狀態的原始碼優化
為了解決運行狀態資訊中,不包含【任務切換次數】這個元素的問題,需要對原始碼進行修改,使得其支持這個功能,能讓我們直觀的查看某個任務被調度了多少次,具體的思路步驟為【任務控制塊中加入此元素】->【任務創建時初始化此元素】->【任務狀態結構體中加入此元素】->【任務切換時對此元素加1】->【獲取任務狀態時能回傳此元素】,按以下步驟進行原始碼修改:
1.在tasks.c中的typedef struct tskTaskControlBlock結構體中加入uint32_t switchTime元素,如下代碼所示:
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
volatile StackType_t * pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
#endif
uint32_t switchTime;
ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; /*< Used to reference a task from an event list. */
UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */
StackType_t * pxStack; /*< Points to the start of the stack. */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t * pxEndOfStack; /*< Points to the highest valid address for the stack. */
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
UBaseType_t uxMutexesHeld;
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
#if ( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
/* Allocate a Newlib reent structure that is specific to this task.
* Note Newlib support has been included by popular demand, but is not
* used by the FreeRTOS maintainers themselves. FreeRTOS is not
* responsible for resulting newlib operation. User must be familiar with
* newlib and must provide system-wide implementations of the necessary
* stubs. Be warned that (at the time of writing) the current newlib design
* implements a system-wide malloc() that must be provided with locks.
*
* See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
* for additional information. */
struct _reent xNewLib_reent;
#endif
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif
/* See the comments in FreeRTOS.h with the definition of
* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
#endif
#if ( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
#if ( configUSE_POSIX_ERRNO == 1 )
int iTaskErrno;
#endif
} tskTCB;
2.在tasks.c中的prvInitialiseNewTask任務初始化函式中加入switchTime的初始化,部分代碼如下所示:
/* This is used as an array index so must ensure it's not too large. First
* remove the privilege bit if one is present. */
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxNewTCB->uxPriority = uxPriority;
pxNewTCB->switchTime = 0;
#if ( configUSE_MUTEXES == 1 )
{
pxNewTCB->uxBasePriority = uxPriority;
pxNewTCB->uxMutexesHeld = 0;
}
#endif /* configUSE_MUTEXES */
3.在task.h的TaskStatus_t結構體中加入uint32_t switchTime,代碼如下所示,因為需要呼叫uxTaskGetSystemState函式來獲得狀態資訊,其引數就是TaskStatus_t指標,
typedef struct xTASK_STATUS
{
TaskHandle_t xHandle; /* The handle of the task to which the rest of the information in the structure relates. */
const char * pcTaskName; /* A pointer to the task's name. This value will be invalid if the task was deleted since the structure was populated! */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
UBaseType_t xTaskNumber; /* A number unique to the task. */
eTaskState eCurrentState; /* The state in which the task existed when the structure was populated. */
UBaseType_t uxCurrentPriority; /* The priority at which the task was running (may be inherited) when the structure was populated. */
UBaseType_t uxBasePriority; /* The priority to which the task will return if the task's current priority has been inherited to avoid unbounded priority inversion when obtaining a mutex. Only valid if configUSE_MUTEXES is defined as 1 in FreeRTOSConfig.h. */
uint32_t ulRunTimeCounter; /* The total run time allocated to the task so far, as defined by the run time stats clock. See https://www.FreeRTOS.org/rtos-run-time-stats.html. Only valid when configGENERATE_RUN_TIME_STATS is defined as 1 in FreeRTOSConfig.h. */
StackType_t * pxStackBase; /* Points to the lowest address of the task's stack area. */
configSTACK_DEPTH_TYPE usStackHighWaterMark; /* The minimum amount of stack space that has remained for the task since the task was created. The closer this value is to zero the closer the task has come to overflowing its stack. */
uint32_t switchTime;
} TaskStatus_t;
4.在任務背景關系切換的函式中對switchTime進行++操作,具體為:在tasks.c的vTaskSwitchContext函式中加入pxCurrentTCB->switchTime++,代碼如下所示:
void vTaskSwitchContext( void )
{
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
{
/* The scheduler is currently suspended - do not allow a context
* switch. */
xYieldPending = pdTRUE;
}
else
{
xYieldPending = pdFALSE;
pxCurrentTCB->switchTime++;
traceTASK_SWITCHED_OUT();
#if ( configGENERATE_RUN_TIME_STATS == 1 )
.
.
.
.
}
5.通過閱讀原始碼得知,uxTaskGetSystemState函式真正獲得任務運行資訊是呼叫的vTaskGetInfo函式,因此在tasks.c的vTaskGetInfo函式中加入pxTaskStatus->switchTime = pxTCB->switchTime,代碼如下所示,這樣一來,呼叫uxTaskGetSystemState函式時,TCB中的switchTime元素就被賦值到TaskStatus_t指標中了,
#if ( configUSE_TRACE_FACILITY == 1 )
void vTaskGetInfo( TaskHandle_t xTask,
TaskStatus_t * pxTaskStatus,
BaseType_t xGetFreeStackSpace,
eTaskState eState )
{
TCB_t * pxTCB;
/* xTask is NULL then get the state of the calling task. */
pxTCB = prvGetTCBFromHandle( xTask );
pxTaskStatus->xHandle = ( TaskHandle_t ) pxTCB;
pxTaskStatus->pcTaskName = ( const char * ) &( pxTCB->pcTaskName[ 0 ] );
pxTaskStatus->uxCurrentPriority = pxTCB->uxPriority;
pxTaskStatus->pxStackBase = pxTCB->pxStack;
pxTaskStatus->xTaskNumber = pxTCB->uxTCBNumber;
pxTaskStatus->switchTime = pxTCB->switchTime;
.
.
.
}
經過以上優化,在監視任務中就可以直接呼叫uxTaskGetSystemState來獲取到各個任務的切換次數,從而為監視任務增加了強有力的監管判斷條件,即某個任務一定時間內切換次數未變,則相應地做出什么處理,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/377212.html
標籤:其他
