mit6.s081 lecture13
Sleep & Wakeup
在实际的使用情况中 我们通过前面所提到的使用锁来保证涉及临界资源时候的不同线程的依次执行
而在其他一些场景中 也会出现不同的线程之间需要交互 或者说是单线程IO等待的情景
课上举的例子包括
假设我们有一个Pipe,并且我正在从Pipe中读数据。但是Pipe当前又没有数据,所以我需要等待一个Pipe非空的事件。
类似的,假设我在读取磁盘,我会告诉磁盘控制器请读取磁盘上的特定块。这或许要花费较长的时间,尤其当磁碟需要旋转时
(通常是毫秒级别),磁盘才能完成读取。而执行读磁盘的进程需要等待读磁盘结束的事件。
- 类似的,一个Unix进程可以调用wait函数。这个会使得调用进程等待任何一个子进程退出。所以这里父进程有意的在等待另一个进程产生的事件。
而处理这种情况 一种解法是直接通过循环/轮询实现busy-wait 直接暴力读取
但是这样的方法会导致CPU占用急剧升高
在某些情况下 假如知道资源会在什么时候到达 在这样的前提下进行sleep一会 也是一个比较通用的解法
但是还有一种情况是不知道此时的阻塞时长 而在这样的前提下 很多Unix风格的操作系统包括xv6
就用的是Sleep&Wakeup的方式
在等待资源的时候 就可以直接sleep
当对应的资源来到了(对应的事件发生了) 此时可以发送一个信号
直接唤醒就行 这其中过程所发生的的任何事件对他都是透明且无感的
换句话来说 在这个维度上 sleep后的任务是无状态的,直至被唤醒之后…
Lost Wakeup
sleep在调用的时候 会带上一把锁 这实际上是一个比较丑陋的实现
但是这样的实现是为了避免lost-wakeup的问题
即 睡着了之后 不知道哪里唤醒 不知道唤醒谁 唤醒了醒着的
而带着把锁就可以保证颗粒度 保证两者所使用的对象是同一个
课上这里用uart的write和read —— 字符渲染解释了出来
这个问题实际上是很严重的 所以就可以结合锁来原子性来达到改变当前进程的状态 达成了逻辑的一致性
which就不会导致上方的lost-wakeup
在接口层面,sleep承诺可以原子性的将进程设置成SLEEPING状态,同时释放锁。这样wakeup就不可能看到这样的场景:锁被释放了但是进程还没有进入到SLEEPING状态。所以sleep这里将释放锁和设置进程为SLEEPING状态这两个行为合并为一个原子操作。
接下来的内容都是关于 sleep&wakeup机制的一些实际使用案例
这里不赘述了~