在同一个 hart 上的两个软线程中大约 LR/SC

About LR/SC in two soft threads on same hart

如果我在同一个 hart 上有两个软线程,线程 #1 首先执行 LR 指令,然后 #2 执行具有相同地址的 LR 指令,最后 #1 SC 到该地址。这个SC会成功吗?如果成功,将配对哪个 LR(#1 或 #2)?

aq/rl 位未设置,LR/SC 的所有地址都相同。

同一个 hart 上的两个线程 运行 意味着必须进行上下文切换。

正在阅读 RISC-V Unprivileged ISA V20191213,我在第 50 页找到了这条推荐:

A store-conditional instruction to a scratch word of memory should be used to forcibly invalidate any existing load reservation:

  • during a preemptive context switch, and
  • if necessary when changing virtual to physical address mappings, such as when migrating pages that might contain an active reservation.

来自 the commit that introduced this text 的提交消息详述:

Commit 170f3c5 clarified that reservations can be cleared with an SC to a dummy memory location. As discussed <170f3c5#commitcomment-29386537>, this patch makes it clear that the reservation "should" be cleared in this way during a preemptive context switch.

Anyone writing preemptive context switch code should be forcibly clearing load reservations. Although other mechanisms might be used, this is the standard way of doing it (hence "should" rather than "must").

Linux 内核在 entry.S 中执行此操作:

/*
 * The current load reservation is effectively part of the processor's
 * state, in the sense that load reservations cannot be shared between
 * different hart contexts.  We can't actually save and restore a load
 * reservation, so instead here we clear any existing reservation --
 * it's always legal for implementations to clear load reservations at
 * any point (as long as the forward progress guarantee is kept, but
 * we'll ignore that here).
 *
 * Dangling load reservations can be the result of taking a trap in the
 * middle of an LR/SC sequence, but can also be the result of a taken
 * forward branch around an SC -- which is how we implement CAS.  As a
 * result we need to clear reservations between the last CAS and the
 * jump back to the new context.  While it is unlikely the store
 * completes, implementations are allowed to expand reservations to be
 * arbitrarily large.
 */
REG_L  a2, PT_EPC(sp)
REG_SC x0, a2, PT_EPC(sp)

此代码定期加载一些内存,然后使用 SC 将相同的值写回相同的位置。 SC清除任何保留,成功与否无所谓

无论如何,在抢占式多任务系统中,程序可以期望操作系统内核在上下文切换期间使任何保留无效。在这样的系统中,如果您在 LR/SC 序列的中间切换,您的 SC 指令将始终失败。

在协作式多任务系统中(您的程序仅在选择让步时才关闭),您的 SC 指令可能会成功——但前提是您在 LR/SC 的中间让步顺序。 并且另一个线程做同样的事情。

我们会有这样的代码序列:

Thread A    Thread B
----------------------
LR
yield
            LR
            yield
SC
yield
            SC

据我了解规范,A 的 SC 会成功,而 B 的 SC 会失败。 SC 指令将始终与最近的 LR 指令配对,因此来自线程 A 的成功 SC 将与来自线程 B 的 LR 配对。

但是这段代码是无稽之谈。规格甚至 used to contain recommendations against it:

Cooperative user-level context switches might not cause a load reservation to be yielded, so user-level threads should generally avoid voluntary context switches in the middle of an LR/SC sequence.