任务创建函数分析

任务创建可以使用xTaskCreate()和 xTaskCreateStatic(),我们就以函数 xTaskCreate()为例来分析一下FreeRTOS 的任务创建过程,函数 xTaskCreateStatic()类似。

  1. BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,
  2. const char * const pcName,
  3. const uint16_t usStackDepth,
  4. void * const pvParameters,
  5. UBaseType_t uxPriority,
  6. TaskHandle_t * const pxCreatedTask )
  7. {
  8. TCB_t *pxNewTCB;
  9. BaseType_t xReturn;
  10. /********************************************************************/
  11. ....................
  12. /********************************************************************/
  13. StackType_t *pxStack;
  14. //动态堆栈空间
  15. pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
  16. if( pxStack != NULL )
  17. {
  18. //申请任务控制块空间
  19. pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
  20. if( pxNewTCB != NULL )
  21. {
  22. //将之前申请的堆栈空间赋值到任务控制块内
  23. pxNewTCB->pxStack = pxStack;
  24. }
  25. else
  26. {
  27. vPortFree( pxStack );
  28. }
  29. }
  30. else
  31. {
  32. pxNewTCB = NULL;
  33. }
  34. if( pxNewTCB != NULL )
  35. {
  36. #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
  37. {
  38. //标记任务堆栈和任务控制块是使用动态内存分配方法得到的
  39. pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
  40. }
  41. #endif /* configSUPPORT_STATIC_ALLOCATION */
  42. //完成对任务控制块中各个字段的初始化工作
  43. prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, \
  44. pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
  45. //将任务控制块加入到就绪列表中
  46. prvAddNewTaskToReadyList( pxNewTCB );
  47. xReturn = pdPASS;
  48. }
  49. else
  50. {
  51. xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
  52. }
  53. return xReturn;
  54. }

任务初始化函数分析

在任务创建函数中我们看到了,任务的初始化是通过prvInitialiseNewTask()函数完成的

  1. static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
  2. const char * const pcName,
  3. const uint32_t ulStackDepth,
  4. void * const pvParameters,
  5. UBaseType_t uxPriority,
  6. TaskHandle_t * const pxCreatedTask,
  7. TCB_t *pxNewTCB,
  8. const MemoryRegion_t * const xRegions )
  9. {
  10. StackType_t *pxTopOfStack;
  11. UBaseType_t x;
  12. /*如果使能了堆栈溢出检测功能或者追踪功能的话 就使用一个定值tskSTACK_FILL_BYTE 来填充任务堆栈,这个值为 0xa5U。 */
  13. #if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || INCLUDE_uxTaskGetStackHighWaterMark == 1 ) )
  14. {
  15. /* Fill the stack with a known value to assist debugging. */
  16. ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
  17. }
  18. #endif
  19. /* Calculate the top of stack address. This depends on whether the stack
  20. grows from high memory to low (as per the 80x86) or vice versa.
  21. portSTACK_GROWTH is used to make the result positive or negative as required
  22. by the port. */
  23. #if( portSTACK_GROWTH < 0 )
  24. {
  25. //计算堆栈的栈顶指针
  26. pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
  27. pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
  28. /* Check the alignment of the calculated top of stack is correct. */
  29. configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
  30. }
  31. #else /* portSTACK_GROWTH */
  32. {
  33. pxTopOfStack = pxNewTCB->pxStack;
  34. /* Check the alignment of the stack buffer is correct. */
  35. configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
  36. /* The other extreme of the stack space is required if stack checking is
  37. performed. */
  38. pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
  39. }
  40. #endif /* portSTACK_GROWTH */
  41. /* 将任务名字存储在任务块中 */
  42. for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
  43. {
  44. pxNewTCB->pcTaskName[ x ] = pcName[ x ];
  45. /* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than
  46. configMAX_TASK_NAME_LEN characters just in case the memory after the
  47. string is not accessible (extremely unlikely). */
  48. if( pcName[ x ] == 0x00 )
  49. {
  50. break;
  51. }
  52. else
  53. {
  54. mtCOVERAGE_TEST_MARKER();
  55. }
  56. }
  57. /* Ensure the name string is terminated in the case that the string length
  58. was greater or equal to configMAX_TASK_NAME_LEN. */
  59. pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
  60. /* 检查设置任务优先级是否超出了范围了. */
  61. if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
  62. {
  63. uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
  64. }
  65. else
  66. {
  67. mtCOVERAGE_TEST_MARKER();
  68. }
  69. //设置任务优先级
  70. pxNewTCB->uxPriority = uxPriority;
  71. //如果有互斥信号量,那就要初始化互斥信号量
  72. #if ( configUSE_MUTEXES == 1 )
  73. {
  74. pxNewTCB->uxBasePriority = uxPriority;
  75. pxNewTCB->uxMutexesHeld = 0;
  76. }
  77. #endif /* configUSE_MUTEXES */
  78. //初始化列表项 xStateListItem 和 xEventListItem,任务控制块结构体中有两个列表项,这里对这两个列表项做初始化。
  79. vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
  80. vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
  81. /* 设置列表项 xStateListItem 和 xEventListItem 属于当前任务的任务控制块,也就是设置这两个列表项的字段 pvOwner 为新创建的任务的任务控制块。 */
  82. listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
  83. /* 设置列表项xEventListItem的字段xItemValue为configMAX_PRIORITIES- uxPriority,
  84. * 比如当前任务优先级 3,最大优先级为 32,那么 xItemValue 就为 32-3=29,这就意味着 xItemValue
  85. * 值越大,优先级就越小。上一章学习列表和列表项的时候我们说过,列表的插入是按照
  86. * xItemValue 的值升序排列的。
  87. */
  88. listSET_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. */
  89. listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
  90. #if ( portCRITICAL_NESTING_IN_TCB == 1 )
  91. {
  92. pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
  93. }
  94. #endif /* portCRITICAL_NESTING_IN_TCB */
  95. #if ( configUSE_APPLICATION_TASK_TAG == 1 )
  96. {
  97. pxNewTCB->pxTaskTag = NULL;
  98. }
  99. #endif /* configUSE_APPLICATION_TASK_TAG */
  100. #if ( configGENERATE_RUN_TIME_STATS == 1 )
  101. {
  102. pxNewTCB->ulRunTimeCounter = 0UL;
  103. }
  104. #endif /* configGENERATE_RUN_TIME_STATS */
  105. #if ( portUSING_MPU_WRAPPERS == 1 )
  106. {
  107. vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );
  108. }
  109. #else
  110. {
  111. /* Avoid compiler warning about unreferenced parameter. */
  112. ( void ) xRegions;
  113. }
  114. #endif
  115. //初始化线程本地存储指针,如果使能了这个功能的话。
  116. #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
  117. {
  118. for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
  119. {
  120. pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL;
  121. }
  122. }
  123. #endif
  124. #if ( configUSE_TASK_NOTIFICATIONS == 1 )
  125. {
  126. pxNewTCB->ulNotifiedValue = 0;
  127. pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
  128. }
  129. #endif
  130. #if ( configUSE_NEWLIB_REENTRANT == 1 )
  131. {
  132. /* Initialise this task's Newlib reent structure. */
  133. _REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
  134. }
  135. #endif
  136. #if( INCLUDE_xTaskAbortDelay == 1 )
  137. {
  138. pxNewTCB->ucDelayAborted = pdFALSE;
  139. }
  140. #endif
  141. /* 调用函数 pxPortInitialiseStack()初始化任务堆栈。*/
  142. #if( portUSING_MPU_WRAPPERS == 1 )
  143. {
  144. pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
  145. }
  146. #else /* portUSING_MPU_WRAPPERS */
  147. {
  148. pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
  149. }
  150. #endif /* portUSING_MPU_WRAPPERS */
  151. //生成任务控制块,返回给参数 pxCreatedTask,从这里可以看出任务句柄其实就是任务控制块。
  152. if( ( void * ) pxCreatedTask != NULL )
  153. {
  154. /* Pass the handle out in an anonymous way. The handle can be used to
  155. change the created task's priority, delete the created task, etc.*/
  156. *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
  157. }
  158. else
  159. {
  160. mtCOVERAGE_TEST_MARKER();
  161. }
  162. }

任务堆栈初始化

通过任务初始化的分析我们知道,最终会调用堆栈初始化函数pxPortInitialiseStack()。

  1. StackType_t *pxPortInitialiseStack( StackType_t * pxTopOfStack,
  2. TaskFunction_t pxCode,
  3. void * pvParameters )
  4. {
  5. pxTopOfStack--;
  6. // 设置xPSR 值为0x01000000,表示使用 Thumb 指令
  7. *pxTopOfStack = portINITIAL_XPSR;
  8. pxTopOfStack--;
  9. //设置PC寄存器地址为pxcode,也就是我们的任务函数
  10. *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;
  11. pxTopOfStack--;
  12. //设置LR的寄存器为 prvTaskExitError函数
  13. *pxTopOfStack = ( StackType_t ) prvTaskExitError;
  14. //跳过 4 个寄存器,R12,R3,R2,R1
  15. pxTopOfStack -= 5;
  16. //设置R0 初始化为 pvParameters。一般情况下,函数调用会将 R0~R3 作为输入参数,R0 也可用作返回结果,如果返回值为 64 位,则 R1 也会用于返回结果
  17. *pxTopOfStack = ( StackType_t ) pvParameters;
  18. pxTopOfStack--;
  19. //保存 EXC_RETURN 值,用于退出 SVC 或 PendSV 中断的时候处理器应该处于什么壮态。
  20. *pxTopOfStack = portINITIAL_EXEC_RETURN;
  21. //跳过 8 个寄存器,R11、R10、R8、R7、R6、R5、R4。
  22. pxTopOfStack -= 8;
  23. return pxTopOfStack;
  24. }

堆栈是用来在进行上下文切换的时候保存现场的,一般在新创建好一个堆栈以后会对其先进行初始化处理,即对 Cortex-M 内核的某些寄存器赋初值。这些初值就保存在任务堆栈中,保存的顺序按照:xPSR、R15(PC)、R14(LR)、R12、R3R0、R11R14。
07.任务创建过程 - 图1

添加任务到就绪列表

任务创建完成以后就会被添加到就绪列表中,FreeRTOS 使用不同的列表表示任务的不同状态,在文件 tasks.c 中就定义了多个列表来完成不同的功能,这些列表如下:

  1. //列表数组 pxReadyTasksLists[]就是任务就绪列表,数组大小为 configMAX_PRIORITIES,也就是说一个优先级一个列表,这样相同优先级的任务就使用一个列表。
  2. PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
  3. PRIVILEGED_DATA static List_t xDelayedTaskList1;
  4. PRIVILEGED_DATA static List_t xDelayedTaskList2;
  5. PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
  6. PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;
  7. PRIVILEGED_DATA static List_t xPendingReadyList;
  1. static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
  2. {
  3. taskENTER_CRITICAL();
  4. {
  5. //变量 uxCurrentNumberOfTasks 为全局变量,用来统计任务数量。
  6. uxCurrentNumberOfTasks++;
  7. //正在运行任务块为 NULL,说明没有任务运行!
  8. if( pxCurrentTCB == NULL )
  9. {
  10. //将新任务的任务控制块赋值给 pxCurrentTCB
  11. pxCurrentTCB = pxNewTCB;
  12. //新创建的任务是第一个任务
  13. if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
  14. {
  15. //需要先初始化相应的列表,通过调用函数 prvInitialiseTaskLists()来初始化相应的列表。
  16. prvInitialiseTaskLists();
  17. }
  18. else
  19. {
  20. mtCOVERAGE_TEST_MARKER();
  21. }
  22. }
  23. else
  24. {
  25. if( xSchedulerRunning == pdFALSE )
  26. {
  27. //新任务的任务优先级比正在运行的任务优先级高。
  28. if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
  29. {
  30. pxCurrentTCB = pxNewTCB;
  31. }
  32. else
  33. {
  34. mtCOVERAGE_TEST_MARKER();
  35. }
  36. }
  37. else
  38. {
  39. mtCOVERAGE_TEST_MARKER();
  40. }
  41. }
  42. //uxTaskNumber 加一,用作任务控制块编号。
  43. uxTaskNumber++;
  44. #if ( configUSE_TRACE_FACILITY == 1 )
  45. {
  46. /* Add a counter into the TCB for tracing only. */
  47. pxNewTCB->uxTCBNumber = uxTaskNumber;
  48. }
  49. #endif /* configUSE_TRACE_FACILITY */
  50. traceTASK_CREATE( pxNewTCB );
  51. //将任务控制块插入到就绪列表中
  52. prvAddTaskToReadyList( pxNewTCB );
  53. portSETUP_TCB( pxNewTCB );
  54. }
  55. taskEXIT_CRITICAL();
  56. if( xSchedulerRunning != pdFALSE )
  57. {
  58. //新任务优先级比正在运行的任务优先级高
  59. if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
  60. {
  61. //如果新任务的任务优先级最高,而且调度器已经开始正常运行了,那么就调用函数taskYIELD_IF_USING_PREEMPTION()完成一次任务切换。
  62. taskYIELD_IF_USING_PREEMPTION();
  63. }
  64. else
  65. {
  66. mtCOVERAGE_TEST_MARKER();
  67. }
  68. }
  69. else
  70. {
  71. mtCOVERAGE_TEST_MARKER();
  72. }
  73. }