【嵌入式】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来获取到各个任务的切换次数,从而为监视任务增加了强有力的监管判断条件,即某个任务一定时间内切换次数未变,则相应地做出什么处理。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>