什么时候应该使用轮询方法,什么时候应该使用基于中断的方法?有没有可以同时使用的场景?
14 回答
如果感兴趣的事件是:
- 异步
- 紧迫的
- 不常见
那么基于中断的处理程序将是有意义的。
如果感兴趣的事件是:
- 同步(即你知道什么时候在一个小窗口内期待它)
- 不紧急(即缓慢的轮询间隔没有不良影响)
- 频繁(即您的大多数轮询周期都会产生“命中”)
那么轮询可能更合适。
其他考虑因素包括您是为操作系统编写设备驱动程序,还是仅编写不支持线程的裸机代码。在裸机情况下,CPU 通常只是在不忙的时候循环,所以它也可能在轮询某些东西。
应尽可能避免轮询,因为它通常会不必要地占用大量 CPU 周期(除非 (a) 您只打算进行短时间轮询或 (b) 您可以在轮询循环中睡一段合理的时间) )。浪费 CPU 周期不仅从性能角度来看是不好的,而且还会增加功耗,这很可能是电池供电的嵌入式应用程序的一个问题。
在决定轮询或中断时,您必须充分了解您试图关注的事件的性质以及您对它的反应。
当什么都没有发生时,中断不需要处理,但当有事情发生时需要你的全部注意力。如果事件是外部事件并且具有噪声边缘或快速脉冲,那么这可能会导致中断的严重问题,您必须小心设置中断。
在这个例子中,中断例程响应激光束变得清晰,并为它被阻塞的事件设置自己:
BEAM_INTR_EN = TRUE; /*re-enable the beam interrupts*/
/*Set the beam interrupt for the next clear to blocked event*/
BEAM_INTR_EDGE = CLEAR_TO_BLOCKED;
BEAM_INTR_FLAG = FALSE; /*Clear the interrupt*/
该代码有 2 个弱点: 1) 如果在清除中断标志之前激光束再次被阻挡 (BEAM_INTR_FLAG = FALSE;)。中断将被错过,代码将与激光束状态不同步。
2) 在后台程序中设置中断或为高于此代码所在优先级的优先级设置中断时,启用中断时必须小心。如果中断标志在启用之前已经设置(错误地),则中断例程将在启用后立即被错误地调用,并且可能是错误的边沿。
修复 1) 的最简单方法是在设置中断后仔细检查,如果已发生则强制中断。要修复 2) 将中断的启用移到仔细检查之后:
/*Set the beam interrupt for the next clear to blocked event*/
BEAM_INTR_EDGE = CLEAR_TO_BLOCKED;
BEAM_INTR_FLAG = FALSE; /*Clear the interrupt*/
/*Double check beam state to see if it has already gone blocked*/
if (BEAM_STATE == BEAM_BLOCKED)
{
BEAM_INTR_FLAG = TRUE; /*Force the interrupt to re-enter the ISR after exiting*/
}
BEAM_INTR_EN = TRUE; /*re-enable the beam interrupts*/
中断的强制使系统使用相同的状态机工作,只是手动强制它循环以覆盖盲点。
基本上:
Set the edge to detect the next interrupt event
Clear the interrupt flag
if (the event has already occurred)
{
Set the interrupt flag to force the interrupt
}
Enable the interrupt
如果对事件的响应时间必须保持一致(例如,在输入线变高后的 1ms +/-10us 后,传输事件信号),那么中断通常是最好的。
如果对事件的响应时间必须在一定时间内(例如,在输入线变为高电平的 1ms 内,传输事件信号),那么最好使用中断。
中断的问题是您必须开始考虑线程,并且两段代码可以同时访问相同的数据。
中断也有利于处理器在等待某些事情发生时进入低功耗模式(睡眠/空闲等)。
话虽如此,如果处理器只有一件事要做,轮询可以对事件做出非常紧张的时间响应,通常中断硬件需要几个周期来响应事件,而紧张的轮询循环会做。
如果事件不是时间关键的并且可能有噪音(例如有人按下开关),那么轮询允许简单的过滤而不会错过长期转换。一个常见的错误是在设置时多次轮询:
void fnInitialiseSystem(void)
{
if (MODE_INPUT == MODE_A) /*First polling of the MODE_INPUT*/
{
PR2 = PR2_MODE_A;
}
else
{
PR2 = PR2_MODE_B;
}
OpenTimer2( TIMER_INT_ON &
T2_PS_1_1 &
T2_POST_1_8 );
if (MODE_INPUT == MODE_A) /*Second polling of the MODE_INPUT*/
{
CurrentMode = MODE_A;
PROBE_INT_EDGE = CLEAR_TO_BLOCKED;
}
else
{
CurrentMode = MODE_B;
PROBE_INT_EDGE = BLOCKED_TO_CLEAR;
}
}
在上面的示例中,MODE_INPUT 是一个外部开关,如果两次轮询 MODE_INPUT 不同,则行为是意外的。在读取这些类型的信号时,最好使用过滤来决定输入的长期状态,并对过滤后的版本执行操作。
例如,使用开关去弹跳只需定期检查一个开关(每 1 毫秒?),如果其中一些(比如 16 个)与过滤版本(开关打开)不同(开关关闭),则更新结果并执行所需的操作. 小心信号混叠,振荡信号可能看起来很稳定!
再次使用轮询和中断的示例是使用不经常更改但在更改时会产生噪声的输入。开关也是一个很好的例子:代码可以设置一个中断来检查开关状态的变化,当中断发生时,可以定期轮询开关,直到开关状态“稳定”(要么改变状态或回到原来的状态)。这具有在什么都没有发生时处理开销低的优势,以及在发生某些事情时过滤噪声的优势。
有时您实际上需要同时使用两者。例如,如果事件是零星的,但以高速爆发的形式出现;您可能需要首先响应中断,然后在重新启用中断轮询之前查看是否已经发生了另一个事件,以避免中断上下文切换的一些开销。我相信 Linux 网络接口在这种模式下运行。
这是我在分析轮询与中断方法时遇到的一些有趣的链接 -
http://web.engr.oregonstate.edu/~traylor/ece473/lectures/interrupts.pdf - 非常有趣的链接
http://www.atarimagazines .com/compute/issue149/60_Interrupts_made_easy.php
http://www.electro-tech-online.com/micro-controllers/8440-interrupt-vs-polling.html
http://www.microchip.com/forums/m397196 -print.aspx
http://www.cs.huji.ac.il/course/2006/67630/Lectures/interrupts.pdf
http://sunsite.nus.edu.sg/LDP/LDP/tlk/node86.html
希望这会有所帮助。
简短的回答是在轮询太慢时使用中断方法。(太慢了,我的意思是如果轮询丢失数据,中断方法是必要的)
基本上,如果由于某些硬件或软件原因中断模式不可用,则使用轮询模式。因此,从功耗、性能等角度来看,中断模式更可取(同意 Paul R)。轮询模式也可用于原型设计、无需外围设备的内核以及用于某些测试目的。
当需要低延迟时,首选中断。如果您每秒轮询某个条件 N 次,那么平均而言,您会在实际发生后 1/N 的一半时间内发现该条件。
当需要绝对确定性定时时,有时首选轮询。就其本质而言,中断可能发生在不可预测的时间,并使时序分析变得非常复杂,而对于轮询系统,就截止期限的满足做出可证明的陈述相对容易。
始终使用中断。这样您就永远不会丢失数据。在事件驱动或线程应用程序中,即使是最慢的信号也应该是中断驱动的。
您应该使用轮询的唯一时间是当您使用调度程序并且硬件上的缓冲区足够深以确保不会丢失数据时。
轮询模式在具有高频事件的系统中很有用,在这些系统中,与进入和退出中断处理程序相关的开销比简单的轮询使用更多的 CPU 周期。例如,轮询可以在 IP 路由器中使用,以最大化可用于数据包处理的 CPU 带宽。
有许多设计约束可以推动决策。我的应用程序结合了中断和轮询:
- 外部和内部时钟源触发中断 - 准确地时间戳至关重要,以便我们可以同步它们。
- 传入的串行消息触发中断。接收 FIFO 必须在溢出之前得到服务。
- 当 FIFO 部分为空时,传出消息会触发中断 - 它必须在下溢之前重新填充。
- ISR 设置的在后台轮询的信号量。这有两个优点:
- 处理传入事件所需的计算可能很长;如果将其留在 ISR 中,则可能会延迟其他 ISR 超出其服务期限。
- 事件可以排序。例如,轮询循环可以确保计算 X 始终发生在 ADC 数据收集和传入消息解析之间,即使有时消息到达的时间比预期的稍早。
您不希望主机在繁忙的循环中等待很长时间,而且当对不经常存在的数据进行频繁检查时,轮询也会变得低效。因此,如果主机和设备都很快,那么轮询是否很快。
Interrupt based design
与基于轮询的方法相比,它要好得多,polling based
因为它期望在每次轮询时返回数据。现在,您可能会说我会解决这种情况,即一次轮询返回了一个错误,但是为什么在它也可以返回错误的情况下浪费所有的 CPU 周期来轮询某些东西呢?并且期望投票可能会失败是实际的产品场景。
Interrupt based designs
当单个民意调查涉及很多层功能时,它就更有意义了。对我来说,这是一种常见的做法:你会每天一遍又一遍地询问(投票interrupt
)你的朋友是否有你需要的信息,或者你会在你有我需要的信息时告诉他。我认为我们在日常生活中做了正确的事情,但没有意识到。
但是interrupt based architectures
在实施时需要对publish-subscribe design principle
. 而且,当在应用程序域中完成时,它们要求发送中断的部分代码写得非常好。这很好,因为它也将复杂性压缩到一个地方。
除上述之外,以下是基于轮询的架构免费为您提供的其他优势:
- 异步的
- 非常适合不经常发生的事件/更新
- 仅在有可用数据的情况下更新
- 更好的错误处理和管理
- 更好地利用 CPU 周期
- 更好的电池寿命管理
- 让听众从底层的复杂性中解脱出来
每当您进行设计sw
并且有此选择时,您应该始终选择interrupt
基于设计而不是polling
基于,因为interrupt
基于设计可以使用侦听器填充polling
基于的情况,但基于轮询的设计永远无法满足需要interrupt
基于设计的要求。
以下是一个简短的比较矩阵:
-INTERRUPT- -LOOP-
Speed fast slow
Eficiency good poor
CPU waste low high
multitasking yes no
complexity high low
debugging +/- easy easy
critical in time excellent poor
code bloat low impact high impact
看,我们有主要的 5 种方法:
1) 盲人
CPU 每 x ms 检查一次数据。ETC 检查针 12。
2)轮询(忙/等待)
CPU 一直在检查并等待 Flag 的产生,就像 UART 在数据包传输后产生一个标志一样。永远检查标志寄存器。(最佳响应时间)但 CPU 无法执行任何其他操作。
3) 中断:
CPU 正常运行,如果发生中断,CPU 会将上下文切换到 ISR。如果引脚 18 出现下降沿,则执行 ISR (1)。不错的响应时间和 CPU 在 ISR 不活动时可以做任何事情。使用您不知道何时发生的紧急应用程序来执行此操作。
4)定期轮询:
CPU 正在做它的事情,但是每毫秒检查一次它的引脚 11。Blind 在两者之间什么都不做。更糟糕的响应时间,而不是紧急应用程序,当您不信任硬件时会引发中断。它可以使用定时器中断来创建。
5) 直接内存访问。
高级接口方法。直接从/向内存传输数据。输入将直接读入内存。输出将直接从内存中写入。两者都使用控制器。