常见轮询模型:忙等待、定时轮询、中断驱动轮询的优劣对比
做硬件调试这些年,轮询模型是绕不开的老朋友。你想想看,系统要对外设状态做合规性检查,到底让 CPU 怎么等?是死死盯着,还是干点别的再回来看?这里头门道不少。我个人习惯把三种模型画成一张图,调试时一眼就知道问题出在哪——先看这张框架图。
忙等待——最直接也最“蛮干”的方式
忙等待,说白了就是写个死循环读状态寄存器。CPU 啥也不干,就盯着那个 bit 翻转。
volatile uint32_t *status_reg = (uint32_t *)0x40001004;
while (!(*status_reg & STATUS_READY)) {
// 空转,啥也不干
}
// 外设准备好,继续处理
这种模型优点很明显:响应快,没有调度抖动,代码简单得像白开水。我在项目中遇到过最典型的用例——芯片复位后的启动序列,PLL 锁定需要几十微秒,用忙等最合适,因为这时候系统还跑不了别的。
缺点也是致命的——CPU 利用率几乎为零。你要是让 i.MX6 处理器在 800 MHz 下空转等待一个 10 ms 的外设,你想想看,800 万个时钟周期全浪费了。
定时轮询——折中的“定时查岗”
定时轮询的思路是:我每隔 N 毫秒派一个定时器中断,在中断里检查外设状态。主业干别的,副业查查岗。
void timer_isr(void) {
if (*status_reg & STATUS_READY) {
process_ready();
}
// 清除定时器中断标志
*timer_clear = 1;
}
好处很清楚:CPU 在等待期间可以跑其他任务,适合中等实时性要求。我之前的相机对焦项目,传感器就绪时间在 5~50 ms 间波动,设 2 ms 的定时轮询,效果不错。
但有个坑:定时周期选不好,轻则响应慢,重则错过窗口。比如外设状态只保持 1 ms,你轮询间隔 5 ms,那就永远抓不到。
| 定时周期 | 响应延迟 | CPU 开销 | 典型场景 |
|---|---|---|---|
| 100 μs | ≈150 μs | 高(接近忙等) | 高精度外设 |
| 1 ms | ≈1.5 ms | 中 | 常规外设 |
| 10 ms | ≈15 ms | 低 | 慢速传感器 |
中断驱动轮询——硬件帮忙“嗑一声”
中断驱动,说白了就是让硬件主动通知你。外设就绪了,拉一根中断线,CPU 在中断服务程序里处理。
void ext_int_isr(void) {
uint32_t data = *status_reg; // 读取状态并清除中断
process_ready(data);
}
这是三种模型里 CPU 利用率最高的——平时 CPU 该干嘛干嘛,只有事件来了才介入。响应也最快,硬件触发到进入 ISR 通常只要几十纳秒。
但别高兴太早。中断驱动最大的坑是:中断风暴!我记得在调试一个 SPI Flash 擦写流程时,外设每完成一个 byte 就触发一次中断,频率高达 5 MHz,CPU 被活活拖死在中断上下文切换里。另一个问题是优先级和嵌套,搞不好就死锁了。
三种模型对比——一张表讲清楚
| 特性 | 忙等待 | 定时轮询 | 中断驱动 |
|---|---|---|---|
| 响应延迟 | 极低(几周期) | 取决于定时周期 | 极低(纳秒级) |
| CPU 利用率 | 极低(几乎为 0) | 中等 | 极高(事件触发才占) |
| 代码复杂度 | 极低 | 中等(需定时器) | 高(ISR/优先级/群) |
| 可预测性 | 高(确定性) | 中等(受调度影响) | 低(中断延迟抖动) |
| CPU 空闲时可处理其他任务 | 否 | 是 | 是 |
| 典型失败案例 | 看门狗溢出 | 错过数据窗口 | 中断风暴导致死锁 |
我的选型经验——没有银弹
做 Polling Compliance 超时调试时,我个人习惯先问三个问题:
- 我需要多快的响应?——微秒级以下用忙等或中断;毫秒级可用定时轮询。
- CPU 还能不能干别的?——如果不能,忙等就是最省事的方案;如果能,优先考虑中断或定时轮询。
- 外设事件频率稳定吗?——稳定且不高,定时轮询很好;突发且高时,中断必须配去抖。