我正在编写多线程应用程序并在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:向示例代码添加更多信息。应用 - 我正在处理新旧代码的混合,并且难以区分相关内容。此外,我知道分享这样的变量一般都是非常沮丧的。但是,这实际上是在一个锁实现中,高级代码将使用它来提供原子性,并且使用pthread或特定于平台的锁定不是一个选项。
答案 0 :(得分:2)
因为您已将类型声明为packed
,所以它会获得一个字节对齐,这意味着它必须一次读取和写入一个字节,因为SPARC不允许未对齐的加载/存储。如果希望编译器使用字加载/存储指令,则需要为其提供4字节对齐:
typdef struct pkd_struct {
uint16_t a;
uint16_t b;
} __attribute__((packed, aligned(4))) pkd_struct_t;
请注意,packed
对于此结构基本上没有意义,因此您可以将其保留。
答案 1 :(得分:-1)
在这里回答我自己的问题 - 这已经让我烦恼了太长时间,希望我能在某些时候拯救某人一些挫折感。
问题是虽然共享数据是对齐的,因为它是打包的,GCC会逐字节地读取它。
关于如何在SPARC(以及我假设的其他RISC平台上)上导致加载/存储膨胀的包装有一些讨论here,但在我的情况下它导致了竞争。