6.s081 xv6-lock实现
xv6-源码解析-LOCK
锁结构
xv6,中用结构体的一个int字段来判断是否上锁
//锁结构体
struct spinlock {
// 锁是否已被持有的标志位
uint locked; // Is the lock held?
// For debugging:
// 译:用于debug的一些附加信息
char *name; // Name of lock.
struct cpu *cpu; // The cpu holding the lock.
};
初始化
void
initlock(struct spinlock *lk, char *name)
{
lk->name = name;
lk->locked = 0;
lk->cpu = 0;
}
加锁动作
__sync_lock_test_and_set:原子性的设置1到指定内存地址,然后返回该内存地址被修改前的值;另外还有内存屏障的作用,防止指令的重排序
//获取锁的方法
void acquire(struct spinlock *lk){
//如果locked已经被其他线程上锁了,那么一定返回1(1表示修改前的值,此时已经被上锁了,所以是1),while继续重试
//如果locked没有被其他线程上锁,那么一定返回0,跳出while
while(__sync_lock_test_and_set(&lk->locked, 1) != 0){
}
}
关中断
如果先获取Lock1,然后触发中断,中断又获取Lock1,那么该程序会一直死锁,中断也永远不可能返回。因此再获取锁之前都要关闭中断。
如果出现先获取lock1,在获取lock2…这样需要记录中断关闭多少次,所以需要用push_off(),封装一下开关中断的方法,而不是直接intr_off();
void
push_off(void)
{
// 获取当前CPU的中断开关状态
int old = intr_get();
// 无论如何,都关闭中断
// 事实上在初次获取到锁之后,中断就已经是关闭状态了
intr_off();
// 如果是刚刚进入锁链
// 就将进入锁链之前的中断状态,也就是上面的old
// 保存在intena中
if(mycpu()->noff == 0)
mycpu()->intena = old;
// 嵌套深度+1
mycpu()->noff += 1;
}
总结
结合上面所说,实现一个锁最终由以下代码,但是获取锁的时候会持续while空转,浪费性能。
// Acquire the lock.
// Loops (spins) until the lock is acquired.
// 译:获取锁
// 在原地循环直到锁被获取
void
acquire(struct spinlock *lk)
{
// 调用push_off关闭中断,增加迭代深度
push_off(); // disable interrupts to avoid deadlock.
// 如果已经持有了要获取的锁,则内核陷入panic
// Xv6不允许可重入锁(re-entrant lock)
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)
// amoswap.w.aq(原子交换字大小的变量值:atomic swap.word.acquire)
// acquire标志保证在此操作之后的访存操作不可以被搬运到此操作之前
// 相当于隐含了内存定序(memory ordering)的语义
// 使用sync_lock_test_and_set 函数,可以原子式的完成test&set操作
// __sync_lock_test_and_set函数的第一个参数是一个指针,指向一个内存位置
// __sync_lock_test_and_set函数的第二个参数是要往上述内存区域写入的值
// 返回值是第一个参数指向的内存位置被改写之前的值
// 如果返回值是1,表示这把锁正在被其他进程持有,我们继续向其中写入1,不会影响到其他进程
// 如果返回值是0,那么说明这把锁是空闲状态,当前进程成功获取了锁,于是可以退出循环
// Test&Set指令必须是原子的,因为多个进程尝试获取锁的过程
// 本质上也是对临界区(lcoked标志)的访问,如果不是原子化的也会出错
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.
// 译:告知C编译器和处理器,不要让load/store命令随意翻越此处
// 这是为了保证对临界区内存的访问严格发生在锁被获取之后
// 在RISC-V架构上,这会生成一个fence指令
// fence指令将会保证位于这条指令前后的访存指令,都互相不越界
__sync_synchronize();
// Record info about lock acquisition for holding() and debugging.
// 译:将当前获取锁的PCU信息记录在锁结构体中,方便调试
lk->cpu = mycpu();
}