中断与子程序调用有何不同?

问题描述

子例程由程序指令调用以执行调用程序所需的功能。 而中断是由输入操作或硬件错误等事件发起的。 但是处理器如何区分它们?

解决方法

为了补充其他好的答案,我将更多地讨论函数调用与中断在逻辑上的相似性,以及它们之间的区别。

从逻辑的角度来看,子例程或函数调用会挂起当前正在执行的调用者并将控制权转移到 sub/func。当该 sub/func 完成时,它会将控制权返回给调用者,并在调用后恢复。

这是从逻辑的角度来看——然而,就硬件而言,没有暂停或恢复或从调用者到被调用者的控制转移——只有一个连续执行的机器代码指令流,恰好包括各种各种分支指令,例如 calljal 例如在 MIPS 上)和 ret (jr)

从调用者到被调用者的控制权转移发生在非常可控的情况下——其中很多是通过软件协议、ABI 和 ABI 中指定的调用约定实现的。调用者和被调用者,根据软件约定,事先约定如何传递参数、返回值传回、被调用者可以免费使用哪些寄存器与哪些寄存器如果使用,则必须在返回时保留/恢复。从调用者到被调用者的控制转移发生在调用点和返回点,它们受程序控制——它们作为执行程序指令的结果同步发生。


当中断发生时,它可能发生在任何两条指令之间——因此,对于一个,没有“调用约定”来管理从用户代码(被中断的内容)到异常处理程序的传输。对于外部中断,异常处理程序并不完全理解被中断的代码(被中断的代码没有尝试对操作系统进行同步调用)。在这种情况下,所有处理器状态(例如寄存器)必须在中断代码中假定为忙/正在使用,基本上已通过将控制权转移到异常处理程序而强制挂起。

外部中断不会向被中断的代码返回值——它们会改变操作系统状态(设备状态),这可能会使其他一些进程从阻塞变为可运行。

因为任何两条指令之间都可能发生外部中断(不像调用可以看到暂停/恢复点的软件),为了使线程可恢复,(与函数调用相比)需要保留更多的处理器状态以防被中断的线程稍后读取该状态。

即使现代硬件在任何时间点都有许多指令在执行,但它必须保留中断发生在两个(动态)相邻指令之间的错觉。部分原因是它可以指示被中断程序中的程序计数器/指令指针,以及在哪里恢复它。

由于外部中断,操作系统的状态会发生变化。作为这些更改的结果,操作系统在从中断返回时可能会选择恢复与被中断线程不同的线程(甚至在另一个进程中),使被中断线程处于挂起状态以供以后恢复。以这种方式,可能会发生线程的调度和交错,这与函数调用和函数返回调用者有本质的区别(逻辑上)。 (一些用户级系​​统具有协程和纤程,它们表现出除了从调用者到被调用者再返回之外的控制转移。)

总而言之,函数调用是在程序的控制下完成的,仅在受控点进行,并通过软件调用约定和某些用户模式调用和返回指令进行。然而,需要明确的是,调用和返回可以在没有这些专用调用和返回指令的情况下完成(它们可以通过替代指令序列轻松模拟)。而外部中断由向处理器发送信号的外部设备触发,可能发生在被中断代码中的任何两条指令之间,部分原因是它们涉及特权更改,需要特权指令才能从中断和暂停中恢复,这将是困难的或无法用其他常规指令进行模拟。

,

显然,运行 call 指令会调用与中断处理完全不同的行为。 (例如 x86 https://wiki.osdev.org/InterruptsWhen an interrupt occurs,what happens to instructions in the pipeline?)。 CPU 切换到内核(管理程序)模式以处理具有不同权限级别的 CPU 上的中断。

在那之后,CPU 不再关心它是如何进入当前状态的。 CPU 只是执行指令;由(操作系统或子程序的)程序员来放置有用的指令,例如在以 ret(正常返回)或 iret(中断返回)结尾的 x86 上。

ret 不是“特殊的”,在 x86 上它只是从堆栈中弹出一个返回地址到程序计数器中。你可以用其他(通常较慢)的方式做同样的事情。 iret also has well-defined behaviour,不是魔法,也许可以用 popf 和远 ref [rsp] 或其他指令的正确组合来模拟,或者可能不依赖于 GDT 条目。尽管如此,它仍然不是魔术,只是对架构状态进行特定更改的指令(例如切换回用户模式,退出主管模式)

其他 ISA 的情况类似;不同的指令名称,以及 CPU 为内核保存足够状态以便在中断后能够返回用户空间的不同方式。

,

这因 CPU 而异:

在某些 CPU 上没有区别(除了中断是由某些硬件事件启动的):

call 指令和中断都将下一条指令的地址压入堆栈并进入函数或中断处理程序。 ret 指令用于从函数或中断返回。

如果我没记错的话,1970 年代中期的 i8080 就是这种 CPU 的一个例子。

在其他 CPU 上,两者之间存在巨大差异:

在函数调用中,进入中断时只存储下一条指令的地址,而存储所有CPU寄存器。

因此,从函数(x86 上的ret)和中断(x86 上的iret)返回有不同的指令。这是必要的,因为离开中断与离开函数的工作方式不同。

MC6800(也是 1970 年中期的芯片)就是这种 CPU 的一个例子。

大多数 CPU(包括 x86)介于这两种变体之间。

甚至有些 CPU 需要从不同类型的中断返回不同的指令(一些 PowerPC 变体有四种不同的指令用于从中断返回)!

还有 Cortex-M,进入中断和进入函数的工作方式完全不同,但可以使用相同的指令离开中断和函数(尽管离开函数的工作方式与离开中断的工作方式不同)...