我正在查看自旋锁的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()
的目的是什么?原子性不能确保在任何情况下只有一个线程能够更改锁的值并且不会被中断吗?为什么我们必须显式禁用中断以防止死锁?
答案 0 :(得分:3)
原子性不能确保在任何情况下只有一个线程能够更改锁的值并且不会被中断吗?
是的,但这不是问题。该操作可确保线程之间的原子性,但是中断处理程序仍然是个问题。
中断(特别是硬件中断)是异步的,并且可以随时发生 ,这意味着在生成中断时代码可以执行任何操作。
当CPU捕获到中断时,正在运行的任何代码都将被“暂停”,并输入中断处理程序。只有在 中断处理程序完成其工作之后,其他任何事情才能照常进行。
现在,假设您的代码需要acquire()
进行自旋锁,并且该函数的定义如下:
void acquire(struct spinlock *lk) {
while(xchg(&lk->locked, 1) != 0)
;
lk->cpu = cpu;
}
如果在上述while
之后(以及release()
之前)发生中断,并且中断处理程序需要获取相同的自旋锁,那么发生的事情是它将尝试执行相同的操作事情:再次调用acquire()
,进入while
循环。但是,由于自旋锁已被保持,因此循环永远不会退出。中断处理程序将继续旋转以尝试获取永远不会释放的锁,因为根据定义,中断处理程序必须执行到其结束,然后CPU才能继续执行其他任何操作。这将导致死锁。此时,除了关闭电源并重新启动CPU外,别无选择。
这就是为什么在显示的代码中需要禁用中断的原因。