在装配指令运行时中断装配指令

问题描述 投票:4回答:2

当一个中断进入CPU时,如果它被确认,则在跳入处理程序之前保存当前地址位置来处理它。否则会被忽略。

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

例如,

mvi a, 03h ; put 3 value into acc. in 8080 assembly

可以将一行指令中断吗?或者如果没有,它是原子?

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

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

例如,如果希望操作64位求和,但是没有办法用“一行指令”进行操作,并且在求和时操作中断。如何在装配水平上防止?

这个概念正在开始为我做好准备。

assembly interrupt cpu-architecture atomicity interrupted-exception
2个回答
3
投票

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

您可以确定单个指令在飞行途中不会以某种方式中断。要么让它们运行直到完成,要么它们的任何副作用都会被恢复,并且它们会在以后重新启动。这是大多数CPU的常见实现。没有它,在存在中断的情况下编写性能良好的代码将很困难。

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

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

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

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


3
投票

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


例如,Intel's x86 manual vol.3 (~1000 page PDF)确实具体说明了这一点:

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

Intel's vol.1 manual中的一个旧段落讨论了使用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)。在复制过程中到达的中断可以设置RCX“starting_value - byte_copied, so on resuming after the interrupt therep movsb”将再次运行并执行其余的复制。

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

在正常情况下(在收集期间没有中断,没有页面错误或其他同步异常),您将获得目标寄存器中的数据,并且屏蔽寄存器最终归零。因此,掩码寄存器为CPU提供存储进度的位置。

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


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


记录有趣行为的相关示例是当x86 popad脱离堆栈顶部(段限制)时。这是针对异常(非外部中断),在vol.3手册的前面部分,6.5节中的异常分类(即故障/陷阱/中止,请参阅PDF以获取更多详细信息)。

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

请注意,这只有在popad本身导致异常时才会出现,而不是出于任何其他原因。外部中断不能像popadrep movsb那样拆分vpgatherdd

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


Out-of-order CPUs roll back to the retirement state on interrupts.

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

When an interrupt occurs, what happens to instructions in the pipeline?

正如Andy Glew所说,当前的CPU不会重命名权限级别,因此逻辑上必须发生的事情(丢弃后来的管道指令)与实际发生的情况相匹配。但有趣的是:x86中断没有完全序列化。这意味着他们不必刷新存储缓冲区以及排除乱序执行ReOrder Buffer。他们确实将OoO后端回滚到退出状态(因为它没有重命名权限级别,所以它不能安全地处理用户和内核uops立即在飞行中),但是中断处理程序可能会在之前开始执行存储缓冲区已将所有待定存储提交到L1d。

(来自退役商店指令的商店数据不是推测性的;它肯定会发生,并且CPU已经放弃了在该存储指令之前需要能够回滚到的状态。所以一个大的存储缓冲区充满了分散的缓存未命中商店可能会伤害中断延迟。)

© www.soinside.com 2019 - 2024. All rights reserved.