16. CPU使用率统计

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

16.2. CPU利用率的作用

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

16.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利用率统计的话,需要自定义配置一下,在STM32CubeIDE中使能以下三个选项

CPU利用率统计

通过上面的配置将会在FreeRTOSConfig.h文件中设置以下三个宏为1。

FreeRTOSConfig.h
1
2
3
 #define configGENERATE_RUN_TIME_STATS            1
 #define configUSE_TRACE_FACILITY                 1
 #define configUSE_STATS_FORMATTING_FUNCTIONS     1

然后需要实现一个中断频率为20000HZ定时器,用于系统运行时间统计,其实配置如下

定时器设置

在定时器中断周期回调函数中只需将CPU_RunTime变量自加即可,这个变量是用于记录系统运行时间的, 其代码如下所示

bsp_basic_tim.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 volatile unsigned long CPU_RunTime = 0UL;

 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
 {
     if(htim->Instance == TIM6 )
     {
         CPU_RunTime++;

     }
 }

使用CPU利用率还需要实现configureTimerForRunTimeStats与getRunTimeCounterValue函数,其函数内容相对简单。

bsp_basic_tim.c
1
2
3
4
5
6
7
8
9
 void configureTimerForRunTimeStats(void)
 {
     CPU_RunTime = 0UL;
 }

 unsigned long getRunTimeCounterValue(void)
 {
     return CPU_RunTime;
 }

16.4. CPU利用率统计实验

CPU利用率实验是在FreeRTOS中创建了三个线程,其中两个线程是普通线程, 另一个线程用于获取CPU利用率与任务相关信息并通过串口打印出来。其线程配置如下所示

线程设置

本小节只讲解重点部分代码,完整代码请打开工程查看。

16.4.1. MX_FREERTOS_Init函数

MX_FREERTOS_Init函数由STM32CubeIDE生成,代码如下所示

app_freertos.c
1
2
3
4
5
6
7
 void MX_FREERTOS_Init(void) {

     LED1TaskHandle = osThreadNew(LED1_Task, NULL, &LED1Task_attributes);
     LED2TaskHandle = osThreadNew(LED2_Task, NULL, &LED2Task_attributes);
     CPUTaskHandle = osThreadNew(CPU_Task, NULL, &CPUTask_attributes);

 }

16.4.2. LED1_Task线程

app_freertos.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 void LED1_Task(void *argument)
 {
     for(;;)
     {
         LED1_ON;
         osDelay(500);   /* 延时500个tick */
         printf("LED1_Task Running,LED1_ON\r\n");
         LED1_OFF;
         osDelay(500);   /* 延时500个tick */
         printf("LED1_Task Running,LED1_OFF\r\n");
     }
 }

16.4.3. LED2_Task线程

app_freertos.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 void LED2_Task(void *argument)
 {
     for(;;)
     {
         LED2_ON;
         osDelay(300);   /* 延时500个tick */
         printf("LED2_Task Running,LED1_ON\r\n");

         LED2_OFF;
         osDelay(300);   /* 延时500个tick */
         printf("LED2_Task Running,LED1_OFF\r\n");
     }
 }

16.4.4. CPU利用率相关线程

app_freertos.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 uint8_t CPU_RunInfo[400];           //保存任务运行时间信息

 void CPU_Task(void *argument)
 {
     //启动定时器
     HAL_TIM_Base_Start_IT(&htim6);

     for(;;)
     {
         memset(CPU_RunInfo,0,400);                          //信息缓冲区清零
         vTaskList((char *)&CPU_RunInfo);  //获取任务运行时间信息
         printf("---------------------------------------------\r\n");
         printf("任务名    任务状态 优先级     剩余栈     任务序号\r\n");
         printf("%s", CPU_RunInfo);
         printf("---------------------------------------------\r\n");

         memset(CPU_RunInfo,0,400);                          //信息缓冲区清零
         vTaskGetRunTimeStats((char *)&CPU_RunInfo);
         printf("任务名         运行计数    使用率\r\n");
         printf("%s", CPU_RunInfo);
         printf("---------------------------------------------\r\n\n");
         osDelay(1000);   /* 延时500个tick */

     }
 }

16.4.5. main函数

app_freertos.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 int main(void)
 {
     HAL_Init();

     if(IS_ENGINEERING_BOOT_MODE())
     {
         SystemClock_Config();
     }

     MX_GPIO_Init();          //初始化GPIO
     MX_USART3_UART_Init(); //初始化串口
     MX_TIM6_Init();         //初始化定时器

     osKernelInitialize();  //初始化内核状态
     MX_FREERTOS_Init();      //创建任务
     osKernelStart();                //启动任务调度器,

     while (1)
     {

     }
 }

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

当程序运行会在串口中不断打印

实验现象