Sleeplock Example with Three Processes

我们用一个非常具体的例子,通过三个进程(A、B、C)和一个用于保护文件的 sleeplock(我们称之为 file_lock)来走一遍完整的流程。


初始状态

  • file_lock: locked = 0 (未锁定), pid = 0
  • file_lock.lk: sleeplock 内部的 spinlock,当前是空闲的
  • 等待队列: 空
  • 进程 A, B, C: 都在运行,准备对同一个文件进行操作

流程开始

第 1 步: 进程 B 成功获取锁

  1. CPU 调度到进程 B。
  2. 进程 B 调用 acquiresleep(&file_lock)
  3. 进入 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)),中断恢复。
  4. acquiresleep 返回。进程 B 现在持有 file_lock,并开始进行一个长时间的 I/O 操作(例如,向磁盘写 1GB 数据)。

此刻状态:

  • file_lock: locked = 1, pid = B
  • file_lock.lk: 空闲
  • 等待队列: 空

第 2 步: 进程 A 尝试获取锁,进入睡眠

  1. CPU 调度到进程 A。
  2. 进程 A 也调用 acquiresleep(&file_lock)
  3. 进入 acquiresleep:
    • 进程 A 获取内部的 spinlock (acquire(&file_lock.lk))。
    • 它检查 while(file_lock.locked)。当前 locked 是 1,循环条件为真,进入循环。
    • 在循环内部,调用 sleep(&file_lock, &file_lock.lk)
  4. 进入 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 也尝试获取锁,同样进入睡眠

  1. CPU 调度到进程 C。
  2. 进程 C 调用 acquiresleep(&file_lock)
  3. 进入 acquiresleep:
    • 进程 C 获取内部的 spinlock (acquire(&file_lock.lk))。注意,它可以获取成功,因为进程 A 已经释放了它。
    • 它检查 while(file_lock.locked)。locked 仍然是 1,循环为真。
    • 调用 sleep(&file_lock, &file_lock.lk)
  4. 进入 sleep 函数:
    • 进程 C 的状态被设置为 SLEEPING。
    • 进程 C 被加入到同一个等待队列。
    • sleep 函数释放内部的 spinlock (release(&file_lock.lk))。
    • 进程 C 让出 CPU。

此刻状态:

  • file_lock: locked = 1, pid = B
  • file_lock.lk: 空闲
  • 等待队列 (&file_lock): [进程 A, 进程 C]

第 4 步: 进程 B 完成任务,释放锁

  1. 进程 B 的 I/O 操作完成,CPU 调度回进程 B。
  2. 进程 B 调用 releasesleep(&file_lock)
  3. 进入 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))。
  4. releasesleep 返回。进程 B 的工作全部完成。

此刻状态:

  • file_lock: locked = 0, pid = 0
  • file_lock.lk: 空闲
  • 等待队列 (&file_lock): [进程 C] (进程 A 已被移出)
  • 进程 A 状态: RUNNABLE

第 5 步: 进程 A 被唤醒,成功获取锁

  1. 调度器发现进程 A 是 RUNNABLE 的,CPU 调度到进程 A。
  2. 进程 A 从 sleep 函数中返回,继续执行。
  3. 返回 acquiresleepwhile 循环:
    • 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))。
  4. acquiresleep 返回。进程 A 现在成功持有 file_lock

最终状态:

  • file_lock: locked = 1, pid = A
  • file_lock.lk: 空闲
  • 等待队列 (&file_lock): [进程 C] (进程 C 仍在耐心等待)

这个流程会一直持续,直到所有进程都完成它们的工作。这个例子清晰地展示了内部的 spinlock 如何像一个“接力棒”,确保任何时候只有一个进程在修改或检查 sleeplock 的状态,从而保证了整个机制的安全和正确。