14. CPU使用率统计

14.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处理数据的时间。

14.2. CPU利用率的作用

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

14.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()这 两个宏定义,具体见代码清单23‑1加粗部分。

代码清单23‑1配置运行时间和任务状态收集关宏定义

1 /*

2 FreeRTOS与运行时间和任务状态收集有关的配置选项

3 /

4 //启用运行时间统计功能

5 #define configGENERATE_RUN_TIME_STATS 1

6 //启用可视化跟踪调试

7 #define configUSE_TRACE_FACILITY 1

8 /* 与宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数

9 * prvWriteNameToBuffer()

10 * vTaskList(),

11 * vTaskGetRunTimeStats()

12 */

13 #define configUSE_STATS_FORMATTING_FUNCTIONS 1

14

15 extern volatileuint32_t CPU_RunTime;

16

17 #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (CPU_RunTime = 0ul)

18 #define portGET_RUN_TIME_COUNTER_VALUE() CPU_RunTime

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

代码清单23‑2定时器中断服务函数

1 /* 用于统计运行时间 */

2 volatileuint32_t CPU_RunTime = 0UL;

3

4 void BASIC_TIM_IRQHandler (void)

5 {

6 if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) {

7 CPU_RunTime++;

8 TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);

9 }

10 }

然后我们就可以在任务中调用vTaskGetRunTimeStats()和vTaskList()函数获得任务的相关信息与CPU使用率的相关信息,然后打印出来即可,具体见代码清单23‑3加粗部分。关于vTaskGetRunTimeStats()和vTaskList()函数的具体实现过程就不讲解了,有兴趣 可以看看源码。

代码清单23‑3获取任务信息与CPU使用率

1 memset(CPU_RunInfo,0,400); //信息缓冲区清零

2

3 vTaskList((char *)&CPU_RunInfo); //获取任务运行时间信息

4

5 printf(“———————————————rn”);

6 printf(“任务名任务状态优先级剩余栈任务序号rn”);

7 printf(“%s”, CPU_RunInfo);

8 printf(“———————————————rn”);

9

10 memset(CPU_RunInfo,0,400); //信息缓冲区清零

11

12 vTaskGetRunTimeStats((char *)&CPU_RunInfo);

13

14 printf(“任务名运行计数使用率rn”);

15 printf(“%s”, CPU_RunInfo);

16 printf(“———————————————rnn”);

14.4. CPU利用率统计实验

CPU利用率实验是是在FreeRTOS中创建了三个任务,其中两个任务是普通任务,另一个任务用于获取CPU利用率与任务相关信息并通过串口打印出来。具体见代码清单23‑4加粗部分。

代码清单23‑4CPU利用率统计实验

1 /**

2 \*

3 * @file main.c

4 * @author fire

5 * @version V1.0

6 * @date 2018-xx-xx

7 * @brief FreeRTOS v9.0.0 + STM32

8 \*

9 * @attention

10 *

11 * 实验平台:野火 STM32开发板

12 * 论坛 :http://www.firebbs.cn

13 * 淘宝 :https://fire-stm32.taobao.com

14 *

15 \*

16 */

17

18 /*

19 \*

20 * 包含的头文件

21 \*

22 */

23 /* FreeRTOS头文件 */

24 #include”FreeRTOS.h”

25 #include”task.h”

26 /* 开发板硬件bsp头文件 */

27 #include”bsp_led.h”

28 #include”bsp_usart.h”

29 #include”bsp_TiMbase.h”

30 #include”string.h”

31 /* 任务句柄 /

32 /*

33 *

34 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄

35 *

36 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么

37 * 这个句柄可以为NULL。

38 */

39 /* 创建任务句柄 */

40 static TaskHandle_t AppTaskCreate_Handle = NULL;

41 /* LED任务句柄 */

42 static TaskHandle_t LED1_Task_Handle = NULL;

43 static TaskHandle_t LED2_Task_Handle = NULL;

44 static TaskHandle_t CPU_Task_Handle = NULL;

45 /* 内核对象句柄 /

46 /*

47 *

48 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核

49 *

50 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我

51 * 们就可以通过这个句柄操作这些内核对象。

52 *

53 *

54 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信

55

56 *

57 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数

58 * 来完成的

59 *

60 */

61

62

63 /* 全局变量声明 /

64 /*

65 * 当我们在写应用程序的时候,可能需要用到一些全局变量。

66 */

67

68

69 /*

70 \*

71 * 函数声明

72 \*

73 */

74 static void AppTaskCreate(void);/* 用于创建任务 */

75

76 static void LED1_Task(void* pvParameters);/* LED1_Task任务实现 */

77 static void LED2_Task(void* pvParameters);/* LED2_Task任务实现 */

78 static void CPU_Task(void* pvParameters);/* CPU_Task任务实现 */

79 static void BSP_Init(void);/* 用于初始化板载相关资源 */

80

81 /*

82 * @brief 主函数

83 * @param 无

84 * @retval 无

85 * @note 第一步:开发板硬件初始化

86 第二步:创建APP应用任务

87 第三步:启动FreeRTOS,开始多任务调度

88 /

89 int main(void)

90 {

91 BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */

92

93 /* 开发板硬件初始化 */

94 BSP_Init();

95 printf(“这是一个[野火]-STM32全系列开发板-FreeRTOS-CPU利用率统计实验!rn”);

96 /* 创建AppTaskCreate任务 */

97 xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,/* 任务入口函数 */

98 (const char* )”AppTaskCreate”,/* 任务名字 */

99 (uint16_t )512, /* 任务栈大小 */

100 (void* )NULL,/* 任务入口函数参数 */

101 (UBaseType_t )1, /* 任务的优先级 */

102 (TaskHandle_t* )&AppTaskCreate_Handle);

103 /* 启动任务调度 */

104 if (pdPASS == xReturn)

105 vTaskStartScheduler(); /* 启动任务,开启调度 */

106 else

107 return -1;

108

109 while (1); /* 正常不会执行到这里 */

110 }

111

112

113 /*

114 * @ 函数名: AppTaskCreate

115 * @ 功能说明:为了方便管理,所有的任务创建函数都放在这个函数里面

116 * @ 参数:无

117 * @ 返回值:无

118 /

119 static void AppTaskCreate(void)

120 {

121 BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */

122

123 taskENTER_CRITICAL(); //进入临界区

124

125 /* 创建LED_Task任务 */

126 xReturn = xTaskCreate((TaskFunction_t )LED1_Task, /* 任务入口函数 */

127 (const char* )”LED1_Task”,/* 任务名字 */

128 (uint16_t )512, /* 任务栈大小 */

129 (void* )NULL, /* 任务入口函数参数 */

130 (UBaseType_t )2, /* 任务的优先级 */

131 (TaskHandle_t* )&LED1_Task_Handle);

132 if (pdPASS == xReturn)

133 printf(“创建LED1_Task任务成功!rn”);

134

135 /* 创建LED_Task任务 */

136 xReturn = xTaskCreate((TaskFunction_t )LED2_Task, /* 任务入口函数 */

137 (const char* )”LED2_Task”,/* 任务名字 */

138 (uint16_t )512, /* 任务栈大小 */

139 (void* )NULL, /* 任务入口函数参数 */

140 (UBaseType_t )3, /* 任务的优先级 */

141 (TaskHandle_t* )&LED2_Task_Handle);

142 if (pdPASS == xReturn)

143 printf(“创建LED2_Task任务成功!rn”);

144

145 /* 创建LED_Task任务 */

146 xReturn = xTaskCreate((TaskFunction_t )CPU_Task, /* 任务入口函数 */

147 (const char* )”CPU_Task”,/* 任务名字 */

148 (uint16_t )512, /* 任务栈大小 */

149 (void* )NULL, /* 任务入口函数参数 */

150 (UBaseType_t )4, /* 任务的优先级 */

151 (TaskHandle_t* )&CPU_Task_Handle);

152 if (pdPASS == xReturn)

153 printf(“创建CPU_Task任务成功!rn”);

154

155 vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务

156

157 taskEXIT_CRITICAL(); //退出临界区

158 }

159

160

161

162 /*

163 * @ 函数名: LED_Task

164 * @ 功能说明: LED_Task任务主体

165 * @ 参数:

166 * @ 返回值:无

167 /

168 static void LED1_Task(void* parameter)

169 {

170 while (1) {

171 LED1_ON;

172 vTaskDelay(500); /* 延时500个tick */

173 printf(“LED1_Task Running,LED1_ONrn”);

174 LED1_OFF;

175 vTaskDelay(500); /* 延时500个tick */

176 printf(“LED1_Task Running,LED1_OFFrn”);

177

178 }

179 }

180

181 static void LED2_Task(void* parameter)

182 {

183 while (1) {

184 LED2_ON;

185 vTaskDelay(300); /* 延时500个tick */

186 printf(“LED2_Task Running,LED1_ONrn”);

187

188 LED2_OFF;

189 vTaskDelay(300); /* 延时500个tick */

190 printf(“LED2_Task Running,LED1_OFFrn”);

191 }

192 }

193

194 static void CPU_Task(void* parameter)

195 {

196 uint8_t CPU_RunInfo[400]; //保存任务运行时间信息

197

198 while (1) {

199 memset(CPU_RunInfo,0,400); //信息缓冲区清零

200

201 vTaskList((char *)&CPU_RunInfo); //获取任务运行时间信息

202

203 printf(“———————————————rn”);

204 printf(“任务名任务状态优先级剩余栈任务序号rn”);

205 printf(“%s”, CPU_RunInfo);

206 printf(“———————————————rn”);

207

208 memset(CPU_RunInfo,0,400); //信息缓冲区清零

209

210 vTaskGetRunTimeStats((char *)&CPU_RunInfo);

211

212 printf(“任务名运行计数使用率rn”);

213 printf(“%s”, CPU_RunInfo);

214 printf(“———————————————rnn”);

215 vTaskDelay(1000); /* 延时500个tick */

216 }

217 }

218

219 /*

220 * @ 函数名: BSP_Init

221 * @ 功能说明:板级外设初始化,所有板子上的初始化均可放在这个函数里面

222 * @ 参数:

223 * @ 返回值:无

224 /

225 static void BSP_Init(void)

226 {

227 /*

228 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15

229 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,

230 * 都统一用这个优先级分组,千万不要再分组,切忌。

231 */

232 NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );

233

234 /* LED 初始化 */

235 LED_GPIO_Config();

236

237 /* 串口初始化 */

238 USART_Config();

239

240 /* 基本定时器初始化 */

241 BASIC_TIM_Init();

242

243 }

244

245 /END OF FILE/

14.5. CPU利用率统计实验现象

程序编译好,用USB线连接电脑和开发板的USB接口(对应丝印为USB转串口),用DAP仿真器把配套程序下载到野火STM32开发板(具体型号根据你买的板子而定,每个型号的板子都配套有对应的程序),在电脑上打开串口调试助手,然后复位开发板就可以在调试助手中看到串口的打印信息,具体见图23‑1。

cpuusa002

图23‑1CPU利用率实验现象