freertos链表与任务代码解析

说明

本节基于代码做大致框架分析,着眼于操作系统设计原理,不拘泥于具体细节。


分析

关于任务,freertos使用了全局变量:

<code>static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*< Prioritised ready tasks. */
static List_t xDelayedTaskList1;\t\t\t\t\t\t/*< Delayed tasks. */
static List_t xDelayedTaskList2;\t\t\t\t\t\t/*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
static List_t * volatile pxDelayedTaskList;\t\t\t\t/*< Points to the delayed task list currently being used. */
static List_t * volatile pxOverflowDelayedTaskList;\t\t/*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
static List_t xPendingReadyList;/<code>

先关注pxReadyTasksLists,这表示的是就绪的任务。configMAX_PRIORITIES就表示任务优先级的最大个数。需要知道程序中将要定义使用的任务个数与它没有必然关系,因为同一个优先级下可以有多个任务存在的,因此,在定义任务的时候,优先级不能大于configMAX_PRIORITIES。


任务创建需要使用函数xTaskCreate(),函数内容如:

<code>BaseType_t xTaskCreate(\tTaskFunction_t pxTaskCode,
\t\t\t\t\t\t\tconst char * const pcName,\t\t/*lint !e971 Unqualified char types are allowed for strings and single characters only. */
\t\t\t\t\t\t\tconst configSTACK_DEPTH_TYPE usStackDepth,
\t\t\t\t\t\t\tvoid * const pvParameters,
\t\t\t\t\t\t\tUBaseType_t uxPriority,
\t\t\t\t\t\t\tTaskHandle_t * const pxCreatedTask )
\t{
\tTCB_t *pxNewTCB;
\tBaseType_t xReturn;

\t\t/* If the stack grows down then allocate the stack then the TCB so the stack
\t\tdoes not grow into the TCB. Likewise if the stack grows up then allocate
\t\tthe TCB then the stack. */
\t\t#if( portSTACK_GROWTH > 0 )
\t\t{
\t\t\t/* Allocate space for the TCB. Where the memory comes from depends on

\t\t\tthe implementation of the port malloc function and whether or not static
\t\t\tallocation is being used. */
\t\t\tpxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

\t\t\tif( pxNewTCB != NULL )
\t\t\t{
\t\t\t\t/* Allocate space for the stack used by the task being created.
\t\t\t\tThe base of the stack memory stored in the TCB so the task can
\t\t\t\tbe deleted later if required. */
\t\t\t\tpxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

\t\t\t\tif( pxNewTCB->pxStack == NULL )
\t\t\t\t{
\t\t\t\t\t/* Could not allocate the stack. Delete the allocated TCB. */
\t\t\t\t\tvPortFree( pxNewTCB );
\t\t\t\t\tpxNewTCB = NULL;
\t\t\t\t}
\t\t\t}
\t\t}
\t\t#else /* portSTACK_GROWTH */
\t\t{
\t\tStackType_t *pxStack;

\t\t\t/* Allocate space for the stack used by the task being created. */
\t\t\tpxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */

\t\t\tif( pxStack != NULL )
\t\t\t{
\t\t\t\t/* Allocate space for the TCB. */
\t\t\t\tpxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */

\t\t\t\tif( pxNewTCB != NULL )
\t\t\t\t{
\t\t\t\t\t/* Store the stack location in the TCB. */
\t\t\t\t\tpxNewTCB->pxStack = pxStack;
\t\t\t\t}
\t\t\t\telse
\t\t\t\t{
\t\t\t\t\t/* The stack cannot be used as the TCB was not created. Free
\t\t\t\t\tit again. */
\t\t\t\t\tvPortFree( pxStack );
\t\t\t\t}
\t\t\t}
\t\t\telse
\t\t\t{
\t\t\t\tpxNewTCB = NULL;
\t\t\t}
\t\t}
\t\t#endif /* portSTACK_GROWTH */

\t\tif( pxNewTCB != NULL )
\t\t{
\t\t\t#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */
\t\t\t{
\t\t\t\t/* Tasks can be created statically or dynamically, so note this
\t\t\t\ttask was created dynamically in case it is later deleted. */
\t\t\t\tpxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
\t\t\t}
\t\t\t#endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */

\t\t\tprvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
\t\t\tprvAddNewTaskToReadyList( pxNewTCB );
\t\t\txReturn = pdPASS;
\t\t}
\t\telse
\t\t{
\t\t\txReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
\t\t}

\t\treturn xReturn;
\t}/<code>

这里讲的是动态创建方法,动态创建意思就是使用malloc类函数来获取内存。另外,定义使用的内存是portSTACK_GROWTH<0的。通过参数传入代码地址,栈大小,任务名称等,然后执行任务初始化操作,这个使用的是prvInitialiseNewTask(),代码如下:

<code>static void prvInitialiseNewTask( \tTaskFunction_t pxTaskCode,
\t\t\t\t\t\t\t\t\tconst char * const pcName,\t\t/*lint !e971 Unqualified char types are allowed for strings and single characters only. */
\t\t\t\t\t\t\t\t\tconst uint32_t ulStackDepth,
\t\t\t\t\t\t\t\t\tvoid * const pvParameters,
\t\t\t\t\t\t\t\t\tUBaseType_t uxPriority,
\t\t\t\t\t\t\t\t\tTaskHandle_t * const pxCreatedTask,
\t\t\t\t\t\t\t\t\tTCB_t *pxNewTCB,
\t\t\t\t\t\t\t\t\tconst MemoryRegion_t * const xRegions )
{
StackType_t *pxTopOfStack;
UBaseType_t x;

\t#if( portUSING_MPU_WRAPPERS == 1 )
\t\t/* Should the task be created in privileged mode? */
\t\tBaseType_t xRunPrivileged;
\t\tif( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
\t\t{
\t\t\txRunPrivileged = pdTRUE;

\t\t}
\t\telse
\t\t{
\t\t\txRunPrivileged = pdFALSE;
\t\t}
\t\tuxPriority &= ~portPRIVILEGE_BIT;
\t#endif /* portUSING_MPU_WRAPPERS == 1 */

\t/* Avoid dependency on memset() if it is not required. */
\t#if( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
\t{
\t\t/* Fill the stack with a known value to assist debugging. */
\t\t( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
\t}
\t#endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */

\t/* Calculate the top of stack address. This depends on whether the stack
\tgrows from high memory to low (as per the 80x86) or vice versa.
\tportSTACK_GROWTH is used to make the result positive or negative as required
\tby the port. */
\t#if( portSTACK_GROWTH < 0 )
\t{
\t\tpxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
\t\tpxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 !e9033 !e9078 MISRA exception. Avoiding casts between pointers and integers is not practical. Size differences accounted for using portPOINTER_SIZE_TYPE type. Checked by assert(). */

\t\t/* Check the alignment of the calculated top of stack is correct. */
\t\tconfigASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );

\t\t#if( configRECORD_STACK_HIGH_ADDRESS == 1 )
\t\t{
\t\t\t/* Also record the stack's high address, which may assist
\t\t\tdebugging. */
\t\t\tpxNewTCB->pxEndOfStack = pxTopOfStack;
\t\t}
\t\t#endif /* configRECORD_STACK_HIGH_ADDRESS */
\t}
\t#else /* portSTACK_GROWTH */
\t{
\t\tpxTopOfStack = pxNewTCB->pxStack;

\t\t/* Check the alignment of the stack buffer is correct. */
\t\tconfigASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );

\t\t/* The other extreme of the stack space is required if stack checking is
\t\tperformed. */
\t\tpxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
\t}
\t#endif /* portSTACK_GROWTH */

\t/* Store the task name in the TCB. */

\tif( pcName != NULL )
\t{
\t\tfor( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
\t\t{
\t\t\tpxNewTCB->pcTaskName[ x ] = pcName[ x ];

\t\t\t/* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than
\t\t\tconfigMAX_TASK_NAME_LEN characters just in case the memory after the
\t\t\tstring is not accessible (extremely unlikely). */
\t\t\tif( pcName[ x ] == ( char ) 0x00 )
\t\t\t{
\t\t\t\tbreak;
\t\t\t}
\t\t\telse
\t\t\t{
\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t}
\t\t}

\t\t/* Ensure the name string is terminated in the case that the string length
\t\twas greater or equal to configMAX_TASK_NAME_LEN. */
\t\tpxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\\0';
\t}
\telse
\t{
\t\t/* The task has not been given a name, so just ensure there is a NULL
\t\tterminator when it is read out. */
\t\tpxNewTCB->pcTaskName[ 0 ] = 0x00;
\t}

\t/* This is used as an array index so must ensure it's not too large. First
\tremove the privilege bit if one is present. */
\tif( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
\t{
\t\tuxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
\t}
\telse
\t{
\t\tmtCOVERAGE_TEST_MARKER();
\t}

\tpxNewTCB->uxPriority = uxPriority;
\t#if ( configUSE_MUTEXES == 1 )
\t{
\t\tpxNewTCB->uxBasePriority = uxPriority;
\t\tpxNewTCB->uxMutexesHeld = 0;
\t}
\t#endif /* configUSE_MUTEXES */

\tvListInitialiseItem( &( pxNewTCB->xStateListItem ) );

\tvListInitialiseItem( &( pxNewTCB->xEventListItem ) );

\t/* Set the pxNewTCB as a link back from the ListItem_t. This is so we can get
\tback to\tthe containing TCB from a generic item in a list. */
\tlistSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );

\t/* Event lists are always in priority order. */
\tlistSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
\tlistSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );

\t#if ( portCRITICAL_NESTING_IN_TCB == 1 )
\t{
\t\tpxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
\t}
\t#endif /* portCRITICAL_NESTING_IN_TCB */

\t#if ( configUSE_APPLICATION_TASK_TAG == 1 )
\t{
\t\tpxNewTCB->pxTaskTag = NULL;
\t}
\t#endif /* configUSE_APPLICATION_TASK_TAG */

\t#if ( configGENERATE_RUN_TIME_STATS == 1 )
\t{
\t\tpxNewTCB->ulRunTimeCounter = 0UL;
\t}
\t#endif /* configGENERATE_RUN_TIME_STATS */

\t#if ( portUSING_MPU_WRAPPERS == 1 )
\t{
\t\tvPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );
\t}
\t#else
\t{
\t\t/* Avoid compiler warning about unreferenced parameter. */
\t\t( void ) xRegions;
\t}
\t#endif

\t#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
\t{
\t\tfor( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
\t\t{
\t\t\tpxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL;
\t\t}
\t}
\t#endif

\t#if ( configUSE_TASK_NOTIFICATIONS == 1 )
\t{

\t\tpxNewTCB->ulNotifiedValue = 0;
\t\tpxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
\t}
\t#endif

\t#if ( configUSE_NEWLIB_REENTRANT == 1 )
\t{
\t\t/* Initialise this task's Newlib reent structure.
\t\tSee the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
\t\tfor additional information. */
\t\t_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
\t}
\t#endif

\t#if( INCLUDE_xTaskAbortDelay == 1 )
\t{
\t\tpxNewTCB->ucDelayAborted = pdFALSE;
\t}
\t#endif

\t/* Initialize the TCB stack to look as if the task was already running,
\tbut had been interrupted by the scheduler. The return address is set
\tto the start of the task function. Once the stack has been initialised
\tthe top of stack variable is updated. */
\t#if( portUSING_MPU_WRAPPERS == 1 )
\t{
\t\t/* If the port has capability to detect stack overflow,
\t\tpass the stack end address to the stack initialization
\t\tfunction as well. */
\t\t#if( portHAS_STACK_OVERFLOW_CHECKING == 1 )
\t\t{
\t\t\t#if( portSTACK_GROWTH < 0 )
\t\t\t{
\t\t\t\tpxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged );
\t\t\t}
\t\t\t#else /* portSTACK_GROWTH */
\t\t\t{
\t\t\t\tpxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged );
\t\t\t}
\t\t\t#endif /* portSTACK_GROWTH */
\t\t}
\t\t#else /* portHAS_STACK_OVERFLOW_CHECKING */
\t\t{
\t\t\tpxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
\t\t}
\t\t#endif /* portHAS_STACK_OVERFLOW_CHECKING */
\t}
\t#else /* portUSING_MPU_WRAPPERS */
\t{
\t\t/* If the port has capability to detect stack overflow,

\t\tpass the stack end address to the stack initialization
\t\tfunction as well. */
\t\t#if( portHAS_STACK_OVERFLOW_CHECKING == 1 )
\t\t{
\t\t\t#if( portSTACK_GROWTH < 0 )
\t\t\t{
\t\t\t\tpxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );
\t\t\t}
\t\t\t#else /* portSTACK_GROWTH */
\t\t\t{
\t\t\t\tpxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );
\t\t\t}
\t\t\t#endif /* portSTACK_GROWTH */
\t\t}
\t\t#else /* portHAS_STACK_OVERFLOW_CHECKING */
\t\t{
\t\t\tpxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
\t\t}
\t\t#endif /* portHAS_STACK_OVERFLOW_CHECKING */
\t}
\t#endif /* portUSING_MPU_WRAPPERS */

\tif( pxCreatedTask != NULL )
\t{
\t\t/* Pass the handle out in an anonymous way. The handle can be used to
\t\tchange the created task's priority, delete the created task, etc.*/
\t\t*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
\t}
\telse
\t{
\t\tmtCOVERAGE_TEST_MARKER();
\t}
}/<code>

注意,函数外传参的优先级是从1开始的,而对于数组来说是从0开始的。之后将任务结构体中的属性,xStateListItem、xEventListItem进行初始化,之后是若定义了任务通知,就也将任务通知相关的属性,如ulNotifiedValue、ucNotifyState也做初始化操作。

最后,将组织好的任务添加进就绪表中,使用函数prvAddNewTaskToReadyList(),内容如:

<code>static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB ) 

{
\t/* Ensure interrupts don't access the task lists while the lists are being
\tupdated. */
\ttaskENTER_CRITICAL();
\t{
\t\tuxCurrentNumberOfTasks++;
\t\tif( pxCurrentTCB == NULL )
\t\t{
\t\t\t/* There are no other tasks, or all the other tasks are in
\t\t\tthe suspended state - make this the current task. */
\t\t\tpxCurrentTCB = pxNewTCB;

\t\t\tif( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
\t\t\t{
\t\t\t\t/* This is the first task to be created so do the preliminary
\t\t\t\tinitialisation required. We will not recover if this call
\t\t\t\tfails, but we will report the failure. */
\t\t\t\tprvInitialiseTaskLists();
\t\t\t}
\t\t\telse
\t\t\t{
\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t}
\t\t}
\t\telse
\t\t{
\t\t\t/* If the scheduler is not already running, make this task the
\t\t\tcurrent task if it is the highest priority task to be created
\t\t\tso far. */
\t\t\tif( xSchedulerRunning == pdFALSE )
\t\t\t{
\t\t\t\tif( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
\t\t\t\t{
\t\t\t\t\tpxCurrentTCB = pxNewTCB;
\t\t\t\t}
\t\t\t\telse
\t\t\t\t{
\t\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t\t}
\t\t\t}
\t\t\telse
\t\t\t{
\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t}
\t\t}

\t\tuxTaskNumber++;

\t\t#if ( configUSE_TRACE_FACILITY == 1 )
\t\t{

\t\t\t/* Add a counter into the TCB for tracing only. */
\t\t\tpxNewTCB->uxTCBNumber = uxTaskNumber;
\t\t}
\t\t#endif /* configUSE_TRACE_FACILITY */
\t\ttraceTASK_CREATE( pxNewTCB );

\t\tprvAddTaskToReadyList( pxNewTCB );

\t\tportSETUP_TCB( pxNewTCB );
\t}
\ttaskEXIT_CRITICAL();

\tif( xSchedulerRunning != pdFALSE )
\t{
\t\t/* If the created task is of a higher priority than the current task
\t\tthen it should run now. */
\t\tif( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
\t\t{
\t\t\ttaskYIELD_IF_USING_PREEMPTION();
\t\t}
\t\telse
\t\t{
\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t}
\t}
\telse
\t{
\t\tmtCOVERAGE_TEST_MARKER();
\t}
}/<code>

先将任务计数个数加1,然后判断pxCurrentTCB,也就是当前任务是否不存在。在第一次执行时,pxCurrentTCB确实是为NULL的。之后,如果是第一次创建任务,就需要初始化任务的链表等list,例如:

<code>pxReadyTasksLists
xDelayedTaskList1
xDelayedTaskList2
xPendingReadyList
xTasksWaitingTermination
xSuspendedTaskList/<code>


这就是函数prvInitialiseTaskLists()实现的内容,代码如下:


<code>static void prvInitialiseTaskLists( void )
{
UBaseType_t uxPriority;

\tfor( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
\t{
\t\tvListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
\t}

\tvListInitialise( &xDelayedTaskList1 );
\tvListInitialise( &xDelayedTaskList2 );
\tvListInitialise( &xPendingReadyList );

\t#if ( INCLUDE_vTaskDelete == 1 )
\t{
\t\tvListInitialise( &xTasksWaitingTermination );
\t}
\t#endif /* INCLUDE_vTaskDelete */

\t#if ( INCLUDE_vTaskSuspend == 1 )
\t{
\t\tvListInitialise( &xSuspendedTaskList );
\t}
\t#endif /* INCLUDE_vTaskSuspend */

\t/* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskList
\tusing list2. */
\tpxDelayedTaskList = &xDelayedTaskList1;
\tpxOverflowDelayedTaskList = &xDelayedTaskList2;
}/<code>

要注意函数prvAddNewTaskToReadyList(),它里面涉及到的两个变量要区分一下,uxCurrentNumberOfTasks与uxTaskNumber。uxCurrentNumberOfTasks表示的是目前系统创建的任务个数,而uxTaskNumber表示的是任务id,它可以用来做调试使用。之后就是调用prvAddTaskToReadyList()将任务添加到pxReadyTasksLists数组中的一个list中。这里看一下这个函数的代码:

<code>#define prvAddTaskToReadyList( pxTCB )\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\ttraceMOVED_TASK_TO_READY_STATE( pxTCB );\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\

\ttaskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );\t\t\t\t\t\t\t\t\t\t\t\t\\
\tvListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \\
\ttracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )/<code>

注意:

<code>#define taskRECORD_READY_PRIORITY( uxPriority )\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t\tif( ( uxPriority ) > uxTopReadyPriority )\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t\t\tuxTopReadyPriority = ( uxPriority );\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t} /* taskRECORD_READY_PRIORITY *//<code>

如果是在系统创建了任务并且已经运行起来之后再执行的动态创建任务操作,则如果新创建的任务优先级高于当前运行的任务优先级(可见uxPriorit越大优先级越高),则需要执行portYIELD(),它的内容如:

<code>#define portYIELD()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t/* Set a PendSV to request a context switch. */\t\t\t\t\t\t\t\t\\
\tportNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;\t\t\t\t\t\t\t\t\\
\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t/* Barriers are normally not required but do ensure the code is completely\t\\
\twithin the specified behaviour for the architecture. */\t\t\t\t\t\t\\
\t__dsb( portSY_FULL_READ_WRITE );\t\t\t\t\t\t\t\t\t\t\t\\
\t__isb( portSY_FULL_READ_WRITE );\t\t\t\t\t\t\t\t\t\t\t\\
}/<code>

也就是执行PENDSV中断。中断里面会调用到vTaskSwitchContext(),函数内容为:

<code>void vTaskSwitchContext( void )
{
\tif( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
\t{
\t\t/* The scheduler is currently suspended - do not allow a context
\t\tswitch. */
\t\txYieldPending = pdTRUE;
\t}
\telse
\t{
\t\txYieldPending = pdFALSE;

\t\ttraceTASK_SWITCHED_OUT();

\t\t#if ( configGENERATE_RUN_TIME_STATS == 1 )
\t\t{
\t\t\t#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
\t\t\t\tportALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
\t\t\t#else
\t\t\t\tulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
\t\t\t#endif

\t\t\t/* Add the amount of time the task has been running to the
\t\t\taccumulated time so far. The time the task started running was
\t\t\tstored in ulTaskSwitchedInTime. Note that there is no overflow
\t\t\tprotection here so count values are only valid until the timer
\t\t\toverflows. The guard against negative values is to protect
\t\t\tagainst suspect run time stat counter implementations - which
\t\t\tare provided by the application, not the kernel. */
\t\t\tif( ulTotalRunTime > ulTaskSwitchedInTime )
\t\t\t{
\t\t\t\tpxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
\t\t\t}
\t\t\telse
\t\t\t{
\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t}
\t\t\tulTaskSwitchedInTime = ulTotalRunTime;
\t\t}
\t\t#endif /* configGENERATE_RUN_TIME_STATS */

\t\t/* Check for stack overflow, if configured. */
\t\ttaskCHECK_FOR_STACK_OVERFLOW();

\t\t/* Before the currently running task is switched out, save its errno. */
\t\t#if( configUSE_POSIX_ERRNO == 1 )
\t\t{
\t\t\tpxCurrentTCB->iTaskErrno = FreeRTOS_errno;
\t\t}
\t\t#endif

\t\t/* Select a new task to run using either the generic C or port
\t\toptimised asm code. */
\t\ttaskSELECT_HIGHEST_PRIORITY_TASK(); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
\t\ttraceTASK_SWITCHED_IN();

\t\t/* After the new task is switched in, update the global errno. */
\t\t#if( configUSE_POSIX_ERRNO == 1 )
\t\t{
\t\t\tFreeRTOS_errno = pxCurrentTCB->iTaskErrno;
\t\t}
\t\t#endif


\t\t#if ( configUSE_NEWLIB_REENTRANT == 1 )
\t\t{
\t\t\t/* Switch Newlib's _impure_ptr variable to point to the _reent
\t\t\tstructure specific to this task.
\t\t\tSee the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
\t\t\tfor additional information. */
\t\t\t_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
\t\t}
\t\t#endif /* configUSE_NEWLIB_REENTRANT */
\t}
}/<code>

由于调度器并未挂起,所以执行else分支,它需要先找出最高优先级任务,调用函数:taskSELECT_HIGHEST_PRIORITY_TASK()。函数内容为:

<code>#define taskSELECT_HIGHEST_PRIORITY_TASK()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\tUBaseType_t uxTopPriority = uxTopReadyPriority;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t\t/* Find the highest priority queue that contains ready tasks. */\t\t\t\t\t\t\t\t\\
\t\twhile( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )\t\t\t\t\t\t\t\\
\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t\t\tconfigASSERT( uxTopPriority );\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t\t\t--uxTopPriority;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t\t/* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of\t\t\t\t\t\t\\
\t\tthe\tsame priority get an equal share of the processor time. */\t\t\t\t\t\t\t\t\t\\
\t\tlistGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );\t\t\t\\
\t\tuxTopReadyPriority = uxTopPriority;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t} /* taskSELECT_HIGHEST_PRIORITY_TASK *//<code>

这是一个通用方法,先根据uxTopReadyPriority一直从数组往下找,数组中存放的都是list,如果list数组中从最大到最小不为空,就可以定位到list(list为空可以查看属性uxNumberOfItems),如果找到一个不为空的项,就需要从list为入口,找到item的下一项。还要注意一点,如果找到的是xListEnd,则需要重新定位到下一个位置,这就是下面代码的含义了。

<code>#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )\t\t\t\t\t\t\t\t\t\t\\
{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
List_t * const pxConstList = ( pxList );\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t/* Increment the index to the next item and return the item, ensuring */\t\t\t\t\\
\t/* we don't return the marker used at the end of the list. */\t\t\t\t\t\t\t\\
\t( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;\t\t\t\t\t\t\t\\
\tif( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )\t\\
\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t\t( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;\t\t\t\t\t\t\\
\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;\t\t\t\t\t\t\t\t\t\t\t\\
}/<code>

最后,需要将uxTopReadyPriority赋值为最高优先级。这也是freertos查找最高优先级任务的方法。另外的方法则是在具体的处理上做优化,主要是利用处理器提供的汇编指令,这里就不分析了。

这就是任务创建的过程。同时也捎带分析系统运行起来之后的任务创建与找到最高优先级任务的过程。

如果系统确实运行起来了,而任务希望调度到另外一个任务如何处理呢?

任务让出自己的运行权限使用的是函数vTaskDelay(),代码如下:


<code>void vTaskDelay( const TickType_t xTicksToDelay )
\t{
\tBaseType_t xAlreadyYielded = pdFALSE;

\t\t/* A delay time of zero just forces a reschedule. */
\t\tif( xTicksToDelay > ( TickType_t ) 0U )
\t\t{
\t\t\tconfigASSERT( uxSchedulerSuspended == 0 );
\t\t\tvTaskSuspendAll();
\t\t\t{
\t\t\t\ttraceTASK_DELAY();

\t\t\t\t/* A task that is removed from the event list while the
\t\t\t\tscheduler is suspended will not get placed in the ready
\t\t\t\tlist or removed from the blocked list until the scheduler
\t\t\t\tis resumed.

\t\t\t\tThis task cannot be in an event list as it is the currently
\t\t\t\texecuting task. */
\t\t\t\tprvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
\t\t\t}
\t\t\txAlreadyYielded = xTaskResumeAll();
\t\t}
\t\telse
\t\t{
\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t}

\t\t/* Force a reschedule if xTaskResumeAll has not already done so, we may
\t\thave put ourselves to sleep. */
\t\tif( xAlreadyYielded == pdFALSE )
\t\t{
\t\t\tportYIELD_WITHIN_API();
\t\t}
\t\telse
\t\t{
\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t}
\t}/<code>


首先,传递给xTicksToDelay()的参数肯定不要为0,因为是想要延时嘛。然后会调用函数vTaskSuspendAll();这个函数就是将变量uxSchedulerSuspended加一。它就可以让整个系统再也不能切换任务了。原因为:切换任务只有2种可能,1个是中断中去切换,1个是任务。而现在任务是主动执行到这条代码,所以只有中断去切,但是中断中判断uxSchedulerSuspended不为0之后就不会去执行切换任务的操作。这个位置是否可以使用关中断呢?其实也是可以的,但这里明显使用vTaskSuspendAll();更合理,因为中断在这个时候还是可以响应的。

之后就需要调用prvAddCurrentTaskToDelayedList(),函数内容为:

<code>static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount;

\t#if( INCLUDE_xTaskAbortDelay == 1 )
\t{
\t\t/* About to enter a delayed list, so ensure the ucDelayAborted flag is
\t\treset to pdFALSE so it can be detected as having been set to pdTRUE
\t\twhen the task leaves the Blocked state. */
\t\tpxCurrentTCB->ucDelayAborted = pdFALSE;
\t}
\t#endif

\t/* Remove the task from the ready list before adding it to the blocked list
\tas the same list item is used for both lists. */
\tif( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
\t{
\t\t/* The current task must be in a ready list, so there is no need to
\t\tcheck, and the port reset macro can be called directly. */
\t\tportRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority ); /*lint !e931 pxCurrentTCB cannot change as it is the calling task. pxCurrentTCB->uxPriority and uxTopReadyPriority cannot change as called with scheduler suspended or in a critical section. */
\t}
\telse
\t{
\t\tmtCOVERAGE_TEST_MARKER();
\t}

\t#if ( INCLUDE_vTaskSuspend == 1 )
\t{
\t\tif( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
\t\t{
\t\t\t/* Add the task to the suspended task list instead of a delayed task
\t\t\tlist to ensure it is not woken by a timing event. It will block
\t\t\tindefinitely. */
\t\t\tvListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
\t\t}
\t\telse
\t\t{
\t\t\t/* Calculate the time at which the task should be woken if the event
\t\t\tdoes not occur. This may overflow but this doesn't matter, the
\t\t\tkernel will manage it correctly. */
\t\t\txTimeToWake = xConstTickCount + xTicksToWait;

\t\t\t/* The list item will be inserted in wake time order. */
\t\t\tlistSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

\t\t\tif( xTimeToWake < xConstTickCount )
\t\t\t{
\t\t\t\t/* Wake time has overflowed. Place this item in the overflow
\t\t\t\tlist. */
\t\t\t\tvListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
\t\t\t}
\t\t\telse
\t\t\t{
\t\t\t\t/* The wake time has not overflowed, so the current block list
\t\t\t\tis used. */
\t\t\t\tvListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

\t\t\t\t/* If the task entering the blocked state was placed at the
\t\t\t\thead of the list of blocked tasks then xNextTaskUnblockTime
\t\t\t\tneeds to be updated too. */
\t\t\t\tif( xTimeToWake < xNextTaskUnblockTime )
\t\t\t\t{
\t\t\t\t\txNextTaskUnblockTime = xTimeToWake;
\t\t\t\t}
\t\t\t\telse
\t\t\t\t{
\t\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t\t}
\t\t\t}
\t\t}
\t}
\t#else /* INCLUDE_vTaskSuspend */
\t{
\t\t/* Calculate the time at which the task should be woken if the event
\t\tdoes not occur. This may overflow but this doesn't matter, the kernel
\t\twill manage it correctly. */
\t\txTimeToWake = xConstTickCount + xTicksToWait;

\t\t/* The list item will be inserted in wake time order. */
\t\tlistSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

\t\tif( xTimeToWake < xConstTickCount )
\t\t{
\t\t\t/* Wake time has overflowed. Place this item in the overflow list. */
\t\t\tvListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
\t\t}
\t\telse
\t\t{
\t\t\t/* The wake time has not overflowed, so the current block list is used. */
\t\t\tvListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

\t\t\t/* If the task entering the blocked state was placed at the head of the
\t\t\tlist of blocked tasks then xNextTaskUnblockTime needs to be updated
\t\t\ttoo. */
\t\t\tif( xTimeToWake < xNextTaskUnblockTime )

\t\t\t{
\t\t\t\txNextTaskUnblockTime = xTimeToWake;
\t\t\t}
\t\t\telse
\t\t\t{
\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t}
\t\t}

\t\t/* Avoid compiler warning when INCLUDE_vTaskSuspend is not 1. */
\t\t( void ) xCanBlockIndefinitely;
\t}
\t#endif /* INCLUDE_vTaskSuspend */
}/<code>

它先将该任务的链表从list中删除出去。如果对应优先级的list为空,由于这个不是优化版本,所以portRESET_READY_PRIORITY()什么也不做。也就是说uxTopReadyPriority还是现在这个最高优先级的值。然后定义一个变量xTimeToWake,它是现在的时间与要延时的时间的和,然后将它的值设置到item中的xItemValue。

然后要看xTimeToWake是否溢出了。如果溢出了,则要将该任务接入pxOverflowDelayedTaskList,否则就接入pxDelayedTaskList。由于系统自己维护一个变量xNextTaskUnblockTime,它表示这个值减为0,就说明有已经到期了。

之后,就需要调用xTaskResumeAll()恢复整个系统的任务调度了,函数内容为:

<code>BaseType_t xTaskResumeAll( void )
{
TCB_t *pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;

\t/* If uxSchedulerSuspended is zero then this function does not match a

\tprevious call to vTaskSuspendAll(). */
\tconfigASSERT( uxSchedulerSuspended );

\t/* It is possible that an ISR caused a task to be removed from an event
\tlist while the scheduler was suspended. If this was the case then the
\tremoved task will have been added to the xPendingReadyList. Once the
\tscheduler has been resumed it is safe to move all the pending ready
\ttasks from this list into their appropriate ready list. */
\ttaskENTER_CRITICAL();
\t{
\t\t--uxSchedulerSuspended;

\t\tif( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
\t\t{
\t\t\tif( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
\t\t\t{
\t\t\t\t/* Move any readied tasks from the pending list into the
\t\t\t\tappropriate ready list. */
\t\t\t\twhile( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
\t\t\t\t{
\t\t\t\t\tpxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
\t\t\t\t\t( void ) uxListRemove( &( pxTCB->xEventListItem ) );
\t\t\t\t\t( void ) uxListRemove( &( pxTCB->xStateListItem ) );
\t\t\t\t\tprvAddTaskToReadyList( pxTCB );

\t\t\t\t\t/* If the moved task has a priority higher than the current
\t\t\t\t\ttask then a yield must be performed. */
\t\t\t\t\tif( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
\t\t\t\t\t{
\t\t\t\t\t\txYieldPending = pdTRUE;
\t\t\t\t\t}
\t\t\t\t\telse
\t\t\t\t\t{
\t\t\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t\t\t}
\t\t\t\t}

\t\t\t\tif( pxTCB != NULL )
\t\t\t\t{
\t\t\t\t\t/* A task was unblocked while the scheduler was suspended,
\t\t\t\t\twhich may have prevented the next unblock time from being
\t\t\t\t\tre-calculated, in which case re-calculate it now. Mainly
\t\t\t\t\timportant for low power tickless implementations, where
\t\t\t\t\tthis can prevent an unnecessary exit from low power
\t\t\t\t\tstate. */
\t\t\t\t\tprvResetNextTaskUnblockTime();
\t\t\t\t}

\t\t\t\t/* If any ticks occurred while the scheduler was suspended then
\t\t\t\tthey should be processed now. This ensures the tick count does

\t\t\t\tnot\tslip, and that any delayed tasks are resumed at the correct
\t\t\t\ttime. */
\t\t\t\t{
\t\t\t\t\tTickType_t xPendedCounts = xPendedTicks; /* Non-volatile copy. */

\t\t\t\t\tif( xPendedCounts > ( TickType_t ) 0U )
\t\t\t\t\t{
\t\t\t\t\t\tdo
\t\t\t\t\t\t{
\t\t\t\t\t\t\tif( xTaskIncrementTick() != pdFALSE )
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\txYieldPending = pdTRUE;
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\telse
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t--xPendedCounts;
\t\t\t\t\t\t} while( xPendedCounts > ( TickType_t ) 0U );

\t\t\t\t\t\txPendedTicks = 0;
\t\t\t\t\t}
\t\t\t\t\telse
\t\t\t\t\t{
\t\t\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t\t\t}
\t\t\t\t}

\t\t\t\tif( xYieldPending != pdFALSE )
\t\t\t\t{
\t\t\t\t\t#if( configUSE_PREEMPTION != 0 )
\t\t\t\t\t{
\t\t\t\t\t\txAlreadyYielded = pdTRUE;
\t\t\t\t\t}
\t\t\t\t\t#endif
\t\t\t\t\ttaskYIELD_IF_USING_PREEMPTION();
\t\t\t\t}
\t\t\t\telse
\t\t\t\t{
\t\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t\t}
\t\t\t}
\t\t}
\t\telse
\t\t{
\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t}
\t}
\ttaskEXIT_CRITICAL();

\treturn xAlreadyYielded;
}/<code>


由于这节并不涉及到xPendingReadyList,所以会直接到代码TickType_t xPendedCounts = xPendedTicks; 注意在锁住调度器的时候,中断xPortSysTickHandler()中会调用到xTaskIncrementTick(),也就是会调用++xPendedTicks; 所以,如果锁住调度器时间太长,这个值就可能为1,2这类值,调用while( xPendedCounts > ( TickType_t ) 0U )与xTaskIncrementTick()目的就是将这个时间弥补到延时列表中。然后如果恰好有任务延时时间到了,而且优先级比当前任务还高,那就需要切换到更高优先级的任务中去。

再来分析分析函数xTaskIncrementTick(),内容为:

<code>BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;

\t/* Called by the portable layer each time a tick interrupt occurs.
\tIncrements the tick then checks to see if the new tick value will cause any
\ttasks to be unblocked. */
\ttraceTASK_INCREMENT_TICK( xTickCount );
\tif( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
\t{
\t\t/* Minor optimisation. The tick count cannot change in this
\t\tblock. */
\t\tconst TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;

\t\t/* Increment the RTOS tick, switching the delayed and overflowed
\t\tdelayed lists if it wraps to 0. */
\t\txTickCount = xConstTickCount;

\t\tif( xConstTickCount == ( TickType_t ) 0U ) /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. */
\t\t{
\t\t\ttaskSWITCH_DELAYED_LISTS();

\t\t}
\t\telse
\t\t{
\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t}

\t\t/* See if this tick has made a timeout expire. Tasks are stored in
\t\tthe\tqueue in the order of their wake time - meaning once one task
\t\thas been found whose block time has not expired there is no need to
\t\tlook any further down the list. */
\t\tif( xConstTickCount >= xNextTaskUnblockTime )
\t\t{
\t\t\tfor( ;; )
\t\t\t{
\t\t\t\tif( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
\t\t\t\t{
\t\t\t\t\t/* The delayed list is empty. Set xNextTaskUnblockTime
\t\t\t\t\tto the maximum possible value so it is extremely
\t\t\t\t\tunlikely that the
\t\t\t\t\tif( xTickCount >= xNextTaskUnblockTime ) test will pass
\t\t\t\t\tnext time through. */
\t\t\t\t\txNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
\t\t\t\t\tbreak;
\t\t\t\t}
\t\t\t\telse
\t\t\t\t{
\t\t\t\t\t/* The delayed list is not empty, get the value of the
\t\t\t\t\titem at the head of the delayed list. This is the time
\t\t\t\t\tat which the task at the head of the delayed list must
\t\t\t\t\tbe removed from the Blocked state. */
\t\t\t\t\tpxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
\t\t\t\t\txItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );

\t\t\t\t\tif( xConstTickCount < xItemValue )
\t\t\t\t\t{
\t\t\t\t\t\t/* It is not time to unblock this item yet, but the
\t\t\t\t\t\titem value is the time at which the task at the head
\t\t\t\t\t\tof the blocked list must be removed from the Blocked
\t\t\t\t\t\tstate -\tso record the item value in
\t\t\t\t\t\txNextTaskUnblockTime. */
\t\t\t\t\t\txNextTaskUnblockTime = xItemValue;
\t\t\t\t\t\tbreak; /*lint !e9011 Code structure here is deedmed easier to understand with multiple breaks. */
\t\t\t\t\t}
\t\t\t\t\telse
\t\t\t\t\t{
\t\t\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t\t\t}

\t\t\t\t\t/* It is time to remove the item from the Blocked state. */
\t\t\t\t\t( void ) uxListRemove( &( pxTCB->xStateListItem ) );


\t\t\t\t\t/* Is the task waiting on an event also? If so remove
\t\t\t\t\tit from the event list. */
\t\t\t\t\tif( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
\t\t\t\t\t{
\t\t\t\t\t\t( void ) uxListRemove( &( pxTCB->xEventListItem ) );
\t\t\t\t\t}
\t\t\t\t\telse
\t\t\t\t\t{
\t\t\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t\t\t}

\t\t\t\t\t/* Place the unblocked task into the appropriate ready
\t\t\t\t\tlist. */
\t\t\t\t\tprvAddTaskToReadyList( pxTCB );

\t\t\t\t\t/* A task being unblocked cannot cause an immediate
\t\t\t\t\tcontext switch if preemption is turned off. */
\t\t\t\t\t#if ( configUSE_PREEMPTION == 1 )
\t\t\t\t\t{
\t\t\t\t\t\t/* Preemption is on, but a context switch should
\t\t\t\t\t\tonly be performed if the unblocked task has a
\t\t\t\t\t\tpriority that is equal to or higher than the
\t\t\t\t\t\tcurrently executing task. */
\t\t\t\t\t\tif( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
\t\t\t\t\t\t{
\t\t\t\t\t\t\txSwitchRequired = pdTRUE;
\t\t\t\t\t\t}
\t\t\t\t\t\telse
\t\t\t\t\t\t{
\t\t\t\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t\t\t\t}
\t\t\t\t\t}
\t\t\t\t\t#endif /* configUSE_PREEMPTION */
\t\t\t\t}
\t\t\t}
\t\t}

\t\t/* Tasks of equal priority to the currently running task will share
\t\tprocessing time (time slice) if preemption is on, and the application
\t\twriter has not explicitly turned time slicing off. */
\t\t#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
\t\t{
\t\t\tif( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
\t\t\t{
\t\t\t\txSwitchRequired = pdTRUE;
\t\t\t}
\t\t\telse
\t\t\t{
\t\t\t\tmtCOVERAGE_TEST_MARKER();

\t\t\t}
\t\t}
\t\t#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */

\t\t#if ( configUSE_TICK_HOOK == 1 )
\t\t{
\t\t\t/* Guard against the tick hook being called when the pended tick
\t\t\tcount is being unwound (when the scheduler is being unlocked). */
\t\t\tif( xPendedTicks == ( TickType_t ) 0 )
\t\t\t{
\t\t\t\tvApplicationTickHook();
\t\t\t}
\t\t\telse
\t\t\t{
\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t}
\t\t}
\t\t#endif /* configUSE_TICK_HOOK */

\t\t#if ( configUSE_PREEMPTION == 1 )
\t\t{
\t\t\tif( xYieldPending != pdFALSE )
\t\t\t{
\t\t\t\txSwitchRequired = pdTRUE;
\t\t\t}
\t\t\telse
\t\t\t{
\t\t\t\tmtCOVERAGE_TEST_MARKER();
\t\t\t}
\t\t}
\t\t#endif /* configUSE_PREEMPTION */
\t}
\telse
\t{
\t\t++xPendedTicks;

\t\t/* The tick hook gets called at regular intervals, even if the
\t\tscheduler is locked. */
\t\t#if ( configUSE_TICK_HOOK == 1 )
\t\t{
\t\t\tvApplicationTickHook();
\t\t}
\t\t#endif
\t}

\treturn xSwitchRequired;
}/<code>


在系统运行中,延时表list与超时表list只需要检测一个即可。检测条件就是看xTickCount + ( TickType_t ) 1是否为0,为0说明已经溢出了。

首先要明确一点,如果系统有未溢出的延时值,那么溢出的延时值就不用检测,因为溢出的延时值肯定更大,而如果xTickCount 溢出,就说明需要将延时list与溢出list进行切换,因为溢出list才是重新开始按照一个新的周期来计算的。这就是代码taskSWITCH_DELAYED_LISTS()的意义。


<code>#define taskSWITCH_DELAYED_LISTS()\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\tList_t *pxTemp;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\t/* The delayed tasks list should be empty when the lists are switched. */\t\t\t\t\t\t\\
\tconfigASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList ) ) );\t\t\t\t\t\t\t\t\t\t\\
\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\tpxTemp = pxDelayedTaskList;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\tpxDelayedTaskList = pxOverflowDelayedTaskList;\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\tpxOverflowDelayedTaskList = pxTemp;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\txNumOfOverflows++;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
\tprvResetNextTaskUnblockTime();\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\
}/<code>

如果xConstTickCount >= xNextTaskUnblockTime,说明有延时任务已经到期了。如果延时表pxDelayedTaskList还没为空,那就需要将第一个到期的延时任务移除出延时表。然后循环操作,发现下一个延时任务的延时时间大于xConstTickCount,那需要重新设置xNextTaskUnblockTime为下一个延时值,然后可以退出函数。如果找到了一个延时任务,然后溢出延时表,发现延时表为空了,那就设置xNextTaskUnblockTime = portMAX_DELAY;

移除出延时表的任务,需要添加到就绪表中,然后判断这个任务是否比当前系统运行中的任务优先级高,如果高的话,那么说明系统需要重新进行调度。

以上就是整个vTaskDelay()执行之后程序运行起来的一个大概流程。


到这里,这节就基本上讲完了。


分享到:


相關文章: