FreeRTOS --(3)任务管理之创建任务

2023-05-16

目录

1、描述任务的结构

2、任务创建

2.1、xTaskCreate

2.2、prvInitialiseNewTask

2.3、pxPortInitialiseStack

2.4、prvAddNewTaskToReadyList


在《FreeRTOS --(7)任务管理之入门篇》文章基本分析了任务相关的轮廓后,我们知道使用什么接口来创建一个任务、怎么去开启调度器、以及根据宏配置,选择调度器的行为;接下来我们深入到 FreeRTOS 任务创建的源码来看看一个任务是怎么被创建的(某大神说过,Read The F**king Source Code ,能用代码解决的,尽量不 BB);

 

1、描述任务的结构

在 FreeRTOS 中,使用 TCB_t 来描述一个任务:


   
   
  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; /*当前堆栈的栈顶,必须位于结构体的第一项*/
  9. #if ( portUSING_MPU_WRAPPERS == 1 )
  10. xMPU_SETTINGS xMPUSettings; /*MPU设置,必须位于结构体的第二项*/
  11. #endif
  12. ListItem_t xStateListItem; /*任务的状态列表项,以引用的方式表示任务的状态*/
  13. ListItem_t xEventListItem; /*事件列表项,用于将任务以引用的方式挂接到事件列表*/
  14. UBaseType_t uxPriority; /*保存任务优先级,0表示最低优先级*/
  15. StackType_t *pxStack; /*指向堆栈的起始位置*/
  16. char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*任务名字*/
  17. #if ( portSTACK_GROWTH > 0 )
  18. StackType_t *pxEndOfStack; /*指向堆栈的尾部*/
  19. #endif
  20. #if ( portCRITICAL_NESTING_IN_TCB == 1 )
  21. UBaseType_t uxCriticalNesting; /*保存临界区嵌套深度*/
  22. #endif
  23. #if ( configUSE_TRACE_FACILITY == 1 )
  24. UBaseType_t uxTCBNumber; /*保存一个数值,每个任务都有唯一的值*/
  25. UBaseType_t uxTaskNumber; /*存储一个特定数值*/
  26. #endif
  27. #if ( configUSE_MUTEXES == 1 )
  28. UBaseType_t uxBasePriority; /*保存任务的基础优先级*/
  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; /*记录任务在运行状态下执行的总时间*/
  39. #endif
  40. #if ( configUSE_NEWLIB_REENTRANT == 1 )
  41. /* 为任务分配一个Newlibreent结构体变量。Newlib是一个C库函数,并非FreeRTOS维护,FreeRTOS也不对使用结果负责。如果用户使用Newlib,必须熟知Newlib的细节*/
  42. struct _reent xNewLib_reent;
  43. #endif
  44. #if( configUSE_TASK_NOTIFICATIONS == 1 )
  45. volatile uint32_t ulNotifiedValue; /*与任务通知相关*/
  46. volatile uint8_t ucNotifyState;
  47. #endif
  48. #if( configSUPPORT_STATIC_ALLOCATION == 1 )
  49. uint8_t ucStaticAllocationFlags; /* 如果堆栈由静态数组分配,则设置为pdTRUE,如果堆栈是动态分配的,则设置为pdFALSE*/
  50. #endif
  51. #if( INCLUDE_xTaskAbortDelay == 1 )
  52. uint8_t ucDelayAborted;
  53. #endif
  54. } tskTCB;
  55. typedef tskTCB TCB_t;

1、pxTopOfStack 必须位于结构体的第一项,指向当前堆栈的栈顶,对于向下增长的堆栈,pxTopOfStack 总是指向最后一个入栈的内容。此指针会移动;

2、如果使用 MPU,xMPUSettings 必须位于结构体的第二项,用于 MPU 设置。

3、状态链表 xStateListItem ,用于将任务挂接到不同的状态链表中,比如任务处于 Ready 状态,那么就要将其挂到 Ready 链表;

4、事件链表 xEventListItem,类似;

5、uxPriority 用于保存任务的优先级,0 为最低优先级;

6、pxStack 指向堆栈的起始位置,申请堆栈内存函数返回的指针就被赋给该变量。pxTopOfStack 指向当前堆栈栈顶,随着进栈出栈,pxTopOfStack 指向的位置是会变化的;pxStack 指向当前堆栈的起始位置,一经分配后,堆栈起始位置就固定了,不会被改变了。那么为什么需要 pxStack 变量呢,这是因为随着任务的运行,堆栈可能会溢出,在堆栈向下增长的系统中,这个变量可用于检查堆栈是否溢出;如果在堆栈向上增长的系统中,要想确定堆栈是否溢出,还需要另外一个变量 pxEndOfStack 来辅助诊断是否堆栈溢出;

7、pcTaskName 用于保存任务的描述或名字,名字的长度由宏 configMAX_TASK_NAME_LEN(位于 FreeRTOSConfig.h 中)指定,包含字符串结束标志。

8、如果堆栈向上生长(portSTACK_GROWTH>0),指针 pxEndOfStack 指向堆栈尾部,用于检验堆栈是否溢出。

9、uxCriticalNesting 用于保存临界区嵌套深度,初始值为 0。

10、仅当宏 configUSE_TRACE_FACILITY(位于 FreeRTOSConfig.h 中)为 1 时有效。变量 uxTCBNumber 存储一个数值,在创建任务时由内核自动分配数值(通常每创建一个任务,值增加 1),每个任务的 uxTCBNumber 值都不同,主要用于调试。变量 uxTaskNumber 用于存储一个特定值,与变量 uxTCBNumber 不同,uxTaskNumber 的数值不是由内核分配的,而是通过 API 函数 vTaskSetTaskNumber() 来设置的,数值由函数参数指定。

11、如果使用互斥量(configUSE_MUTEXES==1),任务优先级被临时提高时,变量 uxBasePriority 用来保存任务原来的优先级。

12、变量 ucStaticAllocationFlags 也需要说明一下,我们前面说过任务创建 API 函数 xTaskCreate() 只能使用动态内存分配的方式创建任务堆栈和任务 TCB,如果要使用静态变量实现任务堆栈和任务 TCB 就需要使用函数 xTaskGenericCreate() 来实现。如果任务堆栈或任务 TCB 由静态数组和静态变量实现,则将该变量设置为 pdTRUE(任务堆栈空间由静态数组变量实现时为 0x01,任务 TCB 由静态变量实现时为 0x02,任务堆栈和任务 TCB 都由静态变量实现时为 0x03),如果堆栈是动态分配的,则将该变量设置为 pdFALSE。

 

2、任务创建

2.1、xTaskCreate

创建一个任务,使用 xTaskCreate 接口,传入的参数在《FreeRTOS --(7)任务管理之入门篇》中有描述,这里不在多说,我们直接看看他的实现,在 task.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. }

首先是根据 portSTACK_GROWTH 宏来判断当前处理器体系结构堆栈的生长方向,portSTACK_GROWTH > 0 代表堆栈向上生长,portSTACK_GROWTH < 0 代表堆栈向下生长;堆栈的生长方向是和处理器的体系结构息息相关,这里拿 Cortex-M 系列的处理器做例子,它的堆栈是向下生长的,所以我们在往 Cortex-M 系列处理器上移植 FreeRTOS 的时候,一定记得这里将 portSTACK_GROWTH 定义得小于 0;

反过来,为啥要在创建任务的时候,判断堆栈的生长方向呢?根本原因是因为,在创建任务的时候,需要为任务的 TCB 和任务的 Stack 分配内存,分配内存需要通过 pvPortMalloc 接口来实现,而 pvPortMalloc 分配内存是从小地址开始分配的,所以:

如果堆栈是向上生长的,先调用 pvPortMalloc 分配任务的 TCB 结构,再去分配任务的 Stack,因为 TCB 大小是固定,但是堆栈要向上生长,这样就避免了堆栈踩到 TCB;

如果堆栈是向下生长的,先调用 pvPortMalloc 分配任务的 Stack,再去分配任务的 TCB 结构,这样 Stack 生长的时候,也可以避免踩到 TCB 结构;

分配 Stack 完成后,将 TCB 的 pxNewTCB->pxStack = pxStack; 此刻 pxStack 便初始化完毕

这里还要唠叨一句,分配任务栈的空间是:

( ( size_t ) usStackDepth ) * sizeof( StackType_t )
   
   

这里的 StackType_t 和 CPU 体系架构相关,32 bit 的 CPU 下,StackType_t 被定义为 uint32_t,也就是 4 字节;

如果为任务分配 TCB 结构和任务 Stack 都成功了,那么会调用到:prvInitialiseNewTask 和 prvAddNewTaskToReadyList;

prvInitialiseNewTask 主要是初始化任务的 TCB 相关的内容;

prvAddNewTaskToReadyList 将初始化好的任务添加到 Ready 链表,即允许投入执行;

如果创建任务成功,返回 pdPASS 否则返回 pdFLASE;

 

2.2、prvInitialiseNewTask

下面来看看 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. }

内容不少,逐行分析;

配置 MPU 部分暂时不关注;

如果使用了 tskSET_NEW_STACKS_TO_KNOWN_VALUE 宏,代表需要将被初始化后任务的 Stack 使用一个固定的值(0xA5)进行填充;

然后判断堆栈的生长方向,为何这里又在判断堆栈的生长方向呢?之前是对初始化完成后的堆栈头指针 pxNewTCB->pxStack 进行了赋值,但是此刻我们需要对当任务跑起来后,实际上用到的堆栈指针(会移动的那个)进行赋初值;

如果堆栈向下生长的话,我们需要将栈顶指针 pxTopOfStack 放置到我们分配的堆栈的末尾,这样才能向下增长;

如果堆栈向上生长的话,我们直接将 pxNewTCB->pxStack 赋值给 pxTopOfStack 就好:

以 Cortex-M3 为例,堆栈是向下生长的 ,所以会走进 portSTACK_GROWTH < 0 的这个分支,将 pxTopOfStack 指向了被分配到堆栈深度的最后,并进行堆栈的 8 字节对齐操作(处理器架构要求);

注意,此刻的 pxTopOfStack 还只是本地的局部变量,并没有直接赋值给 TCB 的 pxTopOfStack;

接着给 TCB->pcTaskName 字段(任务名字)赋值,此字段是字符串,长度受限于 configMAX_TASK_NAME_LEN;

接着给 TCB->uxPriority 任务优先级字段赋值;

如果使用了 MUTEXES 的话,将 uxPriority 赋值给 BasePriority,并将当前 mutex hold 字段设置为 0;

初始化 TCB 的状态 Item,设置 Item 的 Owner 为当前初始化的 TCB;

初始化 TCB 的 Event Item,设置 Item 的 Owner 为当前初始化的 TCB,配置 Event Item 的值为最大的优先级减去当前优先级,这里的 Item 里面的 Value 不直接保存优先级,而是保存优先级的补数,意味着 xItemValue 的值越大,对应的任务优先级越小;

初始化临界区的嵌套次数为 0;

任务通知 Value 初始化为 0;通知状态初始化等;

这里要注意一个地方,portHAS_STACK_OVERFLOW_CHECKING 这个宏在 CM3 中未定义,我想应该是处理器对应堆栈溢出的检测;

调用 pxPortInitialiseStack 传入之前设置的栈顶指针,Task 执行函数,以及要传递给 Task 的参数指针;将结果赋值给了 TCB->pxTopOfStack;

最后一切成功,将 TCB 的地址付给了创建 Task 成功后的句柄;

这里我们要看一下 pxPortInitialiseStack 函数:

 

2.3、pxPortInitialiseStack

带 Port,说明和处理器的体系架构相关,要看懂这个函数在干嘛,需要一点点体系架构方面的知识,这里依然以 Cortex-M3 为例;

在 Cortex-M3 处理器中,当发生异常/中断后,处理器会将关键的寄存器入栈,保护现场,然后跳转到 ISR 中执行,这个是纯硬件行为;当执行完 ISR 后,处理器会顺序出栈;

入栈的时候,再空间上顺序存储内容为:

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

当之行为 ISR,处理器硬件上,就会到之前存储这些玩意的地方(SP)去弹栈,恢复现场;

而 OS 调度切换上下文就是在 ISR 中做;

所以呢,在初始化的时候(CM3 的线程模式),咱们就手动的模拟处理器入栈的行为,将这些玩意先给他放进去准备好,等 OS 调度的时候,只要告诉它堆栈指针,嘿,处理器就会去弹栈;

更多的关于 CM3 处理器的内容参考《Cortex-M3 处理器窥探》

好了,解释完了后,可以看代码了:


   
   
  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. }

按照 CM3 压栈方式,这个函数手动做了一次:

xPSR 的位置给了初值为:0x01000000,其中 bit24 被置 1,表示使用 Thumb 指令;

PC 的位置给了这个任务的入口,当 OS 调度选择好 SP 后,退出调度,这个就会被赋值给 CPU 的 PC 指针,也就是,任务函数被调用;

LR 的位置赋值了一个叫做 prvTaskExitError 的函数,因为我们的任务是永不返回的,所以如果任务返回了,说明出错了;

R12,R3,R2,R1 的位置预留;

R0 的位置保存了传递给任务的参数指针 pvParameters,根据汇编的调用规则,R0 是第一个传参;

剩余的位置给了 R11, R10, R9, R8, R7, R6, R5R4

此刻的 pxTopOfStack 便可用直接赋值给 TCB->pxTopOfStack

OK,此刻 TCB 的基本元素已经悉数初始化完毕;

我们在回到 xTaskCreate 调用,看最后,最后还调用了 prvAddNewTaskToReadyList,将当前已经初始化完毕的任务添加到 Ready 链表;

 

2.4、prvAddNewTaskToReadyList

prvAddNewTaskToReadyList,将任务添加到 Ready 链表,直接干代码:


   
   
  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. }

Ready 链表在多处访问,ISR 中也有访问,为了保证访问的有效性,这里先调用 taskENTER_CRITICAL() 进入临界区;

uxCurrentNumberOfTasks 记录了当前任务的个人,此刻任务新增了,所以这里自加;

pxCurrentTCB 是一个全局变量,在调度器还未工作的时候,指向的是 Ready 中优先级最高的任务;这里判断是否当前 Add 进来的任务是第一个任务,如果是第一个任务,那么调用 prvInitialiseTaskLists 来初始化任务链表;


   
   
  1. PRIVILEGED_DATAstatic List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; /*按照优先级排序的就绪态任务*/
  2. PRIVILEGED_DATAstatic List_t xDelayedTaskList1; /*延时的任务 */
  3. PRIVILEGED_DATAstatic List_t xDelayedTaskList2; /*延时的任务 */
  4. PRIVILEGED_DATAstatic List_t xPendingReadyList; /*任务已就绪,但调度器被挂起 */
  5. #if (INCLUDE_vTaskDelete == 1 )
  6. PRIVILEGED_DATA static List_t xTasksWaitingTermination; /*任务已经被删除,但内存尚未释放*/
  7. #endif
  8. #if (INCLUDE_vTaskSuspend == 1 )
  9. PRIVILEGED_DATA static List_t xSuspendedTaskList; /*当前挂起的任务*/
  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. }

Ready 链表是一个链表数组的形式,将不同优先级的分开来放,典型的空间换时间;

这里基本上都是调用 vListInitialise 函数来初始化各个链表结构,具体的不深入聊,参考《FreeRTOS --(1)链表》

相关链表已经初始化完毕,如果当前 pxCurrentTCB 不为 NULL,那么一定就不是第一次添加任务,此刻判断调度器是否已经开始工作了(创建任务可以在调度器开始工作之前,也可以在调度器工作之后);

pxCurrentTCB 是一个静态的全局变量,这个变量用来指向当前正在运行的任务 TCB

如果调度器还没有开始工作,则比较当前新增的任务的优先级是否大于上一个任务,如果是,那么更新 pxCurrentTCB 到这个最新的任务;

调用 prvAddTaskToReadyList 将当前的这个 TCB 添加到以这个 TCB 优先级为数组下标的那个 Ready 链表尾部;


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

taskRECORD_READY_PRIORITY() 用来更新静态全局变量 uxTopReadyPriority,它记录处于就绪态的最高任务优先级。这个变量参与了 FreeRTOS 的最核心代码:确保处于优先级最高的就绪任务获得 CPU 运行权。它在这里参与如何最快的找到优先级最高的就绪任务。

调用  taskEXIT_CRITICAL(); 退出临界区;

根据 xSchedulerRunning 标准,判断是否调度器已经跑起来了,如果没有跑起来,什么都不做,等着调度器起来,调度(因为前面已经将最高优先级的任务更新到了 pxCurrentTCB ),如果此刻调度器已经在运转,而新加入的这个任务优先级高于了当前运行任务的优先级,那么调用 taskYIELD_IF_USING_PRREEMPTION(),进而走到 portYIELD 强制触发一次任务切换,让最高优先级的任务得到调度;

总体来说,创建一个任务的流程如下:

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS --(3)任务管理之创建任务 的相关文章

  • FreeRTOS系列

    1 多任务系统 1 1 前后台系统 单片机裸机开发时 一般都是在main函数里面用while 1 做一个大循环来完成所有的处理 循环中调用相应的函数完成所需的处理 有时也需要在中断中完成一些处理 相对于多任务系统而言 这就是单人单任务系统也
  • 【FreeRtos学习笔记】STM32 CubeMx——Timers(定时器)

    目录 1 软件定时器 2 示例程序 2 1 例程功能 2 2 步骤 2 3 实验结果 2 4 函数讲解 1 软件定时器 定时器是MCU常用的外设 我们在学习各种单片机时必然会学习它的硬件定时器 但是 MCU自带的硬件定时器资源是有限的 而且
  • FreeRTOS系列

    本文主要介绍如何在任务或中断中向队列发送消息或者从队列中接收消息 使用STM32CubeMX将FreeRTOS移植到工程中 创建两个任务以及两个消息队列 并开启两个中断 两个任务 Keyscan Task 读取按键的键值 并将键值发送到队列
  • 【FreeRTOS】队列的使用

    作者主页 凉开水白菜 作者简介 共同学习 互相监督 热于分享 多加讨论 一起进步 专栏资料 https pan baidu com s 1nc1rfyLiMyw6ZhxiZ1Cumg pwd free 点赞 收藏 再看 养成习惯 订阅的粉丝
  • FreeRTOS ------- 任务(task)

    在学习RTOS的时候 个人觉得带着问题去学习 会了解到更多 1 什么是任务 在FreeRTOS中 每个执行线程都被称为 任务 每个任务都是在自己权限范围内的一个小程序 其具有程序入口每个任务都是在自己权限范围内的一个小程序 其具有程序入口通
  • freeRTOS使用uxTaskGetStackHighWaterMark函数查看任务堆栈空间的使用情况

    摘要 每个任务都有自己的堆栈 堆栈的总大小在创建任务的时候就确定了 此函数用于检查任务从创建好到现在的历史剩余最小值 这个值越小说明任务堆栈溢出的可能性就越大 FreeRTOS 把这个历史剩余最小值叫做 高水位线 此函数相对来说会多耗费一点
  • FreeRTOS_中断

    传送门 博客汇总帖 传送门 Cortex M3 中断 异常 传送门 Cortex M3笔记 基础 笔记内容参考 正点原子的FreeRTOS开发手册 cortex m3权威指南 Cortex M3和Cortex M4权威指南等 文中stm32
  • freertos————互斥锁

    线程安全 多线程程序处于一个多变的环境 可访问的全局变量和堆数据随时可能被其他的线程改变 多个线程同时访问一个共享数据 可能造成严重的后果 出现问题的是之前移植了一个freemodbus的从站 多个任务访问全局变量保持寄存器区 导致最后读出
  • FreeRTOS,串口中断接收中使用xQueueOverwriteFromISR()函数,程序卡死在configASSERT

    原因 UART的中断优先级设置的太高 高于了configMAX SYSCALL INTERRUPT PRIORITY宏定义的安全中断等级 UART的中断等级小于等于宏定义的优先等级即可
  • Arduino IDE将FreeRTOS用于STM32

    介绍 适用于STM32F103C8的FreeRTOS STM32F103C是一种能够使用FreeRTOS的ARM Cortex M3处理器 我们直接在Arduino IDE中开始使用STM32F103C8的FreeRTOS 我们也可以使用K
  • FreeRTOS学习笔记(8)---- 软件定时器

    使用FreeRTOS软件定时器需要在文件FreeRTOSConfig h先做如下配置 1 configUSE TIMERS 使能软件定时器 2 configTIMER TASK PRIORITY 定时器任务优先级 3 configTIMER
  • FreeRTOS死机原因

    1 中断回调函数中没有使用中断级API xxFromISR 函数 xSemaphoreGiveFromISR uart busy HighterTask 正确 xSemaphoreGive uart busy 错误 2 比configMAX
  • FreeRTOS学习---“定时器”篇

    总目录 FreeRTOS学习 任务 篇 FreeRTOS学习 消息队列 篇 FreeRTOS学习 信号量 篇 FreeRTOS学习 事件组 篇 FreeRTOS学习 定时器 篇 FreeRTOS提供了一种软件定时器 用来快速实现一些周期性的
  • 【FreeRTOS 事件】任务通知事件

    普通任务通知事件创建创建及运行 参阅安富莱电子demo define BIT 0 1 lt lt 0 define BIT 1 1 lt lt 1 static TaskHandle t xHandleTaskUserIF NULL sta
  • FreeRTOS轻量级同步--任务通知

    1 简介 在FreeRTOS的配置参数中的configUSE TASK NOTIFICATIONS宏打开 一般RTOS会默认打开 如图1所示 图1 notify宏开关 RTOS在创建任务时 会创建一个32位的通知值ulNotifiedVal
  • FreeRTOS笔记(二)

    FreeRTOS笔记 二 静态任务 文章目录 FreeRTOS笔记 二 静态任务 一 任务定义 二 任务创建 2 1 定义任务栈 2 2 定义任务函数 2 3 定义任务控制块 2 4 实现任务创建函数 三 实现就绪列表 3 1 定义就绪列表
  • 单片机通信数据延迟问题排查

    1 问题说明 笔者在最近的项目中 发现系统的响应延迟较高 经过排查 排除了单片机运行卡死的问题 2 原因分析 具体排查过程这里就不细致说明了 直接给出排查后原因 任务执行周期规划不合理 导致freertos队列发送接收到的命令有延迟 为了便
  • FreeRTOS多任务调度器基础

    Cortex M4中SysTick调度器核心 Cortex M4中的中断管理 Cortex M4中影子栈指针 Cortex M4中SVC和PendSV异常 1 Cortex M4中SysTick调度器核心 systick每一次中断都会触发内
  • FreeRTOSConfig.h 配置优化及深入

    本篇目标 基于上一篇的移植freertos stm32f4 freertos 上 修改 FreeRTOSConfig h 文件的相关配置来优化辅助 FreeRtos 的使用 并且建立一些基本功能 信号量 消息地列等 的简单应用位于 stm3
  • 有可用的 FreeRTOS 解释语言库吗?

    我在一家公司工作 该公司使用 FreeRTOS 为多个设备创建固件 最近 我们对新功能的要求已经超出了我们固件工程师的工作能力 但我们现在也无力雇用任何新人 即使进行微小的更改 也需要固件人员在非常低的级别上进行修改 我一直在为 FreeR

随机推荐

  • C++算法库网站 https://zh.cppreference.com/w/cpp/algorithm

    貌似要出国才能访问的C 43 43 标准库api网站 https zh cppreference com w cpp algorithm 有详细的函数介绍 如下图
  • 关于cv.cvtColor转换的问题及解决方法

    问题描述篇 xff1a 今天在调试困扰了我很久的一个问题 xff0c 在训练网络生成batch数据的时候读入原始图像 xff0c 输出的时候却老是出问题 我们都知道YUV和RGB之间的转换关系 xff1a R 61 Y 43 1 4075
  • Nuttx 驱动开发手册

    目录 Nuttx 代码获取编译 Nuttx 启动流程 Nuttx BootLoader 开发之源码分析 gpio 驱动分析 I2c驱动分析 PX4 框架分析 UORB 进程间通讯分析 PX4应用层驱动分析并实现例程 串口驱动GPS 驱动分析
  • 解析小觅中通过双目相机生成深度图的代码

    最近在使用双目摄像机生成深度图 xff0c 研读一下自带的代码 xff0c 做一个记录 第一部分 xff1a 第一部分是定义了一个命名空间 xff0c 其中包含许多个类 第一个类 xff1a 1 代码 GrabCallbacks类主要用于抓
  • altium designer PCB各层介绍+添加多层+设置正/负片+设置层的网络标号

    top layer 顶层 xff0c 用来走线 bottom layer 底层 xff0c 用来走线 mechanical 机械层 xff0c 用来定义PCB形状和尺寸 keepout layer 禁止布线层 xff0c 用来绘制禁布区 t
  • java死锁产生的条件

    以下四个条件同时满足时机会产生死锁 产生死锁的条件互斥 xff0c 共享资源 X 和 Y 只能被一个线程占用 xff1b 占有且等待 xff0c 线程 T1 已经取得共享资源 X xff0c 在等待共享资源 Y 的时候 xff0c 不释放共
  • PID--位置型PID和增量式PID比较

    一 位置型PID 位置型 PID 算法适用于不带积分元件的执行器 执行器的动作位置与其输入信号呈一一对应的关系 控制器根据第 n 次计算机采样结果与给定值之间的偏差 e 来计算出第 n 次采用后所输出的控制变量的值 以调节阀来简单说明 xf
  • 你可能不知道的室内无人机秘密都在这里(二 )

    接上篇 xff1a 你可能不知道的室内无人机秘密都在这里 xff08 一 xff09 如果说上一篇是无人机现状的一些科普知识篇 xff0c 那这篇就直接上干货了 xff0c 希望能真正帮助到喜欢无人机行业 想深入研究无人机的小伙伴们 具体我
  • 漫话程序员们的家庭装修——书房篇

    身为一名程序员 xff0c 辛辛苦苦码代码N年 xff0c 终于攒下钱买了自己的小窝 xff0c 不好好犒劳一下自己都对不起自己的近视眼和鼠标手 这就来分享一下我装修的心得 xff0c 从书房开始 xff01 书房作为程序员在公司战斗一天回
  • Windows下使用vscode 调试linux kernel

    安装WSL2 在microsoft store上安装Ubuntu xff0c 当不能安装时可能需要梯子window中访问Ubuntu的目录使用 xff1a wsl Ubuntu中需要修改软件源 xff1a 参考 https mirrors
  • 计算器算法----C语言实现(堆栈法)

    1 字符串去空格处理 实现一 xff1a span class hljs keyword void span spacess span class hljs keyword char span span class hljs keyword
  • 链接脚本

    本文转自 xff1a http www cnblogs com li hao p 4107964 html 一 概论 每一个链接过程都由 链接脚本 linker script 一般以lds作为文件的后缀名 控制 链接脚本 主要用于规定如何把
  • 记录2017/9/7趋势科技笔试题

    1 下面程序一共会在屏幕上输出多少个 xff1f include lt iostream gt include lt stdio h gt include lt sys types h gt include lt unistd h gt u
  • 字节对齐算法

    ps xff1a 遇见这种算法纯属一个巧合 xff0c 刚入职的我 xff0c 在忙着调用各种SDK中的API xff0c 无暇顾及代码的具体实现 xff0c 有些代码还被屏蔽了 xff0c 在写flash的过程中 xff0c 参考了前辈们
  • UCOSIII学习笔记

    目录 1 学习环境 2 滴答定时器 3 任务 3 1 UCOSIII系统任务 3 2 UCOSIII任务状态 3 3 UCOSIII任务调度 3 4 任务相关的API函数 3 5 钩子函数 4 UCOSIII的中断 5 UCOSIII的临界
  • QCC5125----GAIA

    1 描述 GAIA全称 xff1a Generic Application Interface Architecture xff0c 实现了端到端 xff0c 主机无关的生态系统 xff0c 支持主机应用程序访问设备功能 底层的数据包由8个
  • 【SQLserver】使用openrowset方法导入EXCEL表格数据

    一 前言 在之前的一篇博文中记录了用OPENDATASOURCE函数将EXCEL数据写入SQLserver表中的方法 这一方法需要表名sheet1为固定名称不可更改 实际业务中可能会遇到表名随着日期而改动的情况 xff0c 如果excel表
  • PDM

    PDM Pulse Density Modulation 1 Protocols Introduction1 1 PDM Introduction1 2 PCM Introduction1 3 PDM To PCM 2 PDM Struct
  • FreeRTOS --(1)链表

    Based On FreeRTOS Kernel V10 3 1 1 相关文件 链表结构是 OS 内部经常使用到的 xff0c FreeRTOS 自然也不例外 xff0c 在深入分析各个模块的工作原理之前 xff0c 首先来分析 FreeR
  • FreeRTOS --(3)任务管理之创建任务

    目录 1 描述任务的结构 2 任务创建 2 1 xTaskCreate 2 2 prvInitialiseNewTask 2 3 pxPortInitialiseStack 2 4 prvAddNewTaskToReadyList 在 Fr