RT-Thread CPU 使用率统计实战:解决 FINSH 组件导致读数恒为 0 的 3 个关键步骤
📅 2026/7/12 6:38:30
👁️ 阅读次数
📝 编程学习
RT-Thread CPU使用率统计实战:解决FINSH组件导致读数恒为0的深度排查指南
1. 问题现象与初步分析
最近在调试RT-Thread的CPU使用率统计功能时,遇到了一个典型问题:调用cpu_usage_get()函数始终返回0值。经过初步排查,发现系统没有进入空闲线程rt_thread_idle_entry(),而根本原因竟是FINSH控制台组件在空闲时持续等待字符输入。
典型症状表现:
- 系统运行正常但CPU使用率显示0%
- 使用
list_thread命令查看时发现idle线程几乎不运行 - 串口终端交互响应正常但影响统计准确性
关键提示:当FINSH组件以阻塞方式等待输入时,会独占CPU资源,导致idle线程无法获得执行权,而CPU使用率统计正是依赖idle线程的运行时间计算得出的。
2. 根本原因剖析
2.1 CPU使用率统计原理
RT-Thread通过空闲线程钩子函数实现CPU使用率统计,其核心算法如下:
static void cpu_usage_idle_hook() { // 1. 首先计算CPU全速运行时的基准值total_count if (total_count == 0) { rt_enter_critical(); tick = rt_tick_get(); while(rt_tick_get() - tick < CPU_USAGE_CALC_TICK) { total_count++; loop = 0; while (loop < CPU_USAGE_LOOP) loop++; } rt_exit_critical(); } // 2. 在相同时间片内统计实际运行计数count count = 0; tick = rt_tick_get(); while (rt_tick_get() - tick < CPU_USAGE_CALC_TICK) { count++; loop = 0; while (loop < CPU_USAGE_LOOP) loop++; } // 3. 计算使用率 = (基准值 - 实际值)/基准值 if (count < total_count) { count = total_count - count; cpu_usage_major = (count * 100) / total_count; cpu_usage_minor = ((count * 100) % total_count) * 100 / total_count; } }2.2 FINSH组件的影响机制
当启用FINSH组件时,默认的rt_hw_console_getchar()实现会以阻塞方式等待串口输入:
// 典型串口获取字符实现(问题版本) char rt_hw_console_getchar(void) { while(!serial_rx_ready()); // 死等字符输入 return serial_getc(); }这种实现会导致以下问题链:
- 系统空闲时进入
rt_thread_idle_entry() - FINSH组件在idle线程中调用
rt_hw_console_getchar() - 函数内部死循环等待输入
- idle线程无法完成完整执行流程
cpu_usage_idle_hook()得不到执行- 统计计数器无法更新
3. 解决方案对比与实现
3.1 方案一:延时法(快速修复)
实现步骤:
- 修改
rt_hw_console_getchar()实现:
char rt_hw_console_getchar(void) { while(!serial_rx_ready()) { rt_thread_delay(1); // 增加1个tick的延时 } return serial_getc(); }优劣分析:
| 优点 | 缺点 |
|---|---|
| 改动量最小 | 增加系统调度开销 |
| 快速解决问题 | 响应速度降低 |
| 兼容性好 | 非根本解决方案 |
适用场景:
- 需要快速验证功能的原型阶段
- 对实时性要求不高的应用
- 资源受限无法进行大改动的场景
3.2 方案二:配置法(推荐方案)
实现步骤:
- 在
rtconfig.h中开启以下配置:
#define RT_USING_IDLE_HOOK #define RT_USING_HOOK #define FINSH_USING_MSH_ONLY- 修改FINSH初始化逻辑:
int finsh_thread_init(void) { /* 仅在使用MSH模式时创建线程 */ #ifdef FINSH_USING_MSH_ONLY rt_thread_t tid; tid = rt_thread_create("tshell", finsh_thread_entry, RT_NULL, 4096, 20, 10); #endif return 0; }关键改进点:
- 使用MSH_ONLY模式避免默认shell占用idle线程
- 单独创建shell线程保持交互功能
- 确保idle线程能正常执行
3.3 方案三:代码重构法(高级方案)
对于需要精确统计的场景,建议重构统计逻辑:
// 新建cpu_usage_adv.c void cpu_usage_adv_init(void) { rt_timer_init(&calc_timer, "cpu_usage", cpu_usage_callback, RT_NULL, RT_TICK_PER_SECOND, RT_TIMER_FLAG_PERIODIC); rt_timer_start(&calc_timer); } static void cpu_usage_callback(void *param) { static rt_tick_t last_idle = 0; rt_tick_t current = rt_thread_idle_gethandler_time(); usage = 100 - (current - last_idle) * 100 / RT_TICK_PER_SECOND; last_idle = current; }优势特征:
- 使用定时器独立于idle线程工作
- 通过系统API直接获取idle线程运行时间
- 计算结果更加精确稳定
- 支持多核CPU扩展
4. 验证与调试技巧
4.1 诊断流程
确认idle线程状态:
list_thread观察idle线程的
max used和status字段检查钩子函数绑定:
rt_thread_idle_sethook(cpu_usage_idle_hook);验证配置宏:
#ifndef RT_USING_IDLE_HOOK #error "CPU usage requires RT_USING_IDLE_HOOK" #endif
4.2 典型调试输出
正常情况应看到如下输出:
CPU利用率:23.5% 线程 状态 优先级 栈最大使用 CPU使用率 ------ ------ -------- ---------- -------- idle ready 31 32% 76.8% shell suspend 20 45% 12.3%异常情况表现:
CPU利用率:0.0% 线程 状态 优先级 栈最大使用 CPU使用率 ------ ------ -------- ---------- -------- idle suspend 31 1% 0.0%5. 进阶优化建议
5.1 多核CPU统计扩展
对于多核系统,需要为每个核心单独统计:
struct cpu_usage { rt_uint8_t core_id; rt_uint8_t usage_major; rt_uint8_t usage_minor; rt_timer_t timer; }; void multi_core_usage_init(void) { for(int i=0; i<CORE_NUM; i++) { cpus[i].core_id = i; rt_timer_init(&cpus[i].timer, "cpu_stat", cpu_stat_cb, &cpus[i], 100, RT_TIMER_FLAG_PERIODIC); } }5.2 动态校准机制
添加自动校准逻辑应对CPU频率变化:
static void auto_calibrate(void) { if(++calib_cnt >= CALIB_CYCLE) { total_count = 0; // 触发重新校准 calib_cnt = 0; } }5.3 线程级CPU统计
扩展实现各线程CPU使用率统计:
struct thread_cpu_usage { rt_thread_t thread; rt_uint32_t run_time; rt_uint32_t total_time; }; void thread_usage_get(struct thread_cpu_usage *usage) { rt_scheduler_lock(); // 遍历线程列表统计运行时间 rt_scheduler_unlock(); }通过本文介绍的三种解决方案,开发者可以根据实际项目需求选择最适合的方式。在资源允许的情况下,推荐采用方案三的重构方法,它不仅解决了FINSH组件导致的问题,还为系统提供了更强大的性能监控能力。
编程学习
技术分享
实战经验