FreeRTOS -- creation task of task management

Posted by KeeganWolf on Wed, 02 Mar 2022 15:26:43 +0100

catalogue

1. Describe the structure of the task

2. Task creation

2.1,xTaskCreate

2.2,prvInitialiseNewTask

2.3,pxPortInitialiseStack

2.4,prvAddNewTaskToReadyList

In< FreeRTOS -- (7) introduction to task management >Article basic analysis After the task related outline is, we know what interface to use to create a task, how to start the scheduler, and select the behavior of the scheduler according to the macro configuration; Next, let's go deep into the source code of FreeRTOS task creation to see how a task is created (a great God said that Read The F**king Source Code can be solved with code, and try not to BB);

 

1. Describe the structure of the task

stay FreeRTOS In, TCB is used_ T to describe a task:

   
  1. /*
  2. * Task control block. A task control block (TCB) is allocated for each task,
  3. * and stores task state information, including a pointer to the task's context
  4. * (the task's run time environment, including register values)
  5. */
  6. typedef struct tskTaskControlBlock
  7. {
  8. volatile StackType_t *pxTopOfStack; /*The top of the current stack must be at the first item of the structure*/
  9. #if ( portUSING_MPU_WRAPPERS == 1 )
  10. xMPU_SETTINGS xMPUSettings; /*MPU Setting must be in the second item of the structure*/
  11. #endif
  12. ListItem_t xStateListItem; /*The status list item of the task, which represents the status of the task by reference*/
  13. ListItem_t xEventListItem; /*Event list item, which is used to attach the task to the event list by reference*/
  14. UBaseType_t uxPriority; /*Save task priority. 0 indicates the lowest priority*/
  15. StackType_t *pxStack; /*Points to the start of the stack*/
  16. char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*Task name*/
  17. #if ( portSTACK_GROWTH > 0 )
  18. StackType_t *pxEndOfStack; /*Points to the end of the stack*/
  19. #endif
  20. #if ( portCRITICAL_NESTING_IN_TCB == 1 )
  21. UBaseType_t uxCriticalNesting; /*Save critical nesting depth*/
  22. #endif
  23. #if ( configUSE_TRACE_FACILITY == 1 )
  24. UBaseType_t uxTCBNumber; /*Save a value. Each task has a unique value*/
  25. UBaseType_t uxTaskNumber; /*Store a specific value*/
  26. #endif
  27. #if ( configUSE_MUTEXES == 1 )
  28. UBaseType_t uxBasePriority; /*Save the basic priority of the task*/
  29. UBaseType_t uxMutexesHeld;
  30. #endif
  31. #if ( configUSE_APPLICATION_TASK_TAG == 1 )
  32. TaskHookFunction_t pxTaskTag;
  33. #endif
  34. #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
  35. void *pvThreadLocalStoragePointers[configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
  36. #endif
  37. #if( configGENERATE_RUN_TIME_STATS == 1 )
  38. uint32_t ulRunTimeCounter; /*Record the total execution time of the task in the running state*/
  39. #endif
  40. #if ( configUSE_NEWLIB_REENTRANT == 1 )
  41. /* Assign a Newlibreent structure variable to the task. Newlib is a C library function, which is not maintained by FreeRTOS, and FreeRTOS is not responsible for the use results. If users use newlib, they must be familiar with the details of newlib*/
  42. struct _reent xNewLib_reent;
  43. #endif
  44. #if( configUSE_TASK_NOTIFICATIONS == 1 )
  45. volatile uint32_t ulNotifiedValue; /*Related to task notification*/
  46. volatile uint8_t ucNotifyState;
  47. #endif
  48. #if( configSUPPORT_STATIC_ALLOCATION == 1 )
  49. uint8_t ucStaticAllocationFlags; /* Set to pdTRUE if the stack is allocated by a static array or pdFALSE if the stack is allocated dynamically*/
  50. #endif
  51. #if( INCLUDE_xTaskAbortDelay == 1 )
  52. uint8_t ucDelayAborted;
  53. #endif
  54. } tskTCB;
  55. typedef tskTCB TCB_t;

1. pxTopOfStack must be in the first item of the structure and point to the top of the current stack. For a downward growing stack, pxTopOfStack always points to the last content put into the stack. The pointer will move;

2. If MPU is used, xmpussettings must be located in the second item of the structure for MPU settings.

3. The status linked list xStateListItem is used to link tasks to different status linked lists. For example, if the task is in Ready status, it should be linked to the Ready linked list;

4. List similar events;

5. uxPriority is used to save the priority of the task, and 0 is the lowest priority;

6. pxStack points to the starting position of the stack, and the pointer returned by the memory function of the application stack is assigned to the variable. pxTopOfStack points to the top of the current stack. The position pointed by pxTopOfStack will change as the stack goes in and out; pxStack points to the starting position of the current stack. Once allocated, the starting position of the stack will be fixed and will not be changed. So why do you need the pxStack variable? This is because the stack may overflow as the task runs. In a system where the stack grows downward, this variable can be used to check whether the stack overflows; If you want to determine whether the stack overflows in the system with upward stack growth, you need another variable pxEndOfStack to help diagnose whether the stack overflows;

7. pcTaskName is used to save the description or name of the task. The length of the name is determined by the macro configMAX_TASK_NAME_LEN (in FreeRTOSConfig.h) is specified and contains the end of string flag.

8. If the stack grows upward (portstack_growth > 0), the pointer pxEndOfStack points to the end of the stack to check whether the stack overflows.

9. uxCriticalNesting is used to save the nesting depth of critical areas. The initial value is 0.

10. Only if macro configure_ TRACE_ Valid when facility (in FreeRTOSConfig.h) is 1. The variable uxTCBNumber stores a value. When creating a task, the kernel automatically assigns the value (usually the value increases by 1 for each task). The uxTCBNumber value of each task is different and is mainly used for debugging. The variable uxTaskNumber is used to store a specific value. Unlike the variable uxTCBNumber, the value of uxTaskNumber is not allocated by the kernel, but set through the API function vTaskSetTaskNumber(), and the value is specified by the function parameters.

11. If mutex is used (configure_mutexes = = 1), when the task priority is temporarily increased, the variable uxBasePriority is used to save the original priority of the task.

12. The variable ucStaticAllocationFlags also needs to be explained. As we said earlier, the task creation API function xTaskCreate() can only use dynamic memory allocation to create task stack and task TCB. If you want to use static variables to implement task stack and task TCB, you need to use the function xTaskGenericCreate(). If the task stack or task TCB is implemented by static array and static variable, set the variable to pdTRUE (0x01 when the task stack space is implemented by static array variable, 0x02 when the task TCB is implemented by static variable, and 0x03 when both task stack and task TCB are implemented by static variable). If the stack is dynamically allocated, set the variable to pdFALSE.

 

2. Task creation

2.1,xTaskCreate

To create a task, use xtask create Interface , the parameters passed in< FreeRTOS -- (7) introduction to task management >It is described in. I won't say more here. Let's take a direct look at its implementation in task In C:

   
  1. BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
  2. const char * const pcName,
  3. const configSTACK_DEPTH_TYPE usStackDepth,
  4. void * const pvParameters,
  5. UBaseType_t uxPriority,
  6. TaskHandle_t * const pxCreatedTask )
  7. {
  8. TCB_t *pxNewTCB;
  9. BaseType_t xReturn;
  10. /* If the stack grows down then allocate the stack then the TCB so the stack
  11. does not grow into the TCB. Likewise if the stack grows up then allocate
  12. the TCB then the stack. */
  13. #if( portSTACK_GROWTH > 0 )
  14. {
  15. /* Allocate space for the TCB. Where the memory comes from depends on
  16. the implementation of the port malloc function and whether or not static
  17. allocation is being used. */
  18. pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
  19. if( pxNewTCB != NULL )
  20. {
  21. /* Allocate space for the stack used by the task being created.
  22. The base of the stack memory stored in the TCB so the task can
  23. be deleted later if required. */
  24. pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
  25. if( pxNewTCB->pxStack == NULL )
  26. {
  27. /* Could not allocate the stack. Delete the allocated TCB. */
  28. vPortFree( pxNewTCB );
  29. pxNewTCB = NULL;
  30. }
  31. }
  32. }
  33. #else /* portSTACK_GROWTH */
  34. {
  35. StackType_t *pxStack;
  36. /* Allocate space for the stack used by the task being created. */
  37. pxStack = 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. */
  38. if( pxStack != NULL )
  39. {
  40. /* Allocate space for the TCB. */
  41. pxNewTCB = ( 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. */
  42. if( pxNewTCB != NULL )
  43. {
  44. /* Store the stack location in the TCB. */
  45. pxNewTCB->pxStack = pxStack;
  46. }
  47. else
  48. {
  49. /* The stack cannot be used as the TCB was not created. Free
  50. it again. */
  51. vPortFree( pxStack );
  52. }
  53. }
  54. else
  55. {
  56. pxNewTCB = NULL;
  57. }
  58. }
  59. #endif /* portSTACK_GROWTH */
  60. if( pxNewTCB != NULL )
  61. {
  62. #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */
  63. {
  64. /* Tasks can be created statically or dynamically, so note this
  65. task was created dynamically in case it is later deleted. */
  66. pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
  67. }
  68. #endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */
  69. prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
  70. prvAddNewTaskToReadyList( pxNewTCB );
  71. xReturn = pdPASS;
  72. }
  73. else
  74. {
  75. xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
  76. }
  77. return xReturn;
  78. }

The first is based on portSTACK_GROWTH macro to determine the growth direction of the current processor architecture stack, portSTACK_GROWTH > 0 means that the stack grows upward, portSTACK_GROWTH < 0 means that the stack grows downward; The growth direction of the stack is closely related to the architecture of the processor. Here, take the Cortex-M series processor as an example. Its stack grows downward. Therefore, when transplanting FreeRTOS to Cortex-M series processors, we must remember to use "portstack" here_ Growth is defined to be less than 0;

Conversely, why judge the growth direction of the Stack when creating tasks? The root cause is that when creating a task, memory needs to be allocated for the TCB of the task and the Stack of the task. The allocation of memory needs to be realized through the pvPortMalloc interface, and the allocation of memory by pvPortMalloc starts from a small address, so:

If the Stack grows upward, first call pvPortMalloc to allocate the TCB structure of the task, and then allocate the Stack of the task, because the size of the TCB is fixed, but the Stack should grow upward, so as to avoid the Stack stepping on the TCB;

If the Stack grows downward, first call pvPortMalloc to allocate the Stack of the task, and then allocate the TCB structure of the task, so that when the Stack grows, it can also avoid stepping on the TCB structure;

After allocating the Stack, set pxnewtcb - > pxStack = pxStack of TCB; At this moment, pxStack is initialized;

Here's another nagging sentence. The space allocated to the task stack is:

( ( size_t ) usStackDepth ) * sizeof( StackType_t )
   

Stacktype here_ T is related to CPU architecture. Stacktype under 32-bit CPU_ T is defined as uint32_t. That is, 4 bytes;

If the TCB structure and task Stack are successfully assigned to the task, it will call: prvInitialiseNewTask and prvAddNewTaskToReadyList;

prvInitialiseNewTask mainly refers to the TCB related content of the initialization task;

Prvaddnewtask toreadylist adds the initialized task to the Ready linked list, that is, it is allowed to be put into execution;

If the task is created successfully, return pdPASS; otherwise, return pdflare;

 

2.2,prvInitialiseNewTask

Let's take a look at the specific implementation of 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. #if( portUSING_MPU_WRAPPERS == 1 )
  13. /* Should the task be created in privileged mode? */
  14. BaseType_t xRunPrivileged;
  15. if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
  16. {
  17. xRunPrivileged = pdTRUE;
  18. }
  19. else
  20. {
  21. xRunPrivileged = pdFALSE;
  22. }
  23. uxPriority &= ~portPRIVILEGE_BIT;
  24. #endif /* portUSING_MPU_WRAPPERS == 1 */
  25. /* Avoid dependency on memset() if it is not required. */
  26. #if( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
  27. {
  28. /* Fill the stack with a known value to assist debugging. */
  29. ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
  30. }
  31. #endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */
  32. /* Calculate the top of stack address. This depends on whether the stack
  33. grows from high memory to low (as per the 80x86) or vice versa.
  34. portSTACK_GROWTH is used to make the result positive or negative as required
  35. by the port. */
  36. #if( portSTACK_GROWTH < 0 )
  37. {
  38. pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
  39. pxTopOfStack = ( 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(). */
  40. /* Check the alignment of the calculated top of stack is correct. */
  41. configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
  42. #if( configRECORD_STACK_HIGH_ADDRESS == 1 )
  43. {
  44. /* Also record the stack's high address, which may assist
  45. debugging. */
  46. pxNewTCB->pxEndOfStack = pxTopOfStack;
  47. }
  48. #endif /* configRECORD_STACK_HIGH_ADDRESS */
  49. }
  50. #else /* portSTACK_GROWTH */
  51. {
  52. pxTopOfStack = pxNewTCB->pxStack;
  53. /* Check the alignment of the stack buffer is correct. */
  54. configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
  55. /* The other extreme of the stack space is required if stack checking is
  56. performed. */
  57. pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
  58. }
  59. #endif /* portSTACK_GROWTH */
  60. /* Store the task name in the TCB. */
  61. if( pcName != NULL )
  62. {
  63. for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
  64. {
  65. pxNewTCB->pcTaskName[ x ] = pcName[ x ];
  66. /* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than
  67. configMAX_TASK_NAME_LEN characters just in case the memory after the
  68. string is not accessible (extremely unlikely). */
  69. if( pcName[ x ] == ( char ) 0x00 )
  70. {
  71. break;
  72. }
  73. else
  74. {
  75. mtCOVERAGE_TEST_MARKER();
  76. }
  77. }
  78. /* Ensure the name string is terminated in the case that the string length
  79. was greater or equal to configMAX_TASK_NAME_LEN. */
  80. pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
  81. }
  82. else
  83. {
  84. /* The task has not been given a name, so just ensure there is a NULL
  85. terminator when it is read out. */
  86. pxNewTCB->pcTaskName[ 0 ] = 0x00;
  87. }
  88. /* This is used as an array index so must ensure it's not too large. First
  89. remove the privilege bit if one is present. */
  90. if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
  91. {
  92. uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
  93. }
  94. else
  95. {
  96. mtCOVERAGE_TEST_MARKER();
  97. }
  98. pxNewTCB->uxPriority = uxPriority;
  99. #if ( configUSE_MUTEXES == 1 )
  100. {
  101. pxNewTCB->uxBasePriority = uxPriority;
  102. pxNewTCB->uxMutexesHeld = 0;
  103. }
  104. #endif /* configUSE_MUTEXES */
  105. vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
  106. vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
  107. /* Set the pxNewTCB as a link back from the ListItem_t. This is so we can get
  108. back to the containing TCB from a generic item in a list. */
  109. listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
  110. /* Event lists are always in priority order. */
  111. 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. */
  112. listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
  113. #if ( portCRITICAL_NESTING_IN_TCB == 1 )
  114. {
  115. pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
  116. }
  117. #endif /* portCRITICAL_NESTING_IN_TCB */
  118. #if ( configUSE_APPLICATION_TASK_TAG == 1 )
  119. {
  120. pxNewTCB->pxTaskTag = NULL;
  121. }
  122. #endif /* configUSE_APPLICATION_TASK_TAG */
  123. #if ( configGENERATE_RUN_TIME_STATS == 1 )
  124. {
  125. pxNewTCB->ulRunTimeCounter = 0UL;
  126. }
  127. #endif /* configGENERATE_RUN_TIME_STATS */
  128. #if ( portUSING_MPU_WRAPPERS == 1 )
  129. {
  130. vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );
  131. }
  132. #else
  133. {
  134. /* Avoid compiler warning about unreferenced parameter. */
  135. ( void ) xRegions;
  136. }
  137. #endif
  138. #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
  139. {
  140. memset( ( void * ) &( pxNewTCB->pvThreadLocalStoragePointers[ 0 ] ), 0x00, sizeof( pxNewTCB->pvThreadLocalStoragePointers ) );
  141. }
  142. #endif
  143. #if ( configUSE_TASK_NOTIFICATIONS == 1 )
  144. {
  145. memset( ( void * ) &( pxNewTCB->ulNotifiedValue[ 0 ] ), 0x00, sizeof( pxNewTCB->ulNotifiedValue ) );
  146. memset( ( void * ) &( pxNewTCB->ucNotifyState[ 0 ] ), 0x00, sizeof( pxNewTCB->ucNotifyState ) );
  147. }
  148. #endif
  149. #if ( configUSE_NEWLIB_REENTRANT == 1 )
  150. {
  151. /* Initialise this task's Newlib reent structure.
  152. See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
  153. for additional information. */
  154. _REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
  155. }
  156. #endif
  157. #if( INCLUDE_xTaskAbortDelay == 1 )
  158. {
  159. pxNewTCB->ucDelayAborted = pdFALSE;
  160. }
  161. #endif
  162. /* Initialize the TCB stack to look as if the task was already running,
  163. but had been interrupted by the scheduler. The return address is set
  164. to the start of the task function. Once the stack has been initialised
  165. the top of stack variable is updated. */
  166. #if( portUSING_MPU_WRAPPERS == 1 )
  167. {
  168. /* If the port has capability to detect stack overflow,
  169. pass the stack end address to the stack initialization
  170. function as well. */
  171. #if( portHAS_STACK_OVERFLOW_CHECKING == 1 )
  172. {
  173. #if( portSTACK_GROWTH < 0 )
  174. {
  175. pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged );
  176. }
  177. #else /* portSTACK_GROWTH */
  178. {
  179. pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged );
  180. }
  181. #endif /* portSTACK_GROWTH */
  182. }
  183. #else /* portHAS_STACK_OVERFLOW_CHECKING */
  184. {
  185. pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
  186. }
  187. #endif /* portHAS_STACK_OVERFLOW_CHECKING */
  188. }
  189. #else /* portUSING_MPU_WRAPPERS */
  190. {
  191. /* If the port has capability to detect stack overflow,
  192. pass the stack end address to the stack initialization
  193. function as well. */
  194. #if( portHAS_STACK_OVERFLOW_CHECKING == 1 )
  195. {
  196. #if( portSTACK_GROWTH < 0 )
  197. {
  198. pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );
  199. }
  200. #else /* portSTACK_GROWTH */
  201. {
  202. pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );
  203. }
  204. #endif /* portSTACK_GROWTH */
  205. }
  206. #else /* portHAS_STACK_OVERFLOW_CHECKING */
  207. {
  208. pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
  209. }
  210. #endif /* portHAS_STACK_OVERFLOW_CHECKING */
  211. }
  212. #endif /* portUSING_MPU_WRAPPERS */
  213. if( pxCreatedTask != NULL )
  214. {
  215. /* Pass the handle out in an anonymous way. The handle can be used to
  216. change the created task's priority, delete the created task, etc.*/
  217. *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
  218. }
  219. else
  220. {
  221. mtCOVERAGE_TEST_MARKER();
  222. }
  223. }

There are many contents, which are analyzed line by line;

The configuration of MPU is not concerned for the time being;

If tskset is used_ NEW_ STACKS_ TO_ KNOWN_ Value macro, which means that the Stack of the initialized task needs to be filled with a fixed value (0xA5);

Then judge the growth direction of the stack. Why are you judging the growth direction of the stack here? Previously, the stack header pointer pxnewtcb - > pxstack after initialization was assigned, but now we need to assign an initial value to the stack pointer (the one that will move) actually used when the task runs;

If the stack grows downward, we need to place the stack top pointer pxTopOfStack at the end of the allocated stack to grow downward;

If the stack grows upward, we can directly assign pxnewtcb - > pxstack to pxTopOfStack:

Taking Cortex-M3 as an example, the stack grows downward, so it will enter portstack_ The branch with growth < 0 points pxTopOfStack to the end of the stack depth allocated, and performs 8-byte alignment of the stack (processor architecture requirements);

Note that at this moment, # pxTopOfStack is only a local variable and is not directly assigned to # pxTopOfStack of TCB;

Then assign a value to the TCB - > pctaskname field (task name). This field is a string and its length is limited by configMAX_TASK_NAME_LEN;

Then assign a value to the TCB - > uxpriority task priority field;

If MUTEXES is used, assign uxPriority to BasePriority and set the current mutex hold field to 0;

Initialize the status Item of TCB, and set the Owner of the Item to the currently initialized TCB;

Initialize the Event Item of TCB, set the Owner of the Item as the currently initialized TCB, and configure the Value of the Event Item as the maximum priority minus the current priority. The Value in the Item here does not directly save the priority, but saves the complement of the priority, which means that the larger the Value of xItemValue, the smaller the corresponding task priority;

The nesting times of initialization critical area is 0;

The task notification Value is initialized to 0; Notification status initialization, etc;

There is one place to pay attention to here, porthas_ STACK_ OVERFLOW_ The macro checking is not defined in CM3. I think it should be the detection of stack overflow corresponding to the processor;

Call pxportinitializestack to pass in the previously set stack top pointer, Task execution function, and parameter pointer to be passed to Task; Assign the result to TCB - > pxtopofstack;

Finally, everything is successful, and the TCB address is paid to the handle after the Task is successfully created;

Here, let's take a look at the pxportinitializestack function:

 

2.3,pxPortInitialiseStack

With Port, the description is related to the architecture of the processor. To understand what this function is doing, you need a little knowledge of the architecture. Here we still take Cortex-M3 as an example;

In Cortex-M3 processor, when an exception / interrupt occurs, the processor will stack the key registers to protect the site, and then jump to ISR for execution. This is a pure hardware behavior; After executing ISR, the processor will be out of the stack in sequence;

When entering the stack, the contents are stored in spatial order as follows:

xPSR,PC,LR,R12,R3,R2,R1,R0;

When it acts as ISR, the processor hardware will go to the place where these things were stored (SP) to pop up the stack and restore the scene;

The OS scheduling switching context is done in ISR;

Therefore, during initialization (CM3 thread mode), we will manually simulate the behavior of the processor into the stack, put these things in it and get ready. When the OS is scheduled, just tell it the stack pointer, hey, the processor will pop up the stack;

For more information about CM3 processor, please refer to< Cortex-M3 processor snooping>

Well, after the explanation, you can look at the code:

   
  1. /* Constants required to set up the initial stack. */
  2. #define portINITIAL_XPSR ( 0x01000000 )
  3. /* For strict compliance with the Cortex-M spec the task start address should
  4. have bit-0 clear, as it is loaded into the PC on exit from an ISR. */
  5. #define portSTART_ADDRESS_MASK ( ( StackType_t ) 0xfffffffeUL )
  6. static void prvTaskExitError( void )
  7. {
  8. /* A function that implements a task must not exit or attempt to return to
  9. its caller as there is nothing to return to. If a task wants to exit it
  10. should instead call vTaskDelete( NULL ).
  11. Artificially force an assert() to be triggered if configASSERT() is
  12. defined, then stop here so application writers can catch the error. */
  13. configASSERT( uxCriticalNesting == ~ 0UL );
  14. portDISABLE_INTERRUPTS();
  15. for( ;; );
  16. }
  17. /*
  18. * See header file for description.
  19. */
  20. StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
  21. {
  22. /* Simulate the stack frame as it would be created by a context switch
  23. interrupt. */
  24. pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
  25. *pxTopOfStack = portINITIAL_XPSR; /* xPSR */
  26. pxTopOfStack--;
  27. *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */
  28. pxTopOfStack--;
  29. *pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */
  30. pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
  31. *pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */
  32. pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
  33. return pxTopOfStack;
  34. }

According to the CM3 stack pressing method, this function is done manually once:

The initial value given by the position of xPSR ^ is 0x01000000, where bit24 is set to 1, indicating the Thumb instruction is used;

The location of the PC gives the entry of this task. When the OS scheduler selects SP and exits the scheduling, this will be assigned to the PC pointer of the CPU, that is, the task function will be called;

The location of LR is assigned a function called prvTaskExitError. Because our task will never return, if the task returns, it indicates an error;

Position reservation of R12, R3, R2 and R1;

The position of R0 holds the parameter pointer pvParameters passed to the task. According to the call rules of the assembly, R0 is the first parameter passed;

The remaining positions are R11, R10, R9, R8, R7, R6, R5 and R4;

At this moment, the # pxTopOfStack can be directly assigned to TCB - > pxTopOfStack;

OK, now all the basic elements of TCB have been initialized;

Let's go back to the # xtask create call. Finally, we call # prvaddnewtask toreadylist to add the currently initialized tasks to the Ready linked list;

 

2.4,prvAddNewTaskToReadyList

Prvaddnewtask toreadylist, add the task to the Ready linked list, and directly execute the following code:

   
  1. static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
  2. {
  3. /* Ensure interrupts don't access the task lists while the lists are being
  4. updated. */
  5. taskENTER_CRITICAL();
  6. {
  7. uxCurrentNumberOfTasks++;
  8. if( pxCurrentTCB == NULL )
  9. {
  10. /* There are no other tasks, or all the other tasks are in
  11. the suspended state - make this the current task. */
  12. pxCurrentTCB = pxNewTCB;
  13. if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
  14. {
  15. /* This is the first task to be created so do the preliminary
  16. initialisation required. We will not recover if this call
  17. fails, but we will report the failure. */
  18. prvInitialiseTaskLists();
  19. }
  20. else
  21. {
  22. mtCOVERAGE_TEST_MARKER();
  23. }
  24. }
  25. else
  26. {
  27. /* If the scheduler is not already running, make this task the
  28. current task if it is the highest priority task to be created
  29. so far. */
  30. if( xSchedulerRunning == pdFALSE )
  31. {
  32. if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
  33. {
  34. pxCurrentTCB = pxNewTCB;
  35. }
  36. else
  37. {
  38. mtCOVERAGE_TEST_MARKER();
  39. }
  40. }
  41. else
  42. {
  43. mtCOVERAGE_TEST_MARKER();
  44. }
  45. }
  46. uxTaskNumber++;
  47. #if ( configUSE_TRACE_FACILITY == 1 )
  48. {
  49. /* Add a counter into the TCB for tracing only. */
  50. pxNewTCB->uxTCBNumber = uxTaskNumber;
  51. }
  52. #endif /* configUSE_TRACE_FACILITY */
  53. traceTASK_CREATE( pxNewTCB );
  54. prvAddTaskToReadyList( pxNewTCB );
  55. portSETUP_TCB( pxNewTCB );
  56. }
  57. taskEXIT_CRITICAL();
  58. if( xSchedulerRunning != pdFALSE )
  59. {
  60. /* If the created task is of a higher priority than the current task
  61. then it should run now. */
  62. if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
  63. {
  64. taskYIELD_IF_USING_PREEMPTION();
  65. }
  66. else
  67. {
  68. mtCOVERAGE_TEST_MARKER();
  69. }
  70. }
  71. else
  72. {
  73. mtCOVERAGE_TEST_MARKER();
  74. }
  75. }

The Ready linked list can be accessed in many places, as well as in ISR. In order to ensure the effectiveness of access, call taskenter first_ Critical () enters the critical zone;

uxCurrentNumberOfTasks records the individuals of the current task. Now the task is added, so it is added here;

pxCurrentTCB is a global variable. When the scheduler is not working, it points to the task with the highest priority in Ready; Here, judge whether the current added task is the first task. If it is the first task, call prvnitializetasklists to initialize the task list;

   
  1. PRIVILEGED_DATAstatic List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; /*Ready status tasks sorted by priority*/
  2. PRIVILEGED_DATAstatic List_t xDelayedTaskList1; /*Delayed tasks */
  3. PRIVILEGED_DATAstatic List_t xDelayedTaskList2; /*Delayed tasks */
  4. PRIVILEGED_DATAstatic List_t xPendingReadyList; /*The task is ready, but the scheduler is suspended */
  5. #if (INCLUDE_vTaskDelete == 1 )
  6. PRIVILEGED_DATA static List_t xTasksWaitingTermination; /*The task has been deleted, but memory has not been freed*/
  7. #endif
  8. #if (INCLUDE_vTaskSuspend == 1 )
  9. PRIVILEGED_DATA static List_t xSuspendedTaskList; /*Currently pending tasks*/
  10. #endif
  11. static void prvInitialiseTaskLists( void )
  12. {
  13. UBaseType_t uxPriority;
  14. for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
  15. {
  16. vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
  17. }
  18. vListInitialise( &xDelayedTaskList1 );
  19. vListInitialise( &xDelayedTaskList2 );
  20. vListInitialise( &xPendingReadyList );
  21. #if ( INCLUDE_vTaskDelete == 1 )
  22. {
  23. vListInitialise( &xTasksWaitingTermination );
  24. }
  25. #endif /* INCLUDE_vTaskDelete */
  26. #if ( INCLUDE_vTaskSuspend == 1 )
  27. {
  28. vListInitialise( &xSuspendedTaskList );
  29. }
  30. #endif /* INCLUDE_vTaskSuspend */
  31. /* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskList
  32. using list2. */
  33. pxDelayedTaskList = &xDelayedTaskList1;
  34. pxOverflowDelayedTaskList = &xDelayedTaskList2;
  35. }

The Ready linked list is in the form of a linked list array, which puts different priorities separately. Typically, space changes time;

Basically, we call the vlistinitialize function to initialize each linked list structure. For details, please refer to< FreeRTOS -- (1) linked list>

The related linked list has been initialized. If the current pxCurrentTCB is not NULL, it must not be the first time to add a task. At this time, judge whether the scheduler has started working (the task can be created before or after the scheduler starts working);

pxCurrentTCB is a static global variable, which is used to point to the currently running task TCB

If the scheduler has not started working, compare whether the priority of the currently added task is higher than that of the previous task. If so, update pxCurrentTCB to the latest task;

Call prvAddTaskToReadyList to add the current TCB to the tail of the Ready linked list with the TCB priority as the index of the array;

   
  1. #define prvAddTaskToReadyList( pxTCB ) \
  2. taskRECORD_READY_PRIORITY( ( pxTCB)->uxPriority ); \
  3. vListInsertEnd( &( pxReadyTasksLists[ (pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );

Macro taskRECORD_READY_PRIORITY() is used to update the static global variable uxTopReadyPriority, which records the highest task priority in the ready state. This variable participates in the core code of FreeRTOS: ensure that the ready task with the highest priority gets the right to run the CPU. It is here to participate in how to find the highest priority ready tasks as soon as possible.

Call} taskEXIT_CRITICAL(); Exit the critical zone;

Judge whether the scheduler has run according to the xSchedulerRunning standard. If not, do nothing and wait for the scheduler to get up and schedule (because the task with the highest priority has been updated to pxCurrentTCB). If the scheduler is already running and the priority of the newly added task is higher than that of the current running task, Then call taskyield_ IF_ USING_ Preemption(), and then go to portYIELD to forcibly trigger a task switching, so that the task with the highest priority can be scheduled;

Generally speaking, the process of creating a task is as follows:

 

Topics: Embedded system IoT FreeRTOS