调度、睡眠与唤醒
多路复用 Multiplexing
场景:
当一个进程等待磁盘请求时, xv6 使之进入睡眠状态,
然后调度执行另一个进程。
另外,当一个进程耗尽了它在处理器上运行的时间片(10w 指令)后,
xv6 使用时钟中断强制它停止运行,这样调度器才能调度运行其他进程
- 简单地使用时钟中断处理程序来驱动上下文切换
- 可能出现多个 CPU 同时切换进程的情况
- 进程退出时必须释放其占用内存与资源,
- 但由于它本身在使用自己的资源(譬如其内核栈)
- 所以不能由该进程本身释放其占有的所有资源
sched()/scheduler
sched() 主要目的是要将进程上下文切换到 mycpu()->scheduler
- 通常一段程序代码的执行被称为 thread
- 如果在内核态执行,为 kernel thread
- 否则在用户态执行,为 user thread
- sched() 调用的条件是:
- 持有 ptable.lock
- 进程释放其他持有的锁
- 并且修改了 p->state 状态
- 调用 sched() 来调度进程
- 传递 intena 位,因为 intena 是 thread 的属性,而非 CPU 的
yield
../../study/os/xv6-public/proc.c
- yield 获取 ptable.lock
- 然后通过调用 sched() 主动释放 CPU
调度中锁循环
- 在遍历 ptable 是先获取 ptable.lock
- 如果没有可调度的进程 (RUNNABLE), 则 Round Robin 结束后释放锁
- 否则出现上下文切换 swtch
- 这时会在 forkret 中释放 ptable.lock 锁
- 后续如果进程调用
sched()
, 根据之前的推导则又持有锁
- 通常在
yield()
中获取
- 也有在
sleep()
中获取, 通过 spinlock 转换成 ptable.lock
- 如此反复,不会出现死锁
ptable.lock
k => u: scheduler (acquire) = swtch => forkret (release)
u => k: yield (acquire) => sched(swtch) => (release)
swtch
Sleep & Wakeup
sleep 和 wakeup 是一对系统调用,其工作方式如下
- sleep 设置 p->state = SLEEPING, 然后调用 sched 释放 CPU
- 让进程在任意的 chan 上休眠,称之为等待队列(wait channel)
- chan 一般就是资源的内核地址
- 用于唤醒时查找到对于 chan 上的进程, Multiplexing
- sleep 需要设置一个等待的锁 lk
- 锁交换 lk <-> ptable.lock
- 如果 lk == ptable.lock 跳过锁交换过程
- sleep 让调用进程休眠,释放所占 CPU
- wakeup(chan) 则唤醒在 chan 上休眠的所有进程
- 让他们的 sleep 调用返回
- 如果没有进程在 chan 上等待唤醒,wakeup 就什么也不做