4. 空闲线程钩子
任务目标
在ThreadX实时操作系统中,空闲线程(idle thread)在所有其他线程均处于阻塞或挂起状态时运行。利用tx_thread_idle_hook注册一个空闲钩子函数,在空闲线程每次被调度时执行,通过统计单位时间内空闲线程的执行次数(或时间),估算CPU利用率。本案例在STM32F407平台上实现,通过串口打印CPU利用率。
实现原理
CPU利用率 = 1 − (空闲时间 / 总时间)。ThreadX在空闲线程中周期性调用钩子函数。我们在钩子函数中累加空闲计数器,同时在一个固定周期(如1秒)的定时器线程中读取该计数器并复位,从而得到空闲占比。由于空闲线程只在CPU无事可做时运行,空闲计数占比越高,CPU利用率越低。
核心代码
/* 头文件 */
#include "tx_api.h"
#include <stdio.h>
/* 空闲钩子计数 */
volatile ULONG idle_count = 0;
ULONG idle_count_last = 0;
/* 空闲钩子函数:每次空闲线程被调度时执行 */
void my_idle_hook(ULONG unused)
{
idle_count++;
}
/* 统计线程:每1秒计算一次CPU利用率 */
void stats_thread_entry(ULONG arg)
{
ULONG total_ticks = TX_TIMER_TICKS_PER_SECOND;
ULONG idle_ticks, cpu_usage;
while (1)
{
/* 等待1秒 */
tx_thread_sleep(total_ticks);
/* 计算空闲占比:空闲计数 / 每秒最大可能空闲计数 */
/* 假设每秒最大空闲计数为100000(根据实际运行调整) */
idle_ticks = idle_count - idle_count_last;
idle_count_last = idle_count;
/* CPU利用率 = 100 - 空闲百分比 */
cpu_usage = 100 - (idle_ticks * 100 / 100000);
if (cpu_usage > 100) cpu_usage = 100;
if (cpu_usage < 0) cpu_usage = 0;
printf("CPU Usage: %lu%%\r\n", cpu_usage);
}
}
/* 应用程序定义 */
void tx_application_define(void *first_unused_memory)
{
TX_THREAD *stats_thread_ptr;
CHAR *stack_ptr;
/* 注册空闲钩子 */
tx_thread_idle_hook_register(my_idle_hook);
/* 创建统计线程 */
tx_thread_create(stats_thread_ptr, "stats", stats_thread_entry, 0,
stack_ptr, 1024, 3, 3, TX_NO_TIME_SLICE);
}
关键说明
- 空闲计数上限标定:代码中
100000为每秒最大空闲计数估算值。实际值取决于MCU主频和ThreadX调度粒度。首次运行时,在系统完全空闲(无其他线程活动)时观察打印值,调整该常数使空闲时CPU显示接近0%。 - 钩子注册:
tx_thread_idle_hook_register必须在tx_application_define中调用,且只能注册一个钩子。 - 精度说明:此方法为近似统计,适合资源受限MCU的轻量级监控。若需更高精度,可使用硬件定时器捕获空闲线程执行时间。
运行与验证
- 在ThreadX初始化完成后,系统会自动调度空闲线程,钩子函数开始累加计数。
- 统计线程每1秒打印一次CPU利用率。
- 验证方法:在系统空闲时,观察串口输出应接近0%;创建一个高优先级忙循环线程后,输出应明显上升(如50%以上)。若数值不随负载变化,检查钩子是否注册成功及计数上限标定是否合理。
常见问题
- 打印值恒为0%:检查
tx_thread_idle_hook_register是否在tx_application_define中调用,且钩子函数未被优化掉(使用volatile修饰计数器)。 - 打印值恒为100%:空闲计数上限标定过小,增大
100000常数。 - 数值波动大:统计周期过短,可增大
tx_thread_sleep的等待时间(如2秒)。