FreeRTOS学习笔记02-任务创建和删除(动态方法和静态方法)

1 前言

任务创建和删除API函数位于文件task.c中,需要包含task.h头文件。

1.1 任务创建

函数描述

                   BaseType_t xTaskCreate(
                            TaskFunction_t pvTaskCode,
                            const char * const pcName,
                            unsigned short usStackDepth,
                            void *pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t * pvCreatedTask
                          );

创建新的任务并加入任务就绪列表。

  如果使用FreeRTOS-MPU(在官方下载包中,为Cortex-M3内核写了两个移植方案,一个是普通的FreeRTOS移植层,还有一个是FreeRTOS-MPU移植层。后者包含完整的内存保护),那么推荐使用函数xTaskCreateRestricted()来代替xTaskCreate()。在使用FreeRTOS-MPU的情况下,使用xTaskCreate()函数可以创建运行在特权模式或用户模式(见下面对函数参数uxPriority的描述)的任务。当运行在特权模式下,任务可以访问整个内存映射;当处于用户模式下,任务仅能访问自己的堆栈。无论在何种模式下,MPU都不会自动捕获堆栈溢出,因此标准的FreeRTOS堆栈溢出检测机制仍然会被用到。xTaskCreateRestricted()函数具有更大的灵活性。

参数描述

pvTaskCode:指针,指向任务函数的入口。任务永远不会返回(位于死循环内)。该参数类型TaskFunction_t定义在文件projdefs.h中,定义为:typedefvoid (*TaskFunction_t)( void * )。
pcName:任务描述。主要用于调试。字符串的最大长度由宏configMAX_TASK_NAME_LEN指定,该宏位于FreeRTOSConfig.h文件中。
usStackDepth:指定任务堆栈大小,能够支持的堆栈变量数量,而不是字节数。比如,在16位宽度的堆栈下,usStackDepth定义为100,则实际使用200字节堆栈存储空间。堆栈的宽度乘以深度必须不超过size_t类型所能表示的最大值。比如,size_t为16位,则可以表示的最大值是65535。
pvParameters:指针,当任务创建时,作为一个参数传递给任务。
uxPriority:任务的优先级。具有MPU支持的系统,可以通过置位优先级参数的portPRIVILEGE_BIT位,随意的在特权(系统)模式下创建任务。比如,创建一个优先级为2的特权任务,参数uxPriority可以设置为( 2 | portPRIVILEGE_BIT )。
pvCreatedTask:用于回传一个句柄(ID),创建任务后可以使用这个句柄引用任务。

返回值

  如果任务成功创建并加入就绪列表函数返回pdPASS,否则函数返回错误码。

用法举例

/* 创建任务. */
void vTaskCode( void * pvParameters )
{
    for( ;; )
    {
       /* 任务代码放在这里 */
    }
}
 
/* 创建任务函数 */
void vOtherFunction( void )
{
    static unsigned char ucParameterToPass;
    xTaskHandlexHandle;
 
     /* 创建任务,存储句柄。注:传递的参数ucParameterToPass必须和任务具有相同的生存周期,
        因此这里定义为静态变量。如果它只是一个自动变量,可能不会有太长的生存周期,因为
                中断和高优先级任务可能会用到它。 */
     xTaskCreate( vTaskCode, "NAME", STACK_SIZE,&ucParameterToPass, tskIDLE_PRIORITY, &xHandle );
 
     /* 使用句柄删除任务. */
    if( xHandle !=NULL )
    {
        vTaskDelete( xHandle );
    }
}

1.2 任务删除

函数描述

voidvTaskDelete( TaskHandle_t xTask );

  从RTOS内核管理器中删除一个任务。任务删除后将会从就绪、阻塞、暂停和事件列表中移除。在文件FreeRTOSConfig.h中,必须定义宏INCLUDE_vTaskDelete 为1,本函数才有效。

  注:被删除的任务,其在任务创建时由内核分配的存储空间,会由空闲任务释放。如果有应用程序调用xTaskDelete(),必须保证空闲任务获取一定的微控制器处理时间。任务代码自己分配的内存是不会自动释放的,因此删除任务前,应该将这些内存释放。

参数描述

xTask:被删除任务的句柄。为NULL表示删除当前任务。

2 FreeRTOS任务创建分析

BaseType_t xTaskCreate(
						TaskFunction_tp vTaskCode,
						const char * constpcName,
                        unsigned short usStackDepth,
                        void *pvParameters,
                        UBaseType_t uxPriority,
                        TaskHandle_t *pvCreatedTask
                        );

这个API函数的作用是创建新的任务并将它加入到任务就绪列表,函数参数含义为:

pvTaskCode:函数指针,指向任务函数的入口。任务永远不会返回(位于死循环内)。该参数类型TaskFunction_t定义在文件projdefs.h中,定义为:typedef void(*TaskFunction_t)( void * ),即参数为空指针类型并返回空类型。
pcName:任务描述。 主要用于调试。字符串的最大长度(包括字符串结束字符)由宏configMAX_TASK_NAME_LEN指定,该宏位于FreeRTOSConfig.h文件中。
usStackDepth:指定任务堆栈大小,能够支持的堆栈变量数量(堆栈深度),而不是字节数。 比如,在16位宽度的堆栈下,usStackDepth定义为100,则实际使用200字节堆栈存储空间。堆栈的宽度乘以深度必须不超过size_t类型所能表示的最大值。比如,size_t为16位,则可以表示堆栈的最大值是65535字节。这是因为堆栈在申请时是以字节为单位的,申请的字节数就是堆栈宽度乘以深度,如果这个乘积超出size_t所表示的范围,就会溢出,分配的堆栈空间也不是我们想要的。
pvParameters:指针,当任务创建时,作为一个参数传递给任务。
uxPriority:任务的优先级。 具有MPU支持的系统,可以通过置位优先级参数的portPRIVILEGE_BIT位,随意的在特权(系统)模式下创建任务。比如,创建一个优先级为2的特权任务,参数uxPriority可以设置为 ( 2 | portPRIVILEGE_BIT )。
pvCreatedTask:用于回传一个句柄(ID),创建任务后可以使用这个句柄引用任务。

虽然xTaskCreate()看上去很像函数,但其实是一个宏,真正被调用的函数是xTaskGenericCreate(),xTaskCreate()宏定义如下所示:

#define xTaskCreate( pvTaskCode, pcName, usStackDepth,pvParameters, uxPriority, pxCreatedTask )    \
      xTaskGenericCreate( ( pvTaskCode ),( pcName ), ( usStackDepth ), ( pvParameters ), ( uxPriority ), ( pxCreatedTask), ( NULL ), ( NULL ), ( NULL ) )

可以看到xTaskCreate比xTaskGenericCreate少了三个参数,在宏定义中,这三个参数被设置为NULL。这三个参数用于使用静态变量的方法分配堆栈、任务TCB空间以及设置MPU相关的参数。一般情况下,这三个参数是不使用的,所以任务创建宏xTaskCreate定义的时候,将这三个参数对用户隐藏了。接下来的章节中,为了方便,我们还是称xTaskCreate()为函数,虽然它是一个宏定义。

  上面我们提到了任务TCB(任务控制块),这是一个需要重点介绍的关键点。它用于存储任务的状态信息,包括任务运行时的环境。每个任务都有自己的任务TCB。任务TCB是一个相对比较大的数据结构,这也是情理之中的,因为与任务相关的代码占到整个FreeRTOS代码量的一半左右,这些代码大都与任务TCB相关,我们先来介绍一下任务TCB数据结构的定义:
typedef struct tskTaskControlBlock
{
    volatile StackType_t    *pxTopOfStack; /*当前堆栈的栈顶,必须位于结构体的第一项*/
 
    #if ( portUSING_MPU_WRAPPERS == 1 )
        xMPU_SETTINGS   xMPUSettings;      /*MPU设置,必须位于结构体的第二项*/
    #endif
 
    ListItem_t          xStateListItem; /*任务的状态列表项,以引用的方式表示任务的状态*/
    ListItem_t          xEventListItem;    /*事件列表项,用于将任务以引用的方式挂接到事件列表*/
    UBaseType_t         uxPriority;        /*保存任务优先级,0表示最低优先级*/
    StackType_t         *pxStack;           /*指向堆栈的起始位置*/
    char               pcTaskName[ configMAX_TASK_NAME_LEN ];/*任务名字*/
 
    #if ( portSTACK_GROWTH > 0 )
        StackType_t     *pxEndOfStack;     /*指向堆栈的尾部*/
    #endif
 
    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        UBaseType_t     uxCriticalNesting; /*保存临界区嵌套深度*/
    #endif
 
    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t     uxTCBNumber;       /*保存一个数值,每个任务都有唯一的值*/
        UBaseType_t     uxTaskNumber;      /*存储一个特定数值*/
    #endif
 
    #if ( configUSE_MUTEXES == 1 )
        UBaseType_t     uxBasePriority;    /*保存任务的基础优先级*/
        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;  /*记录任务在运行状态下执行的总时间*/
    #endif
 
    #if ( configUSE_NEWLIB_REENTRANT == 1 )
        /* 为任务分配一个Newlibreent结构体变量。Newlib是一个C库函数,并非FreeRTOS维护,FreeRTOS也不对使用结果负责。如果用户使用Newlib,必须熟知Newlib的细节*/
        struct _reent xNewLib_reent;
    #endif
 
    #if( configUSE_TASK_NOTIFICATIONS == 1 )
        volatile uint32_t ulNotifiedValue; /*与任务通知相关*/
        volatile uint8_t ucNotifyState;
    #endif
 
    #if( configSUPPORT_STATIC_ALLOCATION == 1 )
        uint8_t ucStaticAllocationFlags; /* 如果堆栈由静态数组分配,则设置为pdTRUE,如果堆栈是动态分配的,则设置为pdFALSE*/
    #endif
 
    #if( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
    #endif
 
} tskTCB;
 
typedef tskTCB TCB_t;

下面我们详细的介绍这个数据结构的主要成员:

  指针pxTopOfStack必须位于结构体的第一项,指向当前堆栈的栈顶,对于向下增长的堆栈,pxTopOfStack总是指向最后一个入栈的项目。

  如果使用MPU,xMPUSettings必须位于结构体的第二项,用于MPU设置。

  接下来是状态列表项xStateListItem和事件列表项xEventListItem,我们在上一章介绍列表和列表项的文章中提到过:列表被FreeRTOS调度器使用,用于跟踪任务,处于就绪、挂起、延时的任务,都会被挂接到各自的列表中。调度器就是通过把任务TCB中的状态列表项xStateListItem和事件列表项xEventListItem挂接到不同的列表中来实现上述过程的。在task.c中,定义了一些静态列表变量,其中有就绪、阻塞、挂起列表,例如当某个任务处于就绪态时,调度器就将这个任务TCB的xStateListItem列表项挂接到就绪列表。事件列表项也与之类似,当队列满的情况下,任务因入队操作而阻塞时,就会将事件列表项挂接到队列的等待入队列表上。

  uxPriority用于保存任务的优先级,0为最低优先级。任务创建时,指定的任务优先级就被保存到该变量中。

  指针pxStack指向堆栈的起始位置,任务创建时会分配指定数目的任务堆栈,申请堆栈内存函数返回的指针就被赋给该变量。很多刚接触FreeRTOS的人会分不清指针pxTopOfStack和pxStack的区别,这里简单说一下:pxTopOfStack指向当前堆栈栈顶,随着进栈出栈,pxTopOfStack指向的位置是会变化的;pxStack指向当前堆栈的起始位置,一经分配后,堆栈起始位置就固定了,不会被改变了。那么为什么需要pxStack变量呢,这是因为随着任务的运行,堆栈可能会溢出,在堆栈向下增长的系统中,这个变量可用于检查堆栈是否溢出;如果在堆栈向上增长的系统中,要想确定堆栈是否溢出,还需要另外一个变量pxEndOfStack来辅助诊断是否堆栈溢出,后面会讲到这个变量。

  字符数组pcTaskName用于保存任务的描述或名字,在任务创建时,由参数指定。名字的长度由宏configMAX_TASK_NAME_LEN(位于FreeRTOSConfig.h中)指定,包含字符串结束标志。

  如果堆栈向上生长(portSTACK_GROWTH > 0),指针pxEndOfStack指向堆栈尾部,用于检验堆栈是否溢出。

  变量uxCriticalNesting用于保存临界区嵌套深度,初始值为0。

  接下来两个变量用于可视化追踪,仅当宏configUSE_TRACE_FACILITY(位于FreeRTOSConfig.h中)为1时有效。变量uxTCBNumber存储一个数值,在创建任务时由内核自动分配数值(通常每创建一个任务,值增加1),每个任务的uxTCBNumber值都不同,主要用于调试。变量uxTaskNumber用于存储一个特定值,与变量uxTCBNumber不同,uxTaskNumber的数值不是由内核分配的,而是通过API函数vTaskSetTaskNumber()来设置的,数值由函数参数指定。

  如果使用互斥量(configUSE_MUTEXES == 1),任务优先级被临时提高时,变量uxBasePriority用来保存任务原来的优先级。

  变量ucStaticAllocationFlags也需要说明一下,我们前面说过任务创建API函数xTaskCreate()只能使用动态内存分配的方式创建任务堆栈和任务TCB,如果要使用静态变量实现任务堆栈和任务TCB就需要使用函数xTaskGenericCreate()来实现。如果任务堆栈或任务TCB由静态数组和静态变量实现,则将该变量设置为pdTRUE(任务堆栈空间由静态数组变量实现时为0x01,任务TCB由静态变量实现时为0x02,任务堆栈和任务TCB都由静态变量实现时为0x03),如果堆栈是动态分配的,则将该变量设置为pdFALSE。

  到这里任务TCB的数据结构就讲完了,下面我们用一个例子来讲述任务创建的过程,为方便起见,假设被创建的任务叫“任务A”,任务函数为vTask_A():
    TaskHandle_t xHandle;
    xTaskCreate(vTask_A,”Task A”,120,NULL,1,&xHandle);

这里创建了一个任务,任务优先级为1,由于硬件平台是32为架构,所以指定了120*4=480字节的任务堆栈,向任务函数vTask_A()传递的参数为空(NULL),任务句柄由变量xHandle保存。当这个语句执行后,任务A被创建并加入就绪任务列表,我们这章的主要目的,就是看看这个语句在执行过程中,发生了什么事情。

创建任务堆栈和任务TCB

  调用函数prvAllocateTCBAndStack()创建任务堆栈和任务TCB。有两种方式创建任务堆栈和任务TCB,一种是使用动态内存分配方法,这样当任务删除时,任务堆栈和任务控制块空间会被释放,可用于其它任务;另一种是使用静态变量来实现,在创建任务前定义好全局或者静态堆栈数组和任务控制块变量,在调用创建任务API函数时,将这两个变量以参数的形式传递给任务创建函数xTaskGenericCreate()。如果使用默认的xTaskCreate()创建任务函数,则使用动态内存分配,因为与静态内存分配有关的参数不可见(在本文一开始我们说过xTaskCreate()其实是一个带参数的宏定义,真正被执行的函数是xTaskGenericCreate(),参考宏xTaskCreate()的定义可以知道,xTaskCreate()对外隐藏了使用静态内存分配的参数,在调用xTaskGenericCreate()时,这些参数被设置为NULL)。

  任务堆栈成功分配后,经过对齐的堆栈起始地址被保存到任务TCB的pxStack字段。如果使能堆栈溢出检查或者使用可视化追踪功能,则使用固定值tskSTACK_FILL_BYTE(0xa5)填充堆栈。

  函数prvAllocateTCBAndStack()的源码去除断言和不常用的条件编译后如下所示:
static TCB_t *prvAllocateTCBAndStack( const uint16_t usStackDepth, StackType_t * const puxStackBuffer, TCB_t * const pxTaskBuffer )
{
TCB_t *pxNewTCB;
StackType_t *pxStack;
 
    /* 分配堆栈空间*/
    pxStack = ( StackType_t * ) pvPortMallocAligned( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ), puxStackBuffer );
    if( pxStack != NULL )
    {
        /* 分配TCB空间 */
        pxNewTCB = ( TCB_t * ) pvPortMallocAligned( sizeof( TCB_t ), pxTaskBuffer );
 
        if( pxNewTCB != NULL )
        {
            /* 将堆栈起始位置存入TCB*/
            pxNewTCB->pxStack = pxStack;
        }
        else
        {
            /* 如果TCB分配失败,释放之前申请的堆栈空间 */
            if( puxStackBuffer == NULL )
            {
                vPortFree( pxStack );
            }
        }
    }
    else
    {
        pxNewTCB = NULL;
    }
 
    if( pxNewTCB != NULL )
    {
        /* 如果需要,使用固定值填充堆栈 */
        #if( ( configCHECK_FOR_STACK_OVERFLOW> 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark== 1 ) )
        {
            /* 仅用于调试 */
            ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) usStackDepth * sizeof( StackType_t ) );
        }
        #endif
    }
 
    return pxNewTCB;
}

初始化任务TCB必要的字段

  调用函数prvInitialiseTCBVariables()初始化任务TCB必要的字段。在调用创建任务API函数xTaskCreate()时,参数pcName(任务描述)、uxPriority(任务优先级)都会被写入任务TCB相应的字段,TCB字段中的xStateListItem和xEventListItem列表项也会被初始化,初始化后的列表项如图2-1所示。在图2-1中,列表项xEventListItem的成员列表项值xItemValue被初始为4,这是因为我在应用中设置的最大优先级数目(configMAX_PRIORITIES)为5,而xEventListItem. xItemValue等于configMAX_PRIORITIES减去任务A的优先级(为1),即5-1=4。这一点很重要,在这里xItemValue不是直接保存任务优先级,而是保存优先级的补数,这意味着xItemValue的值越大,对应的任务优先级越小。FreeRTOS内核使用vListInsert函数(详细见高级篇第一章)将事件列表项插入到一个列表,这个函数根据xItemValue的值的大小顺序来进行插入操作。使用宏listGET_OWNER_OF_HEAD_ENTRY获得列表中的第一个列表项的xItemValue值总是最小,也就是优先级最高的任务!

在这里插入图片描述

初始化任务堆栈

  调用函数pxPortInitialiseStack()初始化任务堆栈,并将最新的栈顶指针赋值给任务TCB的pxTopOfStack字段。

  调用函数pxPortInitialiseStack()后,相当于执行了一次系统节拍时钟中断:将一些重要寄存器入栈。虽然任务还没开始执行,也并没有中断发生,但看上去就像寄存器已经被入栈了,并且部分堆栈值被修改成了我们需要的已知值。对于不同的硬件架构,入栈的寄存器也不相同,所以我们看到这个函数是由移植层提供的。对于Cortex-M3架构,需要依次入栈xPSR、PC、LR、R12、R3~R0、R11~R4,假设堆栈是向下生长的,初始化后的堆栈如图3-1所示。

  在图3-1中我们看到寄存器xPSR被初始为0x01000000,其中bit24被置1,表示使用Thumb指令;寄存器PC被初始化为任务函数指针vTask_A,这样当某次任务切换后,任务A获得CPU控制权,任务函数vTask_A被出栈到PC寄存器,之后会执行任务A的代码;LR寄存器初始化为函数指针prvTaskExitError,这是由移植层提供的一个出错处理函数。当中断发生时,LR被设置成中断要返回的地址,但是每个任务都是一个死循环,正常情况下不应该退出任务函数,所以一旦从任务函数退出,说明那里出错了,这个时候会调用寄存器LR指向的函数来处理这个错误,即prvTaskExitError;根据ATPCS(ARM-Thumb过程调用标准),我们知道子函数调用通过寄存器R0~R3传递参数,在文章的最开始讲xTaskCreate()函数时,提到这个函数有一个空指针类型的参数pvParameters,当任务创建时,它作为一个参数传递给任务,所以这个参数被保存到R0中,用来向任务传递参数。

  任务TCB结构体成员pxTopOfStack表示当前堆栈的栈顶,它指向最后一个入栈的项目,所以在图中它指向R4,TCB结构体另外一个成员pxStack表示堆栈的起始位置,所以在图中它指向堆栈的最开始处。

在这里插入图片描述

进入临界区

  调用taskENTER_CRITICAL()进入临界区,这是一个宏定义,最终进入临界区的代码由移植层提供。

当前任务数量增加1

  在tasks.c中 ,定义了一些静态私有变量,用来跟踪任务的数量或者状态等等,其中变量uxCurrentNumberOfTasks表示当前任务的总数量,每创建一个任务,这个变量都会增加1。

为第一次运行做必要的初始化

  如果这是第一个任务(uxCurrentNumberOfTasks等于1),则调用函数prvInitialiseTaskLists()初始化任务列表。FreeRTOS使用列表来跟踪任务,在tasks.c中,定义了静态类型的列表变量:
PRIVILEGED_DATAstatic List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*按照优先级排序的就绪态任务*/
PRIVILEGED_DATAstatic List_t xDelayedTaskList1;                        /*延时的任务 */
PRIVILEGED_DATAstatic List_t xDelayedTaskList2;                        /*延时的任务 */
PRIVILEGED_DATAstatic List_t xPendingReadyList;                        /*任务已就绪,但调度器被挂起 */
 
#if (INCLUDE_vTaskDelete == 1 )
    PRIVILEGED_DATA static List_t xTasksWaitingTermination;             /*任务已经被删除,但内存尚未释放*/
#endif
 
#if (INCLUDE_vTaskSuspend == 1 )
    PRIVILEGED_DATA static List_t xSuspendedTaskList;                   /*当前挂起的任务*/
#endif

更新当前正在运行的任务TCB指针

tasks.c中定义了一个任务TCB指针型变量:

  PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB= NULL;

  这是一个全局变量,在tasks.c中只定义了这一个全局变量。这个变量用来指向当前正在运行的任务TCB,我们需要多了解一下这个变量。FreeRTOS的核心是确保处于优先级最高的就绪任务获得CPU运行权。在下一章讲述任务切换时会知道,任务切换就是找到优先级最高的就绪任务,而找出的这个最高优先级任务的TCB,就被赋给变量pxCurrentTCB。

  如果调度器还没有准备好(程序刚开始运行时,可能会先创建几个任务,之后才会启动调度器),并且新创建的任务优先级大于变量pxCurrentTCB指向的任务优先级,则设置pxCurrentTCB指向当前新创建的任务TCB(确保pxCurrentTCB指向优先级最高的就绪任务)。
if( xSchedulerRunning == pdFALSE )
{
    if( pxCurrentTCB->uxPriority <= uxPriority )
    {
        pxCurrentTCB = pxNewTCB;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

将新创建的任务加入就绪列表数组

调用prvAddTaskToReadyList(pxNewTCB)将创建的任务TCB加入到就绪列表数组中,任务的优先级确定了加入到就绪列表数组的哪个下标。比如我们新创建的任务优先级为1,则这个任务被加入到列表pxReadyTasksLists[1]中。

   prvAddTaskToReadyList()其实是一个宏,由一系列语句组成,去除其中的跟踪宏外,这个宏定义如下所示:
#defineprvAddTaskToReadyList( pxTCB )                        \
    taskRECORD_READY_PRIORITY( ( pxTCB)->uxPriority );       \
    vListInsertEnd( &( pxReadyTasksLists[ (pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );

宏taskRECORD_READY_PRIORITY()用来更新变量uxTopReadyPriority,这个变量在tasks.c中定义为静态变量,记录处于就绪态的最高任务优先级。这个变量参与了FreeRTOS的最核心代码:确保处于优先级最高的就绪任务获得CPU运行权。它在这里参与如何最快的找到优先级最高的就绪任务。为了最快,不同的架构会各显神通,一些架构还有特殊指令可用,所以这个宏由移植层提供。

退出临界区

如果上面的步骤都正确执行,并且调度器也开始工作,则判断当前任务的优先级是否大于新创建的任务优先级。如果新创建的任务优先级更高,则调用taskYIELD_IF_USING_PREEMPTION()强制进行一次上下文切换,切换后,新创建的任务将获得CPU控制权,精简后的代码如下所示。

 if( xReturn == pdPASS )
    {
        if( xSchedulerRunning != pdFALSE )
        {
            /* 如果新创建的任务优先级大于当前任务优先级,则新创建的任务应该被立即执行。*/
            if(pxCurrentTCB->uxPriority < uxPriority )
            {
                taskYIELD_IF_USING_PREEMPTION();
            }
        }
    }

3 任务创建和删除(动态方法)

FreeRTOS任务动态创建和删除的API函数,即xTaskCreate和vTaskDelete()。
动态创建任务, 调用函数内部向系统申请创建新任务所需的内存,包括任务控制块和栈。 所以调用这个函数,在内存堆空间不足或者碎片话的情况下,可能创建新任务失败,需要判断函数执行后是否成功返回。 其源码解析如下所示。

BaseType_t xTaskCreate( 
    TaskFunction_t pxTaskCode,
    const char * const pcName,
    const uint16_t usStackDepth,
    void * const pvParameters,
    UBaseType_t uxPriority,
    TaskHandle_t * const pxCreatedTask )    
{
    TCB_t *pxNewTCB;
    BaseType_t xReturn;

    // 如果是向下增长的栈, 先申请栈内存再申请任务控制块内存
    // 可以避免栈溢出覆盖了自己任务控制块
    // 对应向上增长的则相反

    // 在旧版本 V8.0.0 中没有这么处理,统一先 TCB 后 Stack
    // 项目上碰到平台栈向下增长, 栈溢出错时候覆盖了自己的 TCB 
    // 导致调试的时候无法获取出错任务信息(比如任务名)
    #if( portSTACK_GROWTH > 0 )
    {
        // 申请任务控制块内存
        pxNewTCB = (TCB_t *)pvPortMalloc(sizeof(TCB_t));
        if( pxNewTCB != NULL )
        {
            // 申请栈内存, 返回地址设置任务中的栈指针
            pxNewTCB->pxStack = (StackType_t *)pvPortMalloc(
                (((size_t)usStackDepth) * sizeof(StackType_t)));

            if( pxNewTCB->pxStack == NULL )
            {
                // 栈内存申请失败, 释放前面申请的任务控制块内存
                vPortFree( pxNewTCB );
                pxNewTCB = NULL;
            }
        }
    }
    #else /*栈向下增长*/
    {
        StackType_t *pxStack;
        pxStack = (StackType_t *)pvPortMalloc(
            (((size_t)usStackDepth) * sizeof(StackType_t)));

        if( pxStack != NULL )
        {
            pxNewTCB = (TCB_t *)pvPortMalloc(sizeof(TCB_t));
            if( pxNewTCB != NULL )
            {
                pxNewTCB->pxStack = pxStack;
            }
            else
            {
                vPortFree( pxStack );
            }
        }
        else
        {
            pxNewTCB = NULL;
        }
    }
    #endif


    if( pxNewTCB != NULL )
    {
        // 成功申请所需内存 执行任务初始化操作

        #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
        {
            // 标志任务控制块和栈是动态申请
            // 删除任务系统会自动回收内存
            pxNewTCB->ucStaticallyAllocated = 
                tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
        }
        #endif /* configSUPPORT_STATIC_ALLOCATION */

        // 初始任务控制块
        prvInitialiseNewTask(pxTaskCode, pcName,
            (uint32_t)usStackDepth, pvParameters, 
            uxPriority, pxCreatedTask, pxNewTCB, NULL );

        // 将新任务插入到就绪链表  
        prvAddNewTaskToReadyList( pxNewTCB );
        xReturn = pdPASS;
    }
    else
    {
        // 创建任务失败,返回错误码
        xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
    }
    return xReturn;
}

3.1 任务创建示例(动态方法)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4 任务创建和删除(静态方法)

使用函数xTaskCreateStatic()来创建任务,即静态方法,任务的堆栈、任务控制块就需要由用户来指定了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

测试例程

/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2017 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

/* FreeRTOS kernel includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "timers.h"

/* Freescale includes. */
#include "fsl_device_registers.h"
#include "fsl_debug_console.h"
#include "board.h"

#include "pin_mux.h"
#include "clock_config.h"
/*******************************************************************************
 * Definitions
 ******************************************************************************/

/* Task priorities. */
#define hello_task_PRIORITY (configMAX_PRIORITIES - 1)
#define TASK1_PRIORITY  8 //任务优先级
#define TASK1_STK_SIZE  256 //任务堆栈大小
TaskHandle_t Task1_Handle; //任务句柄
static void vTask1(void *pvParameters); //Task1任务函数

#define TASK2_PRIORITY  11 //任务优先级
#define TASK2_STK_SIZE  256 //任务堆栈大小
TaskHandle_t Task2_Handle; //任务句柄
static void vTask2(void *pvParameters); //Task1任务函数



/*******************************************************************************
 * Prototypes
 ******************************************************************************/
static void hello_task(void *pvParameters); //hello_task任务函数
TaskHandle_t Hello_Task_Handle; //任务句柄



/*******************************************************************************
 * Code
 ******************************************************************************/
/*!
 * @brief Application entry point.
 */
int main(void)
{
    /* Init board hardware. */
    BOARD_ConfigMPU();
    BOARD_InitPins();
    BOARD_BootClockRUN();
    BOARD_InitDebugConsole();
    if (xTaskCreate(hello_task, "Hello_task", configMINIMAL_STACK_SIZE + 10, NULL, hello_task_PRIORITY, &Hello_Task_Handle) != pdPASS)
    {
        PRINTF("Hello_task creation failed!.\r\n");
        while (1)
            ;
    }
    if (xTaskCreate(vTask1, "vTask1", TASK1_STK_SIZE, NULL, TASK1_PRIORITY, &Task1_Handle) != pdPASS)
    {
        PRINTF("Task1 creation failed!.\r\n");
    }

    if (xTaskCreate(vTask2, "vTask2", TASK2_STK_SIZE, NULL, TASK2_PRIORITY, &Task2_Handle) != pdPASS)
    {
        PRINTF("Task1 creation failed!.\r\n");
    }

    vTaskStartScheduler();
    for (;;)
        ;
}

/*!
 * @brief Task responsible for printing of "Hello world." message.
 */
static void hello_task(void *pvParameters)
{
    static bool bFlag = false;

    for (;;)
    {
        PRINTF("hello_task running.\r\n");
        vTaskDelay(2000);  
        if(false == bFlag)
        {
					PRINTF("hello_task will suspend.\r\n");
            vTaskSuspend(NULL);
            bFlag = true;
        }
    }
}

static void vTask1(void *pvParameters) //Task1任务函数
{
    static float value = 10.0022;

    for (;;)
    {
        PRINTF("vTask1 is running.\r\n");
        printf("%.4f\n",value);
        value = value * 1.01;
        vTaskDelay(100);
    }

}


static void vTask2(void *pvParameters) //Task1任务函数
{
    static int value = 1;

    for (;;)
    {
        PRINTF("vTask2 is running.\r\n");
        printf("%d\n",value);
        value++;
        if( value > 60)
        {
            vTaskResume(Hello_Task_Handle); //恢复Hello_Task
        }
        vTaskDelay(100);
    }


}



5 任务状态

在这里插入图片描述
系统运行过程,任务可能处于以下各种状态,各个状态之间切换的关系如上图所示。

  • Running
    运行状态, 当前正在执行,占有处理器的任务
  • Ready
    就绪状态,准备被运行的任务,没有被挂起和阻塞,但不是当前正在执行的任务,等待更高优先级任务或者同等级任务时间片结束释放处理器
  • Blocked
    阻塞状态,任务在等待一个事件而进入阻塞状态,比如延时、获取信号量等
  • Suspended
    挂起状态,任务由于调用 vTaskSuspend() 而被挂起不能被执行, 直到调用 xTaskResume() 重新恢复

FreeRTOS 任务优先级说明

freeRTOS任务优先级与Unix进程优先级不同

FreeRTOS任务优先:任务优先级数值越小,任务优先级越低。
Unix任务优先: 进程优先级数值越小,进程优先级越高。

下面对 FreeRTOS 优先级相关的几个重要知识点进行下说明,这些知识点在以后的使用中务必要掌握牢固。

 FreeRTOS 中任务的最高优先级是通过 FreeRTOSConfig.h 文件中的 configMAX_PRIORITIES 进行
配置的,用户实际可以使用的优先级范围是 0 到 configMAX_PRIORITIES – 1。 比如我们配置此宏定
义为 5,那么用户可以使用的优先级号是 0,1,2,3,4,不包含 5,对于这一点,初学者要特别的注意。
用户配置任务的优先级数值越小,那么此任务的优先级越低,空闲任务的优先级是 0。
建议用户配置宏定义 configMAX_PRIORITIES 的最大值不要超过 32,即用户任务可以使用的优先级范围是0到31。
因为对于CM内核的移植文件,用户任务的优先级不是大于等于32的话, portmacro.h文件中的宏定义configUSE_PORT_OPTIMISED_TASK_SELECTION会优化优先级列表中要执行的最高优先级任务的获取算法(对于 CM 内核的移植文件,此宏定义默认是使能的,当然,用户也可以在FreeRTOSConfig.h 文件中进行配置)。 相比通用的最高优先级任务获取算法,这两种方式的对比如下:
   通用方式,没有优化—配置宏定义 configUSE_PORT_OPTIMISED_TASK_SELECTION 为 0:
   所有平台的移植文件都可以配置为 0,因为这是通用方式。
   纯 C 编写,比专用方式效率低。
   可用的优先级数量不限制。
   专用方式,进行优化—配置宏定义 configUSE_PORT_OPTIMISED_TASK_SELECTION 为为 1:
   部分平台支持。
   这些平台架构有专用的汇编指令,比如 CLZ(Count Leading Zeros)指令,通过这些指令可以加速算法执行速度。
   比通用方式高效。
   有最大优先级数限制,通常限制为 32 个。
 如果用户在 FreeRTOSConfig.h 文件中配置宏定义 configUSE_TIME_SLICING 为 1,或者没有配置

此宏定义,时间片调度都是使能的。 另外,只要芯片资源允许,可以配置任意多个同优先级任务。
(备注:没有定义 configUSE_TIME_SLICING,也能使用时间片调度是因为此宏定义默认已经在
FreeRTOS.h 文件中使能)
 FreeRTOS 中处于运行状态的任务永远是当前能够运行的最高优先级任务。 下一章节讲解调度器,大
家会对这个知识点有一个全面的认识。

任务优先级分配方案
对于初学者,有时候会纠结任务优先级设置为多少合适,因为任务优先级设置多少是没有标准的。对
于这个问题,我们这里为大家推荐一个标准,任务优先级设置推荐方式如下图所示:
在这里插入图片描述
IRQ 任务:IRQ 任务是指通过中断服务程序进行触发的任务,此类任务应该设置为所有任务里面优先级最高的。

 高优先级后台任务:比如按键检测,触摸检测,USB 消息处理,串口消息处理等,都可以归为这一类任务。

 低优先级的时间片调度任务:比如 emWin 的界面显示,LED 数码管的显示等不需要实时执行的都可以归为这一类任务。 实际应用中用户不必拘泥于将这些任务都设置为优先级 1 的同优先级任务,可以设置多个优先级,只需注意这类任务不需要高实时性。
 空闲任务:空闲任务是系统任务。
 特别注意:IRQ 任务和高优先级任务必须设置为阻塞式(调用消息等待或者延迟等函数即可),只有
这样,高优先级任务才会释放 CPU 的使用权,,从而低优先级任务才有机会得到执行。
这里的优先级分配方案是我们推荐的一种方式,实际项目也可以不采用这种方法。 调试出适合项目需求的才是最好的。

中断优先级和任务优先级区别
部分初学者也容易在这两个概念上面出现问题。 简单的说,这两个之间没有任何关系,不管中断的优先级是多少,中断的优先级永远高于任何任务的优先级,即任务在执行的过程中,中断来了就开始执行中断服务程序。
另外对于 STM32F103,F407 和 F429 来说,中断优先级的数值越小,优先级越高。 而 FreeRTOS的任务优先级是,任务优先级数值越小,任务优先级越低。

原文链接:https://blog.csdn.net/zhzht19861011/article/details/50371956
正点原子教程
https://blog.csdn.net/sty124578/article/details/80534276

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值