🧹RTOS原理与实现12:性能测量
2023-7-20
| 2023-7-26
0  |  阅读时长 0 分钟
type
status
password
date
slug
summary
category
URL
tags
icon

1. 栈使用量测量

1.1 问题概述

当前为每个任务都配备了一个有限的任务栈,用于保存任务的运行状态
notion image
由于栈空间有限,所以存在越界使用的可能,而一旦任务栈越界就可能破坏其他任务的栈或内核数据
以右图为例,
  1. 任务1的栈满时,继续压栈会破坏任务2的栈
  1. 任务2的栈满时,继续压栈会破坏内核数据
这样就会导致任务崩溃或内核崩溃,因此需要根据任务的实际需求,给出合适的栈容量,这里的合适包含了足够且尽可能小的含义
此时就需要栈使用量测量的功能,我们可以根据实测结果,设置合适的栈容量
notion image

1.2 设计原理

notion image
  1. 任务初始化时,将栈空间所有内容清零(初始分配栈时,任务栈空间内容是不确定的)
  1. 任务运行后,栈空间逐渐被其他数值覆盖
  1. 从栈空间统计0单元数,即为栈的最小剩余空间。(栈内空间中所有为零的单元数量)
可见,此处统计的是任务栈的历史最大使用量,这点和FreeRTOS中uxTaskGetStackHighWater函数提供的功能是一致的。

注意事项

为了尽可能准确地测试出任务的栈使用量,有如下注意事项,
  1. 任务要运行足够长的时间
  1. 任务执行路径要尽可能覆盖到所有情况
    1. 以上述代码为例,只有在if条件满足时才会在栈中申请数组,所以如果没有运行过这段代码(那么就不会开辟256个数组),测量结果就是不准确的。
  1. 由于末端压栈内容可能为0,所以返向统计0单元数的方法可能是不准确的。
    1. 💡
      说明:这种情况带来的偏差较小,一般可以忽略
  1. 可能永远无法测量出栈空间的最大用量:主要是上面的①和②难以满足,所以只能是尽可能准确
💡
说明:最终的栈空间分配量可以在测量基础上增加一定数量进行设置

1.3 设计实现

1.3.1 在tTask结构中添加栈统计相关字段

1.3.2 在tTaskInfo结构中添加栈统计相关字段

1.3.3 修改tTaskInit函数

注意:由于此处修改了tTaskInit函数的接口(主要是修改了栈地址的参数传递),所以需要修改所有tTaskInit函数调用,包括空闲任务 / 定时器任务 / 应用任务

1.3.4 修改tTaskGetInfo函数

说明:在统计任务栈剩余单元数时,判断式
中的 <= 非常重要,这确保的统计的准确性(即如果任务栈为空,所有栈单元,尤其是栈顶单元可以被正确统计)
这个说明其实有点儿强迫症了~
更正:上面的理解有误,使用<=反而是错误的,会多统计一个单元,因为
指向的位置已经是栈顶之后的一个单元了

2. CPU使用率测量

2.1 需求概述

测量CPU使用率的目的,
  1. 了解CPU负载,找出低效代码(越低效的代码越消耗CPU)
  1. 发现系统异常(有些系统异常表现为CPU使用率突然一直占用100%)

2.2 设计原理

2.2.1 直接统计

统计原理:统计单位时间内CPU分别运行应用任务代码和空闲任务代码的时间
问题:无法得到单位时间内执行的指令数量(硬件上就不支持),所以只能间接统计

2.2.2 间接统计

notion image
  1. 首先设置特殊代码块,其中包含执行计数,每执行一次特殊代码块就将计数加1
  1. 在系统启动之初的单位时间内(e.g. 1s),仅运行特殊代码块,进而统计出单位时间内仅执行特殊代码块的运行次数Max
  1. 后续每个单位时间统计一次CPU使用率,假设正常运行期间单位时间内特殊代码块运行次数为Cnt,那么CPU使用率即为,
注意:此处先计算Cnt * 100.0是为了将整个计算过程转换为浮点数类型

2.3 设计实现

代码保存在main.c文件中
notion image

2.3.1 添加计数值

说明1:SysTick计数
SytTick启动后,每次SysTick ISR对其加1,作为整个系统的SysTick计数,后续按单位时间统计idleMaxCount与idleCount均依赖该计数值
说明2:idleMaxCount
当前设计将idleTask作为特殊代码块,idleMaxCount用于统计在SysTick使能后,单位时间内仅运行idleTask的运行次数
说明3:idleCount
每个单位时间内idleTask的运行次数,用于计算CPU使用率,
说明4:enableCpuUsageStat
由于CPU使用率统计依赖SysTick,enableCpuUsageStat标志用于同步CPU统计与系统时钟,即只有当系统时钟产生后才进行CPU统计
根据上面的分析,我们添加如下函数,
说明:tTimeTickInit函数和initCpuUsageStat函数需要在main函数中调用

2.3.2 定时器任务启动分离

由于在系统启动后的初始单位时间内需要统计idleMaxCount,所以在main函数中只能启动idleTask,其他任务均不能启动
而原先的tTimerModuleInit函数在初始化定时器相关资源时会启动定时器任务,因此此处需要将该操作分离出来
说明:tTimerModuleInit函数由main函数调用,而tTimerInitTask函数由idleTask任务调用

2.3.3 修改idleTask

说明1:关闭调度
统计CPU需要先启动SysTick,而SysTick ISR中会触发调度,所以此处关闭调度,确保在系统启动的初始单位时间内仅有idleTask运行,以便统计idleMaxCount
说明2:同步CPU统计与系统时钟节拍
在实际运行特殊代码块进行统计之前,需要等待SysTick到来,这就是此处同步的目的

2.3.4 添加checkCpuUsage函数

说明1:checkCpuUsage函数由SysTick ISR调用,当第一个时钟节拍到来时,将enableCpuUsageStat置1,这样特殊代码块就可以执行了
说明2:单位时间
此处设置的单位时间为1s,而TICKS_PER_SEC为1s对应的时钟节拍数

2.3.5 修改SysTick ISR

说明:在完成系统初始单位时间的统计后才处理软硬定时器列表

2.3.6 添加tCpuUsageGet函数

说明1:对cpuUsage的互斥
计算cpuUsage的checkCpuUsage函数虽然没有使用临界区保护cpuUsage,但是其调用者SysTick_Handler在调用过程中使用了临界区进行保护,所以该全局变量是安全的
对idleCount的保护也是以相同的方式实现的,在idleTask中,在临界区中递增idleCount;在checkCpuUsage函数中使用idleCount时虽然没有直接互斥,但是对checkCpuUsage的调用本身是在临界区中进行的
说明2:启动2s后才有有效的CPU使用率
启动后第1s统计idleMaxCount,然后每秒统计一次CPU使用率,所以启动2秒后才有有效的CPU使用率
  • RTOS
  • RTOS原理与实现11:软件定时器RTOS原理与实现13:内核裁剪与移植
    Loading...
    目录