正在运行时中断汇编指令

snr*_*snr 4 assembly interrupt cpu-architecture atomicity interrupted-exception

当中断到达 CPU 时,如果它被确认,则通过在跳转到处理程序之前保存当前地址位置来处理它。否则将被忽略。

我想知道汇编指令调用是否被中断。

例如,

mvi a, 03h ; put 3 value into acc. in 8080 assembly
Run Code Online (Sandbox Code Playgroud)

一行指令可以中断吗?或者如果不是,它是原子的?

是否总能保证“一行汇编指令”总是原子的?

如果8080汇编中没有“lock”关键字呢,那么原子性是如何提供的呢?

例如,如果要操作64位和,但是“一行​​指令”没有办法做到,并且在对和进行操作时出现中断怎么办?如何在装配级别防止它?

对我来说,这个概念正在开始归结。

Pet*_*des 5

是的,包括 8080 和 x86 在内的所有“正常”ISA 都保证指令对于同一内核上的中断是原子的。 要么一条指令已完全执行并且其所有架构效果都是可见的(在中断处理程序中),要么它们都不可见。任何与此规则的偏差通常都会被仔细记录。


例如,英特尔的 x86 手册第 3 卷(约 1000 页 PDF)确实特别指出:

6.6 程序或任务重启
为了允许在处理异常或中断后重启程序或任务,所有异常(中止除外)都保证报告指令边界上的异常。所有中断都保证在指令边界上发生。

老款的Intel的第一卷手册有关使用单核系统的谈判cmpxchg 没有一个lock前缀读-修改-写入原子(相对于其他软件,而不是硬件DMA访问)。

CMPXCHG 指令通常用于测试和修改信号量。它检查信号量是否空闲。如果信号量空闲,则标记为已分配;否则它获取当前所有者的 ID。这一切都是在一个不间断的操作中完成的[因为它是一条指令]。在单处理器系统中,CMPXCHG 指令无需在执行多条指令以测试和修改信号量之前切换到保护级别 0(以禁用中断)。

对于多处理器系统,CMPXCHG 可以与 LOCK 前缀组合以原子地执行比较和交换操作。(有关原子操作的更多信息,请参阅英特尔® 64 和 IA-32 架构软件开发人员手册第 3A 卷第 8 章“多处理器管理”中的“锁定原子操作”。)

(有关lock前缀及其实现方式与非锁定方式的更多信息add [mem], 1,请参阅Can num++ be atomic for 'int num'?

正如英特尔在第一段中指出的那样,实现多指令原子性的一种方法是禁用中断,然后在完成后重新启用。 这比使用互斥锁来保护更大的整数要好,尤其是当您谈论在主程序和中断处理程序之间共享的数据时。如果在主程序持有锁时发生中断,它不能等待锁被释放;那永远不会发生。

在简单的有序流水线上,或者尤其是在微控制器上,禁用中断通常非常便宜。(有时您需要保存先前的中断状态,而不是无条件地启用中断。例如,可能会在已禁用中断的情况下调用一个函数。)

无论如何,禁用中断是您可以在 8080 上使用 64 位整数自动执行某些操作的方法。


根据为该指令记录的规则,一些长时间运行的指令是可中断的。

例如,x86 的rep-string 指令,例如rep movsb(任意大小的单指令 memcpy)在架构上等同于重复基本指令 ( movsb) RCX 次,每次递减 RCX 并递增或递减指针输入(RSI 和 RDI)。在复制期间到达的中断可以设置 RCXstarting_value - byte_copied并且(如果 RCX 是非零)让 RIP 指向指令,因此在中断后恢复rep movsb将再次运行并完成复制的其余部分。

其他 x86 示例包括 SIMD 收集加载 (AVX2/AVX512) 和分散存储 (AVX512)。例如vpgatherdd ymm0, [rdi + ymm1*4], ymm2,根据ymm2设置的元素进行多达 8 次 32 位加载。并将结果合并到 ymm0 中。

在正常情况下(在收集过程中没有中断、没有页面错误或其他同步异常),您会在目标寄存器中获取数据,并且掩码寄存器最终归零。因此,掩码寄存器为 CPU 提供了存储进度的地方。

收集和分散很慢,并且可能需要触发多个页面错误,因此对于同步异常,即使在处理页面错误会取消所有其他页面的病态条件下,这也能保证向前进展。但更相关的是,这意味着避免在中间元素页面错误时重做 TLB 未命中,并且在异步中断到达时不丢弃工作。


其他一些长时间运行的指令(比如在所有内核上wbinvd刷新所有数据缓存)在架构上是不可中断的,甚至在架构上是不可中止的(丢弃部分工作并处理中断)。它具有特权,因此用户空间无法将其作为拒绝服务攻击执行,从而导致高中断延迟。


记录有趣行为的相关示例是 x86popad超出堆栈顶部(段限制)。这是针对异常(不是外部中断),在 vol.3 手册前面的第 6.5 节异常分类中进行了记录(即故障/陷阱/中止,有关更多详细信息,请参阅 PDF。)

注:
一个通常报告为故障的异常子集是不可重启的。此类异常会导致某些处理器状态丢失。例如,执行堆栈帧越过堆栈段末尾的POPAD 指令会导致报告错误。在这种情况下,异常处理程序看到指令指针 (CS:EIP) 已恢复,就好像尚未执行 POPAD 指令一样。但是,内部处理器状态(通用寄存器)将被修改。这种情况被认为是编程错误。引起此类异常的应用程序应由操作系统终止。

请注意,这仅在popad其本身导致异常的情况下,而不是出于任何其他原因。外部中断不能popad以它可以为rep movsbvpgatherdd

(我猜出于popad故障的目的,它有效地迭代工作,一次弹出 1 个寄存器并在逻辑上修改 RSP/ESP/SP 以及目标寄存器。而不是检查整个区域,它会在开始之前加载段限制,因为这需要额外添加,我猜。)


无序 CPU 在中断时回滚到退休状态。

像现代 x86 这样具有乱序执行和将复杂指令拆分为多个 uops 的 CPU 仍然确保了这种情况。当中断到达时,CPU 必须在它正在运行的两条指令之间选择一个点作为中断体系结构发生的位置。它必须放弃任何已经完成的解码工作或开始执行任何后续指令。假设中断返回,它们将被重新获取并重新开始执行。

请参阅当中断发生时,流水线中的指令会发生什么?.

正如 Andy Glew 所说,当前的 CPU 不会重命名特权级别,因此逻辑上发生的事情(在较早的指令完成后执行中断/异常处理程序)与实际发生的事情相匹配。

不过,有趣的事实是:x86 中断并没有完全序列化,至少在纸面上不能保证。(在 x86 术语中,像cpuidiret这样的指令被定义为序列化;排空 OoO 后端和存储缓冲区,以及其他任何可能重要的东西。这是一个非常强大的障碍,而许多其他事情不是,例如mfence。)

实际上(因为 CPU 在实践中不会重命名权限级别),当中断处理程序运行时,乱序后端中不会有任何旧的用户空间指令/uop 仍在运行中。

异步(外部)中断也可能会耗尽存储缓冲区,具体取决于我们如何解释英特尔 SDM vol.3 11.10的措辞:*在以下情况下,存储缓冲区的内容总是被排空到内存中:“...”当生成异常或中断”。显然,这适用于异常(CPU 内核本身生成中断),也可能意味着在服务中断之前。

(从退休的存储指令中存储数据不是推测性的;它肯定会发生,并且 CPU 已经放弃了它需要能够回滚到该存储指令之前的状态。因此,一个充满分散缓存未命中的大型存储缓冲区存储可能会损害中断延迟。要么在任何中断处理程序指令可以运行之前等待它耗尽,要么至少在ISR 中的任何in/outlocked 指令发生之前,如果事实证明存储缓冲区没有被耗尽.)

相关: Sandpile ( https://www.sandpile.org/x86/coherent.htm ) 有一个正在序列化的事物表。中断和异常不是。但同样,这并不意味着他们不会耗尽存储缓冲区。这可以通过实验进行测试:在用户空间中的存储和 ISR 中的负载(不同共享变量的)之间查找 StoreLoad 重新排序,如另一个内核所观察到的。

本节的一部分并不真正属于这个答案,应该移到其他地方。 之所以在这里是因为在评论中讨论了当线程被安排在不同的 CPU 内核上时预期的内存语义(例如写后读)会发生什么?将此作为可能错误的断言的来源,即中断不会耗尽存储缓冲区,这是我在误解“不序列化”之后写的。

  • Intel手册V2提到“INT”指令基本上与“LFENCE”具有相同的序列化属性。AMD 手册并没有这么说(AFAICT)。此外,Intel 和 AMD 手册都提到“异常和中断”会耗尽存储缓冲区和 WC 缓冲区。这表明本文中的术语“中断”指的是硬件中断,术语“异常”指的是程序错误异常和机器检查异常(参见第 3 卷第 6.4 节)。在我看来,“异常和中断”正在完全序列化。 (2认同)
  • @HadiBrais:那篇论文是转移注意力;他们只讨论序列化 OoO exec,而不是内存。我在看第 3.2 节,他们谈论 CPU 不重命名 CS,因此“syscall”正在序列化。言外之意,中断也是如此(至少当从用户空间获取时),尽管他们甚至没有提到这一点。我将从这个答案中删除该部分;再看一眼,发现关系太疏远了。(顺便说一句,我将链接更新为格式更好的版本。ftp://ftp.cs.wisc.edu/sohi/papers/2008/hpca2008-serial.pdf。) (2认同)

Ale*_*nze 4

我不确定 8080 是否设计用于具有共享 RAM 的多 CPU 系统,但这并不一定意味着此类系统不可能或不存在。8086 锁前缀用于此类系统,以确保在执行一系列内存读取、值修改、内存写入 (RMW) 时,只有一个 CPU 可以独占访问内存。锁前缀并不是用来保护一条指令或几条指令不被中断处理程序抢占。

您可以确保各个指令不会在飞行途中被打断。要么让它们运行直到完成,要么恢复它们的任何副作用并在稍后重新启动。这是大多数 CPU 上的常见实现。如果没有它,就很难在存在中断的情况下编写表现良好的代码。

事实上,您无法使用单个 8080 指令执行 64 位加法,因此该操作可以被 ISR 抢占。

如果您根本不想抢占,您可以使用中断禁用和启用指令(DI 和 EI)来保护您的 64 位添加。

如果您想让 ISR 抢占 64 位但又不干扰 64 位加法使用的寄存器,则 ISR 必须通过使用 PUSH 和 POP 指令等来保存和恢复这些寄存器。

查找 8080 手册以获取中断处理的详细说明(例如,此处)。