Multithreading 接收信号量必须是原子的。是Pintos';sema_安全了吗?

Multithreading 接收信号量必须是原子的。是Pintos';sema_安全了吗?,multithreading,x86,interrupt,atomicity,pintos,Multithreading,X86,Interrupt,Atomicity,Pintos,这段代码来自Pintos源代码: 接受信号量的事实是sema->value--。如果它能工作,那一定是一个原子操作。 我们怎么知道这实际上是原子操作呢?我知道,现代CPU保证对齐内存操作(字/双字/四字,具体取决于)是原子的。但是,在这里,我不相信它为什么是原子的。TL:DR:如果在UP系统上禁用中断,那么任何东西都是原子的,只要不计算使用DMA观察内存的系统设备 注意intr_disable()/内部设置级别(旧级别)围绕操作 现代CPU保证对齐的内存操作是原子的 对于多线程观察器,这仅适

这段代码来自Pintos源代码:

接受信号量的事实是
sema->value--。如果它能工作,那一定是一个原子操作。

我们怎么知道这实际上是原子操作呢?我知道,现代CPU保证对齐内存操作(字/双字/四字,具体取决于)是原子的。但是,在这里,我不相信它为什么是原子的。

TL:DR:如果在UP系统上禁用中断,那么任何东西都是原子的,只要不计算使用DMA观察内存的系统设备

注意
intr_disable()/
内部设置级别(旧级别)围绕操作


现代CPU保证对齐的内存操作是原子的

对于多线程观察器,这仅适用于单独的加载或存储,而不适用于读-修改-写操作


<>强>对于原子的东西,我们必须考虑我们所关心的潜在观察者< /强>。重要的是,没有任何东西可以观察到操作部分发生。实现这一点最直接的方法是使操作在物理上/电上是瞬时的,并同时影响所有位(例如,并行总线上的负载或存储在时钟周期的边界从未开始到完成,因此它是原子的“免费”直至并行总线的宽度)。这对于读-修改-写操作是不可能的,在读-修改-写操作中,我们所能做的最好的事情就是阻止观察者查看加载和存储之间的内容

我的回答以不同的方式解释了同样的事情,关于原子意味着什么


在单处理器(UP)系统中,唯一的异步观察者是其他系统设备(如DMA)和中断处理程序。如果我们可以排除非CPU观察员写入信号量,那么我们关心的只是中断的原子性

此代码采用简单的方式退出并禁用中断。这是没有必要的(或者至少如果我们是用asm编写的话就没有必要了)

<>,永远不要在指令的中间。机器的体系结构状态包括或不包括内存减量,因为
dec[mem]
运行或未运行。我们实际上不需要
lock dec[mem]
来实现这一点

顺便说一句,这是的用例。我总是想知道为什么他们不在
cmpxchg
中隐含
lock
,原因是UP系统通常不需要
lock
前缀

此规则的例外情况是可以记录部分进度的可中断指令,如
rep movsb
vpgather
/
vpscatter
请看,这些指令不是原子wrt。即使唯一的观察者是同一内核上的其他代码,也会中断。只有一次重复的
rep whatever
,或聚集或分散的一个元素将发生或没有发生

大多数SIMD指令无法记录部分进度,因此,例如,
vmovdqu ymm0,[rdi]
要么完全发生,要么根本不从其运行的核心的PoV开始。(但当然不能保证原子wrt。系统中的其他观察者,如DMA或MMIO,或其他核心。这就是问题所在。)


没有可靠的方法来确保编译器发出
dec[value]
而不是这样的信息:

mov   eax, [value]
                           ;; interrupt here = bad
dec   eax
                           ;; interrupt here = bad
mov   [value], eax
ISO C11/C++11没有提供请求信号处理程序/中断原子性的方法,但没有提供其他线程。它们确实提供了
原子信号围栏
作为编译器屏障(而线程围栏作为其他线程/内核的屏障),但屏障不能创建原子性,只能控制排序wrt。其他行动

C11/C++11
volatile sig_atomic_t
确实有这个想法,但它只为单独的加载/存储提供原子性,而不是RMW

在特定的实现中,
gcc-Wa,-momit lock prefix=yes
将省略所有锁前缀。()如果您的代码不包括需要在MMIO位置上执行原子RMW的设备驱动程序硬件访问,或者使用伪
锁添加
作为更快的
mfence
,则这对于单线程代码或单处理器计算机是安全的


但是,如果线程之间以及线程和信号处理程序之间有一些原子RMW,那么这在需要在SMP机器上运行的多线程程序中是不可用的。

注意,这里的代码设计为在内核中运行,在单核/cpu系统上,而启用/禁用中断之间的代码段不会被抢占。这段代码在pintos内核中很有用,但一般来说并不有用。1992年的生活简单得多,还没有多核CPU。好吧,反正也不是负担得起的。@Gilgamesz:是的,如果你想让RMW相对于SMP机器上的其他线程是原子的,当然你需要
lock
。这(以及原子MMIO总线周期)就是它存在的原因。关于DMA观察员的部分意味着:不要将网卡编程为查看信号量。当然,系统中也有DMA设备,它们只是没有查看用于信号量的RAM。它只是同步CPU上运行的代码,而不是将内核代码与修改共享数据结构的设备同步。然后,您可能需要
lock dec
甚至在UP上。re:最后两条注释:考虑如果中断处理程序在“interrupt here=bad”位置之一运行会发生什么。中断处理程序递减旧值,因为此代码尚未存储递减值。当然,C编译成asm,但它编译成什么asm很重要。@Gilgamesz:
dec[mem]
要么在信号处理程序之前运行,要么不运行。这与UP内核上的中断的情况相同:异步效果只发生在一致性点
mov   eax, [value]
                           ;; interrupt here = bad
dec   eax
                           ;; interrupt here = bad
mov   [value], eax