获取信号量必须是原子的。 Pintos 的 sema_down 安全吗?
Taking a semaphore must be atomic. Is Pintos's sema_down safe?
这段代码来自Pintos源码:
https://www.cs.usfca.edu/~benson/cs326/pintos/pintos/src/threads/synch.c
void
sema_down (struct semaphore *sema)
{
enum intr_level old_level;
ASSERT (sema != NULL);
ASSERT (!intr_context ());
old_level = intr_disable ();
while (sema->value == 0)
{
list_push_back (&sema->waiters, &thread_current ()->elem);
thread_block ();
}
sema->value--;
intr_set_level (old_level);
}
取信号量的事实是sema->value--;
。如果它有效,它必须是一个原子操作。
我们怎么知道它实际上是原子操作呢?我知道现代 CPU 保证对齐内存操作(对于 word/doubleword/quadword- 它取决于)是原子的。但是,在这里,我不相信为什么它是原子的。
TL:DR:如果你在 UP 系统上禁用中断,只要你不计算使用 DMA 观察内存的系统设备,任何事情都是原子的。
注意操作周围的 intr_disable ();
/ intr_set_level (old_level);
。
modern CPU guarantees that aligned memory operation are atomic
对于多线程观察者,这仅适用于单独的加载 或 存储,不适用于读取-修改-写入操作。
对于原子性的东西,我们必须考虑我们关心的潜在观察者。重要的是,没有什么可以 观察到 操作已经部分发生。实现这一点的最直接方法是让操作在物理/电气上是瞬时的,并同时影响所有位(例如,并行总线上的加载或存储在时钟周期的边界从未开始到完成,因此它是原子的“免费”,直到并行总线的宽度)。这对于读-修改-写是不可能的,我们能做的最好的就是阻止观察者在加载和存储之间查看。
我在 上的回答以不同的方式解释了同一件事,关于原子的含义。
在单处理器 (UP) 系统中,唯一的异步观察者是其他系统设备(例如 DMA)和中断处理程序。如果我们可以排除非 CPU 观察者写入我们的信号量,那么我们关心的只是中断的原子性。
此代码采用简单的方法并禁用中断。这不是必需的(或者至少如果我们用 asm 编写就不是必需的)。
,从不在指令中间。机器的架构状态要么包含内存递减,要么不包含,因为 dec [mem]
要么 运行 要么不包含。为此,我们实际上不需要 lock dec [mem]
。
顺便说一句,这是 的用例。我过去常常想知道为什么他们不直接让 lock
隐含在 cmpxchg
中,原因是 UP 系统通常不需要 lock
前缀。
此规则的例外是可以记录部分进度的可中断指令,如 rep movsb
或 vpgather
/ vpscatter
参见 这些不会是原子的。即使唯一的观察者是同一核心上的其他代码也会中断。仅 rep whatever
的单次迭代或聚集或分散的单个元素会发生或不发生。
大多数 SIMD 指令无法记录部分进度,因此例如 vmovdqu ymm0, [rdi]
从其 运行 所在的核心的 PoV 中要么完全发生要么根本不发生。 (但当然不是 gua运行teed 原子 wrt。系统中的其他观察者,如 DMA 或 MMIO,或其他核心。那是 重要的时候。)
没有可靠的方法来确保编译器发出 dec [value]
而不是像这样的东西:
mov eax, [value]
;; interrupt here = bad
dec eax
;; interrupt here = bad
mov [value], eax
ISO C11/C++11 不提供请求与信号处理程序/中断相关的原子性的方法,但不提供其他线程。他们确实提供 atomic_signal_fence
作为编译器屏障(相对于 thread_fence 作为屏障 wrt。其他 threads/cores),但屏障不能创建原子性,只能控制顺序 wrt。其他操作。
C11/C++11 volatile sig_atomic_t
确实有这个想法,但它只为单独的loads/stores提供原子性,而不是RMW。 It's a typedef for int
on x86 Linux. See that question for some quotes from the standard.
在具体实现上,gcc -Wa,-momit-lock-prefix=yes
将省略所有锁前缀。 (GAS 2.28 docs) 如果您的代码不包含需要在 MMIO 位置上执行原子 RMW 或使用虚拟机的设备驱动程序硬件访问,这对于单线程代码或单处理器机器是安全的lock add
更快 mfence
.
但这在需要在 SMP 机器上 运行 的多线程程序中不可用,如果线程之间以及线程和信号处理程序之间有一些原子 RMW。
这段代码来自Pintos源码: https://www.cs.usfca.edu/~benson/cs326/pintos/pintos/src/threads/synch.c
void
sema_down (struct semaphore *sema)
{
enum intr_level old_level;
ASSERT (sema != NULL);
ASSERT (!intr_context ());
old_level = intr_disable ();
while (sema->value == 0)
{
list_push_back (&sema->waiters, &thread_current ()->elem);
thread_block ();
}
sema->value--;
intr_set_level (old_level);
}
取信号量的事实是sema->value--;
。如果它有效,它必须是一个原子操作。
我们怎么知道它实际上是原子操作呢?我知道现代 CPU 保证对齐内存操作(对于 word/doubleword/quadword- 它取决于)是原子的。但是,在这里,我不相信为什么它是原子的。
TL:DR:如果你在 UP 系统上禁用中断,只要你不计算使用 DMA 观察内存的系统设备,任何事情都是原子的。
注意操作周围的 intr_disable ();
/ intr_set_level (old_level);
。
modern CPU guarantees that aligned memory operation are atomic
对于多线程观察者,这仅适用于单独的加载 或 存储,不适用于读取-修改-写入操作。
对于原子性的东西,我们必须考虑我们关心的潜在观察者。重要的是,没有什么可以 观察到 操作已经部分发生。实现这一点的最直接方法是让操作在物理/电气上是瞬时的,并同时影响所有位(例如,并行总线上的加载或存储在时钟周期的边界从未开始到完成,因此它是原子的“免费”,直到并行总线的宽度)。这对于读-修改-写是不可能的,我们能做的最好的就是阻止观察者在加载和存储之间查看。
我在
在单处理器 (UP) 系统中,唯一的异步观察者是其他系统设备(例如 DMA)和中断处理程序。如果我们可以排除非 CPU 观察者写入我们的信号量,那么我们关心的只是中断的原子性。
此代码采用简单的方法并禁用中断。这不是必需的(或者至少如果我们用 asm 编写就不是必需的)。
dec [mem]
要么 运行 要么不包含。为此,我们实际上不需要 lock dec [mem]
。
顺便说一句,这是 lock
隐含在 cmpxchg
中,原因是 UP 系统通常不需要 lock
前缀。
此规则的例外是可以记录部分进度的可中断指令,如 rep movsb
或 vpgather
/ vpscatter
参见 rep whatever
的单次迭代或聚集或分散的单个元素会发生或不发生。
大多数 SIMD 指令无法记录部分进度,因此例如 vmovdqu ymm0, [rdi]
从其 运行 所在的核心的 PoV 中要么完全发生要么根本不发生。 (但当然不是 gua运行teed 原子 wrt。系统中的其他观察者,如 DMA 或 MMIO,或其他核心。那是
没有可靠的方法来确保编译器发出 dec [value]
而不是像这样的东西:
mov eax, [value]
;; interrupt here = bad
dec eax
;; interrupt here = bad
mov [value], eax
ISO C11/C++11 不提供请求与信号处理程序/中断相关的原子性的方法,但不提供其他线程。他们确实提供 atomic_signal_fence
作为编译器屏障(相对于 thread_fence 作为屏障 wrt。其他 threads/cores),但屏障不能创建原子性,只能控制顺序 wrt。其他操作。
C11/C++11 volatile sig_atomic_t
确实有这个想法,但它只为单独的loads/stores提供原子性,而不是RMW。 It's a typedef for int
on x86 Linux. See that question for some quotes from the standard.
在具体实现上,gcc -Wa,-momit-lock-prefix=yes
将省略所有锁前缀。 (GAS 2.28 docs) 如果您的代码不包含需要在 MMIO 位置上执行原子 RMW 或使用虚拟机的设备驱动程序硬件访问,这对于单线程代码或单处理器机器是安全的lock add
更快 mfence
.
但这在需要在 SMP 机器上 运行 的多线程程序中不可用,如果线程之间以及线程和信号处理程序之间有一些原子 RMW。