1、函数作用
一看名字就知道是把刚创建好的New Task添加到就绪列表里。这个函数在task.c
这个文件中。
2、函数分析
先看函数签名:
static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
传入的参数是新建函数的TCB,在浅析xTaskCreate这篇文章中可以知道,由prvInitialiseNewTask
函数初始化了一个任务的TCB,随后这个TCB也就被传入了我们现在分析的这个函数。
函数里做的第一件事是调用了这么一个函数:
taskENTER_CRITICAL();
看名字是“进入临界区”。官方解释是:“确保正在更任务列表的时候,中断不介入”。那么我猜测,这个函数是用来关闭其他中断的。这个函数拓展到vPortEnterCritical()
,我们看这个函数的实现:
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS(); // 失能中断
uxCriticalNesting++; // 嵌套数+1
/* This is not the interrupt safe version of the enter critical function so
* assert() if it is being called from an interrupt context. Only API
* functions that end in "FromISR" can be used in an interrupt. Only assert if
* the critical nesting count is 1 to protect against recursive calls if the
* assert function also uses a critical section. */
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
果然,进函数第一件事就是失能中断。同时官方注释提醒我们,这个进入临界区的函数并非中断安全的,中断里调用的,应该是有FromISR后缀的接口函数。当嵌套数为1时,做声明的动作,官方解释是避免重复调用。
继续分析:有进入临界区的操作,就必然有退出临界区的操作。在退出临界区之前,有一大段源码要分析:
{
uxCurrentNumberOfTasks++;
if( pxCurrentTCB == NULL )
{
/* There are no other tasks, or all the other tasks are in
* the suspended state - make this the current task. */
pxCurrentTCB = pxNewTCB;
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
{
/* This is the first task to be created so do the preliminary
* initialisation required. We will not recover if this call
* fails, but we will report the failure. */
prvInitialiseTaskLists();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* If the scheduler is not already running, make this task the
* current task if it is the highest priority task to be created
* so far. */
if( xSchedulerRunning == pdFALSE )
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
uxTaskNumber++;
#if ( configUSE_TRACE_FACILITY == 1 )
{
/* Add a counter into the TCB for tracing only. */
pxNewTCB->uxTCBNumber = uxTaskNumber;
}
#endif /* configUSE_TRACE_FACILITY */
traceTASK_CREATE( pxNewTCB );
prvAddTaskToReadyList( pxNewTCB );
portSETUP_TCB( pxNewTCB );
}
首先是uxCurrentNumberOfTasks
这个变量,它是task.c
这个文件里的全局变量,记录有当前有多少个任务,初始化为0。
pxCurrentTCB
也是当前文件的全局变量,是TCB指针,初始化为NULL。这个变量为NULL,说明没有其他任务,或者所有其他任务都被挂起了,则该指针指向函数传入的参数TCB。
如果当前任务数为1,执行新建任务列表的函数prvInitialiseTaskLists
。
如果pxCurrentTCB
已经指向别的任务了,且如果此时调度器未在运行,把这个指针指向任务和当前任务的优先级进行对比,如果当前任务优先级高,则pxCurrentTCB
指向当前任务。
又来了个变量uxTaskNumber
,这个变量可能是与调试有关。下面的宏判断,若开启了追踪设备,将uxTaskNumber
保存至TCB中。
traceTASK_CREATE( pxNewTCB )
是宏定义,没有实现。
prvAddTaskToReadyList( pxNewTCB )
这一步才是真正的把任务添加到就绪列表的操作,对比当前函数prvAddNewTaskToReadyList
可以得知,这个函数主要是针对新创建的任务做了处理。
我们索性看一下prvAddTaskToReadyList( pxNewTCB )
这个宏都做了些什么:
#define prvAddTaskToReadyList( pxTCB ) \
traceMOVED_TASK_TO_READY_STATE( pxTCB ); \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
listINSERT_END( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )
第一个traceMOVED_TASK_TO_READY_STATE
没实现。
第二个taskRECORD_READY_PRIORITY
记录任务的优先级,记录在uxTopReadyPriority
这个变量里。
第三个listINSERT_END
的主要作用是将状态列表Item插入到任务列表里,其底层是操作链表。
第四个tracePOST_MOVED_TASK_TO_READY_STATE
没实现。
回到函数主体。portSETUP_TCB( pxNewTCB );
拓展到(void)(pxNewTCB)
仅仅是点了一下这个变量,估计是避免编译器报错变量定义了但是未使用吧。
至此,临界区里的工作就做完了,接下来是退出临界区taskEXIT_CRITICAL()
,它拓展到如下函数:
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
可以看出其操作与进入临界区的操作是相反的,这里就不过多解释了。
最后一部分:
if( xSchedulerRunning != pdFALSE )
{
/* If the created task is of a higher priority than the current task
* then it should run now. */
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
如果调度器正在运行(吐槽一下,为什么不写成==pdTRUE呢,非要写成!=pdFALSE),而且当前任务优先级比新建任务优先级低,则让当前任务主动让出CPU使用权。
3、总结
这个函数做了啥:
1、在临界区中调用prvInitialiseTaskLists
初始化任务列表 / 把“当前TCB”指向优先级大的TCB。
2、记录任务数量。
3、通过修改链表的方式,把任务添加进任务列表。
4、让调度器运行优先级较高的任务。