4. 性能剖析工具:CPU占用率分析、中断延迟测量、任务切换时间
性能剖析,说白了就是给系统做体检。CPU占用率、中断延迟、任务切换时间,这三项是嵌入式系统性能的核心指标。我做了这么多年嵌入式,见过太多系统跑着跑着就卡死、响应慢、丢数据,最后查下来,基本都是这三个指标出了问题。
这一章,我们就来聊聊怎么测量、分析、优化这三个关键指标。我会结合我实际项目中的经验,给你一些实用的方法和工具。
4.1 CPU占用率分析
CPU占用率,就是CPU忙活的时间占总时间的比例。太高了,系统响应慢;太低了,资源浪费。理想情况是保持在60%-80%之间。
4.1.1 测量方法
测量CPU占用率,常用的方法有两种:
- 硬件定时器法:用一个高精度定时器,统计空闲任务运行的时间。空闲任务跑得越多,CPU占用率越低。
- 软件插桩法:在任务切换点插入代码,记录每个任务的运行时间。这种方法更精细,但会增加系统开销。
我个人习惯用硬件定时器法,因为它对系统影响小,适合长期监控。下面是一个简单的实现示例:
// 伪代码:使用硬件定时器测量CPU占用率
static uint32_t idle_ticks = 0;
static uint32_t total_ticks = 0;
void idle_task(void) {
while(1) {
// 记录空闲开始时间
uint32_t start = get_timer_ticks();
// 执行空闲操作(如WFI)
__WFI();
// 记录空闲结束时间
idle_ticks += (get_timer_ticks() - start);
total_ticks = get_timer_ticks();
}
}
float get_cpu_usage(void) {
// CPU占用率 = (总时间 - 空闲时间) / 总时间 * 100%
return (1.0f - (float)idle_ticks / total_ticks) * 100.0f;
}
4.1.2 分析思路
拿到CPU占用率数据后,怎么分析?我一般按这个步骤来:
- 看整体:先看平均占用率。如果超过90%,系统大概率有问题。
- 看峰值:再看瞬时峰值。如果峰值超过95%,说明有突发任务占用了大量CPU。
- 看分布:最后看各个任务的占用率。哪个任务吃CPU最多,就重点优化哪个。
4.2 中断延迟测量
中断延迟,就是从硬件触发中断,到CPU开始执行中断服务函数的时间。这个时间越短,系统实时性越好。我见过一个项目,中断延迟从10us飙到100us,结果数据采集直接丢包。
4.2.1 测量方法
测量中断延迟,最直接的方法是用示波器。但有时候条件不允许,我们也可以用软件来测。方法如下:
- GPIO翻转法:在中断触发时,翻转一个GPIO;在中断服务函数开始处,再翻转一次。用示波器测量两次翻转的时间差,就是中断延迟。
- 定时器捕获法:用定时器的捕获功能,记录中断触发时刻和CPU响应时刻。这种方法精度高,但需要硬件支持。
下面是一个用GPIO翻转法测量的示例:
// 伪代码:GPIO翻转法测量中断延迟
void interrupt_handler(void) {
// 记录中断触发时刻(由硬件自动设置)
// 这里假设GPIO在中断触发时已经翻转
// 在中断服务函数开始处翻转GPIO
GPIO_TogglePin(GPIO_PORT, GPIO_PIN);
// 处理中断
// ...
// 清除中断标志
clear_interrupt_flag();
}
4.2.2 影响因素
中断延迟受哪些因素影响?我总结了一下:
| 因素 | 影响 | 优化方法 |
|---|---|---|
| 中断优先级 | 高优先级中断会抢占低优先级 | 合理分配优先级,关键中断设最高 |
| 中断嵌套 | 嵌套越深,延迟越大 | 尽量减少嵌套,使用中断分组 |
| 临界区长度 | 临界区越长,中断响应越慢 | 缩短临界区,使用无锁数据结构 |
| CPU频率 | 频率越高,延迟越低 | 在功耗允许下提高频率 |
4.3 任务切换时间
任务切换时间,就是从一个任务切换到另一个任务所需的时间。这个时间越短,系统响应越快。但任务切换是有代价的,每次切换都要保存和恢复上下文。
4.3.1 测量方法
测量任务切换时间,我一般用两种方法:
- GPIO翻转法:在每个任务开始和结束时翻转GPIO,用示波器测量两次翻转的时间差。
- 系统Tick法:利用操作系统的系统Tick,记录任务切换的时刻。这种方法精度较低,但方便。
下面是一个用系统Tick法测量的示例:
// 伪代码:系统Tick法测量任务切换时间
static uint32_t last_switch_tick = 0;
void task_switch_hook(void) {
uint32_t current_tick = get_system_tick();
uint32_t switch_time = current_tick - last_switch_tick;
last_switch_tick = current_tick;
// 记录切换时间
if (switch_time > max_switch_time) {
max_switch_time = switch_time;
}
}
4.3.2 优化方法
任务切换时间长了怎么办?我一般从这几个方面入手:
- 减少任务数量:任务越多,切换越频繁。能合并的任务就合并。
- 降低优先级:优先级高的任务切换频繁,适当降低优先级可以减少切换。
- 使用信号量:用信号量代替轮询,可以减少不必要的任务切换。
- 优化上下文保存:有些CPU支持硬件上下文保存,可以大大减少切换时间。
4.4 知识体系图
下面这张图,展示了性能剖析的核心逻辑和工具关系:
4.5 实战案例
最后,分享一个我实际遇到的案例。一个数据采集系统,CPU占用率只有40%,但数据经常丢包。我一开始以为是网络问题,查了半天没找到原因。
后来我用GPIO翻转法测了中断延迟,发现最大值达到了200us。而数据采集的中断要求响应时间不超过50us。问题找到了:中断延迟太大,导致数据丢失。
进一步分析,发现中断延迟大的原因是临界区太长。有个任务在临界区里做了大量计算,导致中断被阻塞。我把计算移出临界区,中断延迟降到了20us,数据丢包问题彻底解决。