xv6操作系统源码阅读之锁
程序员文章站
2022-06-19 13:26:15
...
在操作系统中,多个线程之间的竞争,多个CPU之间的竞争,多个中断过程之间的竞争都需要同步技术来保证程序操作数据的正确性。
自旋锁
xv6有两种锁,一种是spin-lock,另一种是sleep-lock,首先先看spin-lock。
// Mutual exclusion lock.
struct spinlock {
uint locked; // Is the lock held?
// For debugging:
char *name; // Name of lock.
struct cpu *cpu; // The cpu holding the lock.
uint pcs[10]; // The call stack (an array of program counters)
// that locked the lock.
};
在spinlock的结构体中可以看到一个locked的字段,这个字段为0表示未上锁,而非0表示已经上锁。spinlock通过acquire和release函数来获取和释放锁。
// The xchg is atomic.
while(xchg(&lk->locked, 1) != 0)
;
在acquire函数中调用了xchg函数,xchg指令可以swap内存中的一个word和一个寄存器中的值,之所以要使用xchg指令是因为xchg指令是原子的(由x86硬件保证其原子性)。
xchg函数中调用了xchg指令。
static inline uint
xchg(volatile uint *addr, uint newval)
{
uint result;
// The + in "+m" denotes a read-modify-write operand.
asm volatile("lock; xchgl %0, %1" :
"+m" (*addr), "=a" (result) :
"1" (newval) :
"cc");
return result;
}
对于在acquire中的调用来说,xchg函数每次都会原子地读取lk->locked的值,并且将其设置成1,这样如果lk->locked已经是1的话,while就会一直循环下去,而当lk->locked为0时,此时while就会返回并且由于其原子操作,在返回之前lk->locked已经被设置成1了,其他CPU将继续循环等待。
而在release函数中就只需要将lk->locked清零就可以了。
// Release the lock, equivalent to lk->locked = 0.
// This code can't use a C assignment, since it might
// not be atomic. A real OS would use C atomics here.
asm volatile("movl $0, %0" : "+m" (lk->locked) : );
睡眠锁
睡眠锁与自旋锁的区别在于。自旋锁是短期持有的,在加锁期间中断是关闭的,也就是此期间CPU不能接收中断,当然在加锁期间也就不会发生线程上下文切换。而睡眠锁是长期持有的,可能长达数十ms,而在这期间如果关闭中断不进行线程切换显然是低效的,而睡眠锁可以保证在加锁期间让出CPU,也就是在加锁期间可以发生上下文切换,且在上下文切换发生后可以保证锁的正确性。
上一篇: 多进程的编程