1.TCB_T
TCB_t的全称为Task Control Block,也就是任务控制块。
typedef struct tskTaskControlBlock
{
//栈顶指针,中断或任务切换时,会对任务压栈
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 )
/* 用于调试,表示本任务是第几个创建,每创建一个任务,系统有一个全局变量加1*/
UBaseType_t uxTCBNumber;
/* 调试用,用户通过API函数vTaskSetTaskNumber()来设置,数值由函数参数指定 */
UBaseType_t uxTaskNumber;
#endif
/* 如果使用任务互斥量信号 */
#if ( configUSE_MUTEXES == 1 )
/* 优先级提升前,保存原优先级,优先级反转时用*/
UBaseType_t uxBasePriority;
/* 任务获取的互斥信号量个数,为0*/
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;///*上面维护旧的tskTCB名称,然后定义为下面的新TCB_t名称
栈的生长方式可以分为两种,一种是向下生长,一种是向上生长,FreeRTOS中用portSTACK_GROWTH来区分这两种生长方式,portSTACK_GROWTH大于0为向上生长,小于零为向下生长。两种生长方式的区别可以简单概括如下:
向上生长:入栈时栈顶指针增加,出栈时栈顶指针减小。
向下生长:入栈时栈顶指针减小,出栈时栈顶指针增加。
以下为两种生长方式的示意图:
有了上图栈的生长方式为什么会影响成员变量的个数很好理解了,pxStack是指向栈内存分配的起始地址(低地址),pxEndOfStack是指向栈的尾部的,当栈是向下生长时,pxStack和pxEndOfStack值是一致的,再定义pxEndOfStack浪费了内存,而栈是向上生长时pxStack与pxEndOfStack的值不一致,如果想知道栈的结束地址,必须要定义一个变量pxEndOfStack来存储,以用于后续的栈溢出检测等操作。
FreeRTOS中的任务一共有四种状态分别是运行状态(Running State),就绪状态(Ready State),阻塞状态(Blocked State),挂起状态(Suspended State),其中:
运行状态:正在执行的任务。
就绪状态:等待获得执行权的任务。
阻塞状态:直到某些条件达成才会重新进入就绪态等待获得执行权,否则不会执行的任务。
挂起状态:除非被主动恢复,否则永远不会执行。
四种任务状态的转换关系如图:
这四种链表分别对应着pxCurrentTCB,pxReadyTasksLists,pxDelayedTaskList,xSuspendedTaskList这四个变量。除运行状态外,任务处于其它状态时,都是通过将任务TCB中的xStateListItem挂到相应的链表下来表示的。
3.1.pxCurrentTCB
PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL;
当前运行任务只可能有一个,则pxCurrentTCB只是单个TCB_t指针,始终指向当前运行的任务。
3.2.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三者之间的关系可由以下的图来表示。
当使用时间片时,pxCurrentTCB会在有任务挂接的最高优先级链表中遍历,以实现它们对处理器资源的分时共享,这些具体过程会在后面进行详细分析。
3.3. 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指向的链表地址也会随之交换一次,实现对溢出的处理。对于溢出的处理会在"任务的延时阻塞的实现"进行分析。以下是四个变量之间的关系:
与延时任务链表变量为xNextTaskUnblockTime。其定义如下
PRIVILEGED_DATA static volatile TickType_t xNextTaskUnblockTime = ( TickType_t ) 0U;
xNextTaskUnblockTime存储的是下一个任务进行解除阻塞操作的时间,用来判断在何时进行解除阻塞操作。
3.4. xSuspendedTaskList
PRIVILEGED_DATA static List_t xSuspendedTaskList;
其是一个普通的链表,下面挂接的是处于挂起状态的任务。
4.1. xSchedulerRunning
PRIVILEGED_DATA static volatile BaseType_t xSchedulerRunning = pdFALSE;
该变量表示任务调度器是否已经运行(挂起的任务调度器也算在运行状态)。
4.2. uxSchedulerSuspended
PRIVILEGED_DATA static volatile UBaseType_t uxSchedulerSuspended = ( UBaseType_t ) pdFALSE
uxSchedulerSuspended的作用是记录任务调度器被挂起的次数,当这个变量为0(dFALSE)时,任务调度器不被挂起,任务调度正常执行,当这个变量大于0时代表任务调度器被挂起的次数。如果执行挂起任务调度器操作该变量值会增加,如果执行恢复任务调度器操作,该变量值会减一,直到它为0时才会真正的执行实际的调度器恢复操作,这样可以有效的提高执行效率,这点在后面关于任务调度器的操作上会进一步探讨。
4.3. uxPendedTicks
PRIVILEGED_DATA static volatile UBaseType_t uxPendedTicks = ( UBaseType_t ) 0U;
任务调度器在被挂起期间,系统的时间,仍然是需要增加的。挂起期间漏掉的systick数目便会被存储在这个变量中,以用于恢复调度器时补上漏掉的systick。
4.4. xPendingReadyList
PRIVILEGED_DATA static List_t xPendingReadyList;
这个链表中挂接的是在任务调度器挂起期间解除阻塞条件得到满足的阻塞任务,在任务调度器恢复工作后,这些任务会被移动到就绪链表组中,变为就绪状态。
5.1. xTasksWaitingTermination
PRIVILEGED_DATA static List_t xTasksWaitingTermination;
当任务自己删除自己时,其是不能立刻自己释放自己所占用的内存等资源的,其需要将自己挂接到xTasksWaitingTermination这个链表下,然后让IdleTask来回收其所占用的资源。
5.2. uxDeletedTasksWaitingCleanUp
PRIVILEGED_DATA static volatile UBaseType_t uxDeletedTasksWaitingCleanUp = ( UBaseType
uxDeletedTasksWaitingCleanUp记录了等待IdleTask处理的自己删除自己的任务的数目。
5.3. xIdleTaskHandle
PRIVILEGED_DATA static TaskHandle_t xIdleTaskHandle = NULL;
TaskHandle_t本质上是指向任务TCB的指针,IdleTask是任务调度器在启动时便自动创建的空闲任务,用于回收内存等操作,这个任务句柄指向IdleTask。
6.1. xTickCount
PRIVILEGED_DATA static volatile TickType_t xTickCount = ( TickType_t ) configINITIAL
存储systick的值,用来给系统提供时间信息。
6.2. xNumOfOverflows
PRIVILEGED_DATA static volatile BaseType_t xNumOfOverflows = ( BaseType_t ) 0;
这个值保存了xTickCount溢出的次数。
6.3. uxTaskNumber
PRIVILEGED_DATA static UBaseType_t uxTaskNumber = ( UBaseType_t ) 0U;
每创建一个任务,这个值便会增加一次,为每个任务生成一个唯一的序号,供调试工具使用。注意与uxCurrentNumberOfTasks区分。
6.4. uxCurrentNumberOfTasks
PRIVILEGED_DATA static volatile UBaseType_t uxCurrentNumberOfTasks = ( UBaseType_t )
存储当前任务的数目。