FreeRTOS系列第20篇---FreeRTOS任务创建分析
共 25667字,需浏览 52分钟
·
2021-04-12 00:07
ID:技术让梦想更伟大
整理:李肖遥
回顾任务的创建删除
在FreeRTOS基础系列《FreeRTOS系列第10篇---FreeRTOS任务创建和删除》中介绍了任务创建API函数xTaskCreate()
,我们这里先回顾一下这个函数的声明:
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被创建并加入就绪任务列表,我们这章的主要目的,就是看看这个语句在执行过程中,发生了什么事情。
1.创建任务堆栈和任务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;
}
2.初始化任务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
获得列表中的第一个列表项的xTiemValue
值总是最小,也就是优先级最高的任务!
此外,TCB其它的一些字段也被初始化,比如临界区嵌套次数、运行时间计数器、任务通知值、任务通知状态等。
函数prvInitialiseTCBVariables()
的源码如下所示:
static void prvInitialiseTCBVariables( TCB_t * const pxTCB, const char * const pcName, UBaseType_t uxPriority, \
const MemoryRegion_t * const xRegions, const uint16_t usStackDepth )
{
UBaseType_t x;
/* 将任务描述存入TCB */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxTCB->pcTaskName[ x ] = pcName[ x ];
if( pcName[ x ] == 0x00 )
{
break;
}
}
/* 确保字符串有结束 */
pxTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
/* 调整优先级,宏configMAX_PRIORITIES的值在FreeRTOSConfig.h中设置 */
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
pxTCB->uxPriority = uxPriority;
#if ( configUSE_MUTEXES == 1 ) /*使用互斥量*/
{
pxTCB->uxBasePriority = uxPriority;
pxTCB->uxMutexesHeld = 0;
}
#endif /* configUSE_MUTEXES */
/*初始化列表项*/
vListInitialiseItem( &( pxTCB->xStateListItem ) );
vListInitialiseItem( &( pxTCB->xEventListItem ) );
/* 设置列表项xStateListItem的成员pvOwner指向当前任务控制块 */
listSET_LIST_ITEM_OWNER( &( pxTCB->xStateListItem ), pxTCB );
/* 设置列表项xEventListItem的成员xItemValue*/
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
/* 设置列表项xEventListItem的成员pvOwner指向当前任务控制块 */
listSET_LIST_ITEM_OWNER( &( pxTCB->xEventListItem ), pxTCB );
#if ( portCRITICAL_NESTING_IN_TCB ==1 ) /*使能临界区嵌套功能*/
{
pxTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
}
#endif /* portCRITICAL_NESTING_IN_TCB */
#if ( configUSE_APPLICATION_TASK_TAG == 1 ) /*使能任务标签功能*/
{
pxTCB->pxTaskTag = NULL;
}
#endif /* configUSE_APPLICATION_TASK_TAG */
#if ( configGENERATE_RUN_TIME_STATS == 1 ) /*使能事件统计功能*/
{
pxTCB->ulRunTimeCounter = 0UL;
}
#endif /* configGENERATE_RUN_TIME_STATS */
#if ( portUSING_MPU_WRAPPERS == 1 ) /*使用MPU功能*/
{
vPortStoreTaskMPUSettings( &( pxTCB->xMPUSettings ), xRegions, pxTCB->pxStack, usStackDepth );
}
#else /* portUSING_MPU_WRAPPERS */
{
( void ) xRegions;
( void ) usStackDepth;
}
#endif /* portUSING_MPU_WRAPPERS */
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )/*使能线程本地存储指针*/
{
for( x = 0; x < ( UBaseType_t )configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
{
pxTCB->pvThreadLocalStoragePointers[ x ] = NULL;
}
}
#endif
#if ( configUSE_TASK_NOTIFICATIONS == 1 ) /*使能任务通知功能*/
{
pxTCB->ulNotifiedValue = 0;
pxTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
}
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 ) /*使用Newlib*/
{
_REENT_INIT_PTR( ( &( pxTCB->xNewLib_reent ) ) );
}
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
{
pxTCB->ucDelayAborted = pdFALSE;
}
#endif
}
3.初始化任务堆栈
调用函数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
表示堆栈的起始位置,所以在图中它指向堆栈的最开始处。
4.进入临界区
调用taskENTER_CRITICAL()
进入临界区,这是一个宏定义,最终进入临界区的代码由移植层提供。
5.当前任务数量增加1
在tasks.c中 ,定义了一些静态私有变量,用来跟踪任务的数量或者状态等等,其中变量uxCurrentNumberOfTasks
表示当前任务的总数量,每创建一个任务,这个变量都会增加1。
6.为第一次运行做必要的初始化
如果这是第一个任务(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
现在这些列表都要进行初始化,会调用API函数vListInitialise()
初始化列表,这个函数在《FreeRTOS高级篇1---FreeRTOS列表和列表项》中讲过,每个列表的初始化方式都是相同的。
以就绪态列表pxReadyTasksLists[0]
为例,初始化后如图6-1所示:
「函数prvInitialiseTaskLists()的源代码如下所示:」
static void prvInitialiseTaskLists( void )
{
UBaseType_tuxPriority;
for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
{
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}
vListInitialise( &xDelayedTaskList1 );
vListInitialise( &xDelayedTaskList2 );
vListInitialise( &xPendingReadyList );
#if ( INCLUDE_vTaskDelete == 1 )
{
vListInitialise( &xTasksWaitingTermination );
}
#endif /* INCLUDE_vTaskDelete */
#if ( INCLUDE_vTaskSuspend == 1 )
{
vListInitialise( &xSuspendedTaskList );
}
#endif /* INCLUDE_vTaskSuspend */
/* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskListusing list2. */
pxDelayedTaskList = &xDelayedTaskList1;
pxOverflowDelayedTaskList = &xDelayedTaskList2;
}
7.更新当前正在运行的任务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();
}
}
8.将新创建的任务加入就绪列表数组
调用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运行权。它在这里参与如何最快的找到优先级最高的就绪任务。
为了最快,不同的架构会各显神通,一些架构还有特殊指令可用,所以这个宏由移植层提供。
我们会在下一章介绍任务切换时,以Cortex-M3架构为例,详细介绍如何最快的找到优先级最高的就绪任务。
函数vListInsertEnd()
将列表项插入到列表末端,在《FreeRTOS高级篇1---FreeRTOS列表和列表项》中已经提到过,这里会结合着例子再看一下这个函数。
从前面我们知道,在调用函数vListInsertEnd()
之前,就绪列表pxReadyTasksLists[1]
和任务TCB的状态列表项xStateListItem
都已经初始化好了,见图6-1和图2-1,为了方便查看,我们将这两幅图合成一副,见图8-1。
调用vListInsertEnd(a,b)
会将列表项b,插入到列表a的后面,函数执行完毕后,列表和列表项的关系如图8-2所示。
在此基础上,假设又创建了任务B,任务A和任务B优先级相同,都为1。
和任务A一样,任务B也有它自己的任务TCB,其中的状态列表项字段xStateListItem
也要插入到列表pxReadyTasksLists[1]
中,新的列表和列表项如图8-3所示。
9.退出临界区
调用taskEXIT_CRITICAL()
退出临界区,这是一个宏定义,最终退出临界区的代码由移植层提供。
10.执行上下文切换
如果上面的步骤都正确执行,并且调度器也开始工作,则判断当前任务的优先级是否大于新创建的任务优先级。
如果新创建的任务优先级更高,则调用taskYIELD_IF_USING_PREEMPTION()
强制进行一次上下文切换,切换后,新创建的任务将获得CPU控制权,精简后的代码如下所示。
if( xReturn == pdPASS )
{
if( xSchedulerRunning != pdFALSE )
{
/* 如果新创建的任务优先级大于当前任务优先级,则新创建的任务应该被立即执行。*/
if(pxCurrentTCB->uxPriority < uxPriority )
{
taskYIELD_IF_USING_PREEMPTION();
}
}
}
嵌入式编程专辑 Linux 学习专辑 C/C++编程专辑 Qt进阶学习专辑
关注公众号『技术让梦想更伟大』,后台回复关键字:『Qt』『C语言基础』『C语言难点』『C++』『Linux』『freertos』『指针』『数据结构与算法』『经验技巧篇』『疑问篇』『基础理论篇』『实战篇』『架构篇』『模块化编程』『状态机』『实用工具』『心声社区』『期刊』『视频』······等,查看更多精选内容。
关注我的微信公众号,回复“加群”按规则加入技术交流群。
这是我另一个技术号,程序员的编程学习基地,注重编程思想,欢迎关注!
点击“阅读原文”查看更多分享。