事务内存支持¶
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。如果在事务期间停止,则看起来事务刚刚开始(呈现检查点状态)。然后无法继续该事务,并将采用失败处理程序路由。此外,无法访问事务的第二个寄存器状态。GDB 当前可以用于使用 TM 的程序,但在事务内部的部分中无法合理使用。
POWER9¶
POWER9 上的 TM 在存储完整的寄存器状态方面存在问题。此提交中对此进行了描述
commit 4bb3c7a0208fc13ca70598efd109901a7cd45ae7
Author: Paul Mackerras <[email protected]>
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(如 commit 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 内核在其早期异常处理中使用了此特性。