25.1 CPU 利用率的基本概念

CPU 使用率其实就是系统运行的程序占用的 CPU 资源,表示机器在某段时间程序运行的情况,如果这段时间中,程序一直在占用 CPU 的使用权,那么可以人为 CPU 的利用率是 100%。 CPU 的利用率越高,说明机器在这个时间上运行了很多程序,反之较少。利用率的高低与 CPU 强弱有直接关系,就像一段一模一样的程序,如果使用运算速度很慢的CPU,它可能要运行 1000ms,而使用很运算速度很快的 CPU 可能只需要 10ms,那么在1000ms 这段时间中,前者的 CPU 利用率就是 100%,而后者的 CPU 利用率只有 1%,因为1000ms 内前者都在使用 CPU 做运算,而后者只使用 10ms 的时间做运算,剩下的时间CPU 可以做其他事情。
FreeRTOS 是多任务操作系统,对 CPU 都是分时使用的:比如 A 任务占用 10ms,然后 B 任务占用 30ms,然后空闲 60ms,再又是 A任务占 10ms, B 任务占 30ms,空闲 60ms; 如果在一段时间内都是如此,那么这段时间内的利用率为 40%,因为整个系统中只有 40%的时间是 CPU 处理数据的时间。

25.2 CPU利用率的作用

一个系统设计的好坏,可以使用 CPU 使用率来衡量,一个好的系统必然是能完美响应急需的处理,并且系统的资源不会过于浪费(性价比高)。举个例子,假设一个系统的CPU 利用率经常在 90%~100%徘徊,那么系统就很少有空闲的时候,这时候突然有一些事情急需 CPU 的处理,但是此时 CPU 都很可能被其他任务在占用了,那么这个紧急事件就有可能无法被相应,即使能被相应,那么占用 CPU 的任务又处于等待状态,这种系统就是不够完美的,因为资源处理得太过于紧迫;反过来,假如 CPU 的利用率在 1%以下,那么我们就可以认为这种产品的资源过于浪费,搞一个那么好的 CPU 去干着没啥意义的活(大部分时间处于空闲状态),使用,作为产品的设计,既不能让资源过于浪费,也不能让资源过于紧迫,这种设计才是完美的,在需要的时候能及时处理完突发事件,而且资源也不会过剩,性价比更高。

25.3 CPU 利用率统计

FreeRTOS 是一个很完善很稳定的操作系统, 当然也给我们提供测量各个任务占用CPU 时间的函数接口, 我们可以知道系统中的每个任务占用 CPU 的时间, 从而得知系统设计的是否合理, 出于性能方面的考虑,有的时候,我们希望知道 CPU 的使用率为多少,进而判断此 CPU 的负载情况和对于当前运行环境是否能够“胜任工作”。 所以,在调试的时候很有必要得到当前系统的 CPU 利用率相关信息,但是在产品发布的时候,就可以把CPU 利用率统计这个功能去掉,因为使用任何功能的时候,都是需要消耗系统资源的,FreeRTOS 是使用一个外部的变量进行统计时间的,并且消耗一个高精度的定时器,其用于定时的精度是系统时钟节拍的 10-20 倍,比如当前系统时钟节拍是 1000HZ,那么定时器的计数节拍就要是 10000-20000HZ。而且 FreeRTOS 进行 CPU 利用率统计的时候,也有一定缺陷,因为它没有对进行 CPU 利用率统计时间的变量做溢出保护, 我们使用的是 32 位变量来系统运行的时间计数值,而按 20000HZ 的中断频率计算,每进入一中断就是 50us,变量加一,最大支持计数时间: 2^32 * 50us / 3600s =59.6 分钟, 运行时间超过了 59.6 分钟后统计的结果将不准确,除此之外整个系统一直响应定时器 50us 一次的中断会比较影响系统的性能。
用 户 想 要 使 用 使 用 CPU 利 用 率统 计 的 话 , 需 要 自 定 义 配 置 一 下 ,首 先 在
FreeRTOSConfig.h 配置与系统运行时间和任务状态收集有关的配置选项,并且实现portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() 与portGET_RUN_TIME_COUNTER_VALUE()这两个宏定义。

  1. //启用运行时间统计功能
  2. #define configGENERATE_RUN_TIME_STATS 1
  3. //启用可视化跟踪调试
  4. #define configUSE_TRACE_FACILITY 1
  5. /* 与宏 configUSE_TRACE_FACILITY 同时为 1 时会编译下面 3 个函数
  6. * prvWriteNameToBuffer()
  7. * vTaskList(),
  8. * vTaskGetRunTimeStats()
  9. */
  10. #define configUSE_STATS_FORMATTING_FUNCTIONS 1
  11. extern volatile uint32_t CPU_RunTime;
  12. #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (CPU_RunTime = 0ul)
  13. #define portGET_RUN_TIME_COUNTER_VALUE() CPU_RunTime

然后需要实现一个中断频率为 20000HZ 定时器,用于系统运行时间统计,其实很简单,只需将 CPU_RunTime 变量自加即可, 这个变量是用于记录系统运行时间的, 中断服务函数。

  1. volatile uint32_t CPU_RunTime = 0UL;
  2. void BASIC_TIM_IRQHandler (void)
  3. {
  4. if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) {
  5. CPU_RunTime++;
  6. TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);
  7. }
  8. }

然后我们就可以在任务中调用 vTaskGetRunTimeStats()和 vTaskList()函数获得任务的相关信息与 CPU 使用率的相关信息,然后打印出来即可。

  1. memset(CPU_RunInfo,0,400); //信息缓冲区清零
  2. vTaskList((char *)&CPU_RunInfo); //获取任务运行时间信息
  3. printf("---------------------------------------------\r\n");
  4. printf("任务名 任务状态 优先级 剩余栈 任务序号\r\n");
  5. printf("%s", CPU_RunInfo);
  6. printf("---------------------------------------------\r\n");
  7. memset(CPU_RunInfo,0,400); //信息缓冲区清零
  8. vTaskGetRunTimeStats((char *)&CPU_RunInfo);
  9. printf("任务名 运行计数 使用率\r\n");
  10. printf("%s", CPU_RunInfo);
  11. printf("---------------------------------------------\r\n\n");

25.4 实验

  1. /*
  2. *************************************************************************
  3. * 包含的头文件
  4. *************************************************************************
  5. */
  6. /* FreeRTOS头文件 */
  7. #include "FreeRTOS.h"
  8. #include "task.h"
  9. /* 开发板硬件bsp头文件 */
  10. #include "bsp_led.h"
  11. #include "bsp_usart.h"
  12. #include "bsp_TiMbase.h"
  13. #include "string.h"
  14. /**************************** 任务句柄 ********************************/
  15. /*
  16. * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
  17. * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
  18. * 这个句柄可以为NULL。
  19. */
  20. /* 创建任务句柄 */
  21. static TaskHandle_t AppTaskCreate_Handle = NULL;
  22. /* LED任务句柄 */
  23. static TaskHandle_t LED1_Task_Handle = NULL;
  24. static TaskHandle_t LED2_Task_Handle = NULL;
  25. static TaskHandle_t CPU_Task_Handle = NULL;
  26. /********************************** 内核对象句柄 *********************************/
  27. /*
  28. * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
  29. * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
  30. * 们就可以通过这个句柄操作这些内核对象。
  31. *
  32. * 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
  33. * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
  34. * 来完成的
  35. *
  36. */
  37. /******************************* 全局变量声明 ************************************/
  38. /*
  39. * 当我们在写应用程序的时候,可能需要用到一些全局变量。
  40. */
  41. /*
  42. *************************************************************************
  43. * 函数声明
  44. *************************************************************************
  45. */
  46. static void AppTaskCreate(void);/* 用于创建任务 */
  47. static void LED1_Task(void* pvParameters);/* LED1_Task任务实现 */
  48. static void LED2_Task(void* pvParameters);/* LED2_Task任务实现 */
  49. static void CPU_Task(void* pvParameters);/* CPU_Task任务实现 */
  50. static void BSP_Init(void);/* 用于初始化板载相关资源 */
  51. /*****************************************************************
  52. * @brief 主函数
  53. * @param 无
  54. * @retval 无
  55. * @note 第一步:开发板硬件初始化
  56. 第二步:创建APP应用任务
  57. 第三步:启动FreeRTOS,开始多任务调度
  58. ****************************************************************/
  59. int main(void)
  60. {
  61. BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  62. /* 开发板硬件初始化 */
  63. BSP_Init();
  64. printf("这是一个[野火]-STM32全系列开发板-FreeRTOS-CPU利用率统计实验!\r\n");
  65. /* 创建AppTaskCreate任务 */
  66. xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
  67. (const char* )"AppTaskCreate",/* 任务名字 */
  68. (uint16_t )512, /* 任务栈大小 */
  69. (void* )NULL,/* 任务入口函数参数 */
  70. (UBaseType_t )1, /* 任务的优先级 */
  71. (TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
  72. /* 启动任务调度 */
  73. if(pdPASS == xReturn)
  74. vTaskStartScheduler(); /* 启动任务,开启调度 */
  75. else
  76. return -1;
  77. while(1); /* 正常不会执行到这里 */
  78. }
  79. /***********************************************************************
  80. * @ 函数名 : AppTaskCreate
  81. * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
  82. * @ 参数 : 无
  83. * @ 返回值 : 无
  84. **********************************************************************/
  85. static void AppTaskCreate(void)
  86. {
  87. BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  88. taskENTER_CRITICAL(); //进入临界区
  89. /* 创建LED_Task任务 */
  90. xReturn = xTaskCreate((TaskFunction_t )LED1_Task, /* 任务入口函数 */
  91. (const char* )"LED1_Task",/* 任务名字 */
  92. (uint16_t )512, /* 任务栈大小 */
  93. (void* )NULL, /* 任务入口函数参数 */
  94. (UBaseType_t )2, /* 任务的优先级 */
  95. (TaskHandle_t* )&LED1_Task_Handle);/* 任务控制块指针 */
  96. if(pdPASS == xReturn)
  97. printf("创建LED1_Task任务成功!\r\n");
  98. /* 创建LED_Task任务 */
  99. xReturn = xTaskCreate((TaskFunction_t )LED2_Task, /* 任务入口函数 */
  100. (const char* )"LED2_Task",/* 任务名字 */
  101. (uint16_t )512, /* 任务栈大小 */
  102. (void* )NULL, /* 任务入口函数参数 */
  103. (UBaseType_t )3, /* 任务的优先级 */
  104. (TaskHandle_t* )&LED2_Task_Handle);/* 任务控制块指针 */
  105. if(pdPASS == xReturn)
  106. printf("创建LED2_Task任务成功!\r\n");
  107. /* 创建LED_Task任务 */
  108. xReturn = xTaskCreate((TaskFunction_t )CPU_Task, /* 任务入口函数 */
  109. (const char* )"CPU_Task",/* 任务名字 */
  110. (uint16_t )512, /* 任务栈大小 */
  111. (void* )NULL, /* 任务入口函数参数 */
  112. (UBaseType_t )4, /* 任务的优先级 */
  113. (TaskHandle_t* )&CPU_Task_Handle);/* 任务控制块指针 */
  114. if(pdPASS == xReturn)
  115. printf("创建CPU_Task任务成功!\r\n");
  116. vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  117. taskEXIT_CRITICAL(); //退出临界区
  118. }
  119. /**********************************************************************
  120. * @ 函数名 : LED_Task
  121. * @ 功能说明: LED_Task任务主体
  122. * @ 参数 :
  123. * @ 返回值 : 无
  124. ********************************************************************/
  125. static void LED1_Task(void* parameter)
  126. {
  127. while (1)
  128. {
  129. LED1_ON;
  130. vTaskDelay(500); /* 延时500个tick */
  131. printf("LED1_Task Running,LED1_ON\r\n");
  132. LED1_OFF;
  133. vTaskDelay(500); /* 延时500个tick */
  134. printf("LED1_Task Running,LED1_OFF\r\n");
  135. }
  136. }
  137. static void LED2_Task(void* parameter)
  138. {
  139. while (1)
  140. {
  141. LED2_ON;
  142. vTaskDelay(300); /* 延时500个tick */
  143. printf("LED2_Task Running,LED1_ON\r\n");
  144. LED2_OFF;
  145. vTaskDelay(300); /* 延时500个tick */
  146. printf("LED2_Task Running,LED1_OFF\r\n");
  147. }
  148. }
  149. static void CPU_Task(void* parameter)
  150. {
  151. uint8_t CPU_RunInfo[400]; //保存任务运行时间信息
  152. while (1)
  153. {
  154. memset(CPU_RunInfo,0,400); //信息缓冲区清零
  155. vTaskList((char *)&CPU_RunInfo); //获取任务运行时间信息
  156. printf("---------------------------------------------\r\n");
  157. printf("任务名 任务状态 优先级 剩余栈 任务序号\r\n");
  158. printf("%s", CPU_RunInfo);
  159. printf("---------------------------------------------\r\n");
  160. memset(CPU_RunInfo,0,400); //信息缓冲区清零
  161. vTaskGetRunTimeStats((char *)&CPU_RunInfo);
  162. printf("任务名 运行计数 使用率\r\n");
  163. printf("%s", CPU_RunInfo);
  164. printf("---------------------------------------------\r\n\n");
  165. vTaskDelay(1000); /* 延时500个tick */
  166. }
  167. }
  168. /***********************************************************************
  169. * @ 函数名 : BSP_Init
  170. * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
  171. * @ 参数 :
  172. * @ 返回值 : 无
  173. *********************************************************************/
  174. static void BSP_Init(void)
  175. {
  176. /*
  177. * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
  178. * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
  179. * 都统一用这个优先级分组,千万不要再分组,切忌。
  180. */
  181. NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
  182. /* LED 初始化 */
  183. LED_GPIO_Config();
  184. /* 串口初始化 */
  185. USART_Config();
  186. /* 基本定时器初始化 */
  187. BASIC_TIM_Init();
  188. }
  189. /********************************END OF FILE****************************/