1 前言
. c7 U) m/ I/ U" b* O" l5 L% K出于性能方面的考虑,有的时候,我们希望知道 CPU 的使用率为多少,进而判断此 CPU 的负载情况和对于当前运行环境是否足够“胜任”。本文将介绍一种计算 CPU 占有率的方法以及其实现原理。5 D5 s& O5 C9 l
# {/ t* z4 T& v! ^" s* c2 移植算法
_2 W) V7 C4 A1 X8 ^5 S6 h2.1 算法简介: A1 I2 m+ p9 K: E
此算法是基于操作系统的,理论上不限于任何操作系统,只要有任务调度就可以。本文将以 FreeRTOST 为例来介绍本算法的使用方法。
& j9 S+ w4 s6 }/ ]9 X, \本文所介绍的算法出处为随 Cube 库一起提供的,它在 cube 库中的位置如下图所示:
# f. m3 G( R3 c2 E
& c0 _, P; H o. I
" q* r9 S% L8 s1 |9 R/ w4 D0 n- B4 M" C
7 m1 D- m/ c8 `' G. Y5 G$ c
本文将以 STM32F4 为例,测试环境为 STM3240G-EVAL 评估板。6 D# w/ F3 b/ ~* h+ }5 H
; C) ?% P8 b; C( q5 C' F& u- q- o
2.2 开始移植" }5 f6 F6 e4 l7 }
本文以 CubeF4 内的示例代码工程STM32Cube_FW_F4_V1.10.0\Projects\STM324xG_EVAL\Applications\FreeRTOS\FreeRTOS_ThreadCreation 为例,IDE使用 IAR。
% q9 Y6 H' R% f& _5 G第一步:使用 IAR 打开 FreeRTOS_ThreadCreation 工程,将 cpu_utils.c 文件添加到工程,并在工程中添加对应头文件目录:
" T& c1 c& I* A C2 g- i( }
" `& G1 W* M0 s0 ~, [' \7 F
8 S# z2 C0 x, Q: H5 Q
( m6 [/ n9 B; m0 r: y6 O0 m. O7 V- J第二步:打开 FreeRTOST 的配置头文件 FreeRTOSConfig.h 修改宏 configUSE_IDLE_HOOK 和 configUSE_TICK_HOOK的值为 1:
# a w) p {' l- #define configUSE_PREEMPTION 1( s* Y( O8 A2 C$ r2 n
- #define configUSE_IDLE_HOOK 1 //修改此宏的值为 1/ g# c. [- x( \8 [/ R% i% A
- #define configUSE_TICK_HOOK 1 //修改此宏的值为 1
# `. F9 T- x z! H P6 u8 H9 g/ q3 ~ - #define configCPU_CLOCK_HZ ( SystemCoreClock )
5 E9 Y' o4 a# e5 _ - #define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
( l8 E# f8 R- g$ g - #define configMAX_PRIORITIES ( 8 )
' E! d5 e% @1 g! T - #define configMINIMAL_STACK_SIZE ( ( uint16_t ) 128 )
复制代码
! A0 n0 y) h3 z" u7 f+ Z第三步:继续在 FreeRTOSConfig.h 头文件的末尾处添加 traceTASK_SWITCHED_IN 与 traceTASK_SWITCHED_OUT 定义:
3 \3 J- I- `6 A- #define traceTASK_SWITCHED_IN() extern void StartIdleMonitor(void); ' u. M+ P$ n5 U+ m2 A& t
- StartIdleMonitor()1 M9 o$ e- w6 f2 G
- #define traceTASK_SWITCHED_OUT() extern void EndIdleMonitor(void);
7 [7 k. d6 r& ?3 B6 n9 `0 @ - EndIdleMonitor()
复制代码 2 X5 r: x: y: C: y6 S# O# y' A
第四步:在 main.h 头文件中 include “”cmsis_os.h“”% i D1 z" _" X9 |( Z. ?
Main.h :
4 {. ^. l V& @' N+ U: t5 r, H- #include "stm32f4xx_hal.h"7 j$ m5 Z- l/ b7 f! Q
- #include "stm324xg_eval.h"
; e0 X: M# c! {; `8 r - #include "cmsis_os.h" //添加包含此头文件
3 J/ {2 r4 U; w+ k8 a; R9 ` s4 b - //…
复制代码
5 p" h9 _9 F, M/ k第五步: 修改工程属性,使编译过程不需要函数原型:/ o& A6 ^6 M D; Y8 @4 N
5 B$ Q: y! i: H; u( w" i7 ^8 l/ F
6 q7 V3 u2 q$ C" D0 R1 C
! \" {9 R7 x+ ^6 }! I- V( Z F4 D) l第六步:在工程中任何用户代码处都可以调用 osGetCPUUsage()函数来获取当前 CPU 的使用率:
. f. d, p1 o( v2 d9 |/ H- static void LED_Thread2(void const *argument)6 c7 t1 V( f. K4 @; D! c+ [
- {7 T5 n5 s' K2 J" F7 |& k
- uint32_t count;! M5 S5 j+ m- f: a+ e
- uint16_t usage =0;" S% N( p" }( k0 }" M0 Q- n6 Y
- (void) argument;
9 c2 {6 h) G9 X* q2 |& ]* U J2 {
$ o' y/ U5 V$ \- for(;;)- q, |" f1 |2 X5 _
- {
. A' \9 T% W. S( D - count = osKernelSysTick() + 10000;+ V, V. Q. |3 f% R4 h
- ' f8 ]8 L$ Y3 j$ c
- /* Toggle LED2 every 500 ms for 10 s */9 q! l; c4 A4 A/ e5 w) P
- while (count >= osKernelSysTick())
$ ?9 x3 w: x! q3 N+ p. ^( W - {
7 a4 F! V/ Y3 I! |' D2 C - BSP_LED_Toggle(LED2);
0 ~3 u1 n$ A5 y3 ?$ Y, _ - 0 ~5 v+ p1 B% m2 ?1 I) ~; o
- osDelay(500);
8 s( b" q: G9 ^- n5 } - }0 |0 M0 c( u2 i( O- i
- usage =osGetCPUUsage(); //获取当前 CPU 的使用率7 l8 [3 o; C5 W1 `$ V
- /* Turn off LED2 */
- ^4 ^ `8 S+ U& C3 Y" r - BSP_LED_Off(LED2);) W3 t$ l- P4 G! y
3 ?5 r( n8 a8 N2 \, m9 d7 f, i- /* Resume Thread 1 */% Y# _" j1 n) @! Q* l& s
- osThreadResume(LEDThread1Handle);8 o! a9 D/ t3 b" L
0 B4 E/ V% {' U6 n- B1 O- /* Suspend Thread 2 */
& ~7 j- R& T7 D4 R& Y, {- M1 [ - osThreadSuspend(NULL);6 e: i- p, q) G9 @+ q7 u
- }* a1 _; x- t) u
- }
复制代码 8 {" J4 g% }/ Z( N- u* K
第七步:编译并运行测试- J) Y# d/ I! T L
在调试状态下使用 Live Watch 窗口监控全部变量 osCPU_Usage 的值:7 O/ u. t# E! T" b. `( B9 b
4 z' \$ i, b' Z3 d0 n+ E# [7 p
% S* E* l3 h/ [' a# D
: T2 l: H+ H' }; E+ W( [osCPU_Usage 是在 cpu_utils.c 文件中定义的全局变量,表示当前 CPU 的使用率,是个动态值,由上图可以,CPU 使用率的动态值为 20%。实际在代码中是按第六步中调用 osGetCPUUsage()函数来获取当前 CPU 的使用率的。
4 t7 n0 F. V8 r' t( r/ ^至此,算法使用方法介绍完毕。
- B# H" r0 g4 v" d! F& [! V1 ~! F1 ^$ c1 H
3 算法实现原理分析! W+ p2 t% A( l6 `
操作系统运行时是不断在不同的任务间进行切换,而驱动这一调度过程是通过系统 tick 来驱动的,即每产生一次系统 tick 则检查一下当前正在运行的任务的环境判断是否需要切换任务,即调度,如果需要,则触发 PendSV,通过在 PendSV 中断调用vTaskSwitchContext()函数来实现任务的调度。而本文所要讲述的 CPU 使用率算法是通过在一定时间内(1000 个时间片内),计算空闲任务所占用的时间片总量,100 减去空闲任务所占百分比则为工作任务所占百分比,即 CPU 使用率。- n; D, p |& W
- void vApplicationIdleHook(void). ?0 k1 \, \/ T3 J, f- J& k
- {; E6 ]/ b! }* v: r1 r% I
- if( xIdleHandle == NULL )
: f0 g, Z$ g _; @! i! `# Q- c - {
. I# ~& \( h9 c* g: F* @5 f" ^ - /* Store the handle to the idle task. */
8 |! O4 k9 N7 | - xIdleHandle = xTaskGetCurrentTaskHandle(); //记录空闲任务的句柄, H4 e/ R+ [ A0 d
- }
7 Z/ X& f+ |& E2 M7 W6 I2 l' _" { - }
复制代码
; E- Q9 i+ |% k; k此函数为空闲任务钩子函数,每次当切换到空闲任务时就会运行此钩子函数,它的作用就是记录当前空闲任务的句柄并保存到全局变量 xIdleHandle。0 E z; L% \5 T- u( R8 ~8 x
- void vApplicationTickHook (void)
5 Y1 {! r! X5 p/ n/ F. z - {
- C& D, m c0 I2 f; v - static int tick = 0;5 |2 h; M6 {! w+ r: c3 v9 M
- : |* V% a4 m9 \
- if(tick ++ > CALCULATION_PERIOD) //每 1000 个 tick,刷新一次 CPU 使用率
% X" G9 t0 ~. @3 h3 ^ - {3 p0 m3 Y' C P4 _
- tick = 0;) v4 a, Y5 c% C v
- 4 b2 A' x3 N6 a
- if(osCPU_TotalIdleTime > 1000)% Y' U9 n: x) l8 C; T0 N! |
- {4 k s/ c) A: ]4 |2 l# P
- osCPU_TotalIdleTime = 1000;2 j; U6 j8 N' |: Z0 _9 X
- }( ~4 w% B) K4 x7 l- ?. z$ o
- osCPU_Usage = (100 - (osCPU_TotalIdleTime * 100) / CALCULATION_PERIOD); //这行代码就是
9 l' x, @* U7 d3 G, m( n - CPU 使用率的具体计算方法了7 g8 N% g7 T; ^8 V. H* Z
- osCPU_TotalIdleTime = 0;
) Z4 D4 ?( x5 t, x, e+ a- i" n - }
# j9 D2 n" V$ b - }
复制代码 * @- V S8 F& Y
此函数为操作系统的 tick 钩子函数,即每次产生系统 tick 中断都会进入到此钩子函数。此钩子函数实际上就是具体计算 CPU使用率的算法了。osCPU_TotalIdleTime 是一个全局变量,表示在 1000 个 tick 时间内空闲任务总共占用的时间片,CALCULATION_PERIOD 宏的值为 1000,即每 1000 个 tick 时间内重新计算一次 CPU 的使用率。: Q. Q2 s6 O' |& l" w9 A! A( s' M
下面两个函数就是如何计算 osCPU_TotalIdleTime 的:& C# [- J9 A! H5 X2 P+ n! z
- void StartIdleMonitor (void)
% Y1 ~2 k. n1 K0 N - {
) U1 c. X% l+ c5 e( v - if( xTaskGetCurrentTaskHandle() == xIdleHandle ) //如果是切入到空闲任务( s0 t$ r" @% E" i9 Q% a4 i
- {
8 P/ q* Q+ w/ `5 X+ }1 y - osCPU_IdleStartTime = xTaskGetTickCountFromISR();//记录切入到空闲任务的时间点& O/ Q6 t8 K- Y2 L# e
- }
5 ?' W/ P, Z6 [5 M9 S# X% e) l3 Q' | - }
7 Y0 D. Z) O9 r% J - void EndIdleMonitor (void)! B+ s2 r% H6 Y
- {
0 o( [2 {% V0 [* p1 i) o - if( xTaskGetCurrentTaskHandle() == xIdleHandle ) //如果是从空闲任务切出% f& @4 S1 l; d) o8 B3 n6 {
- {6 e; w7 Y6 b3 v! u
- /* Store the handle to the idle task. */
. i" U$ _8 h! V" ^# ` - osCPU_IdleSpentTime = xTaskGetTickCountFromISR() - osCPU_IdleStartTime; //计算此次空闲
' K9 Y. x! A3 _ - 任务花费多长时间
5 t' a o! }1 H* ~7 w$ |' v - osCPU_TotalIdleTime += osCPU_IdleSpentTime; //空闲任务所占时间进行累加( _2 w! y% d& J. x
- }
- F# P z# F& b- e - }
复制代码
- X! o8 ?. T7 c/ _* `$ n这两个函数是调度器钩子函数,在调度器进行任务切进和切出时分别回调,StartIdleMonitor()函数记录切换到空闲任务时的时间点,EndIdleMonitor()则在推出空闲任务时计算此次空闲任务花费多长时间,并累加到 osCPU_TotalIdleTime,即空闲任务总共占用的时间片。1 v' k8 |. M7 G3 l2 X. D
- uint16_t osGetCPUUsage (void)
5 U5 n/ ], u5 H7 _, e5 c - {
; y+ i: q0 r; T) o) z0 s - return (uint16_t)osCPU_Usage; //直接返回全局变量 osCPU_Usage,即 CPU 使用率
5 T: P2 C1 p3 C& w# v - }
复制代码 1 h# z0 c4 n; i5 z
全局变量 osCPU_Usage 保存的就是 CPU 的使用率,它是在操作系统的 tick 钩子函数中每隔 1000 个 tick 就被重新计算一次。
& l: |0 \. M1 k- g. ?8 V
# _9 F* y( S1 [" T
6 E) {# p! o. a# Q+ g4 结论8 J8 y3 T$ s. Y
通过此方法可以很好的用来评估 STM23 MCU 的运行性能。9 k2 V" m& p1 Z
7 d: @6 M i, N/ z" y0 i |