为什么在配置良好的系统中中断应该很短?

电器工程 stm32 中断
2022-01-14 07:47:24

为什么中断应该很短?

我目前正在使用 STM32,它具有优先中断的功能。

因此,我认为这两个选项之间没有任何区别:

  • 主循环和中断
  • 一个优先级非常低的大中断和其他中断

为什么我们要保持简短?是因为缓存吗?小栈?或者是其他东西?为什么我们不能在中断时创建整个系统并使主循环休眠?

有任何想法吗?

4个回答

不仅没有理由你不能这样做。事件驱动系统并不少见。但。在许多体系结构中,如果您处于一个中断中,则不能有另一个中断,因此在某些系统中,即使是低优先级的中断也可以阻止更高优先级的中断发生,因此一般规则是快速进出。通常一种设计方法是中断进来并进行一些处理,但留下一个任务让内核或前台任务完成,在操作系统中更常见。

我相信 Cortex-M 可以嵌套中断,因此如果一个中断位于另一个之上,它就会得到处理。大多数芯片与 ARM 无关,因此除了 ARM 中的任何内容之外,芯片供应商没有任何理由不能将中断处理程序放在 Cortex-M 和/或优先级处理程序之前事情的结束。

然而,需要设计一个中断驱动的设计。与任何其他设计一样,您需要进行系统工程。您需要知道每个可能的中断(在正常情况下,未定义的指令等是致命的)以及它们发生的频率。许多将是定期。

您还需要知道每个中断需要多长时间,并安排此时间以便您可以确保所有中断都将在满足您的规范的时间内得到处理。例如,一个基于定时器的中断事件需要 50 毫秒才能正常处理,但有一个非周期性随机中断需要 20 毫秒。70 ms 是否可以容忍其中任何一个?芯片/核心有优先解决方案吗?如果没有,您是否已经制定了中断优先级,以便您可以满足所有事情的时间安排?

归根结底,所有中断与某些中断和某些前景在中断的设计和时序方面几乎没有区别;你有同样的问题。

所以一般规则是让中断变得精简、平均和快速,但现实是做你的系统工程,并知道你有多少时间来处理每个或一组它们。它必须花费少于 x 的时间。

您的 ISR 越复杂,重入是/成为一个主要问题。维基百科条目对重入 ISR 有一个非常简洁的描述:

可重入中断处理程序是在中断处理程序早期重新启用中断的中断处理程序。这可能会减少中断延迟。 [6] 一般来说,在编写中断服务程序时,建议尽快在中断处理程序中重新使能中断。这种做法有助于避免丢失中断。 [7]

假设您不是在编写一个通用操作系统,其中重入很可能是不可避免的,您必须记住,在您的定制控制器代码中重入所增加的复杂性可能不值得被认为易于编写懒惰的长时间运行的 ISR。

这是一个例子:

  • 长时间运行,低优先级的 ISR 开始做某事,调用malloc您已实现的版本。
  • 通话中malloc,您会被更高优先级的线程打断:它malloc也会调用
  • 场景 a:高优先级 ISR 在低优先级之前退出 malloc
  • 场景 b:高优先级 ISR 在低优先级后退出 malloc *

好吧,你会说,我会在 malloc 上加一个自旋锁,那么你需要做的就是对spinlock_aquire你创建的原语重复上述条件:是可重入的吗?

我应该在这里指出,只是对事物进行锁定并称其为一天是基于优先级反转的死锁的秘诀。没那么简单。

穷人对所有这些问题的解决方案是保持你的 ISR 简短。例如,在很久以前(还没有更新)的 NT 内核中,任何高于底部两个的 IRQL 甚至都不允许查看分页内存。这是因为分页是由中断处理的......

所以选择变成了:实现一个基本的排队机制,让你的 ISR调度工作单元,或者让你的 ISR 自由运行,但确保你有一个不会因奇怪的问题(例如优先级反转)而窒息的非常健壮的环境。

如果您手头的任务非常简单,例如在您的 arduino 控制的无人机中打开灯,那么请务必继续。但是,如果您想避免以后随着代码变得越来越复杂而出现工程难题,那么您确实应该避免使用 ISR 完全不给自己任何约束带来的好处。


* 澄清:场景 b 不能按面值发生,因为较高优先级的 ISR 将始终在较低优先级的 ISR 之前执行和完成。但是,可以交换任何一个所采用的代码路径。因此,在malloc例程本身中,如果可以访问全局数据结构,则可以以任何可能的方式组合中断该代码。

更进一步,应该指出,任何函数的重入要求都是针对其整个调用树。如果您最终使用第三方库,这将变得非常难以确保。

另请注意:为了解决来自@user253751 的评论,仅通过将东西包裹在锁中并不能解决重入问题。相反,重入有一套很好理解的要求。这里有一些相对简单的代码示例来说明问题。

可以看出,通过查看这个,编写一个可重入的malloc或者资源获取函数变得非常困难。

对于通用计算机,保持中断处理程序简短允许正常处理具有合理的确定性,这可能会或可能不会成为问题,具体取决于应用程序。

在硬实时嵌入式过程中(确定性至关重要),这很有意义。

我实现了一个非常精确的倾斜传感器(2 轴),其中传感器驱动由定时器控制,主循环处于睡眠模式,直到计数器触发中断以更新正在驱动的轴;我使用的外部ADC也由(不同的)定时器控制,以最大限度地减少平均样本的采集抖动(这可能会对采样数据系统造成严重破坏)。

为了保持在非常窄的时间窗口内进行某些计算的能力,中断处理程序需要快速(接受中断,执行所需的最少操作并退出)。

在其他应用程序中,所有有趣的事情都可能发生在中断处理程序中(就像我参与一个具有多达 2000 个同时可用流的视频点播系统时的情况一样)。

中断处理程序没有“一刀切”的答案,并且取决于应用程序,尽管如前所述,嵌套中断可能会变得混乱,特别是如果两个处理程序需要共享一个资源(如果处理不当,可能会导致数据损坏)多任务系统也可能导致优先级倒置)。

中断处理程序采用何种方法取决于应用程序,但“使它们保持简单和快速”的第一种方法始终是一个很好的起点。

我在以前的项目中处理过很多此类事情,这里有一些我处理过的事情(通常是通过艰难的方式学习的)。一些细节可能会根据您的芯片架构以及您与裸机运行的接近程度而有所不同,但总体思路仍然适用。

  • 在您清除/重新启用中断之前的时间段内,可能会触发其他一些不相关的中断。当关键优先级中断挂起时,您可能在为低优先级中断提供服务时做错事。一些中断处理程序实际上可以比非中断线程具有更低的优先级,如果你在处理程序内完成所有工作,这是你无法实现的关系。
    • 如果您在多个源之间共享中断,这将成为更大的问题。如果您尚未完成第一个中断的处理,则可以完全屏蔽第二个中断。一些硬件架构在这方面比其他架构更糟糕。
  • 当中断来自外部设备时,这些设备可能会期望您需要多长时间才能响应它们的中断。我有复杂的 ISR,运行时间太长,外部设备认为我已经死了,不再和我说话。
  • 在某些架构上,ISR 运行在当前执行程序正在使用的任何堆栈之上(以避免为单独的堆栈分配内存,这可能会阻塞、失败或触发中断)。你不知道你将有多少可用的堆栈空间,所以除了清除中断状态和发出其他信号来完成工作之外,你真的无法做任何事情。
  • 一些架构以比普通线程更高的特权级别运行 ISR。他们可以访问通常受保护的低级硬件资源。这意味着在 ISR 中进行大量工作可能存在安全隐患。
  • ISR 在您的系统中具有最高优先级。调用阻塞或内部依赖于中断的函数可能会导致死锁。
  • 一些架构在内部使用中断,例如触发内核之间的缓存刷新,或通知 ALU 数学协处理器已完成计算。当您仍在 ISR 中时,这些中断的服务可能会被阻止,因此在这里花费太长时间可能会对性能产生不明显的影响。
    • 中断的另一个内部用途可能是与调试引擎(JTAG 等)通信。我曾使用过调试探针在 ISR 中不是很有效但在其他情况下效果很好的芯片。调试 ISR 代码比调试普通代码要困难一个数量级。
  • 一些多核 CPU 将始终为特定内核上的中断提供服务。在 ISR 中完成所有工作可能会强制您的代码有效地单线程运行。

值得庆幸的是,所有这些问题都有相同的简单解决方案。您的中断处理程序应存储有关中断的必要信息,清除并重新启用中断,然后向其他代码发出信号以执行实际工作。这在大多数平台上都很容易做到,试图将所有工作都塞进 ISR 本身并没有太大的好处。