FreeRTOS 原理 --- 任务调度机制
任务的状态
- 运行态
- 就绪态
- 阻塞态(被动让出CPU)
- 挂起态(主动让出CPU)
就绪态、阻塞态、挂起态的任务都是由链表进行组织管理
/* Task states returned by eTaskGetState. */ typedef enum { eRunning = 0, /* A task is querying the state of itself, so must be running. */ eReady, /* The task being queried is in a read or pending ready list. */ eBlocked, /* The task being queried is in the Blocked state. */ eSuspended, /* The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */ eDeleted, /* The task being queried has been deleted, but its TCB has not yet been freed. */ eInvalid /* Used as an 'invalid state' value. */ } eTaskState;
链表操作
如下结构体实例描述链表,可称为链表描述符
typedef struct xLIST { volatile UBaseType_t uxNumberOfItems; // 此链表包含多少个任务 ListItem_t * configLIST_VOLATILE pxIndex; // 由 listGET_OWNER_OF_NEXT_ENTRY 可知,pxIndex 指向当前正在执行的任务 MiniListItem_t xListEnd; // 作为链表的一个标记项,不指向任何一个任务,初始化链表描述符,pxIndex 指向 xListEnd } List_t;
如下结构体实例描述链表项
struct xLIST_ITEM { configLIST_VOLATILE TickType_t xItemValue; // 表示处于非就绪态任务唤醒的时刻;插入阻塞链表,会根据此值决定插入的位置 struct xLIST_ITEM * configLIST_VOLATILE pxNext; // 指向此链表项的下一个链表项 struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; // 指向此链表项的上一个链表项 void * pvOwner; // 指向此链表项对应的任务描述符 void * configLIST_VOLATILE pvContainer; // 指向此链表项所在链表描述符 };
由如下链表描述符初始化可知,链表内最开始有一个标记项,不对应任何任务,标记项的上一个链表项和下一个链表项都指向自己,所以是一个循环链表;pxIndex 最开始指向标记项。
void vListInitialise( List_t * const pxList ) { /* The list structure contains a list item which is used to mark the end of the list. To initialise the list the list end is inserted as the only list entry. */ pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */ /* The list end value is the highest possible value in the list to ensure it remains at the end of the list. */ pxList->xListEnd.xItemValue = portMAX_DELAY; /* The list end next and previous pointers point to itself so we know when the list is empty. */ pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */ pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */ pxList->uxNumberOfItems = ( UBaseType_t ) 0U; /* Write known values into the list if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */ listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList ); listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList ); }
如下,链表项插入就绪链表,就是把链表项插入 pxIndex 指向的链表项的前面
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem ) { ListItem_t * const pxIndex = pxList->pxIndex; /* Only effective when configASSERT() is also defined, these tests may catch the list data structures being overwritten in memory. They will not catch data errors caused by incorrect configuration or use of FreeRTOS. */ listTEST_LIST_INTEGRITY( pxList ); listTEST_LIST_ITEM_INTEGRITY( pxNewListItem ); /* Insert a new list item into pxList, but rather than sort the list, makes the new list item the last item to be removed by a call to listGET_OWNER_OF_NEXT_ENTRY(). */ pxNewListItem->pxNext = pxIndex; pxNewListItem->pxPrevious = pxIndex->pxPrevious; /* Only used during decision coverage testing. */ mtCOVERAGE_TEST_DELAY(); pxIndex->pxPrevious->pxNext = pxNewListItem; pxIndex->pxPrevious = pxNewListItem; /* Remember which list the item is in. */ pxNewListItem->pvContainer = ( void * ) pxList; ( pxList->uxNumberOfItems )++; }
如下,链表项插入阻塞链表,根据 xItemValue 决定插入的位置,按从小到大的排列顺序,最后一个链表项是标记项
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem ) { ListItem_t *pxIterator; const TickType_t xValueOfInsertion = pxNewListItem->xItemValue; /* Only effective when configASSERT() is also defined, these tests may catch the list data structures being overwritten in memory. They will not catch data errors caused by incorrect configuration or use of FreeRTOS. */ listTEST_LIST_INTEGRITY( pxList ); listTEST_LIST_ITEM_INTEGRITY( pxNewListItem ); /* Insert the new list item into the list, sorted in xItemValue order. If the list already contains a list item with the same item value then the new list item should be placed after it. This ensures that TCB's which are stored in ready lists (all of which have the same xItemValue value) get a share of the CPU. However, if the xItemValue is the same as the back marker the iteration loop below will not end. Therefore the value is checked first, and the algorithm slightly modified if necessary. */ if( xValueOfInsertion == portMAX_DELAY ) { pxIterator = pxList->xListEnd.pxPrevious; } else { /* *** NOTE *********************************************************** If you find your application is crashing here then likely causes are listed below. In addition see http://www.freertos.org/FAQHelp.html for more tips, and ensure configASSERT() is defined! http://www.freertos.org/a00110.html#configASSERT 1) Stack overflow - see http://www.freertos.org/Stacks-and-stack-overflow-checking.html 2) Incorrect interrupt priority assignment, especially on Cortex-M parts where numerically high priority values denote low actual interrupt priorities, which can seem counter intuitive. See http://www.freertos.org/RTOS-Cortex-M3-M4.html and the definition of configMAX_SYSCALL_INTERRUPT_PRIORITY on http://www.freertos.org/a00110.html 3) Calling an API function from within a critical section or when the scheduler is suspended, or calling an API function that does not end in "FromISR" from an interrupt. 4) Using a queue or semaphore before it has been initialised or before the scheduler has been started (are interrupts firing before vTaskStartScheduler() has been called?). **********************************************************************/ for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */ { /* There is nothing to do here, just iterating to the wanted insertion position. */ } } pxNewListItem->pxNext = pxIterator->pxNext; pxNewListItem->pxNext->pxPrevious = pxNewListItem; pxNewListItem->pxPrevious = pxIterator; pxIterator->pxNext = pxNewListItem; /* Remember which list the item is in. This allows fast removal of the item later. */ pxNewListItem->pvContainer = ( void * ) pxList; ( pxList->uxNumberOfItems )++; }
从链表中删除链表项操作如下:
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove ) { /* The list item knows which list it is in. Obtain the list from the list item. */ List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer; pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious; pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext; /* Only used during decision coverage testing. */ mtCOVERAGE_TEST_DELAY(); /* Make sure the index is left pointing to a valid item. */ if( pxList->pxIndex == pxItemToRemove ) { pxList->pxIndex = pxItemToRemove->pxPrevious; } else { mtCOVERAGE_TEST_MARKER(); } pxItemToRemove->pvContainer = NULL; ( pxList->uxNumberOfItems )--; return pxList->uxNumberOfItems; }
任务控制块描述符
typedef struct tskTaskControlBlock { volatile StackType_t *pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */ #if ( portUSING_MPU_WRAPPERS == 1 ) xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */ #endif ListItem_t xStateListItem; // 链表项,根据任务状态的不同,会被加入Ready, Blocked, Suspended等链表 ListItem_t xEventListItem; // 链表项,可以被插入不同的链表,同一时刻只能被插入某一个链表。比如,当任务因队列满无法往里写导致阻塞,
会把这个链表项插入队列的写等待链表;当任务因获取不到信号量,会把这个链表项插入信号量的读等待链表 UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */ StackType_t *pxStack; /*< Points to the start of the stack. */ char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ #if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) ) StackType_t *pxEndOfStack; /*< Points to the highest valid address for the stack. */ #endif #if ( portCRITICAL_NESTING_IN_TCB == 1 ) UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */ #endif #if ( configUSE_TRACE_FACILITY == 1 ) UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */ UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */ #endif #if ( configUSE_MUTEXES == 1 ) UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */ 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; /*< Stores the amount of time the task has spent in the Running state. */ #endif #if ( configUSE_NEWLIB_REENTRANT == 1 ) /* Allocate a Newlib reent structure that is specific to this task. Note Newlib support has been included by popular demand, but is not used by the FreeRTOS maintainers themselves. FreeRTOS is not responsible for resulting newlib operation. User must be familiar with newlib and must provide system-wide implementations of the necessary stubs. Be warned that (at the time of writing) the current newlib design implements a system-wide malloc() that must be provided with locks. */ struct _reent xNewLib_reent; #endif #if( configUSE_TASK_NOTIFICATIONS == 1 ) volatile uint32_t ulNotifiedValue; volatile uint8_t ucNotifyState; #endif /* See the comments above the definition of tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */ #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 Macro has been consolidated for readability reasons. */ uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */ #endif #if( INCLUDE_xTaskAbortDelay == 1 ) uint8_t ucDelayAborted; #endif } tskTCB;
就绪链表
就绪态的任务,每个任务优先级对应一个链表,如下:
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ] = {0}; /*< Prioritised ready tasks. */
xPortPendSVHandler 中断会取出就绪任务中最高优先级的任务,并且切换到此任务执行。取出任务的规则:如果高优先级链表有任务,则取出;否则去低优先级链表取任务,其内调用如下宏:
#define taskSELECT_HIGHEST_PRIORITY_TASK() \ { \ UBaseType_t uxTopPriority; \ \ /* Find the highest priority list that contains ready tasks. */ \ portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \ configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \ listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \ } /* taskSELECT_HIGHEST_PRIORITY_TASK() */
如下可知,获取任务时,先让 pxIndex 指向下一个链表项,如果下一个链表项是标记项,则让 pxIndex 指向标记项的下一个链表项,然后 pxIndex 对应的任务,所以 pxIndex 就是指向当前正在指向任务对应的链表项。由于插入的任务都是放在 pxIndex 的前面,所以保证后插入的任务后执行
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \ { \ List_t * const pxConstList = ( pxList ); \ /* Increment the index to the next item and return the item, ensuring */ \ /* we don't return the marker used at the end of the list. */ \ ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \ if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \ { \ ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \ } \ ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \ }
阻塞链表
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList = NULL; /*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList = NULL; /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
当获取不到互斥锁,把当前任务从就绪链表移出,然后添加到阻塞链表
挂起链表
PRIVILEGED_DATA static List_t xSuspendedTaskList = {0};
执行 vTaskSuspend() 函数主动挂起某个任务,会把任务对应的链表项加入挂起链表
何时触发任务切换
tick中断
void xPortSysTickHandler( void ) { /* The SysTick runs at the lowest interrupt priority, so when this interrupt executes all interrupts must be unmasked. There is therefore no need to save and then restore the interrupt mask value as its value is already known. */ portDISABLE_INTERRUPTS(); { /* Increment the RTOS tick. */ if( xTaskIncrementTick() != pdFALSE ) { /* A context switch is required. Context switching is performed in the PendSV interrupt. Pend the PendSV interrupt. */ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; } } portENABLE_INTERRUPTS(); }
在 tick 中断,会判断当前被延时的任务是否延时结束,结束则把任务就从阻塞链表移除,并添加到就绪链表。
在这些延时结束的任务中,如果有优先级大于当前任务优先级,则 tick 中断后触发任务切换;如果当前任务所在链表有超过一个的任务,也触发任务切换,达到时间片轮询的目的,所以一个时间片就是一个tick周期。
高优先级任务变为就绪态
- 释放信号量导致高优先级任务可以获得信号量
- 释放互斥锁导致高优先级任务可以获得互斥锁
- 创建了一个高优先级任务,且调度器已启动
- ......
当释放信号量,会把等待此信号量的任务从阻塞链表移到就绪链表,如果优先级大于当前正在执行的任务,则调用如下函数触发 xPortPendSVHandler 中断
#define taskYIELD_IF_USING_PREEMPTION() portYIELD_WITHIN_API() #define portYIELD_WITHIN_API portYIELD #define portYIELD() \ { \ /* Set a PendSV to request a context switch. */ \ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \ __DSB(); \ __ISB(); \ }
当前任务放弃CPU
- 调用 vTaskDelay()