// Mutual exclusion lock. structspinlock { uint locked; // 1 表示该锁被持有, 0 表示该锁没有被持有 // For debugging: char *name; // Name of lock. structcpu *cpu;// The cpu holding the lock. };
// Acquire the lock. // Loops (spins) until the lock is acquired. voidacquire(struct spinlock *lk) { push_off(); // disable interrupts to avoid deadlock. if(holding(lk)) panic("acquire");
// On RISC-V, sync_lock_test_and_set turns into an atomic swap: // a5 = 1 // s1 = &lk->locked // amoswap.w.aq a5, a5, (s1) while(__sync_lock_test_and_set(&lk->locked, 1) != 0) { // 空循环 } // Tell the C compiler and the processor to not move loads or stores // past this point, to ensure that the critical section's memory // references happen strictly after the lock is acquired. // On RISC-V, this emits a fence instruction. __sync_synchronize(); // Record info about lock acquisition for holding() and debugging. lk->cpu = mycpu(); }
// Release the lock. voidrelease(struct spinlock *lk) { if(!holding(lk)) panic("release"); lk->cpu = 0; // Tell the C compiler and the CPU to not move loads or stores // past this point, to ensure that all the stores in the critical // section are visible to other CPUs before the lock is released, // and that loads in the critical section occur strictly before // the lock is released. // On RISC-V, this emits a fence instruction. __sync_synchronize(); // Release the lock, equivalent to lk->locked = 0. // This code doesn't use a C assignment, since the C standard // implies that an assignment might be implemented with // multiple store instructions. // On RISC-V, sync_lock_release turns into an atomic swap: // s1 = &lk->locked // amoswap.w zero, zero, (s1) __sync_lock_release(&lk->locked); pop_off(); }
xv6 中对 spinlock 的使用
之前提到的 kernel/kalloc.c 中的 kalloc()
和 free()
使用锁的难点
哪里该使用锁?
该使用多少个锁?
当一个 CPU 操作一个变量,同时另一个 CPU 可以写它时,需要使用
lock
如果一个不变量涉及到多个内存位置的时候,需要加锁
为了效率而言,能不加锁的地方尽量不加锁
锁减少了并行性
single kernel lock(一个大内核锁)
并行性很差
有些系统调用可能会出问题
system calls such as pipe reads or wait would pose a problem
A memory consistency model (which we often just call a “memory
model”)
编译器或者 CPU 在运行程序的时候可能试图去优化代码而 “乱序执行”
但是需要遵守一些规则,这个规则被称为内存模型
运行结果不能变
sleep lock
有的时候可能需要获取一个锁很久(tens of milliseconds)
例如往磁盘上写文件
如果利用 spinlock 的话很浪费 CPU,需要等很久(空循环)
进程在持有 spinlock 的时候不能 yield
但是也不能设计成能够 yield,可能引发死锁
专门设计一种 sleeplock,允许在等待的时候 yield
允许中断,sleeplock 不能够被用于中断处理程序
允许 yield,因此在 spinlock 保护的临界区内不能使用 sleeplock
sleeplock 的数据结构如下
1 2 3 4 5 6 7 8
// Long-term locks for processes structsleeplock { uint locked; // Is the lock held? structspinlocklk;// spinlock protecting this sleep lock // For debugging: char *name; // Name of lock. int pid; // Process holding lock };