当前位置:   article > 正文

FreeRTOS源码分析-13 内存管理_分散加载 rtos放在制定位置

分散加载 rtos放在制定位置

1 STM32编译过程及程序组成

1.1STM32编译过程

  • 从源文件到.o文件,使用了armcc工具和armasm工具,这些keil帮我们实现了。
  • .o不是可执行文件,只是目标文件。因为地址没有定义,没有在rom中定义地址
  • 从.o文件到镜像文件使用了armlink,是可执行文件.elf在linux常用,.axf是stm32的可执行文件,通过sw 或stlink可进行烧写。.map 内存分布,这个文件很重要,出现bug问题就要熟读map文件。
  • .axf到.bin 二进制或.hexASCII文件使用了fromelf工具。在线IAP更新的时候需要用bin文件,但是无法烧写.hex,可以通过上位机把.hex转换成二进制烧写

1.2 程序的组成

Stack 栈向下

heap 堆

unintialized data未初始化数据段

initialized data初始化数据段

text 代码段

补充:

栈空间和堆空间是指在计算机内存中用于存储数据和程序的两种不同的分配方式。

  1. 栈空间(Stack Space): 栈空间主要用于存储函数调用时的局部变量、函数参数以及函数调用过程中的一些上下文信息。 特点:

    • 栈空间的分配和释放是由编译器自动完成的,无需手动管理。
    • 栈空间的大小是固定的,由系统或编译器预先分配好,通常较小。
    • 栈空间的数据存取速度快,因为栈的存取操作仅仅是简单的指针移动操作。
  2. 堆空间(Heap Space): 堆空间主要用于存储动态分配的内存,在程序运行过程中通过特定的函数(如malloc或new)手动申请和释放。 特点:

    • 堆空间的分配和释放需要手动管理,开发人员负责申请和释放内存。
    • 堆空间的大小可动态调整,可以根据实际需求进行动态分配和释放。
    • 堆空间的数据存取速度相对较慢,因为需要进行动态内存管理,并且可能产生内存碎片。

总结: 栈空间主要用于存储函数调用过程中的局部变量和上下文信息,大小固定,由编译器自动管理;堆空间主要用于动态分配内存,大小可调,需要手动申请和释放。栈空间的访问速度更快,而堆空间具有更大的灵活性。在编程中,正确管理栈空间和堆空间的使用至关重要,以避免内存泄漏、溢出和访问错误等问题。

1.3 存储与运行

未初始化 数据会清0,初始化数据段程序执行时只读。

程序出现bug时,可分析此文件。 

1.4 MDK生成的主要文件分析

 

  • 1、Section Cross References  所有.o文件的包含关系
  • 2、Removing Unused input sections from the image  可以移除未使用的文件,可以减少文件量,如只需要printf,那么其他C库可以删除
  • 3、Image Symbol Table 镜像符号列表,所有全局变量和C函数,在哪个.o文件占用内存多少可以提现
  • 4、Memory Map of the image 文件中code怎么分布,ram怎么分布
  • 5、lmage component sizes 镜像组件大小

没有用到的移除 

 

 镜像符号列表,.o文件放在哪个地址,大小多少。

 全局

内存分布

整个代码内存分布,如果内存不够了,可以查看哪个文件占用内存最多

上述说明:

LR_IROM1 代表代码放到ROM中的大小

所有*.o 所有RO都可以放到ROM1

RW文件放到 RAM

这个文件主要的作用分散加载文件,可以指定heap、stack,或者外部ram在这个文件中修改加载

hex文件的组成:段大小、地址、长度、数据、校验。

.bin文件一般用于改参数,分析flash读写出现的问题等。

2 内存管理概念及应用

2.1 静态内存与动态内存的区别

静态内存:

静态内存是指在程序开始运行时由编译器分配的内存,它的分配是在程序开始编译时完成的,不占用CPU资源。 程序中的各种变量,在编译时系统已经为其分配了所需的内存空间,当该变量在作用域内使用完毕时,系统会 自动释放所占用的内存空间。 变量的分配与释放,都无须程序员自行考虑。

动态内存:

动态内存分配是按输入信息的大小分配所需要的内存单元,他的特点是按需分配,内存分配在堆区。 用户无法确定空间大小,或者空间太大,栈上无法分配时,会采用动态内存分配。

区别:

  • 时间不同: 静态分配发生在程序编译和连接的时候。动态分配则发生在程序调入和执行的时候。
  • 空间不同: 堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由函数malloc进行分配。不过栈的动态分配和堆不同,他的动态分配是由编译器进行释放,无需我们手工实现。 

2.2 C标准库动态内存的缺陷

calloc 是一个在 C 语言中使用的函数,用于动态内存分配。它用于在堆内存上分配指定数量的连续字节,并将每个字节都初始化为零。

其中,num 表示要分配的元素数量,size 表示每个元素的大小(以字节为单位)。calloc 函数会分配 num * size 字节的内存,并返回一个指向新分配内存起始位置的指针。如果分配失败,则返回空指针 NULL

malloc 函数不同,calloc 在分配内存后会自动将每个字节初始化为零。这对于需要清零内存的情况非常方便,例如用于存储数组或结构体等数据结构。

下面是一个使用 calloc 函数动态分配一个包含 5 个整数的数组的例子:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main() {
  4. int* array = calloc(5, sizeof(int));
  5. if (array != NULL) {
  6. for (int i = 0; i < 5; i++) {
  7. printf("%d ", array[i]);
  8. }
  9. free(array); // 释放内存
  10. }
  11. return 0;
  12. }

realloc 函数的语法如下:

其中,ptr 是指向之前通过 malloccallocrealloc 分配的内存块的指针,size 是要重新分配的内存块的新大小(以字节为单位)。realloc 函数会尝试将之前分配的内存块的大小调整为新的大小,并返回一个指向重新分配后内存块起始位置的指针。如果分配失败,则返回空指针 NULL

需要注意的是,realloc 在重新分配内存时可能会进行数据的复制和移动。如果旧的内存块可以满足新的大小,那么就会直接扩展或缩小原来的内存块。否则,realloc 会在其他地方重新分配一块新的内存,并将原先内存块中的数据复制到新的内存块中。

下面是一个使用 realloc 函数重新分配一个整数数组内存大小的例子:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main() {
  4. int* array = malloc(5 * sizeof(int)); // 初始分配 5 个整数的内存
  5. if (array != NULL) {
  6. // 使用内存...
  7. // 重新分配内存大小为 10 个整数
  8. int* new_array = realloc(array, 10 * sizeof(int));
  9. if (new_array != NULL) {
  10. array = new_array; // 更新指针
  11. // 使用重新分配后的内存...
  12. free(array); // 释放内存
  13. }
  14. }
  15. return 0;
  16. }

这些函数在小型嵌入式系统中并不总是可用的,小型嵌入式设备中的 RAM 不足。

它们的实现可能非常的大,占据了相当大的一块代码空间。

他们几乎都不是安全的。

它们并不是确定的,每次调用这些函数执行的时间可能都不一样。

它们有可能产生碎片。

这两个函数会使得链接器配置得复杂。

如果允许堆空间的生长方向覆盖其他变量占据的内存,它们会成为 debug 的灾难。

如果用malloc,当80B和10B释放掉,100B成为了内存碎片,下面看下FreeRTOS怎么解决内存分配

2.3 FreeRTOS动态内存分配介绍

heap1:只能分配不能释放,适合不可能去删除的任务

 heap2:支持分配也支持释放,但是有内存碎片,可用空间会越来越少。释放的空间无法用。

 heap3:在heap2的基础上多了任务保护,类似锁,不会被打断

heap4:有分配,有释放,有管理(默认)

heap5:内部其实也是heap4,有内部ram也可以扩展外部ram。音视频会占用大量内存。

3 内存管理函数应用

3.1 功能需求

  • 1、当按键K3按下 申请内存空间
  • 2、当按键K4按下释放内存空间
  • 3、当按键K5按下打印剩余内存空间

3.2 API

 申请、释放,获取空闲内存

3.3 功能实现

修改按键代码,每个按键按下的时候都去处理

  1. /* USER CODE END Header_Delay_Task */
  2. void Delay_Task(void const * argument)
  3. {
  4. /* USER CODE BEGIN Delay_Task */
  5. EventBits_t KeyEventBits;
  6. uint8_t *HeapBuf = NULL;
  7. /* Infinite loop */
  8. for(;;)
  9. {
  10. /*
  11. 1、修改事件标志组等待状态为 触发后清除标志
  12. 2、检测按键,处理相关功能
  13. */
  14. KeyEventBits = xEventGroupWaitBits(KeyEventGroup,
  15. KEY3_EVENT_BIT|KEY4_EVENT_BIT|KEY5_EVENT_BIT|KEY6_EVENT_BIT,
  16. pdTRUE,
  17. pdFALSE,
  18. portMAX_DELAY);
  19. printf("Key is Down Key Event Bit is %x\r\n",KeyEventBits);
  20. switch(KeyEventBits){
  21. case KEY3_EVENT_BIT:
  22. if(HeapBuf == NULL){
  23. HeapBuf = pvPortMalloc(100);
  24. }
  25. else{
  26. printf("plese press K4 free!\r\n");
  27. }
  28. break;
  29. case KEY4_EVENT_BIT:
  30. if(HeapBuf != NULL){
  31. vPortFree(HeapBuf);
  32. HeapBuf = NULL;
  33. }
  34. else{
  35. printf("plese press K3 Malloc!\r\n");
  36. }
  37. break;
  38. case KEY5_EVENT_BIT:
  39. printf("Heap Free Size is%d\r\n",xPortGetFreeHeapSize());
  40. break;
  41. default:
  42. break;
  43. }
  44. osDelay(10);
  45. }
  46. /* USER CODE END Delay_Task */
  47. }

效果

第一次申请空间是12152

第二次申请空间是12264 

申请100字节,多出了12字节,为什么?下面分析原理。

4 内存管理实现原理

通用的内存管理需求:

  • 按照调用者的要求分配合适的大小的动态内存区,返回该内存块的首地址  
  • 如果没有足够的内存,则返回空指针
  • 用户不在使用该内存是可以调用Free函数释放该内存块,被释放的内存块在归还系统堆之后,可以被重新分配
  • 由于动态内存分配算法的重要性,要求快速分配算法并尽量减少内存碎片

4.1 heap_4内存管理方案(默认也是最经典)

特点:

  • 内存上是低地址,下是高地址,内存分配的地址是从低到高(可以测试打印一下malloc指针,如果第一次比第二次小,说明是低到高。
  • 内存需要字节对齐,遵循AACPS。为什么要对齐?内存分配并没用到系统堆,还是模拟了内存空间,实际是数组,目的是效率,也是8字节对齐。32位机一般是8字节对齐,64位机一般是16字节对齐。
  • 内存管理实际是单向链表的算法去维护,当释放的时候,会进行合并的判断,如果首地址和末地址相同即可合并。
  • 黑色部分代表已分配内存,红色区域代表未分配内存。其中都有两个变量,nNextFreeBlock表示一个链表的指针。释放的时候,内存进行了合并,图2->图3

首先了解控制块

4.2 heap_4内存块初始化

  1. /*
  2. 内存堆初始化
  3. 1、宏定义
  4. #if portBYTE_ALIGNMENT == 8 对齐字节数
  5. #define portBYTE_ALIGNMENT_MASK ( 0x0007 ) 对齐掩码
  6. #endif
  7. 2、内存块链表节点长度
  8. 这里保证可被8整除,内存操作效率高
  9. static const size_t xHeapStructSize = ( sizeof( BlockLink_t ) + ( ( size_t ) ( portBYTE_ALIGNMENT - 1 ) ) ) & ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
  10. */
  11. static void prvHeapInit( void )
  12. {
  13. BlockLink_t *pxFirstFreeBlock;
  14. uint8_t *pucAlignedHeap;
  15. size_t uxAddress;
  16. size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;
  17. /* 获取内存堆数组首地址 */
  18. uxAddress = ( size_t ) ucHeap;
  19. //当addr 后三位不等于0,就需要字节对齐
  20. //不能被8整除,8字节对齐,就是能够被8整除
  21. if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
  22. {
  23. //uxAddress = uxAddress +7???
  24. uxAddress += ( portBYTE_ALIGNMENT - 1 );
  25. //把后三位变成0,保证在8字节对齐上
  26. uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
  27. //获取了总可用长度,由低地址到高地址增长(对齐后地址减去数组地址)
  28. xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;
  29. }
  30. //对齐后的首地址
  31. pucAlignedHeap = ( uint8_t * ) uxAddress;
  32. /*
  33. 初始化了头结点xStart
  34. 下一个可用空闲块为对齐后的首地址
  35. 头结点的内存大小为0
  36. 解析:
  37. xStart 分配在全局内存中
  38. 不用于存储块记录,只用链表操作查找用
  39. */
  40. xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
  41. xStart.xBlockSize = ( size_t ) 0;
  42. /*
  43. 初始化尾节点
  44. 1、获取整个内存堆尾地址
  45. 2、减去一个链表节点长度
  46. 3、再去保证地址在8字节对齐上
  47. 4、把尾节点进行赋值
  48. 5、尾节点下一个指向NULL
  49. */
  50. uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
  51. uxAddress -= xHeapStructSize;
  52. uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
  53. pxEnd = ( void * ) uxAddress;
  54. pxEnd->xBlockSize = 0;
  55. pxEnd->pxNextFreeBlock = NULL;
  56. /*
  57. 初始化内存堆的第一个空闲块
  58. 1、首地址为内存堆可用空闲首地址
  59. 2、减去尾地址,获取到可用空间大小
  60. 3、下个指向尾节点
  61. */
  62. pxFirstFreeBlock = ( void * ) pucAlignedHeap;
  63. pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
  64. pxFirstFreeBlock->pxNextFreeBlock = pxEnd;
  65. /* 更新剩余内存信息 */
  66. xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
  67. xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
  68. /*标志位置位,32Bit 最高位为1 主要用于判断内存块类型使用*/
  69. xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
  70. }

初始化的概述图 

 

4.3 heap_4内存块申请

源码分析

  1. /*
  2. 内存块申请
  3. 根据传入大小,返回内存块指针,无可用空间返回NULL
  4. 1、全局变量
  5. static BlockLink_t xStart, *pxEnd = NULL;//赋值时就为空
  6. */
  7. void *pvPortMalloc( size_t xWantedSize )
  8. {
  9. BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
  10. void *pvReturn = NULL;
  11. //挂起调度器
  12. vTaskSuspendAll();
  13. {
  14. /* */
  15. if( pxEnd == NULL )
  16. {
  17. //触发内存堆的初始化
  18. prvHeapInit();
  19. }
  20. else
  21. {
  22. mtCOVERAGE_TEST_MARKER();
  23. }
  24. /*
  25. 1、最高位用于确定谁拥有区块-应用程序还是内核,所以它必须是0。
  26. */
  27. if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
  28. {
  29. /* */
  30. if( xWantedSize > 0 )
  31. {
  32. //加上链表节点长度,这也就是解释了 我们申请了100字节,但是实际占用了112,为什么多出了12个字节
  33. xWantedSize += xHeapStructSize;
  34. /* 保证能被8整除 */
  35. if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
  36. {
  37. /* Byte alignment required. */
  38. xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
  39. configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );
  40. }
  41. else
  42. {
  43. mtCOVERAGE_TEST_MARKER();
  44. }
  45. }
  46. else
  47. {
  48. mtCOVERAGE_TEST_MARKER();
  49. }
  50. //是否有可用空闲块
  51. if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
  52. {
  53. /*
  54. 查找可用空闲块
  55. 单向链表,先从头结点开始
  56. 1、pxPreviousBlock 开始遍历
  57. 遍历结束条件 大小满足或者没有空闲块了
  58. */
  59. pxPreviousBlock = &xStart;
  60. pxBlock = xStart.pxNextFreeBlock;
  61. while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
  62. {
  63. pxPreviousBlock = pxBlock;
  64. pxBlock = pxBlock->pxNextFreeBlock;
  65. }
  66. /* 是否有空闲块 */
  67. if( pxBlock != pxEnd )
  68. {
  69. /* 获取分配的内存首地址(块的首地址+节点长度) */
  70. pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );
  71. /* 删除 已经分配的节点*/
  72. pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
  73. /* If the block is larger than required it can be split into
  74. two. */
  75. if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
  76. {
  77. /*获取剩余的空闲首地址*/
  78. pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
  79. configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );
  80. /* 计算剩余长度 */
  81. pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
  82. pxBlock->xBlockSize = xWantedSize;
  83. /* 插入到空闲链表中去 */
  84. prvInsertBlockIntoFreeList( pxNewBlockLink );
  85. }
  86. else
  87. {
  88. mtCOVERAGE_TEST_MARKER();
  89. }
  90. //更新我们的剩余空闲大小
  91. xFreeBytesRemaining -= pxBlock->xBlockSize;
  92. if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
  93. {
  94. xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
  95. }
  96. else
  97. {
  98. mtCOVERAGE_TEST_MARKER();
  99. }
  100. /* 将分配出去的内存块,进行标记 */
  101. pxBlock->xBlockSize |= xBlockAllocatedBit;
  102. pxBlock->pxNextFreeBlock = NULL;
  103. }
  104. else
  105. {
  106. mtCOVERAGE_TEST_MARKER();
  107. }
  108. }
  109. else
  110. {
  111. mtCOVERAGE_TEST_MARKER();
  112. }
  113. }
  114. else
  115. {
  116. mtCOVERAGE_TEST_MARKER();
  117. }
  118. traceMALLOC( pvReturn, xWantedSize );
  119. }
  120. //恢复调度器
  121. ( void ) xTaskResumeAll();
  122. configASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 );
  123. return pvReturn;
  124. }

3中的问题:申请100字节,多出了12字节,为什么?

xWantedSize += xHeapStructSize    加上链表节点长度,这也就是解释了 我们申请了100字节,但是实际占用了112,为什么多出了12个字节
                

4.4 heap_4内存块插入

1先找到内存卡

2判断首地址和尾地址是否一样,一样合并

3再判断下一个首地址和尾地址是否一样,一样合并更新

  1. /*
  2. 把内存块插入到空闲内存块中
  3. */
  4. static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
  5. {
  6. BlockLink_t *pxIterator;
  7. uint8_t *puc;
  8. /*
  9. 找到pxBlockToInsert位置
  10. pxIterator->pxNextFreeBlock > pxBlockToInsert 表示已经找到
  11. 之后pxIterator地址在pxBlockToInsert实际物理地址之前
  12. */
  13. for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
  14. {
  15. }
  16. /*
  17. 判断是否可以合并 (向上合并)
  18. 1、内存块长度进行累加
  19. 2、要插入的地址,变成合并后的地址
  20. */
  21. puc = ( uint8_t * ) pxIterator;
  22. if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
  23. {
  24. pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
  25. pxBlockToInsert = pxIterator;
  26. }
  27. else
  28. {
  29. mtCOVERAGE_TEST_MARKER();
  30. }
  31. /*
  32. 判断是否可以合并 (向下合并)
  33. 1、再判断是否为尾节点
  34. 2、内存块长度进行累加
  35. 3、把要合并的内存块从空闲链表中移除
  36. */
  37. puc = ( uint8_t * ) pxBlockToInsert;
  38. if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
  39. {
  40. if( pxIterator->pxNextFreeBlock != pxEnd )
  41. {
  42. /* Form one big block from the two blocks. */
  43. pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
  44. pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
  45. }
  46. else
  47. {
  48. pxBlockToInsert->pxNextFreeBlock = pxEnd;
  49. }
  50. }
  51. /*
  52. 1、这里没有向下合并操作,直接插入就可以,连接到空闲链表中
  53. */
  54. else
  55. {
  56. pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
  57. }
  58. /*
  59. 1、这里没有向上合并操作,直接插入就可以,连接到空闲链表中
  60. */
  61. if( pxIterator != pxBlockToInsert )
  62. {
  63. pxIterator->pxNextFreeBlock = pxBlockToInsert;
  64. }
  65. else
  66. {
  67. mtCOVERAGE_TEST_MARKER();
  68. }
  69. }

 

4.5 heap_4内存块释放

  1. /*
  2. 释放内存块
  3. 参数:传入要释放的内存块地址
  4. */
  5. void vPortFree( void *pv )
  6. {
  7. uint8_t *puc = ( uint8_t * ) pv;
  8. BlockLink_t *pxLink;
  9. //判断内存块有效
  10. if( pv != NULL )
  11. {
  12. /*获取传入内存块的节点地址 */
  13. puc -= xHeapStructSize;
  14. pxLink = ( void * ) puc;
  15. //判断最高位为1
  16. if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
  17. {
  18. //下个节点为NULL
  19. if( pxLink->pxNextFreeBlock == NULL )
  20. {
  21. /*最高位清除置位为0 */
  22. pxLink->xBlockSize &= ~xBlockAllocatedBit;
  23. //挂起调度器
  24. vTaskSuspendAll();
  25. {
  26. /* 更新剩余空间大小 */
  27. xFreeBytesRemaining += pxLink->xBlockSize;
  28. //插入到空闲链表中去
  29. prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
  30. }
  31. //恢复调度器
  32. ( void ) xTaskResumeAll();
  33. }
  34. else
  35. {
  36. mtCOVERAGE_TEST_MARKER();
  37. }
  38. }
  39. else
  40. {
  41. mtCOVERAGE_TEST_MARKER();
  42. }
  43. }
  44. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/blog/article/detail/75945
推荐阅读
相关标签
  

闽ICP备14008679号