为什么我们在 xchg() 之前需要 pushcli()?

Why do we need pushcli() before xchg()?

我正在查看自旋锁的 this implementation,特别是获取和释放函数:

void
acquire(struct spinlock *lk)
{
  pushcli(); // disable interrupts to avoid deadlock.
  if(holding(lk))
    panic("acquire");

  // The xchg is atomic.
  // It also serializes, so that reads after acquire are not
  // reordered before it. 
  while(xchg(&lk->locked, 1) != 0)
    ;

  lk->cpu = cpu;
  getcallerpcs(&lk, lk->pcs);
}

// Release the lock.
void
release(struct spinlock *lk)
{
  if(!holding(lk))
    panic("release");

  lk->pcs[0] = 0;
  lk->cpu = 0;

  xchg(&lk->locked, 0);

  popcli();
}

如果 xchg 函数是原子函数,pushcli()popcli() 的目的是什么?原子性不会确保只有一个线程能够在任何情况下更改锁的值并且不会被中断吗?为什么我们必须明确禁用中断以防止死锁?

Wouldn't atomicity ensure that only one thread is able to change the value of the lock at any instance and not be interrupted?

是的,但这不是问题所在。该操作确保线程之间的原子性,但中断处理程序仍然是一个问题。


中断(特别是硬件中断)是异步的,可以随时发生,这意味着代码可以在生成中断时执行任何操作。

当中断被CPU捕获时,任何运行的代码都是"suspended"并且进入中断处理程序。只有 中断处理程序完成其工作后,其他任何事情都可以正常进行。

现在,假设您的代码需要 acquire() 自旋锁,函数定义如下:

void acquire(struct spinlock *lk) {
    while(xchg(&lk->locked, 1) != 0)
        ;

    lk->cpu = cpu;
}

如果在上述 while 之后(和 release() 之前)的任何时间发生中断,并且中断处理程序需要获取相同的自旋锁,那么它会尝试做同样的事情:再次调用 acquire(),进入 while 循环。然而,由于自旋锁已经被持有,循环将永远不会退出。中断处理程序将继续旋转以尝试获取永远不会释放的锁,因为根据定义,中断处理程序必须执行到结束,然后 CPU 才能继续执行其他任何操作。这会导致死锁。此时除了关闭电源并重新启动 CPU 别无他法。

这就是为什么在您显示的代码中需要禁用中断的原因。