事务内存支持

POWER 内核对该特性的支持目前仅限于支持用户程序使用它。内核本身目前不使用它。

此文件旨在总结 Linux 如何支持它以及您可以从用户程序中期望的行为。

基本概述

硬件事务内存在 POWER8 处理器上受支持,它是一种支持不同形式的原子内存访问的特性。提供几个新指令来分隔事务;事务保证要么以原子方式完成,要么回滚并撤消任何部分更改。

一个简单的事务如下所示

begin_move_money:
  tbegin
  beq   abort_handler

  ld    r4, SAVINGS_ACCT(r3)
  ld    r5, CURRENT_ACCT(r3)
  subi  r5, r5, 1
  addi  r4, r4, 1
  std   r4, SAVINGS_ACCT(r3)
  std   r5, CURRENT_ACCT(r3)

  tend

  b     continue

abort_handler:
  ... test for odd failures ...

  /* Retry the transaction if it failed because it conflicted with
   * someone else: */
  b     begin_move_money

“tbegin”指令表示起始点,“tend”表示结束点。在这些点之间,处理器处于“事务”状态;如果系统内没有与其他事务或非事务访问发生冲突,则任何内存引用都将一次性完成。在此示例中,如果其他处理器没有触及 SAVINGS_ACCT(r3) 或 CURRENT_ACCT(r3),则事务完成,就像它是正常的直线代码一样;已执行从当前帐户到储蓄帐户的原子转账。即使使用了普通的 ld/std 指令(注意没有 lwarx/stwcx),要么 *SAVINGS_ACCT(r3) 和 CURRENT_ACCT(r3)* 都将被更新,要么都不会被更新。

如果与此同时,与事务访问的位置发生冲突,CPU 将中止事务。寄存器和内存状态将回滚到“tbegin”时的状态,控制将从“tbegin+4”继续。第二次将执行跳转到 abort_handler 的分支;中止处理程序可以检查失败的原因并重试。

检查点寄存器包括所有 GPR、FPR、VR/VSR、LR、CCR/CR、CTR、FPCSR 和一些其他状态/标志寄存器;有关详细信息,请参见 ISA。

事务中止的原因

  • 与其他处理器使用的缓存行冲突

  • 信号

  • 上下文切换

  • 有关将中止事务的所有内容的完整文档,请参见 ISA。

系统调用

从活动事务中发出的系统调用将不会被执行,并且该事务将被内核以失败代码 TM_CAUSE_SYSCALL | TM_CAUSE_PERSISTENT 判定失败。

从暂停事务中发出的系统调用将像往常一样执行,并且事务不会被内核显式判定失败。但是,内核执行系统调用的方式可能会导致硬件判定事务失败。系统调用以暂停模式执行,因此任何副作用都将是持久的,独立于事务成功或失败。内核不提供关于哪些系统调用将影响事务成功的保证。

如果调用是通过库进行的,则在依赖于在活动事务期间中止的系统调用时必须小心。库可能会缓存值(这可能会给出成功的假象)或执行在进入内核之前导致事务失败的操作(这可能会产生不同的失败代码)。例如 glibc 的 getpid() 和惰性符号解析。

信号

在事务期间传递信号(同步和异步)提供了第二个线程状态 (ucontext/mcontext) 来表示第二个事务寄存器状态。信号传递 “treclaim” 来捕获两种寄存器状态,因此信号会中止事务。传递给信号处理程序的常规 ucontext_t 表示检查点/原始寄存器状态;信号似乎出现在 “tbegin+4”。

如果 sighandler ucontext 设置了 uc_link,则已传递第二个 ucontext。为了将来的兼容性,应检查 MSR.TS 字段以确定事务状态 -- 如果是,则 uc->uc_link 中的第二个 ucontext 表示信号点的活动事务寄存器。

对于 64 位进程,uc->uc_mcontext.regs->msr 是完整的 64 位 MSR,其 TS 字段显示事务模式。

对于 32 位进程,mcontext 的 MSR 寄存器只有 32 位;高 32 位存储在第二个 ucontext 的 MSR 中,即 uc->uc_link->uc_mcontext.regs->msr 中。顶部字包含事务状态 TS。

但是,基本信号处理程序不需要知道事务,只需从处理程序返回即可正确处理事情

事务感知信号处理程序可以从第二个 ucontext 读取事务寄存器状态。例如,崩溃处理程序需要这样做才能确定导致 SIGSEGV 的指令地址。

示例信号处理程序

  void crash_handler(int sig, siginfo_t *si, void *uc)
  {
    ucontext_t *ucp = uc;
    ucontext_t *transactional_ucp = ucp->uc_link;

    if (ucp_link) {
      u64 msr = ucp->uc_mcontext.regs->msr;
      /* May have transactional ucontext! */
#ifndef __powerpc64__
      msr |= ((u64)transactional_ucp->uc_mcontext.regs->msr) << 32;
#endif
      if (MSR_TM_ACTIVE(msr)) {
         /* Yes, we crashed during a transaction.  Oops. */
 fprintf(stderr, "Transaction to be restarted at 0x%llx, but "
                         "crashy instruction was at 0x%llx\n",
                         ucp->uc_mcontext.regs->nip,
                         transactional_ucp->uc_mcontext.regs->nip);
      }
    }

    fix_the_problem(ucp->dar);
  }

当处于活动事务中接收到信号时,我们需要小心堆栈。堆栈有可能在 tbegin 之后向上移动。这里明显的例子是 tbegin 在 tend 之前返回的函数内部被调用。在这种情况下,堆栈是检查点事务内存状态的一部分。如果我们以非事务方式或暂停方式覆盖此内容,我们将遇到麻烦,因为如果我们获得 tm 中止,程序计数器和堆栈指针将返回到 tbegin,但我们的内存堆栈将不再有效。

为了避免这种情况,当在活动事务中接收到信号时,我们需要使用来自检查点状态的堆栈指针,而不是推测状态。这确保了信号上下文(写入 tm 暂停)将被写入回滚所需的堆栈下方。由于 treclaim,事务被中止,因此在 tbegin 和信号之间写入的任何内存都将被回滚。

对于在非 TM 或暂停模式下接收的信号,我们使用常规/非检查点堆栈指针。

在 sighandler 内部启动并在从 sighandler 返回到内核时暂停的任何事务都将被回收和丢弃。

内核使用的失败原因代码

这些在 <asm/reg.h> 中定义,并区分内核中止事务的不同原因

TM_CAUSE_RESCHED

线程已重新调度。

TM_CAUSE_TLBI

软件 TLB 无效。

TM_CAUSE_FAC_UNAV

FP/VEC/VSX 不可用陷阱。

TM_CAUSE_SYSCALL

来自活动事务的系统调用。

TM_CAUSE_SIGNAL

信号已传递。

TM_CAUSE_MISC

当前未使用。

TM_CAUSE_ALIGNMENT

对齐错误。

TM_CAUSE_EMULATE

触摸内存的模拟。

用户程序的中止处理程序可以检查这些代码作为 TEXASR[0:7]。如果设置了位 7,则表示该错误被认为是持久性的。例如,TM_CAUSE_ALIGNMENT 将是持久性的,而 TM_CAUSE_RESCHED 将不会。

GDB

GDB 和 ptrace 目前不感知 TM。如果在事务期间停止,它看起来就像事务刚刚开始(呈现检查点状态)。然后无法继续该事务,并将采用失败处理程序路径。此外,事务的第二个寄存器状态将无法访问。目前可以在使用 TM 的程序中使用 GDB,但在事务中的部分中不能明智地使用。

POWER9

POWER9 上的 TM 在存储完整的寄存器状态时存在问题。这在此提交中描述

commit 4bb3c7a0208fc13ca70598efd109901a7cd45ae7
Author: Paul Mackerras <paulus@ozlabs.org>
Date:   Wed Mar 21 21:32:01 2018 +1100
KVM: PPC: Book3S HV: Work around transactional memory bugs in POWER9

为了解决这个问题,不同的 POWER9 芯片以不同的方式启用了 TM。

在 POWER9N DD2.01 及以下版本中,TM 已禁用。即未设置 HWCAP2[PPC_FEATURE2_HTM]。

在 POWER9N DD2.1 上,TM 由固件配置为在发生 tm 暂停时始终中止事务。因此,tsuspend 将导致事务中止并回滚。内核异常也会导致事务中止并回滚,并且不会发生异常。如果用户空间构造了一个启用 TM 暂停的 sigcontext,则内核将拒绝该 sigcontext。此模式通过设置 HWCAP2[PPC_FEATURE2_HTM_NO_SUSPEND] 来向用户通告。在此模式下未设置 HWCAP2[PPC_FEATURE2_HTM]。

在 POWER9N DD2.2 及以上版本中,KVM 和 POWERVM 模拟来宾的 TM(如提交 4bb3c7a0208f 中所述),因此为来宾启用了 TM,即为来宾用户空间设置了 HWCAP2[PPC_FEATURE2_HTM]。大量使用 TM 暂停的来宾(tsuspend 或内核暂停)将导致陷入 hypervisor,因此会导致性能下降。主机用户空间已禁用 TM,即未设置 HWCAP2[PPC_FEATURE2_HTM]。(尽管如果我们将来将仿真带入主机用户空间上下文切换,我们可能会在某些时候启用它)。

POWER9C DD1.2 及以上版本仅适用于 POWERVM,因此 Linux 仅作为来宾运行。在这些系统上,TM 的模拟方式与 POWER9N DD2.2 上相同。

从 POWER8 到 POWER9 的来宾迁移将适用于 POWER9N DD2.2 和 POWER9C DD1.2。由于较早的 POWER9 处理器不支持 TM 仿真,因此不支持从 POWER8 到 POWER9 的迁移。

内核实现

h/rfid mtmsrd 怪癖

如 ISA 中定义的,rfid 有一个怪癖,它在早期异常处理中很有用。当在用户空间事务中并且我们通过一些异常进入内核时,MSR 将最终变为 TM=0 和 TS=01(即 TM 关闭但 TM 暂停)。通常,内核想要更改 MSR 中的位,并将执行 rfid 来执行此操作。在这种情况下,rfid 可以具有 SRR0 TM = 0 和 TS = 00(即 TM 关闭和非事务),并且生成的 MSR 将保留之前的 TM = 0 和 TS=01(即保持暂停状态)。这是架构中的一个怪癖,因为这通常是从 TS=01 到 TS=00(即暂停 -> 非事务)的转换,这是一种非法转换。

此怪癖在 rfid 的定义中的架构中描述,包含以下几行

if (MSR 29:31 ¬ = 0b010 | SRR1 29:31 ¬ = 0b000) then

MSR 29:31 <- SRR1 29:31

hrfid 和 mtmsrd 具有相同的怪癖。

Linux 内核在其早期异常处理中使用此怪癖。