【问题标题】:Atomicity of a read on SPARCSPARC 阅读的原子性
【发布时间】:2014-07-29 03:06:10
【问题描述】:

我正在编写一个多线程应用程序,但在 SPARC 平台上遇到了问题。最终我的问题归结为这个平台的原子性以及我如何获得这个结果。

一些伪代码有助于澄清我的问题:

// Global variable
typdef struct pkd_struct{
    uint16_t a;
    uint16_t b;
} __attribute__(packed) pkd_struct_t;

pkd_struct_t shared;

Thread 1:
swap_value() {
  pkd_struct_t prev = shared;
  printf("%d%d\n", prev.a, prev.b);
  ...
}

Thread 2:
use_value() {
  pkd_struct_t next;

  next.a = 0; next.b = 0;
  shared = next;
  printf("%d%d\n", shared.a, shared.b);
  ...
}

线程 1 和 2 正在访问共享变量“shared”。一个是设置,另一个是获取。如果线程 2 将“共享”设置为零,我希望线程 1 在设置之前或之后读取计数——因为“共享”在 4 字节边界上对齐。但是,我偶尔会看到线程 1 读取 0xFFFFFF00 形式的值。即高位 24 位是 OLD,但低位字节是 NEW。看来我得到了一个中间值。

查看反汇编,use_value 函数只是执行“ST”指令。鉴于数据是对齐的并且没有跨越单词边界,对这种行为有什么解释吗?如果 ST 确实不是以这种方式使用的原子,这是否解释了我看到的结果(仅更改了 1 个字节?!?)? x86上没有问题。

更新 1: 我找到了问题,但没有找到原因。 GCC 似乎正在生成逐字节读取共享变量的程序集(因此允许获得部分更新)。添加了评论,但我对 SPARC 组件不太满意。 %i0 是指向共享变量的指针。

xxx+0xc:   ldub      [%i0], %g1             // ld unsigned byte g1 = [i0] -- 0 padded
xxx+0x10:  ...       
xxx+0x14:  ldub      [%i0 + 0x1], %g5       // ld unsigned byte g5 = [i0+1] -- 0 padded
xxx+0x18:  sllx      %g1, 0x18, %g1         // g1 = [i0+0] left shifted by 24 
xxx+0x1c:  ldub      [%i0 + 0x2], %g4       // ld unsigned byte g4 = [i0+2] -- 0 padded
xxx+0x20:  sllx      %g5, 0x10, %g5         // g5 = [i0+1] left shifted by 16
xxx+0x24:  or        %g5, %g1, %g5          // g5 = g5 OR g1
xxx+0x28:  sllx      %g4, 0x8, %g4          // g4 = [i0+2] left shifted by 8
xxx+0x2c:  or        %g4, %g5, %g4          // g4 = g4 OR g5
xxx+0x30:  ldub      [%i0 + 0x3], %g1       // ld unsigned byte g1 = [i0+3] -- 0 padded
xxx+0x34:  or        %g1, %g4, %g1          // g1 = g4 OR g1
xxx+0x38:  ...       
xxx+0x3c:  st        %g1, [%fp + 0x7df]     // store g1 on the stack

知道为什么 GCC 会生成这样的代码吗?

更新 2:向示例代码添加更多信息。 Appologies - 我正在使用新代码和遗留代码的混合,很难区分相关的内容。另外,我理解共享这样的变量通常是非常不鼓励的。但是,这实际上是在锁定实现中,更高级别的代码将使用它来提供原子性,并且使用 pthread 或特定于平台的锁定不是一个选项。

【问题讨论】:

  • 你为什么要用%lld格式化uint32_t?这可能转换的字节数是变量中的两倍。
  • 请不要这样做。您拥有轻松编写保证工作的代码所需的工具。你为什么要搞乱你希望能以某种方式工作的代码?
  • @DavidSchwartz 这是一个锁实现。我知道这通常是非常不受欢迎的。我无法使用 pthread 锁、特定于架构的原子操作等典型工具。当前的实现依赖于对齐的共享数据和 st/ld 本身是原子的。
  • 听起来你正在尝试不可能的事情。没有便携式方法可以做到这一点,而且您似乎已经排除了所有已知的非便携式方法。充其量,您将在 CPU、编译器和您碰巧编译它的选项的组合上“碰巧工作”的代码。

标签: multithreading assembly sparc


【解决方案1】:

因为您已将类型声明为packed,所以它得到一个字节对齐,这意味着它必须一次读取和写入一个字节,因为 SPARC 不允许未对齐的加载/存储。如果您希望编译器使用字加载/存储指令,则需要给它 4 字节对齐:

typdef struct pkd_struct {
    uint16_t a;
    uint16_t b;
} __attribute__((packed, aligned(4))) pkd_struct_t;

请注意,packed 对于这个结构体本质上是没有意义的,所以你可以忽略它。

【讨论】:

  • 感谢您的解释。这比我的答案更清楚,因此将其标记为已接受的解决方案。
【解决方案2】:

在这里回答我自己的问题——这已经困扰我太久了,希望我能在某个时候为某人省去一些挫败感。

问题在于,虽然共享数据是对齐的,但因为它是打包的,GCC 会逐字节读取它。

有一些讨论 here 关于打包如何导致 SPARC(以及我认为的其他 RISC 平台...)上的加载/存储膨胀,但在我的情况下它导致了一场比赛。

【讨论】:

  • 这个答案并没有真正解决 SPARC 平台本身的操作的原子性。
猜你喜欢
  • 1970-01-01
  • 2017-08-12
  • 1970-01-01
  • 1970-01-01
  • 2011-04-26
  • 2016-04-04
  • 2018-08-16
  • 2016-07-01
  • 1970-01-01
相关资源
最近更新 更多