FreeRTOS解析:TCB_t结构体及重要变量说明(Task-1)

FreeRTOS解析:TCB_t结构体及重要变量说明(Task-1)

受博客限制,如果您想获得更好的阅读体验,请前往https://github.com/Nrusher/FreeRTOS-Book或者https://gitee.com/nrush/FreeRTOS-Book下载PDF版本阅读,如果您觉得本文不错也可前往star,以示对作者的鼓励。如发现问题欢迎交流。PDF阅读效果展示见:FreeRTOS解析:List

相关博客:
FreeRTOS解析:List
FreeRTOS解析:任务的创建(TASK-2)

本节内容旨在于对task.c中运用到的重要变量及结构体进行简要介绍,以方便后续源码的理解。

TCB_t

TCB_t的全称为Task Control Block,也就是任务控制块,这个结构体包含了一个任务所有的信息,它的定义以及相关变量的解释如下

typedef struct tskTaskControlBlock             
    {
        // 这里栈顶指针必须位于TCB第一项是为了便于上下文切换操作,详见xPortPendSVHandler中任务切换的操作。
        volatile StackType_t    *pxTopOfStack;    

        // MPU相关暂时不讨论
        #if ( portUSING_MPU_WRAPPERS == 1 )
            xMPU_SETTINGS    xMPUSettings;        
        #endif

        // 表示任务状态,不同的状态会挂接在不同的状态链表下
        ListItem_t            xStateListItem;    
        // 事件链表项,会挂接到不同事件链表下
        ListItem_t            xEventListItem;        
        // 任务优先级,数值越大优先级越高
        UBaseType_t            uxPriority;            
        // 指向堆栈起始位置,这只是单纯的一个分配空间的地址,可以用来检测堆栈是否溢出
        StackType_t            *pxStack;            
        // 任务名
        char                pcTaskName[ configMAX_TASK_NAME_LEN ];

        // 指向栈尾,可以用来检测堆栈是否溢出
        #if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
            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

        // 任务的一个标签值,可以由用户自定义它的意义,例如可以传入一个函数指针可以用来做Hook    函数调用
        #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

        // 支持NEWLIB的一个变量
        #if ( configUSE_NEWLIB_REENTRANT == 1 )
            struct    _reent xNewLib_reent;
        #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;         
        #endif

        // 延时是否被打断
        #if( INCLUDE_xTaskAbortDelay == 1 )
            uint8_t ucDelayAborted;
        #endif

        // 错误标识
        #if( configUSE_POSIX_ERRNO == 1 )
            int iTaskErrno;
        #endif

    } tskTCB;
    typedef tskTCB TCB_t;

在TCB_t结构体的定义中可以看到根据栈的生长方式的不同,其将具有不同的成员变量pxEndOfStack,在这里说明一下栈的生长方式是如何定义的,以及为何生长方式会存在pxEndOfStack这一变量的差异。

栈的生长方式可以分为两种,一种是向下生长,一种是向上生长,FreeRTOS中用portSTACK_GROWTH来区分这两种生长方式,portSTACK_GROWTH大于0为向上生长,小于零为向下生长。两种生长方式的区别可以简单概括如下

  • 向上生长:入栈时栈顶指针增加,出栈时栈顶指针减小。

  • 向下生长:入栈时栈顶指针减小,出栈时栈顶指针增加。

以下是两种生长方式的入栈图,很容易看出区别

FreeRTOS解析:TCB_t结构体及重要变量说明(Task-1)_第1张图片

为什么会有这两种出入栈方式呢?为何不将所有芯片统一成一种生长方式?这一点应该是芯片设计的实际需要,具体原因无法解答。

有了上图栈的生长方式为什么会影响成员变量的个数很好理解了,pxStack是指向栈内存分配的起始地址(低地址),pxEndOfStack是指向栈的尾部的,当栈是向下生长时,pxStack和pxEndOfStack值是一致的,再定义pxEndOfStack浪费了内存,而栈是向上生长时pxStack与pxEndOfStack的值不一致,如果想知道栈的结束地址,必须要定义一个变量pxEndOfStack来存储,以用于后续的栈溢出检测等操作。

状态链表

FreeRTOS中的任务一共有四种状态分别是运行状态(Running State),就绪状态(Ready State),阻塞状态(Blocked State),挂起状态(Suspended State),其含义可以简单理解为

  • 运行状态:正在执行的任务。

  • 就绪状态:等待获得执行权的任务。

  • 阻塞状态:直到某些条件达成才会重新进入就绪态等待获得执行权,否则不会执行的任务。

  • 挂起状态:除非被主动恢复,否则永远不会执行。

《Mastering the FreeRTOS Real Time Kernel》对这四种任务状态的转换关系描述如下图

FreeRTOS解析:TCB_t结构体及重要变量说明(Task-1)_第2张图片

这四种链表分别对应着pxCurrentTCB,pxReadyTasksLists,pxDelayedTaskList,xSuspendedTaskList这四个变量。除运行状态外,任务处于其它状态时,都是通过将任务TCB中的xStateListItem挂到相应的链表下来表示的。

pxCurrentTCB

pxCurrentTCB定义如下

PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL;

当前运行的任务只可能有一个,因此pxCurrentTCB只是单个TCB_t指针,它始终指向当前运行的任务。

pxReadyTasksLists

pxReadyTasksLists定义如下

PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];

pxReadyTasksLists不是单个链表,它是configMAX_PRIORITIES个链表组成的链表数组。链表数组中的每一个成员都是由处于就绪态而又有着相同任务优先级的任务组成的的链表。与之相关的还有一个变量uxTopReadyPriority。uxTopReadyPriority的定义如下

PRIVILEGED_DATA static volatile UBaseType_t uxTopReadyPriority = tskIDLE_PRIORITY;

uxTopReadyPriority存储的是有任务挂接的最高优先级。pxReadyTasksLists、pxCurrentTCB和uxTopReadyPriority三者之间的关系可由以下的图来表示

FreeRTOS解析:TCB_t结构体及重要变量说明(Task-1)_第3张图片

当使用时间片时,pxCurrentTCB会在有任务挂接的最高优先级链表中遍历,以实现它们对处理器资源的分时共享,这些具体过程会在后面进行详细分析。

pxDelayedTaskList

延时链表的作用不仅是用来处理任务的延时,任务的阻塞也是由它进行实现的(至少目前分析的源码看是这样的)。由于pxDelayedTaskList要处理和时间相关的信息,因此需要考虑到系统的systick溢出的处理。为了解决这一繁琐的问题,FreeRTOS设计了两个延时链表和两个延时链表指针来处理溢出问题,它们的定义如下

PRIVILEGED_DATA static List_t xDelayedTaskList1;                        
PRIVILEGED_DATA static List_t xDelayedTaskList2;                        
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;                
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;

其中pxDelayedTaskList指向当前工作的延时任务链表,而pxOverflowDelayedTaskList指向溢出后的链表,xDelayedTaskList1和xDelayedTaskList2是两个实际链表,其中任务的排列顺序是按退出阻塞时间排序的,也就是链表的第一个成员任务是将最早退出阻塞,而最后一个成员任务是最后退出阻塞的。当系统的systick溢出时,pxDelayedTaskList和pxOverflowDelayedTaskList指向的链表地址也会随之交换一次,实现对溢出的处理。对于溢出的处理会在"任务的延时阻塞的实现"进行分析。以下是四个变量之间的关系

FreeRTOS解析:TCB_t结构体及重要变量说明(Task-1)_第4张图片

与延时任务链表变量为xNextTaskUnblockTime。其定义如下

PRIVILEGED_DATA static volatile TickType_t xNextTaskUnblockTime = ( TickType_t ) 0U;

xNextTaskUnblockTime存储的是下一个任务进行解除阻塞操作的时间,用来判断在何时进行解除阻塞操作。

xSuspendedTaskList

xSuspendedTaskList的定义如下

PRIVILEGED_DATA static List_t xSuspendedTaskList;

其是一个普通的链表,下面挂接的是处于挂起状态的任务。

任务调度器操作相关变量

xSchedulerRunning

xSchedulerRunning定义如下

PRIVILEGED_DATA static volatile BaseType_t xSchedulerRunning = pdFALSE;

该变量表示任务调度器是否已经运行(挂起的任务调度器也算在运行状态)。

uxSchedulerSuspended

uxSchedulerSuspended定义如下

PRIVILEGED_DATA static volatile UBaseType_t uxSchedulerSuspended = ( UBaseType_t ) pdFALSE;

uxSchedulerSuspended的作用是记录任务调度器被挂起的次数,当这个变量为0(dFALSE)时,任务调度器不被挂起,任务调度正常执行,当这个变量大于0时代表任务调度器被挂起的次数。如果执行挂起任务调度器操作该变量值会增加,如果执行恢复任务调度器操作,该变量值会减一,直到它为0时才会真正的执行实际的调度器恢复操作,这样可以有效的提高执行效率,这点在后面关于任务调度器的操作上会进一步探讨。

uxPendedTicks

uxPendedTicks定义如下

PRIVILEGED_DATA static volatile UBaseType_t uxPendedTicks = ( UBaseType_t ) 0U;

任务调度器在被挂起期间,系统的时间,仍然是需要增加的。挂起期间漏掉的systick数目便会被存储在这个变量中,以用于恢复调度器时补上漏掉的systick。

xPendingReadyList

xPendingReadyList定义如下

PRIVILEGED_DATA static List_t xPendingReadyList;

这个链表中挂接的是在任务调度器挂起期间解除阻塞条件得到满足的阻塞任务,在任务调度器恢复工作后,这些任务会被移动到就绪链表组中,变为就绪状态。

任务删除相关

xTasksWaitingTermination

xTasksWaitingTermination定义如下

PRIVILEGED_DATA static List_t xTasksWaitingTermination;

当任务自己删除自己时,其是不能立刻自己释放自己所占用的内存等资源的,其需要将自己挂接到xTasksWaitingTermination这个链表下,然后让IdleTask来回收其所占用的资源。

uxDeletedTasksWaitingCleanUp

uxDeletedTasksWaitingCleanUp定义如下

PRIVILEGED_DATA static volatile UBaseType_t uxDeletedTasksWaitingCleanUp = ( UBaseType_t ) 0U;

uxDeletedTasksWaitingCleanUp记录了等待IdleTask处理的自己删除自己的任务的数目。

xIdleTaskHandle

xIdleTaskHandle定义如下

PRIVILEGED_DATA static TaskHandle_t xIdleTaskHandle    = NULL;

TaskHandle_t本质上是指向任务TCB的指针,IdleTask是任务调度器在启动时便自动创建的空闲任务,用于回收内存等操作,这个任务句柄指向IdleTask。

系统信息相关

xTickCount

xTickCount定义如下

PRIVILEGED_DATA static volatile TickType_t xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;

存储systick的值,用来给系统提供时间信息。

xNumOfOverflows

xNumOfOverflows定义如下

PRIVILEGED_DATA static volatile BaseType_t xNumOfOverflows     = ( BaseType_t ) 0;

这个值保存了xTickCount溢出的次数。

uxTaskNumber

uxTaskNumber定义如下

PRIVILEGED_DATA static UBaseType_t uxTaskNumber = ( UBaseType_t ) 0U;

每创建一个任务,这个值便会增加一次,为每个任务生成一个唯一的序号,供调试工具使用。注意与uxCurrentNumberOfTasks区分。

uxCurrentNumberOfTasks

uxCurrentNumberOfTasks定义如下

PRIVILEGED_DATA static volatile UBaseType_t uxCurrentNumberOfTasks     = ( UBaseType_t ) 0U;

存储当前任务的数目。

你可能感兴趣的:(Free,RTOS)