声明:本文为博主的学习篇章,欢迎大家指错,共同学习 在解决一下上篇遗留下来的问题之前,还得提前做些功课,了解一些FreeRTOS的全局变量。
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/* 按照优先级排序的就绪任务列表 */
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; /* 指向当前用于保存已溢出当前滴答计数的任务的延迟任务列表。 */
PRIVILEGED_DATA static List_t xPendingReadyList; /* 调度器挂起时已就绪的任务。当调度器恢复时,它们将被移到就绪列表中 */
#if( INCLUDE_vTaskDelete == 1 )
PRIVILEGED_DATA static List_t xTasksWaitingTermination; /* 已被删除的任务-但它们的内存尚未释放。 */
PRIVILEGED_DATA static volatile UBaseType_t uxDeletedTasksWaitingCleanUp = ( UBaseType_t ) 0U;
#endif
#if ( INCLUDE_vTaskSuspend == 1 )
PRIVILEGED_DATA static List_t xSuspendedTaskList; /* 当前挂起的任务。 */
#endif
在FreeRTOS中,每个任务都有一个状态(就绪、挂起、延时等),所以FreeRTOS就用任务状态列表项来表述该任务的状态。具体是这样的:FreeRTOS会创建一些状态列表,如就绪任务列表,挂起任务列表等,任务处于什么状态就将任务状态列表项挂在哪个列表项。就绪任务列表是一个列表数组,每个不同的优先级享有独立的就绪任务列表,也就是说pxReadyTasksLists[0]是专门给优先级为0的任务挂载的。configMAX_PRIORITIES在文件FreeRTOSConfig.h中定义,描述了FreeRTOS最高的优先级。 当任务因为vTask_Delay等阻塞性堵塞函数暂时无法继续运行下去时,任务状态列表项会被挂在延时任务列表下,表示当前任务在等待阻塞时间结束,并且任务状态列表项中的值会被赋值为阻塞时间。之前有说过,列表项插入列表是根据列表项的值进行升序排序的,也就是说延时列表下挂的第一个列表项永远都是阻塞时间最短的任务。 pxDelayedTaskList用于指向当前的延时任务列表。这里有两个延时任务列表和两个指向延时任务列表的指针。对于一个计数器来说,一直加上去的话总有溢出的时候,系统节拍计数器也不例外,当计数器溢出后计数值会从重装载值重新开始计数。其实当一个任务被阻塞,设定了一个阻塞时间时,其任务状态列表项的值并不是一个需要等待多少时间的值,而是等待时间加上系统计数器当前的计数值。
if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
{
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
else
{
xTimeToWake = xConstTickCount + xTicksToWait;
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
所以当系统节拍计数器溢出时,延迟任务列表下挂的所有任务项的值都需要进行特殊处理,所以就会有两个延时任务列表和两个指针。 xPendingReadyList用于保存已经就绪,但是任务调度器未开启,暂时无法运行的任务状态列表项。根据经验来说这里的任务状态列表项的值应该是任务的优先级,再根据列表项插入列表的API实现来看,此列表的第一项永远都是优先级最高任务状态列表项,对于相同优先级的任务状态列表项,最先进来的列表项排在前面,这也比较符合正常思维。
对于上面所说的全局变量在第一次创建任务的时候需要对这些变量进行初始化。
static void prvInitialiseTaskLists( void )
{
UBaseType_t uxPriority;
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
#if ( INCLUDE_vTaskSuspend == 1 )
{
vListInitialise( &xSuspendedTaskList );
}
#endif
pxDelayedTaskList = &xDelayedTaskList1;
pxOverflowDelayedTaskList = &xDelayedTaskList2;
}
这个函数非常简单,就是调用了之前说过的列表初始化函数。
接着就该继续上一篇遗漏的知识了
将任务加入到就绪任务列表中
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
taskENTER_CRITICAL(); /* 进入临界区 */
{
uxCurrentNumberOfTasks++; /* 任务统计数量加一 */
if( pxCurrentTCB == NULL )
{
pxCurrentTCB = pxNewTCB; /* 如果当前没有任务在运行,就将此任务赋给pxCurrentTCB */
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
{
prvInitialiseTaskLists(); /* 如果当前任务的数量为一,即第一次创建任务就初始化一些列表 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
if( xSchedulerRunning == pdFALSE ) /* 如果没有打开调度器 */
{
/* 如果新创建的任务优先级大于变量pxCurrentTCB指向的任务优先级,则设置pxCurrentTCB指向当前新创建的任务TCB(确保pxCurrentTCB指向优先级最高的就绪任务) */
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
uxTaskNumber++; /* 任务控制块的编号加一 */
#if ( configUSE_TRACE_FACILITY == 1 ) /* 可视化追踪功能 */
{
pxNewTCB->uxTCBNumber = uxTaskNumber;
}
#endif
traceTASK_CREATE( pxNewTCB );
prvAddTaskToReadyList( pxNewTCB ); /* 添加任务到就绪列表中 */
portSETUP_TCB( pxNewTCB ); /* 将pxNewTCB设置为空 */
}
taskEXIT_CRITICAL(); /* 退出临界区 */
if( xSchedulerRunning != pdFALSE )
{ /* 如果创建的任务的优先级高于当前任务,那么现在应该运行它 */
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
taskYIELD_IF_USING_PREEMPTION(); /* 进行任务切换 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
首先是进入临界区,什么是临界区呢,这里又是怎么进入的? 临界区指的是那些必须完整运行,中途不能被打断的代码段。也就是说,在运行这段代码的时候不能切换任务,也不能让中断打断。所以在运行这段代码之前要先进入临界区,运行完后退出临界区就能够确保这段代码运行中途不被打断。如何进入临界区呢?答案就是关中断。这里补充一个知识点,在FreeRTOS中,所有任务的切换都是在PendSV中断函数里进行的,也就是说,要进行任务切换就必须发起一次PendSV中断。在FreeRTOS中,所有中断的优先级都高于最高优先级的任务,所以所有中断都可以打断任务的运行。所以关闭中断也就关闭了任务之间的切换。退出临界区就是开中断啦。 uxCurrentNumberOfTasks是一个全局变量,用于记录当前的任务数量(创建了未删除的所有任务)。 pxCurrentTCB也是一个全局变量,用于记录当前正在执行的任务。 如果调度器还没打开(程序刚开始运行时,可能会先创建几个任务,之后才会启动调度器),并且新创建的任务优先级大于变量pxCurrentTCB指向的任务优先级,则设置pxCurrentTCB指向当前新创建的任务TCB(确保pxCurrentTCB指向优先级最高的就绪任务)。 第一次创建任务需要对一些列表进行初始化。 uxTaskNumber用于记录任务数量变化的次数,每次任务数量发生了变化,这个值就会加一(任务创建和任务删除)。 prvAddTaskToReadyList是在task.c中的一个宏定义
#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 )
taskRECORD_READY_PRIORITY用于将此任务的优先级保存到就绪任务优先级的变量中。vListInsertEnd就是列表项的尾部插入操作了。
|