8-Coordination
Coordination
1 进程切换过程
- 一个进程出于某种原因想要进入休眠状态,比如说出让 CPU 或者等待数据,它会先获取自己的锁;
- 之后进程将自己的状态从 RUNNING 设置为 RUNNABLE;
- 之后进程调用 switch 函数,其实是调用 sched 函数在 sched 函数中再调用的 switch 函数;
- switch 函数将当前的线程切换到调度器线程;
- 调度器线程之前也调用了 switch 函数,现在恢复执行会从自己的 switch 函数返回;
- 返回之后,调度器线程会释放刚刚出让了 CPU 的进程的锁
// 需要切换的进程 |
在进程切换的最开始,进程先获取自己的锁,并且直到调用 swtch 函数时也不释放锁。而另一个线程,也就是调度器线程会在进程的线程完全停止使用自己的栈之后,再释放进程的锁。
- 如果在 swtch()前释放锁,可能有另一个 CPU 核心运行同样的进程;会造成程序崩溃
XV6 中,不允许进程在执行 switch 函数的过程中,持有任何其他的锁。
- 如果 p1 持有另一个锁 l1,当 p1 切换到 p2 时;p1 的 l1 未释放,而如果 p2 需要 l1;会造成死锁:p2 需要 l1 来进行进程切换,p1 拥有 l1,但不能进行进程切换
- 我们不能在等待锁的时候处理中断;所以定时器中断 (调用 yield,让进程出让 CPU) 不能打破死锁
2 Sleep & Wakeup
- 通过循环等待硬件设备在现在等待的时间是不可接受的;
- xv6 中通过 uart 硬件进行读取字符到控制台;当写入一个字符后写入进程 sleep,触发中断 uartintr;中断判断是否已经写入完成,如果写入完成,唤醒写入进程进行下一次写入
- sleep 和 wakeup 需要特定的参数一个 channel;sleep 需要传递一个锁
2.1 lose wakeup
- 使用下面的 sleep 和 wakeup;会造成丢失唤醒问题
- 可能在进程未 sleep 前,中断例程执行成功,wakeup 未唤醒任何进程
void broken_sleep(chan) { |
- sleep 需要传入一个保护条件的锁;调用 sleep 时,锁被当前线程持有,之后这个锁被传递给 sleep
- wakeup 必须在持有条件锁(进程的锁)时才能唤醒进程
2.2 exit
- 关闭所有打开的文件;父进程退出,子进程由 init 进程管理;将自己变为僵尸进程;进入 sched()函数
- 一个进程 exit 后,父进程如果调用了 wait,父进程会返回子进程退出值
- 扫描进程表,找到子进程为僵尸进程的进程;调用 freeproc()
static void |
- 在 Unix 中,对于每一个退出的进程,都需要有一个对应的 wait 系统调用
2.3 kill
- Unix 中的一个进程可以将另一个进程的 ID 传递给 kill 系统调用,并让另一个进程停止运行
- 它先扫描进程表单,找到目标进程。然后只是将进程的 proc 结构体中 killed 标志位设置为 1。如果进程正在 SLEEPING 状态,将其设置为 RUNNABLE。这里只是将 killed 标志位设置为 1,并没有停止进程的运行。所以 kill 系统调用本身还是很温和的。
- 而目标进程运行到内核代码中能安全停止运行的位置时,会检查自己的 killed 标志位,如果设置为 1,目标进程会自愿的执行 exit 系统调用
- 如果进程在用户空间,那么下一次它执行系统调用它就会退出,又或者目标进程正在执行用户代码,当时下一次定时器中断或者其他中断触发了,进程才会退出。所以从一个进程调用 kill,到另一个进程真正退出,中间可能有很明显的延时
- 在内核态 kill 进程,会将进程唤醒;在 sleep 循环中进行 killed 标志位的检查
- 在驱动中,我们期望文件操作不会中断;所以不会在 sleep 循环中进行 killed 标志位的检查
- init 进程的目标就是不退出,它就是在一个循环中不停的调用 wait。如果 init 进程退出了,我认为这是一个 Fatal 级别的错误,然后系统会崩溃。在 exit 函数的最开始就会有如下检查
void |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 LZY的Code生活!