在嵌入式系统中,中断服务例程(ISR)通常用于管理异步事件。例如,当引脚上的状态发生变化时,可以触发中断,而不是浪费CPU周期来轮询输入以查看按钮是否被按下。
中断可以限制CPU周期的浪费数量,从而显著提高系统性能。另一个最受欢迎的例子是使用中断进行USART传输。如果你要传输字符“Hello World”,有两种选择:等待所有字符传输完毕,或者在最后一个字符发送成功后使用中断将每个字符填充到传输缓冲区中。
开发人员通常都会关注自己正在使用的中断。但是,那些工具链自动为您填充的中断又是怎样的呢?
在与开发人员的讨论中,我经常发现他们对软件系统的关注范围很窄。例如,我最近在与一位开发人员交谈时,他告诉我,他们知道自己的系统正在做的一切,因为所有代码都是他们自己设计和编写的。乍一看,这种假设似乎有道理,但实际当今的系统非常复杂。
您怎么知道您的代码有没有以意外的方式与系统中的硬件或其他第三方软件进行交互?
仅仅因为您编写了代码?这并不意味着不会发生意想不到的事情。芯片或正在交互的库中可能存在bug。系统可能看起来像预期的那样工作,但有一个内存被覆盖了,却没有产生任何可观察到的行为。如果意外启用中断怎么办?或者发生了位翻转呢?
我们今天使用的系统比20多年前使用的8位组件复杂得多。您必须通过测量和观察来验证您的假设。除非对其进行测量和审查,否则您无法证明其是否有效。
纵观大多数现代嵌入式软件系统,它们都有由芯片供应商提供的启动代码。启动代码将为您提供几乎所有可用中断的默认中断。
例如,下面的代码片段来自意法半导体的STM32CubeIDE工具,用于STM32F091组件:
你可以看到,每个中断默认都有一个与之关联的处理程序。这很好,但这些默认处理程序是做什么的呢?
让我们随机选取一个处理程序来研究,如TIM16_IRQHandler。如下所示,如果我们跟踪处理程序的定义,会发现两件事:
首先,处理程序被定义为weak。这就告诉编译器,如果开发人员在代码中的其他位置定义了名为TIM16_IRQHandler的函数,则该函数将替换默认函数。这使得开发人员可以轻松定义自己的ISR,而无需修改系统启动代码。
代码还进一步指出,如果开发者没有提供处理程序,则将使用Default_Handler代替。
当您看到Default_Handler的作用时,应该会大吃一惊!现在,这已经不是ST特有的做法了,而是业界的普遍做法。Default_Handler仅有几行代码,非常优雅:
上面的内容只是一个无限循环!
如果系统中的任何默认中断因任何原因触发,系统就会进入无限循环,进而阻止任何其他应用程序代码运行!这太可怕了!
理论上,这会将系统置于安全状态,但很少将嵌入式系统锁定为安全状态!什么时候锁定运行电机或以某种方式与用户交互的系统是安全的?
芯片供应商提供的默认处理程序只是一个占位符。它们会让系统进入一个无限循环,以吸引你的注意力,让你知道发生了意想不到的事情。它并不是用来作为生产中断处理程序的。
您必须检查并更新默认处理程序,使之具有适合您的应用的行为。
即使您没有处理任何安全关键问题,您也可能会希望默认处理程序返回您的应用代码,并设置一个标志,表明发生了意外中断。然后,您自己的诊断代码就可以决定在这种情况下应该采取什么措施。
请优先检查您的默认中断处理程序,并确保它们不会在生产系统上执行的极少数情况下锁定您的系统。
(原文刊登于EDN姊妹网站Embedded,参考链接:Do you know what your default ISRs are doing?,由Ricardo Xie编译。)