🗒️Xv6-Lock
2023-5-16|2023-5-22
Anthony
type
status
date
slug
summary
tags
category
icon
password
本Blog基于任职于美国波特兰洲立大学的教授@hhp3在Youtube上对xv6内核的讲解视频本期讲解xv6中的Spinlocks,视频链接在下面
Spinlock概述
在XV6中,spinlock仅仅由一个变量locked控制
当locked = 0 表示空闲/解锁/释放(free/unlocked/released)
当locked = 1 表示上锁/持有/获取(locked/held/acquire)
可以在kernel/spinlock.h中找到对spinlock结构的描述
其中有用的部分就是locked, 可以从注释中看到name和cpu都是给调试使用的
Spinlock相关函数
功能描述
与spinlock有关的函数分别是acquire, release, initlock, holding。每个函数都接收指向spinlock的指针,以此操作spinlock
Acquire
一个理所当然的acquire函数也许会长成这样
但是这不可避免会导致一些问题,比如有两个进程同时检查锁的状态,并且同时获取了锁(这显然是不合理的)那么就会造成错误。
因此XV6中引入了操作AMOSWAP(Atomic swap instruction)
该指令会同时地将一个值放入内存,并且从该内存地址取出先前的值。AMOSWAP操作是不允许被打断的,因此可以认为AMOSWAP保证了只有一个进程对locked进行访问和修改,不会被其他任何进程影响。
对于上述代码,我们将一个进程获取locked的行为可以描述为:对locked保存的地址,使用AMOSWAP操作向该地址写入“1”然后获取该地址先前的值,如果先前的值是0那么代表该进程成功获取到了锁,如果先前的进程是“1”,该进程就重新执行之前的操作。
Release
对于释放锁,也是使用AMOSWAP将locked变成0即可
代码理解
Init
要初始化一个spinlock,首先获取指向它的指针,以及它的名字,将spinlock结构体的对应字段赋值,然后locked初始化为0,表示未被使用。cpu也设置成0,表示未被进程获取。
Acquire
- 其中的
while(__sync_lock_test_and_set(&lk->locked, 1) != 0)
就是先前提到的AMOSWAP操作。该函数首先获取到spinlock的locked字段,然后尝试修改其为1,该函数会返回locked字段先前的值,如果先前的值是0,就代表成功获取了锁,如果是1,就要继续循环。
- 其中的
if(holding(lk))
检查该进程是否已经持有了锁,如果是,就报错
- 其中的
lk->cpu = mycpu();
将spinlock的cpu字段指向当先cpu,表示锁由当前cpu持有
- 其中的
__sync_synchronize();
注释的描述是“告诉 C 编译器和处理器在超过该点后不要移动加载或存储,以确保临界区的内存引用严格发生在获取锁之后。 在 RISC-V 上,这会发出一条围栏指令。” 其实就是为了防止编译器做优化,把一些应该在获取锁之后执行的代码优化到获取之前去了。
Release
- 该函数首先检查进程是否拥有锁,如果不是则报错
lk->cpu = 0;
将当前锁指向的cpu重新设置成Null
__sync_lock_release(&lk->locked);
使用AMOSWAP操作将locked变成0。(虽然一条store指令又怎么可能不是原语指令呢)
__sync_synchronize();
保证了释放锁之前的所有操作都已经完成
Holding
该函数检查locked位置是否为1,并且拥有该锁的cpu是当前cpu。如果都满足就返回true
特征及注意事项
- spinlock不应该被长时间的持有。正如上文所讲的那样,acquire函数会一直执行循环等待锁的释放(非常耗资源)。因此,当进程获取了锁之后,它应该时刻想着要赶快释放掉。
- 长时间的持有锁可以通过sleep()和 wakeup()来实现
- 锁的主要作用是保护共享数据(critical section)