3. Polling 模式介绍:主动轮询与被动中断
这一章我们来聊聊 Polling 模式。说白了,就是状态机怎么跟外界“打招呼”。是它主动去问“你好了没?”,还是等着对方通知“我好了,快来”!
这两种方式,就是主动轮询和被动中断。我刚入行那会儿,总觉得轮询简单粗暴,啥都用轮询。直到有一次,一个高吞吐的数据采集设备,因为轮询太频繁,CPU 被吃光了,连控制台都卡死。那时候我才意识到,选哪种模式,不是拍脑袋决定的。
3.1 主动轮询:我主动去问
主动轮询,就是状态机每隔一段时间,自己去检查外部条件是否满足。
举个例子:你在等快递。轮询模式就是,你每隔 10 分钟去门口看一眼,快递到了没。要是没到,就回去继续干活,过一会儿再去看。
代码里怎么写?大概是这样:
while(1) {
if (check_sensor() == DATA_READY) {
read_data();
process_data();
}
delay(100); // 轮询间隔
}
简单吧?但这里有个坑:轮询间隔设短了,CPU 忙死;设长了,响应慢。
我记得有个项目,需要响应一个按键。我一开始把轮询间隔设成 1 毫秒,结果 CPU 占用率直接飙到 40%,别的任务干不了。后来改成 10 毫秒,CPU 占用降下来了,但按键响应偶尔有延迟,用户体验不好。
3.2 被动中断:等对方来通知
被动中断就完全不一样了。还是等快递的例子:中断模式就是,你给快递员打电话,说“到了给我发个短信”。然后你就可以安心睡觉、看书、打游戏。等短信一来,再去门口拿。
中断模式下,状态机可以安心干自己的事。直到外部触发了一个“中断信号”,它才暂停当前工作,去处理这个事件。
硬件上通常这样配置:
enable_interrupt(SENSOR_IRQ);
while(1) {
// 正常任务,不用管传感器
do_other_stuff();
}
// 中断服务函数
void sensor_irq_handler() {
read_data();
process_data();
}
中断的好处很明显:CPU 利用率高,响应快。但坏处也很致命:中断服务函数不能写太长,否则会阻塞其他中断。而且,多个中断同时来了怎么办?优先级怎么处理?这都需要仔细设计。
核心区别:轮询是状态机主动“驱动”流程;中断是外部事件“驱动”状态机。
3.3 两种模式的优缺点对比
话不多说,直接上表。这个表我在好多项目里都贴墙上,算是经验总结。
| 特性 | 主动轮询 | 被动中断 |
|---|---|---|
| CPU 占用 | 高,尤其是短间隔轮询 | 低,只在事件发生时占用 |
| 响应延迟 | 取决于轮询间隔,可能不稳定 | 几乎实时,取决于中断优先级 |
| 实现复杂度 | 低,就是 if-check 和 delay | 高,需要配置中断控制器、优先级、嵌套等 |
| 多事件处理 | 容易,循环里顺序检查就行 | 麻烦,需要设计中断屏蔽和嵌套逻辑 |
| 可预测性 | 高,时序是固定的 | 低,中断到达时间不确定 |
| 调试难度 | 低,加个打印就能看 | 高,中断上下文难打印,容易死锁 |
我个人的习惯是:如果事件发生频率很高(比如每秒几百次),用轮询更简单,因为中断开销反而更大。如果事件发生很稀疏(比如按键、报警信号),用中断更合理,CPU 不用白白等着。
一个小技巧:你可以把轮询和中断结合起来。比如,用中断来“唤醒”一个低功耗的轮询任务。我曾经在一个电池供电的设备里这么干过,效果极好。
3.4 状态机里的具体应用
在状态机里,轮询和中断的体现方式也不同。
轮询模式下,状态机自己问“条件满足了没”:
// 轮询式状态机
void state_machine_poll() {
switch(state) {
case IDLE:
if (flag_ready == true) { state = READ; }
break;
case READ:
if (data_valid == true) { state = PROCESS; }
break;
case PROCESS:
// ...
break;
}
}
中断模式下,状态机是被动的:
// 中断驱动状态机
void irq_handler() {
// 直接推进状态机
state = READ;
data = read_data();
// 可能会有状态标记,让主循环去处理
}
void main_loop() {
if (state_changed) {
process_state(state);
state_changed = false;
}
}
注意:在中断里直接改变状态机的状态,要非常小心。如果状态机正在处理上一个状态,你突然改了,会导致逻辑混乱。我一般会在中断里只设一个“事件标记”,然后在主循环里统一处理。
千万别在中断里做耗时操作! 我曾经见过有人把文件写入操作放在中断里,结果系统直接卡死。中断里只做三件事:读寄存器、设标记、清中断。其他事情扔给主循环。
你想想看,为什么很多嵌入式工程师讨厌中断?因为调试太麻烦了。一进中断,程序跑到哪了都不知道,加个 printf 还可能导致死循环。所以,能用轮询解决的事,就别上中断。
但是,如果你做的是实时控制系统,比如电机控制、无人机飞控,那必须用中断。轮询那点延迟,可能直接导致系统抖起来。
3.5 本章核心知识结构
下面的图,是我自己画的。把主动轮询和被动中断的区别、优缺点、应用场景都梳理清楚了。你可以保存下来,做项目时对照着看。
你看这个图,左边是轮询,右边是中断。中间箭头连接,意思是你可以在同一个系统里混合使用。我个人强烈建议,刚开始做设计时,先确定事件频率,再决定用哪种模式。不要想当然。