【问题标题】:TTAS coherence issue?TTAS 一致性问题?
【发布时间】:2021-06-25 12:53:27
【问题描述】:

作为本科生的一部分,我正在参加操作系统课程,我遇到了一个令人沮丧的错误,该错误仅在设置了 -O2/3 标志的情况下出现。

系统:x86
编译器:GCC
模拟器:Bochs/Qemu

我正在使用自旋锁(一种 TTAS 实现)维护关键部分。

static int
xchange(int*s)
{
    int val = LOCKED;
    /* Exchanging value at lock address with 1, returns the old value */
    asm volatile("xchg (%%eax), %%ebx" : "=b"(val) : "0"(val), "a"(s));
    return val;
}

void
TTAS(int *s)
{
    /* While lock is locked, do nothing */
    while(TRUE){
        while(*s == LOCKED){}
        
        /* If lock acquired  */
        if( xchange(s)  == UNLOCKED){
        return;
        }
    }
}

现在,当两个线程在混合了条件等待和锁定的共享变量上工作时,就会出现该错误。线程认为他们已经获得了锁,但随后的读取返回错误(旧)值。 我尝试包装锁以打印出最后一个“所有者”,但这增加的时间导致同步保持。 锁的最后和当前所有者:Thread 2 racing itself

如果我在获取锁后打印它的值。

TTAS(lock <int*>);
print(lock::val);
print(lock::val);

第一个打印'0',第二个打印'1'。
如果我将 TTAS 换成 TAS,它似乎可以工作。

void
TAS(int *s)
{
    /* While lock is locked, do nothing */
        While( xchange(s)  != UNLOCKED){}
}

我无法确定是什么导致了这种行为,希望你们中的一些人可以帮助我推理。\

编辑: 更正了 xchange 上的错误 void return

【问题讨论】:

标签: c multithreading gcc x86 bochs


【解决方案1】:

参考下面的 cmets 和 Peter Cordes 的指导,正确的解决方案是:

#define UNLOCKED 0
#define LOCKED 1
#define TRUE 1


static int
xchg(int volatile *s) {
    int val = LOCKED;

    asm("xchg %0, %1" : "+m"(*s), "+r"(val)::"memory");
    return val;
}

void
TTAS_acquire(int volatile *s) {

    while(TRUE){
        while(*s == LOCKED){}
        if(xchg(s) == UNLOCKED){
            return;
        }
    }
}


void
TTAS_release(int volatile *s) {
    asm("":::"memory");
    *s = UNLOCKED;
}

编辑: 猜猜我在这个上开枪了!提出的解决方案似乎可以解决问题,但这只是表象。我收回这个!还改了错误的返回值,永远不会为空。

编辑2: 在 Peter Cordes 指导下重写了答案,还包括释放功能参见 cmets。

【讨论】:

  • 我认为实际的问题是您在 asm 语句中缺少一个 "memory" clobber 来告诉编译器指向的内存已被修改,以及产生 "=b" 输出。 (顺便说一句,没有必要像那样对寄存器进行硬编码!一种正常的方法是asm("xchg %0, %1" : "+m"(*s), "+r"(val) );,它可以通过使内存操作数本身对编译器可见,并让它选择寻址来避免需要内存破坏器模式。)
  • 我也不认为“弱一致性*是对非易失性的一个很好的描述。更像是 no 一致性,即假设非共享。这对local 变量!强制编译器存储和重新加载val 并不会告诉它*s 已修改。您还应该在自旋循环中的*s 上使用ACCESS_ONCE,即通过volatile int *lwn.net/Articles/793253
  • 请注意 GCC -O3 输出中的 .L11: jmp .L11 无限循环以及您声称修复了该错误的此答案中的代码。 godbolt.org/z/YfTfqW6Wh(在void函数中修复了多个错误,如return。)
  • @PeterCordes 你是绝对正确的,感谢您指出我在代码中输入的错误,我将返回值更改为正确的返回值。我查看了 objdump 文件,但我仍然对这项工作感到困惑,似乎只有 int volatile *s 和 val 的区别是操作: 17: 87 02 xchg %eax,(%edx) 19: 89 44 24 0c mov %eax,0xc(%esp) 1d: 8b 44 24 0c mov 0xc(%esp),%eax VS e: 87 18 xchg %ebx,(%eax)\ 10: 89 d8 mov %ebx,%eax\在 xchg 中
  • 编译器屏障足以获取锁定,您不需要mfencexchg 已经是一个完整的障碍。锁只需要获取/释放语义,因此这对于获取锁来说已经足够强大了。但是解锁需要asm("" ::: "memory");*s = UNLOCKED;(再次使用volatile指针)。像这样:x86 spinlock using cmpxchg
猜你喜欢
  • 2014-10-13
  • 2017-02-23
  • 2016-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-09
  • 2011-10-22
相关资源
最近更新 更多