FreeRTOS学习-任务管理(Task管理)(2)

0. 说明

FreeRTOS的任务管理的内容由2篇博文组成,第一篇FreeRTOS学习-任务管理(Task管理)(1)介绍了FreeRTOS的任务管理的重要概念和外部特性以及对这些特性的实现的概要说明,本篇则介绍任务管理实现的细节,包括关键数据结构和内部函数的实现。由于本文涉及许多代码细节,许多都是枯燥无味的,对于关注FreeRTOS的应用开发的读者而言,阅读第一篇博文足以。对于那些闲来无事,喜欢死磕实现的读者,可以选择性阅读本文。由于本人功力有限,如有阐述错误之处,还请大佬们热心指出,小弟在此拜谢!

本文将分别从对外接口(外特性)和内涵(内部实现)进行剖析。对外接口的部分,不包含前面已经提及的内容,主要是介绍内核内部使用的接口,所谓对外,是从任务管理模块的角度而言的,这些接口会被例如队列管理、任务通知等其他内核模块使用。

1. 任务管理的接口

1.1. 任务管理的数据结构接口

1.1.1. 任务句柄

在FreeRTOS中,句柄通常可以简单地理解为一个指向内核对象的指针,其目的是为了让用户能够方便地指代具体的对象而暴露其内部细节。用户通过句柄以让内核找到对应的对象。而任务句柄实际上就是指向某个任务对象的指针。

任务句柄的定义如下:

typedef void * TaskHandle_t;

通过任务创建API,用户可以获取到新创建的任务的任务句柄。这个任务句柄可以在调用其他任务管理相关的API时使用,用于指示API操作的任务对象。

1.1.2. 任务状态枚举

顾名思义,该类型表示任务所有可能的状态。

定义如下:

/* Task states returned by eTaskGetState. */
typedef enum
{
    eRunning = 0,   /* A task is querying the state of itself, so must be running. */
    eReady,         /* The task being queried is in a read or pending ready list. */
    eBlocked,       /* The task being queried is in the Blocked state. */
    eSuspended,     /* The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
    eDeleted,       /* The task being queried has been deleted, but its TCB has not yet been freed. */
    eInvalid        /* Used as an 'invalid state' value. */
} eTaskState;

这些状态和之前介绍的任务状态是一一匹配的。

1.1.3. 通知动作的种类枚举(NotifyAction)

Notify是一种IPC,或称Task间通信机制,其功能类似于Linux的Signal,用于给某个任务发送信号,并设置相应的行为来控制任务通知携带的通知消息(notification value)。该枚举申明了所有可能的行为种类。

定义如下:

/* Actions that can be performed when vTaskNotify() is called. */
typedef enum
{
    eNoAction = 0,              /* Notify the task without updating its notify value. */
    eSetBits,                   /* Set bits in the task's notification value. */
    eIncrement,                 /* Increment the task's notification value. */
    eSetValueWithOverwrite,     /* Set the task's notification value to a specific value even if the previous value has not yet been read by the task. */
    eSetValueWithoutOverwrite   /* Set the task's notification value if the previous value has been read by the task. */
} eNotifyAction;

这部分的内容涉及到任务通知的相关知识,会在任务通知中详细介绍。

1.1.4. Timeout结构体

仅在实现队列时内核内部使用,这是记录超时所用的数据接口。队列的实现会在队列管理中介绍。

定义如下:

/*
 * Used internally only.
 */
typedef struct xTIME_OUT
{
    BaseType_t xOverflowCount;
    TickType_t xTimeOnEntering;
} TimeOut_t;
1.1.5. 任务状态信息结构体

该结构体包含了某个任务当前的重要状态信息,是调用uxTaskGetSystemState()的返回类型。

定义如下:

/* 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 http://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. */
    uint16_t 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;

PS: 所有lint !exxx是一个静态代码检查的warning代号,注释是为了说明这个定义是为了修复这个warning而进行修改的。

1.1.6. 睡眠模式状态枚举(用于Tickless idle模式)

该枚举通常通过调用eTaskConfirmSleepModeStatus()获取,用于说明系统所采用的任务睡眠模式。

定义如下:

/* Possible return values for eTaskConfirmSleepModeStatus(). */
typedef enum
{
    eAbortSleep = 0,        /* A task has been made ready or a context switch pended since portSUPPORESS_TICKS_AND_SLEEP() was called - abort entering a sleep mode. */
    eStandardSleep,         /* Enter a sleep mode that will not last any longer than the expected idle time. */
    eNoTasksWaitingTimeout  /* No tasks are waiting for a timeout so it is safe to enter a sleep mode that can only be exited by an external interrupt. */
} eSleepModeStatus;

1.2. 任务管理的接口宏

1.2.1. 任务管理的常量

FreeRTOS的版本信息:

#define tskKERNEL_VERSION_NUMBER    "V9.0.0"
#define tskKERNEL_VERSION_MAJOR     9
#define tskKERNEL_VERSION_MINOR     0
#define tskKERNEL_VERSION_BUILD     0

可以看到,FreeRTOS的版本信息有三个部分组成,分别是:

  • 主版本号
  • 次版本号
  • 构建版本号

Idle任务的优先级定义:

#define tskIDLE_PRIORITY            ( ( UBaseType_t ) 0U )

它定义了Idle任务的优先级值,从定义可以看出,Idle的优先级就是最低优先级。

xTaskGetSchedulerState()的返回值:

/* Definitions returned by xTaskGetSchedulerState().  taskSCHEDULER_SUSPENDED is
0 to generate more optimal code when configASSERT() is defined as the constant
is used in assert() statements. */
#define taskSCHEDULER_SUSPENDED     ( ( BaseType_t ) 0 )
#define taskSCHEDULER_NOT_STARTED   ( ( BaseType_t ) 1 )
#define taskSCHEDULER_RUNNING       ( ( BaseType_t ) 2 )

从宏的命名不难理解其含义。

1.2.2. 任务管理的宏函数

宏函数的定义和解释如下:

#define taskYIELD()                 portYIELD() // 强制发起一次任务调度

/* 进入临界区,在临界区内禁止所有中断 */
#define taskENTER_CRITICAL()        portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()

/* 退出临界区,开启中断 */
#define taskEXIT_CRITICAL()         portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

/* 中断开关控制 */
#define taskDISABLE_INTERRUPTS()    portDISABLE_INTERRUPTS()
#define taskENABLE_INTERRUPTS()     portENABLE_INTERRUPTS()

可以看到,这些宏最终都指向了port的实现,意味着他们与体系结构强相关。

2. 任务管理的内涵

2.1. 任务管理的依赖头文件

标准C库:

#include <stdlib.h>
#include <string.h>

#if (configUSE_STATS_FORMATTING_FUNCTIONS == 1)
    #include <stdio.h>
#endif

FreeRTOS库:

#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include "StackMacros.h"

timers.h提供了软件定时器的接口。

StackMacros.h提供了栈溢出检查的相关接口。

这里需要详细介绍一下StackMacros.h

宏原型和栈溢出处理函数如下:需要设置configCHECK_FOR_STACK_OVERFLOW = 1

#define taskCHECK_FOR_STACK_OVERFLOW()

void vApplicationStackOverflowHook( TaskHandle_t xTask, char *pcTaskName );

当发生上下文切换时,调度器会调用该宏检测是否发生了栈溢出。若检测到发生栈溢出后,则调用相应的钩子函数vApplicationStackOverflowHook()。具体的实现会根据栈的生长方向不同、以及检查的粒度不同而进行调整:

  • configCHECK_FOR_STACK_OVERFLOW == 1 && portSTACK_GROWTH < 0:向下增长的栈,configCHECK_FOR_STACK_OVERFLOW配置为1时,则判断栈顶指针是不是超出了栈的底。
  • configCHECK_FOR_STACK_OVERFLOW > 1 && portSTACK_GROWTH < 0:向下增长的栈,configCHECK_FOR_STACK_OVERFLOW配置为>1时,需要检查栈低之上的4个uint32_t是否被踩。因为在开启了检测宏后,创建Task时会初始化stack为magic value(0xa5a5a5a5),因此如果栈低之上的4个uint32_t的值不为magic value则认为发生了栈溢出。
  • configCHECK_FOR_STACK_OVERFLOW == 1 && portSTACK_GROWTH > 0:向上增长的栈,与配置为1的检查相反。
  • configCHECK_FOR_STACK_OVERFLOW > 1 && portSTACK_GROWTH > 0:向上增长的栈,与配置>1的检查相反。

2.2. 任务管理的私有数据接口

2.2.1. 任务队列

FreeRTOS的任务队列与其任务状态有关。主要分为以下几种类型:

  • 带优先级的Ready任务队列:从实现可以看出,每个优先级都有一个Ready队列。因此,为了节省内存空间,configMAX_PRIORITIES越小越好。
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
  • Delayed任务队列:或称Blocked任务队列,从实现可以看到,这里有两个Delayed任务队列,其中一个是另一个的备份,针对tick count溢出的情况。当发生tick count溢出,则会发生切换。
PRIVILEGED_DATA static List_t xDelayedTaskList1;
PRIVILEGED_DATA static List_t xDelayedTaskLIst2;
RPIVILEGED_DATA static List_t * volatile pxDelayedTaskList; /* Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList; /* Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
  • PendingReady任务队列:当调度器挂起时,该队列暂时存储被挂起的Ready任务。一旦调度器恢复,队列中的内容将会被放到对应的Ready队列中。需要注意的是,它被用作一种特殊的事件队列(即由TCB的xEventListItem引用)。
PRIVILEGED_DATA static List_t xPendingReadyList;
  • Suspended任务队列:只有在INCLUDE_vTaskSuspend = 1时,才会被定义的队列。存放被挂起的任务。
PRIVILEGED_DATA static List_t xSuspendedTaskList;
  • Deleted任务队列:只有在INCLUDE_vTaskDelete = 1时,才会被定义的队列。存储那些被终止,但未被回收的任务。
PRIVILEGED_DATA static List_t xTasksWaitingTermination;
PRIVILEGED_DATA static volatile UBaseType_t uxDeletedTasksWaitingCleanUp = ( UBaseType_t ) 0U; /* Indicate whether there are tasks to be cleaned up */
2.2.2. 任务控制块(TCB)

提及任务管理的数据结构,最重要的莫过于任务控制块(Task Control Block,TCB)。定义如下:

typedef struct tskTaskControlBlock
{
    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 )
        xPMU_SETTINGS   xMPUSettings;   /* The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT */
    #endif

    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. */

    #if ( portSTACK_GROWTH > 0 )
        StackType_t *pxEndOfStack;      /* Points to the end of the stack on architectures where the stack grows up from low memory. */
    #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[ confgNUM_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 )
        struct _reent xNewLib_reent;    /* This function is not maintained by FreeRTOS maintainers. */
    #endif

    #if ( configUSE_TASK_NOTIFICATIONS == 1 )
        volatile uint32_t ulNotifiedValue;
        volatile uint8_t ucNotifyState;
    #endif

    #if (tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
        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
} tskTCB;

typedef tskTCB TCB_t;

PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL:

由于TCB对于任务管理来说,实在太过重要。因此,这里需要重点介绍一下其中的每一个成员。

先来看看第一个登场的家伙pxTopOfStack。由于前面都没有介绍到他的类型StackType_t,这里需要补充一下。它被定义在portmacro.h头文件中,可想而知,这是一个与平台相关的类型定义。我们先看一下ARM-CA9的定义:

#define portSTACK_TYPE  uint32_t

typedef portSTACK_TYPE  StackType_t;

再来看一下ARM-CA53的定义:

#define portSTACK_TYPE  size_t

typedef portSTACK_TYPE  StackType_t;

可以看出,两种定义都是由一个宏定义和一个类型定义组成。不同的是,宏定义的内容不相同。其中,ARM-CA9的定义是一个32位的整型数,而ARM-CA53的则是一个size_t类型,C语言规范将这个类型定义为机器字长。所以,StackType_t的语义就是机器字长。而由此可以推测出,栈每次移动的长度即为机器字长。

从概念上而论,进程或任务是对处理器、内存和存储器的抽象。其中,处理器的寄存器现场是对处理器的抽象,在FreeRTOS中,TCB中并没有直接存储他们,而是将其存放于任务的栈内存中,通过pxTopOfStack进行索引;通常,虚拟内存是对系统内存的抽象,但由于FreeRTOS没有实现虚拟内存,只采用寄存器现场中的堆栈指针来索引,故TCB中无法直接体现这一抽象;由于FreeRTOS没有文件系统,所以这里无法体现对存储器的抽象。

xMPUSettings,从定义可以看出,这是一个与MPU功能相关的成员,它记录了MPU的寄存器现场。必须放在第二位。// TODO:MPU的功能有待研究。

xStateListItem,还记得之前提到的链表项与所有者的互联吧,这个成员便是Owner端指向链表项的指针,它记录了该任务所处的任务队列。这是典型的空间换时间的策略,即可以以O(1)的时间复杂度找到该任务所在的队列,便于任务在多个任务队列之间切换。

xEventListItem,与xStaetListItem类似。但这个指针指向了事件队列的链表项。需要注意的是,PendingReady任务队列也被认为是一种特殊的事件队列。

uxPriority记录了任务的实际优先级。创建任务时需要指定。如果使能了互斥锁功能,该成员表示的便是优先级继承发生后的实际优先级。

pxStack记录了栈的起始位置,对于向下增长的栈来说,它指向栈的最低地址。

pcTaskName是任务的名字字符串。

pxEndOfStack指向栈的最高有效地址。在FreeRTOSv10后,如果设置了configRECORD_STACK_HIGH_ADDRESS = 1,向下生长的栈也使用了这个成员

uxCriticalNesting记录临界区的嵌套深度,通常它在与平台相关的代码中内维护。只有当平台相关的代码没有实现内部维护时,才会使用该成员。

以下两个成员被用于实现第三方profiling:

  • uxTCBNumber记录了该TCB的编号,每次创建TCB时都会自增。为调试所用。它使得调试器能够感知任务何时被删除和重新创建。
  • uxTaskNumber记录了该Task的编号,为第三方的profiling程序所用。

若开启了互斥锁功能,则需要用到以下两个成员:

  • uxBasePriority记录了该任务本身的优先级,即不考虑优先级继承情况下的基础优先级。它可以被vTaskPrioritySet()改变。
  • uxMutexesHeld记录该任务持有的锁的数量。

pxTaskTag用于支持Application Task功能,记录了TaskHookFunction_t,可通过调用vTaskSetApplicationTaskTag()来修改;通过xTaskGetApplicationTaskTag()获取;通过xTaskCallApplicationTaskHook()进行调用。

pvThreadLocalStoragePointers用于支持线程局部存储功能。可被vTaskSetThreadLocalStoragePointer()修改;通过pvTaskGetThreadLocalStoragePointer()获取。这是可与外部进行交互的本地数据。// TODO:具体用途还需要进一步研究。

ulRunTimeCounter用于记录当前任务处于Running状态的总时间,也即任务占用CPU的时间总合。用于实现运行时统计的功能。

xNewLib_reent用于支持NewLib标准C库,只有需要使用到NewLib的才会使用该成员。这里不展开介绍。

与Task Notification相关的成员:

  • ulNotifiedValue记录了Notification的值。

  • ucNotifyState记录了Notify的状态,可能的取值如下:

    • taskNOT_WAITING_NOTIFICATION
    • taskWAITING_NOTIFICATION
    • taskNOTIFICATION_RECEIVED

ucStaticallyAllocated用于标识TCB和栈是否为静态申请的,如果为pdTRUE,则不会执行Free操作。

ucDelayAborted用于支持xTaskAbortDelay,即中止任务的睡眠状态。

可以看到,TCB记录了任务的核心信息,所有的任务管理都是围绕它展开的。在系统运行时,会记录当前正在执行的任务的TCB,即pxCurrentTCB。而对于用户而言,任务由任务句柄TaskHandle_t表示,它是一个指针,确切的说,是指向该任务的TCB的指针。因此系统调用可以方便的从任务句柄中获取对应的TCB,进而进行相应的操作。从TCB中,我们还可以得出以下一些比较重要的结论:

  • FreeRTOS的任务之间没有父子关系,因此任务之间不会形成树状关系。它们被分散在各个任务队列中。
  • TCB中没有记录任务的当前所处状态,系统是通过TCB所在队列(xStateListItem成员),或pxCurrentTCB来确定当前任务所处状态的。
2.2.3. 其他关键定义
PRIVILEGED_DATA static volatile UBaseType_t uxCurrentNumberOfTasks  = ( UBaseType_t ) 0U;
PRIVILEGED_DATA static volatile TickType_t xTickCount               = ( TickType_t ) 0U;
PRIVILEGED_DATA static volatile UBaseType_t uxTopReadyPriority      = tskIDLE_PRIORITY;
PRIVILEGED_DATA static volatile BaseType_t xSchedulerRunning        = pdFALSE;
PRIVILEGED_DATA static volatile UBaseType_t uxPendedTicks           = ( UBaseType_t ) 0U;
PRIVILEGED_DATA static volatile BaseType_t xYieldPending            = pdFALSE;
PRIVILEGED_DATA static volatile BaseType_t xNumOfOverflows          = ( BaseType_t ) 0;
PRIVILEGED_DATA static UBaseType_t uxTaskNumber                     = ( UBaseType_t ) 0U;
PRIVILEGED_DATA static volatile TickType_t xNextTaskUnblockTime     = ( TickType_t ) 0U;
PRIVILEGED_DATA static TaskHandle_t xIdleTaskHandle                 = NULL;
PRIVILEGED_DATA static volatile UBaseType_t uxSchedulerSuspended    = ( UBaseType_t ) pdFALSE;

#if ( configGENERATE_RUN_TIME_STATS = 1 )
    PRIVILEGED_DATA static uint32_t ulTaskSwitchedInTime = 0UL;
    PRIVILEGED_DATA static uint32_t ulTotalRunTime = 0UL;
#endif

以上定义记录了任务管理的一些状态信息。

  • uxCurrentNumberOfTasks记录了当前任务的总数。
  • xTickCount记录了当前的Tick数。
  • uxTopReadyPriority记录了目前处于Ready状态的任务中,最高的优先级值。
  • xSchedulerRunning记录调度器当前是否已经开启(或成为初始化)。
  • uxSchedulerSuspended:记录了当前调度器是否被挂起(或称为加锁),这与xSchedulerRunning是完全不同的概念,只有在调度器被开启的情况下,才有可能被挂起。
  • uxPendedTicks:记录调度器挂起的时长,以Tick为单位。
  • xYieldPending:记录当前是不是存在被挂起的调度,如果存在,那便会在合适的时候进行任务调度。
  • xNumOfOverflows:记录当前一共发生了几次系统Tick溢出。
  • uxTaskNumber:供第三方调试工具使用。
  • xNextTaskUnblockTime:记录了即将需要唤醒的任务的唤醒时刻,以Tick为单位。
  • xIdleTaskHandle:记录了Idle服务的任务句柄。
  • ulTaskSwitchedInTime:记录了上一次任务切入时的时间(以调试定时器的时钟作为单位)。
  • ulTotalRunTime:记录了总的执行时间(以调试定时器的时钟作为单位)。

2.3. 任务管理的私有函数

首先介绍两个钩子函数:

#if ( configCHECK_FOR_STACK_OVERFLOW > 0 )
    extern void vApplicationStackOverflowHook( TaskHandle_t xTask, char *pcTaskName )
#endif

#if ( configUSE_TICK_HOOK > 0)
    extern void vApplicationTickHook( void );
#endif

其中vApplicationStackOverflowHook()是在栈溢出异常发生时调用的应用程序级别的钩子函数。关于栈溢出的详情请查看任务管理依赖的头文件

vApplicationTickHook():发次发生tick变化是调用的钩子函数。

FreeRTOSv10后引入用户自定义的初始化流程

#if ( configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H == 1 )
    #include "freertos_tasks_c_addtions.h"

    static void freertos_tasks_c_addtions_init( void )
    {
        #ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
            FREERTOS_TASK_C_ADDITIONS_INIT();
        #endif
    }
#endif

2.4. 任务管理的私有宏

2.4.1. 任务管理的私有控制宏

用于MPU相关功能的宏:

#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE

该宏是为了防止task.h重定义MPU Wrapper中的API。

栈溢出功能相关的宏:

#if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1) )
    #define tskSET_NEW_STACKS_TO_KNOWN_VALUE 1
#else
    #define tskSET_NEW_STACKS_TO_KNOWN_VALUE 0
#endif

对于一些内核调试器,他们需要获取私有函数,因此需要有一个static关键字控制宏:

#ifdef portREMOVE_STATIC_QUALIFIER
    #define static
#endif

若定义了portREMOVE_STATIC_QUALIFIER,则把所有申明为static的函数和变量暴露为全局符号。

Event队列的Item value控制宏:

#if ( configUSE_16_BIT_TICKS == 1 )
    #define taskEVENT_LIST_ITEM_VALUE_IN_USE    0x8000U
#else
    #define taskEVENT_LIST_ITEM_VALUE_IN_USE    0x80000000UL
#endif

在FreeRTOS的事件队列中,其xItemValue通常用于记录Owner Task的优先级。然而,这个值有时也会被用作其他用途。这个位的定义用于告诉调度器不应该修改优先级,而应该由使用该值作其他用途的模块来负责还原为优先级值。

2.4.2. 任务管理的私有常量

NotifyState可能取值的状态常量:用于任务通知功能

#define taskNOT_WAITING_NOTIFICATION    ( ( uint8_t ) 0 )
#define taskWAITING_NOTIFICATION        ( ( uint8_t ) 1 )
#define taskNOTIFICATION_RECEIVED       ( ( uint8_t ) 2 )

用于初始化栈的填充内容:

#define tskSTACK_FILE_BYTE  ( 0xa5U )

由于FreeRTOS支持同时动态和静态创建Task,因此需要记录创建方式,使得回收资源时能正确的执行。

#define tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE   ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
#define tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB      ( ( uint8_t ) 0 )
#define tskSTATICALLY_ALLOCATED_STACK_ONLY          ( ( uint8_t ) 1 )
#define tskSTATICALLY_ALLOCATED_STACK_AND_TCB       ( ( uint8_t ) 2 )

需要注意的是,若tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE发生变化,则StaticTask_t也需要进行相应的更新。

任务状态对应的符号:

#define tskBLOCKED_CHAR     ( 'B' )
#define tskREADY_CHAR       ( 'R' )
#define tskDELETED_CHAR     ( 'D' )
#define tskSUSPENDED_CHAR   ( 'S' )

这些字符用于vTaskList()内打印任务状态。

Idle task的名称:

#ifndef configIDEL_TASK_NAME
    #define configIDEL_TASK_NAME    "IDEL"
#endif
2.4.3. 任务管理的私有宏函数

抢占式调度接口:需要设置configUSE_PREEMPTION = 1

#define taskYIELD_IF_USING_PREEMPTION() portYIELD_WITHIN_API()

与调度相关的宏函数,修改当前所有处于Ready的任务中的最高优先级和选择最高优先级的Task。

#define taskRECORD_READY_PRIORITY( uxPriority ) ...
#define taskSELECT_HIGHEST_PRIORITY_TASK()      ...

根据configUSE_PORT_OPTIMISED_TASK_SELECTION的值不同,存在两种实现。

  • 0:表示不使用体系结构优化的实现,即采用最通用的实现。taskRECORD_READY_PRIORITY()的职责是将uxTopReadyPriority更新为传入的优先级值。而taskSELECT_HIGHEST_PRIORITY_TASK()则是从当前最高优先级的任务队列中找到一个最高优先级的任务。

    #define taskRECORD_READY_PRIORITY( uxPriority )         \
    {                                                       \
        if ( ( uxPriority ) > uxTopReadyPriority )          \
        {                                                   \
            uxTopReadyPriority = ( uxPriority );            \
        }                                                   \
    }
    
    #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( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );\
        uxTopReadyPriority = uxTopPriority;                                     \
    }                                                                           \
    
  • 1:表示使用体系结构优化的实现。在ARM中使用了bitmap的方式记录优先级。Bitmap的方式不需要通过轮询以查找最高优先级的Task,所以效率会比较高。这里使用了GCC的内置函数__builtin_clz(),它将返回从左边算起,第一个"1"之前的0的个数。所以用31减去这个数就是当前的优先级。这里还提供了taskRESET_READY_PRIORITY()接口用于将对应优先级的从位图中清除,只有当该优先级Ready任务队列为空时会清除。

    /* Store/clear the ready priorities in a bit map. */
    #define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )
    #define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )
    
    #define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __builtin_clz( uxReadyPriorities ) )
    
    #define taskRESET_READY_PRIORITY( uxPriority )                              \
    {                                                                           \
        if ( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ) \
        {                                                                       \
            portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
        }                                                                       \
    }                                                                           \
    

Delayed任务队列切换宏:

#define taskSWITCH_DELAYED_LISTS()  ...

当切换时,pxDelayedTaskList指向的任务队列一定是空的。然后,将pxDelayedTaskListpxOverflowDelayedTaskList指针交换,并将xNumOfOverflows自增1。最后,需要重新设置xNextTaskUnblockTime为最近将要超时的时间。

将任务添加到Ready任务队列宏:

#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 )                            \

traceMOVED_TASK_TO_READY_STATE()tracePOST_MOVED_TASK_TO_READY_STATE()是调试用的钩子宏。

将任务句柄转换成TCB的宏:

#define prvGetTCBFromHandle( pxHandle ) ( ( ( pxHandle ) == NULL ) ? ( TCB_t * ) pxCurrentTCB : ( TCB_t * ) ( pxHandle ) )

实现可以看出,其思想类似Linux,NULL代表了正在运行的TCB,即pxCurrentTCB。否则,直接将传入的句柄强制类型转换成为TCB_t

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ESP32使用的是FreeRTOS,它是一个适用于多任务的小型嵌入式系统,为裸机程序提供多任务功能的库。每一种编译器和处理器的组合被称为FreeRTOS端口。在FreeRTOS中,任务管理是非常重要的,通过xTaskCreate函数可以创建任务并进行任务管理。该函数的原型是`BaseType_t xTaskCreate(TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pvCreatedTask)`。其中,pvTaskCode是任务函数,pcName是任务名称,usStackDepth是任务堆栈大小,pvParameters是传递给任务函数的参数,uxPriority是任务优先级,pvCreatedTask任务句柄。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【ESP32学习-3】FreeRTOS](https://blog.csdn.net/csdndulala/article/details/126095135)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [ESP32之FreeRTOS--任务的创建和运行](https://blog.csdn.net/qq_53144843/article/details/121497346)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值