Sleeplock Example with Three Processes
我们用一个非常具体的例子,通过三个进程(A、B、C)和一个用于保护文件的 sleeplock(我们称之为 file_lock)来走一遍完整的流程。
初始状态
file_lock:locked = 0(未锁定),pid = 0file_lock.lk: sleeplock 内部的 spinlock,当前是空闲的- 等待队列: 空
- 进程 A, B, C: 都在运行,准备对同一个文件进行操作
流程开始
第 1 步: 进程 B 成功获取锁
- CPU 调度到进程 B。
- 进程 B 调用
acquiresleep(&file_lock)。 - 进入
acquiresleep:- 进程 B 获取内部的 spinlock (
acquire(&file_lock.lk)),中断被禁用。 - 它检查
while(file_lock.locked)。当前 locked 是 0,循环条件为假,不进入循环。 - 进程 B 将
file_lock.locked设置为 1,file_lock.pid设置为自己的 PID。 - 进程 B 释放内部的 spinlock (
release(&file_lock.lk)),中断恢复。
- 进程 B 获取内部的 spinlock (
acquiresleep返回。进程 B 现在持有file_lock,并开始进行一个长时间的 I/O 操作(例如,向磁盘写 1GB 数据)。
此刻状态:
file_lock:locked = 1,pid = Bfile_lock.lk: 空闲- 等待队列: 空
第 2 步: 进程 A 尝试获取锁,进入睡眠
- CPU 调度到进程 A。
- 进程 A 也调用
acquiresleep(&file_lock)。 - 进入
acquiresleep:- 进程 A 获取内部的 spinlock (
acquire(&file_lock.lk))。 - 它检查
while(file_lock.locked)。当前 locked 是 1,循环条件为真,进入循环。 - 在循环内部,调用
sleep(&file_lock, &file_lock.lk)。
- 进程 A 获取内部的 spinlock (
- 进入
sleep函数(关键步骤):- 进程 A 的状态被设置为 SLEEPING。
- 进程 A 被放入与
&file_lock这个地址关联的等待队列中。 sleep函数释放内部的 spinlock (release(&file_lock.lk))。sleep函数调用调度器,进程 A 让出 CPU,正式进入睡眠。
此刻状态:
file_lock:locked = 1,pid = B(仍然是 B 持有)file_lock.lk: 空闲 (被进程 A 的 sleep 调用释放了)- 等待队列 (
&file_lock): [进程 A]
第 3 步: 进程 C 也尝试获取锁,同样进入睡眠
- CPU 调度到进程 C。
- 进程 C 调用
acquiresleep(&file_lock)。 - 进入
acquiresleep:- 进程 C 获取内部的 spinlock (
acquire(&file_lock.lk))。注意,它可以获取成功,因为进程 A 已经释放了它。 - 它检查
while(file_lock.locked)。locked 仍然是 1,循环为真。 - 调用
sleep(&file_lock, &file_lock.lk)。
- 进程 C 获取内部的 spinlock (
- 进入
sleep函数:- 进程 C 的状态被设置为 SLEEPING。
- 进程 C 被加入到同一个等待队列。
sleep函数释放内部的 spinlock (release(&file_lock.lk))。- 进程 C 让出 CPU。
此刻状态:
file_lock:locked = 1,pid = Bfile_lock.lk: 空闲- 等待队列 (
&file_lock): [进程 A, 进程 C]
第 4 步: 进程 B 完成任务,释放锁
- 进程 B 的 I/O 操作完成,CPU 调度回进程 B。
- 进程 B 调用
releasesleep(&file_lock)。 - 进入
releasesleep:- 进程 B 获取内部的 spinlock (
acquire(&file_lock.lk))。 - 它修改元数据:
file_lock.locked = 0,file_lock.pid = 0。 - 它调用
wakeup(&file_lock)。这个函数会找到等待队列,并唤醒其中的一个进程(比如队列头的进程 A)。进程 A 的状态从 SLEEPING 变为 RUNNABLE,但它还未运行。 - 进程 B 释放内部的 spinlock (
release(&file_lock.lk))。
- 进程 B 获取内部的 spinlock (
releasesleep返回。进程 B 的工作全部完成。
此刻状态:
file_lock:locked = 0,pid = 0file_lock.lk: 空闲- 等待队列 (
&file_lock): [进程 C] (进程 A 已被移出) - 进程 A 状态: RUNNABLE
第 5 步: 进程 A 被唤醒,成功获取锁
- 调度器发现进程 A 是 RUNNABLE 的,CPU 调度到进程 A。
- 进程 A 从 sleep 函数中返回,继续执行。
- 返回
acquiresleep的while循环:- sleep 函数返回后,它会立即重新获取内部的 spinlock (
acquire(&file_lock.lk))。 - 现在它重新判断循环条件
while(file_lock.locked)。由于进程 B 已经将 locked 设置为 0,循环条件为假。 - 进程 A 跳出循环。
- 它将
file_lock.locked设置为 1,pid 设置为自己的 PID。 - 它释放内部的 spinlock (
release(&file_lock.lk))。
- sleep 函数返回后,它会立即重新获取内部的 spinlock (
acquiresleep返回。进程 A 现在成功持有file_lock。
最终状态:
file_lock:locked = 1,pid = Afile_lock.lk: 空闲- 等待队列 (
&file_lock): [进程 C] (进程 C 仍在耐心等待)
这个流程会一直持续,直到所有进程都完成它们的工作。这个例子清晰地展示了内部的 spinlock 如何像一个“接力棒”,确保任何时候只有一个进程在修改或检查 sleeplock 的状态,从而保证了整个机制的安全和正确。